A11y: improve documentation of hotkeys, keyboard navigation acts more like an aria-grid

This commit is contained in:
Pieter Vander Vennet 2023-12-22 18:50:22 +01:00
parent 6da72b80ef
commit c6f738609f
7 changed files with 85 additions and 43 deletions

View file

@ -477,6 +477,7 @@
"selectMapnik": "Set the background layer to OpenStreetMap-carto",
"selectOsmbasedmap": "Set the background layer to on OpenStreetMap-based map (or disable the background raster layer)",
"selectSearch": "Select the search bar to search locations",
"shakePhone": "Shaking your phone",
"title": "Hotkeys"
},
"image": {

View file

@ -1431,6 +1431,11 @@ video {
row-gap: 0.25rem;
}
.gap-x-4 {
-webkit-column-gap: 1rem;
column-gap: 1rem;
}
.gap-x-0\.5 {
-webkit-column-gap: 0.125rem;
column-gap: 0.125rem;
@ -1690,6 +1695,11 @@ video {
border-color: rgb(219 234 254 / var(--tw-border-opacity));
}
.border-red-500 {
--tw-border-opacity: 1;
border-color: rgb(239 68 68 / var(--tw-border-opacity));
}
.border-gray-300 {
--tw-border-opacity: 1;
border-color: rgb(209 213 219 / var(--tw-border-opacity));

View file

@ -83,7 +83,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
readonly osmConnection: OsmConnection
readonly selectedElement: UIEventSource<Feature>
readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>
readonly mapProperties: MapProperties & ExportableMap
readonly mapProperties: MapLibreAdaptor & MapProperties & ExportableMap
readonly osmObjectDownloader: OsmObjectDownloader
readonly dataIsLoading: Store<boolean>

View file

@ -10,17 +10,13 @@ import { FixedUiElement } from "./FixedUiElement"
import Translations from "../i18n/Translations"
export default class Hotkeys {
private static readonly _docs: UIEventSource<
public static readonly _docs: UIEventSource<
{
key: { ctrl?: string; shift?: string; alt?: string; nomod?: string; onUp?: boolean }
documentation: string | Translation
alsoTriggeredBy: Translation[]
}[]
> = new UIEventSource<
{
key: { ctrl?: string; shift?: string; alt?: string; nomod?: string; onUp?: boolean }
documentation: string | Translation
}[]
>([])
> = new UIEventSource([])
/**
* Register a hotkey
@ -48,7 +44,7 @@ export default class Hotkeys {
},
documentation: string | Translation,
action: () => void | false,
alsoTriggeredOn?: Translation[]
alsoTriggeredBy?: Translation[]
) {
const type = key["onUp"] ? "keyup" : "keypress"
let keycode: string = key["ctrl"] ?? key["shift"] ?? key["alt"] ?? key["nomod"]
@ -59,7 +55,7 @@ export default class Hotkeys {
}
}
this._docs.data.push({ key, documentation })
this._docs.data.push({ key, documentation, alsoTriggeredBy })
this._docs.ping()
if (Utils.runningFromConsole) {
return
@ -109,37 +105,56 @@ export default class Hotkeys {
}
static generateDocumentation(): BaseUIElement {
let byKey: [string, string | Translation][] = Hotkeys._docs.data
.map(({ key, documentation }) => {
const modifiers = Object.keys(key).filter((k) => k !== "nomod" && k !== "onUp")
let keycode: string = key["ctrl"] ?? key["shift"] ?? key["alt"] ?? key["nomod"]
if (keycode.length == 1) {
keycode = keycode.toUpperCase()
return new VariableUiElement(
Hotkeys._docs.mapD((docs) => {
let byKey: [string, string | Translation, Translation[] | undefined][] = docs
.map(({ key, documentation, alsoTriggeredBy }) => {
const modifiers = Object.keys(key).filter(
(k) => k !== "nomod" && k !== "onUp"
)
let keycode: string =
key["ctrl"] ?? key["shift"] ?? key["alt"] ?? key["nomod"]
if (keycode.length == 1) {
keycode = keycode.toUpperCase()
}
if (keycode === " ") {
keycode = "Spacebar"
}
modifiers.push(keycode)
return <[string, string | Translation, Translation[] | undefined]>[
modifiers.join("+"),
documentation,
alsoTriggeredBy,
]
})
.sort()
byKey = Utils.NoNull(byKey)
for (let i = byKey.length - 1; i > 0; i--) {
if (byKey[i - 1][0] === byKey[i][0]) {
byKey.splice(i, 1)
}
}
if (keycode === " ") {
keycode = "Spacebar"
}
modifiers.push(keycode)
return <[string, string | Translation]>[modifiers.join("+"), documentation]
const t = Translations.t.hotkeyDocumentation
return new Combine([
new Title(t.title, 1),
t.intro,
new Table(
[t.key, t.action],
byKey.map(([key, doc, alsoTriggeredBy]) => {
let keyEl: BaseUIElement = new FixedUiElement(key).SetClass(
"literal-code w-fit h-fit"
)
if (alsoTriggeredBy?.length > 0) {
keyEl = new Combine([keyEl, ...alsoTriggeredBy]).SetClass(
"flex gap-x-4 items-center"
)
}
return [keyEl, doc]
})
),
])
})
.sort()
byKey = Utils.NoNull(byKey)
for (let i = byKey.length - 1; i > 0; i--) {
if (byKey[i - 1][0] === byKey[i][0]) {
byKey.splice(i, 1)
}
}
const t = Translations.t.hotkeyDocumentation
return new Combine([
new Title(t.title, 1),
t.intro,
new Table(
[t.key, t.action],
byKey.map(([key, doc]) => {
return [new FixedUiElement(key).SetClass("literal-code"), doc]
})
),
])
)
}
static generateDocumentationDynamic(): BaseUIElement {

View file

@ -44,7 +44,10 @@
Translations.t.hotkeyDocumentation.queryCurrentLocation,
() => {
displayLocation()
}
},
[
Translations.t.hotkeyDocumentation.shakePhone
]
)
Motion.singleton.startListening()

View file

@ -511,7 +511,19 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
await Utils.waitFor(250)
}
}
public installCustomKeyboardHandler(viewport: Store<HTMLDivElement>) {
viewport.mapD(
(viewport) => {
const map = this._maplibreMap.data
if (!map) {
return
}
const oldKeyboard = map.keyboard
oldKeyboard._panStep = viewport.getBoundingClientRect().width
},
[this._maplibreMap]
)
}
private removeCurrentLayer(map: MLMap): void {
if (this._currentRasterLayer) {
// hide the previous layer

View file

@ -66,6 +66,7 @@
import FilterPanel from "./BigComponents/FilterPanel.svelte"
import PrivacyPolicy from "./BigComponents/PrivacyPolicy.svelte"
import { BBox } from "../Logic/BBox"
import { MapLibreAdaptor } from "./Map/MapLibreAdaptor.js"
export let state: ThemeViewState
let layout = state.layout
@ -100,7 +101,7 @@
let visualFeedback = state.visualFeedback
let viewport: UIEventSource<HTMLDivElement> = new UIEventSource<HTMLDivElement>(undefined)
let mapproperties: MapProperties = state.mapProperties
state.mapProperties.installCustomKeyboardHandler(viewport)
function updateViewport() {
const rect = viewport.data?.getBoundingClientRect()
if (!rect) {
@ -159,7 +160,7 @@
<div
class="absolute top-0 left-0 flex h-screen w-screen items-center justify-center overflow-hidden pointer-events-none"
>
<div bind:this={$viewport} style="border: 2px solid #ff000044; width: 300px; height: 300px" />
<div bind:this={$viewport} class:border={$visualFeedback} style="border: 2px solid #ff000044; width: 300px; height: 300px" />
</div>
{/if}