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