A11y: screen navigation improvements, see #1181

This commit is contained in:
Pieter Vander Vennet 2023-12-15 01:46:01 +01:00
parent 66369ef0b4
commit af4d9bb2bf
25 changed files with 483 additions and 325 deletions

View file

@ -11,12 +11,6 @@
export let extraClasses = "p-4 md:p-6";
let mainContent: HTMLElement;
onMount(() => {
requestAnimationFrame(() => {
Utils.focusOnFocusableChild(mainContent);
});
});
</script>
@ -31,7 +25,7 @@
use:trapFocus
style="z-index: 21"
>
<div bind:this={mainContent} class="content normal-background" on:click|stopPropagation={() => {}}>
<div class="content normal-background" on:click|stopPropagation={() => {}}>
<div class="h-full rounded-xl">
<slot />
</div>

View file

@ -22,16 +22,13 @@ export default class Hotkeys {
}[]
>([])
private static textElementSelected(event: KeyboardEvent): boolean {
if (event.ctrlKey || event.altKey) {
// This is an event with a modifier-key, lets not ignore it
return false
}
if (event.key === "Escape") {
return false // Another not-printable character that should not be ignored
}
return ["input", "textarea"].includes(document?.activeElement?.tagName?.toLowerCase())
}
/**
* Register a hotkey
* @param key
* @param documentation
* @param action the function to run. It might return 'false', indicating that it didn't do anything and gives control back to the default flow
* @constructor
*/
public static RegisterHotkey(
key: (
| {
@ -50,7 +47,7 @@ export default class Hotkeys {
onUp?: boolean
},
documentation: string | Translation,
action: () => void
action: () => void | false
) {
const type = key["onUp"] ? "keyup" : "keypress"
let keycode: string = key["ctrl"] ?? key["shift"] ?? key["alt"] ?? key["nomod"]
@ -69,8 +66,9 @@ export default class Hotkeys {
if (key["ctrl"] !== undefined) {
document.addEventListener("keydown", function (event) {
if (event.ctrlKey && event.key === keycode) {
action()
event.preventDefault()
if (action() !== false) {
event.preventDefault()
}
}
})
} else if (key["shift"] !== undefined) {
@ -80,15 +78,17 @@ export default class Hotkeys {
return
}
if (event.shiftKey && event.key === keycode) {
action()
event.preventDefault()
if (action() !== false) {
event.preventDefault()
}
}
})
} else if (key["alt"] !== undefined) {
document.addEventListener(type, function (event) {
if (event.altKey && event.key === keycode) {
action()
event.preventDefault()
if (action() !== false) {
event.preventDefault()
}
}
})
} else if (key["nomod"] !== undefined) {
@ -98,8 +98,10 @@ export default class Hotkeys {
return
}
if (event.key === keycode) {
action()
event.preventDefault()
const result = action()
if (result !== false) {
event.preventDefault()
}
}
})
}
@ -113,6 +115,9 @@ export default class Hotkeys {
if (keycode.length == 1) {
keycode = keycode.toUpperCase()
}
if (keycode === " ") {
keycode = "Spacebar"
}
modifiers.push(keycode)
return <[string, string | Translation]>[modifiers.join("+"), documentation]
})
@ -139,4 +144,15 @@ export default class Hotkeys {
static generateDocumentationDynamic(): BaseUIElement {
return new VariableUiElement(Hotkeys._docs.map((_) => Hotkeys.generateDocumentation()))
}
private static textElementSelected(event: KeyboardEvent): boolean {
if (event.ctrlKey || event.altKey) {
// This is an event with a modifier-key, lets not ignore it
return false
}
if (event.key === "Escape") {
return false // Another not-printable character that should not be ignored
}
return ["input", "textarea"].includes(document?.activeElement?.tagName?.toLowerCase())
}
}

View file

@ -1,10 +1,10 @@
<script lang="ts">
import Loading from "./Loading.svelte"
import type { OsmServiceState } from "../../Logic/Osm/OsmConnection"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import { Translation } from "../i18n/Translation"
import Translations from "../i18n/Translations"
import Tr from "./Tr.svelte"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"
import Invalid from "../../assets/svg/Invalid.svelte"
@ -35,10 +35,12 @@
<Loading />
</slot>
{:else if $loadingStatus === "error"}
<div class="alert max-w-64 flex items-center">
<Invalid class="m-2 h-8 w-8 shrink-0" />
<Tr t={offlineModes[$apiState]} />
</div>
<slot name="error">
<div class="alert max-w-64 flex items-center">
<Invalid class="m-2 h-8 w-8 shrink-0" />
<Tr t={offlineModes[$apiState]} />
</div>
</slot>
{:else if $loadingStatus === "logged-in"}
<slot />
{:else if $loadingStatus === "not-attempted"}

View file

@ -1,38 +1,37 @@
<script lang="ts">
import { createEventDispatcher, onMount } from "svelte";
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
import { Utils } from "../../Utils";
import { trapFocus } from 'trap-focus-svelte'
import { createEventDispatcher } from "svelte"
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import { trapFocus } from "trap-focus-svelte"
import { Utils } from "../../Utils"
/**
* The slotted element will be shown on the right side
*/
const dispatch = createEventDispatcher<{ close }>();
let mainContent: HTMLElement;
const dispatch = createEventDispatcher<{ close }>()
let mainContent: HTMLElement
onMount(() => {
window.setTimeout(
() => Utils.focusOnFocusableChild(mainContent), 250
);
});
</script>
<div
autofocus
bind:this={mainContent}
use:trapFocus
class="absolute top-0 right-0 h-screen w-full overflow-y-auto drop-shadow-2xl md:w-6/12 lg:w-5/12 xl:w-4/12"
class="absolute top-0 right-0 h-screen w-full overflow-y-auto drop-shadow-2xl md:w-6/12 lg:w-5/12 xl:w-4/12 normal-background flex flex-col"
role="dialog"
tabindex="-1"
aria-modal="true"
style="max-width: 100vw; max-height: 100vh"
use:trapFocus
>
<div class="normal-background m-0 flex flex-col">
<slot name="close-button">
<button
class="absolute right-10 top-10 h-8 w-8 cursor-pointer"
on:click={() => dispatch("close")}
>
<XCircleIcon />
</button>
</slot>
<slot />
<slot name="close-button">
<button
class="absolute right-10 top-10 h-8 w-8 cursor-pointer"
on:click={() => dispatch("close")}
>
<XCircleIcon />
</button>
</slot>
<div role="document" >
<slot />
</div>
</div>

View file

@ -30,7 +30,7 @@
}
</script>
<div class="tabbedgroup flex h-full w-full focusable">
<div class="tabbedgroup flex h-full w-full">
<TabGroup
class="flex h-full w-full flex-col"
defaultIndex={1}