feat(calendar,todo): add focus trapping to all modals

Apply shared focusTrap action to 7 app-specific modals for
improved keyboard accessibility and WCAG compliance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-24 10:06:32 +01:00
parent fea6a8e64b
commit 5b77369547
7 changed files with 32 additions and 11 deletions

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { eventTagsStore } from '$lib/stores/event-tags.svelte';
import { Plus, X, Check, Pencil, Trash, MagnifyingGlass } from '@manacore/shared-icons';
import { TagColorPicker } from '@manacore/shared-ui';
import { TagColorPicker, focusTrap } from '@manacore/shared-ui';
import type { EventTag } from '@calendar/shared';
interface Props {
@ -153,7 +153,7 @@
<div class="modal-backdrop" onclick={onClose}></div>
<!-- Modal -->
<div class="tag-modal" role="dialog" aria-modal="true" aria-label="Tags">
<div class="tag-modal" role="dialog" aria-modal="true" aria-label="Tags" use:focusTrap>
<!-- Header -->
<div class="modal-header">
<h2 class="modal-title">Tags</h2>

View file

@ -5,7 +5,7 @@
import EventForm from './EventForm.svelte';
import RecurrenceEditDialog from './RecurrenceEditDialog.svelte';
import ReminderSelector from './ReminderSelector.svelte';
import { TagBadge, toastStore as toast } from '@manacore/shared-ui';
import { TagBadge, toastStore as toast, focusTrap } from '@manacore/shared-ui';
import type { CalendarEvent, UpdateEventInput, Reminder } from '@calendar/shared';
import { describeRecurrence, parseRRule } from '@calendar/shared';
import * as api from '$lib/api/events';
@ -189,7 +189,13 @@
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
<div class="modal-backdrop" onclick={handleBackdropClick} role="presentation">
<div class="modal-container" role="dialog" aria-modal="true" aria-labelledby="modal-title">
<div
class="modal-container"
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
use:focusTrap
>
{#if loading}
<div aria-live="polite" aria-label="Laden...">
<EventDetailSkeleton />

View file

@ -15,6 +15,7 @@
type FilterDropdownOption,
} from '@manacore/shared-ui';
import { X } from '@manacore/shared-icons';
import { focusTrap } from '@manacore/shared-ui';
import type { CalendarViewType, Calendar } from '@calendar/shared';
interface Props {
@ -208,7 +209,13 @@
<div class="modal-backdrop" onclick={onClose}></div>
<!-- Modal -->
<div class="settings-modal" role="dialog" aria-modal="true" aria-label="Einstellungen">
<div
class="settings-modal"
role="dialog"
aria-modal="true"
aria-label="Einstellungen"
use:focusTrap
>
<!-- Header -->
<div class="modal-header">
<h2 class="modal-title">Einstellungen</h2>

View file

@ -2,7 +2,7 @@
import { todosStore } from '$lib/stores/todos.svelte';
import type { Task, UpdateTaskInput, TaskPriority } from '$lib/api/todos';
import { PRIORITY_LABELS, PRIORITY_COLORS } from '$lib/api/todos';
import { toastStore as toast } from '@manacore/shared-ui';
import { toastStore as toast, focusTrap } from '@manacore/shared-ui';
import TodoCheckbox from './TodoCheckbox.svelte';
import PriorityBadge from './PriorityBadge.svelte';
import {
@ -177,7 +177,7 @@
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div class="modal-backdrop" onclick={handleBackdropClick} role="presentation">
<div class="modal" role="dialog" aria-labelledby="modal-title" aria-modal="true">
<div class="modal" role="dialog" aria-labelledby="modal-title" aria-modal="true" use:focusTrap>
<!-- Header -->
<div class="modal-header">
<div class="header-left">

View file

@ -1,6 +1,7 @@
<script lang="ts">
import { voiceRecordingStore } from '$lib/stores/voice-recording.svelte';
import { fade, scale } from 'svelte/transition';
import { focusTrap } from '@manacore/shared-ui';
interface Props {
/** Called when recording completes with transcription */
@ -68,6 +69,7 @@
role="dialog"
aria-modal="true"
aria-label="Sprachaufnahme"
use:focusTrap
>
{#if isRequesting}
<!-- Requesting permission state -->

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { labelsStore } from '$lib/stores/labels.svelte';
import { Plus, X, Check, Pencil, Trash, MagnifyingGlass } from '@manacore/shared-icons';
import { TagColorPicker } from '@manacore/shared-ui';
import { TagColorPicker, focusTrap } from '@manacore/shared-ui';
import type { Label } from '$lib/api/labels';
interface Props {
@ -148,7 +148,7 @@
<div class="modal-backdrop" onclick={onClose}></div>
<!-- Modal -->
<div class="tag-modal" role="dialog" aria-modal="true" aria-label="Tags">
<div class="tag-modal" role="dialog" aria-modal="true" aria-label="Tags" use:focusTrap>
<!-- Header -->
<div class="modal-header">
<h2 class="modal-title">Tags</h2>

View file

@ -20,7 +20,7 @@
FunRatingPicker,
TagSelector,
} from './form';
import { ContactSelector } from '@manacore/shared-ui';
import { ContactSelector, focusTrap } from '@manacore/shared-ui';
interface Props {
task: Task;
@ -168,7 +168,13 @@
<svelte:window onkeydown={handleKeydown} />
{#if open}
<div class="modal-backdrop" onclick={handleBackdropClick} role="dialog" aria-modal="true">
<div
class="modal-backdrop"
onclick={handleBackdropClick}
role="dialog"
aria-modal="true"
use:focusTrap
>
<div class="modal-container">
<!-- Header -->
<div class="modal-header">