Files
immich/web/src/lib/utils/focus-util.ts
midzelis 168414d1ab feat: web - view transitions from timeline to viewer, next/prev
feat: web - view transitions from timeline to viewer, next/prev

feat: web - swipe feedback - show image while swiping/dragging left/right

feat: web - swipe feedback - show image while swiping/dragging left/right

tweak animation - no crossfade by default

refactor(web): replace ViewTransitionManager event-driven API with phase-based callbacks

Change-Id: Ia52f300a08a725062acc19574b10593e6a6a6964

fix(web): graceful degradation for ViewTransitionManager, rename AssetViewerFree to AssetViewerReady, extract onClick handler

Change-Id: I4ad85d43e9922742910748a6487cd41f6a6a6964

Change-Id: Ie9c55914b0e87635e0d9e5889ca0ec3d6a6a6964

Change-Id: I0a37b417ee4c247dcc93d442c976eede6a6a6964
2026-03-17 12:23:59 -04:00

55 lines
1.8 KiB
TypeScript

import { focusable, isTabbable, tabbable, type CheckOptions, type TabbableOptions } from 'tabbable';
type TabbableOpts = TabbableOptions & CheckOptions;
let defaultOpts: TabbableOpts = {
includeContainer: false,
};
export const setDefaultTabbleOptions = (options: TabbableOpts) => {
defaultOpts = options;
};
export const getTabbable = (container: Element, includeContainer: boolean = false) =>
tabbable(container, { ...defaultOpts, includeContainer });
export const moveFocus = (
selector: (element: HTMLElement | SVGElement) => boolean,
direction: 'previous' | 'next',
): void => {
const focusableElements = focusable(document.body, { includeContainer: true });
if (focusableElements.length === 0) {
return;
}
debugger;
const currentElement = document.activeElement as HTMLElement | null;
const currentIndex = currentElement ? focusableElements.indexOf(currentElement) : -1;
// If no element is focused, focus the first matching element or the first focusable element
if (currentIndex === -1) {
const firstMatchingElement = focusableElements.find((element) => selector(element));
if (firstMatchingElement) {
firstMatchingElement.focus();
} else if (focusableElements[0]) {
focusableElements[0].focus();
}
return;
}
// Calculate the step direction
const step = direction === 'next' ? 1 : -1;
const totalElements = focusableElements.length;
// Search for the next focusable element that matches the selector
let nextIndex = currentIndex;
do {
nextIndex = (nextIndex + step + totalElements) % totalElements;
const candidateElement = focusableElements[nextIndex];
if (isTabbable(candidateElement) && selector(candidateElement)) {
candidateElement.focus();
break;
}
} while (nextIndex !== currentIndex);
};