@rovula/ui 0.1.28 → 0.1.30

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 (65) hide show
  1. package/dist/cjs/bundle.css +522 -67
  2. package/dist/cjs/bundle.js +589 -589
  3. package/dist/cjs/bundle.js.map +1 -1
  4. package/dist/cjs/types/components/DataTable/DataTable.d.ts +195 -4
  5. package/dist/cjs/types/components/DataTable/DataTable.editing.d.ts +20 -0
  6. package/dist/cjs/types/components/DataTable/DataTable.editing.types.d.ts +145 -0
  7. package/dist/cjs/types/components/DataTable/DataTable.stories.d.ts +294 -6
  8. package/dist/cjs/types/components/Dropdown/Dropdown.d.ts +22 -0
  9. package/dist/cjs/types/components/Dropdown/Dropdown.stories.d.ts +4 -0
  10. package/dist/cjs/types/components/ScrollArea/ScrollArea.d.ts +3 -3
  11. package/dist/cjs/types/components/ScrollArea/ScrollArea.stories.d.ts +4 -0
  12. package/dist/cjs/types/components/Table/Table.d.ts +33 -3
  13. package/dist/cjs/types/components/Table/Table.stories.d.ts +86 -4
  14. package/dist/cjs/types/components/TextInput/TextInput.stories.d.ts +8 -0
  15. package/dist/cjs/types/components/TextInput/TextInput.styles.d.ts +1 -0
  16. package/dist/components/DataTable/DataTable.editing.js +385 -0
  17. package/dist/components/DataTable/DataTable.editing.types.js +1 -0
  18. package/dist/components/DataTable/DataTable.js +993 -50
  19. package/dist/components/DataTable/DataTable.stories.js +1137 -25
  20. package/dist/components/Dropdown/Dropdown.js +8 -6
  21. package/dist/components/ScrollArea/ScrollArea.js +2 -2
  22. package/dist/components/ScrollArea/ScrollArea.stories.js +68 -2
  23. package/dist/components/Table/Table.js +103 -13
  24. package/dist/components/Table/Table.stories.js +226 -9
  25. package/dist/components/TextInput/TextInput.js +6 -4
  26. package/dist/components/TextInput/TextInput.stories.js +8 -0
  27. package/dist/components/TextInput/TextInput.styles.js +7 -1
  28. package/dist/esm/bundle.css +522 -67
  29. package/dist/esm/bundle.js +1545 -1545
  30. package/dist/esm/bundle.js.map +1 -1
  31. package/dist/esm/types/components/DataTable/DataTable.d.ts +195 -4
  32. package/dist/esm/types/components/DataTable/DataTable.editing.d.ts +20 -0
  33. package/dist/esm/types/components/DataTable/DataTable.editing.types.d.ts +145 -0
  34. package/dist/esm/types/components/DataTable/DataTable.stories.d.ts +294 -6
  35. package/dist/esm/types/components/Dropdown/Dropdown.d.ts +22 -0
  36. package/dist/esm/types/components/Dropdown/Dropdown.stories.d.ts +4 -0
  37. package/dist/esm/types/components/ScrollArea/ScrollArea.d.ts +3 -3
  38. package/dist/esm/types/components/ScrollArea/ScrollArea.stories.d.ts +4 -0
  39. package/dist/esm/types/components/Table/Table.d.ts +33 -3
  40. package/dist/esm/types/components/Table/Table.stories.d.ts +86 -4
  41. package/dist/esm/types/components/TextInput/TextInput.stories.d.ts +8 -0
  42. package/dist/esm/types/components/TextInput/TextInput.styles.d.ts +1 -0
  43. package/dist/index.d.ts +493 -122
  44. package/dist/src/theme/global.css +775 -96
  45. package/package.json +14 -2
  46. package/src/components/DataTable/DataTable.editing.tsx +861 -0
  47. package/src/components/DataTable/DataTable.editing.types.ts +192 -0
  48. package/src/components/DataTable/DataTable.stories.tsx +2310 -31
  49. package/src/components/DataTable/DataTable.test.tsx +696 -0
  50. package/src/components/DataTable/DataTable.tsx +2275 -94
  51. package/src/components/Dropdown/Dropdown.tsx +22 -6
  52. package/src/components/ScrollArea/ScrollArea.stories.tsx +146 -3
  53. package/src/components/ScrollArea/ScrollArea.tsx +6 -6
  54. package/src/components/Table/Table.stories.tsx +789 -44
  55. package/src/components/Table/Table.tsx +306 -28
  56. package/src/components/TextInput/TextInput.stories.tsx +80 -0
  57. package/src/components/TextInput/TextInput.styles.ts +7 -1
  58. package/src/components/TextInput/TextInput.tsx +21 -14
  59. package/src/test/setup.ts +50 -0
  60. package/src/theme/global.css +81 -42
  61. package/src/theme/presets/colors.js +12 -0
  62. package/src/theme/themes/variable.css +27 -28
  63. package/src/theme/tokens/baseline.css +2 -1
  64. package/src/theme/tokens/components/scrollbar.css +9 -4
  65. package/src/theme/tokens/components/table.css +63 -0
