feature/tab-buscador-vistas
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user