@elabs-ai/components-data 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +7 -2
- package/dist/index.js +71 -53
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/__contract__/filter-chip.contract.test.tsx +49 -0
- package/src/column-picker/column-picker.tsx +3 -2
- package/src/data-table/data-table.stories.tsx +15 -0
- package/src/data-table/data-table.test.tsx +61 -11
- package/src/data-table/data-table.tsx +54 -15
- package/src/facet-filter/facet-filter.stories.tsx +4 -1
- package/src/facet-filter/facet-filter.test.tsx +3 -3
- package/src/search-input/search-input.test.tsx +35 -0
- package/src/search-input/search-input.tsx +44 -19
- package/src/to-csv.test.ts +13 -0
- package/src/to-csv.ts +7 -30
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/data-table/data-table.tsx","../src/search-input/search-input.tsx","../src/filter-bar/filter-bar.tsx","../src/filter-bar/filter-chip.tsx","../src/facet-filter/facet-filter.tsx","../src/column-picker/column-picker.tsx","../src/to-csv.ts"],"sourcesContent":["\"use client\";\n\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport {\n flexRender,\n getCoreRowModel,\n getFilteredRowModel,\n getPaginationRowModel,\n getSortedRowModel,\n useReactTable,\n type Column,\n type ColumnDef,\n type ColumnFiltersState,\n type ColumnPinningState,\n type ColumnSizingState,\n type OnChangeFn,\n type PaginationState,\n type Row,\n type RowData,\n type RowSelectionState,\n type SortingState,\n type Table as TanstackTable,\n type VisibilityState,\n} from \"@tanstack/react-table\";\nimport { useVirtualizer } from \"@tanstack/react-virtual\";\n// Row drag-reorder (#13). @dnd-kit is the only DnD primitive in the repo (reuse\n// audit found none) — MIT-licensed, attributed in scripts/attributions.sources.json.\n// KeyboardSensor + sortableKeyboardCoordinates already implement the exact key\n// model the issue asks for (Space/Enter lift, arrows move, Space/Enter drop,\n// Escape cancel) and DndContext's built-in `Accessibility` component renders the\n// aria-live announcer — this file supplies the localized announcement text, the\n// localized screen-reader instructions + role description (#98 — dnd-kit ships\n// its own hardcoded-English defaults for both, which need an explicit override\n// same as everything else this feature says out loud), and the token-driven\n// visuals.\nimport {\n DndContext,\n KeyboardSensor,\n PointerSensor,\n closestCenter,\n useSensor,\n useSensors,\n type Announcements,\n type DragCancelEvent,\n type DragEndEvent,\n type DragOverEvent,\n type DragStartEvent,\n type DraggableAttributes,\n type DraggableSyntheticListeners,\n} from \"@dnd-kit/core\";\nimport {\n SortableContext,\n sortableKeyboardCoordinates,\n useSortable,\n verticalListSortingStrategy,\n} from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport { ArrowDown, ArrowUp, ArrowUpDown, GripVertical } from \"lucide-react\";\nimport { Button, Checkbox, Skeleton, Spinner, useLocale } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\n// ─── Column meta seam (#69) ─────────────────────────────────────────────────────\n// `columnDef.meta` is where TanStack lets a caller attach column-specific,\n// renderer-agnostic data — `DataTable` reads exactly two keys from it so\n// numeric-column styling (interaction-guidelines.md § Micro-typography:\n// \"tabular-nums for any number column … DataTable numeric cells\") is the\n// component's job, not a per-caller convention rediscovered at every call\n// site. Exported (not just declared) so a consumer's own `ColumnDef` literal\n// type-checks against a NAMED type, per component-api.md § Types.\n\n/**\n * `DataTable`'s `columnDef.meta` contract, read by the header/body/skeleton\n * cell renderers. Set `numeric: true` on a column to get `tabular-nums` +\n * end-alignment on both the `<th>` and every `<td>` (including the loading\n * skeleton) for free.\n */\nexport interface DataTableColumnMeta {\n /** Numeric column: tabular figures + end alignment on header and cells. */\n numeric?: boolean;\n /**\n * Explicit alignment override for when `numeric` isn't the right cue (or\n * to align a non-numeric column). Independent of `numeric` — `numeric`\n * alone still drives `tabular-nums` even when `align` overrides the\n * alignment away from `\"end\"`.\n */\n align?: \"start\" | \"center\" | \"end\";\n}\n\ndeclare module \"@tanstack/react-table\" {\n // `TData`/`TValue` must stay in the signature to match the interface being\n // augmented, even though `DataTableColumnMeta` (deliberately) doesn't use\n // them; the empty extends-body is how TanStack's own module-augmentation\n // pattern for `ColumnMeta` is documented.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-object-type\n interface ColumnMeta<TData extends RowData, TValue> extends DataTableColumnMeta {}\n}\n\n/**\n * `<th>`/`<td>`/skeleton-`<td>` className for a column's `meta.numeric`/`meta.align`\n * (#69). A pure, module-level helper (no component state) so all three call\n * sites — header, body cell, loading skeleton — stay in lockstep; a drift\n * between them is exactly the \"skeleton doesn't mirror the real layout\" bug\n * loading-states.md warns about. `meta` is typed as the exported\n * `DataTableColumnMeta` (structurally satisfied by TanStack's augmented\n * `ColumnMeta<TData, TValue>`) so the helper doesn't need the table's generic\n * row type.\n *\n * Deliberately takes NO options and NO padding branch: round-1 (#82\n * follow-up) briefly reserved an extra 36px of trailing `<th>` padding here\n * to clear the resize handle, but that moved the header's alignment\n * reference point 24px away from the body `<td>`'s (which keeps the plain\n * 12px `px-3`) — an end-aligned numeric column's own header no longer lined\n * up with the values it labels, defeating the whole point of #69. Reserving\n * space via padding necessarily desyncs header from body, because only the\n * header has a handle to clear. The round-2 fix instead resolves the\n * hit-test collision at the CONTROL that needs to win it — see the sort\n * button's `relative z-10` below — so header and body padding stay\n * byte-identical and this helper only ever contributes alignment +\n * tabular-nums classes.\n */\nfunction numericColumnClasses(meta: DataTableColumnMeta | undefined) {\n if (!meta?.numeric && !meta?.align) return undefined;\n const alignClass =\n meta?.align === \"start\"\n ? \"text-start\"\n : meta?.align === \"center\"\n ? \"text-center\"\n : meta?.align === \"end\"\n ? \"text-end\"\n : meta?.numeric\n ? \"text-end\"\n : undefined;\n return cn(alignClass, meta?.numeric && \"tabular-nums\");\n}\n\n// ─── Public types ─────────────────────────────────────────────────────────────\n\n/** Snapshot of table slice state — used for saved-view serialise/rehydrate. */\nexport interface DataTableViewState {\n sorting: SortingState;\n columnVisibility: VisibilityState;\n columnFilters: ColumnFiltersState;\n globalFilter?: string;\n pagination?: PaginationState;\n /**\n * Which columns are frozen to the left/right edge (#333). OPTIONAL on purpose:\n * the other members predate it, and a required key would break every consumer\n * that already constructs a `DataTableViewState` literal.\n */\n columnPinning?: ColumnPinningState;\n /**\n * Which rows are checked (#11), keyed by row id — see `getRowId`. OPTIONAL\n * like `columnPinning`, for the same reason: the other members predate it.\n */\n rowSelection?: RowSelectionState;\n /**\n * Per-column widths after resizing (#12), keyed by column id. OPTIONAL like\n * `columnPinning`/`rowSelection`, for the same reason: the other members\n * predate it.\n */\n columnSizing?: ColumnSizingState;\n}\n\n/**\n * Argument object fired by `onServerChange` whenever a manual slice changes.\n * The consuming app should re-fetch with these params and update `data`.\n */\nexport interface DataTableServerArgs {\n pagination: PaginationState;\n sorting: SortingState;\n columnFilters: ColumnFiltersState;\n globalFilter: string;\n}\n\n/**\n * Fires when a row is activated (#337).\n *\n * Both activation paths deliver a `click`: a pointer click on the row body, and\n * a keyboard Enter/Space on the row's hidden activation `<button>` (which the\n * browser dispatches as a click). So the handler takes ONE event type — there is\n * nothing for the caller to branch on.\n */\nexport type DataTableRowClickHandler<TData> = (\n row: Row<TData>,\n event: React.MouseEvent<HTMLElement>,\n) => void;\n\n// ─── Props ────────────────────────────────────────────────────────────────────\n\nexport interface DataTableProps<TData, TValue> extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n \"children\"\n> {\n columns: ColumnDef<TData, TValue>[];\n data: TData[];\n /** Render a toolbar above the table; receives the table instance. */\n toolbar?: (table: TanstackTable<TData>) => ReactNode;\n /** Enable client-side pagination. */\n enablePagination?: boolean;\n pageSize?: number;\n /**\n * Hide the pager once there's genuinely only one page\n * (`table.getPageCount() <= 1`). Default `true`. When `manualPagination` is\n * set without `rowCount`/`pageCount`, the page count isn't knowable (TanStack\n * falls back to the current page's row count) — in that ambiguous case the\n * pager still renders regardless of this flag, so the existing dev warning\n * (#227) stays the diagnostic instead of a silently-hidden pager. Set to\n * `false` to always show the pager (e.g. while a server total is still\n * loading and you'd rather show a disabled pager than none).\n */\n hidePaginationWhenSingle?: boolean;\n\n /**\n * Controlled global filter value. When provided, the table reflects this\n * value and the component manages no internal filter state. Keep the source\n * of truth in the app and pass it down — never mutate the filter during\n * render (e.g. `table.setGlobalFilter()` in `toolbar`), which loops.\n */\n globalFilter?: string;\n /** Fires when the table requests a global-filter change (e.g. from typeahead). */\n onGlobalFilterChange?: (value: string) => void;\n\n // ── Controlled slices for saved views ─────────────────────────────────────\n /** Controlled sorting state. When provided the component is sorted-controlled. */\n sorting?: SortingState;\n onSortingChange?: OnChangeFn<SortingState>;\n\n /** Controlled column-visibility state. */\n columnVisibility?: VisibilityState;\n onColumnVisibilityChange?: OnChangeFn<VisibilityState>;\n\n /** Controlled column-filters state. */\n columnFilters?: ColumnFiltersState;\n onColumnFiltersChange?: OnChangeFn<ColumnFiltersState>;\n\n /** Controlled pagination state. */\n pagination?: PaginationState;\n onPaginationChange?: OnChangeFn<PaginationState>;\n\n /**\n * Controlled column-pinning state (#333) — the columns frozen against the\n * left and/or right edge while the rest of the table scrolls horizontally.\n * When provided the component is pinning-controlled; otherwise it manages the\n * slice internally and can be seeded once via `initialView.columnPinning`.\n *\n * A pinned column MUST declare an explicit `size` in its `ColumnDef`: the\n * sticky offset is computed from TanStack's `column.getStart(\"left\")` /\n * `getAfter(\"right\")`, which sum the DECLARED sizes, so an auto-width column\n * would render at a width that doesn't match its own offset. A dev-only\n * warning fires for a pinned column with no `size`.\n *\n * Pinning is a LAYOUT concern, not a query concern — it is client-only and\n * never joins `DataTableServerArgs` / `onServerChange`.\n */\n columnPinning?: ColumnPinningState;\n onColumnPinningChange?: OnChangeFn<ColumnPinningState>;\n\n /**\n * Opt in to column resizing (#12): a drag handle renders on every\n * resizable column's trailing edge — pointer-draggable (TanStack's own\n * `header.getResizeHandler()`) and keyboard-operable (ArrowLeft/ArrowRight\n * on the focused handle, per the WAI-ARIA separator-as-slider practice).\n * Default `false` so a table that doesn't opt in renders byte-identical\n * markup to before this feature existed — no handle, no per-cell width\n * styling.\n */\n enableColumnResizing?: boolean;\n /**\n * When `columnSizing` updates: `\"onChange\"` (default here — TanStack's own\n * default is `\"onEnd\"`) live-updates while dragging; `\"onEnd\"` updates once\n * on release. Only meaningful when `enableColumnResizing` is set.\n */\n columnResizeMode?: \"onChange\" | \"onEnd\";\n /**\n * Controlled column-widths state (#12), keyed by column id — the SAME\n * controlled/uncontrolled shape as `columnPinning`/`rowSelection`.\n * Uncontrolled sizing can be seeded once via `initialView.columnSizing`.\n *\n * A pinned column's sticky offset (`getStart(\"left\")`/`getAfter(\"right\")`)\n * already sums `column.getSize()`, which folds in a `columnSizing`\n * override automatically — so pinning and resizing compose with no extra\n * wiring once this state reaches the table.\n *\n * Sizing is a LAYOUT concern, like `columnPinning`/`rowSelection` — it is\n * client-only and never joins `DataTableServerArgs` / `onServerChange`.\n */\n columnSizing?: ColumnSizingState;\n onColumnSizingChange?: OnChangeFn<ColumnSizingState>;\n\n /**\n * Controlled row-selection state (#11) — which rows are checked, keyed by\n * row id (see `getRowId`). When provided the component is\n * selection-controlled; otherwise it manages the slice internally and can\n * be seeded once via `initialView.rowSelection`. Pair it with a selection\n * column built by `createSelectionColumn` (or drive it yourself off the\n * `table` instance handed to `toolbar`).\n *\n * Selection is a LAYOUT/UI concern, not a query concern — like\n * `columnPinning`, it is client-only and never joins `DataTableServerArgs` /\n * `onServerChange`.\n */\n rowSelection?: RowSelectionState;\n onRowSelectionChange?: OnChangeFn<RowSelectionState>;\n /**\n * Which rows can be selected: `true`/`false` for all rows, or a predicate\n * evaluated per row. Passed straight through to `useReactTable`. Default\n * (TanStack's own): `true`.\n */\n enableRowSelection?: boolean | ((row: Row<TData>) => boolean);\n /**\n * Allow more than one row to be selected at once. Default (TanStack's own):\n * `true`. Set `false` for single-select (radio-style) behaviour.\n */\n enableMultiRowSelection?: boolean;\n /**\n * Stable row id, independent of row INDEX. TanStack's default id is set\n * ONCE per row object when the core row model is built, then reused by\n * reference through sorting/filtering — so a client-side sort or filter\n * does NOT disturb selection identity even without this prop. The real\n * hazard is a `data` array replacement: when the app passes NEW object\n * references (a re-fetch, an optimistic update), TanStack rebuilds the\n * core row model from scratch and reassigns default (index-based) ids, so a\n * row that kept its position but got a new object still keeps its\n * selection — but one that MOVED position silently inherits whatever\n * selection belonged to the id now sitting at its old index. This is\n * unavoidable under `manualPagination`: each page IS a fresh `data` array,\n * so the default index-based id restarts at `0` on every page and a\n * selection made on one page can collide with a different record on the\n * next. Supply `getRowId` whenever `data` can be replaced with new object\n * references (including every server-paginated table) so identity survives\n * the replacement instead of falling back to index.\n */\n getRowId?: (row: TData, index: number) => string;\n\n /**\n * One-shot rehydrate for uncontrolled slices only (ignored for any slice\n * whose corresponding controlled prop is set). Maps to `useReactTable`'s\n * `initialState`.\n */\n initialView?: Partial<DataTableViewState>;\n\n // ── Server-side data model ──────────────────────────────────────────────────\n /**\n * When true, sorting is handled by the server. Pass `sorting` (controlled)\n * and handle `onServerChange` to re-fetch with the new sort params.\n * NOTE: controlled ≠ manual — a controlled `sorting` with `manualSorting:false`\n * still sorts locally.\n */\n manualSorting?: boolean;\n /**\n * When true, filtering is handled by the server.\n * NOTE: a controlled `columnFilters` with `manualFiltering:false` still\n * filters locally.\n */\n manualFiltering?: boolean;\n /** When true, pagination is handled by the server. */\n manualPagination?: boolean;\n\n /**\n * Total row count — used by the server model so TanStack can derive\n * page count. Required when `manualPagination` is true and `pageCount` is\n * not provided.\n */\n rowCount?: number;\n /**\n * Total page count — alternative to `rowCount` for server pagination. When\n * both are provided, `pageCount` wins.\n */\n pageCount?: number;\n\n /**\n * Fired after any manual-slice change with the current {pagination, sorting,\n * columnFilters, globalFilter}. The component never fetches; the app must\n * re-fetch and update `data`.\n */\n onServerChange?: (args: DataTableServerArgs) => void;\n\n /** When true: overlay spinner; on empty+loading show skeleton rows instead of empty message. */\n loading?: boolean;\n\n // ── Virtualization ─────────────────────────────────────────────────────────\n /**\n * Opt-in to row virtualization (for very large lists). Mutually exclusive\n * with enablePagination in practice — if both are set, virtualization wins\n * and pagination is silently ignored.\n */\n enableRowVirtualization?: boolean;\n /** Estimated row height in px (used by the virtualizer). Default: 40. */\n estimateRowHeight?: number;\n /** Virtualizer overscan (rows rendered above/below the visible window). Default: 8. */\n overscan?: number;\n /** CSS max-height of the scroll container in virtualized mode. Default: \"32rem\". */\n maxBodyHeight?: string;\n\n /**\n * Number of skeleton placeholder rows to render while loading.\n * Defaults to `pageSize` (non-virtualized) or `min(10, pageSize)` (virtualized).\n */\n loadingRows?: number;\n\n /**\n * Gentle alternating row stripes (\"zebra\") as the row-separation cue, instead\n * of a hairline divider between every row. Default `true` — the stripe is the\n * single separation gesture, so rows carry no divider (a divider on a striped\n * row would be a redundant boundary). Set `false` for the classic line model\n * (a `border-border-strong` divider between rows, no stripes).\n */\n zebra?: boolean;\n\n // ── Row drag-reorder (#13) ───────────────────────────────────────────────\n /**\n * Opt-in row drag-reorder. Off by default — an existing table renders\n * byte-identical markup with no extra DOM per row until this is set.\n * Fully controlled like every other slice: the component never mutates\n * `data` itself, it only reports the move via `onRowReorder`; the caller\n * re-orders `data` in response.\n *\n * Keyboard-operable out of the box (`@dnd-kit`'s default keyboard sensor):\n * Space/Enter picks a row up, Arrow Up/Down moves it, Space/Enter drops it,\n * Escape cancels. Every position change is announced through a live region\n * (WCAG 4.1.3).\n *\n * Mutually exclusive with `enableRowVirtualization` — a windowed table\n * can't keep dnd-kit's sortable list and a virtualizer in sync, so reorder\n * is silently disabled (a dev warning fires) when both are set. Combining\n * it with active `sorting` also fires a dev warning (both still work, but\n * a sort re-orders the very rows a drag just moved, which reads as broken).\n */\n enableRowReorder?: boolean;\n /**\n * Fires when a row is dropped in a new position. `from`/`to` are indices\n * into the **`data` array you passed in** — never into the sorted, filtered\n * or paginated view the table renders — so they are safe to use directly\n * with `arrayMove`/`slice`+`splice`/immer against your own `data`, unchanged\n * by an active sort or by client-side pagination (the dragged row's true\n * index in the full array, not its index on the current page). Under\n * `manualPagination`, `data` IS the current page, so `from`/`to` are\n * page-relative — reorder that page's own array with them. `row` is the\n * moved record (`data[from]`).\n */\n onRowReorder?: (from: number, to: number, row: TData) => void;\n /**\n * Where the drag activator lives. `\"cell\"` (default) renders a dedicated\n * grip-handle column so the rest of the row keeps its ordinary click/\n * keyboard behavior untouched. `\"row\"` makes the whole row itself the drag\n * activator (no extra column) — reach for this only when the row has no\n * other primary interaction (e.g. no `onRowClick`), since a whole-row\n * activator and a row click target the same surface.\n */\n rowReorderHandle?: \"cell\" | \"row\";\n\n /**\n * Fires when a row is activated (#337). Setting it adds ONE activation\n * target per row: a visually-hidden `<button>` rendered inside the row's\n * first cell. That button is the row's keyboard tab stop and its accessible\n * name; a pointer click anywhere else in the row resolves to the same\n * handler, so mouse and keyboard converge on one control instead of two\n * competing ones (a focusable `<tr>` cannot carry an activation role without\n * destroying `row` table semantics).\n *\n * Guarded: a click that originates on a nested interactive control\n * (button/link/input/checkbox/…) or is the tail end of a text-selection drag\n * does NOT fire it. Optional; omitting it renders rows exactly as before.\n */\n onRowClick?: DataTableRowClickHandler<TData>;\n /**\n * Accessible name for the row's hidden activation button (#337). Only read\n * when `onRowClick` is set. Defaults to the row's first visible cell value\n * when that is a string/number (the row's primary identifier — the same\n * naming a link in that cell would get), else the localized\n * `data.table.rowAction` fallback. Supply it whenever the first cell isn't a\n * good name for the row.\n */\n rowActionLabel?: (row: Row<TData>) => string;\n /**\n * Per-row className, merged alongside the existing zebra/line/hover/selected\n * classes via `cn()` (so it can't accidentally clobber them) (#337).\n */\n rowClassName?: (row: Row<TData>) => string;\n\n /**\n * Accessible name for the table, rendered as a visually-hidden (`sr-only`)\n * `<caption>` — the first child of `<table>`. Screen readers announce it as\n * the table's name and it makes column-header navigation meaningful.\n * Optional; omit it only when the surrounding page already labels the table\n * unambiguously (e.g. an adjacent heading) (#338).\n */\n caption?: ReactNode;\n\n /** Message shown when there are no rows and not loading. */\n emptyMessage?: ReactNode;\n className?: string;\n}\n\n// ─── Row-click guards (module-level — shared by every renderRow call) ────────\n\n/**\n * CSS selector for anything inside a row that owns its own click/keyboard\n * behavior. A row click must not fire when the user actually meant to\n * activate one of these — the row is the activation target for everything\n * ELSE in the row, not a second competing target (#337).\n */\nconst ROW_CLICK_GUARD_SELECTOR =\n 'button, a[href], input, select, textarea, label, summary, [role=\"button\"], [role=\"link\"], [role=\"menuitem\"], [role=\"checkbox\"], [role=\"radio\"], [role=\"switch\"], [role=\"tab\"], [contenteditable=\"true\"]';\n\nfunction isInteractiveEventTarget(target: EventTarget | null): boolean {\n return target instanceof Element && target.closest(ROW_CLICK_GUARD_SELECTOR) !== null;\n}\n\n/**\n * True while the user is completing a text-selection drag — a row click must\n * not fire for the mouseup/click that ends a selection (#337).\n */\nfunction isActiveTextSelection(): boolean {\n if (typeof window === \"undefined\" || typeof window.getSelection !== \"function\") return false;\n return window.getSelection()?.type === \"Range\";\n}\n\n// ─── Pinning helpers (module-level) ──────────────────────────────────────────\n\n/**\n * The 1px seam between the frozen block and the scrolling block (#333), minus\n * the side — `pinnedCellGeometry` appends `after:end-0` or `after:start-0`.\n *\n * A pseudo-element rather than a `border-e`/`border-s` on purpose: see the note\n * in `pinnedCellGeometry`. Token-backed (`bg-border-strong`, the strong rung per\n * ADR 0010) and no shadow, so a shadowless surface (\n * `data-decoration=\"8|9|10\"`) cannot delete it.\n */\nconst PINNED_SEAM_CLASS =\n \"after:pointer-events-none after:absolute after:inset-y-0 after:w-px after:bg-border-strong after:content-['']\";\n\n/**\n * Ids of leaf columns whose ORIGINAL `ColumnDef` declares no `size` (#333).\n *\n * Deliberately reads the raw `columns` prop rather than `column.columnDef`:\n * TanStack merges its `defaultColumnSizing` (`size: 150`) into every resolved\n * column def, so the resolved def can never distinguish \"the author sized this\"\n * from \"the author left it to the default\" — and the whole point of the pinned\n * `size` warning is to catch the second case.\n *\n * Mirrors TanStack's own id resolution: `columnDef.id`, else the `accessorKey`\n * with `.` → `_`, else a string `header`.\n */\nfunction unsizedColumnIds<TData, TValue>(defs: readonly ColumnDef<TData, TValue>[]): Set<string> {\n const out = new Set<string>();\n const walk = (list: readonly ColumnDef<TData, TValue>[]) => {\n for (const def of list) {\n const group = def as { columns?: ColumnDef<TData, TValue>[] };\n if (group.columns) {\n walk(group.columns);\n continue;\n }\n if (def.size !== undefined) continue;\n const accessorKey = (def as { accessorKey?: string | number }).accessorKey;\n const id =\n def.id ??\n (accessorKey !== undefined\n ? String(accessorKey).replace(/\\./gu, \"_\")\n : typeof def.header === \"string\"\n ? def.header\n : undefined);\n if (id) out.add(id);\n }\n };\n walk(defs);\n return out;\n}\n\n// ─── Column resizing (#12) ────────────────────────────────────────────────────\n\n/**\n * Explicit width/min/max triad for one column at its CURRENT size.\n *\n * The table is auto-layout (see the note on `pinnedCellGeometry` below), so\n * without an explicit width an unpinned column is pure browser auto-layout —\n * `column.getSize()` can change (via a drag or a keyboard resize) with\n * nothing rendering differently. A pinned cell already gets this triad from\n * `pinnedCellGeometry`'s own `style`; this is the same triad for the\n * UNPINNED case, so every call site can compute it once and use it in both\n * the pinned-or-not branches (`geometry?.style ?? resizeWidthStyle(size)`).\n * Every call site gates this behind `enableColumnResizing`, so a table that\n * doesn't opt in renders byte-identical markup to before this feature\n * existed.\n */\nfunction resizeWidthStyle(size: number): React.CSSProperties {\n return { width: size, minWidth: size, maxWidth: size };\n}\n\n// ─── Row-selection column (#11) ──────────────────────────────────────────────\n//\n// `flexRender` mounts a function `header`/`cell` as a real React component\n// (`React.createElement(Comp, props)`, not a bare function call — see\n// `@tanstack/react-table`'s `flexRender`), so these are ordinary components:\n// hooks (`useLocale`) are safe inside them.\n\n/**\n * The row's own \"primary identifier\" — the first visible DATA column's value,\n * skipping display columns that carry no `accessorKey`/`accessorFn` (e.g. a\n * leading `createSelectionColumn()` checkbox, or a decorative avatar column).\n * `column.accessorFn` is public TanStack API, populated for any\n * `accessorKey`/`accessorFn` column and `undefined` for a pure display column\n * (`core/column.ts`) — so this is a reliable \"is this a data column\" test.\n * Shared by `rowActionName` (#337) and the selection column's per-row\n * accessible name (#11 I4/I6), so a leading selection column can't silently\n * degrade either one to its generic fallback.\n */\nfunction firstDataCellValue<TData>(row: Row<TData>): string | undefined {\n for (const cell of row.getVisibleCells()) {\n if (!cell.column.accessorFn) continue;\n const value = cell.getValue();\n if (typeof value === \"string\" && value.trim() !== \"\") return value;\n if (typeof value === \"number\") return String(value);\n }\n return undefined;\n}\n\n/**\n * Select-all header cell. Radix `Checkbox` renders a genuinely distinct\n * `indeterminate` glyph + `aria-checked=\"mixed\"` for a partial page\n * selection (see `checkbox.tsx`), so the visual and the accessible state\n * agree without any extra wiring here.\n */\nfunction SelectAllHeaderCell<TData>({ table }: { table: TanstackTable<TData> }) {\n const { t } = useLocale();\n const allSelected = table.getIsAllPageRowsSelected();\n const someSelected = table.getIsSomePageRowsSelected();\n return (\n <Checkbox\n data-slot=\"data-table-select-all\"\n checked={allSelected ? true : someSelected ? \"indeterminate\" : false}\n onCheckedChange={(checked) => table.toggleAllPageRowsSelected(checked === true)}\n aria-label={t(\"data.table.selectAllRows\")}\n />\n );\n}\n\n/**\n * Per-row checkbox cell — disabled when `enableRowSelection` excludes the\n * row. Names each checkbox from the row's own data (#11 I4) instead of the\n * identical generic label every row previously shared, using the same\n * \"first data cell\" lookup `rowActionName` (#337) already uses.\n */\nfunction SelectRowCell<TData>({ row }: { row: Row<TData> }) {\n const { t } = useLocale();\n const name = firstDataCellValue(row);\n return (\n <Checkbox\n data-slot=\"data-table-select-cell\"\n checked={row.getIsSelected()}\n disabled={!row.getCanSelect()}\n onCheckedChange={(checked) => row.toggleSelected(checked === true)}\n aria-label={name ? t(\"data.table.selectRowNamed\", { name }) : t(\"data.table.selectRow\")}\n />\n );\n}\n\n/**\n * Ready-made checkbox selection column (#11): header select-all (with a real\n * `indeterminate` state for a partial page selection) + a per-row checkbox,\n * both built on `@elabs-ai/components-ui`'s `Checkbox` — never hand-roll one.\n *\n * Add it to `columns` and pair it with `rowSelection` / `onRowSelectionChange`\n * (or leave both uncontrolled and read `table.getSelectedRowModel()` from a\n * `toolbar` render-prop to build a bulk-action bar).\n *\n * Declares an explicit `size` (40px) so it plays nicely if a caller pins it —\n * every pinned column must declare one (#333) — without the dev warning.\n */\nexport function createSelectionColumn<TData>(): ColumnDef<TData> {\n return {\n id: \"select\",\n size: 40,\n enableSorting: false,\n enableHiding: false,\n header: ({ table }) =>\n // #11 C1: `toggleAllPageRowsSelected` wipes-then-sets on every row when\n // `enableMultiRowSelection` is off (TanStack's `mutateRowIsSelected`), so\n // a select-all header under single-select leaves only the LAST row\n // selected and pins the header at indeterminate forever. Suppress it.\n table.options.enableMultiRowSelection === false ? null : (\n <SelectAllHeaderCell table={table} />\n ),\n cell: ({ row }) => <SelectRowCell row={row} />,\n };\n}\n\n// ─── Row drag-reorder (#13) ─────────────────────────────────────────────────\n\n/** Render-prop payload `SortableDataRow` hands its child — the live dnd-kit\n * registration for one row. */\ninterface SortableRowRenderArgs {\n setNodeRef: (node: HTMLElement | null) => void;\n setActivatorNodeRef: (node: HTMLElement | null) => void;\n attributes: DraggableAttributes;\n listeners: DraggableSyntheticListeners;\n isDragging: boolean;\n style: React.CSSProperties;\n}\n\n/**\n * Per-row `@dnd-kit` registration, defined ONCE at module level.\n *\n * This must be a real component, not a hook call inlined into `rows.map()`\n * (that would call `useSortable` a variable number of times across renders —\n * the classic \"hook in a loop\" Rules-of-Hooks violation the moment the row\n * count changes) and not a component DEFINED inside `DataTableInner`'s body\n * either (a function created fresh every render gets a new `type` identity,\n * so React would tear down and remount the whole row subtree, including\n * dnd-kit's own internal drag state, on every re-render). A stable top-level\n * component keyed by `id` gives every row its own persistent `useSortable`\n * state via ordinary type+key reconciliation.\n *\n * `transition: null` is deliberate — dnd-kit's own transition is a raw\n * inline `ms` duration, which would bypass the gated `duration-*`/`ease-*`\n * utilities (quality-gates.md \"Motion-tokened\"). The moving row instead gets\n * `transition-transform duration-base ease-standard motion-reduce:transition-none`\n * as a class at the call site; only the live `transform` stays inline.\n */\nfunction SortableDataRow({\n id,\n disabled,\n attributesOverride,\n children,\n}: {\n id: string;\n disabled?: boolean;\n /**\n * `rowReorderHandle: \"row\"` applies `attributes`/`listeners` straight to\n * the `<tr>` (no separate activator element), so dnd-kit's DEFAULT\n * `role=\"button\"` would replace the table's own `role=\"row\"` on that\n * element — destroying its row semantics. Override the role in that mode\n * only; `\"cell\"` mode leaves `role` unset because the grip `<button>` —\n * not the `<tr>` — receives `attributes`/`listeners`. `roleDescription` is\n * overridden in BOTH modes (#98) — it carries dnd-kit's localized\n * `aria-roledescription`, which the activator needs regardless of which\n * element is the activator.\n */\n attributesOverride?: { role?: string; roleDescription?: string; tabIndex?: number };\n children: (args: SortableRowRenderArgs) => ReactNode;\n}) {\n const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, isDragging } =\n useSortable({ id, disabled, transition: null, attributes: attributesOverride });\n return (\n <>\n {children({\n setNodeRef,\n setActivatorNodeRef,\n attributes,\n listeners,\n isDragging,\n style: { transform: CSS.Transform.toString(transform) },\n })}\n </>\n );\n}\n\n// ─── Component (inner, generic) ───────────────────────────────────────────────\n\n/**\n * Branded TanStack Table wrapper with sorting, global filtering, column\n * visibility and optional pagination. The toolbar render-prop hands you the\n * table instance so SearchInput / FacetFilter / ColumnPicker can drive it.\n *\n * Every slice (sorting / columnVisibility / columnFilters / pagination) is\n * independently controllable. Uncontrolled slices are managed internally.\n * Pass `manualSorting` / `manualFiltering` / `manualPagination` to opt into\n * server-driven data; `onServerChange` fires after each slice change so the\n * app can re-fetch.\n *\n * Accepts a forwarded `ref` to the outermost wrapper `<div>` and spreads any\n * additional HTML div props (e.g. `id`, `aria-*`, `data-*`) onto that element.\n */\nfunction DataTableInner<TData, TValue>(\n {\n columns,\n data,\n toolbar,\n enablePagination = false,\n pageSize = 10,\n hidePaginationWhenSingle = true,\n\n // Global filter\n globalFilter: globalFilterProp,\n onGlobalFilterChange,\n\n // Controlled slices\n sorting: sortingProp,\n onSortingChange: onSortingChangeProp,\n columnVisibility: columnVisibilityProp,\n onColumnVisibilityChange: onColumnVisibilityChangeProp,\n columnFilters: columnFiltersProp,\n onColumnFiltersChange: onColumnFiltersChangeProp,\n pagination: paginationProp,\n onPaginationChange: onPaginationChangeProp,\n columnPinning: columnPinningProp,\n onColumnPinningChange: onColumnPinningChangeProp,\n enableColumnResizing = false,\n columnResizeMode = \"onChange\",\n columnSizing: columnSizingProp,\n onColumnSizingChange: onColumnSizingChangeProp,\n rowSelection: rowSelectionProp,\n onRowSelectionChange: onRowSelectionChangeProp,\n enableRowSelection,\n enableMultiRowSelection,\n getRowId,\n\n // Saved views rehydration\n initialView,\n\n // Server-side model\n manualSorting = false,\n manualFiltering = false,\n manualPagination = false,\n rowCount,\n pageCount,\n onServerChange,\n\n // Loading\n loading = false,\n loadingRows,\n\n // Virtualization\n enableRowVirtualization = false,\n estimateRowHeight = 40,\n overscan = 8,\n maxBodyHeight = \"32rem\",\n\n zebra = true,\n\n // Row drag-reorder (#13)\n enableRowReorder = false,\n onRowReorder,\n rowReorderHandle = \"cell\",\n\n onRowClick,\n rowActionLabel,\n rowClassName,\n caption,\n emptyMessage = \"No results.\",\n className,\n ...rest\n }: DataTableProps<TData, TValue>,\n ref: React.Ref<HTMLDivElement>,\n) {\n // Component microcopy goes through the locale seam (ADR 0017) — a screen-reader\n // user in a non-English locale has no workaround for a hardcoded accessible name.\n // `dir` also drives column-resize direction below (#12 review, P1): the resize\n // handle already sits at the column's logical `end` edge (`end-0`, which\n // Tailwind's logical properties flip to the physical LEFT under RTL), so both\n // TanStack's own pointer-drag math and the hand-rolled keyboard path must be\n // told the active direction too, or dragging/pressing an arrow moves the width\n // opposite the visible boundary.\n const { t, dir, formatNumber } = useLocale();\n\n // ── Controlled/uncontrolled detection ────────────────────────────────────\n const isSortingControlled = sortingProp !== undefined;\n const isColumnVisibilityControlled = columnVisibilityProp !== undefined;\n const isColumnFiltersControlled = columnFiltersProp !== undefined;\n const isPaginationControlled = paginationProp !== undefined;\n const isFilterControlled = globalFilterProp !== undefined;\n const isColumnPinningControlled = columnPinningProp !== undefined;\n const isColumnSizingControlled = columnSizingProp !== undefined;\n const isRowSelectionControlled = rowSelectionProp !== undefined;\n\n // ── Internal state (only drives a slice when uncontrolled) ───────────────\n const [internalSorting, setInternalSorting] = useState<SortingState>(\n () => initialView?.sorting ?? [],\n );\n const [internalColumnVisibility, setInternalColumnVisibility] = useState<VisibilityState>(\n () => initialView?.columnVisibility ?? {},\n );\n const [internalColumnFilters, setInternalColumnFilters] = useState<ColumnFiltersState>(\n () => initialView?.columnFilters ?? [],\n );\n const [internalPagination, setInternalPagination] = useState<PaginationState>(\n () =>\n initialView?.pagination ?? {\n pageIndex: 0,\n pageSize,\n },\n );\n const [internalGlobalFilter, setInternalGlobalFilter] = useState<string>(\n () => initialView?.globalFilter ?? \"\",\n );\n const [internalColumnPinning, setInternalColumnPinning] = useState<ColumnPinningState>(\n () => initialView?.columnPinning ?? { left: [], right: [] },\n );\n const [internalColumnSizing, setInternalColumnSizing] = useState<ColumnSizingState>(\n () => initialView?.columnSizing ?? {},\n );\n const [internalRowSelection, setInternalRowSelection] = useState<RowSelectionState>(\n () => initialView?.rowSelection ?? {},\n );\n\n // ── Resolved state (controlled wins over internal) ───────────────────────\n const sorting = isSortingControlled ? sortingProp : internalSorting;\n const columnVisibility = isColumnVisibilityControlled\n ? columnVisibilityProp\n : internalColumnVisibility;\n const columnFilters = isColumnFiltersControlled ? columnFiltersProp : internalColumnFilters;\n const pagination = isPaginationControlled ? paginationProp : internalPagination;\n const globalFilter = isFilterControlled ? globalFilterProp : internalGlobalFilter;\n const columnPinning = isColumnPinningControlled ? columnPinningProp : internalColumnPinning;\n const columnSizing = isColumnSizingControlled ? columnSizingProp : internalColumnSizing;\n const rowSelection = isRowSelectionControlled ? rowSelectionProp : internalRowSelection;\n\n // ── Refs for post-change server callback ─────────────────────────────────\n // We need the current values of ALL slices when any one fires; use refs to\n // avoid stale closures without adding them as deps.\n const sortingRef = useRef(sorting);\n sortingRef.current = sorting;\n const columnFiltersRef = useRef(columnFilters);\n columnFiltersRef.current = columnFilters;\n const paginationRef = useRef(pagination);\n paginationRef.current = pagination;\n const globalFilterRef = useRef(globalFilter);\n globalFilterRef.current = globalFilter;\n const columnVisibilityRef = useRef(columnVisibility);\n columnVisibilityRef.current = columnVisibility;\n const columnPinningRef = useRef(columnPinning);\n columnPinningRef.current = columnPinning;\n const columnSizingRef = useRef(columnSizing);\n columnSizingRef.current = columnSizing;\n const rowSelectionRef = useRef(rowSelection);\n rowSelectionRef.current = rowSelection;\n\n // ── Dev-only guard: manualPagination needs a total to compute page count ──\n // Without `rowCount` (or `pageCount`), TanStack's `getPageCount()` falls back\n // to the CURRENT PAGE's row count (manual mode has no full row model), so the\n // pager silently reads \"Page 1 of 1\" with Next permanently disabled. Warn\n // once per mount so the missing prop is diagnosable instead of silent (#227).\n const warnedMissingRowCountRef = useRef(false);\n useEffect(() => {\n if (\n process.env.NODE_ENV !== \"production\" &&\n manualPagination &&\n rowCount === undefined &&\n pageCount === undefined &&\n !warnedMissingRowCountRef.current\n ) {\n warnedMissingRowCountRef.current = true;\n console.warn(\n \"[DataTable] `manualPagination` is true but neither `rowCount` nor `pageCount` was \" +\n 'provided — the pager will appear stuck (\"Page 1 of 1\", Next disabled). Pass ' +\n \"`rowCount` (or `pageCount`) so the pager can compute the total.\",\n );\n }\n }, [manualPagination, rowCount, pageCount]);\n\n // ── Dev-only guard: manualPagination + rowSelection with no getRowId ──────\n // Under `manualPagination` each page IS a fresh `data` array, so TanStack's\n // default index-based row id restarts at `0` on every page — a selection\n // made on page 1's row 0 can silently apply to page 2's row 0 too (#11 I3).\n // Warn once per mount so this footgun is diagnosable instead of silent (same\n // idiom as the #227 warning above). Heuristic, not full usage tracing: fires\n // whenever selection LOOKS wired up (controlled, or a change handler was\n // passed) — it cannot see an uncontrolled table that never renders a\n // selection column at all.\n const warnedManualSelectionRef = useRef(false);\n useEffect(() => {\n if (\n process.env.NODE_ENV !== \"production\" &&\n manualPagination &&\n getRowId === undefined &&\n (isRowSelectionControlled || onRowSelectionChangeProp !== undefined) &&\n !warnedManualSelectionRef.current\n ) {\n warnedManualSelectionRef.current = true;\n console.warn(\n \"[DataTable] `rowSelection` is wired up under `manualPagination` with no `getRowId` \" +\n \"— each page is a fresh `data` array, so the default index-based id restarts at \" +\n '\"0\" per page and a selection made on one page can silently apply to a different ' +\n \"record on the next. Pass `getRowId` so selection is keyed to a stable identity \" +\n \"instead of position.\",\n );\n }\n }, [manualPagination, getRowId, isRowSelectionControlled, onRowSelectionChangeProp]);\n\n // ── Dev-only guard: enableRowReorder + active sorting (#13) ───────────────\n // Both keep working — this doesn't disable anything — but a sort re-orders\n // the very rows a drag just moved, which reads as broken rather than merely\n // confusing. Warn once per mount, same idiom as the two guards above.\n const warnedReorderSortingRef = useRef(false);\n useEffect(() => {\n if (\n process.env.NODE_ENV !== \"production\" &&\n enableRowReorder &&\n sorting.length > 0 &&\n !warnedReorderSortingRef.current\n ) {\n warnedReorderSortingRef.current = true;\n console.warn(\n \"[DataTable] `enableRowReorder` is set while a column is sorted — the sort will \" +\n \"keep re-ordering rows out from under a manual drag. Clear `sorting` (or avoid \" +\n \"enabling both at once) so a drag's new order stays stable.\",\n );\n }\n }, [enableRowReorder, sorting.length]);\n\n // ── Dev-only guard: enableRowReorder + enableRowVirtualization (#13) ──────\n // A windowed table can't keep dnd-kit's sortable list in sync with a\n // virtualizer that only mounts a subset of rows, so the two are mutually\n // exclusive — virtualization wins (same precedent as enablePagination vs.\n // enableRowVirtualization) and reorder is silently disabled below\n // (`rowReorderActive`). This warning is the diagnostic for why.\n const warnedReorderVirtualizedRef = useRef(false);\n useEffect(() => {\n if (\n process.env.NODE_ENV !== \"production\" &&\n enableRowReorder &&\n enableRowVirtualization &&\n !warnedReorderVirtualizedRef.current\n ) {\n warnedReorderVirtualizedRef.current = true;\n console.warn(\n \"[DataTable] `enableRowReorder` has no effect while `enableRowVirtualization` is \" +\n \"set — the two are mutually exclusive. Virtualization wins; row reorder is disabled.\",\n );\n }\n }, [enableRowReorder, enableRowVirtualization]);\n\n // Only wired up in the non-virtualized body — see the warning above.\n const rowReorderActive = enableRowReorder && !enableRowVirtualization;\n const hasGripColumn = rowReorderActive && rowReorderHandle === \"cell\";\n\n const reorderSensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),\n useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),\n );\n // Backing store for `getReorderRowId` (defined below, once `rows` is in\n // scope) — see its own doc comment for why a WeakMap keyed by row object\n // reference is the round-1 fix for findings 1 & 3.\n const reorderIdentityMapRef = useRef<WeakMap<object, string>>(new WeakMap());\n const reorderIdentityCounterRef = useRef(0);\n // Positions in `data` whose record REPEATS an object reference that already\n // appeared earlier in the array — 2nd and later occurrences only (round-2\n // finding 6). `getReorderRowId` below keys its identity on the record's own\n // object reference, which is exactly what makes an id survive the array\n // REPLACEMENT every reorder idiom performs; the cost is that a record the\n // caller listed twice IS one reference, so both rows would be handed one id\n // — one React key, one dnd-kit registration, and a drop that can only ever\n // name the first occurrence. The positions listed here get their own data\n // index folded into the id so the occurrences stay separately addressable.\n // Only the repeats are suffixed, so a table with no repeated record keeps\n // byte-identical ids (and with them the round-1 focus restore).\n const reorderRepeatedPositions = useMemo(() => {\n const repeats = new Set<number>();\n if (!rowReorderActive) return repeats;\n const seen = new Set<unknown>();\n data.forEach((record, index) => {\n if (record === null || typeof record !== \"object\") return;\n if (seen.has(record)) repeats.add(index);\n else seen.add(record);\n });\n return repeats;\n }, [data, rowReorderActive]);\n // The component's OWN `aria-live=\"polite\"` announcer state — round-1\n // finding 4 (dnd-kit's built-in region is hardcoded `assertive` with no\n // override). `reorderLastAnnouncedPositionRef` de-dupes a same-position\n // re-fire (the pickup self-collision, a no-op arrow press at a boundary).\n const [reorderLiveMessage, setReorderLiveMessage] = useState(\"\");\n const reorderLastAnnouncedPositionRef = useRef<number | null>(null);\n\n /** Fire onServerChange with the LATEST slice values (post-update). */\n function fireServerChange(overrides: Partial<DataTableServerArgs> = {}) {\n if (!onServerChange) return;\n onServerChange({\n pagination: paginationRef.current,\n sorting: sortingRef.current,\n columnFilters: columnFiltersRef.current,\n globalFilter: globalFilterRef.current,\n ...overrides,\n });\n }\n\n // ── Updater helpers — all five slices resolve a functional updater against\n // their *Ref.current (the post-update value), never the render-closure\n // variable, so the resolution stays correct once these callbacks are\n // memoized (a useCallback wrap or the React Compiler) ──────────────────────\n function resolveSorting(updater: Parameters<OnChangeFn<SortingState>>[0]): SortingState {\n return typeof updater === \"function\" ? updater(sortingRef.current) : updater;\n }\n function resolveColumnVisibility(\n updater: Parameters<OnChangeFn<VisibilityState>>[0],\n ): VisibilityState {\n return typeof updater === \"function\" ? updater(columnVisibilityRef.current) : updater;\n }\n function resolveColumnFilters(\n updater: Parameters<OnChangeFn<ColumnFiltersState>>[0],\n ): ColumnFiltersState {\n return typeof updater === \"function\" ? updater(columnFiltersRef.current) : updater;\n }\n function resolvePagination(updater: Parameters<OnChangeFn<PaginationState>>[0]): PaginationState {\n return typeof updater === \"function\" ? updater(paginationRef.current) : updater;\n }\n function resolveGlobalFilter(updater: Parameters<OnChangeFn<string>>[0]): string {\n return typeof updater === \"function\" ? updater(globalFilterRef.current) : updater;\n }\n function resolveColumnPinning(\n updater: Parameters<OnChangeFn<ColumnPinningState>>[0],\n ): ColumnPinningState {\n return typeof updater === \"function\" ? updater(columnPinningRef.current) : updater;\n }\n function resolveColumnSizing(\n updater: Parameters<OnChangeFn<ColumnSizingState>>[0],\n ): ColumnSizingState {\n return typeof updater === \"function\" ? updater(columnSizingRef.current) : updater;\n }\n function resolveRowSelection(\n updater: Parameters<OnChangeFn<RowSelectionState>>[0],\n ): RowSelectionState {\n return typeof updater === \"function\" ? updater(rowSelectionRef.current) : updater;\n }\n\n // ── Row models — omit client model for manual slices ─────────────────────\n const sortedRowModel = manualSorting ? {} : { getSortedRowModel: getSortedRowModel() };\n const filteredRowModel = manualFiltering ? {} : { getFilteredRowModel: getFilteredRowModel() };\n // Only attach the client pagination row model when we actually paginate locally.\n // Under `manualPagination`, TanStack ignores a supplied `getPaginationRowModel`\n // (it returns the pre-pagination rows — i.e. the page the app already fetched),\n // so attaching it there is dead per-render work. `(A && !B) || B === A || B`,\n // but the honest single-branch form documents that manual mode needs no model.\n const paginationRowModel =\n enablePagination && !manualPagination ? { getPaginationRowModel: getPaginationRowModel() } : {};\n\n // ── Table instance ────────────────────────────────────────────────────────\n const table = useReactTable({\n data,\n columns,\n state: {\n sorting,\n columnVisibility,\n columnFilters,\n globalFilter,\n pagination,\n columnPinning,\n columnSizing,\n rowSelection,\n },\n\n // Sorting\n onSortingChange: (updater) => {\n const next = resolveSorting(updater);\n if (!isSortingControlled) setInternalSorting(next);\n onSortingChangeProp?.(updater);\n if (manualSorting) {\n sortingRef.current = next;\n fireServerChange({ sorting: next });\n }\n },\n\n // Column visibility\n onColumnVisibilityChange: (updater) => {\n const next = resolveColumnVisibility(updater);\n if (!isColumnVisibilityControlled) setInternalColumnVisibility(next);\n onColumnVisibilityChangeProp?.(updater);\n // column visibility is never a \"manual\" server concern\n },\n\n // Column filters\n onColumnFiltersChange: (updater) => {\n const next = resolveColumnFilters(updater);\n if (!isColumnFiltersControlled) setInternalColumnFilters(next);\n onColumnFiltersChangeProp?.(updater);\n if (manualFiltering) {\n columnFiltersRef.current = next;\n fireServerChange({ columnFilters: next });\n }\n },\n\n // Global filter\n onGlobalFilterChange: (updater) => {\n const next = resolveGlobalFilter(updater);\n if (!isFilterControlled) setInternalGlobalFilter(next);\n onGlobalFilterChange?.(next);\n if (manualFiltering) {\n globalFilterRef.current = next;\n fireServerChange({ globalFilter: next });\n }\n },\n\n // Pagination\n onPaginationChange: (updater) => {\n const next = resolvePagination(updater);\n if (!isPaginationControlled) setInternalPagination(next);\n onPaginationChangeProp?.(updater);\n if (manualPagination) {\n paginationRef.current = next;\n fireServerChange({ pagination: next });\n }\n },\n\n // Column pinning — a LAYOUT slice, so unlike sorting/filtering/pagination it\n // never fires `onServerChange`: freezing a column changes nothing the server\n // would need to re-query.\n onColumnPinningChange: (updater) => {\n const next = resolveColumnPinning(updater);\n if (!isColumnPinningControlled) setInternalColumnPinning(next);\n onColumnPinningChangeProp?.(updater);\n },\n\n // Column resizing (#12) — a LAYOUT slice, like column pinning: a column's\n // width changes nothing the server would need to re-query, so this never\n // fires onServerChange either. Routed through by BOTH the pointer path\n // (TanStack's own `header.getResizeHandler()`, wired below) and the\n // keyboard path (`handleResizeKeyDown`, via `table.setColumnSizing`) so\n // the two input modes can never diverge in controlled/uncontrolled\n // behaviour.\n columnResizeMode,\n // RTL fix (#12 review, P1): TanStack's pointer-drag math hardcodes LTR\n // unless told otherwise — `deltaDirection = columnResizeDirection ===\n // 'rtl' ? -1 : 1` internally — so under `dir=\"rtl\"` (the resize handle's\n // own edge already flips via `end-0`, see the `useLocale()` call above)\n // dragging would otherwise move the column's width opposite the visible\n // boundary. `handleResizeKeyDown` below mirrors this for the keyboard path.\n columnResizeDirection: dir,\n enableColumnResizing,\n onColumnSizingChange: (updater) => {\n const next = resolveColumnSizing(updater);\n if (!isColumnSizingControlled) setInternalColumnSizing(next);\n onColumnSizingChangeProp?.(updater);\n },\n\n // Row selection (#11) — also a LAYOUT/UI slice, so it never fires\n // onServerChange: which rows are checked changes nothing the server\n // would need to re-query.\n onRowSelectionChange: (updater) => {\n const next = resolveRowSelection(updater);\n if (!isRowSelectionControlled) setInternalRowSelection(next);\n onRowSelectionChangeProp?.(updater);\n },\n enableRowSelection,\n enableMultiRowSelection,\n getRowId,\n\n getCoreRowModel: getCoreRowModel(),\n ...sortedRowModel,\n ...filteredRowModel,\n ...paginationRowModel,\n\n // Server-side options\n manualSorting,\n manualFiltering,\n manualPagination,\n ...(rowCount !== undefined ? { rowCount } : {}),\n ...(pageCount !== undefined ? { pageCount } : {}),\n // No `initialState`: every slice is driven explicitly via `state` above\n // (internal slices are seeded from `initialView` at useState init), so a\n // TanStack `initialState` would be dead/misleading.\n });\n\n const rows = table.getRowModel().rows;\n // colSpan for spacer / empty / skeleton cells must match the number of cells a\n // real data row renders (`row.getVisibleCells()`) — use VISIBLE leaf columns so a\n // hidden column (a first-class slice here via columnVisibility + ColumnPicker)\n // doesn't make those rows over-span.\n const colCount = table.getVisibleLeafColumns().length;\n // Virtualized-table ARIA: only a window of rows is mounted, so assistive tech\n // can't infer the true size from the DOM. aria-rowcount counts the header row(s)\n // plus every data row; rendered data rows carry an absolute 1-based aria-rowindex\n // (header rows occupy 1..headerRowCount). Falls back to rows.length for the\n // client path; uses the server `rowCount` total when provided.\n const headerRowCount = table.getHeaderGroups().length;\n const ariaRowCount = (rowCount ?? rows.length) + headerRowCount;\n\n // ── Row drag-reorder (#13) ────────────────────────────────────────────────\n // `rowActionName` (defined below, but hoisted as a function declaration) is\n // the SAME row-naming lookup `onRowClick`'s hidden button uses (#337) —\n // reusing it means a reorder announcement names a row exactly the way its\n // click target already does, rather than inventing a second convention.\n function reorderRowName(id: string): string {\n const row = rows.find((r) => getReorderRowId(r) === id);\n return row ? rowActionName(row) : id;\n }\n function reorderPosition(id: string): number {\n return rows.findIndex((r) => getReorderRowId(r) === id) + 1;\n }\n\n // ── Stable identity for drag reconciliation (round-1 fix, findings 1 & 3) ──\n // `getRowId`'s own doc comment above states TanStack's fallback: default row\n // ids are assigned ONCE per row object when the core row model is built from\n // the current `data` ARRAY REFERENCE, then carried by reference through\n // sort/filter — but a `data` array REPLACEMENT (exactly what every\n // `onRowReorder` consumer does: `arrayMove`/`slice`+`splice`/immer all\n // return a new array) rebuilds the core row model and reassigns ids by\n // POSITION IN THE NEW ARRAY. So the id that used to denote \"the row now at\n // index 1\" keeps denoting index 1 even though a different record moved\n // there — which is what let a keyboard drop leave focus on the wrong row\n // (a different record now sits at the id the focus restore targets).\n // Requiring every consumer to hand-roll `getRowId` would leave the DEFAULT\n // configuration broken, so when the caller hasn't supplied one, mint an id\n // keyed by the row's own OBJECT REFERENCE (`row.original`) in a `WeakMap` —\n // unlike TanStack's default, this id follows the object wherever it lands\n // in a new array, because every reorder idiom MOVES the element reference,\n // it never clones it. When `getRowId` IS supplied it is already exactly\n // this kind of identity, so it's reused as-is instead of minting a second,\n // divergent id namespace.\n function getReorderRowId(row: Row<TData>): string {\n if (getRowId) return row.id;\n const original: unknown = row.original;\n if (original !== null && typeof original === \"object\") {\n const map = reorderIdentityMapRef.current;\n let id = map.get(original);\n if (id === undefined) {\n id = `__reorder-${reorderIdentityCounterRef.current++}`;\n map.set(original, id);\n }\n // A repeated record shares ONE object reference, so the id minted above\n // is by construction identical for both of its rows — round-2 finding\n // 6. Fold the data position into the repeats so each occupant is its\n // own draggable. Two identical records are interchangeable to the user,\n // so the weaker cross-replacement stability of a suffixed id costs\n // nothing the first-occurrence rule doesn't already give back.\n return reorderRepeatedPositions.has(row.index) ? `${id}__${row.index}` : id;\n }\n // Primitive `TData` (rare) has no object reference to key off — same\n // documented limitation `getRowId`'s own comment already carries for\n // TanStack's own default identity.\n return row.id;\n }\n\n // dnd-kit's own `Accessibility` component's `LiveRegion` hardcodes\n // `aria-live=\"assertive\"` with no way to override it from `DndContext`\n // (`@dnd-kit/accessibility` 3.1.1 accepts an `ariaLiveType` prop on\n // `LiveRegion` itself, but nothing forwards one through `accessibility`) —\n // round-1 finding 4. `.claude/rules/accessibility.md` reserves assertive\n // for terminal errors (`role=\"alert\"`); a sortable list's own position\n // updates are `polite` status. So dnd-kit's built-in announcer is silenced\n // below (every callback returns `undefined`, which `useAnnouncement`\n // treats as \"no update\" — the region stays permanently empty and never\n // fires) and DataTable renders its OWN `aria-live=\"polite\"` region\n // (`reorderLiveMessage`, wired to the `data-table-reorder-live-region`\n // node near the bottom of this function) from the `onDragStart`/\n // `onDragOver`/`onDragEnd`/`onDragCancel` handlers below.\n const silentDragAnnouncements: Announcements = {\n onDragStart: () => undefined,\n onDragOver: () => undefined,\n onDragEnd: () => undefined,\n onDragCancel: () => undefined,\n };\n\n /**\n * Pickup always announces — it's the start of a new, meaningful gesture.\n * Seeding `reorderLastAnnouncedPositionRef` with the row's OWN starting\n * position (not `null`) is what suppresses dnd-kit's immediate self-\n * collision `onDragOver` (over === active, at the same position) that\n * otherwise fires in the same tick and would stomp this message before it\n * is ever observable (WCAG 4.1.3 needs it heard, not just rendered).\n */\n function handleRowDragStart(event: DragStartEvent) {\n const activeRowId = String(event.active.id);\n reorderLastAnnouncedPositionRef.current = reorderPosition(activeRowId);\n setReorderLiveMessage(t(\"data.table.reorderPickedUp\", { name: reorderRowName(activeRowId) }));\n }\n\n /**\n * Announces a real position change only — round-1 finding 4 measured 4\n * announcements for a 2-step move, one of them a same-position self-\n * collision that buried the \"picked up\" message. De-duping on the actual\n * computed position (not on the raw event) means a screen reader hears one\n * `polite` (queued, non-interrupting) announcement per genuine move, not\n * one per keystroke.\n */\n function handleRowDragOver(event: DragOverEvent) {\n const { active, over } = event;\n if (!over) return;\n const position = reorderPosition(String(over.id));\n if (position === reorderLastAnnouncedPositionRef.current) return;\n reorderLastAnnouncedPositionRef.current = position;\n setReorderLiveMessage(\n t(\"data.table.reorderMoved\", {\n name: reorderRowName(String(active.id)),\n position,\n total: rows.length,\n }),\n );\n }\n\n function handleRowDragCancel(event: DragCancelEvent) {\n const activeRowId = String(event.active.id);\n setReorderLiveMessage(\n t(\"data.table.reorderCancelled\", {\n name: reorderRowName(activeRowId),\n position: reorderPosition(activeRowId),\n total: rows.length,\n }),\n );\n reorderLastAnnouncedPositionRef.current = null;\n }\n\n /**\n * The component never mutates `data` itself (D5 — presentation layer, not\n * an SDK): it only reports the move, the same \"controlled slice\" contract\n * every other DataTable feature follows. A no-op drop (dropped on itself,\n * or outside any droppable) fires nothing on the data callback, but still\n * announces (matching the \"dropped back where it started\" reality).\n *\n * `from`/`to` resolve against the ORIGINAL `data` array the caller passed\n * in, never against the sorted/paginated VIEW (`rows`) — round-1 finding 1.\n * Reporting `rows.findIndex(...)` positions meant a caller doing\n * `arrayMove(data, from, to)` (the idiom both shipped stories use) silently\n * moved the WRONG records whenever an active sort or a client-side page\n * had changed which record sat at which view position — measured: a\n * paginated drag on page 2 reported `(0, 1, …)`, corrupting `data[0]`/\n * `data[1]` on page 1. Resolving against `data` itself makes the contract\n * \"indices into the `data` you gave me\" — correct under any sort/filter,\n * correct under client-side pagination (the dragged record's true index in\n * the full array), and correct under `manualPagination` too (there `data`\n * IS the current page, so `from`/`to` are page-relative, which is exactly\n * what a caller reordering that page's own array needs).\n */\n function handleRowDragEnd(event: DragEndEvent) {\n const { active, over } = event;\n const activeRowId = String(active.id);\n setReorderLiveMessage(\n t(\"data.table.reorderDropped\", {\n name: reorderRowName(activeRowId),\n position: reorderPosition(String(over ? over.id : active.id)),\n total: rows.length,\n }),\n );\n reorderLastAnnouncedPositionRef.current = null;\n\n if (!over || active.id === over.id) return;\n const movedRow = rows.find((r) => getReorderRowId(r) === activeRowId);\n const targetRow = rows.find((r) => getReorderRowId(r) === String(over.id));\n if (!movedRow || !targetRow) return;\n // Round-2 finding 6: this used to build a `Map` keyed by `row.original`\n // and read `from`/`to` out of it. A `data` array that repeats a record —\n // the same object reference, or the same primitive, at two positions —\n // can only occupy ONE slot in such a map, so the later occurrence was\n // reported as the earlier one and the documented `arrayMove(data, from,\n // to)` idiom moved a row the user never dragged, silently. `Row.index` is\n // the position TanStack already assigned this row when it built the core\n // row model FROM `data`, carried by reference through sort/filter/\n // pagination (the same property the round-1 fix above relies on) — so it\n // keeps the \"indices into the `data` you gave me\" contract without the\n // value-equality lookup that collapsed the repeats.\n const from = movedRow.index;\n const to = targetRow.index;\n if (from < 0 || from >= data.length || to < 0 || to >= data.length) return;\n onRowReorder?.(from, to, movedRow.original);\n }\n\n // ── Pinning (#333) ────────────────────────────────────────────────────────\n // Are there any pinned columns at all? Everything pinning-related is gated on\n // this so a table with no pinning renders byte-identical markup to before.\n const hasLeftPinned = (columnPinning.left?.length ?? 0) > 0;\n const hasRightPinned = (columnPinning.right?.length ?? 0) > 0;\n\n // Keep keyboard focus out from UNDER the frozen block (WCAG 2.2 SC 2.4.11,\n // \"Focus Not Obscured\"). Tabbing to a control in a centre column that is\n // currently scrolled under the frozen columns makes the browser scroll it to\n // the SCROLLPORT edge — and the browser has no idea a sticky column is parked\n // there, so the focused control lands behind it, invisibly. Measured on\n // `PinnedColumns`: at scrollLeft 295 the \"Latency (ms)\" / p50 / p95 sort\n // buttons focused at viewport x 15 / 100 / 183, all inside the 17…297 frozen\n // block. `scroll-padding` is the platform's answer — it is exactly the \"don't\n // scroll content to here\" inset that `scrollIntoView` honours. Emitted only\n // when something IS pinned, so an unpinned table keeps its previous DOM.\n const pinnedScrollPadding: React.CSSProperties = {\n ...(hasLeftPinned ? { scrollPaddingInlineStart: table.getLeftTotalSize() } : {}),\n ...(hasRightPinned ? { scrollPaddingInlineEnd: table.getRightTotalSize() } : {}),\n };\n\n // Dev-only guard: a pinned column's sticky offset is `getStart(\"left\")` /\n // `getAfter(\"right\")`, i.e. the SUM OF DECLARED SIZES of the columns beside\n // it. The table is auto-layout, so a pinned column with no `size` renders at\n // whatever width its content wants while its neighbours are offset by\n // TanStack's 150px default — the pinned block then overlaps or gaps. Warn\n // once per mount so that mismatch is diagnosable instead of silent (same\n // idiom as the #227 warning above).\n //\n // Read off the RAW `columns` prop, not `column.columnDef`: TanStack merges a\n // default `size: 150` into every resolved column def, so the merged def can\n // never tell us whether the author actually declared one.\n const warnedUnsizedPinnedRef = useRef(false);\n const pinnedIds = [...(columnPinning.left ?? []), ...(columnPinning.right ?? [])];\n const unsizedIds =\n process.env.NODE_ENV === \"production\" || pinnedIds.length === 0\n ? null\n : unsizedColumnIds(columns);\n const pinnedWithoutSizeKey = unsizedIds\n ? pinnedIds.filter((id) => unsizedIds.has(id)).join(\",\")\n : \"\";\n useEffect(() => {\n if (\n process.env.NODE_ENV !== \"production\" &&\n pinnedWithoutSizeKey !== \"\" &&\n !warnedUnsizedPinnedRef.current\n ) {\n warnedUnsizedPinnedRef.current = true;\n console.warn(\n \"[DataTable] Pinned column(s) without an explicit `size` in their `ColumnDef`: \" +\n `${pinnedWithoutSizeKey}. Sticky offsets are computed from the declared sizes, so an ` +\n \"auto-width pinned column will render at a width that doesn't match its own offset. \" +\n \"Give every pinned column a `size`.\",\n );\n }\n }, [pinnedWithoutSizeKey]);\n\n /**\n * Sticky positioning for one pinned header/body cell (#333).\n *\n * Returns `null` for an unpinned column so the caller emits no `style`, no\n * `data-pinned` and no extra classes — that is what keeps a table with no\n * pinning identical to how it rendered before this feature existed.\n *\n * The offset comes from TanStack (`getStart(\"left\")` sums the widths of the\n * left-pinned columns before this one; `getAfter(\"right\")` sums the\n * right-pinned columns after it), and the same declared `size` is forced onto\n * the cell as `width`/`min`/`max` so the rendered width and the offset agree\n * under the table's auto layout.\n */\n function pinnedCellGeometry(column: Column<TData, unknown>) {\n const pinned = column.getIsPinned();\n if (pinned === false) return null;\n const size = column.getSize();\n const style: React.CSSProperties = {\n width: size,\n minWidth: size,\n maxWidth: size,\n ...(pinned === \"left\"\n ? { left: column.getStart(\"left\") }\n : { right: column.getAfter(\"right\") }),\n };\n return {\n pinned,\n style,\n // The seam between the frozen block and the scrolling block is the SOLE\n // structural cue between two regions that share one row fill and one\n // zebra stripe — delete it and a sighted user cannot tell them apart — so\n // it takes the strong rung (ADR 0010 decision test). No shadow: ADR 0020's\n // `--shadow-strength: 0` (`data-decoration=\"8|9|10\"`) would\n // erase a shadow-only cue entirely.\n //\n // It is drawn as a 1px `::after` INSIDE the cell, NOT as `border-e` /\n // `border-s`. A real border cannot work here: Tailwind's Preflight puts\n // the table in the COLLAPSED border model, and a collapsed border is\n // painted by the <table> at the cell's STATIC position — it does not\n // travel with a `position: sticky` cell, and the cell's own opaque fill\n // (which it needs, see `pinnedCellFillClass`) then paints over it. Measured\n // in Chromium on `Data/DataTable → PinnedColumns`: with `border-e` the\n // seam pixel read `143,143,143` (light `--border-strong`) at\n // scrollLeft 0 and `245,245,245` (the plain cell fill — i.e. GONE) once\n // scrolled, in every theme and on both edges. So the one cue vanished\n // exactly when the freeze was doing something. The `::after` lives in the\n // sticky cell's own stacking context, so it moves with it.\n edgeClass:\n pinned === \"left\"\n ? column.getIsLastColumn(\"left\")\n ? PINNED_SEAM_CLASS + \" after:end-0\"\n : \"\"\n : column.getIsFirstColumn(\"right\")\n ? PINNED_SEAM_CLASS + \" after:start-0\"\n : \"\",\n };\n }\n\n // ── Column resizing keyboard path (#12) ───────────────────────────────────\n // TanStack's own `header.getResizeHandler()` is pointer/touch-only — no\n // keyboard path exists in the library — so the WAI-ARIA separator-as-slider\n // practice (drag handle operable via ArrowLeft/ArrowRight when focused)\n // needs one small hand-rolled step. It goes through `table.setColumnSizing`\n // (`table.setColumnSizing = updater => table.options.onColumnSizingChange\n // ?.(updater)`, TanStack's own `ColumnSizing` feature), which is the SAME\n // `onColumnSizingChange` handler passed to `useReactTable` above — so\n // keyboard and pointer resizing share one controlled/uncontrolled code path\n // and can never diverge in behaviour.\n const RESIZE_STEP = 10;\n // ARIA fallback ceiling for the resize separator's `aria-valuemax` when the\n // column declares no explicit `maxSize` — a `ColumnDef` with no `maxSize`\n // resolves through TanStack's own default to `Number.MAX_SAFE_INTEGER`,\n // which is not a value any AT should announce, so the header below omits\n // `aria-valuemax` entirely in that case. Per the WAI-ARIA separator-as-\n // widget pattern, an ELEMENT WITH NO `aria-valuemax` is read with an\n // IMPLICIT default of 100 — so a column at its ordinary starting width\n // (150) already announces as \"150 of 100\", out of its own stated range\n // (#12 review, P2). `Math.max` with the live size at the call site below\n // keeps this always containing the current value: a column dragged past\n // this floor simply raises its own announced ceiling instead of going out\n // of range again.\n const RESIZE_UNBOUNDED_ARIA_MAX = 2000;\n function handleResizeKeyDown(event: React.KeyboardEvent, column: Column<TData, unknown>) {\n let delta = 0;\n if (event.key === \"ArrowRight\") delta = RESIZE_STEP;\n else if (event.key === \"ArrowLeft\") delta = -RESIZE_STEP;\n else return;\n event.preventDefault();\n // Mirror TanStack's own `columnResizeDirection` reversal (passed to\n // `useReactTable` above) for the keyboard path: the handle sits at the\n // column's logical `end` edge, which `end-0` renders on the physical\n // LEFT under `dir=\"rtl\"` — so ArrowRight (physical right, toward the\n // column's own body) must SHRINK the column and ArrowLeft must GROW it,\n // the mirror image of LTR. Without this the keyboard path would diverge\n // from the now-direction-aware pointer path.\n if (dir === \"rtl\") delta = -delta;\n const minSize = column.columnDef.minSize ?? 20;\n const maxSize = column.columnDef.maxSize ?? Number.MAX_SAFE_INTEGER;\n const nextSize = Math.min(maxSize, Math.max(minSize, column.getSize() + delta));\n table.setColumnSizing((old) => ({ ...old, [column.id]: nextSize }));\n }\n\n // #51 — double-click resets a resize handle's column back to its declared\n // `ColumnDef.size`, falling back to TanStack's own default (150, the same\n // fallback idiom as `minSize ?? 20`/`maxSize ?? MAX_SAFE_INTEGER` above) when\n // the author left it unset — by REMOVING any explicit `columnSizing` entry\n // for the column, not by writing the size back in as a literal (PR #81\n // review, \"Remove the sizing override when resetting a column\"). `columnSizing`\n // only ever carries EXPLICIT per-column overrides; a column absent from it\n // always tracks its live `ColumnDef.size` (or the 150 default). Writing the\n // CURRENT declared size back in as a value looks identical today but turns\n // the default into a permanent override: if the `columns` prop later\n // changes this column's authored `size` (e.g. switching table\n // configurations), a column that was never resized follows the new\n // definition for free, while a double-click-reset column would stay pinned\n // to the OLD number forever. Deleting the entry keeps it dynamic, exactly\n // like a column that was never touched. Still goes through the SAME\n // `table.setColumnSizing` dispatch path as `handleResizeKeyDown` — never\n // `column.resetSize()` — so a controlled `columnSizing` consumer observes\n // the reset via `onColumnSizingChange` exactly like every other resize.\n function handleResizeDoubleClick(column: Column<TData, unknown>) {\n table.setColumnSizing((old) => {\n if (!(column.id in old)) return old;\n const { [column.id]: _removed, ...rest } = old;\n return rest;\n });\n }\n\n // ── Scroll container ref for virtualizer ─────────────────────────────────\n const scrollRef = useRef<HTMLDivElement>(null);\n\n // ── Virtualizer (only active in virtualized branch) ───────────────────────\n const virtualizer = useVirtualizer({\n count: enableRowVirtualization ? rows.length : 0,\n getScrollElement: () => (enableRowVirtualization ? scrollRef.current : null),\n estimateSize: () => estimateRowHeight,\n overscan,\n enabled: enableRowVirtualization,\n });\n\n const virtualItems = enableRowVirtualization ? virtualizer.getVirtualItems() : [];\n const totalSize = enableRowVirtualization ? virtualizer.getTotalSize() : 0;\n const paddingTop = virtualItems.length > 0 ? (virtualItems[0]?.start ?? 0) : 0;\n const paddingBottom =\n totalSize > 0 ? totalSize - (virtualItems[virtualItems.length - 1]?.end ?? 0) : 0;\n\n // ── Plain-branch scroll container: overflow measurement ────────────────────\n // #330: the non-virtualized branch's scroll box is `overflow-auto` (it used to\n // clip). Everything that box exposes is gated on MEASURED overflow, because a\n // table that fits must stay exactly as it was:\n // - the keyboard tab stop + its accessible name (WCAG 2.1.1 / axe\n // `scrollable-region-focusable`) — a table that doesn't scroll must NOT\n // gain a focus stop that does nothing and announces \"scrollable\" falsely;\n // - the edge fades, which only make sense when content continues off-edge.\n // So a desktop-width table is a total no-op: no tab stop, no label, no fade.\n const plainScrollRef = useRef<HTMLDivElement>(null);\n const [scrollOverflows, setScrollOverflows] = useState(false);\n const [canScrollLeft, setCanScrollLeft] = useState(false);\n const [canScrollRight, setCanScrollRight] = useState(false);\n\n const updateScrollAffordance = useCallback(() => {\n const el = plainScrollRef.current;\n if (!el) return;\n // 1px tolerance absorbs sub-pixel layout rounding, which would otherwise\n // report a permanent 0.5px overflow on a table that visually fits.\n setScrollOverflows(\n el.scrollWidth > el.clientWidth + 1 || el.scrollHeight > el.clientHeight + 1,\n );\n setCanScrollLeft(el.scrollLeft > 0);\n setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);\n }, []);\n\n useEffect(() => {\n const el = plainScrollRef.current;\n if (!el) return;\n updateScrollAffordance();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(updateScrollAffordance);\n // Observe the CONTAINER (viewport changes) and the <table> inside it\n // (content changes its intrinsic width without resizing the container).\n observer.observe(el);\n if (el.firstElementChild) observer.observe(el.firstElementChild);\n return () => observer.disconnect();\n // Column/row-count changes can also change the table's intrinsic width.\n }, [updateScrollAffordance, colCount, rows.length]);\n\n // ─── Empty / loading state ───────────────────────────────────────────────\n const showEmpty = !loading && rows.length === 0;\n const showSkeletons = loading && rows.length === 0;\n\n // Number of skeleton rows to show — caller can override via `loadingRows`.\n const skeletonRowCount = loadingRows ?? pageSize;\n\n // ─── Render helpers ───────────────────────────────────────────────────────\n\n /**\n * thead — sticky in virtualized mode, normal otherwise.\n * `withRowIndex` (virtualized only) sets the header row's `aria-rowindex` so the\n * windowed `aria-rowcount` on the table stays internally consistent with the\n * absolute indices on the data rows.\n */\n function renderThead(sticky: boolean, withRowIndex = false) {\n return (\n <thead\n className={cn(\n // #173: header bottom is the only cue between header and first data row → border-strong\n \"border-b border-border-strong\",\n // A sticky header scrolls OVER the body, so its fill must be opaque or data\n // rows bleed through the labels; the non-sticky header keeps the /60 wash.\n // z-20 (raised from z-10 for #333) puts the header row above the pinned\n // body cells (z-10) and below the pinned header corner (z-30). No visual\n // delta: nothing else in the table sits between those rungs.\n sticky ? \"sticky top-0 z-20 bg-surface-muted\" : \"bg-surface-muted/60\",\n )}\n >\n {table.getHeaderGroups().map((headerGroup, groupIndex) => (\n <tr key={headerGroup.id} aria-rowindex={withRowIndex ? groupIndex + 1 : undefined}>\n {hasGripColumn && (\n <th key=\"__reorder\" scope=\"col\" className=\"h-10 w-10 px-3 align-middle\">\n <span className=\"sr-only\">{t(\"data.table.reorderColumnHeader\")}</span>\n </th>\n )}\n {headerGroup.headers.map((header) => {\n const geometry = pinnedCellGeometry(header.column);\n const canSort = header.column.getCanSort();\n const sorted = header.column.getIsSorted();\n // String-header fallback (`column.id`) so an icon-only / non-text\n // header still yields a named button (#230).\n const headerLabel =\n typeof header.column.columnDef.header === \"string\"\n ? header.column.columnDef.header\n : header.column.id;\n const sortStateLabel =\n sorted === \"asc\" ? \"ascending\" : sorted === \"desc\" ? \"descending\" : \"not sorted\";\n const SortIcon =\n sorted === \"asc\" ? ArrowUp : sorted === \"desc\" ? ArrowDown : ArrowUpDown;\n // #12: every column gets the same explicit width triad a pinned\n // column already has, gated behind `enableColumnResizing` so a\n // table that doesn't opt in stays byte-identical to before.\n const resizeStyle = enableColumnResizing\n ? resizeWidthStyle(header.getSize())\n : undefined;\n const canResize =\n enableColumnResizing && !header.isPlaceholder && header.column.getCanResize();\n const resizeMax = header.column.columnDef.maxSize;\n return (\n <th\n key={header.id}\n scope=\"col\"\n aria-sort={\n canSort\n ? sorted === \"asc\"\n ? \"ascending\"\n : sorted === \"desc\"\n ? \"descending\"\n : \"none\"\n : undefined\n }\n data-pinned={geometry?.pinned ?? undefined}\n style={geometry?.style ?? resizeStyle}\n className={cn(\n // Same `px-3` the body `<td>` uses (below) — deliberately\n // NOT split into `ps-3`/`pe-3` for a resize-handle\n // override (round-1 briefly did this, see the round-2\n // note on `numericColumnClasses`): the header's padding\n // must stay byte-identical to the body's so an\n // end-aligned numeric column's header lines up with its\n // own values.\n \"h-10 px-3 text-start align-middle font-medium text-muted-foreground\",\n // #69: a numeric column's `meta` overrides the default\n // `text-start` — placed right after the base string so\n // tailwind-merge lets it win over that default.\n numericColumnClasses(header.column.columnDef.meta),\n // `sticky`/pinned already establishes a positioning context\n // for the resize handle's `absolute`; an unpinned resizable\n // header needs its own.\n !geometry && canResize && \"relative\",\n // A pinned HEADER cell is the corner where both freezes meet,\n // so it stacks above the sticky header row (z-20) which is\n // above the pinned body cells (z-10). It needs an OPAQUE\n // fill (scrolled header cells pass underneath it), and that\n // fill has to composite to exactly what its unpinned\n // neighbours show — same problem, same two-layer answer as\n // `pinnedCellFillClass`:\n // sticky branch → the row is already opaque `surface-muted`, so match it.\n // plain branch → the row is `surface-muted/60` over the\n // container's `card`, so paint `card` and\n // re-apply the /60 wash on `::before`.\n // Painting the plain branch's corner solid `surface-muted`\n // read 4-5/255 darker than the header beside it in every\n // theme (measured: 242 vs 247 light, 43 vs 40\n // dark) — the same \"floating pill\"\n // artefact #333 was filed about, moved into the header.\n geometry && \"sticky z-30\",\n geometry &&\n (sticky\n ? \"bg-surface-muted\"\n : \"bg-card before:pointer-events-none before:absolute before:inset-0 before:-z-10 before:bg-surface-muted/60 before:content-['']\"),\n // Separate cn() argument on purpose: the seam is the sole\n // structural cue between the frozen and scrolling blocks, so\n // it must not read as a \"boundary + fill in one class string\"\n // redundancy (separation:check).\n geometry?.edgeClass,\n )}\n >\n {header.isPlaceholder ? null : canSort ? (\n <button\n type=\"button\"\n onClick={header.column.getToggleSortingHandler()}\n aria-label={`Sort by ${headerLabel}, ${sortStateLabel}`}\n // `relative z-10` (round-2 fix, #82 follow-up — replaces\n // round-1's padding-based clearance, see the note on\n // `numericColumnClasses`): on a resizable column the\n // resize handle below is `absolute`, and CSS painting\n // order always puts a positioned descendant above\n // non-positioned in-flow content in the SAME stacking\n // context, regardless of DOM order — so without this,\n // the handle's 24px hit box would win every hit-test\n // where it overlaps this button's own trailing edge\n // (measured: a 12px overlap on an end-aligned\n // sortable+resizable column) no matter which element\n // renders first in markup. Giving the button its own\n // explicit positive z-index (not just `relative`, which\n // alone would still lose — see the code comment on\n // `numericColumnClasses` above) promotes it into a\n // later, higher-stacked paint step than the handle's\n // implicit `z-index: auto`, so the button wins the\n // overlap purely at the hit-test/paint layer — the\n // header's padding, and therefore its alignment with\n // the body `<td>`, never has to move. The handle's own\n // visible drag affordance (the `after:` seam, 0-8px\n // from the cell's trailing edge) sits entirely outside\n // this button's box (which ends at the same 12px inset\n // as the body), so dragging is unaffected.\n className=\"relative z-10 inline-flex items-center gap-1 rounded-sm transition-colors duration-fast ease-standard hover:text-foreground focus-ring\"\n >\n {flexRender(header.column.columnDef.header, header.getContext())}\n <SortIcon\n aria-hidden=\"true\"\n className=\"size-3 shrink-0 transition-colors duration-fast ease-standard\"\n />\n </button>\n ) : (\n flexRender(header.column.columnDef.header, header.getContext())\n )}\n {canResize && (\n <div\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-valuenow={Math.round(header.getSize())}\n aria-valuemin={header.column.columnDef.minSize}\n aria-valuemax={\n resizeMax !== undefined && resizeMax < Number.MAX_SAFE_INTEGER\n ? resizeMax\n : Math.max(header.getSize(), RESIZE_UNBOUNDED_ARIA_MAX)\n }\n // #51: a bare number reads to AT as a dimensionless\n // ordinal (\"150\") rather than a size — aria-valuetext\n // supplies the unit while aria-valuenow (above) stays\n // the plain numeric value TanStack/AT expect. PR #81\n // review, \"Format the announced resize value for the\n // active locale\": `count` (the raw number) drives\n // PluralMessage category selection so a locale whose\n // plural rules pick something other than \"other\" is\n // reachable, and `size` goes through `formatNumber` so\n // an overriding locale renders its own digits/grouping\n // instead of a raw Latin-digit JS number.\n aria-valuetext={t(\"data.table.resizeColumnValue\", {\n count: Math.round(header.getSize()),\n size: formatNumber(Math.round(header.getSize())),\n })}\n aria-label={t(\"data.table.resizeColumn\", { name: headerLabel })}\n tabIndex={0}\n data-slot=\"data-table-resize-handle\"\n onMouseDown={header.getResizeHandler()}\n onTouchStart={header.getResizeHandler()}\n onKeyDown={(event) => handleResizeKeyDown(event, header.column)}\n // #51: double-click resets the column to its declared\n // (or default) size — see `handleResizeDoubleClick`.\n // Pointer-only; it doesn't touch the keyboard path above.\n onDoubleClick={() => handleResizeDoubleClick(header.column)}\n className={cn(\n // #51: the hit box is a literal 24px (clamped to half\n // the header cell so it can never overlap a neighbour,\n // even at `minSize=20`) rather than the `w-2` Tailwind\n // spacing-scale utility. `w-2` compiles to\n // `calc(var(--spacing) * 2)`, and `--spacing` is what\n // `data-density=\"compact\"` rescales — so the old 8px\n // hit box shrank further under compact density\n // (~7.1px). A literal px value is density-independent\n // by construction, which is the actual defect the\n // maintainer's review corrected (NOT `--type-factor`,\n // which this handle never used). Do not widen via\n // overhang into the neighbouring cell instead — on the\n // last column that lands inside the `overflow-auto`\n // box (#330 false positive) and a pinned neighbour\n // paints over/hit-tests away the extra area.\n \"absolute inset-y-0 end-0 w-[min(24px,50%)] cursor-col-resize touch-none select-none\",\n // #51: the focus ring moves to the `after:` pseudo-\n // element (the drawn seam) rather than the box itself\n // — the box is now a 24px hit target, and a 24px focus\n // rectangle would replace the deliberately slim ring\n // already reviewed/approved as the #12 a11y fix\n // (da9b29e). `focus-visible:after:*` targets the\n // pseudo-element the same way `hover:after:w-2` /\n // `focus-visible:after:w-2` below already do.\n \"focus-visible:outline-none\",\n // a11y fix (#12 review, blocking): this handle is the\n // SOLE boundary between two adjacent header cells once\n // resizing is on — no fill/elevation change separates\n // them otherwise — so per the border/border-strong\n // decision test (styling-and-tokens.md) it needs a\n // rung that clears WCAG 1.4.11's 3:1 on its OWN, in\n // EVERY state, including rest (a control with no\n // affordance until hover is unusable without a\n // pointer). `border-strong` measures only 2.86-2.96:1\n // against this `bg-surface-muted` header — that rung\n // is guaranteed only vs `--card`/`--background`, not a\n // same-tone surface, which is the exact trap the rule\n // warns about. `muted-foreground` is guaranteed AA\n // text contrast against `--surface-muted`\n // (TEXT_SURFACES), so it clears the 3:1 non-text\n // minimum with wide margin (measured ~5.3-6.4:1 in\n // both themes, unaffected by density) and is already\n // the header's own label color. A slim persistent\n // `after:` seam (not just a hover reveal) gives the\n // real resting boundary; hover/focus widen the drawn\n // seam to 8px (`after:w-2`) using the same compliant\n // color — a separate width from the 24px pointer hit\n // box below (#51), which the seam does not fill.\n // Dragging keeps the pre-existing full-fill\n // `bg-primary` treatment — that is a drag AFFORDANCE,\n // not a focus indicator, and it is redundant with the\n // pointer capture, so it is out of scope here. The\n // keyboard focus indicator on both branches is the\n // shared compound one (#67), applied to the drawn seam\n // via `focus-visible:after:focus-ring-static`: the\n // element itself is a 24px transparent hit box, so\n // ringing IT would ring nothing a user can see.\n header.column.getIsResizing()\n ? \"after:absolute after:inset-y-0 after:end-0 after:w-2 after:bg-primary after:content-[''] focus-visible:after:focus-ring-static\"\n : \"after:absolute after:inset-y-0 after:end-0 after:w-px after:bg-muted-foreground after:content-[''] hover:after:w-2 focus-visible:after:w-2 focus-visible:after:focus-ring-static\",\n )}\n />\n )}\n </th>\n );\n })}\n </tr>\n ))}\n </thead>\n );\n }\n\n /**\n * Row separation cue, keyed off the absolute row index so it stays stable\n * under virtualization (a CSS `even:`/`odd:` variant would \"swim\" as the\n * windowed `<tr>`s recycle).\n *\n * - zebra (default): a gentle `foreground/5` wash on alternate rows is the ONE\n * separation gesture; rows carry NO divider (#173's strong divider was the cue\n * only because nothing else was — the stripe replaces it, so a border would now\n * be redundant per the surface-separation rule).\n * - lines (`zebra={false}`): the classic `border-border-strong` divider between\n * rows; `last:border-b-0` so the final divider doesn't double with the\n * container's own bottom border (which reads as a heavy edge / shadow).\n */\n function rowSeparationClass(rowIndex: number): string {\n if (!zebra) return \"border-b border-border-strong last:border-b-0\";\n return rowIndex % 2 === 1 ? \"bg-foreground/5\" : \"\";\n }\n\n /**\n * Fill for a PINNED body cell (#333) — the twin of `rowSeparationClass` above,\n * and the fix for the bug this issue reports.\n *\n * A pinned cell sits above horizontally-scrolling content, so it needs an\n * OPAQUE paint or the scrolled columns read straight through its text. But the\n * row's own cues — the zebra stripe, hover, selected — are TRANSLUCENT washes\n * that live on the `<tr>`, and a single opaque `background-color` on the\n * `<td>` hides all three: that is the \"seam / floating pill\" the issue\n * describes.\n *\n * So the cell paints the opaque `bg-card` base and re-applies the row's wash on\n * a decorative `::before` layer at a NEGATIVE stack level. Inside the cell's own\n * stacking context (it has one — `sticky` + a `z-` rung) that layer paints\n * ABOVE the cell's background and BELOW its text, which is exactly the order an\n * unpinned cell gets from the `<tr>`'s translucent background.\n *\n * The wash must NOT be a background-IMAGE gradient on the cell itself: under\n * `[data-decoration]`, `decoration.css` gives every\n * `.bg-card` element the ambient grid AS a `background-image`, so a gradient\n * would overwrite it and punch a flat, ungridded rectangle into the sheet\n * exactly where the frozen column is.\n *\n * Hover and selected stay in CSS (`group-hover/row:` / `group-data-…/row:`\n * against the `group/row` on the `<tr>`) because only the browser knows the\n * pointer is over a SIBLING cell of the same row.\n *\n * Keep this in sync with `rowSeparationClass`. Known limit: a caller's own\n * `rowClassName` background is NOT mirrored here — the component can't know\n * which part of an arbitrary class string is a fill.\n */\n function pinnedCellFillClass(rowIndex: number): string {\n return cn(\n \"bg-card\",\n \"before:pointer-events-none before:absolute before:inset-0 before:-z-10 before:content-['']\",\n zebra && rowIndex % 2 === 1 && \"before:bg-foreground/5\",\n \"group-hover/row:before:bg-foreground/10\",\n \"group-data-[state=selected]/row:before:bg-accent\",\n );\n }\n\n /**\n * Accessible name for a row's hidden activation button (#337). Prefers the\n * caller's `rowActionLabel`, then the row's first DATA column value (via\n * `firstDataCellValue` — skips a leading display column with no accessor,\n * e.g. `createSelectionColumn()`'s own checkbox column, #11 I6), then the\n * localized generic fallback.\n */\n function rowActionName(row: (typeof rows)[number]): string {\n const explicit = rowActionLabel?.(row);\n if (explicit) return explicit;\n const name = firstDataCellValue(row);\n if (name !== undefined) return name;\n return t(\"data.table.rowAction\");\n }\n\n /** A single data row */\n function renderRow(\n row: (typeof rows)[number],\n rowIndex: number,\n extras?: React.HTMLAttributes<HTMLTableRowElement>,\n // Reorder metadata for THIS row, present in either handle mode whenever\n // reorder is active — `activator` is set only in `\"cell\"` mode, where the\n // grip button (not the row) is the drag activator (dnd-kit's\n // `setActivatorNodeRef` pattern).\n dragHandle?: {\n isDragging: boolean;\n activator?: {\n setActivatorNodeRef: (node: HTMLElement | null) => void;\n attributes: DraggableAttributes;\n listeners: DraggableSyntheticListeners;\n };\n },\n ) {\n // #337: `onRowClick` adds exactly ONE activation target per row — a\n // visually-hidden <button> in the first cell. The <tr> stays a plain `row`\n // (a focusable <tr> would be a tab stop with no activation semantics: it\n // can't take role=\"button\" without breaking the table's row/rowgroup\n // structure, so AT would announce a row and never that Enter does anything).\n const clickable = Boolean(onRowClick);\n\n function handleRowClick(event: React.MouseEvent<HTMLTableRowElement>) {\n // The hidden activation button matches this guard too, so a keyboard\n // Enter/Space — which the browser dispatches as a click that bubbles to\n // the row — is handled once, by the button, not twice.\n if (isInteractiveEventTarget(event.target)) return;\n if (isActiveTextSelection()) return;\n onRowClick?.(row, event);\n }\n\n return (\n <tr\n key={row.id}\n data-state={row.getIsSelected() ? \"selected\" : undefined}\n onClick={clickable ? handleRowClick : undefined}\n // Hover/selected are foreground-tint washes so they read more prominent than\n // the zebra stripe in the SAME direction across light/dark themes (the old\n // surface-muted/50 hover went the wrong way over a striped row).\n className={cn(\n // Color-only feedback (no transform/movement) → per\n // docs/MOTION_GUIDELINES.md item 3 this stays under OS reduced-motion\n // (only movement is neutralized); the gated duration-fast/ease-standard\n // pair already collapses toward ~0ms via --motion-factor when the user\n // or OS asks for reduced motion, matching the header sort button.\n \"transition-colors duration-fast ease-standard hover:bg-foreground/10 data-[state=selected]:bg-accent\",\n // #13: the dragged row's live `transform` (set inline via `extras.style`,\n // see `SortableDataRow`) is what actually MOVES it — this class only\n // makes that movement glide instead of snapping, through the gated\n // duration/ease utilities (never a raw ms/ease value —\n // quality-gates.md \"Motion-tokened\") with a reduced-motion\n // neutralizer. Raising the dragged row's stacking + opacity is a\n // colour/composite-only cue, so it isn't gated by the same rule.\n dragHandle &&\n \"relative transition-transform duration-base ease-standard motion-reduce:transition-none\",\n dragHandle?.isDragging && \"z-20 opacity-90 shadow-md\",\n // Named group (#333) so a PINNED cell can re-apply the row's hover /\n // selected wash on top of its own opaque fill — only CSS knows the\n // pointer is over a sibling cell. Purely a selector hook: `group/row`\n // emits no style of its own.\n \"group/row\",\n rowSeparationClass(rowIndex),\n // `<tr>` isn't in the global auto-cursor-pointer role list (button/\n // menuitem/tab/…), so a clickable row needs its own cursor. The focus\n // ring is driven off the hidden button's `:focus-visible` (same\n // `has-[[data-slot=…]:focus-visible]` pattern as InputGroup) so the\n // ring paints on the ROW the user is about to activate, even though\n // focus lives on the sr-only control inside it.\n clickable &&\n \"cursor-pointer has-[[data-slot=data-table-row-action]:focus-visible]:focus-ring-static-inset\",\n rowClassName?.(row),\n )}\n {...extras}\n >\n {dragHandle?.activator && (\n <td className=\"w-10 px-3 py-2 align-middle\">\n <button\n type=\"button\"\n ref={dragHandle.activator.setActivatorNodeRef}\n data-slot=\"data-table-row-drag-handle\"\n aria-label={t(\"data.table.reorderHandle\", { name: rowActionName(row) })}\n className={cn(\n \"inline-flex size-7 cursor-grab items-center justify-center rounded-sm text-muted-foreground transition-colors duration-fast ease-standard hover:bg-foreground/10 hover:text-foreground focus-ring active:cursor-grabbing\",\n dragHandle.isDragging && \"text-foreground\",\n )}\n {...dragHandle.activator.attributes}\n {...dragHandle.activator.listeners}\n >\n <GripVertical aria-hidden=\"true\" className=\"size-4\" />\n </button>\n </td>\n )}\n {row.getVisibleCells().map((cell, cellIndex) => {\n const geometry = pinnedCellGeometry(cell.column);\n // #12: same width triad as the header cell — see `resizeWidthStyle`.\n const resizeStyle = enableColumnResizing\n ? resizeWidthStyle(cell.column.getSize())\n : undefined;\n return (\n <td\n key={cell.id}\n data-pinned={geometry?.pinned ?? undefined}\n style={geometry?.style ?? resizeStyle}\n className={cn(\n \"px-3 py-2 align-middle\",\n // #69: same numeric-column seam as the header — see\n // `numericColumnClasses`.\n numericColumnClasses(cell.column.columnDef.meta),\n // z-10: above the normal (unpositioned) cells it scrolls over,\n // below the sticky header row (z-20) and the pinned corner (z-30).\n geometry && \"sticky z-10\",\n geometry && pinnedCellFillClass(rowIndex),\n // Separate cn() argument — see pinnedCellGeometry's edgeClass.\n geometry?.edgeClass,\n )}\n >\n {clickable && cellIndex === 0 && (\n <button\n type=\"button\"\n data-slot=\"data-table-row-action\"\n // #311: `sr-only` removes the box from the visual layout but\n // not the browser's own focus ring — the ROW paints the\n // deliberate compound indicator (via the `has-[…]` selector\n // above), so the proxy's own native ring must be suppressed\n // or it leaks as a stray dot at the row's edge.\n className=\"sr-only focus-visible:outline-none\"\n onClick={(event) => onRowClick?.(row, event)}\n >\n {rowActionName(row)}\n </button>\n )}\n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n </td>\n );\n })}\n </tr>\n );\n }\n\n /**\n * Skeleton placeholder `<tr>`s — shared by the normal and virtualized tbody\n * renderers so a markup/token/a11y fix only needs to be made once (#231).\n */\n function renderSkeletonBody(count: number) {\n // #69: iterate the real leaf columns (not just a count) so each skeleton\n // `<td>` can read the same `meta.numeric`/`meta.align` as the loaded\n // header/body cells — a loading table whose skeleton didn't mirror the\n // real alignment is exactly the column-shift-on-load bug\n // loading-states.md § \"CLS / space reservation\" warns about.\n const visibleColumns = table.getVisibleLeafColumns();\n return Array.from({ length: count }).map((_, i) => (\n <tr key={`skeleton-${i}`} aria-hidden=\"true\" className={rowSeparationClass(i)}>\n {hasGripColumn && (\n <td className=\"w-10 px-3 py-2 align-middle\">\n <Skeleton className=\"size-4\" />\n </td>\n )}\n {visibleColumns.map((column) => (\n <td\n key={column.id}\n className={cn(\"px-3 py-2 align-middle\", numericColumnClasses(column.columnDef.meta))}\n >\n <Skeleton className=\"h-4 w-full\" />\n </td>\n ))}\n </tr>\n ));\n }\n\n /**\n * Empty-state `<tr>` — shared by the normal and virtualized tbody renderers\n * (#231).\n */\n function renderEmptyBody() {\n return (\n <tr>\n <td\n colSpan={colCount + (hasGripColumn ? 1 : 0)}\n className=\"h-24 px-3 text-center text-muted-foreground\"\n >\n {emptyMessage}\n </td>\n </tr>\n );\n }\n\n // ─── Non-virtualized tbody ────────────────────────────────────────────────\n function renderTbodyNormal() {\n if (showSkeletons) {\n return <tbody>{renderSkeletonBody(skeletonRowCount)}</tbody>;\n }\n if (showEmpty) {\n return <tbody>{renderEmptyBody()}</tbody>;\n }\n if (!rowReorderActive) {\n return <tbody>{rows.map((row, i) => renderRow(row, i))}</tbody>;\n }\n\n // #13: `SortableContext` renders no DOM element of its own (a plain\n // context Provider), so nesting it around `<tbody>` here does not insert\n // anything between `<table>` and `<tbody>` — the real DOM stays valid.\n return (\n <SortableContext\n items={rows.map((r) => getReorderRowId(r))}\n strategy={verticalListSortingStrategy}\n >\n <tbody>\n {rows.map((row, i) => (\n <SortableDataRow\n key={getReorderRowId(row)}\n id={getReorderRowId(row)}\n attributesOverride={{\n // #98: dnd-kit's own `roleDescription: 'sortable'` default is\n // hardcoded English; override it with the localized value in\n // BOTH handle modes — `role` stays row-mode-only (see the\n // `attributesOverride` prop doc above).\n roleDescription: t(\"data.table.reorderRoleDescription\"),\n ...(rowReorderHandle === \"row\" ? { role: \"row\" } : null),\n }}\n >\n {({ setNodeRef, setActivatorNodeRef, attributes, listeners, isDragging, style }) =>\n renderRow(\n row,\n i,\n {\n ref: setNodeRef,\n style,\n // `aria-pressed` is a `DraggableAttributes` field meant for a\n // real `<button>` activator; spread onto a `<tr role=\"row\">`\n // (row-handle mode) it fails axe's `aria-allowed-attr` (that\n // ARIA state is not permitted on the `row` role), so strip it\n // here rather than exempt it downstream.\n ...(rowReorderHandle === \"row\"\n ? (() => {\n const { \"aria-pressed\": _ariaPressed, ...rowAttributes } = attributes;\n return { ...rowAttributes, ...listeners };\n })()\n : {}),\n } as React.HTMLAttributes<HTMLTableRowElement>,\n {\n isDragging,\n activator:\n rowReorderHandle === \"cell\"\n ? { setActivatorNodeRef, attributes, listeners }\n : undefined,\n },\n )\n }\n </SortableDataRow>\n ))}\n </tbody>\n </SortableContext>\n );\n }\n\n // ─── Virtualized tbody ────────────────────────────────────────────────────\n function renderTbodyVirtualized() {\n if (showSkeletons) {\n // For virtualized mode, cap the visible skeleton rows at 10 unless caller\n // has explicitly set loadingRows.\n const virtualSkeletonCount = loadingRows ?? Math.min(10, pageSize);\n return <tbody>{renderSkeletonBody(virtualSkeletonCount)}</tbody>;\n }\n\n return (\n <tbody>\n {showEmpty ? (\n renderEmptyBody()\n ) : (\n <>\n {/* Top spacer — real <tr> so table layout is preserved */}\n {paddingTop > 0 && (\n <tr aria-hidden=\"true\">\n <td style={{ height: paddingTop }} colSpan={colCount} />\n </tr>\n )}\n {virtualItems.map((virtualRow) => {\n const row = rows[virtualRow.index];\n // row is guaranteed present because virtualizer.count === rows.length,\n // but TypeScript doesn't know array indexing is safe here.\n if (!row) return null;\n return renderRow(row, virtualRow.index, {\n ref: virtualizer.measureElement as React.Ref<HTMLTableRowElement>,\n \"data-index\": virtualRow.index,\n // Absolute 1-based row position; header row(s) occupy 1..headerRowCount.\n \"aria-rowindex\": headerRowCount + virtualRow.index + 1,\n } as React.HTMLAttributes<HTMLTableRowElement>);\n })}\n {/* Bottom spacer */}\n {paddingBottom > 0 && (\n <tr aria-hidden=\"true\">\n <td style={{ height: paddingBottom }} colSpan={colCount} />\n </tr>\n )}\n </>\n )}\n </tbody>\n );\n }\n\n // ─── Pagination controls ──────────────────────────────────────────────────\n function renderPagination() {\n // Virtualization wins over pagination per spec — don't render controls\n if (enableRowVirtualization) return null;\n if (!enablePagination && !manualPagination) return null;\n\n // #342: a genuinely single-page table renders a permanently-disabled\n // pager (\"Page 1 of 1\", both buttons disabled) — hide it, UNLESS the page\n // count isn't actually knowable: under `manualPagination` without a\n // `rowCount`/`pageCount`, TanStack's `getPageCount()` falls back to the\n // CURRENT page's row count, so \"<= 1\" there is a false positive for\n // \"really one page\" — the #227 dev warning above stays the diagnostic for\n // exactly that ambiguous case, so this flag doesn't also mask it.\n const pageCountUnknown = manualPagination && rowCount === undefined && pageCount === undefined;\n if (hidePaginationWhenSingle && !pageCountUnknown && table.getPageCount() <= 1) return null;\n\n return (\n <div className=\"flex items-center justify-between\">\n <p className=\"text-body text-muted-foreground\">\n Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount() || 1}\n </p>\n <div className=\"flex gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => table.previousPage()}\n disabled={!table.getCanPreviousPage()}\n >\n Previous\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => table.nextPage()}\n disabled={!table.getCanNextPage()}\n >\n Next\n </Button>\n </div>\n </div>\n );\n }\n\n // ─── Render ───────────────────────────────────────────────────────────────\n\n // #338: visually-hidden accessible name for the table. Must be the FIRST\n // child of <table> per the HTML spec (caption immediately follows the\n // opening tag) — both branches place it before their thead.\n const captionElement = caption != null ? <caption className=\"sr-only\">{caption}</caption> : null;\n\n if (enableRowVirtualization) {\n // Virtualized branch: scroll container wraps the whole table\n // If both enablePagination and enableRowVirtualization are set,\n // virtualization wins; pagination controls are silently suppressed.\n return (\n <div ref={ref} className={cn(\"space-y-3\", className)} {...rest}>\n {toolbar ? toolbar(table) : null}\n {/* Outer border is redundant (surface change) → plain border per #173 spec.\n tabIndex={0} makes the windowed scroll region keyboard-operable — the rows\n themselves aren't focusable, so without it the off-screen rows are\n unreachable by keyboard (WCAG 2.1.1 / axe `scrollable-region-focusable`). */}\n <div\n ref={scrollRef}\n tabIndex={0}\n // Names the focus stop (WCAG 4.1.2) without a landmark role — a `role=\"region\"`\n // here would add a redundant landmark over the inner real <table>.\n aria-label={t(\"data.table.scrollRegion\")}\n aria-busy={loading || undefined}\n className=\"relative overflow-auto rounded-lg border bg-card focus-ring\"\n style={{ maxHeight: maxBodyHeight, ...pinnedScrollPadding }}\n >\n {/* Loading overlay */}\n {loading && rows.length > 0 && (\n <div\n role=\"status\"\n aria-live=\"polite\"\n // z-40 (raised from z-20 for #333): the overlay covers the WHOLE\n // table, so it has to sit above the pinned-column ladder (body z-10,\n // sticky header z-20, pinned header corner z-30) or a frozen column\n // would punch through the \"loading\" scrim.\n className=\"absolute inset-0 z-40 flex items-center justify-center rounded-lg bg-card/80\"\n >\n <Spinner aria-hidden=\"true\" className=\"text-foreground\" />\n <span className=\"sr-only\">Loading table data…</span>\n </div>\n )}\n <table\n aria-busy={loading || undefined}\n aria-rowcount={ariaRowCount}\n className=\"w-full caption-bottom text-body\"\n >\n {captionElement}\n {renderThead(true, true)}\n {renderTbodyVirtualized()}\n </table>\n </div>\n </div>\n );\n }\n\n // Non-virtualized branch.\n // #330: the scroll box is `overflow-auto` (was `overflow-hidden`, silently\n // clipping columns that didn't fit instead of letting them scroll) and\n // keyboard-focusable, parity with the virtualized branch above. Split into\n // an OUTER non-scrolling wrapper (keeps the rounded/border/bg chrome +\n // clip, and is the positioning context for the loading overlay + edge\n // fades) and an INNER scrolling div (the focusable, `overflow-auto` scroll\n // region) so the edge-fade affordance can stay pinned to the visible edges\n // instead of scrolling away with the table content.\n const nonVirtualizedContent = (\n <div ref={ref} className={cn(\"space-y-3\", className)} {...rest}>\n {toolbar ? toolbar(table) : null}\n {/* Outer border is redundant (surface change) → plain border per #173 spec */}\n <div\n aria-busy={loading || undefined}\n className=\"relative overflow-hidden rounded-lg border bg-card\"\n >\n {/* Loading overlay */}\n {loading && rows.length > 0 && (\n <div\n role=\"status\"\n aria-live=\"polite\"\n // z-40 (raised from z-20 for #333): the overlay covers the WHOLE\n // table, so it has to sit above the pinned-column ladder (body z-10,\n // sticky header z-20, pinned header corner z-30) or a frozen column\n // would punch through the \"loading\" scrim.\n className=\"absolute inset-0 z-40 flex items-center justify-center rounded-lg bg-card/80\"\n >\n <Spinner aria-hidden=\"true\" className=\"text-foreground\" />\n <span className=\"sr-only\">Loading table data…</span>\n </div>\n )}\n {/* The tab stop exists ONLY while the region measurably overflows: without\n it, columns beyond the viewport are unreachable by keyboard (WCAG 2.1.1 /\n axe `scrollable-region-focusable`) — but adding it unconditionally would\n give every table that FITS a focus stop that does nothing and announces\n \"scrollable\" when it isn't. `aria-label` moves with it (WCAG 4.1.2:\n a name for a stop that exists, none for one that doesn't). No\n `role=\"region\"` — that would add a redundant landmark over the real\n <table> inside it. */}\n <div\n ref={plainScrollRef}\n data-slot=\"data-table-scroll-region\"\n tabIndex={scrollOverflows ? 0 : undefined}\n aria-label={scrollOverflows ? t(\"data.table.scrollRegion\") : undefined}\n onScroll={updateScrollAffordance}\n className=\"overflow-auto rounded-lg focus-ring-inset\"\n style={hasLeftPinned || hasRightPinned ? pinnedScrollPadding : undefined}\n >\n <table aria-busy={loading || undefined} className=\"w-full caption-bottom text-body\">\n {captionElement}\n {renderThead(false)}\n {renderTbodyNormal()}\n </table>\n </div>\n {/* Horizontal-scroll edge fade — a token-driven affordance that only\n appears once the table actually overflows its container in that\n direction, so a desktop/wide table renders neither (visual no-op).\n\n #333: an edge with a PINNED column renders no fade. The fade lives\n outside the scroll region and would paint a 32px wash straight over\n the frozen column's own text; and the affordance is already carried\n there by the pinned block's `border-border-strong` seam, which is\n what a frozen column means (\"content slides under this edge\"). So\n the fade stays the cue for a FREE edge only. */}\n {canScrollLeft && !hasLeftPinned && (\n <div\n aria-hidden=\"true\"\n data-slot=\"data-table-scroll-fade-left\"\n className=\"pointer-events-none absolute inset-y-0 left-0 z-10 w-8 rounded-lg bg-gradient-to-r from-card to-transparent\"\n />\n )}\n {canScrollRight && !hasRightPinned && (\n <div\n aria-hidden=\"true\"\n data-slot=\"data-table-scroll-fade-right\"\n className=\"pointer-events-none absolute inset-y-0 right-0 z-10 w-8 rounded-lg bg-gradient-to-l from-card to-transparent\"\n />\n )}\n </div>\n\n {renderPagination()}\n </div>\n );\n\n // #13: `DndContext` renders no wrapping DOM element around `children` either\n // — it composes `children` alongside its own hidden a11y nodes (the\n // screen-reader instructions, plus a `role=\"status\"` `LiveRegion` that is\n // permanently silent — see `silentDragAnnouncements` above) as SIBLINGS.\n // Wrapping the whole component root here (rather than reaching inside the\n // `<table>`) is what keeps those hidden nodes out of the table's own DOM —\n // they land beside the table's outer `<div>`, never inside a\n // `<thead>`/`<tbody>`, which is the only place in HTML that would reject\n // them. DataTable's OWN `aria-live=\"polite\"` region (`reorderLiveMessage`)\n // is a further sibling here for the same reason.\n if (!rowReorderActive) return nonVirtualizedContent;\n return (\n <DndContext\n sensors={reorderSensors}\n collisionDetection={closestCenter}\n onDragStart={handleRowDragStart}\n onDragOver={handleRowDragOver}\n onDragEnd={handleRowDragEnd}\n onDragCancel={handleRowDragCancel}\n accessibility={{\n announcements: silentDragAnnouncements,\n // #98: dnd-kit's own hidden keyboard-instructions node is hardcoded\n // English (`defaultScreenReaderInstructions`) unless overridden here.\n screenReaderInstructions: { draggable: t(\"data.table.reorderInstructions\") },\n }}\n >\n {nonVirtualizedContent}\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n data-slot=\"data-table-reorder-live-region\"\n className=\"sr-only\"\n >\n {reorderLiveMessage}\n </div>\n </DndContext>\n );\n}\n\n// ─── Public export with forwardRef + generic cast ─────────────────────────────\n//\n// React.forwardRef strips the generic parameter. The cast below restores it so\n// callers get full type inference on `columns` / `data` while still being able\n// to forward a ref to the root <div>.\n//\n// The ref prop is already declared in DataTableProps (optional) so existing\n// consumers are backward-compatible; the forwardRef call means passing a ref\n// object also works.\n\nconst DataTableWithRef = forwardRef(DataTableInner) as <TData, TValue>(\n props: DataTableProps<TData, TValue> & { ref?: React.Ref<HTMLDivElement> },\n) => React.ReactElement | null;\n\nexport { DataTableWithRef as DataTable };\n","\"use client\";\n\nimport { useId, type InputHTMLAttributes } from \"react\";\nimport { Input } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { SearchIcon } from \"@elabs-ai/components-icons\";\n\nexport interface SearchInputProps extends Omit<\n InputHTMLAttributes<HTMLInputElement>,\n \"onChange\" | \"value\"\n> {\n value: string;\n onValueChange: (value: string) => void;\n /** Visually-hidden accessible label. Defaults to \"Search\". */\n label?: string;\n containerClassName?: string;\n}\n\n/**\n * Search field with a leading icon and a clear button. Controlled.\n *\n * `disabled` (available via the extended `InputHTMLAttributes`) is how a\n * consumer signals a pending fetch (D5 — the app owns fetch state, this\n * control just reflects it; see loading-states.md). It is forwarded to the\n * `<Input>` explicitly AND gates the clear button — while disabled the clear\n * affordance is hidden so it can't mutate the filter mid-request (#269/#8).\n */\nexport function SearchInput({\n value,\n onValueChange,\n label = \"Search\",\n placeholder = \"Search…\",\n className,\n containerClassName,\n disabled,\n ...props\n}: SearchInputProps) {\n const id = useId();\n return (\n <div className={cn(\"relative w-full max-w-xs\", containerClassName)}>\n <label htmlFor={id} className=\"sr-only\">\n {label}\n </label>\n <SearchIcon\n size={16}\n className=\"pointer-events-none absolute start-2.5 top-1/2 -translate-y-1/2 text-muted-foreground\"\n />\n <Input\n id={id}\n value={value}\n onChange={(e) => onValueChange(e.target.value)}\n placeholder={placeholder}\n disabled={disabled}\n className={cn(\"ps-8\", value && \"pe-8\", className)}\n {...props}\n />\n {value && !disabled ? (\n <button\n type=\"button\"\n onClick={() => onValueChange(\"\")}\n aria-label=\"Clear search\"\n className=\"absolute end-2 top-1/2 -translate-y-1/2 rounded-sm p-0.5 text-muted-foreground transition-colors duration-fast ease-standard hover:text-foreground focus-ring animate-in fade-in zoom-in-95 duration-fast ease-entrance\"\n >\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n </button>\n ) : null}\n </div>\n );\n}\n","import { type ReactNode } from \"react\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\nexport interface FilterBarProps {\n /** Left cluster: search + facet filters. */\n children: ReactNode;\n /** Right cluster: column picker, export, primary actions. */\n actions?: ReactNode;\n className?: string;\n}\n\n/** Horizontal toolbar that groups table filters and actions. */\nexport function FilterBar({ children, actions, className }: FilterBarProps) {\n return (\n <div className={cn(\"flex flex-wrap items-center justify-between gap-2\", className)}>\n <div className=\"flex flex-wrap items-center gap-2\">{children}</div>\n {actions ? <div className=\"flex items-center gap-2\">{actions}</div> : null}\n </div>\n );\n}\n","\"use client\";\n\n/**\n * filter-chip.tsx — `@elabs-ai/components-data`'s removable filter chip, with an optional\n * secondary count (\"excluded 1,204\") for `ProcessFilterBar` (RM-056, #221).\n *\n * Deliberately a thin COMPOSING wrapper around `@elabs-ai/components-ui`'s `FilterChip`\n * (`view-toolbar.tsx`, #331) rather than a second implementation — the dedupe\n * audit found the real, accessible, whole-chip-as-button `FilterChip` already\n * lives there (WCAG 2.5.8 target size, WCAG 2.5.3 \"Remove filter: <label>\"\n * accessible name). Building a second one in `packages/data` would duplicate\n * that work; this wrapper reuses it and passes `count`/`countLabel` through\n * the base component's `trailing` slot (#284) — a second, non-shrinking text\n * element, distinct from the truncatable `label` — so the count reaches the\n * chip's ACCESSIBLE NAME (screen readers hear \"Remove filter: Status: Failed\n * · excluded 1,204\") AND survives truncation in the visible chip, instead of\n * being folded into the one string CSS `truncate` can clip from the tail.\n */\nimport { forwardRef } from \"react\";\nimport {\n FilterChip as BaseFilterChip,\n type FilterChipProps as BaseFilterChipProps,\n useLocale,\n} from \"@elabs-ai/components-ui\";\n\n// `trailing` is omitted alongside `label`: this wrapper derives its OWN\n// `trailing` from `count`/`countLabel`. The `Omit` blocks `trailing` written\n// as an object LITERAL, but TypeScript's excess-property check does not\n// apply to a spread of an already-declared variable — `const extra = {\n// trailing: \"x\" }; <FilterChip {...extra} />` still type-checks, and the\n// value would land in `props` regardless of JSX attribute order (PR #408\n// review round 2). So the `Omit` is necessary but not sufficient: below,\n// `trailing` is also stripped from `props` at RUNTIME before it reaches the\n// base component, so a caller-supplied `trailing` — literal or\n// spread-smuggled — can never win at render, the same advertised-but-inert\n// failure mode #382/#284-round-1 already closed elsewhere in the repo\n// (`ContextRail`'s `children` omission).\nexport interface FilterChipProps extends Omit<BaseFilterChipProps, \"label\" | \"trailing\"> {\n /**\n * Label-in-value text — `\"Status: Failed\"`, never `\"Status = failed\"` and\n * never a bare `\"Failed\"`. Same contract as the base `FilterChip`.\n */\n label: string;\n /**\n * How many records this active filter excluded (or matched) — rendered as a\n * secondary, locale-formatted segment alongside `label`. Omit for a bare\n * chip with no count.\n */\n count?: number;\n /**\n * The word placed before the formatted count, e.g. `\"excluded\"` →\n * `\"excluded 1,204\"`. Omitted by default: a bare `count` renders as just the\n * formatted number.\n */\n countLabel?: string;\n}\n\n/**\n * A removable active-filter chip with an optional secondary count.\n *\n * `onRemove` stays REQUIRED (inherited from the base `FilterChip`, diverging\n * from this item's spec draft) — the whole chip IS the remove control, so a\n * chip with no removal affordance is a plain `Badge`, not this component.\n */\nexport const FilterChip = forwardRef<HTMLButtonElement, FilterChipProps>(function FilterChip(\n { label, count, countLabel, ...props },\n ref,\n) {\n const { formatNumber } = useLocale();\n const countText =\n count === undefined\n ? undefined\n : countLabel\n ? `${countLabel} ${formatNumber(count)}`\n : formatNumber(count);\n\n // Runtime guard (belt and braces alongside the `Omit` above): a caller can\n // still smuggle `trailing` into `props` through a spread of an\n // already-declared variable, which the type system cannot catch. Strip it\n // here so the derived count wins regardless of prop order.\n const { trailing: _ignoredTrailing, ...restProps } = props as Omit<BaseFilterChipProps, \"label\">;\n\n return (\n <BaseFilterChip\n ref={ref}\n data-slot=\"filter-chip\"\n label={label}\n {...restProps}\n trailing={countText}\n />\n );\n});\n","import type { ButtonHTMLAttributes } from \"react\";\nimport { forwardRef } from \"react\";\nimport {\n Badge,\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n useLocale,\n} from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\nexport interface FacetOption {\n label: string;\n value: string;\n}\n\nexport interface FacetFilterProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, \"title\"> {\n title: string;\n options: FacetOption[];\n /** Currently selected values (controlled). */\n selected: string[];\n onSelectedChange: (values: string[]) => void;\n}\n\n/**\n * Multi-select faceted filter rendered as a dropdown of toggles.\n *\n * `disabled` (forwarded to the trigger `Button`) is how a consumer signals a\n * pending fetch (D5 — the app owns fetch state, this control just reflects\n * it; see loading-states.md).\n *\n * The trigger takes `Button`'s DEFAULT size (`h-9`), not `sm` (#346): a facet\n * filter lives in a toolbar beside `Select` / `Input` / `DatePicker`, all of\n * which land on `h-9` (Select's own default rung, Input hardcoded, DatePicker\n * via this same Button default). An `sm` trigger was the lone `h-8` outlier in\n * that row, so the top and bottom edges of a filter bar didn't line up.\n */\nexport const FacetFilter = forwardRef<HTMLButtonElement, FacetFilterProps>(function FacetFilter(\n { title, options, selected, onSelectedChange, className, ...props },\n ref,\n) {\n const { t } = useLocale();\n const selectedSet = new Set(selected);\n const toggle = (value: string) => {\n const next = new Set(selectedSet);\n if (next.has(value)) next.delete(value);\n else next.add(value);\n onSelectedChange([...next]);\n };\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button ref={ref} variant=\"outline\" className={cn(\"border-dashed\", className)} {...props}>\n {title}\n {selected.length > 0 ? (\n <Badge\n variant=\"secondary\"\n className=\"ms-1 rounded px-1.5 animate-in fade-in zoom-in-95 duration-fast ease-entrance\"\n >\n {selected.length}\n </Badge>\n ) : null}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent className=\"min-w-[12rem]\">\n <DropdownMenuLabel>{title}</DropdownMenuLabel>\n {options.map((opt) => {\n const checked = selectedSet.has(opt.value);\n return (\n <DropdownMenuItem\n key={opt.value}\n onSelect={(e) => {\n e.preventDefault();\n toggle(opt.value);\n }}\n >\n <span\n aria-hidden=\"true\"\n className={\n \"flex size-4 items-center justify-center rounded border transition-colors duration-fast ease-standard \" +\n (checked ? \"border-primary bg-primary text-primary-foreground\" : \"border-input\")\n }\n >\n {checked ? \"✓\" : \"\"}\n </span>\n {opt.label}\n </DropdownMenuItem>\n );\n })}\n {selected.length > 0 ? (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem onSelect={() => onSelectedChange([])}>\n {t(\"data.facetFilter.clearFilters\")}\n </DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n});\n\nFacetFilter.displayName = \"FacetFilter\";\n","import { type Table } from \"@tanstack/react-table\";\nimport type { ButtonHTMLAttributes, ReactElement, Ref } from \"react\";\nimport { forwardRef } from \"react\";\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n useLocale,\n} from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\nexport interface ColumnPickerProps<TData> extends ButtonHTMLAttributes<HTMLButtonElement> {\n table: Table<TData>;\n /** Trigger label. Defaults to \"Columns\". */\n label?: string;\n}\n\nfunction ColumnPickerInner<TData>(\n { table, label = \"Columns\", className, ...props }: ColumnPickerProps<TData>,\n ref: Ref<HTMLButtonElement>,\n) {\n const { t } = useLocale();\n const columns = table.getAllColumns().filter((c) => c.getCanHide());\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button ref={ref} variant=\"outline\" size=\"sm\" className={cn(className)} {...props}>\n {label}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"min-w-[12rem]\">\n <DropdownMenuLabel>{t(\"data.columnPicker.toggleColumns\")}</DropdownMenuLabel>\n <DropdownMenuSeparator />\n {columns.map((column) => (\n <DropdownMenuItem\n key={column.id}\n onSelect={(e) => {\n e.preventDefault();\n column.toggleVisibility(!column.getIsVisible());\n }}\n >\n <span\n aria-hidden=\"true\"\n className={\n \"flex size-4 items-center justify-center rounded border transition-colors duration-fast ease-standard \" +\n (column.getIsVisible()\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-input\")\n }\n >\n {column.getIsVisible() ? \"✓\" : \"\"}\n </span>\n <span className=\"capitalize\">{column.id}</span>\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\nColumnPickerInner.displayName = \"ColumnPicker\";\n\n// React.forwardRef strips the generic parameter — the cast below restores it\n// (same pattern as DataTable's public export) so callers get full type\n// inference on `table` while still being able to forward a ref to the\n// trigger `Button`.\n//\n// `disabled` (available via the extended `ButtonHTMLAttributes`, forwarded to\n// the trigger) is how a consumer signals a pending fetch (D5 — the app owns\n// fetch state; see loading-states.md).\nexport const ColumnPicker = forwardRef(ColumnPickerInner) as <TData>(\n props: ColumnPickerProps<TData> & { ref?: Ref<HTMLButtonElement> },\n) => ReactElement | null;\n","/**\n * Minimal, dependency-free CSV serializer (RFC 4180).\n *\n * `toCsv` is pure + SSR-safe (no DOM, no deps). `downloadCsv` delegates the\n * browser save mechanics to `@elabs-ai/components-ui`'s shared `downloadBlob` (one home for\n * the Blob → `<a download>` dance; @elabs-ai/components-ui is already a peer dep here).\n * ChartFrame uses its own local copy of `toCsv` in @elabs-ai/components-charts to avoid a\n * cross-sibling dependency (charts → data is not allowed per the one-way rule).\n */\nimport { downloadBlob } from \"@elabs-ai/components-ui\";\n\nexport type CsvColumn<TData> = { key: keyof TData & string; header?: string };\n\nexport interface ToCsvOptions<TData> {\n /** Subset/reorder of columns. Omitted → all keys from rows[0]. */\n columns?: CsvColumn<TData>[];\n /** Emit header row. Default true. */\n header?: boolean;\n /** Field delimiter. Default \",\". */\n delimiter?: string;\n}\n\nexport interface DownloadCsvOptions<TData> extends ToCsvOptions<TData> {\n /** File name without extension. Default \"download\". */\n filename?: string;\n}\n\n/** RFC 4180 injection guard prefixes. */\nconst INJECTION_PREFIXES = [\"=\", \"+\", \"-\", \"@\"];\n\nfunction stringifyValue(value: unknown): string {\n if (value === null || value === undefined) return \"\";\n if (value instanceof Date) return value.toISOString();\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n\nfunction quoteField(field: string, delimiter: string): string {\n // CSV-injection guard: prefix with a single quote if the field starts with a\n // formula trigger character.\n if (INJECTION_PREFIXES.some((p) => field.startsWith(p))) {\n field = \"'\" + field;\n }\n // RFC 4180: quote iff the field contains delimiter, double-quote, CR, or LF.\n if (\n field.includes(delimiter) ||\n field.includes('\"') ||\n field.includes(\"\\n\") ||\n field.includes(\"\\r\")\n ) {\n return '\"' + field.replaceAll('\"', '\"\"') + '\"';\n }\n return field;\n}\n\n/**\n * Serialize rows to a CSV string (no DOM access — safe for SSR / jsdom).\n */\nexport function toCsv<TData extends Record<string, unknown>>(\n rows: TData[],\n opts?: ToCsvOptions<TData>,\n): string {\n const delimiter = opts?.delimiter ?? \",\";\n const includeHeader = opts?.header !== false;\n\n // Derive columns from first row when not provided.\n const firstRow = rows[0];\n const cols: CsvColumn<TData>[] =\n opts?.columns ??\n (firstRow !== undefined\n ? (Object.keys(firstRow) as (keyof TData & string)[]).map((k) => ({ key: k }))\n : []);\n\n const lines: string[] = [];\n\n if (includeHeader && cols.length > 0) {\n const headerRow = cols.map((c) => quoteField(c.header ?? c.key, delimiter)).join(delimiter);\n lines.push(headerRow);\n }\n\n for (const row of rows) {\n const line = cols.map((c) => quoteField(stringifyValue(row[c.key]), delimiter)).join(delimiter);\n lines.push(line);\n }\n\n // RFC 4180: CRLF line terminator, trailing newline.\n return lines.join(\"\\r\\n\") + (lines.length > 0 ? \"\\r\\n\" : \"\");\n}\n\n/**\n * Trigger a CSV file download in the browser. No-op in SSR environments.\n */\nexport function downloadCsv<TData extends Record<string, unknown>>(\n rows: TData[],\n opts?: DownloadCsvOptions<TData>,\n): void {\n if (typeof document === \"undefined\") return;\n\n const csv = toCsv(rows, opts);\n const blob = new Blob([csv], { type: \"text/csv;charset=utf-8;\" });\n downloadBlob(blob, (opts?.filename ?? \"download\") + \".csv\");\n}\n"],"mappings":"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","names":["rest","cn","jsx","jsxs","cn","jsx","jsxs","forwardRef","useLocale","jsx","FilterChip","forwardRef","Button","useLocale","cn","Fragment","jsx","jsxs","FacetFilter","forwardRef","Button","DropdownMenu","DropdownMenuContent","DropdownMenuItem","DropdownMenuLabel","DropdownMenuSeparator","DropdownMenuTrigger","useLocale","cn","jsx","jsxs"]}
|
|
1
|
+
{"version":3,"sources":["../src/data-table/data-table.tsx","../src/search-input/search-input.tsx","../src/filter-bar/filter-bar.tsx","../src/filter-bar/filter-chip.tsx","../src/facet-filter/facet-filter.tsx","../src/column-picker/column-picker.tsx","../src/to-csv.ts"],"sourcesContent":["\"use client\";\n\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport {\n flexRender,\n getCoreRowModel,\n getFilteredRowModel,\n getPaginationRowModel,\n getSortedRowModel,\n useReactTable,\n type Column,\n type ColumnDef,\n type ColumnFiltersState,\n type ColumnPinningState,\n type ColumnSizingState,\n type OnChangeFn,\n type PaginationState,\n type Row,\n type RowData,\n type RowSelectionState,\n type SortingState,\n type Table as TanstackTable,\n type VisibilityState,\n} from \"@tanstack/react-table\";\nimport { useVirtualizer } from \"@tanstack/react-virtual\";\n// Row drag-reorder (#13). @dnd-kit is the only DnD primitive in the repo (reuse\n// audit found none) — MIT-licensed, attributed in scripts/attributions.sources.json.\n// KeyboardSensor + sortableKeyboardCoordinates already implement the exact key\n// model the issue asks for (Space/Enter lift, arrows move, Space/Enter drop,\n// Escape cancel) and DndContext's built-in `Accessibility` component renders the\n// aria-live announcer — this file supplies the localized announcement text, the\n// localized screen-reader instructions + role description (#98 — dnd-kit ships\n// its own hardcoded-English defaults for both, which need an explicit override\n// same as everything else this feature says out loud), and the token-driven\n// visuals.\nimport {\n DndContext,\n KeyboardSensor,\n PointerSensor,\n closestCenter,\n useSensor,\n useSensors,\n type Announcements,\n type DragCancelEvent,\n type DragEndEvent,\n type DragOverEvent,\n type DragStartEvent,\n type DraggableAttributes,\n type DraggableSyntheticListeners,\n} from \"@dnd-kit/core\";\nimport {\n SortableContext,\n sortableKeyboardCoordinates,\n useSortable,\n verticalListSortingStrategy,\n} from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport { ArrowDown, ArrowUp, ArrowUpDown, GripVertical } from \"lucide-react\";\nimport { Button, Checkbox, Skeleton, Spinner, useLocale } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\n// ─── Column meta seam (#69) ─────────────────────────────────────────────────────\n// `columnDef.meta` is where TanStack lets a caller attach column-specific,\n// renderer-agnostic data — `DataTable` reads exactly two keys from it so\n// numeric-column styling (interaction-guidelines.md § Micro-typography:\n// \"tabular-nums for any number column … DataTable numeric cells\") is the\n// component's job, not a per-caller convention rediscovered at every call\n// site. Exported (not just declared) so a consumer's own `ColumnDef` literal\n// type-checks against a NAMED type, per component-api.md § Types.\n\n/**\n * `DataTable`'s `columnDef.meta` contract, read by the header/body/skeleton\n * cell renderers. Set `numeric: true` on a column to get `tabular-nums` +\n * end-alignment on both the `<th>` and every `<td>` (including the loading\n * skeleton) for free.\n */\nexport interface DataTableColumnMeta {\n /** Numeric column: tabular figures + end alignment on header and cells. */\n numeric?: boolean;\n /**\n * Explicit alignment override for when `numeric` isn't the right cue (or\n * to align a non-numeric column). Independent of `numeric` — `numeric`\n * alone still drives `tabular-nums` even when `align` overrides the\n * alignment away from `\"end\"`.\n */\n align?: \"start\" | \"center\" | \"end\";\n}\n\ndeclare module \"@tanstack/react-table\" {\n // `TData`/`TValue` must stay in the signature to match the interface being\n // augmented, even though `DataTableColumnMeta` (deliberately) doesn't use\n // them; the empty extends-body is how TanStack's own module-augmentation\n // pattern for `ColumnMeta` is documented.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-object-type\n interface ColumnMeta<TData extends RowData, TValue> extends DataTableColumnMeta {}\n}\n\n/**\n * `<th>`/`<td>`/skeleton-`<td>` className for a column's `meta.numeric`/`meta.align`\n * (#69). A pure, module-level helper (no component state) so all three call\n * sites — header, body cell, loading skeleton — stay in lockstep; a drift\n * between them is exactly the \"skeleton doesn't mirror the real layout\" bug\n * loading-states.md warns about. `meta` is typed as the exported\n * `DataTableColumnMeta` (structurally satisfied by TanStack's augmented\n * `ColumnMeta<TData, TValue>`) so the helper doesn't need the table's generic\n * row type.\n *\n * Deliberately takes NO options and NO padding branch: round-1 (#82\n * follow-up) briefly reserved an extra 36px of trailing `<th>` padding here\n * to clear the resize handle, but that moved the header's alignment\n * reference point 24px away from the body `<td>`'s (which keeps the plain\n * 12px `px-3`) — an end-aligned numeric column's own header no longer lined\n * up with the values it labels, defeating the whole point of #69. Reserving\n * space via padding necessarily desyncs header from body, because only the\n * header has a handle to clear. The round-2 fix instead resolves the\n * hit-test collision at the CONTROL that needs to win it — see the sort\n * button's `relative z-10` below — so header and body padding stay\n * byte-identical and this helper only ever contributes alignment +\n * tabular-nums classes.\n */\nfunction numericColumnClasses(meta: DataTableColumnMeta | undefined) {\n if (!meta?.numeric && !meta?.align) return undefined;\n const alignClass =\n meta?.align === \"start\"\n ? \"text-start\"\n : meta?.align === \"center\"\n ? \"text-center\"\n : meta?.align === \"end\"\n ? \"text-end\"\n : meta?.numeric\n ? \"text-end\"\n : undefined;\n return cn(alignClass, meta?.numeric && \"tabular-nums\");\n}\n\n// ─── Public types ─────────────────────────────────────────────────────────────\n\n/** Snapshot of table slice state — used for saved-view serialise/rehydrate. */\nexport interface DataTableViewState {\n sorting: SortingState;\n columnVisibility: VisibilityState;\n columnFilters: ColumnFiltersState;\n globalFilter?: string;\n pagination?: PaginationState;\n /**\n * Which columns are frozen to the left/right edge (#333). OPTIONAL on purpose:\n * the other members predate it, and a required key would break every consumer\n * that already constructs a `DataTableViewState` literal.\n */\n columnPinning?: ColumnPinningState;\n /**\n * Which rows are checked (#11), keyed by row id — see `getRowId`. OPTIONAL\n * like `columnPinning`, for the same reason: the other members predate it.\n */\n rowSelection?: RowSelectionState;\n /**\n * Per-column widths after resizing (#12), keyed by column id. OPTIONAL like\n * `columnPinning`/`rowSelection`, for the same reason: the other members\n * predate it.\n */\n columnSizing?: ColumnSizingState;\n}\n\n/**\n * Argument object fired by `onServerChange` whenever a manual slice changes.\n * The consuming app should re-fetch with these params and update `data`.\n */\nexport interface DataTableServerArgs {\n pagination: PaginationState;\n sorting: SortingState;\n columnFilters: ColumnFiltersState;\n globalFilter: string;\n}\n\n/**\n * Fires when a row is activated (#337).\n *\n * Both activation paths deliver a `click`: a pointer click on the row body, and\n * a keyboard Enter/Space on the row's hidden activation `<button>` (which the\n * browser dispatches as a click). So the handler takes ONE event type — there is\n * nothing for the caller to branch on.\n */\nexport type DataTableRowClickHandler<TData> = (\n row: Row<TData>,\n event: React.MouseEvent<HTMLElement>,\n) => void;\n\n// ─── Props ────────────────────────────────────────────────────────────────────\n\nexport interface DataTableProps<TData, TValue> extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n \"children\"\n> {\n columns: ColumnDef<TData, TValue>[];\n data: TData[];\n /** Render a toolbar above the table; receives the table instance. */\n toolbar?: (table: TanstackTable<TData>) => ReactNode;\n /** Enable client-side pagination. */\n enablePagination?: boolean;\n pageSize?: number;\n /**\n * Hide the pager once there's genuinely only one page\n * (`table.getPageCount() <= 1`). Default `true`. When `manualPagination` is\n * set without `rowCount`/`pageCount`, the page count isn't knowable (TanStack\n * falls back to the current page's row count) — in that ambiguous case the\n * pager still renders regardless of this flag, so the existing dev warning\n * (#227) stays the diagnostic instead of a silently-hidden pager. Set to\n * `false` to always show the pager (e.g. while a server total is still\n * loading and you'd rather show a disabled pager than none).\n */\n hidePaginationWhenSingle?: boolean;\n\n /**\n * Controlled global filter value. When provided, the table reflects this\n * value and the component manages no internal filter state. Keep the source\n * of truth in the app and pass it down — never mutate the filter during\n * render (e.g. `table.setGlobalFilter()` in `toolbar`), which loops.\n */\n globalFilter?: string;\n /** Fires when the table requests a global-filter change (e.g. from typeahead). */\n onGlobalFilterChange?: (value: string) => void;\n\n // ── Controlled slices for saved views ─────────────────────────────────────\n /** Controlled sorting state. When provided the component is sorted-controlled. */\n sorting?: SortingState;\n onSortingChange?: OnChangeFn<SortingState>;\n\n /** Controlled column-visibility state. */\n columnVisibility?: VisibilityState;\n onColumnVisibilityChange?: OnChangeFn<VisibilityState>;\n\n /** Controlled column-filters state. */\n columnFilters?: ColumnFiltersState;\n onColumnFiltersChange?: OnChangeFn<ColumnFiltersState>;\n\n /** Controlled pagination state. */\n pagination?: PaginationState;\n onPaginationChange?: OnChangeFn<PaginationState>;\n\n /**\n * Controlled column-pinning state (#333) — the columns frozen against the\n * left and/or right edge while the rest of the table scrolls horizontally.\n * When provided the component is pinning-controlled; otherwise it manages the\n * slice internally and can be seeded once via `initialView.columnPinning`.\n *\n * A pinned column MUST declare an explicit `size` in its `ColumnDef`: the\n * sticky offset is computed from TanStack's `column.getStart(\"left\")` /\n * `getAfter(\"right\")`, which sum the DECLARED sizes, so an auto-width column\n * would render at a width that doesn't match its own offset. A dev-only\n * warning fires for a pinned column with no `size`.\n *\n * Pinning is a LAYOUT concern, not a query concern — it is client-only and\n * never joins `DataTableServerArgs` / `onServerChange`.\n */\n columnPinning?: ColumnPinningState;\n onColumnPinningChange?: OnChangeFn<ColumnPinningState>;\n\n /**\n * Opt in to column resizing (#12): a drag handle renders on every\n * resizable column's trailing edge — pointer-draggable (TanStack's own\n * `header.getResizeHandler()`) and keyboard-operable (ArrowLeft/ArrowRight\n * on the focused handle, per the WAI-ARIA separator-as-slider practice).\n * Default `false` so a table that doesn't opt in renders byte-identical\n * markup to before this feature existed — no handle, no per-cell width\n * styling.\n */\n enableColumnResizing?: boolean;\n /**\n * When `columnSizing` updates: `\"onChange\"` (default here — TanStack's own\n * default is `\"onEnd\"`) live-updates while dragging; `\"onEnd\"` updates once\n * on release. Only meaningful when `enableColumnResizing` is set.\n */\n columnResizeMode?: \"onChange\" | \"onEnd\";\n /**\n * Controlled column-widths state (#12), keyed by column id — the SAME\n * controlled/uncontrolled shape as `columnPinning`/`rowSelection`.\n * Uncontrolled sizing can be seeded once via `initialView.columnSizing`.\n *\n * A pinned column's sticky offset (`getStart(\"left\")`/`getAfter(\"right\")`)\n * already sums `column.getSize()`, which folds in a `columnSizing`\n * override automatically — so pinning and resizing compose with no extra\n * wiring once this state reaches the table.\n *\n * Sizing is a LAYOUT concern, like `columnPinning`/`rowSelection` — it is\n * client-only and never joins `DataTableServerArgs` / `onServerChange`.\n */\n columnSizing?: ColumnSizingState;\n onColumnSizingChange?: OnChangeFn<ColumnSizingState>;\n\n /**\n * Controlled row-selection state (#11) — which rows are checked, keyed by\n * row id (see `getRowId`). When provided the component is\n * selection-controlled; otherwise it manages the slice internally and can\n * be seeded once via `initialView.rowSelection`. Pair it with a selection\n * column built by `createSelectionColumn` (or drive it yourself off the\n * `table` instance handed to `toolbar`).\n *\n * Selection is a LAYOUT/UI concern, not a query concern — like\n * `columnPinning`, it is client-only and never joins `DataTableServerArgs` /\n * `onServerChange`.\n */\n rowSelection?: RowSelectionState;\n onRowSelectionChange?: OnChangeFn<RowSelectionState>;\n /**\n * Which rows can be selected: `true`/`false` for all rows, or a predicate\n * evaluated per row. Passed straight through to `useReactTable`. Default\n * (TanStack's own): `true`.\n */\n enableRowSelection?: boolean | ((row: Row<TData>) => boolean);\n /**\n * Allow more than one row to be selected at once. Default (TanStack's own):\n * `true`. Set `false` for single-select (radio-style) behaviour.\n */\n enableMultiRowSelection?: boolean;\n /**\n * Stable row id, independent of row INDEX. TanStack's default id is set\n * ONCE per row object when the core row model is built, then reused by\n * reference through sorting/filtering — so a client-side sort or filter\n * does NOT disturb selection identity even without this prop. The real\n * hazard is a `data` array replacement: when the app passes NEW object\n * references (a re-fetch, an optimistic update), TanStack rebuilds the\n * core row model from scratch and reassigns default (index-based) ids, so a\n * row that kept its position but got a new object still keeps its\n * selection — but one that MOVED position silently inherits whatever\n * selection belonged to the id now sitting at its old index. This is\n * unavoidable under `manualPagination`: each page IS a fresh `data` array,\n * so the default index-based id restarts at `0` on every page and a\n * selection made on one page can collide with a different record on the\n * next. Supply `getRowId` whenever `data` can be replaced with new object\n * references (including every server-paginated table) so identity survives\n * the replacement instead of falling back to index.\n */\n getRowId?: (row: TData, index: number) => string;\n\n /**\n * One-shot rehydrate for uncontrolled slices only (ignored for any slice\n * whose corresponding controlled prop is set). Maps to `useReactTable`'s\n * `initialState`.\n */\n initialView?: Partial<DataTableViewState>;\n\n // ── Server-side data model ──────────────────────────────────────────────────\n /**\n * When true, sorting is handled by the server. Pass `sorting` (controlled)\n * and handle `onServerChange` to re-fetch with the new sort params.\n * NOTE: controlled ≠ manual — a controlled `sorting` with `manualSorting:false`\n * still sorts locally.\n */\n manualSorting?: boolean;\n /**\n * When true, filtering is handled by the server.\n * NOTE: a controlled `columnFilters` with `manualFiltering:false` still\n * filters locally.\n */\n manualFiltering?: boolean;\n /** When true, pagination is handled by the server. */\n manualPagination?: boolean;\n\n /**\n * Total row count — used by the server model so TanStack can derive\n * page count. Required when `manualPagination` is true and `pageCount` is\n * not provided.\n */\n rowCount?: number;\n /**\n * Total page count — alternative to `rowCount` for server pagination. When\n * both are provided, `pageCount` wins.\n */\n pageCount?: number;\n\n /**\n * Fired after any manual-slice change with the current {pagination, sorting,\n * columnFilters, globalFilter}. The component never fetches; the app must\n * re-fetch and update `data`.\n */\n onServerChange?: (args: DataTableServerArgs) => void;\n\n /** When true: overlay spinner; on empty+loading show skeleton rows instead of empty message. */\n loading?: boolean;\n\n // ── Virtualization ─────────────────────────────────────────────────────────\n /**\n * Opt-in to row virtualization (for very large lists). Mutually exclusive\n * with enablePagination in practice — if both are set, virtualization wins\n * and pagination is silently ignored.\n */\n enableRowVirtualization?: boolean;\n /** Estimated row height in px (used by the virtualizer). Default: 40. */\n estimateRowHeight?: number;\n /** Virtualizer overscan (rows rendered above/below the visible window). Default: 8. */\n overscan?: number;\n /** CSS max-height of the scroll container in virtualized mode. Default: \"32rem\". */\n maxBodyHeight?: string;\n\n /**\n * Number of skeleton placeholder rows to render while loading.\n * Defaults to `pageSize` (non-virtualized) or `min(10, pageSize)` (virtualized).\n */\n loadingRows?: number;\n\n /**\n * Gentle alternating row stripes (\"zebra\") as the row-separation cue, instead\n * of a hairline divider between every row. Default `true` — the stripe is the\n * single separation gesture, so rows carry no divider (a divider on a striped\n * row would be a redundant boundary). Set `false` for the classic line model\n * (a `border-border-strong` divider between rows, no stripes).\n */\n zebra?: boolean;\n\n /**\n * Draw a quiet `--rule` hairline between columns (header and body). Off by\n * default. Pinned cells keep their own seam and never take a divider.\n */\n columnDividers?: boolean;\n\n // ── Row drag-reorder (#13) ───────────────────────────────────────────────\n /**\n * Opt-in row drag-reorder. Off by default — an existing table renders\n * byte-identical markup with no extra DOM per row until this is set.\n * Fully controlled like every other slice: the component never mutates\n * `data` itself, it only reports the move via `onRowReorder`; the caller\n * re-orders `data` in response.\n *\n * Keyboard-operable out of the box (`@dnd-kit`'s default keyboard sensor):\n * Space/Enter picks a row up, Arrow Up/Down moves it, Space/Enter drops it,\n * Escape cancels. Every position change is announced through a live region\n * (WCAG 4.1.3).\n *\n * Mutually exclusive with `enableRowVirtualization` — a windowed table\n * can't keep dnd-kit's sortable list and a virtualizer in sync, so reorder\n * is silently disabled (a dev warning fires) when both are set. Combining\n * it with active `sorting` also fires a dev warning (both still work, but\n * a sort re-orders the very rows a drag just moved, which reads as broken).\n */\n enableRowReorder?: boolean;\n /**\n * Fires when a row is dropped in a new position. `from`/`to` are indices\n * into the **`data` array you passed in** — never into the sorted, filtered\n * or paginated view the table renders — so they are safe to use directly\n * with `arrayMove`/`slice`+`splice`/immer against your own `data`, unchanged\n * by an active sort or by client-side pagination (the dragged row's true\n * index in the full array, not its index on the current page). Under\n * `manualPagination`, `data` IS the current page, so `from`/`to` are\n * page-relative — reorder that page's own array with them. `row` is the\n * moved record (`data[from]`).\n */\n onRowReorder?: (from: number, to: number, row: TData) => void;\n /**\n * Where the drag activator lives. `\"cell\"` (default) renders a dedicated\n * grip-handle column so the rest of the row keeps its ordinary click/\n * keyboard behavior untouched. `\"row\"` makes the whole row itself the drag\n * activator (no extra column) — reach for this only when the row has no\n * other primary interaction (e.g. no `onRowClick`), since a whole-row\n * activator and a row click target the same surface.\n */\n rowReorderHandle?: \"cell\" | \"row\";\n\n /**\n * Fires when a row is activated (#337). Setting it adds ONE activation\n * target per row: a visually-hidden `<button>` rendered inside the row's\n * first cell. That button is the row's keyboard tab stop and its accessible\n * name; a pointer click anywhere else in the row resolves to the same\n * handler, so mouse and keyboard converge on one control instead of two\n * competing ones (a focusable `<tr>` cannot carry an activation role without\n * destroying `row` table semantics).\n *\n * Guarded: a click that originates on a nested interactive control\n * (button/link/input/checkbox/…) or is the tail end of a text-selection drag\n * does NOT fire it. Optional; omitting it renders rows exactly as before.\n */\n onRowClick?: DataTableRowClickHandler<TData>;\n /**\n * Accessible name for the row's hidden activation button (#337). Only read\n * when `onRowClick` is set. Defaults to the row's first visible cell value\n * when that is a string/number (the row's primary identifier — the same\n * naming a link in that cell would get), else the localized\n * `data.table.rowAction` fallback. Supply it whenever the first cell isn't a\n * good name for the row.\n */\n rowActionLabel?: (row: Row<TData>) => string;\n /**\n * Per-row className, merged alongside the existing zebra/line/hover/selected\n * classes via `cn()` (so it can't accidentally clobber them) (#337).\n */\n rowClassName?: (row: Row<TData>) => string;\n\n /**\n * Accessible name for the table, rendered as a visually-hidden (`sr-only`)\n * `<caption>` — the first child of `<table>`. Screen readers announce it as\n * the table's name and it makes column-header navigation meaningful.\n * Optional; omit it only when the surrounding page already labels the table\n * unambiguously (e.g. an adjacent heading) (#338).\n */\n caption?: ReactNode;\n\n /** Message shown when there are no rows and not loading. */\n emptyMessage?: ReactNode;\n className?: string;\n}\n\n// ─── Row-click guards (module-level — shared by every renderRow call) ────────\n\n/**\n * CSS selector for anything inside a row that owns its own click/keyboard\n * behavior. A row click must not fire when the user actually meant to\n * activate one of these — the row is the activation target for everything\n * ELSE in the row, not a second competing target (#337).\n */\nconst ROW_CLICK_GUARD_SELECTOR =\n 'button, a[href], input, select, textarea, label, summary, [role=\"button\"], [role=\"link\"], [role=\"menuitem\"], [role=\"checkbox\"], [role=\"radio\"], [role=\"switch\"], [role=\"tab\"], [contenteditable=\"true\"]';\n\nfunction isInteractiveEventTarget(target: EventTarget | null): boolean {\n return target instanceof Element && target.closest(ROW_CLICK_GUARD_SELECTOR) !== null;\n}\n\n/**\n * True while the user is completing a text-selection drag — a row click must\n * not fire for the mouseup/click that ends a selection (#337).\n */\nfunction isActiveTextSelection(): boolean {\n if (typeof window === \"undefined\" || typeof window.getSelection !== \"function\") return false;\n return window.getSelection()?.type === \"Range\";\n}\n\n// ─── Pinning helpers (module-level) ──────────────────────────────────────────\n\n/**\n * The 1px seam between the frozen block and the scrolling block (#333), minus\n * the side — `pinnedCellGeometry` appends `after:end-0` or `after:start-0`.\n *\n * A pseudo-element rather than a `border-e`/`border-s` on purpose: see the note\n * in `pinnedCellGeometry`. Token-backed (`bg-border-strong`, the strong rung per\n * ADR 0010) and no shadow, so a shadowless surface (\n * `data-decoration=\"8|9|10\"`) cannot delete it.\n */\nconst PINNED_SEAM_CLASS =\n \"after:pointer-events-none after:absolute after:inset-y-0 after:w-px after:bg-border-strong after:content-['']\";\n\n/**\n * Opt-in `columnDividers` hairline. `--rule`, not `--border-strong`: the column\n * is already told apart by alignment and whitespace, so this line is a\n * redundant boundary (ADR 0010). A real border is fine here, unlike the pinned\n * seam above — pinned cells never take it.\n */\nconst COLUMN_DIVIDER_CLASS = \"border-e border-rule last:border-e-0\";\n\n/**\n * Ids of leaf columns whose ORIGINAL `ColumnDef` declares no `size` (#333).\n *\n * Deliberately reads the raw `columns` prop rather than `column.columnDef`:\n * TanStack merges its `defaultColumnSizing` (`size: 150`) into every resolved\n * column def, so the resolved def can never distinguish \"the author sized this\"\n * from \"the author left it to the default\" — and the whole point of the pinned\n * `size` warning is to catch the second case.\n *\n * Mirrors TanStack's own id resolution: `columnDef.id`, else the `accessorKey`\n * with `.` → `_`, else a string `header`.\n */\nfunction unsizedColumnIds<TData, TValue>(defs: readonly ColumnDef<TData, TValue>[]): Set<string> {\n const out = new Set<string>();\n const walk = (list: readonly ColumnDef<TData, TValue>[]) => {\n for (const def of list) {\n const group = def as { columns?: ColumnDef<TData, TValue>[] };\n if (group.columns) {\n walk(group.columns);\n continue;\n }\n if (def.size !== undefined) continue;\n const accessorKey = (def as { accessorKey?: string | number }).accessorKey;\n const id =\n def.id ??\n (accessorKey !== undefined\n ? String(accessorKey).replace(/\\./gu, \"_\")\n : typeof def.header === \"string\"\n ? def.header\n : undefined);\n if (id) out.add(id);\n }\n };\n walk(defs);\n return out;\n}\n\n// ─── Column resizing (#12) ────────────────────────────────────────────────────\n\n/**\n * Explicit width/min/max triad for one column at its CURRENT size.\n *\n * The table is auto-layout (see the note on `pinnedCellGeometry` below), so\n * without an explicit width an unpinned column is pure browser auto-layout —\n * `column.getSize()` can change (via a drag or a keyboard resize) with\n * nothing rendering differently. A pinned cell already gets this triad from\n * `pinnedCellGeometry`'s own `style`; this is the same triad for the\n * UNPINNED case, so every call site can compute it once and use it in both\n * the pinned-or-not branches (`geometry?.style ?? resizeWidthStyle(size)`).\n * Every call site gates this behind `enableColumnResizing`, so a table that\n * doesn't opt in renders byte-identical markup to before this feature\n * existed.\n */\nfunction resizeWidthStyle(size: number): React.CSSProperties {\n return { width: size, minWidth: size, maxWidth: size };\n}\n\n// ─── Row-selection column (#11) ──────────────────────────────────────────────\n//\n// `flexRender` mounts a function `header`/`cell` as a real React component\n// (`React.createElement(Comp, props)`, not a bare function call — see\n// `@tanstack/react-table`'s `flexRender`), so these are ordinary components:\n// hooks (`useLocale`) are safe inside them.\n\n/**\n * The row's own \"primary identifier\" — the first visible DATA column's value,\n * skipping display columns that carry no `accessorKey`/`accessorFn` (e.g. a\n * leading `createSelectionColumn()` checkbox, or a decorative avatar column).\n * `column.accessorFn` is public TanStack API, populated for any\n * `accessorKey`/`accessorFn` column and `undefined` for a pure display column\n * (`core/column.ts`) — so this is a reliable \"is this a data column\" test.\n * Shared by `rowActionName` (#337) and the selection column's per-row\n * accessible name (#11 I4/I6), so a leading selection column can't silently\n * degrade either one to its generic fallback.\n */\nfunction firstDataCellValue<TData>(row: Row<TData>): string | undefined {\n for (const cell of row.getVisibleCells()) {\n if (!cell.column.accessorFn) continue;\n const value = cell.getValue();\n if (typeof value === \"string\" && value.trim() !== \"\") return value;\n if (typeof value === \"number\") return String(value);\n }\n return undefined;\n}\n\n/**\n * Select-all header cell. Radix `Checkbox` renders a genuinely distinct\n * `indeterminate` glyph + `aria-checked=\"mixed\"` for a partial page\n * selection (see `checkbox.tsx`), so the visual and the accessible state\n * agree without any extra wiring here.\n */\nfunction SelectAllHeaderCell<TData>({ table }: { table: TanstackTable<TData> }) {\n const { t } = useLocale();\n const allSelected = table.getIsAllPageRowsSelected();\n const someSelected = table.getIsSomePageRowsSelected();\n return (\n <Checkbox\n data-slot=\"data-table-select-all\"\n checked={allSelected ? true : someSelected ? \"indeterminate\" : false}\n onCheckedChange={(checked) => table.toggleAllPageRowsSelected(checked === true)}\n aria-label={t(\"data.table.selectAllRows\")}\n />\n );\n}\n\n/**\n * Per-row checkbox cell — disabled when `enableRowSelection` excludes the\n * row. Names each checkbox from the row's own data (#11 I4) instead of the\n * identical generic label every row previously shared, using the same\n * \"first data cell\" lookup `rowActionName` (#337) already uses.\n */\nfunction SelectRowCell<TData>({ row }: { row: Row<TData> }) {\n const { t } = useLocale();\n const name = firstDataCellValue(row);\n return (\n <Checkbox\n data-slot=\"data-table-select-cell\"\n checked={row.getIsSelected()}\n disabled={!row.getCanSelect()}\n onCheckedChange={(checked) => row.toggleSelected(checked === true)}\n aria-label={name ? t(\"data.table.selectRowNamed\", { name }) : t(\"data.table.selectRow\")}\n />\n );\n}\n\n/**\n * Ready-made checkbox selection column (#11): header select-all (with a real\n * `indeterminate` state for a partial page selection) + a per-row checkbox,\n * both built on `@elabs-ai/components-ui`'s `Checkbox` — never hand-roll one.\n *\n * Add it to `columns` and pair it with `rowSelection` / `onRowSelectionChange`\n * (or leave both uncontrolled and read `table.getSelectedRowModel()` from a\n * `toolbar` render-prop to build a bulk-action bar).\n *\n * Declares an explicit `size` (40px) so it plays nicely if a caller pins it —\n * every pinned column must declare one (#333) — without the dev warning.\n */\nexport function createSelectionColumn<TData>(): ColumnDef<TData> {\n return {\n id: \"select\",\n size: 40,\n enableSorting: false,\n enableHiding: false,\n header: ({ table }) =>\n // #11 C1: `toggleAllPageRowsSelected` wipes-then-sets on every row when\n // `enableMultiRowSelection` is off (TanStack's `mutateRowIsSelected`), so\n // a select-all header under single-select leaves only the LAST row\n // selected and pins the header at indeterminate forever. Suppress it.\n table.options.enableMultiRowSelection === false ? null : (\n <SelectAllHeaderCell table={table} />\n ),\n cell: ({ row }) => <SelectRowCell row={row} />,\n };\n}\n\n// ─── Row drag-reorder (#13) ─────────────────────────────────────────────────\n\n/** Render-prop payload `SortableDataRow` hands its child — the live dnd-kit\n * registration for one row. */\ninterface SortableRowRenderArgs {\n setNodeRef: (node: HTMLElement | null) => void;\n setActivatorNodeRef: (node: HTMLElement | null) => void;\n attributes: DraggableAttributes;\n listeners: DraggableSyntheticListeners;\n isDragging: boolean;\n style: React.CSSProperties;\n}\n\n/**\n * Per-row `@dnd-kit` registration, defined ONCE at module level.\n *\n * This must be a real component, not a hook call inlined into `rows.map()`\n * (that would call `useSortable` a variable number of times across renders —\n * the classic \"hook in a loop\" Rules-of-Hooks violation the moment the row\n * count changes) and not a component DEFINED inside `DataTableInner`'s body\n * either (a function created fresh every render gets a new `type` identity,\n * so React would tear down and remount the whole row subtree, including\n * dnd-kit's own internal drag state, on every re-render). A stable top-level\n * component keyed by `id` gives every row its own persistent `useSortable`\n * state via ordinary type+key reconciliation.\n *\n * `transition: null` is deliberate — dnd-kit's own transition is a raw\n * inline `ms` duration, which would bypass the gated `duration-*`/`ease-*`\n * utilities (quality-gates.md \"Motion-tokened\"). The moving row instead gets\n * `transition-transform duration-base ease-standard motion-reduce:transition-none`\n * as a class at the call site; only the live `transform` stays inline.\n */\nfunction SortableDataRow({\n id,\n disabled,\n attributesOverride,\n children,\n}: {\n id: string;\n disabled?: boolean;\n /**\n * `rowReorderHandle: \"row\"` applies `attributes`/`listeners` straight to\n * the `<tr>` (no separate activator element), so dnd-kit's DEFAULT\n * `role=\"button\"` would replace the table's own `role=\"row\"` on that\n * element — destroying its row semantics. Override the role in that mode\n * only; `\"cell\"` mode leaves `role` unset because the grip `<button>` —\n * not the `<tr>` — receives `attributes`/`listeners`. `roleDescription` is\n * overridden in BOTH modes (#98) — it carries dnd-kit's localized\n * `aria-roledescription`, which the activator needs regardless of which\n * element is the activator.\n */\n attributesOverride?: { role?: string; roleDescription?: string; tabIndex?: number };\n children: (args: SortableRowRenderArgs) => ReactNode;\n}) {\n const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, isDragging } =\n useSortable({ id, disabled, transition: null, attributes: attributesOverride });\n return (\n <>\n {children({\n setNodeRef,\n setActivatorNodeRef,\n attributes,\n listeners,\n isDragging,\n style: { transform: CSS.Transform.toString(transform) },\n })}\n </>\n );\n}\n\n// ─── Component (inner, generic) ───────────────────────────────────────────────\n\n/**\n * Branded TanStack Table wrapper with sorting, global filtering, column\n * visibility and optional pagination. The toolbar render-prop hands you the\n * table instance so SearchInput / FacetFilter / ColumnPicker can drive it.\n *\n * Every slice (sorting / columnVisibility / columnFilters / pagination) is\n * independently controllable. Uncontrolled slices are managed internally.\n * Pass `manualSorting` / `manualFiltering` / `manualPagination` to opt into\n * server-driven data; `onServerChange` fires after each slice change so the\n * app can re-fetch.\n *\n * Accepts a forwarded `ref` to the outermost wrapper `<div>` and spreads any\n * additional HTML div props (e.g. `id`, `aria-*`, `data-*`) onto that element.\n */\nfunction DataTableInner<TData, TValue>(\n {\n columns,\n data,\n toolbar,\n enablePagination = false,\n pageSize = 10,\n hidePaginationWhenSingle = true,\n\n // Global filter\n globalFilter: globalFilterProp,\n onGlobalFilterChange,\n\n // Controlled slices\n sorting: sortingProp,\n onSortingChange: onSortingChangeProp,\n columnVisibility: columnVisibilityProp,\n onColumnVisibilityChange: onColumnVisibilityChangeProp,\n columnFilters: columnFiltersProp,\n onColumnFiltersChange: onColumnFiltersChangeProp,\n pagination: paginationProp,\n onPaginationChange: onPaginationChangeProp,\n columnPinning: columnPinningProp,\n onColumnPinningChange: onColumnPinningChangeProp,\n enableColumnResizing = false,\n columnResizeMode = \"onChange\",\n columnSizing: columnSizingProp,\n onColumnSizingChange: onColumnSizingChangeProp,\n rowSelection: rowSelectionProp,\n onRowSelectionChange: onRowSelectionChangeProp,\n enableRowSelection,\n enableMultiRowSelection,\n getRowId,\n\n // Saved views rehydration\n initialView,\n\n // Server-side model\n manualSorting = false,\n manualFiltering = false,\n manualPagination = false,\n rowCount,\n pageCount,\n onServerChange,\n\n // Loading\n loading = false,\n loadingRows,\n\n // Virtualization\n enableRowVirtualization = false,\n estimateRowHeight = 40,\n overscan = 8,\n maxBodyHeight = \"32rem\",\n\n zebra = true,\n columnDividers = false,\n\n // Row drag-reorder (#13)\n enableRowReorder = false,\n onRowReorder,\n rowReorderHandle = \"cell\",\n\n onRowClick,\n rowActionLabel,\n rowClassName,\n caption,\n emptyMessage = \"No results.\",\n className,\n ...rest\n }: DataTableProps<TData, TValue>,\n ref: React.Ref<HTMLDivElement>,\n) {\n // Component microcopy goes through the locale seam (ADR 0017) — a screen-reader\n // user in a non-English locale has no workaround for a hardcoded accessible name.\n // `dir` also drives column-resize direction below (#12 review, P1): the resize\n // handle already sits at the column's logical `end` edge (`end-0`, which\n // Tailwind's logical properties flip to the physical LEFT under RTL), so both\n // TanStack's own pointer-drag math and the hand-rolled keyboard path must be\n // told the active direction too, or dragging/pressing an arrow moves the width\n // opposite the visible boundary.\n const { t, dir, formatNumber } = useLocale();\n\n // ── Controlled/uncontrolled detection ────────────────────────────────────\n const isSortingControlled = sortingProp !== undefined;\n const isColumnVisibilityControlled = columnVisibilityProp !== undefined;\n const isColumnFiltersControlled = columnFiltersProp !== undefined;\n const isPaginationControlled = paginationProp !== undefined;\n const isFilterControlled = globalFilterProp !== undefined;\n const isColumnPinningControlled = columnPinningProp !== undefined;\n const isColumnSizingControlled = columnSizingProp !== undefined;\n const isRowSelectionControlled = rowSelectionProp !== undefined;\n\n // ── Internal state (only drives a slice when uncontrolled) ───────────────\n const [internalSorting, setInternalSorting] = useState<SortingState>(\n () => initialView?.sorting ?? [],\n );\n const [internalColumnVisibility, setInternalColumnVisibility] = useState<VisibilityState>(\n () => initialView?.columnVisibility ?? {},\n );\n const [internalColumnFilters, setInternalColumnFilters] = useState<ColumnFiltersState>(\n () => initialView?.columnFilters ?? [],\n );\n const [internalPagination, setInternalPagination] = useState<PaginationState>(\n () =>\n initialView?.pagination ?? {\n pageIndex: 0,\n pageSize,\n },\n );\n const [internalGlobalFilter, setInternalGlobalFilter] = useState<string>(\n () => initialView?.globalFilter ?? \"\",\n );\n const [internalColumnPinning, setInternalColumnPinning] = useState<ColumnPinningState>(\n () => initialView?.columnPinning ?? { left: [], right: [] },\n );\n const [internalColumnSizing, setInternalColumnSizing] = useState<ColumnSizingState>(\n () => initialView?.columnSizing ?? {},\n );\n const [internalRowSelection, setInternalRowSelection] = useState<RowSelectionState>(\n () => initialView?.rowSelection ?? {},\n );\n\n // ── Resolved state (controlled wins over internal) ───────────────────────\n const sorting = isSortingControlled ? sortingProp : internalSorting;\n const columnVisibility = isColumnVisibilityControlled\n ? columnVisibilityProp\n : internalColumnVisibility;\n const columnFilters = isColumnFiltersControlled ? columnFiltersProp : internalColumnFilters;\n const pagination = isPaginationControlled ? paginationProp : internalPagination;\n const globalFilter = isFilterControlled ? globalFilterProp : internalGlobalFilter;\n const columnPinning = isColumnPinningControlled ? columnPinningProp : internalColumnPinning;\n const columnSizing = isColumnSizingControlled ? columnSizingProp : internalColumnSizing;\n const rowSelection = isRowSelectionControlled ? rowSelectionProp : internalRowSelection;\n\n // ── Refs for post-change server callback ─────────────────────────────────\n // We need the current values of ALL slices when any one fires; use refs to\n // avoid stale closures without adding them as deps.\n const sortingRef = useRef(sorting);\n sortingRef.current = sorting;\n const columnFiltersRef = useRef(columnFilters);\n columnFiltersRef.current = columnFilters;\n const paginationRef = useRef(pagination);\n paginationRef.current = pagination;\n const globalFilterRef = useRef(globalFilter);\n globalFilterRef.current = globalFilter;\n const columnVisibilityRef = useRef(columnVisibility);\n columnVisibilityRef.current = columnVisibility;\n const columnPinningRef = useRef(columnPinning);\n columnPinningRef.current = columnPinning;\n const columnSizingRef = useRef(columnSizing);\n columnSizingRef.current = columnSizing;\n const rowSelectionRef = useRef(rowSelection);\n rowSelectionRef.current = rowSelection;\n\n // ── Dev-only guard: manualPagination needs a total to compute page count ──\n // Without `rowCount` (or `pageCount`), TanStack's `getPageCount()` falls back\n // to the CURRENT PAGE's row count (manual mode has no full row model), so the\n // pager silently reads \"Page 1 of 1\" with Next permanently disabled. Warn\n // once per mount so the missing prop is diagnosable instead of silent (#227).\n const warnedMissingRowCountRef = useRef(false);\n useEffect(() => {\n if (\n process.env.NODE_ENV !== \"production\" &&\n manualPagination &&\n rowCount === undefined &&\n pageCount === undefined &&\n !warnedMissingRowCountRef.current\n ) {\n warnedMissingRowCountRef.current = true;\n console.warn(\n \"[DataTable] `manualPagination` is true but neither `rowCount` nor `pageCount` was \" +\n 'provided — the pager will appear stuck (\"Page 1 of 1\", Next disabled). Pass ' +\n \"`rowCount` (or `pageCount`) so the pager can compute the total.\",\n );\n }\n }, [manualPagination, rowCount, pageCount]);\n\n // ── Dev-only guard: manualPagination + rowSelection with no getRowId ──────\n // Under `manualPagination` each page IS a fresh `data` array, so TanStack's\n // default index-based row id restarts at `0` on every page — a selection\n // made on page 1's row 0 can silently apply to page 2's row 0 too (#11 I3).\n // Warn once per mount so this footgun is diagnosable instead of silent (same\n // idiom as the #227 warning above). Heuristic, not full usage tracing: fires\n // whenever selection LOOKS wired up (controlled, or a change handler was\n // passed) — it cannot see an uncontrolled table that never renders a\n // selection column at all.\n const warnedManualSelectionRef = useRef(false);\n useEffect(() => {\n if (\n process.env.NODE_ENV !== \"production\" &&\n manualPagination &&\n getRowId === undefined &&\n (isRowSelectionControlled || onRowSelectionChangeProp !== undefined) &&\n !warnedManualSelectionRef.current\n ) {\n warnedManualSelectionRef.current = true;\n console.warn(\n \"[DataTable] `rowSelection` is wired up under `manualPagination` with no `getRowId` \" +\n \"— each page is a fresh `data` array, so the default index-based id restarts at \" +\n '\"0\" per page and a selection made on one page can silently apply to a different ' +\n \"record on the next. Pass `getRowId` so selection is keyed to a stable identity \" +\n \"instead of position.\",\n );\n }\n }, [manualPagination, getRowId, isRowSelectionControlled, onRowSelectionChangeProp]);\n\n // ── Dev-only guard: enableRowReorder + active sorting (#13) ───────────────\n // Both keep working — this doesn't disable anything — but a sort re-orders\n // the very rows a drag just moved, which reads as broken rather than merely\n // confusing. Warn once per mount, same idiom as the two guards above.\n const warnedReorderSortingRef = useRef(false);\n useEffect(() => {\n if (\n process.env.NODE_ENV !== \"production\" &&\n enableRowReorder &&\n sorting.length > 0 &&\n !warnedReorderSortingRef.current\n ) {\n warnedReorderSortingRef.current = true;\n console.warn(\n \"[DataTable] `enableRowReorder` is set while a column is sorted — the sort will \" +\n \"keep re-ordering rows out from under a manual drag. Clear `sorting` (or avoid \" +\n \"enabling both at once) so a drag's new order stays stable.\",\n );\n }\n }, [enableRowReorder, sorting.length]);\n\n // ── Dev-only guard: enableRowReorder + enableRowVirtualization (#13) ──────\n // A windowed table can't keep dnd-kit's sortable list in sync with a\n // virtualizer that only mounts a subset of rows, so the two are mutually\n // exclusive — virtualization wins (same precedent as enablePagination vs.\n // enableRowVirtualization) and reorder is silently disabled below\n // (`rowReorderActive`). This warning is the diagnostic for why.\n const warnedReorderVirtualizedRef = useRef(false);\n useEffect(() => {\n if (\n process.env.NODE_ENV !== \"production\" &&\n enableRowReorder &&\n enableRowVirtualization &&\n !warnedReorderVirtualizedRef.current\n ) {\n warnedReorderVirtualizedRef.current = true;\n console.warn(\n \"[DataTable] `enableRowReorder` has no effect while `enableRowVirtualization` is \" +\n \"set — the two are mutually exclusive. Virtualization wins; row reorder is disabled.\",\n );\n }\n }, [enableRowReorder, enableRowVirtualization]);\n\n // Only wired up in the non-virtualized body — see the warning above.\n const rowReorderActive = enableRowReorder && !enableRowVirtualization;\n const hasGripColumn = rowReorderActive && rowReorderHandle === \"cell\";\n\n const reorderSensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),\n useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),\n );\n // Backing store for `getReorderRowId` (defined below, once `rows` is in\n // scope) — see its own doc comment for why a WeakMap keyed by row object\n // reference is the round-1 fix for findings 1 & 3.\n const reorderIdentityMapRef = useRef<WeakMap<object, string>>(new WeakMap());\n const reorderIdentityCounterRef = useRef(0);\n // Positions in `data` whose record REPEATS an object reference that already\n // appeared earlier in the array — 2nd and later occurrences only (round-2\n // finding 6). `getReorderRowId` below keys its identity on the record's own\n // object reference, which is exactly what makes an id survive the array\n // REPLACEMENT every reorder idiom performs; the cost is that a record the\n // caller listed twice IS one reference, so both rows would be handed one id\n // — one React key, one dnd-kit registration, and a drop that can only ever\n // name the first occurrence. The positions listed here get their own data\n // index folded into the id so the occurrences stay separately addressable.\n // Only the repeats are suffixed, so a table with no repeated record keeps\n // byte-identical ids (and with them the round-1 focus restore).\n const reorderRepeatedPositions = useMemo(() => {\n const repeats = new Set<number>();\n if (!rowReorderActive) return repeats;\n const seen = new Set<unknown>();\n data.forEach((record, index) => {\n if (record === null || typeof record !== \"object\") return;\n if (seen.has(record)) repeats.add(index);\n else seen.add(record);\n });\n return repeats;\n }, [data, rowReorderActive]);\n // The component's OWN `aria-live=\"polite\"` announcer state — round-1\n // finding 4 (dnd-kit's built-in region is hardcoded `assertive` with no\n // override). `reorderLastAnnouncedPositionRef` de-dupes a same-position\n // re-fire (the pickup self-collision, a no-op arrow press at a boundary).\n const [reorderLiveMessage, setReorderLiveMessage] = useState(\"\");\n const reorderLastAnnouncedPositionRef = useRef<number | null>(null);\n\n /** Fire onServerChange with the LATEST slice values (post-update). */\n function fireServerChange(overrides: Partial<DataTableServerArgs> = {}) {\n if (!onServerChange) return;\n onServerChange({\n pagination: paginationRef.current,\n sorting: sortingRef.current,\n columnFilters: columnFiltersRef.current,\n globalFilter: globalFilterRef.current,\n ...overrides,\n });\n }\n\n // ── Updater helpers — all five slices resolve a functional updater against\n // their *Ref.current (the post-update value), never the render-closure\n // variable, so the resolution stays correct once these callbacks are\n // memoized (a useCallback wrap or the React Compiler) ──────────────────────\n function resolveSorting(updater: Parameters<OnChangeFn<SortingState>>[0]): SortingState {\n return typeof updater === \"function\" ? updater(sortingRef.current) : updater;\n }\n function resolveColumnVisibility(\n updater: Parameters<OnChangeFn<VisibilityState>>[0],\n ): VisibilityState {\n return typeof updater === \"function\" ? updater(columnVisibilityRef.current) : updater;\n }\n function resolveColumnFilters(\n updater: Parameters<OnChangeFn<ColumnFiltersState>>[0],\n ): ColumnFiltersState {\n return typeof updater === \"function\" ? updater(columnFiltersRef.current) : updater;\n }\n function resolvePagination(updater: Parameters<OnChangeFn<PaginationState>>[0]): PaginationState {\n return typeof updater === \"function\" ? updater(paginationRef.current) : updater;\n }\n function resolveGlobalFilter(updater: Parameters<OnChangeFn<string>>[0]): string {\n return typeof updater === \"function\" ? updater(globalFilterRef.current) : updater;\n }\n function resolveColumnPinning(\n updater: Parameters<OnChangeFn<ColumnPinningState>>[0],\n ): ColumnPinningState {\n return typeof updater === \"function\" ? updater(columnPinningRef.current) : updater;\n }\n function resolveColumnSizing(\n updater: Parameters<OnChangeFn<ColumnSizingState>>[0],\n ): ColumnSizingState {\n return typeof updater === \"function\" ? updater(columnSizingRef.current) : updater;\n }\n function resolveRowSelection(\n updater: Parameters<OnChangeFn<RowSelectionState>>[0],\n ): RowSelectionState {\n return typeof updater === \"function\" ? updater(rowSelectionRef.current) : updater;\n }\n\n // ── Row models — omit client model for manual slices ─────────────────────\n const sortedRowModel = manualSorting ? {} : { getSortedRowModel: getSortedRowModel() };\n const filteredRowModel = manualFiltering ? {} : { getFilteredRowModel: getFilteredRowModel() };\n // Only attach the client pagination row model when we actually paginate locally.\n // Under `manualPagination`, TanStack ignores a supplied `getPaginationRowModel`\n // (it returns the pre-pagination rows — i.e. the page the app already fetched),\n // so attaching it there is dead per-render work. `(A && !B) || B === A || B`,\n // but the honest single-branch form documents that manual mode needs no model.\n const paginationRowModel =\n enablePagination && !manualPagination ? { getPaginationRowModel: getPaginationRowModel() } : {};\n\n // ── Table instance ────────────────────────────────────────────────────────\n const table = useReactTable({\n data,\n columns,\n state: {\n sorting,\n columnVisibility,\n columnFilters,\n globalFilter,\n pagination,\n columnPinning,\n columnSizing,\n rowSelection,\n },\n\n // Sorting\n onSortingChange: (updater) => {\n const next = resolveSorting(updater);\n if (!isSortingControlled) setInternalSorting(next);\n onSortingChangeProp?.(updater);\n if (manualSorting) {\n sortingRef.current = next;\n fireServerChange({ sorting: next });\n }\n },\n\n // Column visibility\n onColumnVisibilityChange: (updater) => {\n const next = resolveColumnVisibility(updater);\n if (!isColumnVisibilityControlled) setInternalColumnVisibility(next);\n onColumnVisibilityChangeProp?.(updater);\n // column visibility is never a \"manual\" server concern\n },\n\n // Column filters\n onColumnFiltersChange: (updater) => {\n const next = resolveColumnFilters(updater);\n if (!isColumnFiltersControlled) setInternalColumnFilters(next);\n onColumnFiltersChangeProp?.(updater);\n if (manualFiltering) {\n columnFiltersRef.current = next;\n fireServerChange({ columnFilters: next });\n }\n },\n\n // Global filter\n onGlobalFilterChange: (updater) => {\n const next = resolveGlobalFilter(updater);\n if (!isFilterControlled) setInternalGlobalFilter(next);\n onGlobalFilterChange?.(next);\n if (manualFiltering) {\n globalFilterRef.current = next;\n fireServerChange({ globalFilter: next });\n }\n },\n\n // Pagination\n onPaginationChange: (updater) => {\n const next = resolvePagination(updater);\n if (!isPaginationControlled) setInternalPagination(next);\n onPaginationChangeProp?.(updater);\n if (manualPagination) {\n paginationRef.current = next;\n fireServerChange({ pagination: next });\n }\n },\n\n // Column pinning — a LAYOUT slice, so unlike sorting/filtering/pagination it\n // never fires `onServerChange`: freezing a column changes nothing the server\n // would need to re-query.\n onColumnPinningChange: (updater) => {\n const next = resolveColumnPinning(updater);\n if (!isColumnPinningControlled) setInternalColumnPinning(next);\n onColumnPinningChangeProp?.(updater);\n },\n\n // Column resizing (#12) — a LAYOUT slice, like column pinning: a column's\n // width changes nothing the server would need to re-query, so this never\n // fires onServerChange either. Routed through by BOTH the pointer path\n // (TanStack's own `header.getResizeHandler()`, wired below) and the\n // keyboard path (`handleResizeKeyDown`, via `table.setColumnSizing`) so\n // the two input modes can never diverge in controlled/uncontrolled\n // behaviour.\n columnResizeMode,\n // RTL fix (#12 review, P1): TanStack's pointer-drag math hardcodes LTR\n // unless told otherwise — `deltaDirection = columnResizeDirection ===\n // 'rtl' ? -1 : 1` internally — so under `dir=\"rtl\"` (the resize handle's\n // own edge already flips via `end-0`, see the `useLocale()` call above)\n // dragging would otherwise move the column's width opposite the visible\n // boundary. `handleResizeKeyDown` below mirrors this for the keyboard path.\n columnResizeDirection: dir,\n enableColumnResizing,\n onColumnSizingChange: (updater) => {\n const next = resolveColumnSizing(updater);\n if (!isColumnSizingControlled) setInternalColumnSizing(next);\n onColumnSizingChangeProp?.(updater);\n },\n\n // Row selection (#11) — also a LAYOUT/UI slice, so it never fires\n // onServerChange: which rows are checked changes nothing the server\n // would need to re-query.\n onRowSelectionChange: (updater) => {\n const next = resolveRowSelection(updater);\n if (!isRowSelectionControlled) setInternalRowSelection(next);\n onRowSelectionChangeProp?.(updater);\n },\n enableRowSelection,\n enableMultiRowSelection,\n getRowId,\n\n getCoreRowModel: getCoreRowModel(),\n ...sortedRowModel,\n ...filteredRowModel,\n ...paginationRowModel,\n\n // Server-side options\n manualSorting,\n manualFiltering,\n manualPagination,\n ...(rowCount !== undefined ? { rowCount } : {}),\n ...(pageCount !== undefined ? { pageCount } : {}),\n // No `initialState`: every slice is driven explicitly via `state` above\n // (internal slices are seeded from `initialView` at useState init), so a\n // TanStack `initialState` would be dead/misleading.\n });\n\n const rows = table.getRowModel().rows;\n // colSpan for spacer / empty / skeleton cells must match the number of cells a\n // real data row renders (`row.getVisibleCells()`) — use VISIBLE leaf columns so a\n // hidden column (a first-class slice here via columnVisibility + ColumnPicker)\n // doesn't make those rows over-span.\n const colCount = table.getVisibleLeafColumns().length;\n // Virtualized-table ARIA: only a window of rows is mounted, so assistive tech\n // can't infer the true size from the DOM. aria-rowcount counts the header row(s)\n // plus every data row; rendered data rows carry an absolute 1-based aria-rowindex\n // (header rows occupy 1..headerRowCount). Falls back to rows.length for the\n // client path; uses the server `rowCount` total when provided.\n const headerRowCount = table.getHeaderGroups().length;\n const ariaRowCount = (rowCount ?? rows.length) + headerRowCount;\n\n // ── Row drag-reorder (#13) ────────────────────────────────────────────────\n // `rowActionName` (defined below, but hoisted as a function declaration) is\n // the SAME row-naming lookup `onRowClick`'s hidden button uses (#337) —\n // reusing it means a reorder announcement names a row exactly the way its\n // click target already does, rather than inventing a second convention.\n function reorderRowName(id: string): string {\n const row = rows.find((r) => getReorderRowId(r) === id);\n return row ? rowActionName(row) : id;\n }\n function reorderPosition(id: string): number {\n return rows.findIndex((r) => getReorderRowId(r) === id) + 1;\n }\n\n // ── Stable identity for drag reconciliation (round-1 fix, findings 1 & 3) ──\n // `getRowId`'s own doc comment above states TanStack's fallback: default row\n // ids are assigned ONCE per row object when the core row model is built from\n // the current `data` ARRAY REFERENCE, then carried by reference through\n // sort/filter — but a `data` array REPLACEMENT (exactly what every\n // `onRowReorder` consumer does: `arrayMove`/`slice`+`splice`/immer all\n // return a new array) rebuilds the core row model and reassigns ids by\n // POSITION IN THE NEW ARRAY. So the id that used to denote \"the row now at\n // index 1\" keeps denoting index 1 even though a different record moved\n // there — which is what let a keyboard drop leave focus on the wrong row\n // (a different record now sits at the id the focus restore targets).\n // Requiring every consumer to hand-roll `getRowId` would leave the DEFAULT\n // configuration broken, so when the caller hasn't supplied one, mint an id\n // keyed by the row's own OBJECT REFERENCE (`row.original`) in a `WeakMap` —\n // unlike TanStack's default, this id follows the object wherever it lands\n // in a new array, because every reorder idiom MOVES the element reference,\n // it never clones it. When `getRowId` IS supplied it is already exactly\n // this kind of identity, so it's reused as-is instead of minting a second,\n // divergent id namespace.\n function getReorderRowId(row: Row<TData>): string {\n if (getRowId) return row.id;\n const original: unknown = row.original;\n if (original !== null && typeof original === \"object\") {\n const map = reorderIdentityMapRef.current;\n let id = map.get(original);\n if (id === undefined) {\n id = `__reorder-${reorderIdentityCounterRef.current++}`;\n map.set(original, id);\n }\n // A repeated record shares ONE object reference, so the id minted above\n // is by construction identical for both of its rows — round-2 finding\n // 6. Fold the data position into the repeats so each occupant is its\n // own draggable. Two identical records are interchangeable to the user,\n // so the weaker cross-replacement stability of a suffixed id costs\n // nothing the first-occurrence rule doesn't already give back.\n return reorderRepeatedPositions.has(row.index) ? `${id}__${row.index}` : id;\n }\n // Primitive `TData` (rare) has no object reference to key off — same\n // documented limitation `getRowId`'s own comment already carries for\n // TanStack's own default identity.\n return row.id;\n }\n\n // dnd-kit's own `Accessibility` component's `LiveRegion` hardcodes\n // `aria-live=\"assertive\"` with no way to override it from `DndContext`\n // (`@dnd-kit/accessibility` 3.1.1 accepts an `ariaLiveType` prop on\n // `LiveRegion` itself, but nothing forwards one through `accessibility`) —\n // round-1 finding 4. `.claude/rules/accessibility.md` reserves assertive\n // for terminal errors (`role=\"alert\"`); a sortable list's own position\n // updates are `polite` status. So dnd-kit's built-in announcer is silenced\n // below (every callback returns `undefined`, which `useAnnouncement`\n // treats as \"no update\" — the region stays permanently empty and never\n // fires) and DataTable renders its OWN `aria-live=\"polite\"` region\n // (`reorderLiveMessage`, wired to the `data-table-reorder-live-region`\n // node near the bottom of this function) from the `onDragStart`/\n // `onDragOver`/`onDragEnd`/`onDragCancel` handlers below.\n const silentDragAnnouncements: Announcements = {\n onDragStart: () => undefined,\n onDragOver: () => undefined,\n onDragEnd: () => undefined,\n onDragCancel: () => undefined,\n };\n\n /**\n * Pickup always announces — it's the start of a new, meaningful gesture.\n * Seeding `reorderLastAnnouncedPositionRef` with the row's OWN starting\n * position (not `null`) is what suppresses dnd-kit's immediate self-\n * collision `onDragOver` (over === active, at the same position) that\n * otherwise fires in the same tick and would stomp this message before it\n * is ever observable (WCAG 4.1.3 needs it heard, not just rendered).\n */\n function handleRowDragStart(event: DragStartEvent) {\n const activeRowId = String(event.active.id);\n reorderLastAnnouncedPositionRef.current = reorderPosition(activeRowId);\n setReorderLiveMessage(t(\"data.table.reorderPickedUp\", { name: reorderRowName(activeRowId) }));\n }\n\n /**\n * Announces a real position change only — round-1 finding 4 measured 4\n * announcements for a 2-step move, one of them a same-position self-\n * collision that buried the \"picked up\" message. De-duping on the actual\n * computed position (not on the raw event) means a screen reader hears one\n * `polite` (queued, non-interrupting) announcement per genuine move, not\n * one per keystroke.\n */\n function handleRowDragOver(event: DragOverEvent) {\n const { active, over } = event;\n if (!over) return;\n const position = reorderPosition(String(over.id));\n if (position === reorderLastAnnouncedPositionRef.current) return;\n reorderLastAnnouncedPositionRef.current = position;\n setReorderLiveMessage(\n t(\"data.table.reorderMoved\", {\n name: reorderRowName(String(active.id)),\n position,\n total: rows.length,\n }),\n );\n }\n\n function handleRowDragCancel(event: DragCancelEvent) {\n const activeRowId = String(event.active.id);\n setReorderLiveMessage(\n t(\"data.table.reorderCancelled\", {\n name: reorderRowName(activeRowId),\n position: reorderPosition(activeRowId),\n total: rows.length,\n }),\n );\n reorderLastAnnouncedPositionRef.current = null;\n }\n\n /**\n * The component never mutates `data` itself (D5 — presentation layer, not\n * an SDK): it only reports the move, the same \"controlled slice\" contract\n * every other DataTable feature follows. A no-op drop (dropped on itself,\n * or outside any droppable) fires nothing on the data callback, but still\n * announces (matching the \"dropped back where it started\" reality).\n *\n * `from`/`to` resolve against the ORIGINAL `data` array the caller passed\n * in, never against the sorted/paginated VIEW (`rows`) — round-1 finding 1.\n * Reporting `rows.findIndex(...)` positions meant a caller doing\n * `arrayMove(data, from, to)` (the idiom both shipped stories use) silently\n * moved the WRONG records whenever an active sort or a client-side page\n * had changed which record sat at which view position — measured: a\n * paginated drag on page 2 reported `(0, 1, …)`, corrupting `data[0]`/\n * `data[1]` on page 1. Resolving against `data` itself makes the contract\n * \"indices into the `data` you gave me\" — correct under any sort/filter,\n * correct under client-side pagination (the dragged record's true index in\n * the full array), and correct under `manualPagination` too (there `data`\n * IS the current page, so `from`/`to` are page-relative, which is exactly\n * what a caller reordering that page's own array needs).\n */\n function handleRowDragEnd(event: DragEndEvent) {\n const { active, over } = event;\n const activeRowId = String(active.id);\n setReorderLiveMessage(\n t(\"data.table.reorderDropped\", {\n name: reorderRowName(activeRowId),\n position: reorderPosition(String(over ? over.id : active.id)),\n total: rows.length,\n }),\n );\n reorderLastAnnouncedPositionRef.current = null;\n\n if (!over || active.id === over.id) return;\n const movedRow = rows.find((r) => getReorderRowId(r) === activeRowId);\n const targetRow = rows.find((r) => getReorderRowId(r) === String(over.id));\n if (!movedRow || !targetRow) return;\n // Round-2 finding 6: this used to build a `Map` keyed by `row.original`\n // and read `from`/`to` out of it. A `data` array that repeats a record —\n // the same object reference, or the same primitive, at two positions —\n // can only occupy ONE slot in such a map, so the later occurrence was\n // reported as the earlier one and the documented `arrayMove(data, from,\n // to)` idiom moved a row the user never dragged, silently. `Row.index` is\n // the position TanStack already assigned this row when it built the core\n // row model FROM `data`, carried by reference through sort/filter/\n // pagination (the same property the round-1 fix above relies on) — so it\n // keeps the \"indices into the `data` you gave me\" contract without the\n // value-equality lookup that collapsed the repeats.\n const from = movedRow.index;\n const to = targetRow.index;\n if (from < 0 || from >= data.length || to < 0 || to >= data.length) return;\n onRowReorder?.(from, to, movedRow.original);\n }\n\n // ── Pinning (#333) ────────────────────────────────────────────────────────\n // Are there any pinned columns at all? Everything pinning-related is gated on\n // this so a table with no pinning renders byte-identical markup to before.\n const hasLeftPinned = (columnPinning.left?.length ?? 0) > 0;\n const hasRightPinned = (columnPinning.right?.length ?? 0) > 0;\n\n // Keep keyboard focus out from UNDER the frozen block (WCAG 2.2 SC 2.4.11,\n // \"Focus Not Obscured\"). Tabbing to a control in a centre column that is\n // currently scrolled under the frozen columns makes the browser scroll it to\n // the SCROLLPORT edge — and the browser has no idea a sticky column is parked\n // there, so the focused control lands behind it, invisibly. Measured on\n // `PinnedColumns`: at scrollLeft 295 the \"Latency (ms)\" / p50 / p95 sort\n // buttons focused at viewport x 15 / 100 / 183, all inside the 17…297 frozen\n // block. `scroll-padding` is the platform's answer — it is exactly the \"don't\n // scroll content to here\" inset that `scrollIntoView` honours. Emitted only\n // when something IS pinned, so an unpinned table keeps its previous DOM.\n const pinnedScrollPadding: React.CSSProperties = {\n ...(hasLeftPinned ? { scrollPaddingInlineStart: table.getLeftTotalSize() } : {}),\n ...(hasRightPinned ? { scrollPaddingInlineEnd: table.getRightTotalSize() } : {}),\n };\n\n // Dev-only guard: a pinned column's sticky offset is `getStart(\"left\")` /\n // `getAfter(\"right\")`, i.e. the SUM OF DECLARED SIZES of the columns beside\n // it. The table is auto-layout, so a pinned column with no `size` renders at\n // whatever width its content wants while its neighbours are offset by\n // TanStack's 150px default — the pinned block then overlaps or gaps. Warn\n // once per mount so that mismatch is diagnosable instead of silent (same\n // idiom as the #227 warning above).\n //\n // Read off the RAW `columns` prop, not `column.columnDef`: TanStack merges a\n // default `size: 150` into every resolved column def, so the merged def can\n // never tell us whether the author actually declared one.\n const warnedUnsizedPinnedRef = useRef(false);\n const pinnedIds = [...(columnPinning.left ?? []), ...(columnPinning.right ?? [])];\n const unsizedIds =\n process.env.NODE_ENV === \"production\" || pinnedIds.length === 0\n ? null\n : unsizedColumnIds(columns);\n const pinnedWithoutSizeKey = unsizedIds\n ? pinnedIds.filter((id) => unsizedIds.has(id)).join(\",\")\n : \"\";\n useEffect(() => {\n if (\n process.env.NODE_ENV !== \"production\" &&\n pinnedWithoutSizeKey !== \"\" &&\n !warnedUnsizedPinnedRef.current\n ) {\n warnedUnsizedPinnedRef.current = true;\n console.warn(\n \"[DataTable] Pinned column(s) without an explicit `size` in their `ColumnDef`: \" +\n `${pinnedWithoutSizeKey}. Sticky offsets are computed from the declared sizes, so an ` +\n \"auto-width pinned column will render at a width that doesn't match its own offset. \" +\n \"Give every pinned column a `size`.\",\n );\n }\n }, [pinnedWithoutSizeKey]);\n\n /**\n * Sticky positioning for one pinned header/body cell (#333).\n *\n * Returns `null` for an unpinned column so the caller emits no `style`, no\n * `data-pinned` and no extra classes — that is what keeps a table with no\n * pinning identical to how it rendered before this feature existed.\n *\n * The offset comes from TanStack (`getStart(\"left\")` sums the widths of the\n * left-pinned columns before this one; `getAfter(\"right\")` sums the\n * right-pinned columns after it), and the same declared `size` is forced onto\n * the cell as `width`/`min`/`max` so the rendered width and the offset agree\n * under the table's auto layout.\n */\n function pinnedCellGeometry(column: Column<TData, unknown>) {\n const pinned = column.getIsPinned();\n if (pinned === false) return null;\n const size = column.getSize();\n const style: React.CSSProperties = {\n width: size,\n minWidth: size,\n maxWidth: size,\n ...(pinned === \"left\"\n ? { left: column.getStart(\"left\") }\n : { right: column.getAfter(\"right\") }),\n };\n return {\n pinned,\n style,\n // The seam between the frozen block and the scrolling block is the SOLE\n // structural cue between two regions that share one row fill and one\n // zebra stripe — delete it and a sighted user cannot tell them apart — so\n // it takes the strong rung (ADR 0010 decision test). No shadow: ADR 0020's\n // `--shadow-strength: 0` (`data-decoration=\"8|9|10\"`) would\n // erase a shadow-only cue entirely.\n //\n // It is drawn as a 1px `::after` INSIDE the cell, NOT as `border-e` /\n // `border-s`. A real border cannot work here: Tailwind's Preflight puts\n // the table in the COLLAPSED border model, and a collapsed border is\n // painted by the <table> at the cell's STATIC position — it does not\n // travel with a `position: sticky` cell, and the cell's own opaque fill\n // (which it needs, see `pinnedCellFillClass`) then paints over it. Measured\n // in Chromium on `Data/DataTable → PinnedColumns`: with `border-e` the\n // seam pixel read `143,143,143` (light `--border-strong`) at\n // scrollLeft 0 and `245,245,245` (the plain cell fill — i.e. GONE) once\n // scrolled, in every theme and on both edges. So the one cue vanished\n // exactly when the freeze was doing something. The `::after` lives in the\n // sticky cell's own stacking context, so it moves with it.\n edgeClass:\n pinned === \"left\"\n ? column.getIsLastColumn(\"left\")\n ? PINNED_SEAM_CLASS + \" after:end-0\"\n : \"\"\n : column.getIsFirstColumn(\"right\")\n ? PINNED_SEAM_CLASS + \" after:start-0\"\n : \"\",\n };\n }\n\n // ── Column resizing keyboard path (#12) ───────────────────────────────────\n // TanStack's own `header.getResizeHandler()` is pointer/touch-only — no\n // keyboard path exists in the library — so the WAI-ARIA separator-as-slider\n // practice (drag handle operable via ArrowLeft/ArrowRight when focused)\n // needs one small hand-rolled step. It goes through `table.setColumnSizing`\n // (`table.setColumnSizing = updater => table.options.onColumnSizingChange\n // ?.(updater)`, TanStack's own `ColumnSizing` feature), which is the SAME\n // `onColumnSizingChange` handler passed to `useReactTable` above — so\n // keyboard and pointer resizing share one controlled/uncontrolled code path\n // and can never diverge in behaviour.\n const RESIZE_STEP = 10;\n // ARIA fallback ceiling for the resize separator's `aria-valuemax` when the\n // column declares no explicit `maxSize` — a `ColumnDef` with no `maxSize`\n // resolves through TanStack's own default to `Number.MAX_SAFE_INTEGER`,\n // which is not a value any AT should announce, so the header below omits\n // `aria-valuemax` entirely in that case. Per the WAI-ARIA separator-as-\n // widget pattern, an ELEMENT WITH NO `aria-valuemax` is read with an\n // IMPLICIT default of 100 — so a column at its ordinary starting width\n // (150) already announces as \"150 of 100\", out of its own stated range\n // (#12 review, P2). `Math.max` with the live size at the call site below\n // keeps this always containing the current value: a column dragged past\n // this floor simply raises its own announced ceiling instead of going out\n // of range again.\n const RESIZE_UNBOUNDED_ARIA_MAX = 2000;\n function handleResizeKeyDown(event: React.KeyboardEvent, column: Column<TData, unknown>) {\n let delta = 0;\n if (event.key === \"ArrowRight\") delta = RESIZE_STEP;\n else if (event.key === \"ArrowLeft\") delta = -RESIZE_STEP;\n else return;\n event.preventDefault();\n // Mirror TanStack's own `columnResizeDirection` reversal (passed to\n // `useReactTable` above) for the keyboard path: the handle sits at the\n // column's logical `end` edge, which `end-0` renders on the physical\n // LEFT under `dir=\"rtl\"` — so ArrowRight (physical right, toward the\n // column's own body) must SHRINK the column and ArrowLeft must GROW it,\n // the mirror image of LTR. Without this the keyboard path would diverge\n // from the now-direction-aware pointer path.\n if (dir === \"rtl\") delta = -delta;\n const minSize = column.columnDef.minSize ?? 20;\n const maxSize = column.columnDef.maxSize ?? Number.MAX_SAFE_INTEGER;\n const nextSize = Math.min(maxSize, Math.max(minSize, column.getSize() + delta));\n table.setColumnSizing((old) => ({ ...old, [column.id]: nextSize }));\n }\n\n // #51 — double-click resets a resize handle's column back to its declared\n // `ColumnDef.size`, falling back to TanStack's own default (150, the same\n // fallback idiom as `minSize ?? 20`/`maxSize ?? MAX_SAFE_INTEGER` above) when\n // the author left it unset — by REMOVING any explicit `columnSizing` entry\n // for the column, not by writing the size back in as a literal (PR #81\n // review, \"Remove the sizing override when resetting a column\"). `columnSizing`\n // only ever carries EXPLICIT per-column overrides; a column absent from it\n // always tracks its live `ColumnDef.size` (or the 150 default). Writing the\n // CURRENT declared size back in as a value looks identical today but turns\n // the default into a permanent override: if the `columns` prop later\n // changes this column's authored `size` (e.g. switching table\n // configurations), a column that was never resized follows the new\n // definition for free, while a double-click-reset column would stay pinned\n // to the OLD number forever. Deleting the entry keeps it dynamic, exactly\n // like a column that was never touched. Still goes through the SAME\n // `table.setColumnSizing` dispatch path as `handleResizeKeyDown` — never\n // `column.resetSize()` — so a controlled `columnSizing` consumer observes\n // the reset via `onColumnSizingChange` exactly like every other resize.\n function handleResizeDoubleClick(column: Column<TData, unknown>) {\n table.setColumnSizing((old) => {\n if (!(column.id in old)) return old;\n const { [column.id]: _removed, ...rest } = old;\n return rest;\n });\n }\n\n // ── Scroll container ref for virtualizer ─────────────────────────────────\n const scrollRef = useRef<HTMLDivElement>(null);\n\n // ── Virtualizer (only active in virtualized branch) ───────────────────────\n const virtualizer = useVirtualizer({\n count: enableRowVirtualization ? rows.length : 0,\n getScrollElement: () => (enableRowVirtualization ? scrollRef.current : null),\n estimateSize: () => estimateRowHeight,\n overscan,\n enabled: enableRowVirtualization,\n });\n\n const virtualItems = enableRowVirtualization ? virtualizer.getVirtualItems() : [];\n const totalSize = enableRowVirtualization ? virtualizer.getTotalSize() : 0;\n const paddingTop = virtualItems.length > 0 ? (virtualItems[0]?.start ?? 0) : 0;\n const paddingBottom =\n totalSize > 0 ? totalSize - (virtualItems[virtualItems.length - 1]?.end ?? 0) : 0;\n\n // ── Plain-branch scroll container: overflow measurement ────────────────────\n // #330: the non-virtualized branch's scroll box is `overflow-auto` (it used to\n // clip). Everything that box exposes is gated on MEASURED overflow, because a\n // table that fits must stay exactly as it was:\n // - the keyboard tab stop + its accessible name (WCAG 2.1.1 / axe\n // `scrollable-region-focusable`) — a table that doesn't scroll must NOT\n // gain a focus stop that does nothing and announces \"scrollable\" falsely;\n // - the edge fades, which only make sense when content continues off-edge.\n // So a desktop-width table is a total no-op: no tab stop, no label, no fade.\n const plainScrollRef = useRef<HTMLDivElement>(null);\n const [scrollOverflows, setScrollOverflows] = useState(false);\n const [canScrollLeft, setCanScrollLeft] = useState(false);\n const [canScrollRight, setCanScrollRight] = useState(false);\n\n const updateScrollAffordance = useCallback(() => {\n const el = plainScrollRef.current;\n if (!el) return;\n // 1px tolerance absorbs sub-pixel layout rounding, which would otherwise\n // report a permanent 0.5px overflow on a table that visually fits.\n setScrollOverflows(\n el.scrollWidth > el.clientWidth + 1 || el.scrollHeight > el.clientHeight + 1,\n );\n setCanScrollLeft(el.scrollLeft > 0);\n setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);\n }, []);\n\n useEffect(() => {\n const el = plainScrollRef.current;\n if (!el) return;\n updateScrollAffordance();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(updateScrollAffordance);\n // Observe the CONTAINER (viewport changes) and the <table> inside it\n // (content changes its intrinsic width without resizing the container).\n observer.observe(el);\n if (el.firstElementChild) observer.observe(el.firstElementChild);\n return () => observer.disconnect();\n // Column/row-count changes can also change the table's intrinsic width.\n }, [updateScrollAffordance, colCount, rows.length]);\n\n // ─── Empty / loading state ───────────────────────────────────────────────\n const showEmpty = !loading && rows.length === 0;\n const showSkeletons = loading && rows.length === 0;\n\n // Number of skeleton rows to show — caller can override via `loadingRows`.\n const skeletonRowCount = loadingRows ?? pageSize;\n\n // ─── Render helpers ───────────────────────────────────────────────────────\n\n /**\n * thead — sticky in virtualized mode, normal otherwise.\n * `withRowIndex` (virtualized only) sets the header row's `aria-rowindex` so the\n * windowed `aria-rowcount` on the table stays internally consistent with the\n * absolute indices on the data rows.\n */\n function renderThead(sticky: boolean, withRowIndex = false) {\n return (\n <thead\n className={cn(\n // #173: header bottom is the only cue between header and first data row → border-strong\n \"border-b border-border-strong\",\n // A sticky header scrolls OVER the body, so its fill must be opaque or data\n // rows bleed through the labels; the non-sticky header keeps the /60 wash.\n // z-20 (raised from z-10 for #333) puts the header row above the pinned\n // body cells (z-10) and below the pinned header corner (z-30). No visual\n // delta: nothing else in the table sits between those rungs.\n sticky ? \"sticky top-0 z-20 bg-surface-muted\" : \"bg-surface-muted/60\",\n )}\n >\n {table.getHeaderGroups().map((headerGroup, groupIndex) => (\n <tr key={headerGroup.id} aria-rowindex={withRowIndex ? groupIndex + 1 : undefined}>\n {hasGripColumn && (\n <th key=\"__reorder\" scope=\"col\" className=\"h-10 w-10 px-3 align-middle\">\n <span className=\"sr-only\">{t(\"data.table.reorderColumnHeader\")}</span>\n </th>\n )}\n {headerGroup.headers.map((header) => {\n const geometry = pinnedCellGeometry(header.column);\n const canSort = header.column.getCanSort();\n const sorted = header.column.getIsSorted();\n // String-header fallback (`column.id`) so an icon-only / non-text\n // header still yields a named button (#230).\n const headerLabel =\n typeof header.column.columnDef.header === \"string\"\n ? header.column.columnDef.header\n : header.column.id;\n const sortStateLabel =\n sorted === \"asc\" ? \"ascending\" : sorted === \"desc\" ? \"descending\" : \"not sorted\";\n const SortIcon =\n sorted === \"asc\" ? ArrowUp : sorted === \"desc\" ? ArrowDown : ArrowUpDown;\n // #12: every column gets the same explicit width triad a pinned\n // column already has, gated behind `enableColumnResizing` so a\n // table that doesn't opt in stays byte-identical to before.\n const resizeStyle = enableColumnResizing\n ? resizeWidthStyle(header.getSize())\n : undefined;\n const canResize =\n enableColumnResizing && !header.isPlaceholder && header.column.getCanResize();\n const resizeMax = header.column.columnDef.maxSize;\n return (\n <th\n key={header.id}\n scope=\"col\"\n aria-sort={\n canSort\n ? sorted === \"asc\"\n ? \"ascending\"\n : sorted === \"desc\"\n ? \"descending\"\n : \"none\"\n : undefined\n }\n data-pinned={geometry?.pinned ?? undefined}\n style={geometry?.style ?? resizeStyle}\n className={cn(\n // Same `px-3` the body `<td>` uses (below) — deliberately\n // NOT split into `ps-3`/`pe-3` for a resize-handle\n // override (round-1 briefly did this, see the round-2\n // note on `numericColumnClasses`): the header's padding\n // must stay byte-identical to the body's so an\n // end-aligned numeric column's header lines up with its\n // own values.\n \"h-10 px-3 text-start align-middle font-table-header text-muted-foreground\",\n // #69: a numeric column's `meta` overrides the default\n // `text-start` — placed right after the base string so\n // tailwind-merge lets it win over that default.\n numericColumnClasses(header.column.columnDef.meta),\n // `sticky`/pinned already establishes a positioning context\n // for the resize handle's `absolute`; an unpinned resizable\n // header needs its own.\n !geometry && canResize && \"relative\",\n // A pinned HEADER cell is the corner where both freezes meet,\n // so it stacks above the sticky header row (z-20) which is\n // above the pinned body cells (z-10). It needs an OPAQUE\n // fill (scrolled header cells pass underneath it), and that\n // fill has to composite to exactly what its unpinned\n // neighbours show — same problem, same two-layer answer as\n // `pinnedCellFillClass`:\n // sticky branch → the row is already opaque `surface-muted`, so match it.\n // plain branch → the row is `surface-muted/60` over the\n // container's `card`, so paint `card` and\n // re-apply the /60 wash on `::before`.\n // Painting the plain branch's corner solid `surface-muted`\n // read 4-5/255 darker than the header beside it in every\n // theme (measured: 242 vs 247 light, 43 vs 40\n // dark) — the same \"floating pill\"\n // artefact #333 was filed about, moved into the header.\n geometry && \"sticky z-30\",\n geometry &&\n (sticky\n ? \"bg-surface-muted\"\n : \"bg-card before:pointer-events-none before:absolute before:inset-0 before:-z-10 before:bg-surface-muted/60 before:content-['']\"),\n // Separate cn() argument on purpose: the seam is the sole\n // structural cue between the frozen and scrolling blocks, so\n // it must not read as a \"boundary + fill in one class string\"\n // redundancy (separation:check).\n geometry?.edgeClass,\n columnDividers && !geometry && COLUMN_DIVIDER_CLASS,\n )}\n >\n {header.isPlaceholder ? null : canSort ? (\n <button\n type=\"button\"\n onClick={header.column.getToggleSortingHandler()}\n aria-label={`Sort by ${headerLabel}, ${sortStateLabel}`}\n // `relative z-10` (round-2 fix, #82 follow-up — replaces\n // round-1's padding-based clearance, see the note on\n // `numericColumnClasses`): on a resizable column the\n // resize handle below is `absolute`, and CSS painting\n // order always puts a positioned descendant above\n // non-positioned in-flow content in the SAME stacking\n // context, regardless of DOM order — so without this,\n // the handle's 24px hit box would win every hit-test\n // where it overlaps this button's own trailing edge\n // (measured: a 12px overlap on an end-aligned\n // sortable+resizable column) no matter which element\n // renders first in markup. Giving the button its own\n // explicit positive z-index (not just `relative`, which\n // alone would still lose — see the code comment on\n // `numericColumnClasses` above) promotes it into a\n // later, higher-stacked paint step than the handle's\n // implicit `z-index: auto`, so the button wins the\n // overlap purely at the hit-test/paint layer — the\n // header's padding, and therefore its alignment with\n // the body `<td>`, never has to move. The handle's own\n // visible drag affordance (the `after:` seam, 0-8px\n // from the cell's trailing edge) sits entirely outside\n // this button's box (which ends at the same 12px inset\n // as the body), so dragging is unaffected.\n className=\"relative z-10 inline-flex items-center gap-1 rounded-sm transition-colors duration-fast ease-standard hover:text-foreground focus-ring\"\n >\n {flexRender(header.column.columnDef.header, header.getContext())}\n <SortIcon\n aria-hidden=\"true\"\n className=\"size-3 shrink-0 transition-colors duration-fast ease-standard\"\n />\n </button>\n ) : (\n flexRender(header.column.columnDef.header, header.getContext())\n )}\n {canResize && (\n <div\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-valuenow={Math.round(header.getSize())}\n aria-valuemin={header.column.columnDef.minSize}\n aria-valuemax={\n resizeMax !== undefined && resizeMax < Number.MAX_SAFE_INTEGER\n ? resizeMax\n : Math.max(header.getSize(), RESIZE_UNBOUNDED_ARIA_MAX)\n }\n // #51: a bare number reads to AT as a dimensionless\n // ordinal (\"150\") rather than a size — aria-valuetext\n // supplies the unit while aria-valuenow (above) stays\n // the plain numeric value TanStack/AT expect. PR #81\n // review, \"Format the announced resize value for the\n // active locale\": `count` (the raw number) drives\n // PluralMessage category selection so a locale whose\n // plural rules pick something other than \"other\" is\n // reachable, and `size` goes through `formatNumber` so\n // an overriding locale renders its own digits/grouping\n // instead of a raw Latin-digit JS number.\n aria-valuetext={t(\"data.table.resizeColumnValue\", {\n count: Math.round(header.getSize()),\n size: formatNumber(Math.round(header.getSize())),\n })}\n aria-label={t(\"data.table.resizeColumn\", { name: headerLabel })}\n tabIndex={0}\n data-slot=\"data-table-resize-handle\"\n onMouseDown={header.getResizeHandler()}\n onTouchStart={header.getResizeHandler()}\n onKeyDown={(event) => handleResizeKeyDown(event, header.column)}\n // #51: double-click resets the column to its declared\n // (or default) size — see `handleResizeDoubleClick`.\n // Pointer-only; it doesn't touch the keyboard path above.\n onDoubleClick={() => handleResizeDoubleClick(header.column)}\n className={cn(\n // #51: the hit box is a literal 24px (clamped to half\n // the header cell so it can never overlap a neighbour,\n // even at `minSize=20`) rather than the `w-2` Tailwind\n // spacing-scale utility. `w-2` compiles to\n // `calc(var(--spacing) * 2)`, and `--spacing` is what\n // `data-density=\"compact\"` rescales — so the old 8px\n // hit box shrank further under compact density\n // (~7.1px). A literal px value is density-independent\n // by construction, which is the actual defect the\n // maintainer's review corrected (NOT `--type-factor`,\n // which this handle never used). Do not widen via\n // overhang into the neighbouring cell instead — on the\n // last column that lands inside the `overflow-auto`\n // box (#330 false positive) and a pinned neighbour\n // paints over/hit-tests away the extra area.\n \"absolute inset-y-0 end-0 w-[min(24px,50%)] cursor-col-resize touch-none select-none\",\n // #51: the focus ring moves to the `after:` pseudo-\n // element (the drawn seam) rather than the box itself\n // — the box is now a 24px hit target, and a 24px focus\n // rectangle would replace the deliberately slim ring\n // already reviewed/approved as the #12 a11y fix\n // (da9b29e). `focus-visible:after:*` targets the\n // pseudo-element the same way `hover:after:w-2` /\n // `focus-visible:after:w-2` below already do.\n \"focus-visible:outline-none\",\n // a11y fix (#12 review, blocking): this handle is the\n // SOLE boundary between two adjacent header cells once\n // resizing is on — no fill/elevation change separates\n // them otherwise — so per the border/border-strong\n // decision test (styling-and-tokens.md) it needs a\n // rung that clears WCAG 1.4.11's 3:1 on its OWN, in\n // EVERY state, including rest (a control with no\n // affordance until hover is unusable without a\n // pointer). `border-strong` measures only 2.86-2.96:1\n // against this `bg-surface-muted` header — that rung\n // is guaranteed only vs `--card`/`--background`, not a\n // same-tone surface, which is the exact trap the rule\n // warns about. `muted-foreground` is guaranteed AA\n // text contrast against `--surface-muted`\n // (TEXT_SURFACES), so it clears the 3:1 non-text\n // minimum with wide margin (measured ~5.3-6.4:1 in\n // both themes, unaffected by density) and is already\n // the header's own label color. A slim persistent\n // `after:` seam (not just a hover reveal) gives the\n // real resting boundary; hover/focus widen the drawn\n // seam to 8px (`after:w-2`) using the same compliant\n // color — a separate width from the 24px pointer hit\n // box below (#51), which the seam does not fill.\n // Dragging keeps the pre-existing full-fill\n // `bg-primary` treatment — that is a drag AFFORDANCE,\n // not a focus indicator, and it is redundant with the\n // pointer capture, so it is out of scope here. The\n // keyboard focus indicator on both branches is the\n // shared compound one (#67), applied to the drawn seam\n // via `focus-visible:after:focus-ring-static`: the\n // element itself is a 24px transparent hit box, so\n // ringing IT would ring nothing a user can see.\n header.column.getIsResizing()\n ? \"after:absolute after:inset-y-0 after:end-0 after:w-2 after:bg-primary after:content-[''] focus-visible:after:focus-ring-static\"\n : \"after:absolute after:inset-y-0 after:end-0 after:w-px after:bg-muted-foreground after:content-[''] hover:after:w-2 focus-visible:after:w-2 focus-visible:after:focus-ring-static\",\n )}\n />\n )}\n </th>\n );\n })}\n </tr>\n ))}\n </thead>\n );\n }\n\n /**\n * Row separation cue, keyed off the absolute row index so it stays stable\n * under virtualization (a CSS `even:`/`odd:` variant would \"swim\" as the\n * windowed `<tr>`s recycle).\n *\n * - zebra (default): a gentle `--table-stripe` wash on alternate rows is the ONE\n * separation gesture; rows carry NO divider (#173's strong divider was the cue\n * only because nothing else was — the stripe replaces it, so a border would now\n * be redundant per the surface-separation rule). A theme that turns the stripe\n * off (`--table-stripe: transparent`) sets `--table-row-rule-width` to put the\n * strong divider back as the sole cue; it is `0px` by default, so the stock\n * stripe carries no border and no extra pixel.\n * - lines (`zebra={false}`): the classic `border-border-strong` divider between\n * rows; `last:border-b-0` so the final divider doesn't double with the\n * container's own bottom border (which reads as a heavy edge / shadow).\n */\n function rowSeparationClass(rowIndex: number): string {\n if (!zebra) return \"border-b border-border-strong last:border-b-0\";\n return cn(\n \"border-b-(length:--table-row-rule-width) border-border-strong last:border-b-0\",\n // Separate cn() argument: the stripe and the (theme-gated) rule are\n // alternative cues, never both at once — see the jsdoc above.\n rowIndex % 2 === 1 && \"bg-table-stripe\",\n );\n }\n\n /**\n * Fill for a PINNED body cell (#333) — the twin of `rowSeparationClass` above,\n * and the fix for the bug this issue reports.\n *\n * A pinned cell sits above horizontally-scrolling content, so it needs an\n * OPAQUE paint or the scrolled columns read straight through its text. But the\n * row's own cues — the zebra stripe, hover, selected — are TRANSLUCENT washes\n * that live on the `<tr>`, and a single opaque `background-color` on the\n * `<td>` hides all three: that is the \"seam / floating pill\" the issue\n * describes.\n *\n * So the cell paints the opaque `bg-card` base and re-applies the row's wash on\n * a decorative `::before` layer at a NEGATIVE stack level. Inside the cell's own\n * stacking context (it has one — `sticky` + a `z-` rung) that layer paints\n * ABOVE the cell's background and BELOW its text, which is exactly the order an\n * unpinned cell gets from the `<tr>`'s translucent background.\n *\n * The wash must NOT be a background-IMAGE gradient on the cell itself: under\n * `[data-decoration]`, `decoration.css` gives every\n * `.bg-card` element the ambient grid AS a `background-image`, so a gradient\n * would overwrite it and punch a flat, ungridded rectangle into the sheet\n * exactly where the frozen column is.\n *\n * Hover and selected stay in CSS (`group-hover/row:` / `group-data-…/row:`\n * against the `group/row` on the `<tr>`) because only the browser knows the\n * pointer is over a SIBLING cell of the same row.\n *\n * Keep this in sync with `rowSeparationClass`. Known limit: a caller's own\n * `rowClassName` background is NOT mirrored here — the component can't know\n * which part of an arbitrary class string is a fill.\n */\n function pinnedCellFillClass(rowIndex: number): string {\n return cn(\n \"bg-card\",\n \"before:pointer-events-none before:absolute before:inset-0 before:-z-10 before:content-['']\",\n zebra && rowIndex % 2 === 1 && \"before:bg-table-stripe\",\n \"group-hover/row:before:bg-table-row-hover\",\n \"group-data-[state=selected]/row:before:bg-accent\",\n );\n }\n\n /**\n * Accessible name for a row's hidden activation button (#337). Prefers the\n * caller's `rowActionLabel`, then the row's first DATA column value (via\n * `firstDataCellValue` — skips a leading display column with no accessor,\n * e.g. `createSelectionColumn()`'s own checkbox column, #11 I6), then the\n * localized generic fallback.\n */\n function rowActionName(row: (typeof rows)[number]): string {\n const explicit = rowActionLabel?.(row);\n if (explicit) return explicit;\n const name = firstDataCellValue(row);\n if (name !== undefined) return name;\n return t(\"data.table.rowAction\");\n }\n\n /** A single data row */\n function renderRow(\n row: (typeof rows)[number],\n rowIndex: number,\n extras?: React.HTMLAttributes<HTMLTableRowElement>,\n // Reorder metadata for THIS row, present in either handle mode whenever\n // reorder is active — `activator` is set only in `\"cell\"` mode, where the\n // grip button (not the row) is the drag activator (dnd-kit's\n // `setActivatorNodeRef` pattern).\n dragHandle?: {\n isDragging: boolean;\n activator?: {\n setActivatorNodeRef: (node: HTMLElement | null) => void;\n attributes: DraggableAttributes;\n listeners: DraggableSyntheticListeners;\n };\n },\n ) {\n // #337: `onRowClick` adds exactly ONE activation target per row — a\n // visually-hidden <button> in the first cell. The <tr> stays a plain `row`\n // (a focusable <tr> would be a tab stop with no activation semantics: it\n // can't take role=\"button\" without breaking the table's row/rowgroup\n // structure, so AT would announce a row and never that Enter does anything).\n const clickable = Boolean(onRowClick);\n\n function handleRowClick(event: React.MouseEvent<HTMLTableRowElement>) {\n // The hidden activation button matches this guard too, so a keyboard\n // Enter/Space — which the browser dispatches as a click that bubbles to\n // the row — is handled once, by the button, not twice.\n if (isInteractiveEventTarget(event.target)) return;\n if (isActiveTextSelection()) return;\n onRowClick?.(row, event);\n }\n\n return (\n <tr\n key={row.id}\n data-state={row.getIsSelected() ? \"selected\" : undefined}\n onClick={clickable ? handleRowClick : undefined}\n // Hover/selected are foreground-tint washes so they read more prominent than\n // the zebra stripe in the SAME direction across light/dark themes (the old\n // surface-muted/50 hover went the wrong way over a striped row).\n className={cn(\n // Color-only feedback (no transform/movement) → per\n // docs/MOTION_GUIDELINES.md item 3 this stays under OS reduced-motion\n // (only movement is neutralized); the gated duration-fast/ease-standard\n // pair already collapses toward ~0ms via --motion-factor when the user\n // or OS asks for reduced motion, matching the header sort button.\n \"transition-colors duration-fast ease-standard hover:bg-table-row-hover data-[state=selected]:bg-accent\",\n // #13: the dragged row's live `transform` (set inline via `extras.style`,\n // see `SortableDataRow`) is what actually MOVES it — this class only\n // makes that movement glide instead of snapping, through the gated\n // duration/ease utilities (never a raw ms/ease value —\n // quality-gates.md \"Motion-tokened\") with a reduced-motion\n // neutralizer. Raising the dragged row's stacking + opacity is a\n // colour/composite-only cue, so it isn't gated by the same rule.\n dragHandle &&\n \"relative transition-transform duration-base ease-standard motion-reduce:transition-none\",\n dragHandle?.isDragging && \"z-20 opacity-90 shadow-md\",\n // Named group (#333) so a PINNED cell can re-apply the row's hover /\n // selected wash on top of its own opaque fill — only CSS knows the\n // pointer is over a sibling cell. Purely a selector hook: `group/row`\n // emits no style of its own.\n \"group/row\",\n rowSeparationClass(rowIndex),\n // `<tr>` isn't in the global auto-cursor-pointer role list (button/\n // menuitem/tab/…), so a clickable row needs its own cursor. The focus\n // ring is driven off the hidden button's `:focus-visible` (same\n // `has-[[data-slot=…]:focus-visible]` pattern as InputGroup) so the\n // ring paints on the ROW the user is about to activate, even though\n // focus lives on the sr-only control inside it.\n clickable &&\n \"cursor-pointer has-[[data-slot=data-table-row-action]:focus-visible]:focus-ring-static-inset\",\n rowClassName?.(row),\n )}\n {...extras}\n >\n {dragHandle?.activator && (\n <td className=\"w-10 px-3 py-2 align-middle\">\n <button\n type=\"button\"\n ref={dragHandle.activator.setActivatorNodeRef}\n data-slot=\"data-table-row-drag-handle\"\n aria-label={t(\"data.table.reorderHandle\", { name: rowActionName(row) })}\n className={cn(\n \"inline-flex size-7 cursor-grab items-center justify-center rounded-sm text-muted-foreground transition-colors duration-fast ease-standard hover:bg-foreground/10 hover:text-foreground focus-ring active:cursor-grabbing\",\n dragHandle.isDragging && \"text-foreground\",\n )}\n {...dragHandle.activator.attributes}\n {...dragHandle.activator.listeners}\n >\n <GripVertical aria-hidden=\"true\" className=\"size-4\" />\n </button>\n </td>\n )}\n {row.getVisibleCells().map((cell, cellIndex) => {\n const geometry = pinnedCellGeometry(cell.column);\n // #12: same width triad as the header cell — see `resizeWidthStyle`.\n const resizeStyle = enableColumnResizing\n ? resizeWidthStyle(cell.column.getSize())\n : undefined;\n return (\n <td\n key={cell.id}\n data-pinned={geometry?.pinned ?? undefined}\n style={geometry?.style ?? resizeStyle}\n className={cn(\n \"px-3 py-2 align-middle\",\n // #69: same numeric-column seam as the header — see\n // `numericColumnClasses`.\n numericColumnClasses(cell.column.columnDef.meta),\n // z-10: above the normal (unpositioned) cells it scrolls over,\n // below the sticky header row (z-20) and the pinned corner (z-30).\n geometry && \"sticky z-10\",\n geometry && pinnedCellFillClass(rowIndex),\n // Separate cn() argument — see pinnedCellGeometry's edgeClass.\n geometry?.edgeClass,\n columnDividers && !geometry && COLUMN_DIVIDER_CLASS,\n )}\n >\n {clickable && cellIndex === 0 && (\n <button\n type=\"button\"\n data-slot=\"data-table-row-action\"\n // #311: `sr-only` removes the box from the visual layout but\n // not the browser's own focus ring — the ROW paints the\n // deliberate compound indicator (via the `has-[…]` selector\n // above), so the proxy's own native ring must be suppressed\n // or it leaks as a stray dot at the row's edge.\n className=\"sr-only focus-visible:outline-none\"\n onClick={(event) => onRowClick?.(row, event)}\n >\n {rowActionName(row)}\n </button>\n )}\n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n </td>\n );\n })}\n </tr>\n );\n }\n\n /**\n * Skeleton placeholder `<tr>`s — shared by the normal and virtualized tbody\n * renderers so a markup/token/a11y fix only needs to be made once (#231).\n */\n function renderSkeletonBody(count: number) {\n // #69: iterate the real leaf columns (not just a count) so each skeleton\n // `<td>` can read the same `meta.numeric`/`meta.align` as the loaded\n // header/body cells — a loading table whose skeleton didn't mirror the\n // real alignment is exactly the column-shift-on-load bug\n // loading-states.md § \"CLS / space reservation\" warns about.\n const visibleColumns = table.getVisibleLeafColumns();\n return Array.from({ length: count }).map((_, i) => (\n <tr key={`skeleton-${i}`} aria-hidden=\"true\" className={rowSeparationClass(i)}>\n {hasGripColumn && (\n <td className=\"w-10 px-3 py-2 align-middle\">\n <Skeleton className=\"size-4\" />\n </td>\n )}\n {visibleColumns.map((column) => (\n <td\n key={column.id}\n className={cn(\n \"px-3 py-2 align-middle\",\n numericColumnClasses(column.columnDef.meta),\n columnDividers && COLUMN_DIVIDER_CLASS,\n )}\n >\n <Skeleton className=\"h-4 w-full\" />\n </td>\n ))}\n </tr>\n ));\n }\n\n /**\n * Empty-state `<tr>` — shared by the normal and virtualized tbody renderers\n * (#231).\n */\n function renderEmptyBody() {\n return (\n <tr>\n <td\n colSpan={colCount + (hasGripColumn ? 1 : 0)}\n className=\"h-24 px-3 text-center text-muted-foreground\"\n >\n {emptyMessage}\n </td>\n </tr>\n );\n }\n\n // ─── Non-virtualized tbody ────────────────────────────────────────────────\n function renderTbodyNormal() {\n if (showSkeletons) {\n return <tbody>{renderSkeletonBody(skeletonRowCount)}</tbody>;\n }\n if (showEmpty) {\n return <tbody>{renderEmptyBody()}</tbody>;\n }\n if (!rowReorderActive) {\n return <tbody>{rows.map((row, i) => renderRow(row, i))}</tbody>;\n }\n\n // #13: `SortableContext` renders no DOM element of its own (a plain\n // context Provider), so nesting it around `<tbody>` here does not insert\n // anything between `<table>` and `<tbody>` — the real DOM stays valid.\n return (\n <SortableContext\n items={rows.map((r) => getReorderRowId(r))}\n strategy={verticalListSortingStrategy}\n >\n <tbody>\n {rows.map((row, i) => (\n <SortableDataRow\n key={getReorderRowId(row)}\n id={getReorderRowId(row)}\n attributesOverride={{\n // #98: dnd-kit's own `roleDescription: 'sortable'` default is\n // hardcoded English; override it with the localized value in\n // BOTH handle modes — `role` stays row-mode-only (see the\n // `attributesOverride` prop doc above).\n roleDescription: t(\"data.table.reorderRoleDescription\"),\n ...(rowReorderHandle === \"row\" ? { role: \"row\" } : null),\n }}\n >\n {({ setNodeRef, setActivatorNodeRef, attributes, listeners, isDragging, style }) =>\n renderRow(\n row,\n i,\n {\n ref: setNodeRef,\n style,\n // `aria-pressed` is a `DraggableAttributes` field meant for a\n // real `<button>` activator; spread onto a `<tr role=\"row\">`\n // (row-handle mode) it fails axe's `aria-allowed-attr` (that\n // ARIA state is not permitted on the `row` role), so strip it\n // here rather than exempt it downstream.\n ...(rowReorderHandle === \"row\"\n ? (() => {\n const { \"aria-pressed\": _ariaPressed, ...rowAttributes } = attributes;\n return { ...rowAttributes, ...listeners };\n })()\n : {}),\n } as React.HTMLAttributes<HTMLTableRowElement>,\n {\n isDragging,\n activator:\n rowReorderHandle === \"cell\"\n ? { setActivatorNodeRef, attributes, listeners }\n : undefined,\n },\n )\n }\n </SortableDataRow>\n ))}\n </tbody>\n </SortableContext>\n );\n }\n\n // ─── Virtualized tbody ────────────────────────────────────────────────────\n function renderTbodyVirtualized() {\n if (showSkeletons) {\n // For virtualized mode, cap the visible skeleton rows at 10 unless caller\n // has explicitly set loadingRows.\n const virtualSkeletonCount = loadingRows ?? Math.min(10, pageSize);\n return <tbody>{renderSkeletonBody(virtualSkeletonCount)}</tbody>;\n }\n\n return (\n <tbody>\n {showEmpty ? (\n renderEmptyBody()\n ) : (\n <>\n {/* Top spacer — real <tr> so table layout is preserved */}\n {paddingTop > 0 && (\n <tr aria-hidden=\"true\">\n <td style={{ height: paddingTop }} colSpan={colCount} />\n </tr>\n )}\n {virtualItems.map((virtualRow) => {\n const row = rows[virtualRow.index];\n // row is guaranteed present because virtualizer.count === rows.length,\n // but TypeScript doesn't know array indexing is safe here.\n if (!row) return null;\n return renderRow(row, virtualRow.index, {\n ref: virtualizer.measureElement as React.Ref<HTMLTableRowElement>,\n \"data-index\": virtualRow.index,\n // Absolute 1-based row position; header row(s) occupy 1..headerRowCount.\n \"aria-rowindex\": headerRowCount + virtualRow.index + 1,\n } as React.HTMLAttributes<HTMLTableRowElement>);\n })}\n {/* Bottom spacer */}\n {paddingBottom > 0 && (\n <tr aria-hidden=\"true\">\n <td style={{ height: paddingBottom }} colSpan={colCount} />\n </tr>\n )}\n </>\n )}\n </tbody>\n );\n }\n\n // ─── Pagination controls ──────────────────────────────────────────────────\n function renderPagination() {\n // Virtualization wins over pagination per spec — don't render controls\n if (enableRowVirtualization) return null;\n if (!enablePagination && !manualPagination) return null;\n\n // #342: a genuinely single-page table renders a permanently-disabled\n // pager (\"Page 1 of 1\", both buttons disabled) — hide it, UNLESS the page\n // count isn't actually knowable: under `manualPagination` without a\n // `rowCount`/`pageCount`, TanStack's `getPageCount()` falls back to the\n // CURRENT page's row count, so \"<= 1\" there is a false positive for\n // \"really one page\" — the #227 dev warning above stays the diagnostic for\n // exactly that ambiguous case, so this flag doesn't also mask it.\n const pageCountUnknown = manualPagination && rowCount === undefined && pageCount === undefined;\n if (hidePaginationWhenSingle && !pageCountUnknown && table.getPageCount() <= 1) return null;\n\n return (\n <div className=\"flex items-center justify-between\">\n <p className=\"text-body text-muted-foreground\">\n Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount() || 1}\n </p>\n <div className=\"flex gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => table.previousPage()}\n disabled={!table.getCanPreviousPage()}\n >\n Previous\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => table.nextPage()}\n disabled={!table.getCanNextPage()}\n >\n Next\n </Button>\n </div>\n </div>\n );\n }\n\n // ─── Render ───────────────────────────────────────────────────────────────\n\n // #338: visually-hidden accessible name for the table. Must be the FIRST\n // child of <table> per the HTML spec (caption immediately follows the\n // opening tag) — both branches place it before their thead.\n const captionElement = caption != null ? <caption className=\"sr-only\">{caption}</caption> : null;\n\n if (enableRowVirtualization) {\n // Virtualized branch: scroll container wraps the whole table\n // If both enablePagination and enableRowVirtualization are set,\n // virtualization wins; pagination controls are silently suppressed.\n return (\n <div ref={ref} className={cn(\"space-y-3\", className)} {...rest}>\n {toolbar ? toolbar(table) : null}\n {/* Outer border is redundant (surface change) → plain border per #173 spec.\n tabIndex={0} makes the windowed scroll region keyboard-operable — the rows\n themselves aren't focusable, so without it the off-screen rows are\n unreachable by keyboard (WCAG 2.1.1 / axe `scrollable-region-focusable`). */}\n <div\n ref={scrollRef}\n tabIndex={0}\n // Names the focus stop (WCAG 4.1.2). A naming-capable role is required\n // for that name to compute at all — `aria-label` on a plain `<div>`\n // (role `generic`) is not guaranteed to produce an accessible name.\n // `group`, not `region`: a landmark per table would be redundant over\n // the real <table> and collide under axe `landmark-unique` when two\n // tables share a page.\n role=\"group\"\n aria-label={t(\"data.table.scrollRegion\")}\n aria-busy={loading || undefined}\n className=\"relative overflow-auto rounded-lg border bg-card focus-ring\"\n style={{ maxHeight: maxBodyHeight, ...pinnedScrollPadding }}\n >\n {/* Loading overlay */}\n {loading && rows.length > 0 && (\n <div\n role=\"status\"\n aria-live=\"polite\"\n // z-40 (raised from z-20 for #333): the overlay covers the WHOLE\n // table, so it has to sit above the pinned-column ladder (body z-10,\n // sticky header z-20, pinned header corner z-30) or a frozen column\n // would punch through the \"loading\" scrim.\n className=\"absolute inset-0 z-40 flex items-center justify-center rounded-lg bg-card/80\"\n >\n <Spinner aria-hidden=\"true\" className=\"text-foreground\" />\n <span className=\"sr-only\">{t(\"data.table.loading\")}</span>\n </div>\n )}\n <table\n aria-busy={loading || undefined}\n aria-rowcount={ariaRowCount}\n className=\"w-full caption-bottom text-body\"\n >\n {captionElement}\n {renderThead(true, true)}\n {renderTbodyVirtualized()}\n </table>\n </div>\n </div>\n );\n }\n\n // Non-virtualized branch.\n // #330: the scroll box is `overflow-auto` (was `overflow-hidden`, silently\n // clipping columns that didn't fit instead of letting them scroll) and\n // keyboard-focusable, parity with the virtualized branch above. Split into\n // an OUTER non-scrolling wrapper (keeps the rounded/border/bg chrome +\n // clip, and is the positioning context for the loading overlay + edge\n // fades) and an INNER scrolling div (the focusable, `overflow-auto` scroll\n // region) so the edge-fade affordance can stay pinned to the visible edges\n // instead of scrolling away with the table content.\n const nonVirtualizedContent = (\n <div ref={ref} className={cn(\"space-y-3\", className)} {...rest}>\n {toolbar ? toolbar(table) : null}\n {/* Outer border is redundant (surface change) → plain border per #173 spec */}\n <div\n aria-busy={loading || undefined}\n className=\"relative overflow-hidden rounded-lg border bg-card\"\n >\n {/* Loading overlay */}\n {loading && rows.length > 0 && (\n <div\n role=\"status\"\n aria-live=\"polite\"\n // z-40 (raised from z-20 for #333): the overlay covers the WHOLE\n // table, so it has to sit above the pinned-column ladder (body z-10,\n // sticky header z-20, pinned header corner z-30) or a frozen column\n // would punch through the \"loading\" scrim.\n className=\"absolute inset-0 z-40 flex items-center justify-center rounded-lg bg-card/80\"\n >\n <Spinner aria-hidden=\"true\" className=\"text-foreground\" />\n <span className=\"sr-only\">{t(\"data.table.loading\")}</span>\n </div>\n )}\n {/* The tab stop exists ONLY while the region measurably overflows: without\n it, columns beyond the viewport are unreachable by keyboard (WCAG 2.1.1 /\n axe `scrollable-region-focusable`) — but adding it unconditionally would\n give every table that FITS a focus stop that does nothing and announces\n \"scrollable\" when it isn't. `aria-label` moves with it (WCAG 4.1.2:\n a name for a stop that exists, none for one that doesn't) — and\n `role=\"group\"` moves with BOTH of them: `aria-label` on a plain\n `<div>` (role `generic`) is not guaranteed to compute into an\n accessible name, so the stop needs a naming-capable role. `group`,\n never the `region` landmark: that would be redundant over the real\n <table> and collide (axe `landmark-unique`) with every other\n overflowing table on the page. */}\n <div\n ref={plainScrollRef}\n data-slot=\"data-table-scroll-region\"\n tabIndex={scrollOverflows ? 0 : undefined}\n role={scrollOverflows ? \"group\" : undefined}\n aria-label={scrollOverflows ? t(\"data.table.scrollRegion\") : undefined}\n onScroll={updateScrollAffordance}\n className=\"overflow-auto rounded-lg focus-ring-inset\"\n style={hasLeftPinned || hasRightPinned ? pinnedScrollPadding : undefined}\n >\n <table aria-busy={loading || undefined} className=\"w-full caption-bottom text-body\">\n {captionElement}\n {renderThead(false)}\n {renderTbodyNormal()}\n </table>\n </div>\n {/* Horizontal-scroll edge fade — a token-driven affordance that only\n appears once the table actually overflows its container in that\n direction, so a desktop/wide table renders neither (visual no-op).\n\n #333: an edge with a PINNED column renders no fade. The fade lives\n outside the scroll region and would paint a 32px wash straight over\n the frozen column's own text; and the affordance is already carried\n there by the pinned block's `border-border-strong` seam, which is\n what a frozen column means (\"content slides under this edge\"). So\n the fade stays the cue for a FREE edge only. */}\n {canScrollLeft && !hasLeftPinned && (\n <div\n aria-hidden=\"true\"\n data-slot=\"data-table-scroll-fade-left\"\n className=\"pointer-events-none absolute inset-y-0 left-0 z-10 w-8 rounded-lg bg-gradient-to-r from-card to-transparent\"\n />\n )}\n {canScrollRight && !hasRightPinned && (\n <div\n aria-hidden=\"true\"\n data-slot=\"data-table-scroll-fade-right\"\n className=\"pointer-events-none absolute inset-y-0 right-0 z-10 w-8 rounded-lg bg-gradient-to-l from-card to-transparent\"\n />\n )}\n </div>\n\n {renderPagination()}\n </div>\n );\n\n // #13: `DndContext` renders no wrapping DOM element around `children` either\n // — it composes `children` alongside its own hidden a11y nodes (the\n // screen-reader instructions, plus a `role=\"status\"` `LiveRegion` that is\n // permanently silent — see `silentDragAnnouncements` above) as SIBLINGS.\n // Wrapping the whole component root here (rather than reaching inside the\n // `<table>`) is what keeps those hidden nodes out of the table's own DOM —\n // they land beside the table's outer `<div>`, never inside a\n // `<thead>`/`<tbody>`, which is the only place in HTML that would reject\n // them. DataTable's OWN `aria-live=\"polite\"` region (`reorderLiveMessage`)\n // is a further sibling here for the same reason.\n if (!rowReorderActive) return nonVirtualizedContent;\n return (\n <DndContext\n sensors={reorderSensors}\n collisionDetection={closestCenter}\n onDragStart={handleRowDragStart}\n onDragOver={handleRowDragOver}\n onDragEnd={handleRowDragEnd}\n onDragCancel={handleRowDragCancel}\n accessibility={{\n announcements: silentDragAnnouncements,\n // #98: dnd-kit's own hidden keyboard-instructions node is hardcoded\n // English (`defaultScreenReaderInstructions`) unless overridden here.\n screenReaderInstructions: { draggable: t(\"data.table.reorderInstructions\") },\n }}\n >\n {nonVirtualizedContent}\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n data-slot=\"data-table-reorder-live-region\"\n className=\"sr-only\"\n >\n {reorderLiveMessage}\n </div>\n </DndContext>\n );\n}\n\n// ─── Public export with forwardRef + generic cast ─────────────────────────────\n//\n// React.forwardRef strips the generic parameter. The cast below restores it so\n// callers get full type inference on `columns` / `data` while still being able\n// to forward a ref to the root <div>.\n//\n// The ref prop is already declared in DataTableProps (optional) so existing\n// consumers are backward-compatible; the forwardRef call means passing a ref\n// object also works.\n\nconst DataTableWithRef = forwardRef(DataTableInner) as <TData, TValue>(\n props: DataTableProps<TData, TValue> & { ref?: React.Ref<HTMLDivElement> },\n) => React.ReactElement | null;\n\nexport { DataTableWithRef as DataTable };\n","\"use client\";\n\nimport { forwardRef, useId, useRef, type InputHTMLAttributes } from \"react\";\nimport { Input, useLocale } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { SearchIcon } from \"@elabs-ai/components-icons\";\n\nexport interface SearchInputProps extends Omit<\n InputHTMLAttributes<HTMLInputElement>,\n \"onChange\" | \"value\"\n> {\n value: string;\n onValueChange: (value: string) => void;\n /** Visually-hidden accessible label. Defaults to the localized \"Search\" microcopy. */\n label?: string;\n containerClassName?: string;\n}\n\n/**\n * Search field with a leading icon and a clear button. Controlled.\n *\n * `disabled` (available via the extended `InputHTMLAttributes`) is how a\n * consumer signals a pending fetch (D5 — the app owns fetch state, this\n * control just reflects it; see loading-states.md). It is forwarded to the\n * `<Input>` explicitly AND gates the clear button — while disabled the clear\n * affordance is hidden so it can't mutate the filter mid-request (#269/#8).\n */\nexport const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(function SearchInput(\n {\n value,\n onValueChange,\n label,\n placeholder,\n className,\n containerClassName,\n disabled,\n id: idProp,\n ...props\n },\n forwardedRef,\n) {\n const { t } = useLocale();\n const resolvedLabel = label ?? t(\"data.searchInput.label\");\n const resolvedPlaceholder = placeholder ?? t(\"data.searchInput.placeholder\");\n const generatedId = useId();\n const id = idProp ?? generatedId;\n const inputRef = useRef<HTMLInputElement>(null);\n\n const setRefs = (node: HTMLInputElement | null) => {\n inputRef.current = node;\n if (typeof forwardedRef === \"function\") forwardedRef(node);\n else if (forwardedRef) forwardedRef.current = node;\n };\n\n const handleClear = () => {\n onValueChange(\"\");\n // The clear button unmounts the instant `value` becomes falsy — without\n // this, the browser drops focus to <body> instead of leaving it\n // somewhere the keyboard user can keep typing.\n inputRef.current?.focus();\n };\n\n return (\n <div className={cn(\"relative w-full max-w-xs\", containerClassName)}>\n <label htmlFor={id} className=\"sr-only\">\n {resolvedLabel}\n </label>\n <SearchIcon\n size={16}\n className=\"pointer-events-none absolute start-2.5 top-1/2 -translate-y-1/2 text-muted-foreground\"\n />\n <Input\n ref={setRefs}\n id={id}\n value={value}\n onChange={(e) => onValueChange(e.target.value)}\n placeholder={resolvedPlaceholder}\n disabled={disabled}\n className={cn(\"ps-8\", value && \"pe-8\", className)}\n {...props}\n />\n {value && !disabled ? (\n <button\n type=\"button\"\n onClick={handleClear}\n aria-label={t(\"data.searchInput.clear\")}\n className=\"absolute end-2 top-1/2 -translate-y-1/2 rounded-sm p-0.5 text-muted-foreground transition-colors duration-fast ease-standard hover:text-foreground focus-ring animate-in fade-in zoom-in-95 duration-fast ease-entrance\"\n >\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n </button>\n ) : null}\n </div>\n );\n});\n","import { type ReactNode } from \"react\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\nexport interface FilterBarProps {\n /** Left cluster: search + facet filters. */\n children: ReactNode;\n /** Right cluster: column picker, export, primary actions. */\n actions?: ReactNode;\n className?: string;\n}\n\n/** Horizontal toolbar that groups table filters and actions. */\nexport function FilterBar({ children, actions, className }: FilterBarProps) {\n return (\n <div className={cn(\"flex flex-wrap items-center justify-between gap-2\", className)}>\n <div className=\"flex flex-wrap items-center gap-2\">{children}</div>\n {actions ? <div className=\"flex items-center gap-2\">{actions}</div> : null}\n </div>\n );\n}\n","\"use client\";\n\n/**\n * filter-chip.tsx — `@elabs-ai/components-data`'s removable filter chip, with an optional\n * secondary count (\"excluded 1,204\") for `ProcessFilterBar` (RM-056, #221).\n *\n * Deliberately a thin COMPOSING wrapper around `@elabs-ai/components-ui`'s `FilterChip`\n * (`view-toolbar.tsx`, #331) rather than a second implementation — the dedupe\n * audit found the real, accessible, whole-chip-as-button `FilterChip` already\n * lives there (WCAG 2.5.8 target size, WCAG 2.5.3 \"Remove filter: <label>\"\n * accessible name). Building a second one in `packages/data` would duplicate\n * that work; this wrapper reuses it and passes `count`/`countLabel` through\n * the base component's `trailing` slot (#284) — a second, non-shrinking text\n * element, distinct from the truncatable `label` — so the count reaches the\n * chip's ACCESSIBLE NAME (screen readers hear \"Remove filter: Status: Failed\n * · excluded 1,204\") AND survives truncation in the visible chip, instead of\n * being folded into the one string CSS `truncate` can clip from the tail.\n */\nimport { forwardRef } from \"react\";\nimport {\n FilterChip as BaseFilterChip,\n type FilterChipProps as BaseFilterChipProps,\n useLocale,\n} from \"@elabs-ai/components-ui\";\n\n// `trailing` is omitted alongside `label`: this wrapper derives its OWN\n// `trailing` from `count`/`countLabel`. The `Omit` blocks `trailing` written\n// as an object LITERAL, but TypeScript's excess-property check does not\n// apply to a spread of an already-declared variable — `const extra = {\n// trailing: \"x\" }; <FilterChip {...extra} />` still type-checks, and the\n// value would land in `props` regardless of JSX attribute order (PR #408\n// review round 2). So the `Omit` is necessary but not sufficient: below,\n// `trailing` is also stripped from `props` at RUNTIME before it reaches the\n// base component, so a caller-supplied `trailing` — literal or\n// spread-smuggled — can never win at render, the same advertised-but-inert\n// failure mode #382/#284-round-1 already closed elsewhere in the repo\n// (`ContextRail`'s `children` omission).\nexport interface FilterChipProps extends Omit<BaseFilterChipProps, \"label\" | \"trailing\"> {\n /**\n * Label-in-value text — `\"Status: Failed\"`, never `\"Status = failed\"` and\n * never a bare `\"Failed\"`. Same contract as the base `FilterChip`.\n */\n label: string;\n /**\n * How many records this active filter excluded (or matched) — rendered as a\n * secondary, locale-formatted segment alongside `label`. Omit for a bare\n * chip with no count.\n */\n count?: number;\n /**\n * The word placed before the formatted count, e.g. `\"excluded\"` →\n * `\"excluded 1,204\"`. Omitted by default: a bare `count` renders as just the\n * formatted number.\n */\n countLabel?: string;\n}\n\n/**\n * A removable active-filter chip with an optional secondary count.\n *\n * `onRemove` stays REQUIRED (inherited from the base `FilterChip`, diverging\n * from this item's spec draft) — the whole chip IS the remove control, so a\n * chip with no removal affordance is a plain `Badge`, not this component.\n */\nexport const FilterChip = forwardRef<HTMLButtonElement, FilterChipProps>(function FilterChip(\n { label, count, countLabel, ...props },\n ref,\n) {\n const { formatNumber } = useLocale();\n const countText =\n count === undefined\n ? undefined\n : countLabel\n ? `${countLabel} ${formatNumber(count)}`\n : formatNumber(count);\n\n // Runtime guard (belt and braces alongside the `Omit` above): a caller can\n // still smuggle `trailing` into `props` through a spread of an\n // already-declared variable, which the type system cannot catch. Strip it\n // here so the derived count wins regardless of prop order.\n const { trailing: _ignoredTrailing, ...restProps } = props as Omit<BaseFilterChipProps, \"label\">;\n\n return (\n <BaseFilterChip\n ref={ref}\n data-slot=\"filter-chip\"\n label={label}\n {...restProps}\n trailing={countText}\n />\n );\n});\n","import type { ButtonHTMLAttributes } from \"react\";\nimport { forwardRef } from \"react\";\nimport {\n Badge,\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n useLocale,\n} from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\nexport interface FacetOption {\n label: string;\n value: string;\n}\n\nexport interface FacetFilterProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, \"title\"> {\n title: string;\n options: FacetOption[];\n /** Currently selected values (controlled). */\n selected: string[];\n onSelectedChange: (values: string[]) => void;\n}\n\n/**\n * Multi-select faceted filter rendered as a dropdown of toggles.\n *\n * `disabled` (forwarded to the trigger `Button`) is how a consumer signals a\n * pending fetch (D5 — the app owns fetch state, this control just reflects\n * it; see loading-states.md).\n *\n * The trigger takes `Button`'s DEFAULT size (`h-9`), not `sm` (#346): a facet\n * filter lives in a toolbar beside `Select` / `Input` / `DatePicker`, all of\n * which land on `h-9` (Select's own default rung, Input hardcoded, DatePicker\n * via this same Button default). An `sm` trigger was the lone `h-8` outlier in\n * that row, so the top and bottom edges of a filter bar didn't line up.\n */\nexport const FacetFilter = forwardRef<HTMLButtonElement, FacetFilterProps>(function FacetFilter(\n { title, options, selected, onSelectedChange, className, ...props },\n ref,\n) {\n const { t } = useLocale();\n const selectedSet = new Set(selected);\n const toggle = (value: string) => {\n const next = new Set(selectedSet);\n if (next.has(value)) next.delete(value);\n else next.add(value);\n onSelectedChange([...next]);\n };\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button ref={ref} variant=\"outline\" className={cn(\"border-dashed\", className)} {...props}>\n {title}\n {selected.length > 0 ? (\n <Badge\n variant=\"secondary\"\n className=\"ms-1 rounded px-1.5 animate-in fade-in zoom-in-95 duration-fast ease-entrance\"\n >\n {selected.length}\n </Badge>\n ) : null}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent className=\"min-w-[12rem]\">\n <DropdownMenuLabel>{title}</DropdownMenuLabel>\n {options.map((opt) => {\n const checked = selectedSet.has(opt.value);\n return (\n <DropdownMenuItem\n key={opt.value}\n onSelect={(e) => {\n e.preventDefault();\n toggle(opt.value);\n }}\n >\n <span\n aria-hidden=\"true\"\n className={\n \"flex size-4 items-center justify-center rounded border transition-colors duration-fast ease-standard \" +\n (checked ? \"border-primary bg-primary text-primary-foreground\" : \"border-input\")\n }\n >\n {checked ? \"✓\" : \"\"}\n </span>\n {opt.label}\n </DropdownMenuItem>\n );\n })}\n {selected.length > 0 ? (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem onSelect={() => onSelectedChange([])}>\n {t(\"data.facetFilter.clearFilters\")}\n </DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n});\n\nFacetFilter.displayName = \"FacetFilter\";\n","import { type Table } from \"@tanstack/react-table\";\nimport type { ButtonHTMLAttributes, ReactElement, Ref } from \"react\";\nimport { forwardRef } from \"react\";\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n useLocale,\n} from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\nexport interface ColumnPickerProps<TData> extends ButtonHTMLAttributes<HTMLButtonElement> {\n table: Table<TData>;\n /** Trigger label. Defaults to \"Columns\". */\n label?: string;\n}\n\nfunction ColumnPickerInner<TData>(\n { table, label, className, ...props }: ColumnPickerProps<TData>,\n ref: Ref<HTMLButtonElement>,\n) {\n const { t } = useLocale();\n const resolvedLabel = label ?? t(\"data.columnPicker.label\");\n const columns = table.getAllColumns().filter((c) => c.getCanHide());\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button ref={ref} variant=\"outline\" size=\"sm\" className={cn(className)} {...props}>\n {resolvedLabel}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"min-w-[12rem]\">\n <DropdownMenuLabel>{t(\"data.columnPicker.toggleColumns\")}</DropdownMenuLabel>\n <DropdownMenuSeparator />\n {columns.map((column) => (\n <DropdownMenuItem\n key={column.id}\n onSelect={(e) => {\n e.preventDefault();\n column.toggleVisibility(!column.getIsVisible());\n }}\n >\n <span\n aria-hidden=\"true\"\n className={\n \"flex size-4 items-center justify-center rounded border transition-colors duration-fast ease-standard \" +\n (column.getIsVisible()\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-input\")\n }\n >\n {column.getIsVisible() ? \"✓\" : \"\"}\n </span>\n <span className=\"capitalize\">{column.id}</span>\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\nColumnPickerInner.displayName = \"ColumnPicker\";\n\n// React.forwardRef strips the generic parameter — the cast below restores it\n// (same pattern as DataTable's public export) so callers get full type\n// inference on `table` while still being able to forward a ref to the\n// trigger `Button`.\n//\n// `disabled` (available via the extended `ButtonHTMLAttributes`, forwarded to\n// the trigger) is how a consumer signals a pending fetch (D5 — the app owns\n// fetch state; see loading-states.md).\nexport const ColumnPicker = forwardRef(ColumnPickerInner) as <TData>(\n props: ColumnPickerProps<TData> & { ref?: Ref<HTMLButtonElement> },\n) => ReactElement | null;\n","/**\n * Minimal, dependency-free CSV serializer (RFC 4180).\n *\n * `toCsv` is pure + SSR-safe (no DOM, no deps). `downloadCsv` delegates the\n * browser save mechanics to `@elabs-ai/components-ui`'s shared `downloadBlob` (one home for\n * the Blob → `<a download>` dance; @elabs-ai/components-ui is already a peer dep here).\n * Value stringification + injection-guarded field quoting live in\n * `@elabs-ai/components-ui`'s `csv` lib (`csvStringifyValue`/`csvQuoteField`) — the shared\n * home so `@elabs-ai/components-charts`'s ChartFrame serializer can reuse the same logic\n * without a charts → data dependency (not allowed per the one-way rule).\n */\nimport { csvQuoteField, csvStringifyValue, downloadBlob } from \"@elabs-ai/components-ui\";\n\nexport type CsvColumn<TData> = { key: keyof TData & string; header?: string };\n\nexport interface ToCsvOptions<TData> {\n /** Subset/reorder of columns. Omitted → all keys from rows[0]. */\n columns?: CsvColumn<TData>[];\n /** Emit header row. Default true. */\n header?: boolean;\n /** Field delimiter. Default \",\". */\n delimiter?: string;\n}\n\nexport interface DownloadCsvOptions<TData> extends ToCsvOptions<TData> {\n /** File name without extension. Default \"download\". */\n filename?: string;\n}\n\nconst stringifyValue = csvStringifyValue;\nconst quoteField = csvQuoteField;\n\n/**\n * Serialize rows to a CSV string (no DOM access — safe for SSR / jsdom).\n */\nexport function toCsv<TData extends Record<string, unknown>>(\n rows: TData[],\n opts?: ToCsvOptions<TData>,\n): string {\n const delimiter = opts?.delimiter ?? \",\";\n const includeHeader = opts?.header !== false;\n\n // Derive columns from first row when not provided.\n const firstRow = rows[0];\n const cols: CsvColumn<TData>[] =\n opts?.columns ??\n (firstRow !== undefined\n ? (Object.keys(firstRow) as (keyof TData & string)[]).map((k) => ({ key: k }))\n : []);\n\n const lines: string[] = [];\n\n if (includeHeader && cols.length > 0) {\n const headerRow = cols.map((c) => quoteField(c.header ?? c.key, delimiter)).join(delimiter);\n lines.push(headerRow);\n }\n\n for (const row of rows) {\n const line = cols.map((c) => quoteField(stringifyValue(row[c.key]), delimiter)).join(delimiter);\n lines.push(line);\n }\n\n // RFC 4180: CRLF line terminator, trailing newline.\n return lines.join(\"\\r\\n\") + (lines.length > 0 ? \"\\r\\n\" : \"\");\n}\n\n/**\n * Trigger a CSV file download in the browser. No-op in SSR environments.\n */\nexport function downloadCsv<TData extends Record<string, unknown>>(\n rows: TData[],\n opts?: DownloadCsvOptions<TData>,\n): void {\n if (typeof document === \"undefined\") return;\n\n const csv = toCsv(rows, opts);\n const blob = new Blob([csv], { type: \"text/csv;charset=utf-8;\" });\n downloadBlob(blob, (opts?.filename ?? \"download\") + \".csv\");\n}\n"],"mappings":"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","names":["rest","forwardRef","useRef","useLocale","cn","jsx","jsxs","SearchInput","cn","jsx","jsxs","forwardRef","useLocale","jsx","FilterChip","forwardRef","Button","useLocale","cn","Fragment","jsx","jsxs","FacetFilter","forwardRef","Button","DropdownMenu","DropdownMenuContent","DropdownMenuItem","DropdownMenuLabel","DropdownMenuSeparator","DropdownMenuTrigger","useLocale","cn","jsx","jsxs"]}
|