Internalexport function Root() {
const [isShortcutsPanelOpen, setIsShortcutsPanelOpen] = useState(false);
const navigate = useNavigate();
const location = useLocation();
const { toggleDebug, setDebugEnabled, openDebugMenu } = useDebugActions();
const handleToggleShortcutsPanel = useCallback(() => {
setIsShortcutsPanelOpen((prev) => !prev);
}, []);
// Handles navigation shortcuts
const handleNavigationShortcut = useCallback(
(action: string) => {
// Map shortcut actions to routes
const routes: Record<string, string> = {
"navigate:home": "/",
"navigate:import": "/import",
"navigate:review": "/review",
"navigate:sync": "/sync",
"navigate:statistics": "/statistics",
"navigate:settings": "/settings",
};
if (routes[action]) {
navigate({ to: routes[action] });
}
},
[navigate],
);
// Handles context-aware save based on current route
const handleContextSave = useCallback(() => {
// Save sync config when on sync page
if (location.pathname === "/sync") {
const syncConfig = getSyncConfig();
if (syncConfig) {
saveSyncConfig(syncConfig);
toast.success("Sync configuration saved");
}
} else if (location.pathname === "/settings") {
// Save match config when on settings page
const matchConfig = getMatchConfig();
if (matchConfig) {
saveMatchConfig(matchConfig);
toast.success("Match configuration saved");
}
}
}, [location.pathname]);
// Handles search focus with data attribute selector
const handleSearchFocus = useCallback(() => {
// Try to find search input with data attribute first (for page-specific search)
let searchInput = document.querySelector(
"[data-search-input]",
) as HTMLInputElement;
// If not found with general selector, try the specific settings search input
if (!searchInput && location.pathname === "/settings") {
searchInput = document.querySelector(
"[data-search-input='settings']",
) as HTMLInputElement;
}
if (searchInput) {
searchInput.focus();
searchInput.select();
}
}, [location.pathname]);
// Handles modal close - scoped to shortcuts panel only when open
const handleCloseModal = useCallback(() => {
// Only close shortcuts panel with Escape when it's the active dialog
// This prevents interfering with nested Radix modals or page-level Escape handling
if (isShortcutsPanelOpen) {
setIsShortcutsPanelOpen(false);
}
// Other modal closing is handled by their respective components or Radix Dialog
}, [isShortcutsPanelOpen]);
// Global keyboard shortcuts listener
useEffect(() => {
const isShortcutInScope = (shortcut: (typeof SHORTCUTS)[0]): boolean => {
// Skip page-scoped shortcuts unless on the correct route
if (
shortcut.scope === "matching-page" &&
location.pathname !== "/review"
) {
return false;
}
// Skip settings-page scoped shortcuts unless on settings route
if (
shortcut.scope === "settings-page" &&
location.pathname !== "/settings"
) {
return false;
}
return true;
};
const handleKeyDown = (event: KeyboardEvent) => {
// Special case: Handle Escape to close ShortcutsPanel only when it's open
// This must be done before other shortcut processing to avoid conflicts
if (event.key === "Escape" && isShortcutsPanelOpen) {
event.preventDefault();
event.stopPropagation();
handleCloseModal();
return;
}
// Skip if focus is in input or textarea (allow native keyboard handling)
if (
event.target instanceof HTMLInputElement ||
event.target instanceof HTMLTextAreaElement
) {
return;
}
const dispatchShortcutAction = (action: string): boolean => {
if (action.startsWith("navigate:")) {
handleNavigationShortcut(action);
return true;
}
switch (action) {
case "focus:search":
handleSearchFocus();
return true;
case "focus:settings-search":
// Focus the settings search input using the data attribute
if (location.pathname === "/settings") {
const settingsSearchInput = document.querySelector(
"[data-search-input='settings']",
) as HTMLInputElement;
if (settingsSearchInput) {
settingsSearchInput.focus();
settingsSearchInput.select();
return true;
}
}
return false;
case "save:config":
handleContextSave();
return true;
case "toggle:debug":
setDebugEnabled(true);
openDebugMenu();
return true;
case "toggle:shortcuts-panel":
handleToggleShortcutsPanel();
return true;
default:
return false;
}
};
// Check each shortcut for a match
for (const shortcut of SHORTCUTS) {
if (!matchesShortcut(event, shortcut)) continue;
// Skip if shortcut is not in current scope
if (!isShortcutInScope(shortcut)) {
continue;
}
// Let page-level handlers take precedence for undo/redo
// These are intentionally not prevented here; the page must handle preventDefault()
if (shortcut.action === "undo" || shortcut.action === "redo") return;
const handled = dispatchShortcutAction(shortcut.action);
if (handled) {
event.preventDefault();
// Stop propagation to prevent duplicate toggles and avoid interfering with nested modals
event.stopPropagation();
}
// Only process one shortcut per keystroke
break;
}
};
globalThis.addEventListener("keydown", handleKeyDown);
return () => globalThis.removeEventListener("keydown", handleKeyDown);
}, [
handleNavigationShortcut,
handleSearchFocus,
handleContextSave,
toggleDebug,
handleToggleShortcutsPanel,
handleCloseModal,
location.pathname,
isShortcutsPanelOpen,
]);
return (
<BaseLayout onOpenShortcutsPanel={handleToggleShortcutsPanel}>
<Suspense fallback={<PageLoadingFallback />}>
<Outlet />
</Suspense>
<ShortcutsPanel
isOpen={isShortcutsPanelOpen}
onClose={() => setIsShortcutsPanelOpen(false)}
/>
</BaseLayout>
);
}
Root layout component that wraps all pages with the base layout. Manages global keyboard shortcuts and overlay visibility.