From 7d38ac2720b3d2ec8d9be6a9826c67b8781be9b3 Mon Sep 17 00:00:00 2001 From: hreyes Date: Tue, 14 Apr 2026 13:03:07 -0600 Subject: [PATCH] feature/tabs-navegation --- .../keyboard/KeyboardManager.svelte | 44 ++++++++++- .../keyboard/ShortcutsHelpModal.svelte | 39 ++++++++-- .../ui/select/select-content.svelte | 73 ++++++++++++++++++- .../components/ui/select/select-root.svelte | 2 + .../ui/select/select-search-context.ts | 2 + .../ui/select/select-searchable.svelte | 28 ++++++- .../ui/select/select-trigger.svelte | 5 ++ frontend/src/routes/dashboard/+layout.svelte | 5 +- 8 files changed, 183 insertions(+), 15 deletions(-) diff --git a/frontend/src/lib/components/keyboard/KeyboardManager.svelte b/frontend/src/lib/components/keyboard/KeyboardManager.svelte index 2be1f147..594790a3 100644 --- a/frontend/src/lib/components/keyboard/KeyboardManager.svelte +++ b/frontend/src/lib/components/keyboard/KeyboardManager.svelte @@ -8,8 +8,22 @@ import ShortcutsHelpModal from './ShortcutsHelpModal.svelte'; let showHelp = $state(false); + /** Element to restore focus when closing the F1 shortcuts help modal */ + let helpFocusRestore: HTMLElement | null = null; let lastShortcutTime = 0; + function restoreHelpFocus() { + const el = helpFocusRestore; + helpFocusRestore = null; + if (el && document.contains(el) && typeof el.focus === 'function') { + try { + el.focus(); + } catch { + /* ignore e.g. disconnected nodes */ + } + } + } + // Verificar si el usuario está autenticado function isAuthenticated(): boolean { if (!browser) return false; @@ -23,7 +37,10 @@ function autoFocusFirstInput() { // Small delay to allow Svelte to render the new view setTimeout(() => { - const container = document.getElementById('main-form-content') || document.body; + const container = + document.getElementById('main-form-content') || + document.getElementById('dashboard-main-content') || + document.body; // 1. Try to find active tab panels (Shadcn/Radix uses role="tabpanel" and data-state="active") const activePanels = container.querySelectorAll('[role="tabpanel"][data-state="active"]'); @@ -159,7 +176,16 @@ return; } event.preventDefault(); - showHelp = !showHelp; + if (showHelp) { + showHelp = false; + restoreHelpFocus(); + } else { + const active = document.activeElement; + if (active instanceof HTMLElement) { + helpFocusRestore = active; + } + showHelp = true; + } return; } @@ -167,6 +193,7 @@ if (key === 'Escape') { if (showHelp) { showHelp = false; + restoreHelpFocus(); event.preventDefault(); return; } @@ -239,7 +266,10 @@ const route = GLOBAL_NAV[lowerKey as keyof typeof GLOBAL_NAV]; if (route) { event.preventDefault(); - goto(route); + lastShortcutTime = Date.now(); + void goto(route).then(() => { + autoFocusFirstInput(); + }); return; } } @@ -379,5 +409,11 @@ /> {#if showHelp} - (showHelp = false)} /> + { + showHelp = false; + restoreHelpFocus(); + }} + /> {/if} diff --git a/frontend/src/lib/components/keyboard/ShortcutsHelpModal.svelte b/frontend/src/lib/components/keyboard/ShortcutsHelpModal.svelte index 4914b966..0b35839d 100644 --- a/frontend/src/lib/components/keyboard/ShortcutsHelpModal.svelte +++ b/frontend/src/lib/components/keyboard/ShortcutsHelpModal.svelte @@ -1,4 +1,5 @@ @@ -88,7 +159,7 @@ class="placeholder:text-muted-foreground flex h-8 w-full rounded-md border border-input bg-background px-3 py-1 text-sm outline-none focus:border-ring focus:ring-1 focus:ring-ring" oninput={(event) => updateQuery((event.currentTarget as HTMLInputElement).value)} onclick={(e) => e.stopPropagation()} - onkeydown={(e) => e.stopPropagation()} + onkeydown={handleSearchInputKeydown} /> {/if} diff --git a/frontend/src/lib/components/ui/select/select-root.svelte b/frontend/src/lib/components/ui/select/select-root.svelte index 62bc3d59..34b34716 100644 --- a/frontend/src/lib/components/ui/select/select-root.svelte +++ b/frontend/src/lib/components/ui/select/select-root.svelte @@ -24,9 +24,11 @@ let open = $state(false); const query = writable(''); const openStore = writable(false); + const triggerRef = writable(null); const context: SelectSearchContext = { query, open: openStore, + triggerRef, setOpen: (next) => { open = next; } diff --git a/frontend/src/lib/components/ui/select/select-search-context.ts b/frontend/src/lib/components/ui/select/select-search-context.ts index d7a7e0b3..57a3e3bf 100644 --- a/frontend/src/lib/components/ui/select/select-search-context.ts +++ b/frontend/src/lib/components/ui/select/select-search-context.ts @@ -6,4 +6,6 @@ export type SelectSearchContext = { query: Writable; open: Writable; setOpen: (next: boolean) => void; + /** Nodo del trigger para devolver foco al cerrar (bits-ui bloquea onCloseAutoFocus por defecto). */ + triggerRef: Writable; }; diff --git a/frontend/src/lib/components/ui/select/select-searchable.svelte b/frontend/src/lib/components/ui/select/select-searchable.svelte index 36820144..a8afe634 100644 --- a/frontend/src/lib/components/ui/select/select-searchable.svelte +++ b/frontend/src/lib/components/ui/select/select-searchable.svelte @@ -1,4 +1,5 @@ -
+
{#if csvImportBanner}