From 7359495a73fd3f90063e6923ffafbbcdcbcb1bd2 Mon Sep 17 00:00:00 2001 From: Iu Date: Sun, 11 Oct 2026 14:51:00 +0300 Subject: [PATCH] feat(ui): keyboard navigation and focus restore in album grid - Cursor separate from selection; arrow keys move it, Space/Shift+Arrow/Enter/Esc act - Click on empty grid space clears selection back to album properties - Focus returns to the last viewed photo after closing the viewer --- frontend/src/lib/components/PhotoGrid.svelte | 24 ++- frontend/src/lib/stores/viewer-return.ts | 12 ++ .../src/routes/(app)/albums/[id]/+page.svelte | 183 +++++++++++++++++- .../albums/[id]/photos/[photoId]/+page.svelte | 7 + .../grid-keyboard-navigation/.openspec.yaml | 2 + .../grid-keyboard-navigation/design.md | 160 +++++++++++++++ .../grid-keyboard-navigation/proposal.md | 47 +++++ .../specs/ui-workspace/spec.md | 123 ++++++++++++ .../changes/grid-keyboard-navigation/tasks.md | 35 ++++ 9 files changed, 588 insertions(+), 5 deletions(-) create mode 100644 frontend/src/lib/stores/viewer-return.ts create mode 100644 openspec/changes/grid-keyboard-navigation/.openspec.yaml create mode 100644 openspec/changes/grid-keyboard-navigation/design.md create mode 100644 openspec/changes/grid-keyboard-navigation/proposal.md create mode 100644 openspec/changes/grid-keyboard-navigation/specs/ui-workspace/spec.md create mode 100644 openspec/changes/grid-keyboard-navigation/tasks.md diff --git a/frontend/src/lib/components/PhotoGrid.svelte b/frontend/src/lib/components/PhotoGrid.svelte index 09ad607..be42372 100644 --- a/frontend/src/lib/components/PhotoGrid.svelte +++ b/frontend/src/lib/components/PhotoGrid.svelte @@ -6,16 +6,31 @@ interface Props { photos: Photo[]; selectedIds?: string[]; + cursorIndex?: number | null; + cursorVisible?: boolean; onSelect?: (index: number, event: MouseEvent) => void; onOpen?: (index: number) => void; onDragStart?: (index: number, event: DragEvent) => void; onContextMenu?: (index: number, event: MouseEvent) => void; + onCursorFocus?: (index: number) => void; } - let { photos, selectedIds = [], onSelect, onOpen, onDragStart, onContextMenu }: Props = - $props(); + let { + photos, + selectedIds = [], + cursorIndex = null, + cursorVisible = false, + onSelect, + onOpen, + onDragStart, + onContextMenu, + onCursorFocus + }: Props = $props(); const isSelected = (id: string) => selectedIds.includes(id); + const isCursor = (i: number) => cursorIndex === i; + const tabIndexFor = (i: number) => + cursorIndex == null ? (i === 0 ? 0 : -1) : isCursor(i) ? 0 : -1; {#if photos.length === 0} @@ -29,13 +44,18 @@