From 81fe5f03d9d930fb45e2794e6ec189893a628114 Mon Sep 17 00:00:00 2001 From: hreyes Date: Mon, 9 Feb 2026 10:30:51 -0600 Subject: [PATCH] feature/dropdown --- .../lib/components/sidebar/nav-main.svelte | 147 +++++++++--------- .../code_pedimento_regimens/+page.svelte | 2 +- .../reference_data/containers/+page.svelte | 2 +- .../reference_data/countries/+page.svelte | 2 +- .../currency_types/+page.svelte | 2 +- .../customs_warehouses/+page.svelte | 2 +- .../reference_data/invoice_types/+page.svelte | 2 +- .../material_types/+page.svelte | 2 +- .../reference_data/sectors/+page.svelte | 2 +- .../reference_data/states/+page.svelte | 2 +- .../transport_modes/+page.svelte | 2 +- .../transport_types/+page.svelte | 2 +- .../valuation_methods/+page.svelte | 2 +- 13 files changed, 89 insertions(+), 82 deletions(-) diff --git a/frontend/src/lib/components/sidebar/nav-main.svelte b/frontend/src/lib/components/sidebar/nav-main.svelte index 0ecc6740..e21cdf95 100644 --- a/frontend/src/lib/components/sidebar/nav-main.svelte +++ b/frontend/src/lib/components/sidebar/nav-main.svelte @@ -22,63 +22,57 @@ const sidebar = useSidebar(); - // Estado del Sistema Híbrido + // Estado del Sistema Híbrido controlado por hover estricto (sin timers) let activeTitle = $state(null); - // Flags de estado físico - let isHoveringTrigger = false; - let isHoveringContent = false; - let closeTimer: ReturnType | null = null; - // PROTOCOLO DE TRASPASO (Handover Protocol) - // 1. Cuando entramos en zona segura, cancelamos cualquier intento de cierre. - function clearCloseTimer() { - if (closeTimer) { - clearTimeout(closeTimer); - closeTimer = null; - } - } + function shouldKeepOpen(event: PointerEvent, title: string) { + const next = event.relatedTarget as Node | null; + if (!next) return false; - // 2. Cuando salimos, esperamos un "micro-tick" (75ms) para ver si el mouse aterriza en la otra zona. - // Si no aterriza, cerramos. - function scheduleClose() { - clearCloseTimer(); - closeTimer = setTimeout(() => { - if (!isHoveringTrigger && !isHoveringContent) { - activeTitle = null; - } - }, 75); - } + const trigger = document.getElementById(`trigger-${title}`); + const content = document.getElementById(`content-${title}`); + + // Mantenemos abierto si el puntero se mueve hacia el trigger o el contenido del menú + return (trigger && trigger.contains(next)) || (content && content.contains(next)); + } function handleTriggerEnter(title: string) { if (sidebar.state !== "collapsed") return; - clearCloseTimer(); - isHoveringTrigger = true; activeTitle = title; } - function handleTriggerLeave() { - isHoveringTrigger = false; - scheduleClose(); - } + function handleTriggerLeave(event: PointerEvent, title: string) { + if (sidebar.state !== "collapsed") return; + + // Si nos movemos al contenido (o nos quedamos en el trigger), no cerramos + if (shouldKeepOpen(event, title)) return; + + activeTitle = null; + } - function handleContentEnter() { - clearCloseTimer(); - isHoveringContent = true; - } + function handleContentEnter(title: string) { + if (sidebar.state !== "collapsed") return; + activeTitle = title; + } - function handleContentLeave() { - isHoveringContent = false; - scheduleClose(); - } + function handleContentLeave(event: PointerEvent, title: string) { + if (sidebar.state !== "collapsed") return; + + // Si nos movemos de vuelta al trigger (o dentro del contenido), no cerramos + if (shouldKeepOpen(event, title)) return; + + activeTitle = null; + } function onOpenChange(open: boolean, title: string) { + // Sincronización base if (open) { activeTitle = title; + } else { + if (activeTitle === title) { + activeTitle = null; + } } - // "AUTHORITY STRATEGY": - // Ignoramos explícitamente cuando `open` es false. - // La librería NO tiene permiso para cerrar el menú. - // Solo nosotros (vía scheduleClose) podemos poner activeTitle = null. } @@ -88,31 +82,29 @@ {#each items as item (item.title)} {#if item.items && item.items.length > 0} {#if sidebar.state === "collapsed"} - + onOpenChange(v, item.title)} - modal={false} + modal={false} > - + {#snippet child({ props })}
handleTriggerEnter(item.title)} - onmouseleave={handleTriggerLeave} + id={`trigger-${item.title}`} + class="relative z-30 w-full flex justify-center" + onpointerenter={() => handleTriggerEnter(item.title)} + onpointerleave={(e) => handleTriggerLeave(e, item.title)} > - -
- - + {#if item.icon} {:else}
{/if} - {item.title} + + {item.title}
{/snippet} @@ -120,25 +112,40 @@ handleContentEnter(item.title)} + onpointerleave={(e) => handleContentLeave(e, item.title)} > -
- - {item.title} - + +
+ {#if item.icon} + + {:else} +
+ {/if} +
+ + +
+ + +
+ {item.title} +
+ + {#each item.items as subItem (subItem.title)} - - - {subItem.title} + + + {subItem.title} {/each} diff --git a/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.svelte b/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.svelte index 6dbad5a2..64f56a42 100644 --- a/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.svelte @@ -23,7 +23,7 @@ 'Lista Códigos', obtenerAtajosLista({ manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: handleRefresh + manejarActualizar: reloadData }) ); diff --git a/frontend/src/routes/dashboard/reference_data/containers/+page.svelte b/frontend/src/routes/dashboard/reference_data/containers/+page.svelte index 8d836528..2c100037 100644 --- a/frontend/src/routes/dashboard/reference_data/containers/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/containers/+page.svelte @@ -23,7 +23,7 @@ 'Lista Contenedores', obtenerAtajosLista({ manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: handleRefresh + manejarActualizar: reloadData }) ); diff --git a/frontend/src/routes/dashboard/reference_data/countries/+page.svelte b/frontend/src/routes/dashboard/reference_data/countries/+page.svelte index eb3ad94f..79385cc0 100644 --- a/frontend/src/routes/dashboard/reference_data/countries/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/countries/+page.svelte @@ -22,7 +22,7 @@ 'Lista Países', obtenerAtajosLista({ manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: handleRefresh + manejarActualizar: reloadData }) ); diff --git a/frontend/src/routes/dashboard/reference_data/currency_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/currency_types/+page.svelte index 3649a1b4..1d97b61a 100644 --- a/frontend/src/routes/dashboard/reference_data/currency_types/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/currency_types/+page.svelte @@ -22,7 +22,7 @@ 'Lista Tipos de Moneda', obtenerAtajosLista({ manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: handleRefresh + manejarActualizar: reloadData }) ); diff --git a/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte b/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte index 4fc1ef4b..1cc6f738 100644 --- a/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte @@ -22,7 +22,7 @@ 'Lista Almacenes Aduanales', obtenerAtajosLista({ manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: handleRefresh + manejarActualizar: reloadData }) ); diff --git a/frontend/src/routes/dashboard/reference_data/invoice_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/invoice_types/+page.svelte index f74a1352..d0190423 100644 --- a/frontend/src/routes/dashboard/reference_data/invoice_types/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/invoice_types/+page.svelte @@ -22,7 +22,7 @@ 'Lista Tipos de Factura', obtenerAtajosLista({ manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: handleRefresh + manejarActualizar: reloadData }) ); diff --git a/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte index 7fd85348..c0179001 100644 --- a/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte @@ -23,7 +23,7 @@ 'Lista Tipos de Material', obtenerAtajosLista({ manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: handleRefresh + manejarActualizar: reloadData }) ); diff --git a/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte b/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte index c250d5c7..ede8b240 100644 --- a/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte @@ -23,7 +23,7 @@ 'Lista Sectores', obtenerAtajosLista({ manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: handleRefresh + manejarActualizar: reloadData }) ); diff --git a/frontend/src/routes/dashboard/reference_data/states/+page.svelte b/frontend/src/routes/dashboard/reference_data/states/+page.svelte index 66b9ff76..e826d219 100644 --- a/frontend/src/routes/dashboard/reference_data/states/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/states/+page.svelte @@ -23,7 +23,7 @@ 'Lista Estados', obtenerAtajosLista({ manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: handleRefresh + manejarActualizar: reloadData }) ); diff --git a/frontend/src/routes/dashboard/reference_data/transport_modes/+page.svelte b/frontend/src/routes/dashboard/reference_data/transport_modes/+page.svelte index 4deaa913..b2ef69da 100644 --- a/frontend/src/routes/dashboard/reference_data/transport_modes/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/transport_modes/+page.svelte @@ -23,7 +23,7 @@ 'Lista Modos de Transporte', obtenerAtajosLista({ manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: handleRefresh + manejarActualizar: reloadData }) ); diff --git a/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte index f0998fa9..61fbac39 100644 --- a/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte @@ -23,7 +23,7 @@ 'Lista Tipos de Transporte', obtenerAtajosLista({ manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: handleRefresh + manejarActualizar: reloadData }) ); diff --git a/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte b/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte index f4d99f23..19cfa51d 100644 --- a/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte +++ b/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte @@ -22,7 +22,7 @@ 'Lista Métodos de Valoración', obtenerAtajosLista({ manejarNuevo: () => (showCreateDialog = true), - manejarActualizar: handleRefresh + manejarActualizar: reloadData }) );