const DESTINATION_SELECTOR = "[data-search-field-id]"; const INTERACTIVE_SELECTOR = [ 'input:not([type="hidden"])', "select", "textarea", "button", "a[href]", "summary", '[contenteditable="true"]', "[tabindex]", ].join(", "); type HighlightedFieldSetter = (fieldId: string | null) => void; export type SearchDestinationController = { navigate: (fieldId: string) => void; cancel: () => void; dispose: () => void; }; export type SearchDestinationOptions = { setHighlightedFieldId: HighlightedFieldSetter; initialDelayMs?: number; retryDelayMs?: number; maxRetries?: number; highlightDurationMs?: number; }; function findDestination(fieldId: string): HTMLElement | null { return ( Array.from( document.querySelectorAll(DESTINATION_SELECTOR), ).find((candidate) => candidate.dataset.searchFieldId === fieldId) ?? null ); } function isVisuallyHidden(element: HTMLElement): boolean { let current: HTMLElement | null = element; while (current) { const style = window.getComputedStyle(current); if ( style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse" ) { return true; } current = current.parentElement; } return false; } function isEnabledInteractive(element: HTMLElement): boolean { return !( element.matches('input[type="hidden"]') || element.matches(":disabled") || element.getAttribute("aria-disabled") === "true" || element.getAttribute("tabindex") === "-1" || element.closest('[hidden], [inert], [aria-hidden="true"]') || isVisuallyHidden(element) ); } function focusDestination(destination: HTMLElement): void { const interactive = Array.from( destination.querySelectorAll(INTERACTIVE_SELECTOR), ).find(isEnabledInteractive); if (interactive) { interactive.focus({ preventScroll: true }); return; } const previousTabIndex = destination.getAttribute("tabindex"); destination.tabIndex = -1; destination.focus({ preventScroll: true }); if (previousTabIndex === null) { destination.removeAttribute("tabindex"); } else { destination.setAttribute("tabindex", previousTabIndex); } } function prefersReducedMotion(): boolean { return ( typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches ); } export function createSearchDestinationController({ setHighlightedFieldId, initialDelayMs = 120, retryDelayMs = 60, maxRetries = 10, highlightDurationMs = 3000, }: SearchDestinationOptions): SearchDestinationController { let generation = 0; let destinationTimer: number | null = null; let highlightTimer: number | null = null; const clearTimers = (): void => { if (destinationTimer !== null) { window.clearTimeout(destinationTimer); destinationTimer = null; } if (highlightTimer !== null) { window.clearTimeout(highlightTimer); highlightTimer = null; } }; const invalidate = (clearHighlight: boolean): void => { generation += 1; clearTimers(); if (clearHighlight) { setHighlightedFieldId(null); } }; const navigate = (fieldId: string): void => { invalidate(true); const navigationGeneration = generation; // Publish the id immediately so nested-tab renderers can reveal a lazy // field. The full highlight duration starts only after discovery. setHighlightedFieldId(fieldId); const attemptDestination = (attempt: number): void => { destinationTimer = window.setTimeout( () => { destinationTimer = null; if (navigationGeneration !== generation) { return; } const destination = findDestination(fieldId); if (!destination) { if (attempt < maxRetries) { attemptDestination(attempt + 1); } else { setHighlightedFieldId(null); } return; } focusDestination(destination); destination.scrollIntoView?.({ behavior: prefersReducedMotion() ? "auto" : "smooth", block: "center", }); highlightTimer = window.setTimeout(() => { if (navigationGeneration === generation) { setHighlightedFieldId(null); highlightTimer = null; } }, highlightDurationMs); }, attempt === 0 ? initialDelayMs : retryDelayMs, ); }; attemptDestination(0); }; return { navigate, cancel: () => invalidate(true), dispose: () => invalidate(false), }; }