feature/tab-buscador-vistas

This commit is contained in:
2026-04-15 10:23:21 -06:00
parent 07f5553fd9
commit 2774262c90
27 changed files with 195 additions and 87 deletions

View File

@@ -33,6 +33,49 @@
let scrollContainer = $state<HTMLDivElement>();
let loadingTrigger = $state<HTMLDivElement>();
function findRowActionTrigger(tr: HTMLTableRowElement): HTMLElement | null {
const lastTd = tr.querySelector('td:last-of-type');
if (!lastTd) return null;
return lastTd.querySelector<HTMLElement>(
'[data-slot="dropdown-menu-trigger"], button[aria-haspopup="menu"], button[aria-haspopup="dialog"]'
);
}
function handleCatalogTableKeydown(event: KeyboardEvent) {
if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return;
const target = event.target as HTMLElement | null;
if (!target || !scrollContainer?.contains(target)) return;
if (
target.tagName === 'INPUT' ||
target.tagName === 'TEXTAREA' ||
target.tagName === 'SELECT' ||
target.isContentEditable
) {
return;
}
const row = target.closest<HTMLTableRowElement>('tr[data-slot="table-row"]');
if (!row || row.closest('[role="dialog"]')) return;
const lastCell = row.querySelector('td:last-of-type');
if (!lastCell || !lastCell.contains(target)) return;
const tbody = row.closest('tbody');
if (!tbody || !scrollContainer.contains(tbody)) return;
const rows = Array.from(
tbody.querySelectorAll<HTMLTableRowElement>('tr[data-slot="table-row"]')
);
const idx = rows.indexOf(row);
if (idx < 0) return;
const delta = event.key === 'ArrowDown' ? 1 : -1;
const nextIdx = idx + delta;
if (nextIdx < 0 || nextIdx >= rows.length) return;
const nextTrigger = findRowActionTrigger(rows[nextIdx]);
if (!nextTrigger) return;
event.preventDefault();
event.stopPropagation();
nextTrigger.focus();
rows[nextIdx].scrollIntoView({ block: 'nearest', behavior: 'smooth' });
}
onMount(() => {
const observer = new IntersectionObserver(
(entries) => {
@@ -61,13 +104,14 @@
<div
class="catalog-table-scroll min-h-[320px] max-h-[calc(100svh-280px)]"
bind:this={scrollContainer}
onkeydown={handleCatalogTableKeydown}
>
<Table.Root>
<Table.Header class="catalog-table-header">
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
{@const headerList = headerGroup.headers}
{@const lastHeaderColId = headerList[headerList.length - 1]?.column.id}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerList as header (header.id)}
{@const colId = header.column.id}
<Table.Head
@@ -96,6 +140,7 @@
{@const visibleCells = row.getVisibleCells()}
{@const lastCellColId = visibleCells[visibleCells.length - 1]?.column.id}
<Table.Row
inTabOrder={false}
data-state={row.getIsSelected() && 'selected'}
class="catalog-table-row"
>
@@ -119,7 +164,7 @@
</Table.Row>
{/each}
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center text-sm text-muted-foreground">
{emptyMessage}
</Table.Cell>
@@ -127,7 +172,7 @@
{/if}
{#if hasMore}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center p-0">
<div bind:this={loadingTrigger} class="flex h-full w-full items-center justify-center">
{#if loading}

View File

@@ -44,7 +44,7 @@
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerGroup.headers as header (header.id)}
<Table.Head>
{#if !header.isPlaceholder}
@@ -60,7 +60,7 @@
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<FlexRender
@@ -71,7 +71,7 @@
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>

View File

@@ -47,7 +47,7 @@
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerGroup.headers as header (header.id)}
<Table.Head>
{#if !header.isPlaceholder}
@@ -63,7 +63,7 @@
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<FlexRender
@@ -74,7 +74,7 @@
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>

View File

@@ -44,7 +44,7 @@
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerGroup.headers as header (header.id)}
<Table.Head>
{#if !header.isPlaceholder}
@@ -60,7 +60,7 @@
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<FlexRender
@@ -71,7 +71,7 @@
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>

View File

@@ -44,7 +44,7 @@
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerGroup.headers as header (header.id)}
<Table.Head>
{#if !header.isPlaceholder}
@@ -60,7 +60,7 @@
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<FlexRender
@@ -71,7 +71,7 @@
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>

View File

@@ -39,7 +39,7 @@
<div class="relative overflow-hidden rounded-md border bg-background">
<Table.Root>
<Table.Header class="bg-muted/50">
<Table.Row>
<Table.Row inTabOrder={false}>
{#each columns as col}
<Table.Head class="h-10 px-4 text-xs font-semibold whitespace-nowrap"
>{col.header}</Table.Head
@@ -49,7 +49,7 @@
</Table.Header>
<Table.Body>
{#if data.length === 0}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell
colspan={columns.length}
class="h-24 text-center text-sm text-muted-foreground"
@@ -59,7 +59,7 @@
</Table.Row>
{:else}
{#each data as row, i}
<Table.Row class="group transition-colors hover:bg-muted/30">
<Table.Row inTabOrder={false} class="group transition-colors hover:bg-muted/30">
{#each columns as col}
<Table.Cell class="px-4 py-2 text-sm whitespace-nowrap">
{#if col.render}

View File

@@ -76,7 +76,7 @@
<Table.Root>
<Table.Header class="catalog-table-header">
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerGroup.headers as header (header.id)}
<Table.Head class="catalog-table-head-cell">
{#if !header.isPlaceholder}
@@ -92,8 +92,7 @@
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row
data-state={row.getIsSelected() && 'selected'}
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && 'selected'}
onclick={() => onRowClick?.(row.original)}
class="cursor-pointer {row.getIsSelected() ? 'catalog-table-row-selected' : 'catalog-table-row'}"
>
@@ -104,7 +103,7 @@
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center text-sm text-muted-foreground">
No hay resultados.
</Table.Cell>
@@ -112,7 +111,7 @@
{/each}
{#if hasMore}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-20 text-center">
<div bind:this={loadingTrigger}>
{#if loading}

View File

@@ -42,7 +42,7 @@
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerGroup.headers as header (header.id)}
<Table.Head>
{#if !header.isPlaceholder}
@@ -58,7 +58,7 @@
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<FlexRender
@@ -69,7 +69,7 @@
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>

View File

@@ -286,7 +286,7 @@
<div class="rounded-md border">
<Table.Root>
<Table.Header>
<Table.Row class="bg-muted/20">
<Table.Row inTabOrder={false} class="bg-muted/20">
<Table.Head class="w-10"></Table.Head>
<Table.Head>Campo Original</Table.Head>
<Table.Head>Campo Externo</Table.Head>
@@ -294,17 +294,17 @@
</Table.Header>
<Table.Body>
{#if loading}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={3} class="h-24 text-center">Cargando...</Table.Cell>
</Table.Row>
{:else if error}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={3} class="h-24 text-center text-destructive">
{error}
</Table.Cell>
</Table.Row>
{:else if items.length === 0}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={3} class="h-24 text-center text-muted-foreground">
No hay items en el pool. Usa "Insertar" para crear uno.
</Table.Cell>
@@ -312,7 +312,7 @@
{:else}
{#each items as item}
{@const isSelected = item.id === selectedItemId}
<Table.Row
<Table.Row inTabOrder={false}
class={[
'cursor-pointer transition-colors',
isSelected

View File

@@ -42,7 +42,7 @@
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerGroup.headers as header (header.id)}
<Table.Head>
{#if !header.isPlaceholder}
@@ -59,7 +59,7 @@
<Table.Body>
{#if table.getRowModel().rows.length}
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<FlexRender
@@ -71,7 +71,7 @@
</Table.Row>
{/each}
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>

View File

@@ -44,7 +44,7 @@
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerGroup.headers as header (header.id)}
<Table.Head>
{#if !header.isPlaceholder}
@@ -60,7 +60,7 @@
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<FlexRender
@@ -71,7 +71,7 @@
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>

View File

@@ -42,7 +42,7 @@
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerGroup.headers as header (header.id)}
<Table.Head>
{#if !header.isPlaceholder}
@@ -58,7 +58,7 @@
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<FlexRender
@@ -69,7 +69,7 @@
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>

View File

@@ -43,7 +43,7 @@
<Table.Root>
<Table.Header class="sticky top-0 z-20 border-b bg-background/95 shadow-sm backdrop-blur-md">
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerGroup.headers as header (header.id)}
<Table.Head class="whitespace-nowrap">
{#if !header.isPlaceholder}
@@ -60,7 +60,7 @@
<Table.Body>
{#if table.getRowModel().rows.length}
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell class="whitespace-nowrap">
<FlexRender
@@ -72,7 +72,7 @@
</Table.Row>
{/each}
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>

View File

@@ -45,7 +45,7 @@
<Table.Root>
<Table.Header class="sticky top-0 z-20 border-b bg-background/95 shadow-sm backdrop-blur-md">
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerGroup.headers as header (header.id)}
<Table.Head class="whitespace-nowrap">
{#if !header.isPlaceholder}
@@ -61,7 +61,7 @@
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell class="whitespace-nowrap">
<FlexRender
@@ -72,7 +72,7 @@
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>

View File

@@ -42,7 +42,7 @@
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerGroup.headers as header (header.id)}
<Table.Head>
{#if !header.isPlaceholder}
@@ -58,7 +58,7 @@
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<FlexRender
@@ -69,7 +69,7 @@
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>

View File

@@ -131,7 +131,7 @@
<div class="min-h-0 flex-1 overflow-auto">
<Table.Root>
<Table.Header class="catalog-table-header">
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Head class="catalog-table-head-cell w-[100px]">Clave</Table.Head>
<Table.Head class="catalog-table-head-cell">Descripción</Table.Head>
<Table.Head class="catalog-table-head-cell text-right">Estatus</Table.Head>
@@ -139,7 +139,7 @@
</Table.Header>
<Table.Body>
{#if loading && page === 1}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={3} class="h-24 text-center">
<div class="flex justify-center">
<Loader2 class="h-6 w-6 animate-spin" />
@@ -147,14 +147,14 @@
</Table.Cell>
</Table.Row>
{:else if sectors.length === 0}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={3} class="h-24 text-center">
No se encontraron sectores.
</Table.Cell>
</Table.Row>
{:else}
{#each sectors as sector}
<Table.Row class="catalog-table-row">
<Table.Row inTabOrder={false} class="catalog-table-row">
<Table.Cell class="font-medium">{sector.key}</Table.Cell>
<Table.Cell>{sector.description}</Table.Cell>
<Table.Cell class="text-right">
@@ -168,7 +168,7 @@
{/each}
{/if}
{#if loading && page > 1}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={3} class="h-12 text-center">
<div class="flex justify-center">
<Loader2 class="h-4 w-4 animate-spin" />

View File

@@ -44,7 +44,7 @@
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerGroup.headers as header (header.id)}
<Table.Head>
{#if !header.isPlaceholder}
@@ -60,7 +60,7 @@
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<FlexRender
@@ -71,7 +71,7 @@
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>

View File

@@ -42,7 +42,7 @@
<div class="rounded-md border">
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Row inTabOrder={false}>
{#each columns as column (column.key)}
<Table.Head>{column.label}</Table.Head>
{/each}
@@ -50,7 +50,7 @@
</Table.Header>
<Table.Body>
{#each data as row, i (i)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each columns as column (column.key)}
<Table.Cell>
{getCellValue(row, column)}
@@ -58,7 +58,7 @@
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>

View File

@@ -44,7 +44,7 @@
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerGroup.headers as header (header.id)}
<Table.Head>
{#if !header.isPlaceholder}
@@ -60,7 +60,7 @@
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<FlexRender
@@ -71,7 +71,7 @@
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>

View File

@@ -44,7 +44,7 @@
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerGroup.headers as header (header.id)}
<Table.Head>
{#if !header.isPlaceholder}
@@ -60,7 +60,7 @@
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<FlexRender
@@ -71,7 +71,7 @@
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>

View File

@@ -44,7 +44,7 @@
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerGroup.headers as header (header.id)}
<Table.Head>
{#if !header.isPlaceholder}
@@ -60,7 +60,7 @@
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<FlexRender
@@ -71,7 +71,7 @@
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>

View File

@@ -44,7 +44,7 @@
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerGroup.headers as header (header.id)}
<Table.Head>
{#if !header.isPlaceholder}
@@ -60,7 +60,7 @@
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<FlexRender
@@ -71,7 +71,7 @@
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>

View File

@@ -44,7 +44,7 @@
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerGroup.headers as header (header.id)}
<Table.Head>
{#if !header.isPlaceholder}
@@ -60,7 +60,7 @@
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<FlexRender
@@ -71,7 +71,7 @@
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>

View File

@@ -44,7 +44,7 @@
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerGroup.headers as header (header.id)}
<Table.Head>
{#if !header.isPlaceholder}
@@ -60,7 +60,7 @@
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<FlexRender
@@ -71,7 +71,7 @@
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>

View File

@@ -44,7 +44,7 @@
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
<Table.Row inTabOrder={false}>
{#each headerGroup.headers as header (header.id)}
<Table.Head>
{#if !header.isPlaceholder}
@@ -60,7 +60,7 @@
</Table.Header>
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row data-state={row.getIsSelected() && "selected"}>
<Table.Row inTabOrder={false} data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<Table.Cell>
<FlexRender
@@ -71,7 +71,7 @@
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Row inTabOrder={false}>
<Table.Cell colspan={columns.length} class="h-24 text-center">
No hay resultados.
</Table.Cell>

View File

@@ -39,6 +39,69 @@
return !!(htmlEl.offsetWidth || htmlEl.offsetHeight || htmlEl.getClientRects().length);
}
function inputIsSearchCandidate(el: HTMLInputElement): boolean {
const t = (el.type || 'text').toLowerCase();
return !['hidden', 'checkbox', 'radio', 'file', 'button', 'submit', 'reset'].includes(t);
}
/**
* Alt+B: focus the current view's primary filter/search when possible.
* Order: [data-view-search] → placeholder hints (buscar/search/…) →
* first visible text-like input before the main table / catalog scroll → global sr-only fallback.
*/
function focusViewSearchOrGlobal() {
const main =
document.getElementById('dashboard-main-content') ||
document.getElementById('main-form-content');
const tryFocus = (el: HTMLElement | null | undefined): boolean => {
if (!el || !isVisibleInMainContent(el)) return false;
if (el.closest('[role="dialog"]')) return false;
try {
el.focus();
} catch {
return false;
}
setTimeout(() => {
el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' });
}, 50);
return true;
};
if (main) {
const explicit = main.querySelector<HTMLElement>('[data-view-search]:not([disabled])');
if (tryFocus(explicit)) return;
const phPattern = /buscar|search|filtrar|filter|búsqueda|busqueda/i;
const inputs = Array.from(
main.querySelectorAll<HTMLInputElement>('input:not([disabled])')
).filter(inputIsSearchCandidate);
for (const input of inputs) {
if (!isVisibleInMainContent(input)) continue;
if (input.closest('[role="dialog"]')) continue;
if (phPattern.test(input.placeholder || '')) {
if (tryFocus(input)) return;
}
}
const boundary = main.querySelector(
'table, tbody, .catalog-table-scroll, [data-slot="table-container"]'
);
if (boundary) {
for (const input of inputs) {
if (!isVisibleInMainContent(input)) continue;
if (input.closest('[role="dialog"]')) continue;
if (input.compareDocumentPosition(boundary) & Node.DOCUMENT_POSITION_FOLLOWING) {
if (tryFocus(input)) return;
}
}
}
}
document.getElementById('global-search-input')?.focus();
}
/** First meaningful control in main area (forms); optional delay for post-navigation paint. */
function focusMainContentPrimary(options?: { delay?: number }) {
const delay = options?.delay ?? 350;
@@ -295,11 +358,11 @@
// Solo permitir si está autenticado
if (authenticated && altKey && modifiers.length === 1) {
// Exactly Alt + Key (no shift/ctrl)
// Special: Alt+B -> Focus Search
// Special: Alt+B -> Focus view search / filters or global fallback
if (lowerKey === 'b') {
event.preventDefault();
const searchInput = document.getElementById('global-search-input');
searchInput?.focus();
lastShortcutTime = Date.now();
focusViewSearchOrGlobal();
return;
}

View File

@@ -6,20 +6,21 @@
ref = $bindable(null),
class: className,
children,
inTabOrder = true,
...restProps
}: WithElementRef<HTMLAttributes<HTMLTableRowElement>> = $props();
}: WithElementRef<HTMLAttributes<HTMLTableRowElement>> & { inTabOrder?: boolean } = $props();
</script>
<tr
bind:this={ref}
data-slot="table-row"
tabindex="0"
{...restProps}
tabindex={inTabOrder ? 0 : -1}
class={cn(
"hover:[&,&>svelte-css-wrapper]:[&>th,td]:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
"focus-visible:bg-accent focus-visible:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
className
)}
{...restProps}
>
{@render children?.()}
</tr>