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
This commit is contained in:
@@ -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;
|
||||
</script>
|
||||
|
||||
{#if photos.length === 0}
|
||||
@@ -29,13 +44,18 @@
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
data-photo-cell
|
||||
tabindex={tabIndexFor(i)}
|
||||
draggable={isSelected(photo.id)}
|
||||
class="border-border bg-bg-muted group relative block aspect-square w-full overflow-hidden rounded-lg border {isSelected(
|
||||
photo.id
|
||||
)
|
||||
? 'ring-primary ring-2'
|
||||
: ''} {isCursor(i) && cursorVisible
|
||||
? 'outline outline-2 outline-offset-2 outline-text'
|
||||
: ''}"
|
||||
aria-pressed={isSelected(photo.id)}
|
||||
onfocus={() => onCursorFocus?.(i)}
|
||||
onclick={(e) => onSelect?.(i, e)}
|
||||
ondblclick={() => onOpen?.(i)}
|
||||
ondragstart={(e) => onDragStart?.(i, e)}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
let pending: { albumId: string; photoId: string } | null = null;
|
||||
|
||||
export const viewerReturn = {
|
||||
remember(albumId: string, photoId: string) {
|
||||
if (albumId && photoId) pending = { albumId, photoId };
|
||||
},
|
||||
consume(albumId: string): string | null {
|
||||
const p = pending;
|
||||
pending = null;
|
||||
return p && p.albumId === albumId ? p.photoId : null;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user