fix: improve settings search highlight

This commit is contained in:
Linus Rath
2026-05-06 18:58:06 +02:00
parent 2dc8537780
commit 70c1ddd48c
3 changed files with 64 additions and 25 deletions
+36 -19
View File
@@ -442,37 +442,54 @@ export default function SettingsPage() {
}, [isDesktop, mobileShowContent]);
// After clicking a search sub-result, scroll the matching setting into view
// and add a temporary highlight class. Two RAFs to wait for the tab content
// to mount and lay out before querying the DOM.
// and add a temporary highlight class. Some tabs fetch data and render
// their SettingItems only after a loading state, so retry until the element
// shows up (or we give up after ~2s).
useEffect(() => {
if (!pendingHighlight) return;
if (pendingHighlight.tab !== activeTab) return;
if (typeof window === 'undefined') return;
let cancelled = false;
let retryTimer: ReturnType<typeof setTimeout> | undefined;
let cleanupTimer: ReturnType<typeof setTimeout> | undefined;
let highlightedEl: HTMLElement | null = null;
const r1 = window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
if (cancelled) return;
const escaped = pendingHighlight.label.replace(/"/g, '\\"');
const el = document.querySelector<HTMLElement>(`[data-search-label="${escaped}"]`);
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
el.classList.add('settings-search-highlight');
highlightedEl = el;
cleanupTimer = setTimeout(() => {
el.classList.remove('settings-search-highlight');
}, 2000);
}
setPendingHighlight(null);
});
});
const escaped = pendingHighlight.label.replace(/"/g, '\\"');
const selector = `[data-search-label="${escaped}"]`;
const deadline = Date.now() + 2000;
const tryHighlight = () => {
if (cancelled) return;
const el = document.querySelector<HTMLElement>(selector);
if (!el) {
if (Date.now() < deadline) {
retryTimer = setTimeout(tryHighlight, 80);
}
return;
}
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Remove + reflow + add restarts the CSS animation if the class was
// already present (re-clicking the same sub-result).
el.classList.remove('settings-search-highlight');
void el.offsetWidth;
el.classList.add('settings-search-highlight');
highlightedEl = el;
cleanupTimer = setTimeout(() => {
el.classList.remove('settings-search-highlight');
highlightedEl = null;
}, 1800);
};
// First attempt next frame so the freshly-mounted tab content is in DOM.
const raf = window.requestAnimationFrame(tryHighlight);
// Do NOT reset pendingHighlight here — that would retrigger this effect
// and the cleanup below would strip the class right after we added it.
return () => {
cancelled = true;
window.cancelAnimationFrame(r1);
window.cancelAnimationFrame(raf);
if (retryTimer) clearTimeout(retryTimer);
if (cleanupTimer) clearTimeout(cleanupTimer);
if (highlightedEl) highlightedEl.classList.remove('settings-search-highlight');
};