diff --git a/frontend/src/lib/components/AnnotationEditOverlay.svelte b/frontend/src/lib/components/AnnotationEditOverlay.svelte index 9793287f..f7ce3c95 100644 --- a/frontend/src/lib/components/AnnotationEditOverlay.svelte +++ b/frontend/src/lib/components/AnnotationEditOverlay.svelte @@ -42,6 +42,11 @@ $effect(() => { return () => ro.disconnect(); }); +// Auto-focus the SVG when the overlay mounts so arrow keys work immediately. +$effect(() => { + svgEl?.focus({ preventScroll: true }); +}); + type HandleId = 'nw' | 'ne' | 'sw' | 'se' | 'n' | 's' | 'e' | 'w'; // L-bracket arm length in pixels. Each corner shows two short lines meeting at 90°. @@ -221,24 +226,31 @@ function handleKeyDown(event: KeyboardEvent): void { }, 300); } -// Preview rect in pixel space (maps live normalized coords back to SVG pixel coordinates) +// Preview rect in pixel space (maps live normalized coords back to SVG pixel coordinates). +// Shown during pointer drag and during keyboard nudging (whenever live coords differ from stored). let previewX = $derived(((liveX - annotation.x) / annotation.width) * svgWidth); let previewY = $derived(((liveY - annotation.y) / annotation.height) * svgHeight); let previewW = $derived((liveWidth / annotation.width) * svgWidth); let previewH = $derived((liveHeight / annotation.height) * svgHeight); +let hasLiveChanges = $derived( + liveX !== annotation.x || + liveY !== annotation.y || + liveWidth !== annotation.width || + liveHeight !== annotation.height +);
{m.annotation_edit_mode_active()}
- handlePointerDown(e, 'move')} /> - {#if dragState} + {#if dragState || hasLiveChanges} { const liveRegion = document.querySelector('[aria-live="polite"]'); expect(liveRegion).not.toBeNull(); }); + + it('SVG root has tabindex="0" so it can receive keyboard focus', async () => { + render(AnnotationEditOverlay, { annotation }); + + const svg = document.querySelector('svg[role="application"]'); + expect(svg).not.toBeNull(); + expect(svg!.getAttribute('tabindex')).toBe('0'); + }); });