@elabs-ai/components-data 4.2.0 → 5.1.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.
Files changed (131) hide show
  1. package/README.md +1 -1
  2. package/dist/chunk-3OVN7MMO.js +2 -0
  3. package/dist/chunk-3OVN7MMO.js.map +1 -0
  4. package/dist/chunk-3UFWYVDS.js +2 -0
  5. package/dist/chunk-3UFWYVDS.js.map +1 -0
  6. package/dist/chunk-47ZBQZYS.js +47 -0
  7. package/dist/chunk-47ZBQZYS.js.map +1 -0
  8. package/dist/chunk-5K2IFOV2.js +2 -0
  9. package/dist/chunk-5K2IFOV2.js.map +1 -0
  10. package/dist/chunk-677ORWI6.js +17 -0
  11. package/dist/chunk-677ORWI6.js.map +1 -0
  12. package/dist/chunk-7ZYUY3UL.js +2 -0
  13. package/dist/chunk-7ZYUY3UL.js.map +1 -0
  14. package/dist/chunk-BEYHV3ZX.js +37 -0
  15. package/dist/chunk-BEYHV3ZX.js.map +1 -0
  16. package/dist/chunk-DBPJ7LVM.js +57 -0
  17. package/dist/chunk-DBPJ7LVM.js.map +1 -0
  18. package/dist/chunk-E3V3SYY7.js +87 -0
  19. package/dist/chunk-E3V3SYY7.js.map +1 -0
  20. package/dist/chunk-GBO56756.js +81 -0
  21. package/dist/chunk-GBO56756.js.map +1 -0
  22. package/dist/chunk-HG2YMSRM.js +95 -0
  23. package/dist/chunk-HG2YMSRM.js.map +1 -0
  24. package/dist/chunk-K2GHSRCP.js +1823 -0
  25. package/dist/chunk-K2GHSRCP.js.map +1 -0
  26. package/dist/chunk-KCB33PD3.js +43 -0
  27. package/dist/chunk-KCB33PD3.js.map +1 -0
  28. package/dist/chunk-MV2YVXEZ.js +67 -0
  29. package/dist/chunk-MV2YVXEZ.js.map +1 -0
  30. package/dist/chunk-NKVJUQYW.js +82 -0
  31. package/dist/chunk-NKVJUQYW.js.map +1 -0
  32. package/dist/chunk-OASM53LL.js +29 -0
  33. package/dist/chunk-OASM53LL.js.map +1 -0
  34. package/dist/chunk-RZ5MYZOA.js +2 -0
  35. package/dist/chunk-RZ5MYZOA.js.map +1 -0
  36. package/dist/chunk-SK6347WB.js +100 -0
  37. package/dist/chunk-SK6347WB.js.map +1 -0
  38. package/dist/chunk-TBB33PHQ.js +58 -0
  39. package/dist/chunk-TBB33PHQ.js.map +1 -0
  40. package/dist/chunk-VCLA45IC.js +150 -0
  41. package/dist/chunk-VCLA45IC.js.map +1 -0
  42. package/dist/chunk-XN6O6Z2N.js +58 -0
  43. package/dist/chunk-XN6O6Z2N.js.map +1 -0
  44. package/dist/chunk-ZFEW36RG.js +77 -0
  45. package/dist/chunk-ZFEW36RG.js.map +1 -0
  46. package/dist/chunk-ZQXJ34VJ.js +34 -0
  47. package/dist/chunk-ZQXJ34VJ.js.map +1 -0
  48. package/dist/chunk-ZUCGV2XD.js +42 -0
  49. package/dist/chunk-ZUCGV2XD.js.map +1 -0
  50. package/dist/column-picker/column-picker.js +8 -0
  51. package/dist/column-picker/column-picker.js.map +1 -0
  52. package/dist/column-picker/index.js +9 -0
  53. package/dist/column-picker/index.js.map +1 -0
  54. package/dist/data-table/card-layout.js +11 -0
  55. package/dist/data-table/card-layout.js.map +1 -0
  56. package/dist/data-table/cell-scales.js +26 -0
  57. package/dist/data-table/cell-scales.js.map +1 -0
  58. package/dist/data-table/cells/bar-cell.js +14 -0
  59. package/dist/data-table/cells/bar-cell.js.map +1 -0
  60. package/dist/data-table/cells/category-legend.js +9 -0
  61. package/dist/data-table/cells/category-legend.js.map +1 -0
  62. package/dist/data-table/cells/columns-cell.js +13 -0
  63. package/dist/data-table/cells/columns-cell.js.map +1 -0
  64. package/dist/data-table/cells/heatmap-cell.js +13 -0
  65. package/dist/data-table/cells/heatmap-cell.js.map +1 -0
  66. package/dist/data-table/cells/markdown-cell.js +13 -0
  67. package/dist/data-table/cells/markdown-cell.js.map +1 -0
  68. package/dist/data-table/cells/sparkline-cell.js +13 -0
  69. package/dist/data-table/cells/sparkline-cell.js.map +1 -0
  70. package/dist/data-table/column-meta.js +12 -0
  71. package/dist/data-table/column-meta.js.map +1 -0
  72. package/dist/data-table/data-table.js +23 -0
  73. package/dist/data-table/data-table.js.map +1 -0
  74. package/dist/data-table/index.js +23 -0
  75. package/dist/data-table/index.js.map +1 -0
  76. package/dist/data-table/ranks-column.js +13 -0
  77. package/dist/data-table/ranks-column.js.map +1 -0
  78. package/dist/data-table/sticky-rows.js +10 -0
  79. package/dist/data-table/sticky-rows.js.map +1 -0
  80. package/dist/data-table/use-table-breakpoint.js +13 -0
  81. package/dist/data-table/use-table-breakpoint.js.map +1 -0
  82. package/dist/facet-filter/facet-filter.js +8 -0
  83. package/dist/facet-filter/facet-filter.js.map +1 -0
  84. package/dist/facet-filter/index.js +9 -0
  85. package/dist/facet-filter/index.js.map +1 -0
  86. package/dist/filter-bar/filter-bar.js +8 -0
  87. package/dist/filter-bar/filter-bar.js.map +1 -0
  88. package/dist/filter-bar/filter-chip.js +9 -0
  89. package/dist/filter-bar/filter-chip.js.map +1 -0
  90. package/dist/filter-bar/index.js +13 -0
  91. package/dist/filter-bar/index.js.map +1 -0
  92. package/dist/index.d.ts +547 -8
  93. package/dist/index.js +102 -1512
  94. package/dist/index.js.map +1 -1
  95. package/dist/search-input/index.js +9 -0
  96. package/dist/search-input/index.js.map +1 -0
  97. package/dist/search-input/search-input.js +9 -0
  98. package/dist/search-input/search-input.js.map +1 -0
  99. package/dist/to-csv.js +10 -0
  100. package/dist/to-csv.js.map +1 -0
  101. package/package.json +7 -7
  102. package/src/data-table/card-layout.test.tsx +26 -0
  103. package/src/data-table/card-layout.tsx +74 -0
  104. package/src/data-table/cell-scales.test.ts +118 -0
  105. package/src/data-table/cell-scales.ts +246 -0
  106. package/src/data-table/cells/bar-cell.test.tsx +158 -0
  107. package/src/data-table/cells/bar-cell.tsx +135 -0
  108. package/src/data-table/cells/category-legend.test.tsx +37 -0
  109. package/src/data-table/cells/category-legend.tsx +58 -0
  110. package/src/data-table/cells/columns-cell.test.tsx +27 -0
  111. package/src/data-table/cells/columns-cell.tsx +79 -0
  112. package/src/data-table/cells/heatmap-cell.test.tsx +31 -0
  113. package/src/data-table/cells/heatmap-cell.tsx +116 -0
  114. package/src/data-table/cells/markdown-cell.test.tsx +36 -0
  115. package/src/data-table/cells/markdown-cell.tsx +91 -0
  116. package/src/data-table/cells/sparkline-cell.test.tsx +43 -0
  117. package/src/data-table/cells/sparkline-cell.tsx +113 -0
  118. package/src/data-table/column-meta.test.ts +42 -0
  119. package/src/data-table/column-meta.ts +282 -0
  120. package/src/data-table/data-table.stories.tsx +733 -1
  121. package/src/data-table/data-table.test.tsx +486 -0
  122. package/src/data-table/data-table.tsx +965 -165
  123. package/src/data-table/ranks-column.test.tsx +65 -0
  124. package/src/data-table/ranks-column.tsx +91 -0
  125. package/src/data-table/sticky-rows.test.ts +16 -0
  126. package/src/data-table/sticky-rows.ts +77 -0
  127. package/src/data-table/use-table-breakpoint.test.ts +14 -0
  128. package/src/data-table/use-table-breakpoint.ts +67 -0
  129. package/src/filter-bar/filter-chip.stories.tsx +1 -0
  130. package/src/index.ts +64 -0
  131. package/src/templates-data-app.stories.tsx +7 -2
package/dist/index.js CHANGED
@@ -1,1531 +1,121 @@
1
1
  "use client";
