forked from MapComplete/MapComplete
		
	A11y: screen navigation improvements, see #1181
This commit is contained in:
		
							parent
							
								
									66369ef0b4
								
							
						
					
					
						commit
						af4d9bb2bf
					
				
					 25 changed files with 483 additions and 325 deletions
				
			
		|  | @ -4,11 +4,12 @@ import { Map as MlMap, SourceSpecification } from "maplibre-gl" | |||
| import { AvailableRasterLayers, RasterLayerPolygon } from "../../Models/RasterLayers" | ||||
| import { Utils } from "../../Utils" | ||||
| import { BBox } from "../../Logic/BBox" | ||||
| import { ExportableMap, MapProperties } from "../../Models/MapProperties" | ||||
| import { ExportableMap, KeyNavigationEvent, MapProperties } from "../../Models/MapProperties" | ||||
| import SvelteUIElement from "../Base/SvelteUIElement" | ||||
| import MaplibreMap from "./MaplibreMap.svelte" | ||||
| import { RasterLayerProperties } from "../../Models/RasterLayerProperties" | ||||
| import * as htmltoimage from "html-to-image" | ||||
| import { ALL } from "node:dns" | ||||
| 
 | ||||
| /** | ||||
|  * The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties` | ||||
|  | @ -40,12 +41,13 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | |||
|     readonly lastClickLocation: Store<undefined | { lon: number; lat: number }> | ||||
|     readonly minzoom: UIEventSource<number> | ||||
|     readonly maxzoom: UIEventSource<number> | ||||
| 
 | ||||
|     /** | ||||
|      * When was the last navigation by arrow keys? | ||||
|      * If set, this is a hint to use arrow compatibility | ||||
|      * Number of _seconds_ since epoch | ||||
|      * Functions that are called when one of those actions has happened | ||||
|      * @private | ||||
|      */ | ||||
|     readonly lastKeyNavigation: UIEventSource<number> = new UIEventSource<number>(undefined) | ||||
|     private _onKeyNavigation: ((event: KeyNavigationEvent) => void | boolean)[] = [] | ||||
| 
 | ||||
|     private readonly _maplibreMap: Store<MLMap> | ||||
|     /** | ||||
|      * Used for internal bookkeeping (to remove a rasterLayer when done loading) | ||||
|  | @ -132,13 +134,32 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | |||
|                 handleClick(e) | ||||
|             }) | ||||
|             map.getContainer().addEventListener("keydown", (event) => { | ||||
|                 if ( | ||||
|                     event.key === "ArrowRight" || | ||||
|                     event.key === "ArrowLeft" || | ||||
|                     event.key === "ArrowUp" || | ||||
|                     event.key === "ArrowDown" | ||||
|                 ) { | ||||
|                     this.lastKeyNavigation.setData(Date.now() / 1000) | ||||
|                 let locked: "islocked" = undefined | ||||
|                 if (!this.allowMoving.data) { | ||||
|                     locked = "islocked" | ||||
|                 } | ||||
|                 switch (event.key) { | ||||
|                     case "ArrowUp": | ||||
|                         this.pingKeycodeEvent(locked ?? "north") | ||||
|                         break | ||||
|                     case "ArrowRight": | ||||
|                         this.pingKeycodeEvent(locked ?? "east") | ||||
|                         break | ||||
|                     case "ArrowDown": | ||||
|                         this.pingKeycodeEvent(locked ?? "south") | ||||
|                         break | ||||
|                     case "ArrowLeft": | ||||
|                         this.pingKeycodeEvent(locked ?? "west") | ||||
|                         break | ||||
|                     case "+": | ||||
|                         this.pingKeycodeEvent("in") | ||||
|                         break | ||||
|                     case "=": | ||||
|                         this.pingKeycodeEvent("in") | ||||
|                         break | ||||
|                     case "-": | ||||
|                         this.pingKeycodeEvent("out") | ||||
|                         break | ||||
|                 } | ||||
|             }) | ||||
|         }) | ||||
|  | @ -154,7 +175,10 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | |||
|         }) | ||||
|         this.zoom.addCallbackAndRunD((z) => self.SetZoom(z)) | ||||
|         this.maxbounds.addCallbackAndRun((bbox) => self.setMaxBounds(bbox)) | ||||
|         this.allowMoving.addCallbackAndRun((allowMoving) => self.setAllowMoving(allowMoving)) | ||||
|         this.allowMoving.addCallbackAndRun((allowMoving) => { | ||||
|             self.setAllowMoving(allowMoving) | ||||
|             self.pingKeycodeEvent(allowMoving ? "unlocked" : "locked") | ||||
|         }) | ||||
|         this.allowRotating.addCallbackAndRunD((allowRotating) => | ||||
|             self.setAllowRotating(allowRotating) | ||||
|         ) | ||||
|  | @ -240,6 +264,13 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | |||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     public onKeyNavigationEvent(f: (event: KeyNavigationEvent) => void | boolean) { | ||||
|         this._onKeyNavigation.push(f) | ||||
|         return () => { | ||||
|             this._onKeyNavigation.splice(this._onKeyNavigation.indexOf(f), 1) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public async exportAsPng( | ||||
|         rescaleIcons: number = 1, | ||||
|         progress: UIEventSource<{ current: number; total: number }> = undefined | ||||
|  | @ -268,6 +299,24 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | |||
|         return await MapLibreAdaptor.toBlob(drawOn) | ||||
|     } | ||||
| 
 | ||||
|     private pingKeycodeEvent( | ||||
|         key: "north" | "east" | "south" | "west" | "in" | "out" | "islocked" | "locked" | "unlocked" | ||||
|     ) { | ||||
|         const event = { | ||||
|             date: new Date(), | ||||
|             key: key, | ||||
|         } | ||||
| 
 | ||||
|         for (let i = 0; i < this._onKeyNavigation.length; i++) { | ||||
|             const f = this._onKeyNavigation[i] | ||||
|             const unregister = f(event) | ||||
|             if (unregister === true) { | ||||
|                 this._onKeyNavigation.splice(i, 1) | ||||
|                 i-- | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Exports the background map and lines to PNG. | ||||
|      * Markers are _not_ rendered | ||||
|  | @ -373,7 +422,6 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | |||
| 
 | ||||
|             for (const label of labels) { | ||||
|                 if (isDisplayed(label)) { | ||||
|                     console.log("Exporting label", label) | ||||
|                     await this.drawElement(drawOn, <HTMLElement>label, rescaleIcons, pixelRatio) | ||||
|                 } | ||||
|             } | ||||
|  | @ -565,16 +613,17 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | |||
|             map.rotateTo(0, { duration: 0 }) | ||||
|             map.setPitch(0) | ||||
|             map.dragRotate.disable() | ||||
|             map.keyboard.disableRotation() | ||||
|             map.touchZoomRotate.disableRotation() | ||||
|         } else { | ||||
|             map.dragRotate.enable() | ||||
|             map.keyboard.enableRotation() | ||||
|             map.touchZoomRotate.enableRotation() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private setAllowMoving(allow: true | boolean | undefined) { | ||||
|         const map = this._maplibreMap.data | ||||
|         console.log("Setting 'allowMoving' to", allow) | ||||
|         if (!map) { | ||||
|             return | ||||
|         } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue