ui(timeline): auto-focus first photo on every section switch
The previous auto-focus guard was one-shot for the lifetime of the component, so switching from All Photos → Discarded (or any other filter-based section) carried over the old activePhotoId — and if it wasn't in the new view, nothing was focused at all. A new effect watches currentSection and, on any change (or fresh mount after a Duplicates/Memories detour), resets the guard and clears the stale selection so the existing auto-focus picks the first visible photo of the new view. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -207,9 +207,11 @@ export function Timeline() {
|
|||||||
const prevViewModeRef = useRef(viewMode)
|
const prevViewModeRef = useRef(viewMode)
|
||||||
|
|
||||||
// Auto-focus the first photo on initial grid load so arrow-key nav
|
// Auto-focus the first photo on initial grid load so arrow-key nav
|
||||||
// works immediately without a pre-click. One-shot — after the user
|
// works immediately without a pre-click. One-shot per section — after
|
||||||
// explicitly clears the selection (Escape), we don't re-focus, so
|
// the user explicitly clears the selection (Escape), we don't
|
||||||
// the metadata sidebar can collapse and stay collapsed.
|
// re-focus within the same section, so the metadata sidebar can
|
||||||
|
// collapse and stay collapsed. Reset by the section-change effect
|
||||||
|
// below so switching views (e.g. All → Discarded) re-focuses.
|
||||||
const didAutoFocusRef = useRef(false)
|
const didAutoFocusRef = useRef(false)
|
||||||
// visibleSequence isn't in scope yet (derived below from items). Read
|
// visibleSequence isn't in scope yet (derived below from items). Read
|
||||||
// it through a ref so this effect can focus the first *visually*
|
// it through a ref so this effect can focus the first *visually*
|
||||||
@@ -230,6 +232,23 @@ export function Timeline() {
|
|||||||
selectPhoto(firstVisible)
|
selectPhoto(firstVisible)
|
||||||
}, [viewMode, activePhotoId, photos, selectPhoto])
|
}, [viewMode, activePhotoId, photos, selectPhoto])
|
||||||
|
|
||||||
|
// On section switch (e.g. All Photos → Discarded) or fresh mount,
|
||||||
|
// reset the one-shot guard and clear the stale active photo so the
|
||||||
|
// effect above picks the first photo of the new view once its query
|
||||||
|
// resolves. The previous section's activePhotoId almost never
|
||||||
|
// belongs to the new section — carrying it over leaves the metadata
|
||||||
|
// sidebar showing something that isn't even in the visible grid.
|
||||||
|
// Ref starts null so the first mount after a Duplicates/Memories
|
||||||
|
// detour also trips this path.
|
||||||
|
const clearSelection = usePhotoStore((s) => s.clearSelection)
|
||||||
|
const prevSectionRef = useRef<string | null>(null)
|
||||||
|
useEffect(() => {
|
||||||
|
if (prevSectionRef.current === currentSection) return
|
||||||
|
prevSectionRef.current = currentSection
|
||||||
|
didAutoFocusRef.current = false
|
||||||
|
clearSelection()
|
||||||
|
}, [currentSection, clearSelection])
|
||||||
|
|
||||||
// Membership in the active heap (drives the green tint on each
|
// Membership in the active heap (drives the green tint on each
|
||||||
// thumbnail). Subscribed once at this level so we don't have hundreds
|
// thumbnail). Subscribed once at this level so we don't have hundreds
|
||||||
// of thumbnails each subscribing to the same query.
|
// of thumbnails each subscribing to the same query.
|
||||||
|
|||||||
Reference in New Issue
Block a user