2
-
3
- // src/data-table/data-table.tsx
4
2
  import {
5
- forwardRef,
6
- useCallback,
7
- useEffect,
8
- useMemo,
9
- useRef,
10
- useState
11
- } from "react";
3
+ downloadCsv,
4
+ toCsv
5
+ } from "./chunk-ZQXJ34VJ.js";
6
+ import "./chunk-3UFWYVDS.js";
7
+ import {
8
+ FacetFilter
9
+ } from "./chunk-ZFEW36RG.js";
10
+ import "./chunk-3OVN7MMO.js";
11
+ import {
12
+ FilterBar
13
+ } from "./chunk-677ORWI6.js";
14
+ import {
15
+ FilterChip
16
+ } from "./chunk-OASM53LL.js";
17
+ import "./chunk-7ZYUY3UL.js";
18
+ import {
19
+ SearchInput
20
+ } from "./chunk-E3V3SYY7.js";
21
+ import "./chunk-RZ5MYZOA.js";
22
+ import {
23
+ DataTableWithRef,
24
+ createSelectionColumn
25
+ } from "./chunk-K2GHSRCP.js";
26
+ import {
27
+ DATA_TABLE_BREAKPOINT_THRESHOLDS,
28
+ tableBreakpointForWidth,
29
+ useTableBreakpoint
30
+ } from "./chunk-BEYHV3ZX.js";
12
31
  import {
13
- flexRender,
14
- getCoreRowModel,
15
- getFilteredRowModel,
16
- getPaginationRowModel,
17
- getSortedRowModel,
18
- useReactTable
19
- } from "@tanstack/react-table";
20
- import { useVirtualizer } from "@tanstack/react-virtual";
32
+ HeatmapCell,
33
+ HeatmapLegend,
34
+ heatmapCellStyle
35
+ } from "./chunk-NKVJUQYW.js";
21
36
  import {
22
- DndContext,
23
- KeyboardSensor,
24
- PointerSensor,
25
- closestCenter,
26
- useSensor,
27
- useSensors
28
- } from "@dnd-kit/core";
37
+ MarkdownCell,
38
+ isSafeCellUrl,
39
+ parseCellMarkdown
40
+ } from "./chunk-MV2YVXEZ.js";
29
41
  import {
30
- SortableContext,
31
- sortableKeyboardCoordinates,
32
- useSortable,
33
- verticalListSortingStrategy
34
- } from "@dnd-kit/sortable";
35
- import { CSS } from "@dnd-kit/utilities";
36
- import { ArrowDown, ArrowUp, ArrowUpDown, GripVertical } from "lucide-react";
37
- import { Button, Checkbox, Skeleton, Spinner, useLocale } from "@elabs-ai/components-ui";
38
- import { cn } from "@elabs-ai/components-ui/lib/cn";
39
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
40
- function numericColumnClasses(meta) {
41
- if (!meta?.numeric && !meta?.align) return void 0;
42
- const alignClass = meta?.align === "start" ? "text-start" : meta?.align === "center" ? "text-center" : meta?.align === "end" ? "text-end" : meta?.numeric ? "text-end" : void 0;
43
- return cn(alignClass, meta?.numeric && "tabular-nums");
44
- }
45
- var ROW_CLICK_GUARD_SELECTOR = 'button, a[href], input, select, textarea, label, summary, [role="button"], [role="link"], [role="menuitem"], [role="checkbox"], [role="radio"], [role="switch"], [role="tab"], [contenteditable="true"]';
46
- function isInteractiveEventTarget(target) {
47
- return target instanceof Element && target.closest(ROW_CLICK_GUARD_SELECTOR) !== null;
48
- }
49
- function isActiveTextSelection() {
50
- if (typeof window === "undefined" || typeof window.getSelection !== "function") return false;
51
- return window.getSelection()?.type === "Range";
52
- }
53
- var PINNED_SEAM_CLASS = "after:pointer-events-none after:absolute after:inset-y-0 after:w-px after:bg-border-strong after:content-['']";
54
- var COLUMN_DIVIDER_CLASS = "border-e border-rule last:border-e-0";
55
- function unsizedColumnIds(defs) {
56
- const out = /* @__PURE__ */ new Set();
57
- const walk = (list) => {
58
- for (const def of list) {
59
- const group = def;
60
- if (group.columns) {
61
- walk(group.columns);
62
- continue;
63
- }
64
- if (def.size !== void 0) continue;
65
- const accessorKey = def.accessorKey;
66
- const id = def.id ?? (accessorKey !== void 0 ? String(accessorKey).replace(/\./gu, "_") : typeof def.header === "string" ? def.header : void 0);
67
- if (id) out.add(id);
68
- }
69
- };
70
- walk(defs);
71
- return out;
72
- }
73
- function resizeWidthStyle(size) {
74
- return { width: size, minWidth: size, maxWidth: size };
75
- }
76
- function firstDataCellValue(row) {
77
- for (const cell of row.getVisibleCells()) {
78
- if (!cell.column.accessorFn) continue;
79
- const value = cell.getValue();
80
- if (typeof value === "string" && value.trim() !== "") return value;
81
- if (typeof value === "number") return String(value);
82
- }
83
- return void 0;
84
- }
85
- function SelectAllHeaderCell({ table }) {
86
- const { t } = useLocale();
87
- const allSelected = table.getIsAllPageRowsSelected();
88
- const someSelected = table.getIsSomePageRowsSelected();
89
- return /* @__PURE__ */ jsx(
90
- Checkbox,
91
- {
92
- "data-slot": "data-table-select-all",
93
- checked: allSelected ? true : someSelected ? "indeterminate" : false,
94
- onCheckedChange: (checked) => table.toggleAllPageRowsSelected(checked === true),
95
- "aria-label": t("data.table.selectAllRows")
96
- }
97
- );
98
- }
99
- function SelectRowCell({ row }) {
100
- const { t } = useLocale();
101
- const name = firstDataCellValue(row);
102
- return /* @__PURE__ */ jsx(
103
- Checkbox,
104
- {
105
- "data-slot": "data-table-select-cell",
106
- checked: row.getIsSelected(),
107
- disabled: !row.getCanSelect(),
108
- onCheckedChange: (checked) => row.toggleSelected(checked === true),
109
- "aria-label": name ? t("data.table.selectRowNamed", { name }) : t("data.table.selectRow")
110
- }
111
- );
112
- }
113
- function createSelectionColumn() {
114
- return {
115
- id: "select",
116
- size: 40,
117
- enableSorting: false,
118
- enableHiding: false,
119
- header: ({ table }) => (
120
- // #11 C1: `toggleAllPageRowsSelected` wipes-then-sets on every row when
121
- // `enableMultiRowSelection` is off (TanStack's `mutateRowIsSelected`), so
122
- // a select-all header under single-select leaves only the LAST row
123
- // selected and pins the header at indeterminate forever. Suppress it.
124
- table.options.enableMultiRowSelection === false ? null : /* @__PURE__ */ jsx(SelectAllHeaderCell, { table })
125
- ),
126
- cell: ({ row }) => /* @__PURE__ */ jsx(SelectRowCell, { row })
127
- };
128
- }
129
- function SortableDataRow({
130
- id,
131
- disabled,
132
- attributesOverride,
133
- children
134
- }) {
135
- const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, isDragging } = useSortable({ id, disabled, transition: null, attributes: attributesOverride });
136
- return /* @__PURE__ */ jsx(Fragment, { children: children({
137
- setNodeRef,
138
- setActivatorNodeRef,
139
- attributes,
140
- listeners,
141
- isDragging,
142
- style: { transform: CSS.Transform.toString(transform) }
143
- }) });
144
- }
145
- function DataTableInner({
146
- columns,
147
- data,
148
- toolbar,
149
- enablePagination = false,
150
- pageSize = 10,
151
- hidePaginationWhenSingle = true,
152
- // Global filter
153
- globalFilter: globalFilterProp,
154
- onGlobalFilterChange,
155
- // Controlled slices
156
- sorting: sortingProp,
157
- onSortingChange: onSortingChangeProp,
158
- columnVisibility: columnVisibilityProp,
159
- onColumnVisibilityChange: onColumnVisibilityChangeProp,
160
- columnFilters: columnFiltersProp,
161
- onColumnFiltersChange: onColumnFiltersChangeProp,
162
- pagination: paginationProp,
163
- onPaginationChange: onPaginationChangeProp,
164
- columnPinning: columnPinningProp,
165
- onColumnPinningChange: onColumnPinningChangeProp,
166
- enableColumnResizing = false,
167
- columnResizeMode = "onChange",
168
- columnSizing: columnSizingProp,
169
- onColumnSizingChange: onColumnSizingChangeProp,
170
- rowSelection: rowSelectionProp,
171
- onRowSelectionChange: onRowSelectionChangeProp,
172
- enableRowSelection,
173
- enableMultiRowSelection,
174
- getRowId,
175
- // Saved views rehydration
176
- initialView,
177
- // Server-side model
178
- manualSorting = false,
179
- manualFiltering = false,
180
- manualPagination = false,
181
- rowCount,
182
- pageCount,
183
- onServerChange,
184
- // Loading
185
- loading = false,
186
- loadingRows,
187
- // Virtualization
188
- enableRowVirtualization = false,
189
- estimateRowHeight = 40,
190
- overscan = 8,
191
- maxBodyHeight = "32rem",
192
- zebra = true,
193
- columnDividers = false,
194
- // Row drag-reorder (#13)
195
- enableRowReorder = false,
196
- onRowReorder,
197
- rowReorderHandle = "cell",
198
- onRowClick,
199
- rowActionLabel,
200
- rowClassName,
201
- caption,
202
- emptyMessage = "No results.",
203
- className,
204
- ...rest
205
- }, ref) {
206
- const { t, dir, formatNumber } = useLocale();
207
- const isSortingControlled = sortingProp !== void 0;
208
- const isColumnVisibilityControlled = columnVisibilityProp !== void 0;
209
- const isColumnFiltersControlled = columnFiltersProp !== void 0;
210
- const isPaginationControlled = paginationProp !== void 0;
211
- const isFilterControlled = globalFilterProp !== void 0;
212
- const isColumnPinningControlled = columnPinningProp !== void 0;
213
- const isColumnSizingControlled = columnSizingProp !== void 0;
214
- const isRowSelectionControlled = rowSelectionProp !== void 0;
215
- const [internalSorting, setInternalSorting] = useState(
216
- () => initialView?.sorting ?? []
217
- );
218
- const [internalColumnVisibility, setInternalColumnVisibility] = useState(
219
- () => initialView?.columnVisibility ?? {}
220
- );
221
- const [internalColumnFilters, setInternalColumnFilters] = useState(
222
- () => initialView?.columnFilters ?? []
223
- );
224
- const [internalPagination, setInternalPagination] = useState(
225
- () => initialView?.pagination ?? {
226
- pageIndex: 0,
227
- pageSize
228
- }
229
- );
230
- const [internalGlobalFilter, setInternalGlobalFilter] = useState(
231
- () => initialView?.globalFilter ?? ""
232
- );
233
- const [internalColumnPinning, setInternalColumnPinning] = useState(
234
- () => initialView?.columnPinning ?? { left: [], right: [] }
235
- );
236
- const [internalColumnSizing, setInternalColumnSizing] = useState(
237
- () => initialView?.columnSizing ?? {}
238
- );
239
- const [internalRowSelection, setInternalRowSelection] = useState(
240
- () => initialView?.rowSelection ?? {}
241
- );
242
- const sorting = isSortingControlled ? sortingProp : internalSorting;
243
- const columnVisibility = isColumnVisibilityControlled ? columnVisibilityProp : internalColumnVisibility;
244
- const columnFilters = isColumnFiltersControlled ? columnFiltersProp : internalColumnFilters;
245
- const pagination = isPaginationControlled ? paginationProp : internalPagination;
246
- const globalFilter = isFilterControlled ? globalFilterProp : internalGlobalFilter;
247
- const columnPinning = isColumnPinningControlled ? columnPinningProp : internalColumnPinning;
248
- const columnSizing = isColumnSizingControlled ? columnSizingProp : internalColumnSizing;
249
- const rowSelection = isRowSelectionControlled ? rowSelectionProp : internalRowSelection;
250
- const sortingRef = useRef(sorting);
251
- sortingRef.current = sorting;
252
- const columnFiltersRef = useRef(columnFilters);
253
- columnFiltersRef.current = columnFilters;
254
- const paginationRef = useRef(pagination);
255
- paginationRef.current = pagination;
256
- const globalFilterRef = useRef(globalFilter);
257
- globalFilterRef.current = globalFilter;
258
- const columnVisibilityRef = useRef(columnVisibility);
259
- columnVisibilityRef.current = columnVisibility;
260
- const columnPinningRef = useRef(columnPinning);
261
- columnPinningRef.current = columnPinning;
262
- const columnSizingRef = useRef(columnSizing);
263
- columnSizingRef.current = columnSizing;
264
- const rowSelectionRef = useRef(rowSelection);
265
- rowSelectionRef.current = rowSelection;
266
- const warnedMissingRowCountRef = useRef(false);
267
- useEffect(() => {
268
- if (process.env.NODE_ENV !== "production" && manualPagination && rowCount === void 0 && pageCount === void 0 && !warnedMissingRowCountRef.current) {
269
- warnedMissingRowCountRef.current = true;
270
- console.warn(
271
- '[DataTable] `manualPagination` is true but neither `rowCount` nor `pageCount` was provided \u2014 the pager will appear stuck ("Page 1 of 1", Next disabled). Pass `rowCount` (or `pageCount`) so the pager can compute the total.'
272
- );
273
- }
274
- }, [manualPagination, rowCount, pageCount]);
275
- const warnedManualSelectionRef = useRef(false);
276
- useEffect(() => {
277
- if (process.env.NODE_ENV !== "production" && manualPagination && getRowId === void 0 && (isRowSelectionControlled || onRowSelectionChangeProp !== void 0) && !warnedManualSelectionRef.current) {
278
- warnedManualSelectionRef.current = true;
279
- console.warn(
280
- '[DataTable] `rowSelection` is wired up under `manualPagination` with no `getRowId` \u2014 each page is a fresh `data` array, so the default index-based id restarts at "0" per page and a selection made on one page can silently apply to a different record on the next. Pass `getRowId` so selection is keyed to a stable identity instead of position.'
281
- );
282
- }
283
- }, [manualPagination, getRowId, isRowSelectionControlled, onRowSelectionChangeProp]);
284
- const warnedReorderSortingRef = useRef(false);
285
- useEffect(() => {
286
- if (process.env.NODE_ENV !== "production" && enableRowReorder && sorting.length > 0 && !warnedReorderSortingRef.current) {
287
- warnedReorderSortingRef.current = true;
288
- console.warn(
289
- "[DataTable] `enableRowReorder` is set while a column is sorted \u2014 the sort will keep re-ordering rows out from under a manual drag. Clear `sorting` (or avoid enabling both at once) so a drag's new order stays stable."
290
- );
291
- }
292
- }, [enableRowReorder, sorting.length]);
293
- const warnedReorderVirtualizedRef = useRef(false);
294
- useEffect(() => {
295
- if (process.env.NODE_ENV !== "production" && enableRowReorder && enableRowVirtualization && !warnedReorderVirtualizedRef.current) {
296
- warnedReorderVirtualizedRef.current = true;
297
- console.warn(
298
- "[DataTable] `enableRowReorder` has no effect while `enableRowVirtualization` is set \u2014 the two are mutually exclusive. Virtualization wins; row reorder is disabled."
299
- );
300
- }
301
- }, [enableRowReorder, enableRowVirtualization]);
302
- const rowReorderActive = enableRowReorder && !enableRowVirtualization;
303
- const hasGripColumn = rowReorderActive && rowReorderHandle === "cell";
304
- const reorderSensors = useSensors(
305
- useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),
306
- useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
307
- );
308
- const reorderIdentityMapRef = useRef(/* @__PURE__ */ new WeakMap());
309
- const reorderIdentityCounterRef = useRef(0);
310
- const reorderRepeatedPositions = useMemo(() => {
311
- const repeats = /* @__PURE__ */ new Set();
312
- if (!rowReorderActive) return repeats;
313
- const seen = /* @__PURE__ */ new Set();
314
- data.forEach((record, index) => {
315
- if (record === null || typeof record !== "object") return;
316
- if (seen.has(record)) repeats.add(index);
317
- else seen.add(record);
318
- });
319
- return repeats;
320
- }, [data, rowReorderActive]);
321
- const [reorderLiveMessage, setReorderLiveMessage] = useState("");
322
- const reorderLastAnnouncedPositionRef = useRef(null);
323
- function fireServerChange(overrides = {}) {
324
- if (!onServerChange) return;
325
- onServerChange({
326
- pagination: paginationRef.current,
327
- sorting: sortingRef.current,
328
- columnFilters: columnFiltersRef.current,
329
- globalFilter: globalFilterRef.current,
330
- ...overrides
331
- });
332
- }
333
- function resolveSorting(updater) {
334
- return typeof updater === "function" ? updater(sortingRef.current) : updater;
335
- }
336
- function resolveColumnVisibility(updater) {
337
- return typeof updater === "function" ? updater(columnVisibilityRef.current) : updater;
338
- }
339
- function resolveColumnFilters(updater) {
340
- return typeof updater === "function" ? updater(columnFiltersRef.current) : updater;
341
- }
342
- function resolvePagination(updater) {
343
- return typeof updater === "function" ? updater(paginationRef.current) : updater;
344
- }
345
- function resolveGlobalFilter(updater) {
346
- return typeof updater === "function" ? updater(globalFilterRef.current) : updater;
347
- }
348
- function resolveColumnPinning(updater) {
349
- return typeof updater === "function" ? updater(columnPinningRef.current) : updater;
350
- }
351
- function resolveColumnSizing(updater) {
352
- return typeof updater === "function" ? updater(columnSizingRef.current) : updater;
353
- }
354
- function resolveRowSelection(updater) {
355
- return typeof updater === "function" ? updater(rowSelectionRef.current) : updater;
356
- }
357
- const sortedRowModel = manualSorting ? {} : { getSortedRowModel: getSortedRowModel() };
358
- const filteredRowModel = manualFiltering ? {} : { getFilteredRowModel: getFilteredRowModel() };
359
- const paginationRowModel = enablePagination && !manualPagination ? { getPaginationRowModel: getPaginationRowModel() } : {};
360
- const table = useReactTable({
361
- data,
362
- columns,
363
- state: {
364
- sorting,
365
- columnVisibility,
366
- columnFilters,
367
- globalFilter,
368
- pagination,
369
- columnPinning,
370
- columnSizing,
371
- rowSelection
372
- },
373
- // Sorting
374
- onSortingChange: (updater) => {
375
- const next = resolveSorting(updater);
376
- if (!isSortingControlled) setInternalSorting(next);
377
- onSortingChangeProp?.(updater);
378
- if (manualSorting) {
379
- sortingRef.current = next;
380
- fireServerChange({ sorting: next });
381
- }
382
- },
383
- // Column visibility
384
- onColumnVisibilityChange: (updater) => {
385
- const next = resolveColumnVisibility(updater);
386
- if (!isColumnVisibilityControlled) setInternalColumnVisibility(next);
387
- onColumnVisibilityChangeProp?.(updater);
388
- },
389
- // Column filters
390
- onColumnFiltersChange: (updater) => {
391
- const next = resolveColumnFilters(updater);
392
- if (!isColumnFiltersControlled) setInternalColumnFilters(next);
393
- onColumnFiltersChangeProp?.(updater);
394
- if (manualFiltering) {
395
- columnFiltersRef.current = next;
396
- fireServerChange({ columnFilters: next });
397
- }
398
- },
399
- // Global filter
400
- onGlobalFilterChange: (updater) => {
401
- const next = resolveGlobalFilter(updater);
402
- if (!isFilterControlled) setInternalGlobalFilter(next);
403
- onGlobalFilterChange?.(next);
404
- if (manualFiltering) {
405
- globalFilterRef.current = next;
406
- fireServerChange({ globalFilter: next });
407
- }
408
- },
409
- // Pagination
410
- onPaginationChange: (updater) => {
411
- const next = resolvePagination(updater);
412
- if (!isPaginationControlled) setInternalPagination(next);
413
- onPaginationChangeProp?.(updater);
414
- if (manualPagination) {
415
- paginationRef.current = next;
416
- fireServerChange({ pagination: next });
417
- }
418
- },
419
- // Column pinning — a LAYOUT slice, so unlike sorting/filtering/pagination it
420
- // never fires `onServerChange`: freezing a column changes nothing the server
421
- // would need to re-query.
422
- onColumnPinningChange: (updater) => {
423
- const next = resolveColumnPinning(updater);
424
- if (!isColumnPinningControlled) setInternalColumnPinning(next);
425
- onColumnPinningChangeProp?.(updater);
426
- },
427
- // Column resizing (#12) — a LAYOUT slice, like column pinning: a column's
428
- // width changes nothing the server would need to re-query, so this never
429
- // fires onServerChange either. Routed through by BOTH the pointer path
430
- // (TanStack's own `header.getResizeHandler()`, wired below) and the
431
- // keyboard path (`handleResizeKeyDown`, via `table.setColumnSizing`) so
432
- // the two input modes can never diverge in controlled/uncontrolled
433
- // behaviour.
434
- columnResizeMode,
435
- // RTL fix (#12 review, P1): TanStack's pointer-drag math hardcodes LTR
436
- // unless told otherwise — `deltaDirection = columnResizeDirection ===
437
- // 'rtl' ? -1 : 1` internally — so under `dir="rtl"` (the resize handle's
438
- // own edge already flips via `end-0`, see the `useLocale()` call above)
439
- // dragging would otherwise move the column's width opposite the visible
440
- // boundary. `handleResizeKeyDown` below mirrors this for the keyboard path.
441
- columnResizeDirection: dir,
442
- enableColumnResizing,
443
- onColumnSizingChange: (updater) => {
444
- const next = resolveColumnSizing(updater);
445
- if (!isColumnSizingControlled) setInternalColumnSizing(next);
446
- onColumnSizingChangeProp?.(updater);
447
- },
448
- // Row selection (#11) — also a LAYOUT/UI slice, so it never fires
449
- // onServerChange: which rows are checked changes nothing the server
450
- // would need to re-query.
451
- onRowSelectionChange: (updater) => {
452
- const next = resolveRowSelection(updater);
453
- if (!isRowSelectionControlled) setInternalRowSelection(next);
454
- onRowSelectionChangeProp?.(updater);
455
- },
456
- enableRowSelection,
457
- enableMultiRowSelection,
458
- getRowId,
459
- getCoreRowModel: getCoreRowModel(),
460
- ...sortedRowModel,
461
- ...filteredRowModel,
462
- ...paginationRowModel,
463
- // Server-side options
464
- manualSorting,
465
- manualFiltering,
466
- manualPagination,
467
- ...rowCount !== void 0 ? { rowCount } : {},
468
- ...pageCount !== void 0 ? { pageCount } : {}
469
- // No `initialState`: every slice is driven explicitly via `state` above
470
- // (internal slices are seeded from `initialView` at useState init), so a
471
- // TanStack `initialState` would be dead/misleading.
472
- });
473
- const rows = table.getRowModel().rows;
474
- const colCount = table.getVisibleLeafColumns().length;
475
- const headerRowCount = table.getHeaderGroups().length;
476
- const ariaRowCount = (rowCount ?? rows.length) + headerRowCount;
477
- function reorderRowName(id) {
478
- const row = rows.find((r) => getReorderRowId(r) === id);
479
- return row ? rowActionName(row) : id;
480
- }
481
- function reorderPosition(id) {
482
- return rows.findIndex((r) => getReorderRowId(r) === id) + 1;
483
- }
484
- function getReorderRowId(row) {
485
- if (getRowId) return row.id;
486
- const original = row.original;
487
- if (original !== null && typeof original === "object") {
488
- const map = reorderIdentityMapRef.current;
489
- let id = map.get(original);
490
- if (id === void 0) {
491
- id = `__reorder-${reorderIdentityCounterRef.current++}`;
492
- map.set(original, id);
493
- }
494
- return reorderRepeatedPositions.has(row.index) ? `${id}__${row.index}` : id;
495
- }
496
- return row.id;
497
- }
498
- const silentDragAnnouncements = {
499
- onDragStart: () => void 0,
500
- onDragOver: () => void 0,
501
- onDragEnd: () => void 0,
502
- onDragCancel: () => void 0
503
- };
504
- function handleRowDragStart(event) {
505
- const activeRowId = String(event.active.id);
506
- reorderLastAnnouncedPositionRef.current = reorderPosition(activeRowId);
507
- setReorderLiveMessage(t("data.table.reorderPickedUp", { name: reorderRowName(activeRowId) }));
508
- }
509
- function handleRowDragOver(event) {
510
- const { active, over } = event;
511
- if (!over) return;
512
- const position = reorderPosition(String(over.id));
513
- if (position === reorderLastAnnouncedPositionRef.current) return;
514
- reorderLastAnnouncedPositionRef.current = position;
515
- setReorderLiveMessage(
516
- t("data.table.reorderMoved", {
517
- name: reorderRowName(String(active.id)),
518
- position,
519
- total: rows.length
520
- })
521
- );
522
- }
523
- function handleRowDragCancel(event) {
524
- const activeRowId = String(event.active.id);
525
- setReorderLiveMessage(
526
- t("data.table.reorderCancelled", {
527
- name: reorderRowName(activeRowId),
528
- position: reorderPosition(activeRowId),
529
- total: rows.length
530
- })
531
- );
532
- reorderLastAnnouncedPositionRef.current = null;
533
- }
534
- function handleRowDragEnd(event) {
535
- const { active, over } = event;
536
- const activeRowId = String(active.id);
537
- setReorderLiveMessage(
538
- t("data.table.reorderDropped", {
539
- name: reorderRowName(activeRowId),
540
- position: reorderPosition(String(over ? over.id : active.id)),
541
- total: rows.length
542
- })
543
- );
544
- reorderLastAnnouncedPositionRef.current = null;
545
- if (!over || active.id === over.id) return;
546
- const movedRow = rows.find((r) => getReorderRowId(r) === activeRowId);
547
- const targetRow = rows.find((r) => getReorderRowId(r) === String(over.id));
548
- if (!movedRow || !targetRow) return;
549
- const from = movedRow.index;
550
- const to = targetRow.index;
551
- if (from < 0 || from >= data.length || to < 0 || to >= data.length) return;
552
- onRowReorder?.(from, to, movedRow.original);
553
- }
554
- const hasLeftPinned = (columnPinning.left?.length ?? 0) > 0;
555
- const hasRightPinned = (columnPinning.right?.length ?? 0) > 0;
556
- const pinnedScrollPadding = {
557
- ...hasLeftPinned ? { scrollPaddingInlineStart: table.getLeftTotalSize() } : {},
558
- ...hasRightPinned ? { scrollPaddingInlineEnd: table.getRightTotalSize() } : {}
559
- };
560
- const warnedUnsizedPinnedRef = useRef(false);
561
- const pinnedIds = [...columnPinning.left ?? [], ...columnPinning.right ?? []];
562
- const unsizedIds = process.env.NODE_ENV === "production" || pinnedIds.length === 0 ? null : unsizedColumnIds(columns);
563
- const pinnedWithoutSizeKey = unsizedIds ? pinnedIds.filter((id) => unsizedIds.has(id)).join(",") : "";
564
- useEffect(() => {
565
- if (process.env.NODE_ENV !== "production" && pinnedWithoutSizeKey !== "" && !warnedUnsizedPinnedRef.current) {
566
- warnedUnsizedPinnedRef.current = true;
567
- console.warn(
568
- `[DataTable] Pinned column(s) without an explicit \`size\` in their \`ColumnDef\`: ${pinnedWithoutSizeKey}. Sticky offsets are computed from the declared sizes, so an auto-width pinned column will render at a width that doesn't match its own offset. Give every pinned column a \`size\`.`
569
- );
570
- }
571
- }, [pinnedWithoutSizeKey]);
572
- function pinnedCellGeometry(column) {
573
- const pinned = column.getIsPinned();
574
- if (pinned === false) return null;
575
- const size = column.getSize();
576
- const style = {
577
- width: size,
578
- minWidth: size,
579
- maxWidth: size,
580
- ...pinned === "left" ? { left: column.getStart("left") } : { right: column.getAfter("right") }
581
- };
582
- return {
583
- pinned,
584
- style,
585
- // The seam between the frozen block and the scrolling block is the SOLE
586
- // structural cue between two regions that share one row fill and one
587
- // zebra stripe — delete it and a sighted user cannot tell them apart — so
588
- // it takes the strong rung (ADR 0010 decision test). No shadow: ADR 0020's
589
- // `--shadow-strength: 0` (`data-decoration="8|9|10"`) would
590
- // erase a shadow-only cue entirely.
591
- //
592
- // It is drawn as a 1px `::after` INSIDE the cell, NOT as `border-e` /
593
- // `border-s`. A real border cannot work here: Tailwind's Preflight puts
594
- // the table in the COLLAPSED border model, and a collapsed border is
595
- // painted by the <table> at the cell's STATIC position — it does not
596
- // travel with a `position: sticky` cell, and the cell's own opaque fill
597
- // (which it needs, see `pinnedCellFillClass`) then paints over it. Measured
598
- // in Chromium on `Data/DataTable → PinnedColumns`: with `border-e` the
599
- // seam pixel read `143,143,143` (light `--border-strong`) at
600
- // scrollLeft 0 and `245,245,245` (the plain cell fill — i.e. GONE) once
601
- // scrolled, in every theme and on both edges. So the one cue vanished
602
- // exactly when the freeze was doing something. The `::after` lives in the
603
- // sticky cell's own stacking context, so it moves with it.
604
- edgeClass: pinned === "left" ? column.getIsLastColumn("left") ? PINNED_SEAM_CLASS + " after:end-0" : "" : column.getIsFirstColumn("right") ? PINNED_SEAM_CLASS + " after:start-0" : ""
605
- };
606
- }
607
- const RESIZE_STEP = 10;
608
- const RESIZE_UNBOUNDED_ARIA_MAX = 2e3;
609
- function handleResizeKeyDown(event, column) {
610
- let delta = 0;
611
- if (event.key === "ArrowRight") delta = RESIZE_STEP;
612
- else if (event.key === "ArrowLeft") delta = -RESIZE_STEP;
613
- else return;
614
- event.preventDefault();
615
- if (dir === "rtl") delta = -delta;
616
- const minSize = column.columnDef.minSize ?? 20;
617
- const maxSize = column.columnDef.maxSize ?? Number.MAX_SAFE_INTEGER;
618
- const nextSize = Math.min(maxSize, Math.max(minSize, column.getSize() + delta));
619
- table.setColumnSizing((old) => ({ ...old, [column.id]: nextSize }));
620
- }
621
- function handleResizeDoubleClick(column) {
622
- table.setColumnSizing((old) => {
623
- if (!(column.id in old)) return old;
624
- const { [column.id]: _removed, ...rest2 } = old;
625
- return rest2;
626
- });
627
- }
628
- const scrollRef = useRef(null);
629
- const virtualizer = useVirtualizer({
630
- count: enableRowVirtualization ? rows.length : 0,
631
- getScrollElement: () => enableRowVirtualization ? scrollRef.current : null,
632
- estimateSize: () => estimateRowHeight,
633
- overscan,
634
- enabled: enableRowVirtualization
635
- });
636
- const virtualItems = enableRowVirtualization ? virtualizer.getVirtualItems() : [];
637
- const totalSize = enableRowVirtualization ? virtualizer.getTotalSize() : 0;
638
- const paddingTop = virtualItems.length > 0 ? virtualItems[0]?.start ?? 0 : 0;
639
- const paddingBottom = totalSize > 0 ? totalSize - (virtualItems[virtualItems.length - 1]?.end ?? 0) : 0;
640
- const plainScrollRef = useRef(null);
641
- const [scrollOverflows, setScrollOverflows] = useState(false);
642
- const [canScrollLeft, setCanScrollLeft] = useState(false);
643
- const [canScrollRight, setCanScrollRight] = useState(false);
644
- const updateScrollAffordance = useCallback(() => {
645
- const el = plainScrollRef.current;
646
- if (!el) return;
647
- setScrollOverflows(
648
- el.scrollWidth > el.clientWidth + 1 || el.scrollHeight > el.clientHeight + 1
649
- );
650
- setCanScrollLeft(el.scrollLeft > 0);
651
- setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
652
- }, []);
653
- useEffect(() => {
654
- const el = plainScrollRef.current;
655
- if (!el) return;
656
- updateScrollAffordance();
657
- if (typeof ResizeObserver === "undefined") return;
658
- const observer = new ResizeObserver(updateScrollAffordance);
659
- observer.observe(el);
660
- if (el.firstElementChild) observer.observe(el.firstElementChild);
661
- return () => observer.disconnect();
662
- }, [updateScrollAffordance, colCount, rows.length]);
663
- const showEmpty = !loading && rows.length === 0;
664
- const showSkeletons = loading && rows.length === 0;
665
- const skeletonRowCount = loadingRows ?? pageSize;
666
- function renderThead(sticky, withRowIndex = false) {
667
- return /* @__PURE__ */ jsx(
668
- "thead",
669
- {
670
- className: cn(
671
- // #173: header bottom is the only cue between header and first data row → border-strong
672
- "border-b border-border-strong",
673
- // A sticky header scrolls OVER the body, so its fill must be opaque or data
674
- // rows bleed through the labels; the non-sticky header keeps the /60 wash.
675
- // z-20 (raised from z-10 for #333) puts the header row above the pinned
676
- // body cells (z-10) and below the pinned header corner (z-30). No visual
677
- // delta: nothing else in the table sits between those rungs.
678
- sticky ? "sticky top-0 z-20 bg-surface-muted" : "bg-surface-muted/60"
679
- ),
680
- children: table.getHeaderGroups().map((headerGroup, groupIndex) => /* @__PURE__ */ jsxs("tr", { "aria-rowindex": withRowIndex ? groupIndex + 1 : void 0, children: [
681
- hasGripColumn && /* @__PURE__ */ jsx("th", { scope: "col", className: "h-10 w-10 px-3 align-middle", children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("data.table.reorderColumnHeader") }) }, "__reorder"),
682
- headerGroup.headers.map((header) => {
683
- const geometry = pinnedCellGeometry(header.column);
684
- const canSort = header.column.getCanSort();
685
- const sorted = header.column.getIsSorted();
686
- const headerLabel = typeof header.column.columnDef.header === "string" ? header.column.columnDef.header : header.column.id;
687
- const sortStateLabel = sorted === "asc" ? "ascending" : sorted === "desc" ? "descending" : "not sorted";
688
- const SortIcon = sorted === "asc" ? ArrowUp : sorted === "desc" ? ArrowDown : ArrowUpDown;
689
- const resizeStyle = enableColumnResizing ? resizeWidthStyle(header.getSize()) : void 0;
690
- const canResize = enableColumnResizing && !header.isPlaceholder && header.column.getCanResize();
691
- const resizeMax = header.column.columnDef.maxSize;
692
- return /* @__PURE__ */ jsxs(
693
- "th",
694
- {
695
- scope: "col",
696
- "aria-sort": canSort ? sorted === "asc" ? "ascending" : sorted === "desc" ? "descending" : "none" : void 0,
697
- "data-pinned": geometry?.pinned ?? void 0,
698
- style: geometry?.style ?? resizeStyle,
699
- className: cn(
700
- // Same `px-3` the body `<td>` uses (below) — deliberately
701
- // NOT split into `ps-3`/`pe-3` for a resize-handle
702
- // override (round-1 briefly did this, see the round-2
703
- // note on `numericColumnClasses`): the header's padding
704
- // must stay byte-identical to the body's so an
705
- // end-aligned numeric column's header lines up with its
706
- // own values.
707
- "h-10 px-3 text-start align-middle font-table-header text-muted-foreground",
708
- // #69: a numeric column's `meta` overrides the default
709
- // `text-start` — placed right after the base string so
710
- // tailwind-merge lets it win over that default.
711
- numericColumnClasses(header.column.columnDef.meta),
712
- // `sticky`/pinned already establishes a positioning context
713
- // for the resize handle's `absolute`; an unpinned resizable
714
- // header needs its own.
715
- !geometry && canResize && "relative",
716
- // A pinned HEADER cell is the corner where both freezes meet,
717
- // so it stacks above the sticky header row (z-20) which is
718
- // above the pinned body cells (z-10). It needs an OPAQUE
719
- // fill (scrolled header cells pass underneath it), and that
720
- // fill has to composite to exactly what its unpinned
721
- // neighbours show — same problem, same two-layer answer as
722
- // `pinnedCellFillClass`:
723
- // sticky branch → the row is already opaque `surface-muted`, so match it.
724
- // plain branch → the row is `surface-muted/60` over the
725
- // container's `card`, so paint `card` and
726
- // re-apply the /60 wash on `::before`.
727
- // Painting the plain branch's corner solid `surface-muted`
728
- // read 4-5/255 darker than the header beside it in every
729
- // theme (measured: 242 vs 247 light, 43 vs 40
730
- // dark) — the same "floating pill"
731
- // artefact #333 was filed about, moved into the header.
732
- geometry && "sticky z-30",
733
- geometry && (sticky ? "bg-surface-muted" : "bg-card before:pointer-events-none before:absolute before:inset-0 before:-z-10 before:bg-surface-muted/60 before:content-['']"),
734
- // Separate cn() argument on purpose: the seam is the sole
735
- // structural cue between the frozen and scrolling blocks, so
736
- // it must not read as a "boundary + fill in one class string"
737
- // redundancy (separation:check).
738
- geometry?.edgeClass,
739
- columnDividers && !geometry && COLUMN_DIVIDER_CLASS
740
- ),
741
- children: [
742
- header.isPlaceholder ? null : canSort ? /* @__PURE__ */ jsxs(
743
- "button",
744
- {
745
- type: "button",
746
- onClick: header.column.getToggleSortingHandler(),
747
- "aria-label": `Sort by ${headerLabel}, ${sortStateLabel}`,
748
- className: "relative z-10 inline-flex items-center gap-1 rounded-sm transition-colors duration-fast ease-standard hover:text-foreground focus-ring",
749
- children: [
750
- flexRender(header.column.columnDef.header, header.getContext()),
751
- /* @__PURE__ */ jsx(
752
- SortIcon,
753
- {
754
- "aria-hidden": "true",
755
- className: "size-3 shrink-0 transition-colors duration-fast ease-standard"
756
- }
757
- )
758
- ]
759
- }
760
- ) : flexRender(header.column.columnDef.header, header.getContext()),
761
- canResize && /* @__PURE__ */ jsx(
762
- "div",
763
- {
764
- role: "separator",
765
- "aria-orientation": "vertical",
766
- "aria-valuenow": Math.round(header.getSize()),
767
- "aria-valuemin": header.column.columnDef.minSize,
768
- "aria-valuemax": resizeMax !== void 0 && resizeMax < Number.MAX_SAFE_INTEGER ? resizeMax : Math.max(header.getSize(), RESIZE_UNBOUNDED_ARIA_MAX),
769
- "aria-valuetext": t("data.table.resizeColumnValue", {
770
- count: Math.round(header.getSize()),
771
- size: formatNumber(Math.round(header.getSize()))
772
- }),
773
- "aria-label": t("data.table.resizeColumn", { name: headerLabel }),
774
- tabIndex: 0,
775
- "data-slot": "data-table-resize-handle",
776
- onMouseDown: header.getResizeHandler(),
777
- onTouchStart: header.getResizeHandler(),
778
- onKeyDown: (event) => handleResizeKeyDown(event, header.column),
779
- onDoubleClick: () => handleResizeDoubleClick(header.column),
780
- className: cn(
781
- // #51: the hit box is a literal 24px (clamped to half
782
- // the header cell so it can never overlap a neighbour,
783
- // even at `minSize=20`) rather than the `w-2` Tailwind
784
- // spacing-scale utility. `w-2` compiles to
785
- // `calc(var(--spacing) * 2)`, and `--spacing` is what
786
- // `data-density="compact"` rescales — so the old 8px
787
- // hit box shrank further under compact density
788
- // (~7.1px). A literal px value is density-independent
789
- // by construction, which is the actual defect the
790
- // maintainer's review corrected (NOT `--type-factor`,
791
- // which this handle never used). Do not widen via
792
- // overhang into the neighbouring cell instead — on the
793
- // last column that lands inside the `overflow-auto`
794
- // box (#330 false positive) and a pinned neighbour
795
- // paints over/hit-tests away the extra area.
796
- "absolute inset-y-0 end-0 w-[min(24px,50%)] cursor-col-resize touch-none select-none",
797
- // #51: the focus ring moves to the `after:` pseudo-
798
- // element (the drawn seam) rather than the box itself
799
- // — the box is now a 24px hit target, and a 24px focus
800
- // rectangle would replace the deliberately slim ring
801
- // already reviewed/approved as the #12 a11y fix
802
- // (da9b29e). `focus-visible:after:*` targets the
803
- // pseudo-element the same way `hover:after:w-2` /
804
- // `focus-visible:after:w-2` below already do.
805
- "focus-visible:outline-none",
806
- // a11y fix (#12 review, blocking): this handle is the
807
- // SOLE boundary between two adjacent header cells once
808
- // resizing is on — no fill/elevation change separates
809
- // them otherwise — so per the border/border-strong
810
- // decision test (styling-and-tokens.md) it needs a
811
- // rung that clears WCAG 1.4.11's 3:1 on its OWN, in
812
- // EVERY state, including rest (a control with no
813
- // affordance until hover is unusable without a
814
- // pointer). `border-strong` measures only 2.86-2.96:1
815
- // against this `bg-surface-muted` header — that rung
816
- // is guaranteed only vs `--card`/`--background`, not a
817
- // same-tone surface, which is the exact trap the rule
818
- // warns about. `muted-foreground` is guaranteed AA
819
- // text contrast against `--surface-muted`
820
- // (TEXT_SURFACES), so it clears the 3:1 non-text
821
- // minimum with wide margin (measured ~5.3-6.4:1 in
822
- // both themes, unaffected by density) and is already
823
- // the header's own label color. A slim persistent
824
- // `after:` seam (not just a hover reveal) gives the
825
- // real resting boundary; hover/focus widen the drawn
826
- // seam to 8px (`after:w-2`) using the same compliant
827
- // color — a separate width from the 24px pointer hit
828
- // box below (#51), which the seam does not fill.
829
- // Dragging keeps the pre-existing full-fill
830
- // `bg-primary` treatment — that is a drag AFFORDANCE,
831
- // not a focus indicator, and it is redundant with the
832
- // pointer capture, so it is out of scope here. The
833
- // keyboard focus indicator on both branches is the
834
- // shared compound one (#67), applied to the drawn seam
835
- // via `focus-visible:after:focus-ring-static`: the
836
- // element itself is a 24px transparent hit box, so
837
- // ringing IT would ring nothing a user can see.
838
- header.column.getIsResizing() ? "after:absolute after:inset-y-0 after:end-0 after:w-2 after:bg-primary after:content-[''] focus-visible:after:focus-ring-static" : "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"
839
- )
840
- }
841
- )
842
- ]
843
- },
844
- header.id
845
- );
846
- })
847
- ] }, headerGroup.id))
848
- }
849
- );
850
- }
851
- function rowSeparationClass(rowIndex) {
852
- if (!zebra) return "border-b border-border-strong last:border-b-0";
853
- return cn(
854
- "border-b-(length:--table-row-rule-width) border-border-strong last:border-b-0",
855
- // Separate cn() argument: the stripe and the (theme-gated) rule are
856
- // alternative cues, never both at once — see the jsdoc above.
857
- rowIndex % 2 === 1 && "bg-table-stripe"
858
- );
859
- }
860
- function pinnedCellFillClass(rowIndex) {
861
- return cn(
862
- "bg-card",
863
- "before:pointer-events-none before:absolute before:inset-0 before:-z-10 before:content-['']",
864
- zebra && rowIndex % 2 === 1 && "before:bg-table-stripe",
865
- "group-hover/row:before:bg-table-row-hover",
866
- "group-data-[state=selected]/row:before:bg-accent"
867
- );
868
- }
869
- function rowActionName(row) {
870
- const explicit = rowActionLabel?.(row);
871
- if (explicit) return explicit;
872
- const name = firstDataCellValue(row);
873
- if (name !== void 0) return name;
874
- return t("data.table.rowAction");
875
- }
876
- function renderRow(row, rowIndex, extras, dragHandle) {
877
- const clickable = Boolean(onRowClick);
878
- function handleRowClick(event) {
879
- if (isInteractiveEventTarget(event.target)) return;
880
- if (isActiveTextSelection()) return;
881
- onRowClick?.(row, event);
882
- }
883
- return /* @__PURE__ */ jsxs(
884
- "tr",
885
- {
886
- "data-state": row.getIsSelected() ? "selected" : void 0,
887
- onClick: clickable ? handleRowClick : void 0,
888
- className: cn(
889
- // Color-only feedback (no transform/movement) → per
890
- // docs/MOTION_GUIDELINES.md item 3 this stays under OS reduced-motion
891
- // (only movement is neutralized); the gated duration-fast/ease-standard
892
- // pair already collapses toward ~0ms via --motion-factor when the user
893
- // or OS asks for reduced motion, matching the header sort button.
894
- "transition-colors duration-fast ease-standard hover:bg-table-row-hover data-[state=selected]:bg-accent",
895
- // #13: the dragged row's live `transform` (set inline via `extras.style`,
896
- // see `SortableDataRow`) is what actually MOVES it — this class only
897
- // makes that movement glide instead of snapping, through the gated
898
- // duration/ease utilities (never a raw ms/ease value —
899
- // quality-gates.md "Motion-tokened") with a reduced-motion
900
- // neutralizer. Raising the dragged row's stacking + opacity is a
901
- // colour/composite-only cue, so it isn't gated by the same rule.
902
- dragHandle && "relative transition-transform duration-base ease-standard motion-reduce:transition-none",
903
- dragHandle?.isDragging && "z-20 opacity-90 shadow-md",
904
- // Named group (#333) so a PINNED cell can re-apply the row's hover /
905
- // selected wash on top of its own opaque fill — only CSS knows the
906
- // pointer is over a sibling cell. Purely a selector hook: `group/row`
907
- // emits no style of its own.
908
- "group/row",
909
- rowSeparationClass(rowIndex),
910
- // `<tr>` isn't in the global auto-cursor-pointer role list (button/
911
- // menuitem/tab/…), so a clickable row needs its own cursor. The focus
912
- // ring is driven off the hidden button's `:focus-visible` (same
913
- // `has-[[data-slot=…]:focus-visible]` pattern as InputGroup) so the
914
- // ring paints on the ROW the user is about to activate, even though
915
- // focus lives on the sr-only control inside it.
916
- clickable && "cursor-pointer has-[[data-slot=data-table-row-action]:focus-visible]:focus-ring-static-inset",
917
- rowClassName?.(row)
918
- ),
919
- ...extras,
920
- children: [
921
- dragHandle?.activator && /* @__PURE__ */ jsx("td", { className: "w-10 px-3 py-2 align-middle", children: /* @__PURE__ */ jsx(
922
- "button",
923
- {
924
- type: "button",
925
- ref: dragHandle.activator.setActivatorNodeRef,
926
- "data-slot": "data-table-row-drag-handle",
927
- "aria-label": t("data.table.reorderHandle", { name: rowActionName(row) }),
928
- className: cn(
929
- "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",
930
- dragHandle.isDragging && "text-foreground"
931
- ),
932
- ...dragHandle.activator.attributes,
933
- ...dragHandle.activator.listeners,
934
- children: /* @__PURE__ */ jsx(GripVertical, { "aria-hidden": "true", className: "size-4" })
935
- }
936
- ) }),
937
- row.getVisibleCells().map((cell, cellIndex) => {
938
- const geometry = pinnedCellGeometry(cell.column);
939
- const resizeStyle = enableColumnResizing ? resizeWidthStyle(cell.column.getSize()) : void 0;
940
- return /* @__PURE__ */ jsxs(
941
- "td",
942
- {
943
- "data-pinned": geometry?.pinned ?? void 0,
944
- style: geometry?.style ?? resizeStyle,
945
- className: cn(
946
- "px-3 py-2 align-middle",
947
- // #69: same numeric-column seam as the header — see
948
- // `numericColumnClasses`.
949
- numericColumnClasses(cell.column.columnDef.meta),
950
- // z-10: above the normal (unpositioned) cells it scrolls over,
951
- // below the sticky header row (z-20) and the pinned corner (z-30).
952
- geometry && "sticky z-10",
953
- geometry && pinnedCellFillClass(rowIndex),
954
- // Separate cn() argument — see pinnedCellGeometry's edgeClass.
955
- geometry?.edgeClass,
956
- columnDividers && !geometry && COLUMN_DIVIDER_CLASS
957
- ),
958
- children: [
959
- clickable && cellIndex === 0 && /* @__PURE__ */ jsx(
960
- "button",
961
- {
962
- type: "button",
963
- "data-slot": "data-table-row-action",
964
- className: "sr-only focus-visible:outline-none",
965
- onClick: (event) => onRowClick?.(row, event),
966
- children: rowActionName(row)
967
- }
968
- ),
969
- flexRender(cell.column.columnDef.cell, cell.getContext())
970
- ]
971
- },
972
- cell.id
973
- );
974
- })
975
- ]
976
- },
977
- row.id
978
- );
979
- }
980
- function renderSkeletonBody(count) {
981
- const visibleColumns = table.getVisibleLeafColumns();
982
- return Array.from({ length: count }).map((_, i) => /* @__PURE__ */ jsxs("tr", { "aria-hidden": "true", className: rowSeparationClass(i), children: [
983
- hasGripColumn && /* @__PURE__ */ jsx("td", { className: "w-10 px-3 py-2 align-middle", children: /* @__PURE__ */ jsx(Skeleton, { className: "size-4" }) }),
984
- visibleColumns.map((column) => /* @__PURE__ */ jsx(
985
- "td",
986
- {
987
- className: cn(
988
- "px-3 py-2 align-middle",
989
- numericColumnClasses(column.columnDef.meta),
990
- columnDividers && COLUMN_DIVIDER_CLASS
991
- ),
992
- children: /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full" })
993
- },
994
- column.id
995
- ))
996
- ] }, `skeleton-${i}`));
997
- }
998
- function renderEmptyBody() {
999
- return /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
1000
- "td",
1001
- {
1002
- colSpan: colCount + (hasGripColumn ? 1 : 0),
1003
- className: "h-24 px-3 text-center text-muted-foreground",
1004
- children: emptyMessage
1005
- }
1006
- ) });
1007
- }
1008
- function renderTbodyNormal() {
1009
- if (showSkeletons) {
1010
- return /* @__PURE__ */ jsx("tbody", { children: renderSkeletonBody(skeletonRowCount) });
1011
- }
1012
- if (showEmpty) {
1013
- return /* @__PURE__ */ jsx("tbody", { children: renderEmptyBody() });
1014
- }
1015
- if (!rowReorderActive) {
1016
- return /* @__PURE__ */ jsx("tbody", { children: rows.map((row, i) => renderRow(row, i)) });
1017
- }
1018
- return /* @__PURE__ */ jsx(
1019
- SortableContext,
1020
- {
1021
- items: rows.map((r) => getReorderRowId(r)),
1022
- strategy: verticalListSortingStrategy,
1023
- children: /* @__PURE__ */ jsx("tbody", { children: rows.map((row, i) => /* @__PURE__ */ jsx(
1024
- SortableDataRow,
1025
- {
1026
- id: getReorderRowId(row),
1027
- attributesOverride: {
1028
- // #98: dnd-kit's own `roleDescription: 'sortable'` default is
1029
- // hardcoded English; override it with the localized value in
1030
- // BOTH handle modes — `role` stays row-mode-only (see the
1031
- // `attributesOverride` prop doc above).
1032
- roleDescription: t("data.table.reorderRoleDescription"),
1033
- ...rowReorderHandle === "row" ? { role: "row" } : null
1034
- },
1035
- children: ({ setNodeRef, setActivatorNodeRef, attributes, listeners, isDragging, style }) => renderRow(
1036
- row,
1037
- i,
1038
- {
1039
- ref: setNodeRef,
1040
- style,
1041
- // `aria-pressed` is a `DraggableAttributes` field meant for a
1042
- // real `<button>` activator; spread onto a `<tr role="row">`
1043
- // (row-handle mode) it fails axe's `aria-allowed-attr` (that
1044
- // ARIA state is not permitted on the `row` role), so strip it
1045
- // here rather than exempt it downstream.
1046
- ...rowReorderHandle === "row" ? (() => {
1047
- const { "aria-pressed": _ariaPressed, ...rowAttributes } = attributes;
1048
- return { ...rowAttributes, ...listeners };
1049
- })() : {}
1050
- },
1051
- {
1052
- isDragging,
1053
- activator: rowReorderHandle === "cell" ? { setActivatorNodeRef, attributes, listeners } : void 0
1054
- }
1055
- )
1056
- },
1057
- getReorderRowId(row)
1058
- )) })
1059
- }
1060
- );
1061
- }
1062
- function renderTbodyVirtualized() {
1063
- if (showSkeletons) {
1064
- const virtualSkeletonCount = loadingRows ?? Math.min(10, pageSize);
1065
- return /* @__PURE__ */ jsx("tbody", { children: renderSkeletonBody(virtualSkeletonCount) });
1066
- }
1067
- return /* @__PURE__ */ jsx("tbody", { children: showEmpty ? renderEmptyBody() : /* @__PURE__ */ jsxs(Fragment, { children: [
1068
- paddingTop > 0 && /* @__PURE__ */ jsx("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx("td", { style: { height: paddingTop }, colSpan: colCount }) }),
1069
- virtualItems.map((virtualRow) => {
1070
- const row = rows[virtualRow.index];
1071
- if (!row) return null;
1072
- return renderRow(row, virtualRow.index, {
1073
- ref: virtualizer.measureElement,
1074
- "data-index": virtualRow.index,
1075
- // Absolute 1-based row position; header row(s) occupy 1..headerRowCount.
1076
- "aria-rowindex": headerRowCount + virtualRow.index + 1
1077
- });
1078
- }),
1079
- paddingBottom > 0 && /* @__PURE__ */ jsx("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx("td", { style: { height: paddingBottom }, colSpan: colCount }) })
1080
- ] }) });
1081
- }
1082
- function renderPagination() {
1083
- if (enableRowVirtualization) return null;
1084
- if (!enablePagination && !manualPagination) return null;
1085
- const pageCountUnknown = manualPagination && rowCount === void 0 && pageCount === void 0;
1086
- if (hidePaginationWhenSingle && !pageCountUnknown && table.getPageCount() <= 1) return null;
1087
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1088
- /* @__PURE__ */ jsxs("p", { className: "text-body text-muted-foreground", children: [
1089
- "Page ",
1090
- table.getState().pagination.pageIndex + 1,
1091
- " of ",
1092
- table.getPageCount() || 1
1093
- ] }),
1094
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1095
- /* @__PURE__ */ jsx(
1096
- Button,
1097
- {
1098
- variant: "outline",
1099
- size: "sm",
1100
- onClick: () => table.previousPage(),
1101
- disabled: !table.getCanPreviousPage(),
1102
- children: "Previous"
1103
- }
1104
- ),
1105
- /* @__PURE__ */ jsx(
1106
- Button,
1107
- {
1108
- variant: "outline",
1109
- size: "sm",
1110
- onClick: () => table.nextPage(),
1111
- disabled: !table.getCanNextPage(),
1112
- children: "Next"
1113
- }
1114
- )
1115
- ] })
1116
- ] });
1117
- }
1118
- const captionElement = caption != null ? /* @__PURE__ */ jsx("caption", { className: "sr-only", children: caption }) : null;
1119
- if (enableRowVirtualization) {
1120
- return /* @__PURE__ */ jsxs("div", { ref, className: cn("space-y-3", className), ...rest, children: [
1121
- toolbar ? toolbar(table) : null,
1122
- /* @__PURE__ */ jsxs(
1123
- "div",
1124
- {
1125
- ref: scrollRef,
1126
- tabIndex: 0,
1127
- role: "group",
1128
- "aria-label": t("data.table.scrollRegion"),
1129
- "aria-busy": loading || void 0,
1130
- className: "relative overflow-auto rounded-lg border bg-card focus-ring",
1131
- style: { maxHeight: maxBodyHeight, ...pinnedScrollPadding },
1132
- children: [
1133
- loading && rows.length > 0 && /* @__PURE__ */ jsxs(
1134
- "div",
1135
- {
1136
- role: "status",
1137
- "aria-live": "polite",
1138
- className: "absolute inset-0 z-40 flex items-center justify-center rounded-lg bg-card/80",
1139
- children: [
1140
- /* @__PURE__ */ jsx(Spinner, { "aria-hidden": "true", className: "text-foreground" }),
1141
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("data.table.loading") })
1142
- ]
1143
- }
1144
- ),
1145
- /* @__PURE__ */ jsxs(
1146
- "table",
1147
- {
1148
- "aria-busy": loading || void 0,
1149
- "aria-rowcount": ariaRowCount,
1150
- className: "w-full caption-bottom text-body",
1151
- children: [
1152
- captionElement,
1153
- renderThead(true, true),
1154
- renderTbodyVirtualized()
1155
- ]
1156
- }
1157
- )
1158
- ]
1159
- }
1160
- )
1161
- ] });
1162
- }
1163
- const nonVirtualizedContent = /* @__PURE__ */ jsxs("div", { ref, className: cn("space-y-3", className), ...rest, children: [
1164
- toolbar ? toolbar(table) : null,
1165
- /* @__PURE__ */ jsxs(
1166
- "div",
1167
- {
1168
- "aria-busy": loading || void 0,
1169
- className: "relative overflow-hidden rounded-lg border bg-card",
1170
- children: [
1171
- loading && rows.length > 0 && /* @__PURE__ */ jsxs(
1172
- "div",
1173
- {
1174
- role: "status",
1175
- "aria-live": "polite",
1176
- className: "absolute inset-0 z-40 flex items-center justify-center rounded-lg bg-card/80",
1177
- children: [
1178
- /* @__PURE__ */ jsx(Spinner, { "aria-hidden": "true", className: "text-foreground" }),
1179
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("data.table.loading") })
1180
- ]
1181
- }
1182
- ),
1183
- /* @__PURE__ */ jsx(
1184
- "div",
1185
- {
1186
- ref: plainScrollRef,
1187
- "data-slot": "data-table-scroll-region",
1188
- tabIndex: scrollOverflows ? 0 : void 0,
1189
- role: scrollOverflows ? "group" : void 0,
1190
- "aria-label": scrollOverflows ? t("data.table.scrollRegion") : void 0,
1191
- onScroll: updateScrollAffordance,
1192
- className: "overflow-auto rounded-lg focus-ring-inset",
1193
- style: hasLeftPinned || hasRightPinned ? pinnedScrollPadding : void 0,
1194
- children: /* @__PURE__ */ jsxs("table", { "aria-busy": loading || void 0, className: "w-full caption-bottom text-body", children: [
1195
- captionElement,
1196
- renderThead(false),
1197
- renderTbodyNormal()
1198
- ] })
1199
- }
1200
- ),
1201
- canScrollLeft && !hasLeftPinned && /* @__PURE__ */ jsx(
1202
- "div",
1203
- {
1204
- "aria-hidden": "true",
1205
- "data-slot": "data-table-scroll-fade-left",
1206
- className: "pointer-events-none absolute inset-y-0 left-0 z-10 w-8 rounded-lg bg-gradient-to-r from-card to-transparent"
1207
- }
1208
- ),
1209
- canScrollRight && !hasRightPinned && /* @__PURE__ */ jsx(
1210
- "div",
1211
- {
1212
- "aria-hidden": "true",
1213
- "data-slot": "data-table-scroll-fade-right",
1214
- className: "pointer-events-none absolute inset-y-0 right-0 z-10 w-8 rounded-lg bg-gradient-to-l from-card to-transparent"
1215
- }
1216
- )
1217
- ]
1218
- }
1219
- ),
1220
- renderPagination()
1221
- ] });
1222
- if (!rowReorderActive) return nonVirtualizedContent;
1223
- return /* @__PURE__ */ jsxs(
1224
- DndContext,
1225
- {
1226
- sensors: reorderSensors,
1227
- collisionDetection: closestCenter,
1228
- onDragStart: handleRowDragStart,
1229
- onDragOver: handleRowDragOver,
1230
- onDragEnd: handleRowDragEnd,
1231
- onDragCancel: handleRowDragCancel,
1232
- accessibility: {
1233
- announcements: silentDragAnnouncements,
1234
- // #98: dnd-kit's own hidden keyboard-instructions node is hardcoded
1235
- // English (`defaultScreenReaderInstructions`) unless overridden here.
1236
- screenReaderInstructions: { draggable: t("data.table.reorderInstructions") }
1237
- },
1238
- children: [
1239
- nonVirtualizedContent,
1240
- /* @__PURE__ */ jsx(
1241
- "div",
1242
- {
1243
- role: "status",
1244
- "aria-live": "polite",
1245
- "aria-atomic": "true",
1246
- "data-slot": "data-table-reorder-live-region",
1247
- className: "sr-only",
1248
- children: reorderLiveMessage
1249
- }
1250
- )
1251
- ]
1252
- }
1253
- );
1254
- }
1255
- var DataTableWithRef = forwardRef(DataTableInner);
1256
-
1257
- // src/search-input/search-input.tsx
1258
- import { forwardRef as forwardRef2, useId, useRef as useRef2 } from "react";
1259
- import { Input, useLocale as useLocale2 } from "@elabs-ai/components-ui";
1260
- import { cn as cn2 } from "@elabs-ai/components-ui/lib/cn";
1261
- import { SearchIcon } from "@elabs-ai/components-icons";
1262
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1263
- var SearchInput = forwardRef2(function SearchInput2({
1264
- value,
1265
- onValueChange,
1266
- label,
1267
- placeholder,
1268
- className,
1269
- containerClassName,
1270
- disabled,
1271
- id: idProp,
1272
- ...props
1273
- }, forwardedRef) {
1274
- const { t } = useLocale2();
1275
- const resolvedLabel = label ?? t("data.searchInput.label");
1276
- const resolvedPlaceholder = placeholder ?? t("data.searchInput.placeholder");
1277
- const generatedId = useId();
1278
- const id = idProp ?? generatedId;
1279
- const inputRef = useRef2(null);
1280
- const setRefs = (node) => {
1281
- inputRef.current = node;
1282
- if (typeof forwardedRef === "function") forwardedRef(node);
1283
- else if (forwardedRef) forwardedRef.current = node;
1284
- };
1285
- const handleClear = () => {
1286
- onValueChange("");
1287
- inputRef.current?.focus();
1288
- };
1289
- return /* @__PURE__ */ jsxs2("div", { className: cn2("relative w-full max-w-xs", containerClassName), children: [
1290
- /* @__PURE__ */ jsx2("label", { htmlFor: id, className: "sr-only", children: resolvedLabel }),
1291
- /* @__PURE__ */ jsx2(
1292
- SearchIcon,
1293
- {
1294
- size: 16,
1295
- className: "pointer-events-none absolute start-2.5 top-1/2 -translate-y-1/2 text-muted-foreground"
1296
- }
1297
- ),
1298
- /* @__PURE__ */ jsx2(
1299
- Input,
1300
- {
1301
- ref: setRefs,
1302
- id,
1303
- value,
1304
- onChange: (e) => onValueChange(e.target.value),
1305
- placeholder: resolvedPlaceholder,
1306
- disabled,
1307
- className: cn2("ps-8", value && "pe-8", className),
1308
- ...props
1309
- }
1310
- ),
1311
- value && !disabled ? /* @__PURE__ */ jsx2(
1312
- "button",
1313
- {
1314
- type: "button",
1315
- onClick: handleClear,
1316
- "aria-label": t("data.searchInput.clear"),
1317
- 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",
1318
- children: /* @__PURE__ */ jsx2(
1319
- "svg",
1320
- {
1321
- width: "14",
1322
- height: "14",
1323
- viewBox: "0 0 24 24",
1324
- fill: "none",
1325
- stroke: "currentColor",
1326
- strokeWidth: "2",
1327
- strokeLinecap: "round",
1328
- strokeLinejoin: "round",
1329
- "aria-hidden": "true",
1330
- children: /* @__PURE__ */ jsx2("path", { d: "M18 6 6 18M6 6l12 12" })
1331
- }
1332
- )
1333
- }
1334
- ) : null
1335
- ] });
1336
- });
1337
-
1338
- // src/filter-bar/filter-bar.tsx
1339
- import "react";
1340
- import { cn as cn3 } from "@elabs-ai/components-ui/lib/cn";
1341
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1342
- function FilterBar({ children, actions, className }) {
1343
- return /* @__PURE__ */ jsxs3("div", { className: cn3("flex flex-wrap items-center justify-between gap-2", className), children: [
1344
- /* @__PURE__ */ jsx3("div", { className: "flex flex-wrap items-center gap-2", children }),
1345
- actions ? /* @__PURE__ */ jsx3("div", { className: "flex items-center gap-2", children: actions }) : null
1346
- ] });
1347
- }
1348
-
1349
- // src/filter-bar/filter-chip.tsx
1350
- import { forwardRef as forwardRef3 } from "react";
42
+ SparklineCell
43
+ } from "./chunk-HG2YMSRM.js";
1351
44
  import {
1352
- FilterChip as BaseFilterChip,
1353
- useLocale as useLocale3
1354
- } from "@elabs-ai/components-ui";
1355
- import { jsx as jsx4 } from "react/jsx-runtime";
1356
- var FilterChip = forwardRef3(function FilterChip2({ label, count, countLabel, ...props }, ref) {
1357
- const { formatNumber } = useLocale3();
1358
- const countText = count === void 0 ? void 0 : countLabel ? `${countLabel} ${formatNumber(count)}` : formatNumber(count);
1359
- const { trailing: _ignoredTrailing, ...restProps } = props;
1360
- return /* @__PURE__ */ jsx4(
1361
- BaseFilterChip,
1362
- {
1363
- ref,
1364
- "data-slot": "filter-chip",
1365
- label,
1366
- ...restProps,
1367
- trailing: countText
1368
- }
1369
- );
1370
- });
1371
-
1372
- // src/facet-filter/facet-filter.tsx
1373
- import { forwardRef as forwardRef4 } from "react";
45
+ columnSizeStyle,
46
+ formatCellValue,
47
+ resolveShowAt
48
+ } from "./chunk-TBB33PHQ.js";
1374
49
  import {
1375
- Badge,
1376
- Button as Button2,
1377
- DropdownMenu,
1378
- DropdownMenuContent,
1379
- DropdownMenuItem,
1380
- DropdownMenuLabel,
1381
- DropdownMenuSeparator,
1382
- DropdownMenuTrigger,
1383
- useLocale as useLocale4
1384
- } from "@elabs-ai/components-ui";
1385
- import { cn as cn4 } from "@elabs-ai/components-ui/lib/cn";
1386
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1387
- var FacetFilter = forwardRef4(function FacetFilter2({ title, options, selected, onSelectedChange, className, ...props }, ref) {
1388
- const { t } = useLocale4();
1389
- const selectedSet = new Set(selected);
1390
- const toggle = (value) => {
1391
- const next = new Set(selectedSet);
1392
- if (next.has(value)) next.delete(value);
1393
- else next.add(value);
1394
- onSelectedChange([...next]);
1395
- };
1396
- return /* @__PURE__ */ jsxs4(DropdownMenu, { children: [
1397
- /* @__PURE__ */ jsx5(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs4(Button2, { ref, variant: "outline", className: cn4("border-dashed", className), ...props, children: [
1398
- title,
1399
- selected.length > 0 ? /* @__PURE__ */ jsx5(
1400
- Badge,
1401
- {
1402
- variant: "secondary",
1403
- className: "ms-1 rounded px-1.5 animate-in fade-in zoom-in-95 duration-fast ease-entrance",
1404
- children: selected.length
1405
- }
1406
- ) : null
1407
- ] }) }),
1408
- /* @__PURE__ */ jsxs4(DropdownMenuContent, { className: "min-w-[12rem]", children: [
1409
- /* @__PURE__ */ jsx5(DropdownMenuLabel, { children: title }),
1410
- options.map((opt) => {
1411
- const checked = selectedSet.has(opt.value);
1412
- return /* @__PURE__ */ jsxs4(
1413
- DropdownMenuItem,
1414
- {
1415
- onSelect: (e) => {
1416
- e.preventDefault();
1417
- toggle(opt.value);
1418
- },
1419
- children: [
1420
- /* @__PURE__ */ jsx5(
1421
- "span",
1422
- {
1423
- "aria-hidden": "true",
1424
- className: "flex size-4 items-center justify-center rounded border transition-colors duration-fast ease-standard " + (checked ? "border-primary bg-primary text-primary-foreground" : "border-input"),
1425
- children: checked ? "\u2713" : ""
1426
- }
1427
- ),
1428
- opt.label
1429
- ]
1430
- },
1431
- opt.value
1432
- );
1433
- }),
1434
- selected.length > 0 ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
1435
- /* @__PURE__ */ jsx5(DropdownMenuSeparator, {}),
1436
- /* @__PURE__ */ jsx5(DropdownMenuItem, { onSelect: () => onSelectedChange([]), children: t("data.facetFilter.clearFilters") })
1437
- ] }) : null
1438
- ] })
1439
- ] });
1440
- });
1441
- FacetFilter.displayName = "FacetFilter";
1442
-
1443
- // src/column-picker/column-picker.tsx
1444
- import "@tanstack/react-table";
1445
- import { forwardRef as forwardRef5 } from "react";
50
+ DataTableRankCell,
51
+ DataTableRankHeader,
52
+ computeRowRanks
53
+ } from "./chunk-XN6O6Z2N.js";
1446
54
  import {
1447
- Button as Button3,
1448
- DropdownMenu as DropdownMenu2,
1449
- DropdownMenuContent as DropdownMenuContent2,
1450
- DropdownMenuItem as DropdownMenuItem2,
1451
- DropdownMenuLabel as DropdownMenuLabel2,
1452
- DropdownMenuSeparator as DropdownMenuSeparator2,
1453
- DropdownMenuTrigger as DropdownMenuTrigger2,
1454
- useLocale as useLocale5
1455
- } from "@elabs-ai/components-ui";
1456
- import { cn as cn5 } from "@elabs-ai/components-ui/lib/cn";
1457
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1458
- function ColumnPickerInner({ table, label, className, ...props }, ref) {
1459
- const { t } = useLocale5();
1460
- const resolvedLabel = label ?? t("data.columnPicker.label");
1461
- const columns = table.getAllColumns().filter((c) => c.getCanHide());
1462
- return /* @__PURE__ */ jsxs5(DropdownMenu2, { children: [
1463
- /* @__PURE__ */ jsx6(DropdownMenuTrigger2, { asChild: true, children: /* @__PURE__ */ jsx6(Button3, { ref, variant: "outline", size: "sm", className: cn5(className), ...props, children: resolvedLabel }) }),
1464
- /* @__PURE__ */ jsxs5(DropdownMenuContent2, { align: "end", className: "min-w-[12rem]", children: [
1465
- /* @__PURE__ */ jsx6(DropdownMenuLabel2, { children: t("data.columnPicker.toggleColumns") }),
1466
- /* @__PURE__ */ jsx6(DropdownMenuSeparator2, {}),
1467
- columns.map((column) => /* @__PURE__ */ jsxs5(
1468
- DropdownMenuItem2,
1469
- {
1470
- onSelect: (e) => {
1471
- e.preventDefault();
1472
- column.toggleVisibility(!column.getIsVisible());
1473
- },
1474
- children: [
1475
- /* @__PURE__ */ jsx6(
1476
- "span",
1477
- {
1478
- "aria-hidden": "true",
1479
- className: "flex size-4 items-center justify-center rounded border transition-colors duration-fast ease-standard " + (column.getIsVisible() ? "border-primary bg-primary text-primary-foreground" : "border-input"),
1480
- children: column.getIsVisible() ? "\u2713" : ""
1481
- }
1482
- ),
1483
- /* @__PURE__ */ jsx6("span", { className: "capitalize", children: column.id })
1484
- ]
1485
- },
1486
- column.id
1487
- ))
1488
- ] })
1489
- ] });
1490
- }
1491
- ColumnPickerInner.displayName = "ColumnPicker";
1492
- var ColumnPicker = forwardRef5(ColumnPickerInner);
1493
-
1494
- // src/to-csv.ts
1495
- import { csvQuoteField, csvStringifyValue, downloadBlob } from "@elabs-ai/components-ui";
1496
- var stringifyValue = csvStringifyValue;
1497
- var quoteField = csvQuoteField;
1498
- function toCsv(rows, opts) {
1499
- const delimiter = opts?.delimiter ?? ",";
1500
- const includeHeader = opts?.header !== false;
1501
- const firstRow = rows[0];
1502
- const cols = opts?.columns ?? (firstRow !== void 0 ? Object.keys(firstRow).map((k) => ({ key: k })) : []);
1503
- const lines = [];
1504
- if (includeHeader && cols.length > 0) {
1505
- const headerRow = cols.map((c) => quoteField(c.header ?? c.key, delimiter)).join(delimiter);
1506
- lines.push(headerRow);
1507
- }
1508
- for (const row of rows) {
1509
- const line = cols.map((c) => quoteField(stringifyValue(row[c.key]), delimiter)).join(delimiter);
1510
- lines.push(line);
1511
- }
1512
- return lines.join("\r\n") + (lines.length > 0 ? "\r\n" : "");
1513
- }
1514
- function downloadCsv(rows, opts) {
1515
- if (typeof document === "undefined") return;
1516
- const csv = toCsv(rows, opts);
1517
- const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
1518
- downloadBlob(blob, (opts?.filename ?? "download") + ".csv");
1519
- }
55
+ stickyRowPinning
56
+ } from "./chunk-47ZBQZYS.js";
57
+ import "./chunk-5K2IFOV2.js";
58
+ import {
59
+ ColumnPicker
60
+ } from "./chunk-DBPJ7LVM.js";
61
+ import {
62
+ DataTableCard,
63
+ DataTableCardList
64
+ } from "./chunk-KCB33PD3.js";
65
+ import {
66
+ CategoryLegend
67
+ } from "./chunk-ZUCGV2XD.js";
68
+ import {
69
+ ColumnsCell
70
+ } from "./chunk-GBO56756.js";
71
+ import {
72
+ BarCell
73
+ } from "./chunk-SK6347WB.js";
74
+ import {
75
+ barDomain,
76
+ barGeometry,
77
+ computeColumnScales,
78
+ extentOf,
79
+ heatmapColorScaleSpec,
80
+ seriesValues
81
+ } from "./chunk-VCLA45IC.js";
1520
82
  export {
83
+ BarCell,
84
+ CategoryLegend,
1521
85
  ColumnPicker,
86
+ ColumnsCell,
87
+ DATA_TABLE_BREAKPOINT_THRESHOLDS,
1522
88
  DataTableWithRef as DataTable,
89
+ DataTableCard,
90
+ DataTableCardList,
91
+ DataTableRankCell,
92
+ DataTableRankHeader,
1523
93
  FacetFilter,
1524
94
  FilterBar,
1525
95
  FilterChip,
96
+ HeatmapCell,
97
+ HeatmapLegend,
98
+ MarkdownCell,
1526
99
  SearchInput,
100
+ SparklineCell,
101
+ barDomain,
102
+ barGeometry,
103
+ columnSizeStyle,
104
+ computeColumnScales,
105
+ computeRowRanks,
1527
106
  createSelectionColumn,
1528
107
  downloadCsv,
1529
- toCsv
108
+ extentOf,
109
+ formatCellValue,
110
+ heatmapCellStyle,
111
+ heatmapColorScaleSpec,
112
+ isSafeCellUrl,
113
+ parseCellMarkdown,
114
+ resolveShowAt,
115
+ seriesValues,
116
+ stickyRowPinning,
117
+ tableBreakpointForWidth,
118
+ toCsv,
119
+ useTableBreakpoint
1530
120
  };
1531
121
  //# sourceMappingURL=index.js.map