mirror of
https://github.com/immich-app/immich.git
synced 2026-01-04 17:17:35 -08:00
* feat: improve focus * feat: keyboard nav * feat: improve focus * typo * test * fix test * lint * bad merge * lint * inadvertent * lint * fix: flappy e2e test * bad merge and fix tests * use modulus in loop * tests * react to modal dialog refactor * regression due to deferLayout * Review comments * Re-use change-date instead of new component * bad merge * Review comments * rework moveFocus * lint * Fix outline * use Date * Finish up removing/reducing date parsing * lint * title * strings * Rework dates, rework earlier/later algorithm * bad merge * fix tests * Fix race in scroll comp * consolidate scroll methods * Review comments * console.log * Edge cases in scroll compensation * edge case, optimizations * review comments * lint * lint * More edge cases * lint --------- Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com> Co-authored-by: Alex <alex.tran1502@gmail.com>
55 lines
1.8 KiB
TypeScript
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;
|
|
}
|
|
|
|
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);
|
|
};
|