A11y: improve documentation of hotkeys, keyboard navigation acts more like an aria-grid
This commit is contained in:
parent
6da72b80ef
commit
c6f738609f
7 changed files with 85 additions and 43 deletions
|
@ -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": {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -44,7 +44,10 @@
|
|||
Translations.t.hotkeyDocumentation.queryCurrentLocation,
|
||||
() => {
|
||||
displayLocation()
|
||||
}
|
||||
},
|
||||
[
|
||||
Translations.t.hotkeyDocumentation.shakePhone
|
||||
]
|
||||
)
|
||||
|
||||
Motion.singleton.startListening()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
Loading…
Reference in a new issue