TypeScript error fixes: - Fix UUID mock type in chunker.test.ts - Remove invalid timestamp property from Message types in tests - Fix mockFetch type in client.test.ts - Add missing parameters property to tool definition in test Accessibility fixes (109 → 40 warnings, remaining are CSS @apply): - Add aria-labels to all toggle switches and icon-only buttons - Add tabindex="-1" to all dialog elements with role="dialog" - Add onkeydown handlers to modal backdrops for keyboard accessibility - Fix form labels: change decorative labels to spans, use fieldset/legend for groups - Convert fileInput variables to $state() for proper reactivity - Fix closure captures in ThinkingBlock and HtmlPreview with $derived() - Add role="region" to drag-and-drop zones - Restore keyboard navigation to BranchNavigator All 547 tests pass.
128 lines
3.4 KiB
Svelte
128 lines
3.4 KiB
Svelte
<script lang="ts">
|
|
/**
|
|
* ShortcutsModal - Display available keyboard shortcuts
|
|
*/
|
|
import { keyboardShortcuts, formatShortcut, type Shortcut } from '$lib/utils/keyboard';
|
|
|
|
interface Props {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
}
|
|
|
|
let { isOpen, onClose }: Props = $props();
|
|
|
|
// Get all registered shortcuts
|
|
const shortcuts = $derived(keyboardShortcuts.getShortcuts());
|
|
|
|
// Group shortcuts by category
|
|
const groupedShortcuts = $derived.by(() => {
|
|
const groups: Record<string, Shortcut[]> = {
|
|
'Navigation': [],
|
|
'Chat': [],
|
|
'General': []
|
|
};
|
|
|
|
for (const shortcut of shortcuts) {
|
|
if (['new-chat', 'search', 'toggle-sidenav'].includes(shortcut.id)) {
|
|
groups['Navigation'].push(shortcut);
|
|
} else if (['focus-input', 'send-message', 'stop-generation'].includes(shortcut.id)) {
|
|
groups['Chat'].push(shortcut);
|
|
} else {
|
|
groups['General'].push(shortcut);
|
|
}
|
|
}
|
|
|
|
return groups;
|
|
});
|
|
|
|
/**
|
|
* Handle backdrop click to close
|
|
*/
|
|
function handleBackdropClick(event: MouseEvent): void {
|
|
if (event.target === event.currentTarget) {
|
|
onClose();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle escape key to close
|
|
*/
|
|
function handleKeydown(event: KeyboardEvent): void {
|
|
if (event.key === 'Escape') {
|
|
onClose();
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<svelte:window on:keydown={handleKeydown} />
|
|
|
|
{#if isOpen}
|
|
<!-- Backdrop -->
|
|
<div
|
|
class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"
|
|
onclick={handleBackdropClick}
|
|
onkeydown={handleKeydown}
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="shortcuts-dialog-title"
|
|
tabindex="-1"
|
|
>
|
|
<!-- Dialog -->
|
|
<div class="mx-4 w-full max-w-md rounded-xl border border-theme bg-theme-primary shadow-2xl">
|
|
<!-- Header -->
|
|
<div class="flex items-center justify-between border-b border-theme px-6 py-4">
|
|
<h2 id="shortcuts-dialog-title" class="text-lg font-semibold text-theme-primary">
|
|
Keyboard Shortcuts
|
|
</h2>
|
|
<button
|
|
type="button"
|
|
onclick={onClose}
|
|
class="rounded-lg p-1.5 text-theme-muted transition-colors hover:bg-theme-secondary hover:text-theme-primary"
|
|
aria-label="Close dialog"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-5 w-5"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Content -->
|
|
<div class="max-h-[60vh] overflow-y-auto px-6 py-4">
|
|
{#each Object.entries(groupedShortcuts) as [group, items]}
|
|
{#if items.length > 0}
|
|
<div class="mb-4 last:mb-0">
|
|
<h3 class="mb-2 text-xs font-semibold uppercase tracking-wider text-theme-muted">
|
|
{group}
|
|
</h3>
|
|
<div class="space-y-2">
|
|
{#each items as shortcut}
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-sm text-theme-secondary">{shortcut.description}</span>
|
|
<kbd class="rounded bg-theme-secondary px-2 py-1 font-mono text-xs text-theme-muted">
|
|
{formatShortcut(shortcut.key, shortcut.modifiers)}
|
|
</kbd>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
{/each}
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="border-t border-theme px-6 py-3">
|
|
<p class="text-center text-xs text-theme-muted">
|
|
Press <kbd class="rounded bg-theme-secondary px-1.5 py-0.5 font-mono">Shift+?</kbd> to toggle this panel
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|