feat(timeline): focus follows archive, snaps to first on view load
- New selection.focusAfter(excluded) walks selection.order forward past the archived/restored set so X-ing through the timeline keeps the cursor on the next live photo instead of falling back to photo[0] via the auto-anchor effect. Wired into gridKeyNav.toggleArchive (X key) and BulkActionBar.onArchive. - Auto-focus effect on the timeline always re-anchors to photos[0] on view load (pageCount → 1), instead of preserving a stale uid from the previous filter. - PhotoGrid re-anchors focus when the previously focused uid isn't in the new photo set, so drilling into a /tags category drops the cursor on its first tile instead of carrying a stale selection from whatever view the user came from. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -113,3 +113,51 @@ export function setFocused(uid: string | null): void {
|
||||
export function setAnchor(uid: string | null): void {
|
||||
selection.anchor = uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance focus to the photo immediately after `excluded` in the current
|
||||
* order, skipping any uid that's in `excluded`. Falls back to the closest
|
||||
* non-excluded uid *before* the excluded set when the user is already at
|
||||
* the tail. Returns `null` when nothing else is left.
|
||||
*
|
||||
* Used right after a mutation that removes the focused photo from the
|
||||
* current view (archive / restore / approve / delete) — calling this
|
||||
* *before* the photo cache refetches keeps focus stable instead of the
|
||||
* effect-driven anchor falling back to photo[0].
|
||||
*/
|
||||
export function focusAfter(excluded: Iterable<string>): string | null {
|
||||
const excludedSet = excluded instanceof Set ? excluded : new Set(excluded);
|
||||
const order = selection.order;
|
||||
if (order.length === 0) {
|
||||
setFocused(null);
|
||||
return null;
|
||||
}
|
||||
// Anchor index: prefer current focus, else the first excluded uid we
|
||||
// can find (covers the case where focus was already null).
|
||||
let anchorIdx = indexOf(selection.focused);
|
||||
if (anchorIdx === -1) {
|
||||
for (let i = 0; i < order.length; i++) {
|
||||
if (excludedSet.has(order[i])) {
|
||||
anchorIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (anchorIdx === -1) return null;
|
||||
for (let i = anchorIdx + 1; i < order.length; i++) {
|
||||
if (!excludedSet.has(order[i])) {
|
||||
setFocused(order[i]);
|
||||
setAnchor(order[i]);
|
||||
return order[i];
|
||||
}
|
||||
}
|
||||
for (let i = anchorIdx - 1; i >= 0; i--) {
|
||||
if (!excludedSet.has(order[i])) {
|
||||
setFocused(order[i]);
|
||||
setAnchor(order[i]);
|
||||
return order[i];
|
||||
}
|
||||
}
|
||||
setFocused(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user