@@ -1,70 +1,1013 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { flexRender, getCoreRowModel, getFilteredRowModel,
3
- // getPaginationRowModel,
4
- getSortedRowModel, useReactTable, } from "@tanstack/react-table";
5
- import { useEffect, useRef, useState } from "react";
6
- import { ArrowDownIcon, ArrowUpIcon, ArrowsUpDownIcon, ClipboardDocumentListIcon, } from "@heroicons/react/16/solid";
7
- import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../Table/Table";
8
- export function DataTable({ data, columns, manualSorting = false, onSorting, fetchMoreData, }) {
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
13
+ import { flexRender, getCoreRowModel, getExpandedRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, } from "@tanstack/react-table";
14
+ import React, { useCallback, useEffect, useLayoutEffect, useRef, useState, } from "react";
15
+ import { useVirtualizer } from "@tanstack/react-virtual";
16
+ function columnMetaAlignClass(meta) {
17
+ switch (meta === null || meta === void 0 ? void 0 : meta.align) {
18
+ case "center":
19
+ return "text-center";
20
+ case "right":
21
+ return "text-right";
22
+ case "left":
23
+ return "text-left";
24
+ default:
25
+ return undefined;
26
+ }
27
+ }
28
+ /**
29
+ * Walk visible body cells left-to-right; when a column defines `meta.colSpan`,
30
+ * emit one `<td>` spanning that many columns and skip the covered cells.
31
+ */
32
+ function getVisibleCellsForRender(cells) {
33
+ const out = [];
34
+ let i = 0;
35
+ while (i < cells.length) {
36
+ const cell = cells[i];
37
+ const meta = cell.column.columnDef.meta;
38
+ let want = 1;
39
+ if ((meta === null || meta === void 0 ? void 0 : meta.colSpan) != null) {
40
+ const raw = typeof meta.colSpan === "function"
41
+ ? meta.colSpan(cell.row)
42
+ : meta.colSpan;
43
+ const n = Math.floor(Number(raw));
44
+ if (Number.isFinite(n) && n >= 1)
45
+ want = n;
46
+ }
47
+ const maxSpan = cells.length - i;
48
+ const colSpan = Math.min(want, maxSpan);
49
+ out.push({ cell, colSpan: Math.max(1, colSpan) });
50
+ i += Math.max(1, colSpan);
51
+ }
52
+ return out;
53
+ }
54
+ function renderDataTableBodyCells(opts) {
55
+ const { row, getSubRows, firstDataCellId, fixedColStyles, flexWidth, lockPxExactWidth, resizable, tableLayout, resizableSlackGrowColId, cellClassName, onCellClick, } = opts;
56
+ return getVisibleCellsForRender(row.getVisibleCells()).map(({ cell, colSpan }) => {
57
+ var _a;
58
+ const isFirstDataCell = Boolean(getSubRows) && cell.id === firstDataCellId(row);
59
+ const usePixelResizeWidth = resizable &&
60
+ tableLayout !== "equal" &&
61
+ !(resizableSlackGrowColId != null &&
62
+ cell.column.id === resizableSlackGrowColId);
63
+ return (_jsx(TableCell, { colSpan: colSpan > 1 ? colSpan : undefined, style: resolveBodyCellWidthStyle(cell.column.columnDef, fixedColStyles === null || fixedColStyles === void 0 ? void 0 : fixedColStyles.get(cell.column.id), flexWidth, lockPxExactWidth, usePixelResizeWidth ? cell.column.getSize() : undefined), className: cn(columnMetaAlignClass(cell.column.columnDef.meta), (_a = cell.column.columnDef.meta) === null || _a === void 0 ? void 0 : _a.cellClassName, typeof cellClassName === "function"
64
+ ? cellClassName(cell, row)
65
+ : cellClassName), onClick: onCellClick
66
+ ? (e) => onCellClick(cell, row, e)
67
+ : undefined, children: isFirstDataCell ? (_jsxs("div", { className: "flex items-center gap-1", style: { paddingLeft: `${row.depth * 20}px` }, children: [row.getCanExpand() ? (_jsx("button", { type: "button", onClick: row.getToggleExpandedHandler(), className: "flex items-center justify-center size-5 rounded hover:bg-table-c-hover shrink-0 transition-colors", "aria-label": row.getIsExpanded() ? "Collapse" : "Expand", children: row.getIsExpanded() ? (_jsx(ChevronDown, { className: "size-4" })) : (_jsx(ChevronRight, { className: "size-4" })) })) : (_jsx("span", { className: "size-5 shrink-0" })), flexRender(cell.column.columnDef.cell, cell.getContext())] })) : (flexRender(cell.column.columnDef.cell, cell.getContext())) }, cell.id));
68
+ });
69
+ }
70
+ import * as Portal from "@radix-ui/react-portal";
71
+ import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors, } from "@dnd-kit/core";
72
+ import { restrictToVerticalAxis, restrictToParentElement, } from "@dnd-kit/modifiers";
73
+ import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy, } from "@dnd-kit/sortable";
74
+ import { CSS } from "@dnd-kit/utilities";
75
+ import { ArrowDown, ArrowUp, ArrowUpDown, ChevronDown, ChevronRight, ClipboardList, Columns3, EllipsisVertical, Equal, EyeOff, Loader2, } from "lucide-react";
76
+ import ActionButton from "@/components/ActionButton/ActionButton";
77
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/DropdownMenu/DropdownMenu";
78
+ import { Checkbox } from "../Checkbox/Checkbox";
79
+ import { Switch } from "../Switch/Switch";
80
+ import Button from "../Button/Button";
81
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TablePagination, TableRow, } from "../Table/Table";
82
+ import { cn } from "@/utils/cn";
83
+ import { EditContext, useDataTableEditing, resolveEditableColumns, detectEditableColumnIds, } from "./DataTable.editing";
84
+ // ---------------------------------------------------------------------------
85
+ // Checkbox column builder
86
+ // ---------------------------------------------------------------------------
87
+ function buildCheckboxColumn() {
88
+ return {
89
+ id: "__select__",
90
+ header: ({ table }) => (_jsx(Checkbox, { checked: table.getIsSomeRowsSelected()
91
+ ? "indeterminate"
92
+ : table.getIsAllRowsSelected(), onCheckedChange: (v) => table.toggleAllRowsSelected(!!v), "aria-label": "Select all" })),
93
+ cell: ({ row }) => (_jsx(Checkbox, { checked: row.getIsSelected(), onCheckedChange: (v) => row.toggleSelected(!!v), "aria-label": "Select row", disabled: !row.getCanSelect() })),
94
+ enableSorting: false,
95
+ enableHiding: false,
96
+ enableResizing: false,
97
+ size: 42,
98
+ maxSize: 42,
99
+ minSize: 42,
100
+ meta: { exactWidth: 42 },
101
+ };
102
+ }
103
+ // ---------------------------------------------------------------------------
104
+ // Row-actions column builder
105
+ // ---------------------------------------------------------------------------
106
+ function buildActionsColumn(render) {
107
+ return {
108
+ id: "__actions__",
109
+ header: () => null,
110
+ cell: ({ row }) => (_jsx("div", { className: "flex items-center justify-center gap-1", children: render(row) })),
111
+ enableSorting: false,
112
+ enableHiding: false,
113
+ enableResizing: false,
114
+ size: 80,
115
+ maxSize: 80,
116
+ minSize: 80,
117
+ meta: { exactWidth: 80 },
118
+ };
119
+ }
120
+ // ---------------------------------------------------------------------------
121
+ // Row-reorder drag-handle column
122
+ // ---------------------------------------------------------------------------
123
+ function buildReorderColumn() {
124
+ return {
125
+ id: "__reorder__",
126
+ header: () => null,
127
+ cell: ({ row }) => _jsx(RowDragHandle, { rowId: row.id }),
128
+ enableSorting: false,
129
+ enableHiding: false,
130
+ enableResizing: false,
131
+ size: 54,
132
+ maxSize: 54,
133
+ minSize: 54,
134
+ meta: { exactWidth: 54 },
135
+ };
136
+ }
137
+ function RowDragHandle({ rowId }) {
138
+ const { attributes, listeners, setNodeRef, isDragging } = useSortable({
139
+ id: rowId,
140
+ });
141
+ return (_jsx(ActionButton, Object.assign({ ref: setNodeRef }, attributes, listeners, { variant: "icon", size: "sm", active: isDragging, className: cn("touch-none", isDragging ? "cursor-grabbing" : "cursor-grab"), "aria-label": "Drag to reorder", children: _jsx(Equal, { className: "!size-4" }) })));
142
+ }
143
+ // ---------------------------------------------------------------------------
144
+ // SortableTableRow — wraps a <TableRow> with dnd-kit sortable transform
145
+ // ---------------------------------------------------------------------------
146
+ function SortableTableRow(_a) {
147
+ var { id, children } = _a, props = __rest(_a, ["id", "children"]);
148
+ const { transform, transition, isDragging, setNodeRef } = useSortable({
149
+ id,
150
+ });
151
+ const style = {
152
+ transform: CSS.Transform.toString(transform ? Object.assign(Object.assign({}, transform), { scaleX: 1, scaleY: 1 }) : null),
153
+ transition,
154
+ opacity: isDragging ? 0.5 : 1,
155
+ position: "relative",
156
+ zIndex: isDragging ? 50 : undefined,
157
+ };
158
+ return (_jsx(TableRow, Object.assign({ ref: setNodeRef, style: style }, props, { children: children })));
159
+ }
160
+ const PINNED_START = ["__reorder__", "__select__"];
161
+ const PINNED_END = ["__actions__"];
162
+ /** Label for column-management menu (“Hide {name} column”). */
163
+ function manageColumnHeaderLabel(header) {
164
+ const h = header.column.columnDef.header;
165
+ return typeof h === "string" ? h : header.column.id;
166
+ }
167
+ /**
168
+ * If a column carries `meta.exactWidth`, return CSS that locks the cell to
169
+ * exactly that width. Accepts a number (px) or any CSS value / calc().
170
+ *
171
+ * Works in every `tableLayout` mode — `minWidth` + `maxWidth` constrain the
172
+ * cell even in `table-layout: auto` without requiring `table-fixed` on the
173
+ * table element. `overflow: hidden` prevents content from pushing wider.
174
+ */
175
+ function getExactWidthStyle(colDef, lock) {
176
+ var _a;
177
+ const w = (_a = colDef.meta) === null || _a === void 0 ? void 0 : _a.exactWidth;
178
+ if (w == null)
179
+ return undefined;
180
+ // Only "lock" when the value is safely lockable (number or px string).
181
+ // For `calc()`, `%`, `rem`, etc. we apply only `width` even in fixed mode,
182
+ // otherwise the column becomes rigid in ways that surprise callers.
183
+ const lockable = exactWidthToPx(w) != null;
184
+ const shouldLock = lock && lockable;
185
+ // Lockable = number or "123px" — set all three to that fixed width.
186
+ // Do NOT use `calc(100% - ${w}px)` here: that means “fill minus w”, not “width = w”.
187
+ const locked = typeof w === "number" ? `${w}px` : w.trim();
188
+ return shouldLock
189
+ ? {
190
+ width: locked,
191
+ minWidth: locked,
192
+ maxWidth: locked,
193
+ overflow: "hidden",
194
+ boxSizing: "border-box",
195
+ }
196
+ : {
197
+ width: w,
198
+ };
199
+ }
200
+ /**
201
+ * Convert `meta.exactWidth` to a numeric px value (only when possible).
202
+ * - `number` => px
203
+ * - `"123px"` => 123
204
+ * - other string formats => null (can't be summed at build time)
205
+ */
206
+ function exactWidthToPx(exactWidth) {
207
+ if (typeof exactWidth === "number") {
208
+ return Number.isFinite(exactWidth) ? exactWidth : null;
209
+ }
210
+ if (typeof exactWidth === "string") {
211
+ const m = exactWidth.trim().match(/^(-?\d*\.?\d+)\s*px$/i);
212
+ return m ? parseFloat(m[1]) : null;
213
+ }
214
+ return null;
215
+ }
216
+ /**
217
+ * Compute the auto-fill width for columns that do NOT have `exactWidth`.
218
+ * Formula: `calc((100% - <totalExactPx>px) / <flexCount>)`
219
+ *
220
+ * Only values convertible to numeric px participate in the subtraction.
221
+ */
222
+ function useFlexColumnWidth(table) {
223
+ return React.useMemo(() => {
224
+ var _a;
225
+ const visible = table.getVisibleLeafColumns();
226
+ let totalExactPx = 0;
227
+ let flexCount = 0;
228
+ for (const col of visible) {
229
+ const ew = (_a = col.columnDef.meta) === null || _a === void 0 ? void 0 : _a.exactWidth;
230
+ const asPx = exactWidthToPx(ew);
231
+ if (asPx != null) {
232
+ totalExactPx += asPx;
233
+ }
234
+ else if (ew == null) {
235
+ flexCount += 1;
236
+ }
237
+ }
238
+ if (flexCount === 0)
239
+ return undefined;
240
+ if (totalExactPx === 0)
241
+ return undefined;
242
+ return `calc((100% - ${totalExactPx}px) / ${flexCount})`;
243
+ }, [table.getVisibleLeafColumns()]);
244
+ }
245
+ /**
246
+ * Resolve the inline style for a **header** cell (th).
247
+ * - Has `exactWidth` → locked width (min/max/overflow)
248
+ * - `resizableWidth` (TanStack `getSize()`) → drag-resize width (`fixed`/`auto` only)
249
+ * - flexWidth available → equal calc() (`equal` + `resizable` uses this, not getSize)
250
+ * - Otherwise → fallback (e.g. fixed layout map or undefined)
251
+ */
252
+ function resolveHeaderWidthStyle(colDef, flexWidth, fallback, lockExactWidth, resizableWidth) {
253
+ const exact = getExactWidthStyle(colDef, lockExactWidth);
254
+ if (exact)
255
+ return exact;
256
+ if (resizableWidth != null) {
257
+ const colMax = colDef.maxSize;
258
+ return Object.assign({ width: resizableWidth }, (colMax != null &&
259
+ colMax !== Number.MAX_SAFE_INTEGER && { maxWidth: colMax }));
260
+ }
261
+ if (flexWidth)
262
+ return { width: flexWidth };
263
+ return fallback;
264
+ }
265
+ /**
266
+ * Resolve the inline style for a **body** cell (td).
267
+ * - Has `exactWidth` → locked width via min/maxWidth + overflow:hidden
268
+ * - `resizableWidth` (TanStack column `getSize()`) → matches header when resizable
269
+ * - fixedCellStyle available → "fixed" layout map
270
+ * - flexWidth available → equal calc()
271
+ * - Otherwise → undefined
272
+ */
273
+ function resolveBodyCellWidthStyle(colDef, fixedCellStyle, flexWidth, lockExactWidth, resizableWidth) {
274
+ const exact = getExactWidthStyle(colDef, lockExactWidth);
275
+ if (exact)
276
+ return exact;
277
+ if (resizableWidth != null) {
278
+ const colMax = colDef.maxSize;
279
+ return Object.assign({ width: resizableWidth }, (colMax != null &&
280
+ colMax !== Number.MAX_SAFE_INTEGER && { maxWidth: colMax }));
281
+ }
282
+ if (fixedCellStyle)
283
+ return fixedCellStyle;
284
+ if (flexWidth)
285
+ return { width: flexWidth };
286
+ return undefined;
287
+ }
288
+ function SortableColumnRow({ col, label, isLastVisible, showReorder, showVisibility, }) {
289
+ var _a;
290
+ const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging, } = useSortable({ id: col.id });
291
+ const style = {
292
+ transform: isDragging
293
+ ? `translateY(${(_a = transform === null || transform === void 0 ? void 0 : transform.y) !== null && _a !== void 0 ? _a : 0}px)`
294
+ : CSS.Transform.toString(transform),
295
+ transition: isDragging ? undefined : transition,
296
+ };
297
+ return (_jsxs("div", { ref: setNodeRef, style: style, className: cn("flex h-14 items-center gap-4 pl-6 pr-8 bg-modal-surface", "relative select-none", isDragging &&
298
+ "z-50 border border-primary-500/40 shadow-[0_8px_24px_-4px_rgba(0,0,0,0.24)] scale-[1.01]"), children: [showReorder && (_jsx(ActionButton, Object.assign({ ref: setActivatorNodeRef }, attributes, listeners, { variant: "icon", size: "sm", active: isDragging, "aria-label": `Drag to reorder ${label}`, className: cn("shrink-0 touch-none", isDragging ? "cursor-grabbing" : "cursor-grab"), children: _jsx(Equal, { className: "size-[14px]" }) }))), showVisibility && (_jsx(Switch, { checked: col.getIsVisible(), onCheckedChange: (v) => col.toggleVisibility(v), disabled: isLastVisible })), _jsx("span", { className: cn("flex-1 typography-subtitle4", isDragging ? "text-text-contrast-max" : "text-text-g-contrast-high"), children: label })] }));
299
+ }
300
+ function ManageColumnPanel({ table, onClose, maxListHeight = 400, options = {}, }) {
301
+ const { reorder = true, visibility = true, hideAll = true, showAll = true, } = options;
302
+ const hideable = table.getAllLeafColumns().filter((col) => col.getCanHide());
303
+ const visibleCount = hideable.filter((col) => col.getIsVisible()).length;
304
+ const headerLabel = (col) => {
305
+ const h = col.columnDef.header;
306
+ return typeof h === "string" ? h : col.id;
307
+ };
308
+ const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, {
309
+ coordinateGetter: sortableKeyboardCoordinates,
310
+ }));
311
+ const handleDragEnd = (event) => {
312
+ const { active, over } = event;
313
+ if (!over || active.id === over.id)
314
+ return;
315
+ const allColIds = table.getAllLeafColumns().map((c) => c.id);
316
+ const movable = allColIds.filter((id) => !PINNED_START.includes(id) &&
317
+ !PINNED_END.includes(id));
318
+ const oldIndex = movable.indexOf(active.id);
319
+ const newIndex = movable.indexOf(over.id);
320
+ if (oldIndex === -1 || newIndex === -1)
321
+ return;
322
+ const fixedStart = allColIds.filter((id) => PINNED_START.includes(id));
323
+ const fixedEnd = allColIds.filter((id) => PINNED_END.includes(id));
324
+ table.setColumnOrder([
325
+ ...fixedStart,
326
+ ...arrayMove(movable, oldIndex, newIndex),
327
+ ...fixedEnd,
328
+ ]);
329
+ };
330
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center gap-2 px-6 pt-4 pb-3 border-b border-modal-line", children: [_jsx("span", { className: "flex-1 typography-subtitle3 text-text-contrast-max", children: "Manage column" }), hideAll && (_jsx(Button, { variant: "text", color: "secondary", size: "sm", disabled: visibleCount <= 1, onClick: () => {
331
+ var _a;
332
+ const firstVisible = (_a = hideable.find((col) => col.getIsVisible())) !== null && _a !== void 0 ? _a : hideable[0];
333
+ hideable.forEach((col) => col.toggleVisibility(col.id === firstVisible.id));
334
+ }, children: "Hide all" })), showAll && (_jsx(Button, { variant: "text", color: "primary", size: "sm", onClick: () => table.toggleAllColumnsVisible(true), children: "Show all" })), _jsx(Button, { variant: "outline", color: "primary", size: "sm", onClick: onClose, children: "Done" })] }), _jsx(DndContext, { sensors: sensors, collisionDetection: closestCenter, modifiers: [restrictToVerticalAxis, restrictToParentElement], onDragEnd: handleDragEnd, children: _jsx(SortableContext, { items: hideable.map((c) => c.id), strategy: verticalListSortingStrategy, children: _jsx("div", { className: "overflow-y-auto ui-scrollbar", style: { maxHeight: maxListHeight }, children: hideable.map((col) => (_jsx(SortableColumnRow, { col: col, label: headerLabel(col), isLastVisible: col.getIsVisible() && visibleCount === 1, showReorder: reorder, showVisibility: visibility }, col.id))) }) }) })] }));
335
+ }
336
+ // ---------------------------------------------------------------------------
337
+ // DataTable
338
+ // ---------------------------------------------------------------------------
339
+ export function DataTable({ data, columns, manualSorting = false, onSorting, paginationMode = "infinite", totalCount, pageIndex: controlledPageIndex, pageSize: controlledPageSize, onPaginationChange, pageSizeOptions, fetchMoreData, fetchMoreOffset, fetchingMore = false, fetchingMoreLabel = "Loading more…", loading = false, loadingLabel = "Loading…", highlightRowId, scrollToHighlightOnMouseLeave = false, selectable = false, onRowSelectionChange, rowActions, reorderable = false, getRowId: getRowIdProp, onRowReorder, isRowReorderLocked, onRowClick, onCellClick, getSubRows, defaultExpanded, expanded: controlledExpanded, onExpandedChange, bordered = true, surface = "default", striped = false, divided = true, rowClassName, cellClassName, headerCellClassName, headerClassName, headerRowClassName, sortIndicatorVisibility = "hover", tableLayout = "auto", columnManagement: columnManagementProp = false, resizable = false, columnMinSize = 60, columnMaxSize = Number.MAX_SAFE_INTEGER, virtualized = false, virtualRowEstimate, className, enableEditing = false, editDisplayMode = "cell", editTrigger = "click", onCellCommit, alwaysEditing, enableCellTabTraversal, editableColumnIds: editableColumnIdsProp, testId, }) {
9
340
  var _a;
10
- const tableBodyRef = useRef(null);
341
+ // scrollable container ref — lives on the wrapper div, not tbody
342
+ const scrollRef = useRef(null);
343
+ const tableHeaderRef = useRef(null);
344
+ const userInteractingRef = useRef(false);
345
+ /** Suppresses marking user scroll during highlight-driven `scrollTo` / `scrollIntoView`. */
346
+ const programmaticScrollLockRef = useRef(0);
347
+ const [stickyHeaderHeight, setStickyHeaderHeight] = useState(48);
348
+ const scheduleProgrammaticScrollEnd = useCallback(() => {
349
+ const el = scrollRef.current;
350
+ let settled = false;
351
+ const unlock = () => {
352
+ if (settled)
353
+ return;
354
+ settled = true;
355
+ programmaticScrollLockRef.current = Math.max(0, programmaticScrollLockRef.current - 1);
356
+ };
357
+ el === null || el === void 0 ? void 0 : el.addEventListener("scrollend", unlock, { once: true });
358
+ window.setTimeout(unlock, 550);
359
+ }, []);
360
+ const virtualMeasureRowElement = useCallback((element, entry, instance) => {
361
+ var _a, _b;
362
+ const direction = instance.scrollDirection;
363
+ if (direction === "forward" || direction === null) {
364
+ const box = (_a = entry === null || entry === void 0 ? void 0 : entry.borderBoxSize) === null || _a === void 0 ? void 0 : _a[0];
365
+ if (box) {
366
+ return Math.round(box.blockSize);
367
+ }
368
+ return Math.round(element.getBoundingClientRect().height);
369
+ }
370
+ const raw = element.getAttribute("data-index");
371
+ const indexKey = raw != null ? Number.parseInt(raw, 10) : NaN;
372
+ const cache = instance.measurementsCache;
373
+ const cached = Number.isFinite(indexKey) && (cache === null || cache === void 0 ? void 0 : cache[indexKey])
374
+ ? cache[indexKey].size
375
+ : undefined;
376
+ if (typeof cached === "number") {
377
+ return cached;
378
+ }
379
+ const box = (_b = entry === null || entry === void 0 ? void 0 : entry.borderBoxSize) === null || _b === void 0 ? void 0 : _b[0];
380
+ if (box) {
381
+ return Math.round(box.blockSize);
382
+ }
383
+ return Math.round(element.getBoundingClientRect().height);
384
+ }, []);
385
+ // Normalize columnManagement: boolean | options → boolean + options object
386
+ const columnManagement = !!columnManagementProp;
387
+ const columnManagementOptions = typeof columnManagementProp === "object" ? columnManagementProp : {};
388
+ // ---- state ----
11
389
  const [sorting, setSorting] = useState([]);
12
390
  const [columnFilters, setColumnFilters] = useState([]);
13
391
  const [columnVisibility, setColumnVisibility] = useState({});
392
+ const [columnOrder, setColumnOrder] = useState([]);
393
+ const [columnSizing, setColumnSizing] = useState({});
394
+ const [columnSizingInfo, setColumnSizingInfo] = useState({
395
+ startOffset: null,
396
+ startSize: null,
397
+ deltaOffset: null,
398
+ deltaPercentage: null,
399
+ isResizingColumn: false,
400
+ columnSizingStart: [],
401
+ });
14
402
  const [rowSelection, setRowSelection] = useState({});
15
- const table = useReactTable({
16
- data,
403
+ const [internalExpanded, setInternalExpanded] = useState((_a = defaultExpanded) !== null && _a !== void 0 ? _a : {});
404
+ // Controlled: use prop directly every render; uncontrolled: use internal state
405
+ const isExpandedControlled = controlledExpanded !== undefined;
406
+ const expanded = isExpandedControlled ? controlledExpanded : internalExpanded;
407
+ const [pagination, setPagination] = useState({
408
+ pageIndex: controlledPageIndex !== null && controlledPageIndex !== void 0 ? controlledPageIndex : 0,
409
+ pageSize: controlledPageSize !== null && controlledPageSize !== void 0 ? controlledPageSize : 10,
410
+ });
411
+ // ---- column-management panel state (lifted here so hiding a column doesn't unmount it) ----
412
+ const [manageOpen, setManageOpen] = useState(false);
413
+ const [manageAnchorId, setManageAnchorId] = useState(null);
414
+ const [columnManageMenuOpenId, setColumnManageMenuOpenId] = useState(null);
415
+ const [managePanelPos, setManagePanelPos] = useState({ top: 0, right: 0, maxListHeight: 400 });
416
+ const manageMenuTriggerRef = useRef(new Map());
417
+ const openManagePanelAt = useCallback((colId, anchorRect) => {
418
+ const PANEL_W = 460;
419
+ const MARGIN = 8;
420
+ setColumnManageMenuOpenId(null);
421
+ const idealRight = window.innerWidth - anchorRect.right;
422
+ const right = Math.min(Math.max(idealRight, MARGIN), window.innerWidth - PANEL_W - MARGIN);
423
+ const top = anchorRect.bottom + MARGIN;
424
+ const maxListHeight = Math.max(80, window.innerHeight - top - 80);
425
+ setManagePanelPos({ top, right, maxListHeight });
426
+ setManageAnchorId(colId);
427
+ setManageOpen(true);
428
+ }, []);
429
+ // Close manage panel on Escape
430
+ useEffect(() => {
431
+ if (!manageOpen)
432
+ return;
433
+ const onKey = (e) => {
434
+ if (e.key === "Escape")
435
+ setManageOpen(false);
436
+ };
437
+ document.addEventListener("keydown", onKey);
438
+ return () => document.removeEventListener("keydown", onKey);
439
+ }, [manageOpen]);
440
+ // Sync controlled pagination (server mode)
441
+ useEffect(() => {
442
+ if (paginationMode === "server") {
443
+ setPagination({
444
+ pageIndex: controlledPageIndex !== null && controlledPageIndex !== void 0 ? controlledPageIndex : 0,
445
+ pageSize: controlledPageSize !== null && controlledPageSize !== void 0 ? controlledPageSize : 10,
446
+ });
447
+ }
448
+ }, [paginationMode, controlledPageIndex, controlledPageSize]);
449
+ // ---- editing engine ----
450
+ const editableColIds = React.useMemo(() => editableColumnIdsProp !== null && editableColumnIdsProp !== void 0 ? editableColumnIdsProp : (enableEditing
451
+ ? detectEditableColumnIds(columns)
452
+ : []), [enableEditing, editableColumnIdsProp, columns]);
453
+ const editingState = useDataTableEditing({
454
+ enabled: enableEditing,
455
+ editDisplayMode,
456
+ editTrigger,
457
+ editableColumnIds: editableColIds,
458
+ });
459
+ // ---- build final columns ----
460
+ const finalColumns = React.useMemo(() => {
461
+ let cols;
462
+ if (enableEditing) {
463
+ cols = resolveEditableColumns(columns, {
464
+ editing: editingState,
465
+ onCellCommit: onCellCommit,
466
+ alwaysEditing: alwaysEditing,
467
+ enableCellTabTraversal: enableCellTabTraversal !== null && enableCellTabTraversal !== void 0 ? enableCellTabTraversal : true,
468
+ editableColumnIds: editableColIds,
469
+ });
470
+ }
471
+ else {
472
+ cols = [...columns];
473
+ }
474
+ if (reorderable)
475
+ cols.unshift(buildReorderColumn());
476
+ if (selectable)
477
+ cols.unshift(buildCheckboxColumn());
478
+ if (rowActions)
479
+ cols.push(buildActionsColumn(rowActions));
480
+ return cols;
481
+ }, [
17
482
  columns,
18
- manualSorting,
19
- onSortingChange: setSorting,
20
- onColumnFiltersChange: setColumnFilters,
21
- getCoreRowModel: getCoreRowModel(),
22
- // getPaginationRowModel: getPaginationRowModel(),
23
- getSortedRowModel: getSortedRowModel(),
24
- getFilteredRowModel: getFilteredRowModel(),
25
- onColumnVisibilityChange: setColumnVisibility,
26
- onRowSelectionChange: setRowSelection,
27
- state: {
28
- sorting,
483
+ selectable,
484
+ reorderable,
485
+ rowActions,
486
+ enableEditing,
487
+ editingState,
488
+ onCellCommit,
489
+ alwaysEditing,
490
+ enableCellTabTraversal,
491
+ ]);
492
+ // ---- table instance ----
493
+ const isPaginated = paginationMode === "client" || paginationMode === "server";
494
+ const table = useReactTable(Object.assign(Object.assign(Object.assign(Object.assign({ data, columns: finalColumns, columnResizeMode: "onChange", defaultColumn: {
495
+ minSize: columnMinSize,
496
+ maxSize: columnMaxSize,
497
+ } }, (getRowIdProp
498
+ ? { getRowId: (row) => getRowIdProp(row) }
499
+ : { getRowId: (row) => row.id })), { manualSorting, manualPagination: paginationMode === "server", rowCount: paginationMode === "server" ? totalCount !== null && totalCount !== void 0 ? totalCount : data.length : undefined, getSubRows, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, onColumnVisibilityChange: setColumnVisibility, onColumnOrderChange: setColumnOrder, onColumnSizingChange: setColumnSizing, onColumnSizingInfoChange: setColumnSizingInfo, onRowSelectionChange: setRowSelection, onExpandedChange: (updater) => {
500
+ const next = typeof updater === "function" ? updater(expanded) : updater;
501
+ if (!isExpandedControlled)
502
+ setInternalExpanded(next);
503
+ onExpandedChange === null || onExpandedChange === void 0 ? void 0 : onExpandedChange(next);
504
+ }, onPaginationChange: (updater) => {
505
+ const next = typeof updater === "function" ? updater(pagination) : updater;
506
+ setPagination(next);
507
+ onPaginationChange === null || onPaginationChange === void 0 ? void 0 : onPaginationChange(next);
508
+ }, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), getExpandedRowModel: getExpandedRowModel() }), (isPaginated ? { getPaginationRowModel: getPaginationRowModel() } : {})), { enableRowSelection: selectable, enableColumnResizing: resizable, state: Object.assign({ sorting,
29
509
  columnFilters,
30
510
  columnVisibility,
511
+ columnOrder,
512
+ columnSizing,
513
+ columnSizingInfo,
31
514
  rowSelection,
32
- // pagination: {
33
- // pageSize: 100,
34
- // pageIndex: 0,
35
- // },
36
- },
37
- });
515
+ expanded }, (isPaginated ? { pagination } : {})) }));
516
+ /** Body row order: sortable rows follow header sort; locked rows stay at the bottom. */
517
+ const tableRowsForView = React.useMemo(() => {
518
+ const rows = table.getRowModel().rows;
519
+ if (!isRowReorderLocked)
520
+ return rows;
521
+ const locked = rows.filter((r) => isRowReorderLocked(r));
522
+ const unlocked = rows.filter((r) => !isRowReorderLocked(r));
523
+ if (locked.length === 0)
524
+ return rows;
525
+ const order = new Map(data.map((row, index) => [
526
+ getRowIdProp
527
+ ? getRowIdProp(row)
528
+ : String(row.id),
529
+ index,
530
+ ]));
531
+ locked.sort((a, b) => { var _a, _b; return ((_a = order.get(a.id)) !== null && _a !== void 0 ? _a : 0) - ((_b = order.get(b.id)) !== null && _b !== void 0 ? _b : 0); });
532
+ return [...unlocked, ...locked];
533
+ }, [table.getRowModel().rows, isRowReorderLocked, data, getRowIdProp]);
534
+ // ---- side-effects ----
38
535
  useEffect(() => {
39
536
  onSorting === null || onSorting === void 0 ? void 0 : onSorting(sorting);
40
537
  }, [sorting, onSorting]);
