mirror of
https://github.com/immich-app/immich.git
synced 2025-11-23 23:00:42 +09:00
feat(web, a11y): focus management for modals and popups (#8298)
* feat(web, a11y): focus management for modals and popups * feat: hide asset options dropdown on escape key
This commit is contained in:
62
web/src/lib/components/shared-components/focus-trap.svelte
Normal file
62
web/src/lib/components/shared-components/focus-trap.svelte
Normal file
@@ -0,0 +1,62 @@
|
||||
<script lang="ts">
|
||||
import { shortcuts } from '$lib/utils/shortcut';
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
|
||||
let container: HTMLElement;
|
||||
let triggerElement: HTMLElement;
|
||||
|
||||
onMount(() => {
|
||||
triggerElement = document.activeElement as HTMLElement;
|
||||
const focusableElements = getFocusableElements();
|
||||
focusableElements[0]?.focus();
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
triggerElement?.focus();
|
||||
});
|
||||
|
||||
const getFocusableElements = () => {
|
||||
return Array.from(
|
||||
container.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'),
|
||||
) as HTMLElement[];
|
||||
};
|
||||
|
||||
const trapFocus = (direction: 'forward' | 'backward', event: KeyboardEvent) => {
|
||||
const focusableElements = getFocusableElements();
|
||||
const elementCount = focusableElements.length;
|
||||
const firstElement = focusableElements[0];
|
||||
const lastElement = focusableElements.at(elementCount - 1);
|
||||
|
||||
if (document.activeElement === lastElement && direction === 'forward') {
|
||||
event.preventDefault();
|
||||
firstElement?.focus();
|
||||
} else if (document.activeElement === firstElement && direction === 'backward') {
|
||||
event.preventDefault();
|
||||
lastElement?.focus();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={container}
|
||||
use:shortcuts={[
|
||||
{
|
||||
ignoreInputFields: false,
|
||||
shortcut: { key: 'Tab' },
|
||||
onShortcut: (event) => {
|
||||
trapFocus('forward', event);
|
||||
},
|
||||
preventDefault: false,
|
||||
},
|
||||
{
|
||||
ignoreInputFields: false,
|
||||
shortcut: { key: 'Tab', shift: true },
|
||||
onShortcut: (event) => {
|
||||
trapFocus('backward', event);
|
||||
},
|
||||
preventDefault: false,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
Reference in New Issue
Block a user