forked from MapComplete/MapComplete
		
	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,10 +105,15 @@ 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"] | ||||
|         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() | ||||
|                         } | ||||
|  | @ -120,7 +121,11 @@ export default class Hotkeys { | |||
|                             keycode = "Spacebar" | ||||
|                         } | ||||
|                         modifiers.push(keycode) | ||||
|                 return <[string, string | Translation]>[modifiers.join("+"), documentation] | ||||
|                         return <[string, string | Translation, Translation[] | undefined]>[ | ||||
|                             modifiers.join("+"), | ||||
|                             documentation, | ||||
|                             alsoTriggeredBy, | ||||
|                         ] | ||||
|                     }) | ||||
|                     .sort() | ||||
|                 byKey = Utils.NoNull(byKey) | ||||
|  | @ -135,11 +140,21 @@ export default class Hotkeys { | |||
|                     t.intro, | ||||
|                     new Table( | ||||
|                         [t.key, t.action], | ||||
|                 byKey.map(([key, doc]) => { | ||||
|                     return [new FixedUiElement(key).SetClass("literal-code"), doc] | ||||
|                         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] | ||||
|                         }) | ||||
|                     ), | ||||
|                 ]) | ||||
|             }) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     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…
	
	Add table
		Add a link
		
	
		Reference in a new issue