41
538
  useEffect(() => {
539
+ onRowSelectionChange === null || onRowSelectionChange === void 0 ? void 0 : onRowSelectionChange(rowSelection);
540
+ }, [rowSelection, onRowSelectionChange]);
541
+ // Infinite scroll — listener on the wrapper div
542
+ useEffect(() => {
543
+ if (paginationMode !== "infinite")
544
+ return;
545
+ const el = scrollRef.current;
546
+ if (!el)
547
+ return;
42
548
  const handleScroll = () => {
43
- if (tableBodyRef.current) {
44
- const { scrollTop, scrollHeight, clientHeight } = tableBodyRef.current;
45
- if (scrollTop + clientHeight >= scrollHeight - 10) {
46
- fetchMoreData === null || fetchMoreData === void 0 ? void 0 : fetchMoreData();
47
- }
549
+ if (programmaticScrollLockRef.current > 0)
550
+ return;
551
+ // Do not set userInteractingRef here: programmatic highlight scroll (often
552
+ // `behavior: "smooth"`) emits many scroll events after the programmatic
553
+ // lock unlocks, which would flip userInteracting and block the next follow.
554
+ // Pause is handled only via wheel / pointer when scrollToHighlightOnMouseLeave.
555
+ const { scrollTop, scrollHeight, clientHeight } = el;
556
+ if (fetchingMore || loading)
557
+ return;
558
+ const offset = typeof fetchMoreOffset === "number" ? fetchMoreOffset : 10;
559
+ if (scrollTop + clientHeight >= scrollHeight - offset) {
560
+ fetchMoreData === null || fetchMoreData === void 0 ? void 0 : fetchMoreData();
48
561
  }
49
562
  };
50
- const tableBodyElement = tableBodyRef.current;
51
- if (tableBodyElement) {
52
- tableBodyElement.addEventListener("scroll", handleScroll);
563
+ el.addEventListener("scroll", handleScroll);
564
+ return () => el.removeEventListener("scroll", handleScroll);
565
+ }, [paginationMode, fetchMoreData, fetchingMore, loading, fetchMoreOffset]);
566
+ const isEmpty = tableRowsForView.length === 0;
567
+ // Virtualizer row offsets must match scroll coordinates: tbody starts below <thead>.
568
+ useLayoutEffect(() => {
569
+ if (!virtualized)
570
+ return;
571
+ const el = tableHeaderRef.current;
572
+ if (!el)
573
+ return;
574
+ const update = () => {
575
+ setStickyHeaderHeight(Math.round(el.getBoundingClientRect().height));
576
+ };
577
+ update();
578
+ const ro = new ResizeObserver(update);
579
+ ro.observe(el);
580
+ return () => ro.disconnect();
581
+ }, [virtualized]);
582
+ const scrollHighlightIntoViewRef = useRef(() => { });
583
+ const hadVirtualScrollSizeRef = useRef(false);
584
+ const rowVirtualizer = useVirtualizer({
585
+ count: virtualized ? tableRowsForView.length : 0,
586
+ getScrollElement: () => scrollRef.current,
587
+ estimateSize: () => virtualRowEstimate !== null && virtualRowEstimate !== void 0 ? virtualRowEstimate : 40,
588
+ overscan: 4,
589
+ // Offsets must include sticky thead height so item.start matches document offsets inside the scrollport.
590
+ paddingStart: virtualized ? stickyHeaderHeight : 0,
591
+ scrollPaddingStart: 0,
592
+ measureElement: virtualMeasureRowElement,
593
+ onChange: () => {
594
+ var _a, _b;
595
+ if (!virtualized || !highlightRowId)
596
+ return;
597
+ const h = (_b = (_a = scrollRef.current) === null || _a === void 0 ? void 0 : _a.clientHeight) !== null && _b !== void 0 ? _b : 0;
598
+ if (h < 1) {
599
+ hadVirtualScrollSizeRef.current = false;
600
+ return;
601
+ }
602
+ if (hadVirtualScrollSizeRef.current)
603
+ return;
604
+ hadVirtualScrollSizeRef.current = true;
605
+ requestAnimationFrame(() => {
606
+ if (userInteractingRef.current)
607
+ return;
608
+ scrollHighlightIntoViewRef.current("auto");
609
+ });
610
+ },
611
+ });
612
+ const scrollHighlightIntoView = useCallback((behavior = "smooth") => {
613
+ var _a, _b;
614
+ if (!highlightRowId)
615
+ return;
616
+ if (userInteractingRef.current)
617
+ return;
618
+ const container = scrollRef.current;
619
+ if (!container)
620
+ return;
621
+ const ids = Array.isArray(highlightRowId)
622
+ ? highlightRowId
623
+ : [highlightRowId];
624
+ // Virtualized: off-screen rows are not in the DOM, so querySelector fails.
625
+ // Drive scroll via the virtualizer so the target row mounts, then DOM path can fine-tune if needed.
626
+ if (virtualized) {
627
+ void rowVirtualizer.getTotalSize();
628
+ for (const id of ids) {
629
+ const idx = tableRowsForView.findIndex((r) => r.id === id);
630
+ if (idx !== -1) {
631
+ // `scrollToIndex({ align: "center" })` aligns item *start* to viewport center
632
+ // in TanStack Virtual; also thead was throwing off scroll coords — use
633
+ // paddingStart + explicit center of row in the scrollport.
634
+ const estimate = virtualRowEstimate !== null && virtualRowEstimate !== void 0 ? virtualRowEstimate : 40;
635
+ const startOffset = rowVirtualizer.getOffsetForIndex(idx, "start");
636
+ if (!startOffset)
637
+ continue;
638
+ const [scrollTopAlignStart] = startOffset;
639
+ const vzWithCache = rowVirtualizer;
640
+ const measured = (_b = (_a = vzWithCache.measurementsCache) === null || _a === void 0 ? void 0 : _a[idx]) === null || _b === void 0 ? void 0 : _b.size;
641
+ const rowH = typeof measured === "number" ? measured : estimate;
642
+ const target = scrollTopAlignStart + rowH / 2 - container.clientHeight / 2;
643
+ programmaticScrollLockRef.current += 1;
644
+ scheduleProgrammaticScrollEnd();
645
+ rowVirtualizer.scrollToOffset(Math.max(0, target), {
646
+ align: "start",
647
+ behavior: behavior === "smooth" ? "smooth" : "auto",
648
+ });
649
+ return;
650
+ }
651
+ }
652
+ return;
53
653
  }
54
- return () => {
55
- if (tableBodyElement) {
56
- tableBodyElement.removeEventListener("scroll", handleScroll);
654
+ for (const id of ids) {
655
+ const rowEl = container.querySelector(`[data-row-id="${id}"]`);
656
+ if (rowEl) {
657
+ programmaticScrollLockRef.current += 1;
658
+ scheduleProgrammaticScrollEnd();
659
+ rowEl.scrollIntoView({ block: "center", behavior });
660
+ break;
57
661
  }
662
+ }
663
+ }, [
664
+ highlightRowId,
665
+ tableRowsForView,
666
+ virtualized,
667
+ rowVirtualizer,
668
+ virtualRowEstimate,
669
+ stickyHeaderHeight,
670
+ scheduleProgrammaticScrollEnd,
671
+ ]);
672
+ scrollHighlightIntoViewRef.current = scrollHighlightIntoView;
673
+ // Allow one onChange retry when the highlight target or virtualization toggles
674
+ // (e.g. scrollport height was 0 on first layout).
675
+ useLayoutEffect(() => {
676
+ hadVirtualScrollSizeRef.current = false;
677
+ }, [virtualized, highlightRowId]);
678
+ // Scroll to highlighted row(s) when the value changes (e.g. user picks one).
679
+ // When `scrollToHighlightOnMouseLeave` is set, auto-scroll is skipped while
680
+ // `userInteractingRef` is true (wheel / pointer on table only; not raw scroll).
681
+ useLayoutEffect(() => {
682
+ scrollHighlightIntoView("smooth");
683
+ }, [virtualized, scrollHighlightIntoView, highlightRowId]);
684
+ // ---- pagination bar props ----
685
+ const paginationBarProps = {
686
+ pageIndex: pagination.pageIndex,
687
+ pageSize: pagination.pageSize,
688
+ totalCount: paginationMode === "server"
689
+ ? totalCount !== null && totalCount !== void 0 ? totalCount : data.length
690
+ : table.getFilteredRowModel().rows.length,
691
+ pageSizeOptions,
692
+ onPageChange: (idx) => table.setPagination((prev) => (Object.assign(Object.assign({}, prev), { pageIndex: idx }))),
693
+ onPageSizeChange: (size) => table.setPagination({ pageIndex: 0, pageSize: size }),
694
+ };
695
+ const computedFlexWidth = useFlexColumnWidth(table);
696
+ const flexWidth = tableLayout === "equal" ? computedFlexWidth : undefined;
697
+ const fixedColStyles = React.useMemo(() => {
698
+ if (tableLayout !== "fixed")
699
+ return null;
700
+ const cols = table.getVisibleLeafColumns();
701
+ const isPxExactCol = (col) => {
702
+ var _a;
703
+ const ew = (_a = col.columnDef.meta) === null || _a === void 0 ? void 0 : _a.exactWidth;
704
+ return exactWidthToPx(ew) != null;
58
705
  };
59
- }, [fetchMoreData]);
60
- const isEmpty = ((_a = table.getRowModel().rows) === null || _a === void 0 ? void 0 : _a.length) === 0;
61
- return (_jsx("div", { className: "flex w-full h-full rounded-xl overflow-hidden border border-primary-10", children: _jsxs(Table, { className: isEmpty ? "h-full" : "", rootRef: tableBodyRef, children: [_jsx(TableHeader, { className: "sticky top-0", children: table.getHeaderGroups().map((headerGroup) => (_jsx(TableRow, { className: "", children: headerGroup.headers.map((header, i) => {
62
- var _a;
63
- return (_jsx(TableHead, { children: _jsxs("div", { className: "flex flex-row items-center cursor-pointer", onClick: header.column.getToggleSortingHandler(), children: [header.isPlaceholder
64
- ? null
65
- : flexRender(header.column.columnDef.header, header.getContext()), (_a = {
66
- asc: _jsx(ArrowUpIcon, { className: "ml-3 h-4 w-4" }),
67
- desc: _jsx(ArrowDownIcon, { className: "ml-3 h-4 w-4" }),
68
- }[header.column.getIsSorted()]) !== null && _a !== void 0 ? _a : (header.column.getCanSort() ? (_jsx(ArrowsUpDownIcon, { className: "ml-3 h-4 w-4 text-text-g-contrast-high" })) : null)] }) }, header.id));
69
- }) }, headerGroup.id))) }), _jsx(TableBody, { className: "overflow-y-scroll", children: !isEmpty ? (table.getRowModel().rows.map((row) => (_jsx(TableRow, { "data-state": row.getIsSelected() && "selected", className: "", children: row.getVisibleCells().map((cell) => (_jsx(TableCell, { children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))) }, row.id)))) : (_jsx(TableRow, { className: "h-full self-stretch", children: _jsx(TableCell, { colSpan: columns.length, className: "typography-body1 text-text-g-contrast-medium text-center h-full", children: _jsxs("div", { className: "flex flex-1 h-full flex-col items-center justify-center gap-3", children: [_jsx(ClipboardDocumentListIcon, { className: "w-8 text-secondary-120" }), "There is no information yet."] }) }) })) })] }) }));
706
+ // Full-width table + honour `size` as px: lock every non-exact column to
707
+ // its `size`, except the last “flex” column which absorbs leftover width
708
+ // (avoids `calc()` on every <col>/<th> browsers often distribute badly).
709
+ const flexColIndices = cols
710
+ .map((c, i) => (!isPxExactCol(c) ? i : -1))
711
+ .filter((i) => i >= 0);
712
+ const growColIndex = flexColIndices.length > 0
713
+ ? flexColIndices[flexColIndices.length - 1]
714
+ : -1;
715
+ const map = new Map();
716
+ cols.forEach((col, index) => {
717
+ var _a, _b, _c;
718
+ if (isPxExactCol(col))
719
+ return;
720
+ if (index === growColIndex) {
721
+ // No entry → <col> has no width; cell styles omit width — column grows.
722
+ return;
723
+ }
724
+ const colSize = (_c = (_a = col.columnDef.size) !== null && _a !== void 0 ? _a : (_b = col.getSize) === null || _b === void 0 ? void 0 : _b.call(col)) !== null && _c !== void 0 ? _c : 150;
725
+ const px = typeof colSize === "number"
726
+ ? colSize
727
+ : Number.isFinite(parseFloat(String(colSize)))
728
+ ? parseFloat(String(colSize))
729
+ : 150;
730
+ map.set(col.id, {
731
+ width: `${px}px`,
732
+ minWidth: `${px}px`,
733
+ maxWidth: `${px}px`,
734
+ overflow: "hidden",
735
+ boxSizing: "border-box",
736
+ });
737
+ });
738
+ return map;
739
+ }, [tableLayout, table.getVisibleLeafColumns()]);
740
+ /**
741
+ * When `resizable` + `table-layout: fixed`, the table is often wider than
742
+ * `getTotalSize()` (`max(100%, …)`). Browsers then redistribute slack across
743
+ * *all* columns — even `<col>` + `th` locked to 42px can visually grow.
744
+ * Pick one non-exact column to absorb slack (empty `<col>`, no pixel width on
745
+ * cells) like the non-resizable `fixed` “grow” column. Not used for `equal`
746
+ * — there we leave non-exact `<col>`s empty and use `flexWidth` on cells only.
747
+ */
748
+ const resizableSlackGrowColId = React.useMemo(() => {
749
+ var _a, _b;
750
+ if (!resizable || tableLayout === "equal")
751
+ return null;
752
+ const cols = table.getVisibleLeafColumns();
753
+ const flexIndices = cols
754
+ .map((c, i) => {
755
+ var _a;
756
+ const ew = (_a = c.columnDef.meta) === null || _a === void 0 ? void 0 : _a.exactWidth;
757
+ return exactWidthToPx(ew) == null ? i : -1;
758
+ })
759
+ .filter((i) => i >= 0);
760
+ if (flexIndices.length === 0)
761
+ return null;
762
+ return (_b = (_a = cols[flexIndices[flexIndices.length - 1]]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null;
763
+ }, [resizable, tableLayout, table.getVisibleLeafColumns()]);
764
+ // Helper: find the first data cell (not checkbox/actions) for expand toggle
765
+ const firstDataCellId = (row) => {
766
+ var _a;
767
+ return (_a = row
768
+ .getVisibleCells()
769
+ .find((c) => c.column.id !== "__select__" &&
770
+ c.column.id !== "__actions__" &&
771
+ c.column.id !== "__reorder__")) === null || _a === void 0 ? void 0 : _a.id;
772
+ };
773
+ // ---- row reorder (drag-and-drop) ----
774
+ const rowReorderSensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, {
775
+ coordinateGetter: sortableKeyboardCoordinates,
776
+ }));
777
+ const rowIds = React.useMemo(() => tableRowsForView.filter((r) => !(isRowReorderLocked === null || isRowReorderLocked === void 0 ? void 0 : isRowReorderLocked(r))).map((r) => r.id), [tableRowsForView, isRowReorderLocked]);
778
+ const handleRowDragEnd = (event) => {
779
+ const { active, over } = event;
780
+ if (!over || active.id === over.id || !onRowReorder)
781
+ return;
782
+ const oldIndex = data.findIndex((item) => (getRowIdProp
783
+ ? getRowIdProp(item)
784
+ : item.id) === active.id);
785
+ const newIndex = data.findIndex((item) => (getRowIdProp
786
+ ? getRowIdProp(item)
787
+ : item.id) === over.id);
788
+ if (oldIndex === -1 || newIndex === -1)
789
+ return;
790
+ onRowReorder(arrayMove([...data], oldIndex, newIndex));
791
+ };
792
+ // `resizable` uses `table-layout: fixed`; without min/max on `meta.exactWidth`
793
+ // (e.g. __select__), those columns absorb extra table width even when inline
794
+ // `width: 42px` is set. Lock px-based exactWidth whenever fixed layout applies.
795
+ const lockPxExactWidth = tableLayout === "fixed" || resizable;
796
+ const showColumnMenuVisibility = columnManagement && columnManagementOptions.visibility !== false;
797
+ const visibleHideableLeafCount = columnManagement
798
+ ? table
799
+ .getAllLeafColumns()
800
+ .filter((c) => c.getCanHide() && c.getIsVisible()).length
801
+ : 0;
802
+ // ---- render ----
803
+ const editContextValue = enableEditing ? editingState : null;
804
+ return (_jsx(EditContext.Provider, { value: editContextValue, children: _jsxs("div", { className: cn("flex flex-col w-full h-full min-h-0", bordered && "overflow-hidden rounded-md border border-table-c-border", !bordered && "overflow-hidden rounded-none", className), "data-surface": surface === "panel" ? "panel" : undefined, "data-testid": testId, children: [_jsx("div", { ref: scrollRef, className: cn("relative min-h-0 overflow-auto", "ui-scrollbar ui-scrollbar-x-m ui-scrollbar-y-s", isPaginated ? "flex-1" : "h-full"), onWheel: scrollToHighlightOnMouseLeave
805
+ ? () => {
806
+ userInteractingRef.current = true;
807
+ }
808
+ : undefined, onPointerDownCapture: scrollToHighlightOnMouseLeave
809
+ ? () => {
810
+ userInteractingRef.current = true;
811
+ }
812
+ : undefined, onMouseLeave: scrollToHighlightOnMouseLeave
813
+ ? () => {
814
+ userInteractingRef.current = false;
815
+ scrollHighlightIntoView("smooth");
816
+ }
817
+ : undefined, children: _jsxs(Table, { scrollableWrapper: false, className: cn((tableLayout === "fixed" ||
818
+ tableLayout === "equal" ||
819
+ resizable) &&
820
+ "table-fixed", isEmpty && "h-full"), style: resizable
821
+ ? {
822
+ // At least full scroll width, but grow when column sum exceeds it.
823
+ width: `max(100%, ${table.getTotalSize()}px)`,
824
+ }
825
+ : { width: "100%" }, children: [tableLayout === "fixed" && !resizable && (_jsx("colgroup", { children: table.getVisibleLeafColumns().map((col) => {
826
+ const colDef = col.columnDef;
827
+ const exact = getExactWidthStyle(colDef, lockPxExactWidth);
828
+ const style = exact !== null && exact !== void 0 ? exact : fixedColStyles === null || fixedColStyles === void 0 ? void 0 : fixedColStyles.get(col.id);
829
+ return _jsx("col", { style: style }, col.id);
830
+ }) })), resizable && (_jsx("colgroup", { children: table.getVisibleLeafColumns().map((col) => {
831
+ const colDef = col.columnDef;
832
+ const exact = getExactWidthStyle(colDef, lockPxExactWidth);
833
+ if (exact)
834
+ return _jsx("col", { style: exact }, col.id);
835
+ // Equal: only lock exact cols; flex columns share via flexWidth on th/td.
836
+ if (tableLayout === "equal")
837
+ return _jsx("col", {}, col.id);
838
+ if (resizableSlackGrowColId != null &&
839
+ col.id === resizableSlackGrowColId) {
840
+ return _jsx("col", {}, col.id);
841
+ }
842
+ return _jsx("col", { style: { width: col.getSize() } }, col.id);
843
+ }) })), _jsx(TableHeader, { ref: tableHeaderRef, className: cn("sticky top-0 z-10", headerClassName), "data-testid": testId ? `${testId}-thead` : undefined, children: table.getHeaderGroups().map((headerGroup) => (_jsx(TableRow, { divided: false, colDivided: divided, className: cn("hover:bg-transparent", headerRowClassName), children: headerGroup.headers.map((header) => {
844
+ var _a;
845
+ const canSort = header.column.getCanSort();
846
+ const isSorted = header.column.getIsSorted();
847
+ const showManage = columnManagement && header.column.getCanHide();
848
+ const canResize = resizable &&
849
+ tableLayout !== "equal" &&
850
+ header.column.getCanResize();
851
+ const isResizing = header.column.getIsResizing();
852
+ const fixedFallback = !resizable && fixedColStyles
853
+ ? fixedColStyles.get(header.column.id)
854
+ : undefined;
855
+ const usePixelResizeWidth = resizable &&
856
+ tableLayout !== "equal" &&
857
+ !(resizableSlackGrowColId != null &&
858
+ header.column.id === resizableSlackGrowColId);
859
+ const headerStyle = resolveHeaderWidthStyle(header.column.columnDef, flexWidth, fixedFallback, lockPxExactWidth, usePixelResizeWidth ? header.getSize() : undefined);
860
+ return (_jsxs(TableHead, { colSpan: header.colSpan, style: headerStyle, className: cn("relative overflow-visible group/col whitespace-pre", isResizing && "select-none", (_a = header.column.columnDef.meta) === null || _a === void 0 ? void 0 : _a.headerCellClassName, typeof headerCellClassName === "function"
861
+ ? headerCellClassName(header)
862
+ : headerCellClassName), children: [_jsxs("div", { className: "flex flex-row items-center gap-1 group/header", children: [_jsx("span", { className: cn("flex flex-1 flex-row items-center overflow-hidden", canSort && "cursor-pointer select-none", columnMetaAlignClass(header.column.columnDef.meta)), onClick: header.column.getToggleSortingHandler(), children: header.isPlaceholder
863
+ ? null
864
+ : flexRender(header.column.columnDef.header, header.getContext()) }), canSort && (_jsx(ActionButton, { variant: "icon", size: "xs", className: cn("shrink-0 transition-opacity", isSorted
865
+ ? "opacity-100"
866
+ : sortIndicatorVisibility === "always"
867
+ ? "opacity-100"
868
+ : "opacity-0 group-hover/header:opacity-100"), onClick: header.column.getToggleSortingHandler(), "aria-label": isSorted === "asc"
869
+ ? "Sorted ascending — click to sort descending"
870
+ : isSorted === "desc"
871
+ ? "Sorted descending — click to clear sort"
872
+ : "Sort column", children: isSorted === "asc" ? (_jsx(ArrowUp, { className: "!size-4" })) : isSorted === "desc" ? (_jsx(ArrowDown, { className: "!size-4" })) : (_jsx(ArrowUpDown, { className: "!size-4 text-text-g-contrast-medium" })) })), showManage && (_jsxs(DropdownMenu, { open: columnManageMenuOpenId === header.column.id, onOpenChange: (open) => setColumnManageMenuOpenId(open ? header.column.id : null), modal: false, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(ActionButton, { variant: "icon", size: "sm", active: (manageOpen &&
873
+ manageAnchorId === header.column.id) ||
874
+ columnManageMenuOpenId === header.column.id, "aria-haspopup": "menu", "aria-expanded": columnManageMenuOpenId === header.column.id, "aria-label": "Column options", className: "shrink-0", ref: (el) => {
875
+ if (el) {
876
+ manageMenuTriggerRef.current.set(header.column.id, el);
877
+ }
878
+ else {
879
+ manageMenuTriggerRef.current.delete(header.column.id);
880
+ }
881
+ }, onClick: (e) => e.stopPropagation(), children: _jsx(EllipsisVertical, { className: "!size-4" }) }) }), _jsxs(DropdownMenuContent, { align: "end", sideOffset: 4, className: "min-w-[220px] py-1", onCloseAutoFocus: (e) => e.preventDefault(), children: [showColumnMenuVisibility && (_jsxs(DropdownMenuItem, { className: "py-3", disabled: header.column.getIsVisible() &&
882
+ visibleHideableLeafCount <= 1, icon: _jsx(EyeOff, { className: "size-4", "aria-hidden": true }), onSelect: () => {
883
+ header.column.toggleVisibility(false);
884
+ }, children: ["Hide ", manageColumnHeaderLabel(header), " ", "column"] })), _jsx(DropdownMenuItem, { className: "py-3", icon: _jsx(Columns3, { className: "size-4", "aria-hidden": true }), onSelect: () => {
885
+ requestAnimationFrame(() => {
886
+ const el = manageMenuTriggerRef.current.get(header.column.id);
887
+ if (el) {
888
+ openManagePanelAt(header.column.id, el.getBoundingClientRect());
889
+ }
890
+ });
891
+ }, children: "Manage columns" })] })] }))] }), canResize && (_jsx("div", { onMouseDown: header.getResizeHandler(document), onTouchStart: header.getResizeHandler(document), onClick: (e) => e.stopPropagation(), className: cn("absolute right-0 top-0 h-full w-[5px] z-10", "cursor-col-resize select-none touch-none", "flex items-center justify-center group/resize"), children: _jsx("div", { className: cn("h-4/5 w-1.5 rounded-full transition-all duration-150", isResizing
892
+ ? "opacity-100 w-0.5 bg-primary-500"
893
+ : "opacity-0 bg-table-c-col-line group-hover/col:opacity-100 group-hover/resize:bg-primary-400") }) }))] }, header.id));
894
+ }) }, headerGroup.id))) }), _jsx(TableBody, { striped: striped, "data-testid": testId ? `${testId}-tbody` : undefined, children: !isEmpty ? (virtualized ? ((() => {
895
+ var _a;
896
+ const virtualItems = rowVirtualizer.getVirtualItems();
897
+ if (!virtualItems.length)
898
+ return null;
899
+ // paddingStart models thead height in *scroll* coords; tbody spacers are only
900
+ // for rows inside tbody — subtract it or the first row is pushed down twice.
901
+ const top = Math.max(0, virtualItems[0].start - stickyHeaderHeight);
902
+ const bottom = rowVirtualizer.getTotalSize() -
903
+ ((_a = virtualItems[virtualItems.length - 1].end) !== null && _a !== void 0 ? _a : 0);
904
+ return (_jsxs(_Fragment, { children: [top > 0 && (_jsx("tr", { "aria-hidden": true, children: _jsx("td", { colSpan: finalColumns.length, style: { height: top } }) })), virtualItems.map((item) => {
905
+ const row = tableRowsForView[item.index];
906
+ const isExpandable = Boolean(getSubRows);
907
+ const rowBg = isExpandable && !striped
908
+ ? "bg-table-bg-a"
909
+ : undefined;
910
+ const isHighlighted = Array.isArray(highlightRowId)
911
+ ? highlightRowId.includes(row.id)
912
+ : highlightRowId === row.id;
913
+ const rowProps = {
914
+ divided: isExpandable && !striped ? true : !striped,
915
+ colDivided: divided,
916
+ "data-state": row.getIsSelected()
917
+ ? "selected"
918
+ : undefined,
919
+ "data-highlighted": isHighlighted
920
+ ? "true"
921
+ : undefined,
922
+ className: cn(rowBg, onRowClick && "cursor-pointer", typeof rowClassName === "function"
923
+ ? rowClassName(row, item.index)
924
+ : rowClassName),
925
+ onClick: onRowClick
926
+ ? (e) => onRowClick(row, e)
927
+ : undefined,
928
+ };
929
+ return (_jsx(TableRow, Object.assign({ ref: rowVirtualizer.measureElement, "data-index": item.index, "data-row-id": row.id }, rowProps, { children: renderDataTableBodyCells({
930
+ row,
931
+ getSubRows,
932
+ firstDataCellId,
933
+ fixedColStyles,
934
+ flexWidth,
935
+ lockPxExactWidth,
936
+ resizable,
937
+ tableLayout,
938
+ resizableSlackGrowColId,
939
+ cellClassName,
940
+ onCellClick,
941
+ }) }), row.id));
942
+ }), bottom > 0 && (_jsx("tr", { "aria-hidden": true, children: _jsx("td", { colSpan: finalColumns.length, style: { height: bottom } }) }))] }));
943
+ })()) : reorderable ? (_jsx(DndContext, { sensors: rowReorderSensors, collisionDetection: closestCenter, modifiers: [restrictToVerticalAxis], onDragEnd: handleRowDragEnd, children: _jsx(SortableContext, { items: rowIds, strategy: verticalListSortingStrategy, children: tableRowsForView.map((row, rowIndex) => {
944
+ const isExpandable = Boolean(getSubRows);
945
+ const rowBg = isExpandable && !striped
946
+ ? "bg-table-bg-a"
947
+ : undefined;
948
+ const locked = isRowReorderLocked === null || isRowReorderLocked === void 0 ? void 0 : isRowReorderLocked(row);
949
+ const isHighlighted = Array.isArray(highlightRowId)
950
+ ? highlightRowId.includes(row.id)
951
+ : highlightRowId === row.id;
952
+ const rowProps = {
953
+ divided: isExpandable && !striped ? true : !striped,
954
+ colDivided: divided,
955
+ "data-state": row.getIsSelected()
956
+ ? "selected"
957
+ : undefined,
958
+ "data-highlighted": isHighlighted
959
+ ? "true"
960
+ : undefined,
961
+ className: cn(rowBg, onRowClick && "cursor-pointer", typeof rowClassName === "function"
962
+ ? rowClassName(row, rowIndex)
963
+ : rowClassName),
964
+ onClick: onRowClick
965
+ ? (e) => onRowClick(row, e)
966
+ : undefined,
967
+ };
968
+ const cells = renderDataTableBodyCells({
969
+ row,
970
+ getSubRows,
971
+ firstDataCellId,
972
+ fixedColStyles,
973
+ flexWidth,
974
+ lockPxExactWidth,
975
+ resizable,
976
+ tableLayout,
977
+ resizableSlackGrowColId,
978
+ cellClassName,
979
+ onCellClick,
980
+ });
981
+ return locked ? (_jsx(TableRow, Object.assign({ "data-row-id": row.id }, rowProps, { children: cells }), row.id)) : (_jsx(SortableTableRow, Object.assign({ id: row.id, "data-row-id": row.id }, rowProps, { children: cells }), row.id));
982
+ }) }) })) : (tableRowsForView.map((row, rowIndex) => {
983
+ const isExpandable = Boolean(getSubRows);
984
+ // Non-striped expandable: solid bg-a on every row
985
+ const rowBg = isExpandable && !striped ? "bg-table-bg-a" : undefined;
986
+ const isHighlighted = Array.isArray(highlightRowId)
987
+ ? highlightRowId.includes(row.id)
988
+ : highlightRowId === row.id;
989
+ return (_jsx(TableRow, { divided: isExpandable && !striped ? true : !striped, colDivided: divided, "data-state": row.getIsSelected() ? "selected" : undefined, className: cn(rowBg, onRowClick && "cursor-pointer", typeof rowClassName === "function"
990
+ ? rowClassName(row, rowIndex)
991
+ : rowClassName), "data-highlighted": isHighlighted ? "true" : undefined, onClick: onRowClick
992
+ ? (e) => onRowClick(row, e)
993
+ : undefined, "data-row-id": row.id, children: renderDataTableBodyCells({
994
+ row,
995
+ getSubRows,
996
+ firstDataCellId,
997
+ fixedColStyles,
998
+ flexWidth,
999
+ lockPxExactWidth,
1000
+ resizable,
1001
+ tableLayout,
1002
+ resizableSlackGrowColId,
1003
+ cellClassName,
1004
+ onCellClick,
1005
+ }) }, row.id));
1006
+ }))) : loading ? (_jsx(TableRow, { className: "h-full hover:bg-transparent", divided: false, children: _jsx(TableCell, { colSpan: finalColumns.length, className: "typography-body1 text-text-g-contrast-medium text-center h-full min-h-[200px]", children: _jsxs("div", { className: "flex flex-1 min-h-[200px] h-full flex-col items-center justify-center gap-3 py-16", role: "status", "aria-live": "polite", "aria-busy": "true", children: [_jsx(Loader2, { className: "size-8 shrink-0 animate-spin text-secondary-120", "aria-hidden": true }), loadingLabel] }) }) })) : (_jsx(TableRow, { className: "h-full hover:bg-transparent", divided: false, children: _jsx(TableCell, { colSpan: finalColumns.length, className: "typography-body1 text-text-g-contrast-medium text-center h-full", children: _jsxs("div", { className: "flex flex-1 h-full flex-col items-center justify-center gap-3 py-16", children: [_jsx(ClipboardList, { className: "w-8 text-secondary-120" }), "There is no information yet."] }) }) })) }), paginationMode === "infinite" &&
1007
+ fetchingMore &&
1008
+ !isEmpty &&
1009
+ table.getVisibleLeafColumns().length > 0 && (_jsx("tfoot", { className: "[&_tr]:bg-table-c-row-bg", children: _jsx(TableRow, { divided: false, className: "hover:!bg-table-c-row-bg border-t border-t-table-c-row-line", children: _jsx(TableCell, { colSpan: table.getVisibleLeafColumns().length, className: "py-3 text-center typography-body3 text-text-g-contrast-medium bg-inherit", children: _jsxs("span", { role: "status", "aria-live": "polite", className: "inline-flex items-center justify-center gap-2", children: [_jsx(Loader2, { className: "size-4 shrink-0 animate-spin", "aria-hidden": true }), fetchingMoreLabel] }) }) }) }))] }) }), isPaginated && _jsx(TablePagination, Object.assign({}, paginationBarProps)), columnManagement && manageOpen && (_jsxs(Portal.Root, { children: [_jsx("div", { className: "fixed inset-0 z-40", onClick: () => setManageOpen(false) }), _jsx("div", { className: "fixed z-50 w-[460px] rounded-lg bg-modal-surface shadow-[0px_12px_24px_-4px_rgba(0,0,0,0.12)] overflow-hidden", style: {
1010
+ top: managePanelPos.top,
1011
+ right: managePanelPos.right,
1012
+ }, onClick: (e) => e.stopPropagation(), children: _jsx(ManageColumnPanel, { table: table, onClose: () => setManageOpen(false), maxListHeight: managePanelPos.maxListHeight, options: columnManagementOptions }) })] }))] }) }));
70
1013
  }