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
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue