@crisp-ui-kit/crisp 0.54.2 → 0.54.4
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/board.css +13 -1
- package/dist/board.layered.css +13 -1
- package/dist/chip.css +7 -1
- package/dist/chip.d.ts +2 -1
- package/dist/chip.layered.css +7 -1
- package/dist/cjs/components/chip/chip.js +3 -2
- package/dist/cjs/components/chip/chip.recipe.js +37 -1
- package/dist/cjs/components/datatable/datatable.js +489 -111
- package/dist/cjs/components/emptystate/emptystate.js +3 -2
- package/dist/cjs/components/emptystate/emptystate.recipe.js +26 -1
- package/dist/cjs/components/personchip/index.js +17 -0
- package/dist/cjs/components/personchip/personchip.js +50 -0
- package/dist/cjs/components/table/table.js +2 -2
- package/dist/cjs/components/tabletoolbar/tabletoolbar.js +41 -4
- package/dist/cjs/components/tabs/tabs.js +2 -2
- package/dist/cjs/components/tabs/tabs.recipe.js +30 -3
- package/dist/cjs/components/text/index.js +7 -0
- package/dist/cjs/components/text/text.js +55 -0
- package/dist/cjs/components/text/text.recipe.js +34 -0
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/product/board/board.js +60 -2
- package/dist/cjs/product/board/board.recipe.js +10 -0
- package/dist/cjs/product/comments/comments.js +426 -0
- package/dist/cjs/product/comments/comments.recipe.js +126 -0
- package/dist/cjs/product/comments/index.js +8 -0
- package/dist/cjs/product/headerbar/headerbar.recipe.js +9 -1
- package/dist/cjs/product/listpage/listpage.recipe.js +10 -1
- package/dist/cjs/product/objectsettings/index.js +16 -0
- package/dist/cjs/product/objectsettings/objectsettings.js +365 -0
- package/dist/cjs/product/objectsettings/objectsettings.recipe.js +65 -0
- package/dist/cjs/product/recordpage/recordpage.recipe.js +10 -1
- package/dist/cjs/product/recordstable/recordstable.js +89 -6
- package/dist/cjs/product/richtext/richtext.recipe.js +26 -1
- package/dist/cjs/product/taskstable/taskstable.recipe.js +10 -1
- package/dist/comments.css +80 -0
- package/dist/comments.d.ts +7 -0
- package/dist/comments.layered.css +82 -0
- package/dist/emptystate.css +15 -1
- package/dist/emptystate.d.ts +2 -2
- package/dist/emptystate.layered.css +15 -1
- package/dist/esm/components/chip/chip.js +3 -2
- package/dist/esm/components/chip/chip.recipe.js +37 -1
- package/dist/esm/components/datatable/datatable.js +488 -111
- package/dist/esm/components/emptystate/emptystate.js +4 -3
- package/dist/esm/components/emptystate/emptystate.recipe.js +26 -1
- package/dist/esm/components/emptystate/index.js +1 -1
- package/dist/esm/components/personchip/index.js +1 -0
- package/dist/esm/components/personchip/personchip.js +47 -0
- package/dist/esm/components/table/table.js +2 -2
- package/dist/esm/components/tabletoolbar/tabletoolbar.js +43 -6
- package/dist/esm/components/tabs/tabs.js +2 -2
- package/dist/esm/components/tabs/tabs.recipe.js +30 -3
- package/dist/esm/components/text/index.js +2 -0
- package/dist/esm/components/text/text.js +19 -0
- package/dist/esm/components/text/text.recipe.js +31 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/product/board/board.js +61 -3
- package/dist/esm/product/board/board.recipe.js +10 -0
- package/dist/esm/product/comments/comments.js +388 -0
- package/dist/esm/product/comments/comments.recipe.js +123 -0
- package/dist/esm/product/comments/index.js +2 -0
- package/dist/esm/product/headerbar/headerbar.recipe.js +9 -1
- package/dist/esm/product/listpage/listpage.recipe.js +10 -1
- package/dist/esm/product/objectsettings/index.js +2 -0
- package/dist/esm/product/objectsettings/objectsettings.js +322 -0
- package/dist/esm/product/objectsettings/objectsettings.recipe.js +62 -0
- package/dist/esm/product/recordpage/recordpage.recipe.js +10 -1
- package/dist/esm/product/recordstable/recordstable.js +90 -8
- package/dist/esm/product/richtext/richtext.recipe.js +26 -1
- package/dist/esm/product/taskstable/taskstable.recipe.js +10 -1
- package/dist/headerbar.css +1 -1
- package/dist/headerbar.layered.css +1 -1
- package/dist/listpage.css +8 -1
- package/dist/listpage.layered.css +8 -1
- package/dist/objectsettings.css +49 -0
- package/dist/objectsettings.d.ts +7 -0
- package/dist/objectsettings.layered.css +51 -0
- package/dist/recordpage.css +13 -2
- package/dist/recordpage.layered.css +13 -2
- package/dist/richtext.css +11 -1
- package/dist/richtext.layered.css +11 -1
- package/dist/styles.css +224 -10
- package/dist/styles.layered.css +224 -10
- package/dist/tabs.css +7 -1
- package/dist/tabs.d.ts +2 -2
- package/dist/tabs.layered.css +7 -1
- package/dist/taskstable.css +8 -1
- package/dist/taskstable.layered.css +8 -1
- package/dist/text.css +9 -0
- package/dist/text.d.ts +7 -0
- package/dist/text.layered.css +11 -0
- package/dist/types/components/chip/chip.d.ts +32 -0
- package/dist/types/components/chip/chip.recipe.d.ts +31 -0
- package/dist/types/components/datatable/datatable.d.ts +28 -4
- package/dist/types/components/emptystate/emptystate.d.ts +29 -4
- package/dist/types/components/emptystate/emptystate.recipe.d.ts +2 -0
- package/dist/types/components/emptystate/index.d.ts +1 -1
- package/dist/types/components/personchip/index.d.ts +1 -0
- package/dist/types/components/personchip/personchip.d.ts +37 -0
- package/dist/types/components/table/table.d.ts +36 -0
- package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +8 -2
- package/dist/types/components/tabs/tabs.d.ts +4 -2
- package/dist/types/components/tabs/tabs.recipe.d.ts +18 -3
- package/dist/types/components/text/index.d.ts +2 -0
- package/dist/types/components/text/text.d.ts +32 -0
- package/dist/types/components/text/text.recipe.d.ts +40 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/product/board/board.d.ts +20 -1
- package/dist/types/product/comments/comments.d.ts +192 -0
- package/dist/types/product/comments/comments.recipe.d.ts +3 -0
- package/dist/types/product/comments/index.d.ts +2 -0
- package/dist/types/product/objectsettings/index.d.ts +2 -0
- package/dist/types/product/objectsettings/objectsettings.d.ts +261 -0
- package/dist/types/product/objectsettings/objectsettings.recipe.d.ts +3 -0
- package/dist/types/product/recordstable/recordstable.d.ts +24 -2
- package/package.json +21 -1
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.isLabelTruncated = isLabelTruncated;
|
|
36
37
|
exports.DataTable = DataTable;
|
|
37
38
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
38
39
|
const lucide_react_1 = require("lucide-react");
|
|
@@ -45,7 +46,55 @@ const select_1 = require("../select");
|
|
|
45
46
|
const spinner_1 = require("../spinner");
|
|
46
47
|
const switch_1 = require("../switch");
|
|
47
48
|
const table_1 = require("../table");
|
|
49
|
+
const tooltip_1 = require("../tooltip");
|
|
48
50
|
const datatable_recipe_1 = require("./datatable.recipe");
|
|
51
|
+
// #534: whether an element's own content is being cut off by its own box --
|
|
52
|
+
// the same fact `.crisp-datatable-th-label`'s CSS ellipsis is silently
|
|
53
|
+
// applying, exported so the comparison itself (not layout) can be
|
|
54
|
+
// mutation-tested without a real browser.
|
|
55
|
+
function isLabelTruncated(el) {
|
|
56
|
+
return el.scrollWidth > el.clientWidth;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The header label span: a `Tooltip` (full text, `asChild` so the wrapper
|
|
60
|
+
* never displaces the ellipsis CSS the span already carries -- see
|
|
61
|
+
* `Tooltip`'s own `asChild` doc) permanently closed until the label actually
|
|
62
|
+
* overflows its own box. Re-measures on mount and on resize (column drag,
|
|
63
|
+
* viewport change, header text change) via `ResizeObserver`; SSR-safe the
|
|
64
|
+
* same way `MiddleTruncate` is -- the DOM is only touched inside an effect,
|
|
65
|
+
* so the server (and the first client paint) render the closed tooltip.
|
|
66
|
+
*/
|
|
67
|
+
function ThLabel({ header }) {
|
|
68
|
+
const ref = React.useRef(null);
|
|
69
|
+
const [overflowing, setOverflowing] = React.useState(false);
|
|
70
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: `header` isn't read in the body, but a rename (col.header changing with the column's own width unchanged) needs a re-measure a ResizeObserver on `el` won't fire for -- same reasoning as Tabs' own re-measure effect.
|
|
71
|
+
React.useLayoutEffect(() => {
|
|
72
|
+
const el = ref.current;
|
|
73
|
+
if (!el)
|
|
74
|
+
return;
|
|
75
|
+
const measure = () => setOverflowing(isLabelTruncated(el));
|
|
76
|
+
measure();
|
|
77
|
+
if (typeof ResizeObserver === "undefined")
|
|
78
|
+
return;
|
|
79
|
+
const ro = new ResizeObserver(measure);
|
|
80
|
+
ro.observe(el);
|
|
81
|
+
return () => ro.disconnect();
|
|
82
|
+
}, [header]);
|
|
83
|
+
// `Tooltip` always wraps the SAME span (never swapped in/out of the tree
|
|
84
|
+
// based on `overflowing`) so the span's own DOM node identity survives
|
|
85
|
+
// every toggle -- a real-browser-only bug (happy-dom has no
|
|
86
|
+
// ResizeObserver, so the test suite never exercised this path): swapping
|
|
87
|
+
// between a bare `<span>` and `<Tooltip asChild><span/></Tooltip>` changes
|
|
88
|
+
// the element type at this position, so React unmounts the old span and
|
|
89
|
+
// mounts a new one; the effect above never re-runs (its deps are
|
|
90
|
+
// `[header]`, unchanged), so its ResizeObserver keeps watching the now
|
|
91
|
+
// -detached old node, whose deferred initial callback then reports 0x0 and
|
|
92
|
+
// flips `overflowing` straight back to false -- the tooltip would never
|
|
93
|
+
// actually show. `open={false}` when it fits keeps the Tooltip mounted
|
|
94
|
+
// (asChild costs no extra DOM either way) but pins it permanently closed,
|
|
95
|
+
// so hovering a non-overflowing header still opens nothing.
|
|
96
|
+
return ((0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { content: header, asChild: true, open: overflowing ? undefined : false, children: (0, jsx_runtime_1.jsx)("span", { ref: ref, className: "crisp-datatable-th-label", children: header }) }));
|
|
97
|
+
}
|
|
49
98
|
/** A `date` input needs `yyyy-mm-dd`; cells may hold a Date, an ISO string or
|
|
50
99
|
* a display string. Returns "" when it cannot be parsed, so the field opens
|
|
51
100
|
* empty rather than showing an invalid value. */
|
|
@@ -250,6 +299,51 @@ function renderTyped(col, value, avatarTone, row, display, locale = "en-US") {
|
|
|
250
299
|
}
|
|
251
300
|
}
|
|
252
301
|
const CHECK_W = 40;
|
|
302
|
+
// crisp/#577 -- cell-level virtualization on both axes.
|
|
303
|
+
//
|
|
304
|
+
// ROW_HEIGHT/GROUP_ROW_HEIGHT must track table.recipe.ts's `c.size.tableRow`
|
|
305
|
+
// (36px) and datatable.recipe.ts's `.crisp-datatable-group` height, which
|
|
306
|
+
// reads `c.size.menuRow` (32px) -- there is no way to read a recipe's CSS
|
|
307
|
+
// value back out in JS, so these are a second, deliberately-commented copy.
|
|
308
|
+
// A drift between the two would only show up as visual jitter (a spacer's
|
|
309
|
+
// height not quite matching the rows it stands in for), never a crash, which
|
|
310
|
+
// is exactly the kind of silent drift AUTHORING.md Lesson 31 warns about.
|
|
311
|
+
const ROW_HEIGHT = 36;
|
|
312
|
+
const GROUP_ROW_HEIGHT = 32;
|
|
313
|
+
// Overscan is a performance cushion, not a measured value -- Attio's own
|
|
314
|
+
// Companies grid (measured live, #577's own issue body) rendered 24 rows for
|
|
315
|
+
// what a 715px-tall viewport fits at 36px/row (~20), i.e. a handful either
|
|
316
|
+
// side; these are the same shape of cushion, not a parity target.
|
|
317
|
+
const ROW_OVERSCAN = 6;
|
|
318
|
+
const COL_OVERSCAN = 2;
|
|
319
|
+
// The row window a server render (or a client's very first paint, before a
|
|
320
|
+
// real measurement lands) assumes when `height` is a CSS string rather than
|
|
321
|
+
// a plain pixel number -- see the `height` prop doc on why a string can't be
|
|
322
|
+
// turned into an exact row count up front.
|
|
323
|
+
const DEFAULT_VIRTUAL_VIEWPORT_PX = 480;
|
|
324
|
+
/**
|
|
325
|
+
* Cumulative `offsets` has length n+1 (offsets[i] is where item i STARTS,
|
|
326
|
+
* offsets[n] is the total). Returns the index i (clamped to [0, n-1]) whose
|
|
327
|
+
* span contains `target` -- i.e. the largest i with offsets[i] <= target.
|
|
328
|
+
* Shared by the row and column windowing below; a plain linear scan would
|
|
329
|
+
* also be correct but is the wrong complexity for a 10,000-row table
|
|
330
|
+
* recomputed on every scroll event.
|
|
331
|
+
*/
|
|
332
|
+
function offsetIndex(offsets, target) {
|
|
333
|
+
const n = offsets.length - 1;
|
|
334
|
+
if (n <= 0)
|
|
335
|
+
return 0;
|
|
336
|
+
let lo = 0;
|
|
337
|
+
let hi = n - 1;
|
|
338
|
+
while (lo < hi) {
|
|
339
|
+
const mid = (lo + hi + 1) >> 1;
|
|
340
|
+
if (offsets[mid] <= target)
|
|
341
|
+
lo = mid;
|
|
342
|
+
else
|
|
343
|
+
hi = mid - 1;
|
|
344
|
+
}
|
|
345
|
+
return lo;
|
|
346
|
+
}
|
|
253
347
|
// A sentinel that can't collide with a real record id (crisp never generates
|
|
254
348
|
// one, and a consumer's own ids come from their own data store) -- picking
|
|
255
349
|
// this row calls `onCreateReference`, not the cell's own commit (#449).
|
|
@@ -337,7 +431,7 @@ function DataReferenceEditor({ col, value, isPending, onCommit, onCancel, }) {
|
|
|
337
431
|
onCommit(v);
|
|
338
432
|
} }) }));
|
|
339
433
|
}
|
|
340
|
-
function DataTable({ columns: columnsProp, data, selectable = false, selectedIds, onSelectionChange, selectAllLabel: selectAllLabelProp, selectRowLabel = (row) => `Select row ${row.id}`, selectionAnnouncement, columnMenu = false, onColumnsChange, groupBy, onRowsReorder, error, sort: sortProp, defaultSort, onSortChange, filterable = false, searchPlaceholder: searchPlaceholderProp, searchLabel: searchLabelProp, onEdit, stickyFirst = true, stickyLast = false, scrollProps, locale: localeProp, avatarTone = "brand", surface, interactive = false, onRowClick, onAddRow, addRowLabel: addRowLabelProp, empty: emptyProp, showCount = true, countLabel, toolbar, className, reorderRowLabel: reorderRowLabelProp, sortAscendingLabel: sortAscendingLabelProp, sortDescendingLabel: sortDescendingLabelProp, moveColumnLeftLabel: moveColumnLeftLabelProp, moveColumnRightLabel: moveColumnRightLabelProp, editColumnLabel: editColumnLabelProp, hideColumnLabel: hideColumnLabelProp, renameColumnLabel: renameColumnLabelProp, viewSettingsLabel: viewSettingsLabelProp, columnsGroupLabel: columnsGroupLabelProp, ...rest }) {
|
|
434
|
+
function DataTable({ columns: columnsProp, data, selectable = false, selectedIds, onSelectionChange, selectAllLabel: selectAllLabelProp, selectRowLabel = (row) => `Select row ${row.id}`, selectionAnnouncement, columnMenu = false, onColumnsChange, groupBy, onRowsReorder, error, sort: sortProp, defaultSort, onSortChange, filterable = false, searchPlaceholder: searchPlaceholderProp, searchLabel: searchLabelProp, onEdit, stickyFirst = true, stickyLast = false, scrollProps, locale: localeProp, avatarTone = "brand", surface, interactive = false, onRowClick, onAddRow, addRowLabel: addRowLabelProp, empty: emptyProp, showCount = true, countLabel, toolbar, height, className, reorderRowLabel: reorderRowLabelProp, sortAscendingLabel: sortAscendingLabelProp, sortDescendingLabel: sortDescendingLabelProp, moveColumnLeftLabel: moveColumnLeftLabelProp, moveColumnRightLabel: moveColumnRightLabelProp, editColumnLabel: editColumnLabelProp, hideColumnLabel: hideColumnLabelProp, renameColumnLabel: renameColumnLabelProp, viewSettingsLabel: viewSettingsLabelProp, columnsGroupLabel: columnsGroupLabelProp, ...rest }) {
|
|
341
435
|
// crisp/#471: same resolution order as TableToolbar's own — an explicit
|
|
342
436
|
// prop wins, then the provider's translation of the built-in English
|
|
343
437
|
// default, then that default itself with no provider in the tree.
|
|
@@ -479,21 +573,52 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
479
573
|
const [pending, setPending] = React.useState(null);
|
|
480
574
|
const [editError, setEditError] = React.useState(null);
|
|
481
575
|
const [scrolledX, setScrolledX] = React.useState(false);
|
|
482
|
-
// Mirrors `scrolledX` for the trailing edge: true while there is still
|
|
483
|
-
// unrevealed content sliding under the sticky trailing column, i.e. while
|
|
484
|
-
// NOT scrolled all the way to the end. Recomputed after every render (see
|
|
485
|
-
// the effect below), not just on scroll, because a column resize or a data
|
|
486
|
-
// change can move `scrollWidth` without firing a scroll event.
|
|
487
|
-
const [scrolledXEnd, setScrolledXEnd] = React.useState(false);
|
|
488
576
|
const scrollRef = React.useRef(null);
|
|
577
|
+
// crisp/#577 -- the scroll container's own position/size, read on scroll
|
|
578
|
+
// and on mount/resize. `w` (viewport width) is ALWAYS tracked, since column
|
|
579
|
+
// virtualization doesn't need `height` -- the grid already has a
|
|
580
|
+
// determinate horizontal scroll container. `h` only matters once `height`
|
|
581
|
+
// is set; its initial value is the "first window" SSR/first-paint renders
|
|
582
|
+
// (see the `height` prop doc): a plain-number `height` gets an exact
|
|
583
|
+
// viewport up front, a CSS-string one gets `DEFAULT_VIRTUAL_VIEWPORT_PX`
|
|
584
|
+
// until the real size is measured after mount. `w` starts at `Infinity` --
|
|
585
|
+
// "not yet measured" reads as "render every column", the same as today,
|
|
586
|
+
// rather than guessing a width and risking a visible column pop after
|
|
587
|
+
// hydration.
|
|
588
|
+
const [viewport, setViewport] = React.useState(() => ({
|
|
589
|
+
top: 0,
|
|
590
|
+
left: 0,
|
|
591
|
+
h: typeof height === "number"
|
|
592
|
+
? height
|
|
593
|
+
: height != null
|
|
594
|
+
? DEFAULT_VIRTUAL_VIEWPORT_PX
|
|
595
|
+
: Number.POSITIVE_INFINITY,
|
|
596
|
+
w: Number.POSITIVE_INFINITY,
|
|
597
|
+
}));
|
|
598
|
+
// Client-only measurement: re-reads the scroll container's real box on
|
|
599
|
+
// mount and whenever it resizes (a window resize, a sidebar toggling, a
|
|
600
|
+
// consumer's own layout change) -- none of which fire a `scroll` event.
|
|
601
|
+
// Never runs during SSR (effects don't run there at all), which is what
|
|
602
|
+
// keeps the "first window" guess above the only thing a server ever
|
|
603
|
+
// renders.
|
|
489
604
|
React.useEffect(() => {
|
|
490
|
-
if (!stickyLast)
|
|
491
|
-
return;
|
|
492
605
|
const el = scrollRef.current;
|
|
493
606
|
if (!el)
|
|
494
607
|
return;
|
|
495
|
-
|
|
496
|
-
|
|
608
|
+
const measure = () => {
|
|
609
|
+
setViewport((v) => ({
|
|
610
|
+
...v,
|
|
611
|
+
h: height != null ? el.clientHeight : v.h,
|
|
612
|
+
w: el.clientWidth,
|
|
613
|
+
}));
|
|
614
|
+
};
|
|
615
|
+
measure();
|
|
616
|
+
if (typeof ResizeObserver === "undefined")
|
|
617
|
+
return;
|
|
618
|
+
const ro = new ResizeObserver(measure);
|
|
619
|
+
ro.observe(el);
|
|
620
|
+
return () => ro.disconnect();
|
|
621
|
+
}, [height]);
|
|
497
622
|
// column widths (drag-to-resize) + fixed-layout sizing
|
|
498
623
|
const [widths, setWidths] = React.useState({});
|
|
499
624
|
const [resizingKey, setResizingKey] = React.useState(null);
|
|
@@ -502,6 +627,33 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
502
627
|
const minWidth = (showGrip ? GRIP_W : 0) +
|
|
503
628
|
(selectable ? CHECK_W : 0) +
|
|
504
629
|
columns.reduce((sum, col) => sum + colWidth(col), 0);
|
|
630
|
+
// Mirrors `scrolledX` for the trailing edge: true while there is still
|
|
631
|
+
// unrevealed content sliding under the sticky trailing column, i.e. while
|
|
632
|
+
// NOT scrolled all the way to the end. Recomputed whenever `minWidth`
|
|
633
|
+
// changes (a column resize or the column set changing), not just on
|
|
634
|
+
// scroll, since either can move `scrollWidth` without firing a scroll
|
|
635
|
+
// event. This used to run after EVERY render (no dependency array at
|
|
636
|
+
// all) -- harmless on its own, but crisp/#577's own `viewport` effects
|
|
637
|
+
// (below) also read layout and call `setViewport` on their own schedule,
|
|
638
|
+
// and the two together resonated: each render's `setScrolledXEnd`
|
|
639
|
+
// triggered another render, which reran this effect, which could
|
|
640
|
+
// observe a still-settling layout and set a DIFFERENT value, which
|
|
641
|
+
// triggered yet another render -- "Maximum update depth exceeded",
|
|
642
|
+
// caught reviewing the virtualized docs example (the first place this
|
|
643
|
+
// repo ever exercises `stickyLast` in a real browser; the existing
|
|
644
|
+
// jsdom tests never hit real layout, so this never surfaced there).
|
|
645
|
+
// `minWidth` is exactly what this comparison's `scrollWidth` side
|
|
646
|
+
// tracks, so it's the one dependency that actually matters.
|
|
647
|
+
const [scrolledXEnd, setScrolledXEnd] = React.useState(false);
|
|
648
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: minWidth is what scrollWidth tracks, see comment above -- not read directly in the body
|
|
649
|
+
React.useEffect(() => {
|
|
650
|
+
if (!stickyLast)
|
|
651
|
+
return;
|
|
652
|
+
const el = scrollRef.current;
|
|
653
|
+
if (!el)
|
|
654
|
+
return;
|
|
655
|
+
setScrolledXEnd(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
|
|
656
|
+
}, [stickyLast, minWidth]);
|
|
505
657
|
const colByKey = React.useMemo(() => {
|
|
506
658
|
const m = new Map();
|
|
507
659
|
for (const c of columns)
|
|
@@ -562,6 +714,110 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
562
714
|
rows: groupRows,
|
|
563
715
|
}));
|
|
564
716
|
}, [rows, groupBy]);
|
|
717
|
+
const renderItems = React.useMemo(() => {
|
|
718
|
+
const items = [];
|
|
719
|
+
for (const { group, rows: groupRows } of groupedRows) {
|
|
720
|
+
if (group !== null) {
|
|
721
|
+
items.push({
|
|
722
|
+
kind: "group",
|
|
723
|
+
key: `__group_${group}`,
|
|
724
|
+
label: group,
|
|
725
|
+
count: groupRows.length,
|
|
726
|
+
height: GROUP_ROW_HEIGHT,
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
for (const row of groupRows)
|
|
730
|
+
items.push({ kind: "row", key: row.id, row, height: ROW_HEIGHT });
|
|
731
|
+
}
|
|
732
|
+
return items;
|
|
733
|
+
}, [groupedRows]);
|
|
734
|
+
const rowOffsets = React.useMemo(() => {
|
|
735
|
+
const offsets = new Array(renderItems.length + 1);
|
|
736
|
+
offsets[0] = 0;
|
|
737
|
+
for (let i = 0; i < renderItems.length; i++)
|
|
738
|
+
offsets[i + 1] = offsets[i] + renderItems[i].height;
|
|
739
|
+
return offsets;
|
|
740
|
+
}, [renderItems]);
|
|
741
|
+
const totalRowsHeight = rowOffsets[rowOffsets.length - 1] ?? 0;
|
|
742
|
+
// O(1) lookup from a row's id to its position in `renderItems` -- used by
|
|
743
|
+
// the keyboard-nav scroll-into-view effect below, which knows a row by id
|
|
744
|
+
// (from `selCell`), not by its index in the flattened item list.
|
|
745
|
+
const rowIdToItemIndex = React.useMemo(() => {
|
|
746
|
+
const m = new Map();
|
|
747
|
+
renderItems.forEach((it, i) => {
|
|
748
|
+
if (it.kind === "row")
|
|
749
|
+
m.set(it.row.id, i);
|
|
750
|
+
});
|
|
751
|
+
return m;
|
|
752
|
+
}, [renderItems]);
|
|
753
|
+
// `height == null` (the default) makes this a no-op: `viewport.h` starts at
|
|
754
|
+
// `Infinity`, so the visible range below covers every item, both spacers
|
|
755
|
+
// come out to 0px, and neither renders (see the `> 0` guards further down)
|
|
756
|
+
// -- the DOM is byte-identical to before virtualization existed.
|
|
757
|
+
const rowFirstVisible = renderItems.length === 0 ? 0 : offsetIndex(rowOffsets, viewport.top);
|
|
758
|
+
const rowLastVisible = renderItems.length === 0
|
|
759
|
+
? 0
|
|
760
|
+
: offsetIndex(rowOffsets, Math.max(viewport.top, viewport.top + viewport.h - 1));
|
|
761
|
+
const rowStart = Math.max(0, rowFirstVisible - ROW_OVERSCAN);
|
|
762
|
+
const rowEnd = Math.min(renderItems.length, rowLastVisible + 1 + ROW_OVERSCAN);
|
|
763
|
+
const topSpacerHeight = rowOffsets[rowStart] ?? 0;
|
|
764
|
+
const bottomSpacerHeight = totalRowsHeight - (rowOffsets[rowEnd] ?? 0);
|
|
765
|
+
const visibleItems = renderItems.slice(rowStart, rowEnd);
|
|
766
|
+
// crisp/#577 -- COLUMN virtualization. Always on (no `height` needed): the
|
|
767
|
+
// sticky-pinned columns (the leading `columns[0]` when `stickyFirst`, the
|
|
768
|
+
// trailing column when `stickyLast`) are excluded from the window and
|
|
769
|
+
// always render, exactly like Attio's own checkbox + entity columns while
|
|
770
|
+
// its own grid scrolls under them. Coordinates below are in the SAME
|
|
771
|
+
// horizontal axis `scrollLeft` uses: a sticky column doesn't leave layout
|
|
772
|
+
// flow (only its paint position pins), so its width still occupies real
|
|
773
|
+
// space in that axis.
|
|
774
|
+
const colAlwaysFirst = stickyFirst && columns.length > 0;
|
|
775
|
+
const colAlwaysLastIdx = stickyLast &&
|
|
776
|
+
columns.length > 0 &&
|
|
777
|
+
!(colAlwaysFirst && columns.length === 1)
|
|
778
|
+
? columns.length - 1
|
|
779
|
+
: -1;
|
|
780
|
+
const colWindowStart = colAlwaysFirst ? 1 : 0;
|
|
781
|
+
const colWindowEnd = colAlwaysLastIdx >= 0 ? colAlwaysLastIdx : columns.length;
|
|
782
|
+
const colLeadingFixedWidth = (showGrip ? GRIP_W : 0) +
|
|
783
|
+
(selectable ? CHECK_W : 0) +
|
|
784
|
+
(colAlwaysFirst ? colWidth(columns[0]) : 0);
|
|
785
|
+
// Cumulative widths of just the WINDOWABLE columns, relative to their own
|
|
786
|
+
// start (not the table's) -- kept separate from the absolute scroll axis
|
|
787
|
+
// so `leftSpacerWidth + rightSpacerWidth + (visible widths)` provably sums
|
|
788
|
+
// to the windowable total no matter how many leading fixed columns there are.
|
|
789
|
+
const colRelOffsets = [0];
|
|
790
|
+
for (let i = colWindowStart; i < colWindowEnd; i++)
|
|
791
|
+
colRelOffsets.push(colRelOffsets[colRelOffsets.length - 1] + colWidth(columns[i]));
|
|
792
|
+
const colWindowableCount = colWindowEnd - colWindowStart;
|
|
793
|
+
const colTargetLeft = Math.max(0, Math.min(colRelOffsets[colWindowableCount] ?? 0, viewport.left - colLeadingFixedWidth));
|
|
794
|
+
const colTargetRight = Math.max(0, Math.min(colRelOffsets[colWindowableCount] ?? 0, viewport.left - colLeadingFixedWidth + viewport.w - 1));
|
|
795
|
+
const colFirstVisibleK = colWindowableCount === 0 ? 0 : offsetIndex(colRelOffsets, colTargetLeft);
|
|
796
|
+
const colLastVisibleK = colWindowableCount === 0 ? 0 : offsetIndex(colRelOffsets, colTargetRight);
|
|
797
|
+
const colStartK = Math.max(0, colFirstVisibleK - COL_OVERSCAN);
|
|
798
|
+
const colEndK = Math.min(colWindowableCount, colLastVisibleK + 1 + COL_OVERSCAN);
|
|
799
|
+
const leftSpacerWidth = colRelOffsets[colStartK] ?? 0;
|
|
800
|
+
const rightSpacerWidth = (colRelOffsets[colWindowableCount] ?? 0) - (colRelOffsets[colEndK] ?? 0);
|
|
801
|
+
const colSlots = [];
|
|
802
|
+
if (colAlwaysFirst)
|
|
803
|
+
colSlots.push({ type: "col", i: 0 });
|
|
804
|
+
if (leftSpacerWidth > 0)
|
|
805
|
+
colSlots.push({ type: "spacer", side: "left", width: leftSpacerWidth });
|
|
806
|
+
for (let i = colWindowStart + colStartK; i < colWindowStart + colEndK; i++)
|
|
807
|
+
colSlots.push({ type: "col", i });
|
|
808
|
+
if (rightSpacerWidth > 0)
|
|
809
|
+
colSlots.push({ type: "spacer", side: "right", width: rightSpacerWidth });
|
|
810
|
+
if (colAlwaysLastIdx >= 0)
|
|
811
|
+
colSlots.push({ type: "col", i: colAlwaysLastIdx });
|
|
812
|
+
// aria-rowcount/-rowindex, aria-colcount/-colindex (#577): a virtualized
|
|
813
|
+
// native `<table>` still exposes its TRUE size and each real cell's TRUE
|
|
814
|
+
// position to assistive tech, even though most rows/columns aren't in the
|
|
815
|
+
// DOM at any moment -- the spacer/placeholder cells are `aria-hidden`
|
|
816
|
+
// instead of numbered. `+1` header row, 1-based indices per the ARIA spec.
|
|
817
|
+
const ariaRowCount = 1 + renderItems.length;
|
|
818
|
+
const ariaColCount = (showGrip ? 1 : 0) + (selectable ? 1 : 0) + columns.length;
|
|
819
|
+
const ariaColIndexBase = (showGrip ? 1 : 0) + (selectable ? 1 : 0);
|
|
820
|
+
const colAriaIndex = (i) => ariaColIndexBase + i + 1;
|
|
565
821
|
const allSelected = rows.length > 0 && rows.every((r) => selected.has(r.id));
|
|
566
822
|
const someSelected = selected.size > 0 && !allSelected;
|
|
567
823
|
function toggleAll() {
|
|
@@ -713,6 +969,72 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
713
969
|
}
|
|
714
970
|
setEditing(null);
|
|
715
971
|
}
|
|
972
|
+
// crisp/#577 -- keyboard nav to an off-screen (virtualized-out) cell
|
|
973
|
+
// scrolls it into the rendered window, on both axes. Column scroll-into-
|
|
974
|
+
// view runs regardless of `height` -- the horizontal scroll container (and
|
|
975
|
+
// its own virtualization) exists either way; row scroll-into-view only
|
|
976
|
+
// makes sense once `height` gives the grid an internal vertical scroll
|
|
977
|
+
// position to move. `widths` is read only indirectly, through `colWidth`'s
|
|
978
|
+
// closure over it -- biome can't see that, but a column resize while a
|
|
979
|
+
// cell is selected genuinely needs to re-run this to keep it in view, so
|
|
980
|
+
// the dependency is real even though the lint rule can't prove it.
|
|
981
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: widths is read via colWidth's closure, see comment above
|
|
982
|
+
React.useEffect(() => {
|
|
983
|
+
if (!selCell)
|
|
984
|
+
return;
|
|
985
|
+
const el = scrollRef.current;
|
|
986
|
+
if (!el)
|
|
987
|
+
return;
|
|
988
|
+
// Sets `viewport` directly from the SAME value just assigned to the
|
|
989
|
+
// real DOM property, rather than waiting for a follow-up `scroll` event
|
|
990
|
+
// to round-trip through the `onScroll` handler above -- a real browser
|
|
991
|
+
// does fire one when `scrollTop`/`scrollLeft` is set programmatically,
|
|
992
|
+
// but relying on that would make this effect's own visible result (the
|
|
993
|
+
// newly-selected cell actually rendering) depend on a second render
|
|
994
|
+
// triggered from outside React's own update, which is both slower and
|
|
995
|
+
// one more thing that has to be true for this to work.
|
|
996
|
+
if (height != null) {
|
|
997
|
+
const itemIndex = rowIdToItemIndex.get(selCell.id);
|
|
998
|
+
if (itemIndex != null) {
|
|
999
|
+
const itemTop = rowOffsets[itemIndex];
|
|
1000
|
+
const itemBottom = itemTop + renderItems[itemIndex].height;
|
|
1001
|
+
if (itemTop < el.scrollTop) {
|
|
1002
|
+
el.scrollTop = itemTop;
|
|
1003
|
+
setViewport((v) => ({ ...v, top: itemTop }));
|
|
1004
|
+
}
|
|
1005
|
+
else if (itemBottom > el.scrollTop + el.clientHeight) {
|
|
1006
|
+
const newTop = itemBottom - el.clientHeight;
|
|
1007
|
+
el.scrollTop = newTop;
|
|
1008
|
+
setViewport((v) => ({ ...v, top: newTop }));
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
const ci = columns.findIndex((c) => c.key === selCell.key);
|
|
1013
|
+
if (ci >= 0) {
|
|
1014
|
+
let left = (showGrip ? GRIP_W : 0) + (selectable ? CHECK_W : 0);
|
|
1015
|
+
for (let i = 0; i < ci; i++)
|
|
1016
|
+
left += colWidth(columns[i]);
|
|
1017
|
+
const right = left + colWidth(columns[ci]);
|
|
1018
|
+
if (left < el.scrollLeft) {
|
|
1019
|
+
el.scrollLeft = left;
|
|
1020
|
+
setViewport((v) => ({ ...v, left }));
|
|
1021
|
+
}
|
|
1022
|
+
else if (right > el.scrollLeft + el.clientWidth) {
|
|
1023
|
+
const newLeft = right - el.clientWidth;
|
|
1024
|
+
el.scrollLeft = newLeft;
|
|
1025
|
+
setViewport((v) => ({ ...v, left: newLeft }));
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}, [
|
|
1029
|
+
selCell,
|
|
1030
|
+
height,
|
|
1031
|
+
columns,
|
|
1032
|
+
widths,
|
|
1033
|
+
selectable,
|
|
1034
|
+
rowIdToItemIndex,
|
|
1035
|
+
rowOffsets,
|
|
1036
|
+
renderItems,
|
|
1037
|
+
]);
|
|
716
1038
|
const totalCols = columns.length + (selectable ? 1 : 0) + (showGrip ? 1 : 0);
|
|
717
1039
|
const hasSummary = columns.some((c) => c.summary);
|
|
718
1040
|
// sticky attrs for the leading column(s): grip col (index -2) + selection
|
|
@@ -817,19 +1139,51 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
817
1139
|
return dir === "asc" ? ((0, jsx_runtime_1.jsx)(lucide_react_1.ArrowUp, { className: "crisp-datatable-chevron", size: 13, strokeWidth: 2 })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.ArrowDown, { className: "crisp-datatable-chevron", size: 13, strokeWidth: 2 }));
|
|
818
1140
|
return ((0, jsx_runtime_1.jsx)(lucide_react_1.ChevronsUpDown, { className: "crisp-datatable-chevron", size: 13, strokeWidth: 2 }));
|
|
819
1141
|
}
|
|
820
|
-
return ((0, jsx_runtime_1.jsxs)("div", { ref: gridRef, className: (0, datatable_recipe_1.datatableRecipe)({ className }), tabIndex: -1, onKeyDown: onGridKeyDown, ...rest, children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-datatable-live", role: "status", "aria-live": "polite", children: selectionText }), (filterable || showCount || toolbar || columnMenu) && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-datatable-toolbar", children: [columnMenu && ((0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "start", trigger: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-datatable-viewsettings", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.SlidersHorizontal, { size: 14, strokeWidth: 1.75 }), viewSettingsLabel] }), children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-datatable-group-label", children: columnsGroupLabel }), allColumns.map((c) => ((0, jsx_runtime_1.jsxs)(menu_1.MenuItem, { onSelect: () => c.hidden ? showColumn(c.key) : hideColumn(c.key), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: 14, strokeWidth: 2, style: { opacity: c.hidden ? 0 : 1 } }), typeof c.header === "string" ? c.header : c.key] }, c.key)))] })), filterable && ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-search", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)("input", { type: "search", placeholder: searchPlaceholder, value: query, onChange: (e) => setQuery(e.target.value), "aria-label": searchLabel })] })), showCount && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-count", children: resolvedCountLabel(rows.length, data.length) })), toolbar] })), (0, jsx_runtime_1.jsxs)(table_1.Table, { interactive: interactive, surface: surface,
|
|
1142
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ref: gridRef, className: (0, datatable_recipe_1.datatableRecipe)({ className }), tabIndex: -1, onKeyDown: onGridKeyDown, ...rest, children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-datatable-live", role: "status", "aria-live": "polite", children: selectionText }), (filterable || showCount || toolbar || columnMenu) && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-datatable-toolbar", children: [columnMenu && ((0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "start", trigger: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-datatable-viewsettings", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.SlidersHorizontal, { size: 14, strokeWidth: 1.75 }), viewSettingsLabel] }), children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-datatable-group-label", children: columnsGroupLabel }), allColumns.map((c) => ((0, jsx_runtime_1.jsxs)(menu_1.MenuItem, { onSelect: () => c.hidden ? showColumn(c.key) : hideColumn(c.key), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: 14, strokeWidth: 2, style: { opacity: c.hidden ? 0 : 1 } }), typeof c.header === "string" ? c.header : c.key] }, c.key)))] })), filterable && ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-search", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)("input", { type: "search", placeholder: searchPlaceholder, value: query, onChange: (e) => setQuery(e.target.value), "aria-label": searchLabel })] })), showCount && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-count", children: resolvedCountLabel(rows.length, data.length) })), toolbar] })), (0, jsx_runtime_1.jsxs)(table_1.Table, { interactive: interactive, surface: surface,
|
|
1143
|
+
// crisp/#581: on the <table> itself (`tableStyle`), not the outer
|
|
1144
|
+
// wrapper (`style`) -- see `Table`'s own doc comment on why. This is
|
|
1145
|
+
// what lets the grid actually scroll horizontally INSIDE its own
|
|
1146
|
+
// box instead of widening its consumer's page.
|
|
1147
|
+
tableStyle: { minWidth }, "aria-rowcount": ariaRowCount, "aria-colcount": ariaColCount, scrollProps: {
|
|
821
1148
|
...scrollProps,
|
|
822
1149
|
ref: scrollRef,
|
|
1150
|
+
// crisp/#577: `height` opts into an internally-scrolling viewport
|
|
1151
|
+
// (and so into row virtualization, see the prop's own doc) by
|
|
1152
|
+
// bounding + capping overflow here -- merged onto, not replacing,
|
|
1153
|
+
// whatever style a consumer already passed for #554's sticky
|
|
1154
|
+
// header/footer bounding.
|
|
1155
|
+
style: {
|
|
1156
|
+
...scrollProps?.style,
|
|
1157
|
+
...(height != null
|
|
1158
|
+
? { overflowY: "auto", height, maxHeight: height }
|
|
1159
|
+
: {}),
|
|
1160
|
+
},
|
|
823
1161
|
onScroll: (e) => {
|
|
824
1162
|
const el = e.currentTarget;
|
|
825
1163
|
setScrolledX(el.scrollLeft > 0);
|
|
826
1164
|
if (stickyLast) {
|
|
827
1165
|
setScrolledXEnd(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
|
|
828
1166
|
}
|
|
1167
|
+
// Re-reads clientHeight/clientWidth here too, not just
|
|
1168
|
+
// scrollTop/scrollLeft -- a defensive fallback for whenever
|
|
1169
|
+
// ResizeObserver isn't available (or hasn't fired yet): any
|
|
1170
|
+
// scroll re-syncs the viewport size along with its position,
|
|
1171
|
+
// rather than trusting a possibly-stale mount-time measurement.
|
|
1172
|
+
setViewport((v) => ({
|
|
1173
|
+
top: el.scrollTop,
|
|
1174
|
+
left: el.scrollLeft,
|
|
1175
|
+
h: height != null ? el.clientHeight : v.h,
|
|
1176
|
+
w: el.clientWidth,
|
|
1177
|
+
}));
|
|
829
1178
|
},
|
|
830
1179
|
...(scrolledX ? { "data-scrolled-x": "" } : {}),
|
|
831
1180
|
...(scrolledXEnd ? { "data-scrolled-x-end": "" } : {}),
|
|
832
|
-
}, children: [(0, jsx_runtime_1.jsxs)("colgroup", { children: [showGrip && (0, jsx_runtime_1.jsx)("col", { style: { width: GRIP_W } }), selectable && (0, jsx_runtime_1.jsx)("col", { style: { width: CHECK_W } }),
|
|
1181
|
+
}, children: [(0, jsx_runtime_1.jsxs)("colgroup", { children: [showGrip && (0, jsx_runtime_1.jsx)("col", { style: { width: GRIP_W } }), selectable && (0, jsx_runtime_1.jsx)("col", { style: { width: CHECK_W } }), colSlots.map((slot) => slot.type === "spacer" ? ((0, jsx_runtime_1.jsx)("col", { style: { width: slot.width } }, `__spacer_${slot.side}`)) : ((0, jsx_runtime_1.jsx)("col", { style: { width: colWidth(columns[slot.i]) } }, columns[slot.i].key)))] }), (0, jsx_runtime_1.jsx)(table_1.TableHeader, { children: (0, jsx_runtime_1.jsxs)(table_1.TableRow, { "aria-rowindex": 1, children: [showGrip && ((0, jsx_runtime_1.jsx)(table_1.TableHead, { className: "crisp-datatable-col-grip", "aria-colindex": showGrip ? 1 : undefined, ...stickyCol(-2) })), selectable && ((0, jsx_runtime_1.jsx)(table_1.TableHead, { className: "crisp-datatable-col-check", "aria-colindex": showGrip ? 2 : 1, ...stickyCol(-1), children: (0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-check", children: (0, jsx_runtime_1.jsx)(checkbox_1.Checkbox, { "aria-label": selectAllLabel, checked: allSelected, indeterminate: someSelected, onChange: toggleAll }) }) })), colSlots.map((slot) => {
|
|
1182
|
+
if (slot.type === "spacer") {
|
|
1183
|
+
return ((0, jsx_runtime_1.jsx)("th", { role: "presentation", "aria-hidden": "true", style: { padding: 0, border: "none" } }, `__spacer_${slot.side}`));
|
|
1184
|
+
}
|
|
1185
|
+
const col = columns[slot.i];
|
|
1186
|
+
const i = slot.i;
|
|
833
1187
|
const Icon = col.icon;
|
|
834
1188
|
// The sort direction was painted (`data-sorted`) and never
|
|
835
1189
|
// announced: a screen reader heard the same header before and
|
|
@@ -845,8 +1199,8 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
845
1199
|
? "ascending"
|
|
846
1200
|
: "descending"
|
|
847
1201
|
: "none";
|
|
848
|
-
const inner = ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-th-inner", children: [Icon ? (0, jsx_runtime_1.jsx)(Icon, { size: 16, strokeWidth: 1.75 }) : null, (0, jsx_runtime_1.jsx)(
|
|
849
|
-
return ((0, jsx_runtime_1.jsxs)(table_1.TableHead, { align: alignFor(col), "aria-sort": ariaSort, ...dataStickyCol(i), children: [renaming === col.key ? ((0, jsx_runtime_1.jsx)("input", {
|
|
1202
|
+
const inner = ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-th-inner", children: [Icon ? (0, jsx_runtime_1.jsx)(Icon, { size: 16, strokeWidth: 1.75 }) : null, (0, jsx_runtime_1.jsx)(ThLabel, { header: col.header }), col.sortable ? (0, jsx_runtime_1.jsx)(ChevronFor, { colKey: col.key }) : null] }));
|
|
1203
|
+
return ((0, jsx_runtime_1.jsxs)(table_1.TableHead, { align: alignFor(col), "aria-sort": ariaSort, "aria-colindex": colAriaIndex(i), ...dataStickyCol(i), children: [renaming === col.key ? ((0, jsx_runtime_1.jsx)("input", {
|
|
850
1204
|
// biome-ignore lint/a11y/noAutofocus: the rename item opens this field
|
|
851
1205
|
autoFocus: true, className: "crisp-datatable-th-rename", defaultValue: typeof col.header === "string" ? col.header : "", "aria-label": renameColumnLabel, onBlur: (e) => {
|
|
852
1206
|
renameColumn(col.key, e.currentTarget.value);
|
|
@@ -861,104 +1215,128 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
861
1215
|
} })) : columnMenu ? ((0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "start", trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-datatable-sort", "data-sorted": sortDirByKey.get(col.key), "aria-label": `${typeof col.header === "string"
|
|
862
1216
|
? col.header
|
|
863
1217
|
: col.key} ${crispMessages["column options"] ?? "column options"}`, children: inner }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => setSort({ key: col.key, dir: "asc" }), children: sortAscendingLabel }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => setSort({ key: col.key, dir: "desc" }), children: sortDescendingLabel }), col.managed === false ? null : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => moveColumn(col.key, -1), children: moveColumnLeftLabel }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => moveColumn(col.key, 1), children: moveColumnRightLabel }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => setRenaming(col.key), children: editColumnLabel }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => hideColumn(col.key), children: hideColumnLabel })] }))] })) : col.sortable ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-datatable-sort", "data-sorted": sortDirByKey.get(col.key), onClick: () => cycleSort(col.key), children: inner })) : (inner), col.resizable !== false && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-resize", "data-resizing": resizingKey === col.key ? "" : undefined, onPointerDown: (e) => startResize(col, e), onPointerMove: moveResize, onPointerUp: endResize }))] }, col.key));
|
|
864
|
-
})] }) }), (0, jsx_runtime_1.jsx)(table_1.TableBody, { children: error ? ((0, jsx_runtime_1.jsx)(table_1.TableRow, { children: (0, jsx_runtime_1.jsx)(table_1.TableCell, { colSpan: totalCols, children: (0, jsx_runtime_1.jsx)("div", { className: "crisp-datatable-empty", children: error }) }) })) : rows.length === 0 ? ((0, jsx_runtime_1.jsx)(table_1.TableRow, { children: (0, jsx_runtime_1.jsx)(table_1.TableCell, { colSpan: totalCols, children: (0, jsx_runtime_1.jsx)("div", { className: "crisp-datatable-empty", children: empty }) }) })) : (
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
setDropRowId(
|
|
886
|
-
}, children: (0, jsx_runtime_1.jsx)(lucide_react_1.GripVertical, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }) })), selectable && ((0, jsx_runtime_1.jsx)(table_1.TableCell, { className: "crisp-datatable-col-check", ...stickyCol(-1), onClick: (e) => e.stopPropagation(), children: (0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-check", children: (0, jsx_runtime_1.jsx)(checkbox_1.Checkbox, { "aria-label": selectRowLabel(row), checked: selected.has(row.id), onChange: () => toggleRow(row.id) }) }) })), columns.map((col, i) => {
|
|
887
|
-
const isEditing = editing?.id === row.id && editing?.key === col.key;
|
|
888
|
-
const value = cellValue(col, row);
|
|
889
|
-
const sticky = dataStickyCol(i);
|
|
890
|
-
const isPending = pending?.id === row.id && pending?.key === col.key;
|
|
891
|
-
const errorHere = editError?.id === row.id && editError?.key === col.key
|
|
892
|
-
? editError.message
|
|
893
|
-
: null;
|
|
894
|
-
// #453: a `switch` editable column IS always its own
|
|
895
|
-
// live widget -- flipping the toggle IS the commit
|
|
896
|
-
// gesture, there's no meaningfully different "rest"
|
|
897
|
-
// display for a boolean. `select`/`reference` are
|
|
898
|
-
// NOT this: at rest they must show the column's own
|
|
899
|
-
// render/format like every other editable type
|
|
900
|
-
// (#412 empty handling included), and only open on
|
|
901
|
-
// double-click (#411) -- this branch previously
|
|
902
|
-
// rendered them unconditionally, replacing every
|
|
903
|
-
// row's own render with a picker trigger, which is
|
|
904
|
-
// the regression #453 reported (also #432/#449,
|
|
905
|
-
// which never had a "rest" state of their own to get
|
|
906
|
-
// right). See the `isEditing` block below for their
|
|
907
|
-
// actual editor.
|
|
908
|
-
if (col.editable && col.editor === "switch") {
|
|
909
|
-
return ((0, jsx_runtime_1.jsxs)(table_1.TableCell, { align: alignFor(col), ...sticky, className: "crisp-datatable-td--picker", "data-pending": isPending ? "" : undefined, children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-edit-picker", children: [(0, jsx_runtime_1.jsx)(switch_1.Switch, { "aria-label": `Edit ${col.key}`, checked: value === true || value === "true", disabled: isPending, onCheckedChange: (checked) => commitEdit(row.id, col.key, String(checked)) }), isPending && ((0, jsx_runtime_1.jsx)(spinner_1.Spinner, { size: 14, className: "crisp-datatable-edit-pending" }))] }), errorHere && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-edit-error", children: errorHere }))] }, col.key));
|
|
1218
|
+
})] }) }), (0, jsx_runtime_1.jsx)(table_1.TableBody, { children: error ? ((0, jsx_runtime_1.jsx)(table_1.TableRow, { children: (0, jsx_runtime_1.jsx)(table_1.TableCell, { colSpan: totalCols, children: (0, jsx_runtime_1.jsx)("div", { className: "crisp-datatable-empty", children: error }) }) })) : rows.length === 0 ? ((0, jsx_runtime_1.jsx)(table_1.TableRow, { children: (0, jsx_runtime_1.jsx)(table_1.TableCell, { colSpan: totalCols, children: (0, jsx_runtime_1.jsx)("div", { className: "crisp-datatable-empty", children: empty }) }) })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [topSpacerHeight > 0 && ((0, jsx_runtime_1.jsx)("tr", { "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)("td", { colSpan: totalCols, style: {
|
|
1219
|
+
height: topSpacerHeight,
|
|
1220
|
+
padding: 0,
|
|
1221
|
+
border: "none",
|
|
1222
|
+
} }) })), visibleItems.map((item, localIndex) => {
|
|
1223
|
+
const itemIndex = rowStart + localIndex;
|
|
1224
|
+
// header row is aria-rowindex 1, so the first body item is 2
|
|
1225
|
+
const ariaRowIndex = itemIndex + 2;
|
|
1226
|
+
if (item.kind === "group") {
|
|
1227
|
+
return ((0, jsx_runtime_1.jsx)(table_1.TableRow, { "aria-rowindex": ariaRowIndex, children: (0, jsx_runtime_1.jsxs)(table_1.TableCell, { colSpan: totalCols, className: "crisp-datatable-group", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-group-label", children: item.label }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-group-count", children: item.count })] }) }, item.key));
|
|
1228
|
+
}
|
|
1229
|
+
const row = item.row;
|
|
1230
|
+
return ((0, jsx_runtime_1.jsxs)(table_1.TableRow, { "aria-rowindex": ariaRowIndex, "data-selected": selected.has(row.id) ? "" : undefined, "data-drop-target": showGrip &&
|
|
1231
|
+
dragRowId &&
|
|
1232
|
+
dragRowId !== row.id &&
|
|
1233
|
+
dropRowId === row.id
|
|
1234
|
+
? ""
|
|
1235
|
+
: undefined, onClick: onRowClick ? () => onRowClick(row) : undefined, style: onRowClick ? { cursor: "pointer" } : undefined, onDragOver: showGrip && dragRowId
|
|
1236
|
+
? (e) => {
|
|
1237
|
+
e.preventDefault();
|
|
1238
|
+
if (dropRowId !== row.id)
|
|
1239
|
+
setDropRowId(row.id);
|
|
910
1240
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
// opening/using the picker must not also fire
|
|
916
|
-
// the row's onRowClick. Escape is handled by
|
|
917
|
-
// `DataSelectEditor`/`DataReferenceEditor`
|
|
918
|
-
// themselves (a document-level listener, not a
|
|
919
|
-
// `TableCell`-level `onKeyDown`) -- opening the
|
|
920
|
-
// picker moves DOM focus into a floating,
|
|
921
|
-
// portaled popover, so a handler here can't
|
|
922
|
-
// rely on the key event ever bubbling through
|
|
923
|
-
// this element (#453).
|
|
924
|
-
onClick: (e) => e.stopPropagation(), children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-edit-picker", children: [col.editor === "select" ? ((0, jsx_runtime_1.jsx)(DataSelectEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commitEdit(row.id, col.key, v), onCancel: () => setEditing(null) })) : ((0, jsx_runtime_1.jsx)(DataReferenceEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commitEdit(row.id, col.key, v), onCancel: () => setEditing(null) })), isPending && ((0, jsx_runtime_1.jsx)(spinner_1.Spinner, { size: 14, className: "crisp-datatable-edit-pending" }))] }), errorHere && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-edit-error", children: errorHere }))] }, col.key));
|
|
1241
|
+
: undefined, onDrop: showGrip && dragRowId
|
|
1242
|
+
? (e) => {
|
|
1243
|
+
e.preventDefault();
|
|
1244
|
+
reorderRows(dragRowId, row.id);
|
|
925
1245
|
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1246
|
+
: undefined, children: [showGrip && ((0, jsx_runtime_1.jsx)(table_1.TableCell, { className: "crisp-datatable-col-grip", "aria-colindex": showGrip ? 1 : undefined, ...stickyCol(-2), onClick: (e) => e.stopPropagation(), children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-datatable-grip", "aria-label": reorderRowLabel, draggable: true, onDragStart: (e) => {
|
|
1247
|
+
setDragRowId(row.id);
|
|
1248
|
+
e.dataTransfer.effectAllowed = "move";
|
|
1249
|
+
}, onDragEnd: () => {
|
|
1250
|
+
setDragRowId(null);
|
|
1251
|
+
setDropRowId(null);
|
|
1252
|
+
}, children: (0, jsx_runtime_1.jsx)(lucide_react_1.GripVertical, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }) })), selectable && ((0, jsx_runtime_1.jsx)(table_1.TableCell, { className: "crisp-datatable-col-check", "aria-colindex": showGrip ? 2 : 1, ...stickyCol(-1), onClick: (e) => e.stopPropagation(), children: (0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-check", children: (0, jsx_runtime_1.jsx)(checkbox_1.Checkbox, { "aria-label": selectRowLabel(row), checked: selected.has(row.id), onChange: () => toggleRow(row.id) }) }) })), colSlots.map((slot) => {
|
|
1253
|
+
if (slot.type === "spacer") {
|
|
1254
|
+
return ((0, jsx_runtime_1.jsx)("td", { role: "presentation", "aria-hidden": "true", style: { padding: 0, border: "none" } }, `__spacer_${slot.side}`));
|
|
1255
|
+
}
|
|
1256
|
+
const col = columns[slot.i];
|
|
1257
|
+
const i = slot.i;
|
|
1258
|
+
const isEditing = editing?.id === row.id && editing?.key === col.key;
|
|
1259
|
+
const value = cellValue(col, row);
|
|
1260
|
+
const sticky = dataStickyCol(i);
|
|
1261
|
+
const isPending = pending?.id === row.id && pending?.key === col.key;
|
|
1262
|
+
const errorHere = editError?.id === row.id && editError?.key === col.key
|
|
1263
|
+
? editError.message
|
|
1264
|
+
: null;
|
|
1265
|
+
// #453: a `switch` editable column IS always its own
|
|
1266
|
+
// live widget -- flipping the toggle IS the commit
|
|
1267
|
+
// gesture, there's no meaningfully different "rest"
|
|
1268
|
+
// display for a boolean. `select`/`reference` are
|
|
1269
|
+
// NOT this: at rest they must show the column's own
|
|
1270
|
+
// render/format like every other editable type
|
|
1271
|
+
// (#412 empty handling included), and only open on
|
|
1272
|
+
// double-click (#411) -- this branch previously
|
|
1273
|
+
// rendered them unconditionally, replacing every
|
|
1274
|
+
// row's own render with a picker trigger, which is
|
|
1275
|
+
// the regression #453 reported (also #432/#449,
|
|
1276
|
+
// which never had a "rest" state of their own to get
|
|
1277
|
+
// right). See the `isEditing` block below for their
|
|
1278
|
+
// actual editor.
|
|
1279
|
+
if (col.editable && col.editor === "switch") {
|
|
1280
|
+
return ((0, jsx_runtime_1.jsxs)(table_1.TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, className: "crisp-datatable-td--picker", "data-pending": isPending ? "" : undefined, children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-edit-picker", children: [(0, jsx_runtime_1.jsx)(switch_1.Switch, { "aria-label": `Edit ${col.key}`, checked: value === true || value === "true", disabled: isPending, onCheckedChange: (checked) => commitEdit(row.id, col.key, String(checked)) }), isPending && ((0, jsx_runtime_1.jsx)(spinner_1.Spinner, { size: 14, className: "crisp-datatable-edit-pending" }))] }), errorHere && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-edit-error", children: errorHere }))] }, col.key));
|
|
1281
|
+
}
|
|
1282
|
+
if (isEditing &&
|
|
1283
|
+
(col.editor === "select" || col.editor === "reference")) {
|
|
1284
|
+
return ((0, jsx_runtime_1.jsxs)(table_1.TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, className: "crisp-datatable-td--picker", "data-pending": isPending ? "" : undefined,
|
|
1285
|
+
// Same as the plain text editor's own cell:
|
|
1286
|
+
// opening/using the picker must not also fire
|
|
1287
|
+
// the row's onRowClick. Escape is handled by
|
|
1288
|
+
// `DataSelectEditor`/`DataReferenceEditor`
|
|
1289
|
+
// themselves (a document-level listener, not a
|
|
1290
|
+
// `TableCell`-level `onKeyDown`) -- opening the
|
|
1291
|
+
// picker moves DOM focus into a floating,
|
|
1292
|
+
// portaled popover, so a handler here can't
|
|
1293
|
+
// rely on the key event ever bubbling through
|
|
1294
|
+
// this element (#453).
|
|
1295
|
+
onClick: (e) => e.stopPropagation(), children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-edit-picker", children: [col.editor === "select" ? ((0, jsx_runtime_1.jsx)(DataSelectEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commitEdit(row.id, col.key, v), onCancel: () => setEditing(null) })) : ((0, jsx_runtime_1.jsx)(DataReferenceEditor, { col: col, value: value, isPending: isPending, onCommit: (v) => commitEdit(row.id, col.key, v), onCancel: () => setEditing(null) })), isPending && ((0, jsx_runtime_1.jsx)(spinner_1.Spinner, { size: 14, className: "crisp-datatable-edit-pending" }))] }), errorHere && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-edit-error", children: errorHere }))] }, col.key));
|
|
1296
|
+
}
|
|
1297
|
+
if (isEditing) {
|
|
1298
|
+
return ((0, jsx_runtime_1.jsxs)(table_1.TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, onClick: (e) => e.stopPropagation(), children: [(0, jsx_runtime_1.jsx)("input", { className: "crisp-datatable-edit",
|
|
1299
|
+
// biome-ignore lint/a11y/noAutofocus: focus the field the moment it opens
|
|
1300
|
+
autoFocus: true, ...editorFor(col), "data-editor": editorFor(col).type, disabled: isPending, defaultValue: col.type === "date"
|
|
1301
|
+
? toDateInputValue(value)
|
|
1302
|
+
: col.type === "datetime"
|
|
1303
|
+
? toDateTimeInputValue(value)
|
|
1304
|
+
: asText(value), "aria-label": `Edit ${col.key}`, onKeyDown: (e) => {
|
|
1305
|
+
if (isPending)
|
|
1306
|
+
return;
|
|
1307
|
+
if (e.key === "Enter")
|
|
1308
|
+
commitEdit(row.id, col.key, e.currentTarget.value);
|
|
1309
|
+
else if (e.key === "Escape")
|
|
1310
|
+
setEditing(null);
|
|
1311
|
+
}, onBlur: (e) => {
|
|
1312
|
+
if (isPending)
|
|
1313
|
+
return;
|
|
937
1314
|
commitEdit(row.id, col.key, e.currentTarget.value);
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1315
|
+
} }), isPending && ((0, jsx_runtime_1.jsx)(spinner_1.Spinner, { size: 14, className: "crisp-datatable-edit-pending" }))] }, col.key));
|
|
1316
|
+
}
|
|
1317
|
+
return ((0, jsx_runtime_1.jsxs)(table_1.TableCell, { align: alignFor(col), "aria-colindex": colAriaIndex(i), ...sticky, className: col.editable
|
|
1318
|
+
? "crisp-datatable-editable"
|
|
1319
|
+
: undefined, "data-selected": selCell?.id === row.id && selCell?.key === col.key
|
|
1320
|
+
? ""
|
|
1321
|
+
: undefined, onClick: () => {
|
|
1322
|
+
// the keyboard model's anchor -- a single click
|
|
1323
|
+
// only selects. A genuine double-click (or Enter
|
|
1324
|
+
// on the selected cell) opens the editor (#411),
|
|
1325
|
+
// matching RecordsTable's own onDoubleClick.
|
|
1326
|
+
setSelCell({ id: row.id, key: col.key });
|
|
1327
|
+
gridRef.current?.focus({ preventScroll: true });
|
|
1328
|
+
}, onDoubleClick: col.editable
|
|
1329
|
+
? () => openEditing(row.id, col.key)
|
|
1330
|
+
: undefined, children: [col.render
|
|
1331
|
+
? col.render(value, row)
|
|
1332
|
+
: renderTyped(col, value, avatarTone, row, col.format?.(value, row), locale), errorHere && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-edit-error", children: errorHere }))] }, col.key));
|
|
1333
|
+
})] }, row.id));
|
|
1334
|
+
}), bottomSpacerHeight > 0 && ((0, jsx_runtime_1.jsx)("tr", { "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)("td", { colSpan: totalCols, style: {
|
|
1335
|
+
height: bottomSpacerHeight,
|
|
1336
|
+
padding: 0,
|
|
1337
|
+
border: "none",
|
|
1338
|
+
} }) }))] })) }), (hasSummary || onAddRow) && ((0, jsx_runtime_1.jsxs)(table_1.TableFooter, { children: [hasSummary && ((0, jsx_runtime_1.jsxs)(table_1.TableRow, { children: [showGrip && (0, jsx_runtime_1.jsx)(table_1.TableCell, { ...stickyCol(-2) }), selectable && (0, jsx_runtime_1.jsx)(table_1.TableCell, { ...stickyCol(-1) }), colSlots.map((slot) => slot.type === "spacer" ? ((0, jsx_runtime_1.jsx)("td", { role: "presentation", "aria-hidden": "true", style: { padding: 0, border: "none" } }, `__spacer_${slot.side}`)) : ((0, jsx_runtime_1.jsx)(table_1.TableCell, { align: alignFor(columns[slot.i]), "aria-colindex": colAriaIndex(slot.i), ...dataStickyCol(slot.i), children: columns[slot.i].summary
|
|
1339
|
+
? columns[slot.i].summary?.(rows)
|
|
1340
|
+
: null }, columns[slot.i].key)))] })), onAddRow && ((0, jsx_runtime_1.jsx)(table_1.TableRow, { children: (0, jsx_runtime_1.jsx)(table_1.TableCell, { className: "crisp-datatable-addcell", colSpan: totalCols, children: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-datatable-addrow", onClick: onAddRow, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), addRowLabel] }) }) }))] }))] })] }));
|
|
963
1341
|
}
|
|
964
1342
|
DataTable.displayName = "DataTable";
|