@bmsuisse/datagrid 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +407 -7
- package/dist/index.js +1360 -389
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1487,9 +1487,76 @@ function EnumEditor({
|
|
|
1487
1487
|
] });
|
|
1488
1488
|
}
|
|
1489
1489
|
|
|
1490
|
+
// src/edit/MultilineStringEditor.tsx
|
|
1491
|
+
import { useLayoutEffect, useRef as useRef4 } from "react";
|
|
1492
|
+
|
|
1493
|
+
// src/components/ui/textarea.tsx
|
|
1494
|
+
import { forwardRef as forwardRef6 } from "react";
|
|
1495
|
+
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
1496
|
+
var Textarea = forwardRef6(({ className, ...props }, ref) => {
|
|
1497
|
+
return /* @__PURE__ */ jsx19(
|
|
1498
|
+
"textarea",
|
|
1499
|
+
{
|
|
1500
|
+
className: cn(
|
|
1501
|
+
"flex min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
1502
|
+
className
|
|
1503
|
+
),
|
|
1504
|
+
ref,
|
|
1505
|
+
...props
|
|
1506
|
+
}
|
|
1507
|
+
);
|
|
1508
|
+
});
|
|
1509
|
+
Textarea.displayName = "Textarea";
|
|
1510
|
+
|
|
1511
|
+
// src/edit/MultilineStringEditor.tsx
|
|
1512
|
+
import { jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1513
|
+
function MultilineStringEditor({
|
|
1514
|
+
column,
|
|
1515
|
+
rowId,
|
|
1516
|
+
value,
|
|
1517
|
+
onChange,
|
|
1518
|
+
error,
|
|
1519
|
+
autoFocus
|
|
1520
|
+
}) {
|
|
1521
|
+
const errorId = editErrorId(rowId, column.id);
|
|
1522
|
+
const textareaRef = useRef4(null);
|
|
1523
|
+
const selectionRef = useRef4(null);
|
|
1524
|
+
const text = value === null || value === void 0 ? "" : String(value);
|
|
1525
|
+
useLayoutEffect(() => {
|
|
1526
|
+
const el = textareaRef.current;
|
|
1527
|
+
if (!el) return;
|
|
1528
|
+
el.style.height = "auto";
|
|
1529
|
+
el.style.height = `${el.scrollHeight}px`;
|
|
1530
|
+
if (selectionRef.current) {
|
|
1531
|
+
if (document.activeElement !== el) el.focus();
|
|
1532
|
+
el.setSelectionRange(selectionRef.current.start, selectionRef.current.end);
|
|
1533
|
+
}
|
|
1534
|
+
}, [text]);
|
|
1535
|
+
return /* @__PURE__ */ jsxs12("div", { children: [
|
|
1536
|
+
/* @__PURE__ */ jsx20(
|
|
1537
|
+
Textarea,
|
|
1538
|
+
{
|
|
1539
|
+
ref: textareaRef,
|
|
1540
|
+
className: "resize-none overflow-hidden",
|
|
1541
|
+
"aria-label": `Edit ${column.header}`,
|
|
1542
|
+
"aria-invalid": error ? true : void 0,
|
|
1543
|
+
"aria-describedby": error ? errorId : void 0,
|
|
1544
|
+
"data-testid": `edit-${rowId}-${column.id}`,
|
|
1545
|
+
autoFocus,
|
|
1546
|
+
value: text,
|
|
1547
|
+
onChange: (event) => {
|
|
1548
|
+
selectionRef.current = { start: event.target.selectionStart ?? 0, end: event.target.selectionEnd ?? 0 };
|
|
1549
|
+
onChange(event.target.value);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
),
|
|
1553
|
+
error && /* @__PURE__ */ jsx20(EditFieldError, { id: errorId, message: error })
|
|
1554
|
+
] });
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1490
1557
|
// src/edit/NumberEditor.tsx
|
|
1491
|
-
import { useEffect as useEffect4, useRef as
|
|
1492
|
-
import { jsx as
|
|
1558
|
+
import { useEffect as useEffect4, useRef as useRef5, useState as useState5 } from "react";
|
|
1559
|
+
import { jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1493
1560
|
function textFromValue(value) {
|
|
1494
1561
|
return value === null || value === void 0 ? "" : String(value);
|
|
1495
1562
|
}
|
|
@@ -1503,14 +1570,14 @@ function NumberEditor({
|
|
|
1503
1570
|
}) {
|
|
1504
1571
|
const errorId = editErrorId(rowId, column.id);
|
|
1505
1572
|
const [text, setText] = useState5(() => textFromValue(value));
|
|
1506
|
-
const lastEmittedRef =
|
|
1573
|
+
const lastEmittedRef = useRef5(value);
|
|
1507
1574
|
useEffect4(() => {
|
|
1508
1575
|
if (value === lastEmittedRef.current) return;
|
|
1509
1576
|
lastEmittedRef.current = value;
|
|
1510
1577
|
setText(textFromValue(value));
|
|
1511
1578
|
}, [value]);
|
|
1512
|
-
return /* @__PURE__ */
|
|
1513
|
-
/* @__PURE__ */
|
|
1579
|
+
return /* @__PURE__ */ jsxs13("div", { children: [
|
|
1580
|
+
/* @__PURE__ */ jsx21(
|
|
1514
1581
|
Input,
|
|
1515
1582
|
{
|
|
1516
1583
|
type: "number",
|
|
@@ -1529,12 +1596,12 @@ function NumberEditor({
|
|
|
1529
1596
|
}
|
|
1530
1597
|
}
|
|
1531
1598
|
),
|
|
1532
|
-
error && /* @__PURE__ */
|
|
1599
|
+
error && /* @__PURE__ */ jsx21(EditFieldError, { id: errorId, message: error })
|
|
1533
1600
|
] });
|
|
1534
1601
|
}
|
|
1535
1602
|
|
|
1536
1603
|
// src/edit/StringEditor.tsx
|
|
1537
|
-
import { jsx as
|
|
1604
|
+
import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1538
1605
|
function StringEditor({
|
|
1539
1606
|
column,
|
|
1540
1607
|
rowId,
|
|
@@ -1544,8 +1611,8 @@ function StringEditor({
|
|
|
1544
1611
|
autoFocus
|
|
1545
1612
|
}) {
|
|
1546
1613
|
const errorId = editErrorId(rowId, column.id);
|
|
1547
|
-
return /* @__PURE__ */
|
|
1548
|
-
/* @__PURE__ */
|
|
1614
|
+
return /* @__PURE__ */ jsxs14("div", { children: [
|
|
1615
|
+
/* @__PURE__ */ jsx22(
|
|
1549
1616
|
Input,
|
|
1550
1617
|
{
|
|
1551
1618
|
className: "h-8",
|
|
@@ -1558,31 +1625,31 @@ function StringEditor({
|
|
|
1558
1625
|
onChange: (event) => onChange(event.target.value)
|
|
1559
1626
|
}
|
|
1560
1627
|
),
|
|
1561
|
-
error && /* @__PURE__ */
|
|
1628
|
+
error && /* @__PURE__ */ jsx22(EditFieldError, { id: errorId, message: error })
|
|
1562
1629
|
] });
|
|
1563
1630
|
}
|
|
1564
1631
|
|
|
1565
1632
|
// src/edit/registry.tsx
|
|
1566
|
-
import { jsx as
|
|
1633
|
+
import { jsx as jsx23 } from "react/jsx-runtime";
|
|
1567
1634
|
function renderDefaultEditWidget(column, rowId, value, onChange, error, autoFocus) {
|
|
1568
1635
|
switch (column.type) {
|
|
1569
1636
|
case "string":
|
|
1570
|
-
return /* @__PURE__ */
|
|
1637
|
+
return column.multiline ? /* @__PURE__ */ jsx23(MultilineStringEditor, { column, rowId, value, onChange, error, autoFocus }) : /* @__PURE__ */ jsx23(StringEditor, { column, rowId, value, onChange, error, autoFocus });
|
|
1571
1638
|
case "enum":
|
|
1572
|
-
return /* @__PURE__ */
|
|
1639
|
+
return /* @__PURE__ */ jsx23(EnumEditor, { column, rowId, value, onChange, error, autoFocus });
|
|
1573
1640
|
case "boolean":
|
|
1574
|
-
return /* @__PURE__ */
|
|
1641
|
+
return /* @__PURE__ */ jsx23(BooleanEditor, { column, rowId, value, onChange, error, autoFocus });
|
|
1575
1642
|
case "number":
|
|
1576
1643
|
case "currency":
|
|
1577
|
-
return /* @__PURE__ */
|
|
1644
|
+
return /* @__PURE__ */ jsx23(NumberEditor, { column, rowId, value, onChange, error, autoFocus });
|
|
1578
1645
|
case "date":
|
|
1579
1646
|
case "datetime":
|
|
1580
|
-
return /* @__PURE__ */
|
|
1647
|
+
return /* @__PURE__ */ jsx23(DateEditor, { column, rowId, value, onChange, error, autoFocus });
|
|
1581
1648
|
}
|
|
1582
1649
|
}
|
|
1583
1650
|
|
|
1584
1651
|
// src/edit/editingState.ts
|
|
1585
|
-
import { useEffect as useEffect5, useRef as
|
|
1652
|
+
import { useEffect as useEffect5, useRef as useRef6, useState as useState6 } from "react";
|
|
1586
1653
|
function editValuesEqual(a, b) {
|
|
1587
1654
|
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
|
|
1588
1655
|
return a === b;
|
|
@@ -1684,13 +1751,13 @@ function useEditingState(editing, getRowId) {
|
|
|
1684
1751
|
editing?.onDiscard?.();
|
|
1685
1752
|
}
|
|
1686
1753
|
const ctx = editing ? { pendingEdits, editErrors, activeRowId, autoFocusTarget, getRowId, onEdit: commitEdit, onActivateRow: activateRow } : void 0;
|
|
1687
|
-
const ctxRef =
|
|
1754
|
+
const ctxRef = useRef6(void 0);
|
|
1688
1755
|
ctxRef.current = ctx;
|
|
1689
1756
|
return { pendingEdits, editErrors, hasEditErrors, ctx, ctxRef, handleSaveEdits, handleDiscardEdits };
|
|
1690
1757
|
}
|
|
1691
1758
|
|
|
1692
1759
|
// src/edit/renderEditableCell.tsx
|
|
1693
|
-
import { jsx as
|
|
1760
|
+
import { jsx as jsx24 } from "react/jsx-runtime";
|
|
1694
1761
|
function renderEditableCell(column, row, rawValue, ctx) {
|
|
1695
1762
|
const staticContent = (value2) => column.cell ? column.cell(value2, row) : defaultFormat(column, value2);
|
|
1696
1763
|
if (!ctx || !isEditable(column, row)) return staticContent(rawValue);
|
|
@@ -1702,7 +1769,7 @@ function renderEditableCell(column, row, rawValue, ctx) {
|
|
|
1702
1769
|
const autoFocus = ctx.autoFocusTarget?.rowId === rowId && ctx.autoFocusTarget.columnId === column.id;
|
|
1703
1770
|
const onChange = (next) => ctx.onEdit(column, row, next);
|
|
1704
1771
|
const editor = column.renderEditCell ? column.renderEditCell(value, row, onChange, error, autoFocus) : renderDefaultEditWidget(column, rowId, value, onChange, error, autoFocus);
|
|
1705
|
-
return /* @__PURE__ */
|
|
1772
|
+
return /* @__PURE__ */ jsx24(
|
|
1706
1773
|
"div",
|
|
1707
1774
|
{
|
|
1708
1775
|
onKeyDown: (event) => {
|
|
@@ -1715,7 +1782,7 @@ function renderEditableCell(column, row, rawValue, ctx) {
|
|
|
1715
1782
|
}
|
|
1716
1783
|
);
|
|
1717
1784
|
}
|
|
1718
|
-
return /* @__PURE__ */
|
|
1785
|
+
return /* @__PURE__ */ jsx24(
|
|
1719
1786
|
"span",
|
|
1720
1787
|
{
|
|
1721
1788
|
role: "button",
|
|
@@ -1738,7 +1805,7 @@ function renderEditableCell(column, row, rawValue, ctx) {
|
|
|
1738
1805
|
}
|
|
1739
1806
|
|
|
1740
1807
|
// src/edit/EditingBar.tsx
|
|
1741
|
-
import { jsx as
|
|
1808
|
+
import { jsx as jsx25, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1742
1809
|
function resolveEditLabel(label, changedRowCount, fallback) {
|
|
1743
1810
|
if (label === void 0) return fallback(changedRowCount);
|
|
1744
1811
|
return typeof label === "function" ? label(changedRowCount) : label;
|
|
@@ -1758,25 +1825,25 @@ function EditingBar({
|
|
|
1758
1825
|
testIdPrefix = "datagrid"
|
|
1759
1826
|
}) {
|
|
1760
1827
|
const hasEditErrors = editErrors.size > 0;
|
|
1761
|
-
return /* @__PURE__ */
|
|
1828
|
+
return /* @__PURE__ */ jsxs15(
|
|
1762
1829
|
"div",
|
|
1763
1830
|
{
|
|
1764
1831
|
"data-testid": `${testIdPrefix}-edit-bar`,
|
|
1765
1832
|
className: "flex shrink-0 flex-wrap items-center justify-between gap-2 rounded-md border bg-muted/50 px-3 py-2 text-sm",
|
|
1766
1833
|
children: [
|
|
1767
|
-
/* @__PURE__ */
|
|
1834
|
+
/* @__PURE__ */ jsxs15("span", { "aria-live": "polite", children: [
|
|
1768
1835
|
pendingRowCount,
|
|
1769
1836
|
" row",
|
|
1770
1837
|
pendingRowCount === 1 ? "" : "s",
|
|
1771
1838
|
" changed",
|
|
1772
|
-
hasEditErrors && /* @__PURE__ */
|
|
1839
|
+
hasEditErrors && /* @__PURE__ */ jsxs15("span", { className: "ml-2 text-destructive", children: [
|
|
1773
1840
|
"Fix the highlighted errors before saving (",
|
|
1774
1841
|
describeErrorRows(editErrors),
|
|
1775
1842
|
")."
|
|
1776
1843
|
] })
|
|
1777
1844
|
] }),
|
|
1778
|
-
/* @__PURE__ */
|
|
1779
|
-
/* @__PURE__ */
|
|
1845
|
+
/* @__PURE__ */ jsxs15("div", { className: "flex gap-2", children: [
|
|
1846
|
+
/* @__PURE__ */ jsx25(
|
|
1780
1847
|
Button,
|
|
1781
1848
|
{
|
|
1782
1849
|
type: "button",
|
|
@@ -1788,7 +1855,7 @@ function EditingBar({
|
|
|
1788
1855
|
children: resolveEditLabel(editing.discardLabel, pendingRowCount, () => "Discard")
|
|
1789
1856
|
}
|
|
1790
1857
|
),
|
|
1791
|
-
/* @__PURE__ */
|
|
1858
|
+
/* @__PURE__ */ jsx25(
|
|
1792
1859
|
Button,
|
|
1793
1860
|
{
|
|
1794
1861
|
type: "button",
|
|
@@ -1809,6 +1876,633 @@ function EditingBar({
|
|
|
1809
1876
|
);
|
|
1810
1877
|
}
|
|
1811
1878
|
|
|
1879
|
+
// src/cell-editing/rangeUtils.ts
|
|
1880
|
+
function buildIndexMap(ids) {
|
|
1881
|
+
const map = /* @__PURE__ */ new Map();
|
|
1882
|
+
ids.forEach((id, index) => map.set(id, index));
|
|
1883
|
+
return map;
|
|
1884
|
+
}
|
|
1885
|
+
function normalizeRange(range, rowIndex, columnIndex) {
|
|
1886
|
+
const anchorRow = rowIndex.get(range.anchor.rowId);
|
|
1887
|
+
const focusRow = rowIndex.get(range.focus.rowId);
|
|
1888
|
+
const anchorCol = columnIndex.get(range.anchor.columnId);
|
|
1889
|
+
const focusCol = columnIndex.get(range.focus.columnId);
|
|
1890
|
+
if (anchorRow === void 0 || focusRow === void 0 || anchorCol === void 0 || focusCol === void 0) {
|
|
1891
|
+
return void 0;
|
|
1892
|
+
}
|
|
1893
|
+
return {
|
|
1894
|
+
rowStart: Math.min(anchorRow, focusRow),
|
|
1895
|
+
rowEnd: Math.max(anchorRow, focusRow),
|
|
1896
|
+
colStart: Math.min(anchorCol, focusCol),
|
|
1897
|
+
colEnd: Math.max(anchorCol, focusCol)
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
function isCellInRange(cell, range, rowIndex, columnIndex) {
|
|
1901
|
+
const normalized = normalizeRange(range, rowIndex, columnIndex);
|
|
1902
|
+
if (!normalized) return false;
|
|
1903
|
+
const row = rowIndex.get(cell.rowId);
|
|
1904
|
+
const col = columnIndex.get(cell.columnId);
|
|
1905
|
+
if (row === void 0 || col === void 0) return false;
|
|
1906
|
+
return row >= normalized.rowStart && row <= normalized.rowEnd && col >= normalized.colStart && col <= normalized.colEnd;
|
|
1907
|
+
}
|
|
1908
|
+
function cellsInRange(range, rowIds, columnIds, rowIndex, columnIndex) {
|
|
1909
|
+
const normalized = normalizeRange(range, rowIndex, columnIndex);
|
|
1910
|
+
if (!normalized) return [];
|
|
1911
|
+
const cells = [];
|
|
1912
|
+
for (let r = normalized.rowStart; r <= normalized.rowEnd; r++) {
|
|
1913
|
+
const rowId = rowIds[r];
|
|
1914
|
+
if (rowId === void 0) continue;
|
|
1915
|
+
for (let c = normalized.colStart; c <= normalized.colEnd; c++) {
|
|
1916
|
+
const columnId = columnIds[c];
|
|
1917
|
+
if (columnId === void 0) continue;
|
|
1918
|
+
cells.push({ rowId, columnId });
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
return cells;
|
|
1922
|
+
}
|
|
1923
|
+
function extendRangeForFill(base, target) {
|
|
1924
|
+
const rowOvershoot = Math.max(0, base.rowStart - target.row, target.row - base.rowEnd);
|
|
1925
|
+
const colOvershoot = Math.max(0, base.colStart - target.col, target.col - base.colEnd);
|
|
1926
|
+
if (rowOvershoot === 0 && colOvershoot === 0) return base;
|
|
1927
|
+
if (rowOvershoot >= colOvershoot) {
|
|
1928
|
+
return {
|
|
1929
|
+
rowStart: Math.min(base.rowStart, target.row),
|
|
1930
|
+
rowEnd: Math.max(base.rowEnd, target.row),
|
|
1931
|
+
colStart: base.colStart,
|
|
1932
|
+
colEnd: base.colEnd
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
return {
|
|
1936
|
+
rowStart: base.rowStart,
|
|
1937
|
+
rowEnd: base.rowEnd,
|
|
1938
|
+
colStart: Math.min(base.colStart, target.col),
|
|
1939
|
+
colEnd: Math.max(base.colEnd, target.col)
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
function extendRangeForFillToCellRange(base, target, rowIds, columnIds, rowIndex, columnIndex) {
|
|
1943
|
+
const normalizedBase = normalizeRange(base, rowIndex, columnIndex);
|
|
1944
|
+
if (!normalizedBase) return void 0;
|
|
1945
|
+
const targetRow = rowIndex.get(target.rowId);
|
|
1946
|
+
const targetCol = columnIndex.get(target.columnId);
|
|
1947
|
+
if (targetRow === void 0 || targetCol === void 0) return void 0;
|
|
1948
|
+
const extended = extendRangeForFill(normalizedBase, { row: targetRow, col: targetCol });
|
|
1949
|
+
const startRowId = rowIds[extended.rowStart];
|
|
1950
|
+
const endRowId = rowIds[extended.rowEnd];
|
|
1951
|
+
const startColumnId = columnIds[extended.colStart];
|
|
1952
|
+
const endColumnId = columnIds[extended.colEnd];
|
|
1953
|
+
if (startRowId === void 0 || endRowId === void 0 || startColumnId === void 0 || endColumnId === void 0) {
|
|
1954
|
+
return void 0;
|
|
1955
|
+
}
|
|
1956
|
+
return { anchor: { rowId: startRowId, columnId: startColumnId }, focus: { rowId: endRowId, columnId: endColumnId } };
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
// src/cell-editing/useCellSelection.ts
|
|
1960
|
+
import { useCallback as useCallback2, useMemo as useMemo3, useRef as useRef7, useState as useState7 } from "react";
|
|
1961
|
+
function useCellSelection({ rowIds, columnIds, onNavigateToRow }) {
|
|
1962
|
+
const [selection, setSelectionState] = useState7(void 0);
|
|
1963
|
+
const [isDragging, setIsDragging] = useState7(false);
|
|
1964
|
+
const [dragFocus, setDragFocus] = useState7(void 0);
|
|
1965
|
+
const draggingRef = useRef7(false);
|
|
1966
|
+
const dragFocusRef = useRef7(void 0);
|
|
1967
|
+
const rowIndex = useMemo3(() => buildIndexMap(rowIds), [rowIds]);
|
|
1968
|
+
const columnIndex = useMemo3(() => buildIndexMap(columnIds), [columnIds]);
|
|
1969
|
+
const startSelection = useCallback2((cell, options) => {
|
|
1970
|
+
setSelectionState((prev) => options?.extend && prev ? { anchor: prev.anchor, focus: cell } : { anchor: cell, focus: cell });
|
|
1971
|
+
draggingRef.current = true;
|
|
1972
|
+
setIsDragging(true);
|
|
1973
|
+
dragFocusRef.current = void 0;
|
|
1974
|
+
setDragFocus(void 0);
|
|
1975
|
+
}, []);
|
|
1976
|
+
const updateDrag = useCallback2((cell) => {
|
|
1977
|
+
if (!draggingRef.current) return;
|
|
1978
|
+
dragFocusRef.current = cell;
|
|
1979
|
+
setDragFocus(cell);
|
|
1980
|
+
}, []);
|
|
1981
|
+
const endDrag = useCallback2(() => {
|
|
1982
|
+
if (!draggingRef.current) return;
|
|
1983
|
+
draggingRef.current = false;
|
|
1984
|
+
setIsDragging(false);
|
|
1985
|
+
const finalFocus = dragFocusRef.current;
|
|
1986
|
+
dragFocusRef.current = void 0;
|
|
1987
|
+
setDragFocus(void 0);
|
|
1988
|
+
if (finalFocus) {
|
|
1989
|
+
setSelectionState((prev) => prev ? { anchor: prev.anchor, focus: finalFocus } : prev);
|
|
1990
|
+
}
|
|
1991
|
+
}, []);
|
|
1992
|
+
const moveSelection = useCallback2(
|
|
1993
|
+
(direction, options) => {
|
|
1994
|
+
const from = selection?.focus ?? { rowId: rowIds[0] ?? "", columnId: columnIds[0] ?? "" };
|
|
1995
|
+
const rowIdx = rowIndex.get(from.rowId);
|
|
1996
|
+
const colIdx = columnIndex.get(from.columnId);
|
|
1997
|
+
if (rowIdx === void 0 || colIdx === void 0 || rowIds.length === 0 || columnIds.length === 0) return;
|
|
1998
|
+
let nextRowIdx = rowIdx;
|
|
1999
|
+
let nextColIdx = colIdx;
|
|
2000
|
+
if (direction === "up") nextRowIdx = Math.max(0, rowIdx - 1);
|
|
2001
|
+
else if (direction === "down") nextRowIdx = Math.min(rowIds.length - 1, rowIdx + 1);
|
|
2002
|
+
else if (direction === "left") nextColIdx = Math.max(0, colIdx - 1);
|
|
2003
|
+
else nextColIdx = Math.min(columnIds.length - 1, colIdx + 1);
|
|
2004
|
+
const nextRowId = rowIds[nextRowIdx];
|
|
2005
|
+
const nextColumnId = columnIds[nextColIdx];
|
|
2006
|
+
if (nextRowId !== from.rowId) onNavigateToRow?.(nextRowId);
|
|
2007
|
+
const nextCell = { rowId: nextRowId, columnId: nextColumnId };
|
|
2008
|
+
const anchor = options?.extend ? selection?.anchor ?? nextCell : nextCell;
|
|
2009
|
+
setSelectionState({ anchor, focus: nextCell });
|
|
2010
|
+
},
|
|
2011
|
+
[selection, rowIds, columnIds, rowIndex, columnIndex, onNavigateToRow]
|
|
2012
|
+
);
|
|
2013
|
+
const setSelection = useCallback2((range) => {
|
|
2014
|
+
setSelectionState(range);
|
|
2015
|
+
}, []);
|
|
2016
|
+
const clearSelection = useCallback2(() => {
|
|
2017
|
+
draggingRef.current = false;
|
|
2018
|
+
setIsDragging(false);
|
|
2019
|
+
setDragFocus(void 0);
|
|
2020
|
+
setSelectionState(void 0);
|
|
2021
|
+
}, []);
|
|
2022
|
+
const effectiveRange = useMemo3(() => {
|
|
2023
|
+
if (!selection) return void 0;
|
|
2024
|
+
if (isDragging && dragFocus) return { anchor: selection.anchor, focus: dragFocus };
|
|
2025
|
+
return selection;
|
|
2026
|
+
}, [selection, isDragging, dragFocus]);
|
|
2027
|
+
const [isFillDragging, setIsFillDragging] = useState7(false);
|
|
2028
|
+
const [fillTarget, setFillTarget] = useState7(void 0);
|
|
2029
|
+
const fillDraggingRef = useRef7(false);
|
|
2030
|
+
const fillTargetRef = useRef7(void 0);
|
|
2031
|
+
const fillBaseRef = useRef7(void 0);
|
|
2032
|
+
const startFillDrag = useCallback2(() => {
|
|
2033
|
+
if (!selection) return;
|
|
2034
|
+
fillBaseRef.current = selection;
|
|
2035
|
+
fillDraggingRef.current = true;
|
|
2036
|
+
setIsFillDragging(true);
|
|
2037
|
+
}, [selection]);
|
|
2038
|
+
const updateFillDrag = useCallback2((cell) => {
|
|
2039
|
+
if (!fillDraggingRef.current) return;
|
|
2040
|
+
fillTargetRef.current = cell;
|
|
2041
|
+
setFillTarget(cell);
|
|
2042
|
+
}, []);
|
|
2043
|
+
const endFillDrag = useCallback2(() => {
|
|
2044
|
+
if (!fillDraggingRef.current) return void 0;
|
|
2045
|
+
fillDraggingRef.current = false;
|
|
2046
|
+
setIsFillDragging(false);
|
|
2047
|
+
const sourceRange = fillBaseRef.current;
|
|
2048
|
+
const target = fillTargetRef.current;
|
|
2049
|
+
fillBaseRef.current = void 0;
|
|
2050
|
+
fillTargetRef.current = void 0;
|
|
2051
|
+
setFillTarget(void 0);
|
|
2052
|
+
if (!sourceRange || !target) return void 0;
|
|
2053
|
+
const finalRange = extendRangeForFillToCellRange(sourceRange, target, rowIds, columnIds, rowIndex, columnIndex);
|
|
2054
|
+
if (!finalRange) return void 0;
|
|
2055
|
+
setSelectionState(finalRange);
|
|
2056
|
+
return { sourceRange, finalRange };
|
|
2057
|
+
}, [rowIds, columnIds, rowIndex, columnIndex]);
|
|
2058
|
+
const fillPreviewRange = useMemo3(() => {
|
|
2059
|
+
if (!isFillDragging || !selection || !fillTarget) return void 0;
|
|
2060
|
+
return extendRangeForFillToCellRange(selection, fillTarget, rowIds, columnIds, rowIndex, columnIndex);
|
|
2061
|
+
}, [isFillDragging, selection, fillTarget, rowIds, columnIds, rowIndex, columnIndex]);
|
|
2062
|
+
return {
|
|
2063
|
+
selection,
|
|
2064
|
+
isDragging,
|
|
2065
|
+
effectiveRange,
|
|
2066
|
+
startSelection,
|
|
2067
|
+
updateDrag,
|
|
2068
|
+
endDrag,
|
|
2069
|
+
moveSelection,
|
|
2070
|
+
setSelection,
|
|
2071
|
+
clearSelection,
|
|
2072
|
+
isFillDragging,
|
|
2073
|
+
fillPreviewRange,
|
|
2074
|
+
startFillDrag,
|
|
2075
|
+
updateFillDrag,
|
|
2076
|
+
endFillDrag
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
// src/cell-editing/useCellEditingState.ts
|
|
2081
|
+
import { useRef as useRef8, useState as useState8 } from "react";
|
|
2082
|
+
function cellKey(rowId, columnId) {
|
|
2083
|
+
return `${rowId}:${columnId}`;
|
|
2084
|
+
}
|
|
2085
|
+
function useCellEditingState(cellEditing, getRowId) {
|
|
2086
|
+
const [editingCell, setEditingCell] = useState8(void 0);
|
|
2087
|
+
const [initialText, setInitialText] = useState8(void 0);
|
|
2088
|
+
const [hasDraft, setHasDraft] = useState8(false);
|
|
2089
|
+
const [draftValue, setDraftValue] = useState8(void 0);
|
|
2090
|
+
const [overrides, setOverrides] = useState8(/* @__PURE__ */ new Map());
|
|
2091
|
+
const [errors, setErrors] = useState8(/* @__PURE__ */ new Map());
|
|
2092
|
+
const justCommittedRef = useRef8(false);
|
|
2093
|
+
function closeEditor() {
|
|
2094
|
+
setEditingCell(void 0);
|
|
2095
|
+
setInitialText(void 0);
|
|
2096
|
+
setHasDraft(false);
|
|
2097
|
+
setDraftValue(void 0);
|
|
2098
|
+
}
|
|
2099
|
+
function applyChanges(changes) {
|
|
2100
|
+
if (!cellEditing || cellEditing.disabled || changes.length === 0) return;
|
|
2101
|
+
setOverrides((prev) => {
|
|
2102
|
+
const next = new Map(prev);
|
|
2103
|
+
for (const change of changes) {
|
|
2104
|
+
next.set(cellKey(change.rowId, change.columnId), { value: change.value, previousValue: change.previousValue });
|
|
2105
|
+
}
|
|
2106
|
+
return next;
|
|
2107
|
+
});
|
|
2108
|
+
setErrors((prev) => {
|
|
2109
|
+
if (prev.size === 0) return prev;
|
|
2110
|
+
const next = new Map(prev);
|
|
2111
|
+
for (const change of changes) next.delete(cellKey(change.rowId, change.columnId));
|
|
2112
|
+
return next.size === prev.size ? prev : next;
|
|
2113
|
+
});
|
|
2114
|
+
void cellEditing.onCellsChange(changes);
|
|
2115
|
+
}
|
|
2116
|
+
function onBeginEdit(cell, text) {
|
|
2117
|
+
if (!cellEditing || cellEditing.disabled) return;
|
|
2118
|
+
setEditingCell(cell);
|
|
2119
|
+
setInitialText(text);
|
|
2120
|
+
setHasDraft(text !== void 0);
|
|
2121
|
+
setDraftValue(text);
|
|
2122
|
+
}
|
|
2123
|
+
function onChangeDraft(value) {
|
|
2124
|
+
setHasDraft(true);
|
|
2125
|
+
setDraftValue(value);
|
|
2126
|
+
}
|
|
2127
|
+
function onCommitEdit(row, previousValue) {
|
|
2128
|
+
const cell = editingCell;
|
|
2129
|
+
const shouldCommit = hasDraft;
|
|
2130
|
+
const value = draftValue;
|
|
2131
|
+
closeEditor();
|
|
2132
|
+
if (!cell || !shouldCommit) return;
|
|
2133
|
+
justCommittedRef.current = true;
|
|
2134
|
+
applyChanges([{ rowId: cell.rowId, row, columnId: cell.columnId, previousValue, value }]);
|
|
2135
|
+
}
|
|
2136
|
+
function onCommitValue(row, previousValue, value) {
|
|
2137
|
+
const cell = editingCell;
|
|
2138
|
+
closeEditor();
|
|
2139
|
+
if (!cell) return;
|
|
2140
|
+
justCommittedRef.current = true;
|
|
2141
|
+
applyChanges([{ rowId: cell.rowId, row, columnId: cell.columnId, previousValue, value }]);
|
|
2142
|
+
}
|
|
2143
|
+
function consumeJustCommitted() {
|
|
2144
|
+
const value = justCommittedRef.current;
|
|
2145
|
+
justCommittedRef.current = false;
|
|
2146
|
+
return value;
|
|
2147
|
+
}
|
|
2148
|
+
function onSetError(rowId, columnId, message) {
|
|
2149
|
+
setErrors((prev) => {
|
|
2150
|
+
const key = cellKey(rowId, columnId);
|
|
2151
|
+
if (message === void 0) {
|
|
2152
|
+
if (!prev.has(key)) return prev;
|
|
2153
|
+
const next2 = new Map(prev);
|
|
2154
|
+
next2.delete(key);
|
|
2155
|
+
return next2;
|
|
2156
|
+
}
|
|
2157
|
+
const next = new Map(prev);
|
|
2158
|
+
next.set(key, message);
|
|
2159
|
+
return next;
|
|
2160
|
+
});
|
|
2161
|
+
}
|
|
2162
|
+
function getError(rowId, columnId) {
|
|
2163
|
+
return errors.get(cellKey(rowId, columnId));
|
|
2164
|
+
}
|
|
2165
|
+
function resolveValue(rowId, columnId, rawValue) {
|
|
2166
|
+
const override = overrides.get(cellKey(rowId, columnId));
|
|
2167
|
+
if (!override) return rawValue;
|
|
2168
|
+
if (rawValue !== override.previousValue) return rawValue;
|
|
2169
|
+
return override.value;
|
|
2170
|
+
}
|
|
2171
|
+
const ctx = cellEditing ? {
|
|
2172
|
+
editingCell,
|
|
2173
|
+
initialText,
|
|
2174
|
+
hasDraft,
|
|
2175
|
+
draftValue,
|
|
2176
|
+
getRowId,
|
|
2177
|
+
getError,
|
|
2178
|
+
resolveValue,
|
|
2179
|
+
onBeginEdit,
|
|
2180
|
+
onCancelEdit: closeEditor,
|
|
2181
|
+
onChangeDraft,
|
|
2182
|
+
onCommitEdit,
|
|
2183
|
+
onCommitValue,
|
|
2184
|
+
onSetError,
|
|
2185
|
+
consumeJustCommitted
|
|
2186
|
+
} : void 0;
|
|
2187
|
+
const ctxRef = useRef8(void 0);
|
|
2188
|
+
ctxRef.current = ctx;
|
|
2189
|
+
return { ctx, ctxRef, applyChanges };
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
// src/cell-editing/renderCellModeCell.tsx
|
|
2193
|
+
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
2194
|
+
function isAtomicEditorType(type) {
|
|
2195
|
+
return type === "enum" || type === "boolean" || type === "date" || type === "datetime";
|
|
2196
|
+
}
|
|
2197
|
+
function renderCellModeCell(column, row, rawValue, ctx) {
|
|
2198
|
+
const staticContent = (value2) => column.cell ? column.cell(value2, row) : defaultFormat(column, value2);
|
|
2199
|
+
if (!ctx) return staticContent(rawValue);
|
|
2200
|
+
const rowId = ctx.getRowId(row);
|
|
2201
|
+
const value = ctx.resolveValue(rowId, column.id, rawValue);
|
|
2202
|
+
const isThisCellEditing = ctx.editingCell?.rowId === rowId && ctx.editingCell.columnId === column.id && isEditable(column, row);
|
|
2203
|
+
if (!isThisCellEditing) {
|
|
2204
|
+
return /* @__PURE__ */ jsx26("span", { "data-testid": `cell-${rowId}-${column.id}`, className: "block w-full", children: staticContent(value) });
|
|
2205
|
+
}
|
|
2206
|
+
const isAtomic = isAtomicEditorType(column.type);
|
|
2207
|
+
const error = ctx.getError(rowId, column.id);
|
|
2208
|
+
const startValue = ctx.initialText !== void 0 ? ctx.initialText : value;
|
|
2209
|
+
const currentValue = ctx.hasDraft ? ctx.draftValue : startValue;
|
|
2210
|
+
function validate(candidate) {
|
|
2211
|
+
return column.validateEdit?.(candidate, row);
|
|
2212
|
+
}
|
|
2213
|
+
const onChange = (next) => {
|
|
2214
|
+
const message = validate(next);
|
|
2215
|
+
ctx.onSetError(rowId, column.id, message);
|
|
2216
|
+
if (isAtomic && !message) {
|
|
2217
|
+
ctx.onCommitValue(row, value, next);
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
ctx.onChangeDraft(next);
|
|
2221
|
+
};
|
|
2222
|
+
const editor = column.renderEditCell ? column.renderEditCell(currentValue, row, onChange, error, true) : renderDefaultEditWidget(column, rowId, currentValue, onChange, error, true);
|
|
2223
|
+
function handleKeyDown(event) {
|
|
2224
|
+
if (event.key === "Escape") {
|
|
2225
|
+
event.stopPropagation();
|
|
2226
|
+
ctx.onCancelEdit();
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2229
|
+
const isMultilineNewline = column.type === "string" && column.multiline && event.key === "Enter" && event.shiftKey;
|
|
2230
|
+
if (isMultilineNewline) return;
|
|
2231
|
+
if (isAtomic || event.key !== "Enter" && event.key !== "Tab") return;
|
|
2232
|
+
const message = validate(currentValue);
|
|
2233
|
+
if (message) {
|
|
2234
|
+
event.stopPropagation();
|
|
2235
|
+
return;
|
|
2236
|
+
}
|
|
2237
|
+
ctx.onCommitEdit(row, value);
|
|
2238
|
+
}
|
|
2239
|
+
function handleBlur() {
|
|
2240
|
+
if (isAtomic) return;
|
|
2241
|
+
if (validate(currentValue)) ctx.onCancelEdit();
|
|
2242
|
+
else ctx.onCommitEdit(row, value);
|
|
2243
|
+
}
|
|
2244
|
+
return /* @__PURE__ */ jsx26("div", { onKeyDown: handleKeyDown, onBlur: handleBlur, children: editor });
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
// src/cell-editing/SelectionOverlay.tsx
|
|
2248
|
+
import { useLayoutEffect as useLayoutEffect2, useState as useState9 } from "react";
|
|
2249
|
+
import { Fragment as Fragment2, jsx as jsx27, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2250
|
+
function cellSelector(rowId, columnId) {
|
|
2251
|
+
const escape = (value) => typeof CSS !== "undefined" && CSS.escape ? CSS.escape(value) : value.replace(/["\\]/g, "\\$&");
|
|
2252
|
+
return `[data-cell-row="${escape(rowId)}"][data-cell-col="${escape(columnId)}"]`;
|
|
2253
|
+
}
|
|
2254
|
+
function computeRangeRect(container, range) {
|
|
2255
|
+
const anchorEl = container.querySelector(cellSelector(range.anchor.rowId, range.anchor.columnId));
|
|
2256
|
+
const focusEl = container.querySelector(cellSelector(range.focus.rowId, range.focus.columnId));
|
|
2257
|
+
if (!anchorEl || !focusEl) return void 0;
|
|
2258
|
+
const containerRect = container.getBoundingClientRect();
|
|
2259
|
+
const a = anchorEl.getBoundingClientRect();
|
|
2260
|
+
const f = focusEl.getBoundingClientRect();
|
|
2261
|
+
const left = Math.min(a.left, f.left) - containerRect.left + container.scrollLeft;
|
|
2262
|
+
const top = Math.min(a.top, f.top) - containerRect.top + container.scrollTop;
|
|
2263
|
+
const right = Math.max(a.right, f.right) - containerRect.left + container.scrollLeft;
|
|
2264
|
+
const bottom = Math.max(a.bottom, f.bottom) - containerRect.top + container.scrollTop;
|
|
2265
|
+
return { top, left, width: right - left, height: bottom - top };
|
|
2266
|
+
}
|
|
2267
|
+
function rectsEqual(a, b) {
|
|
2268
|
+
if (a === b) return true;
|
|
2269
|
+
if (!a || !b) return false;
|
|
2270
|
+
return a.top === b.top && a.left === b.left && a.width === b.width && a.height === b.height;
|
|
2271
|
+
}
|
|
2272
|
+
var HANDLE_SIZE = 8;
|
|
2273
|
+
function SelectionOverlay({
|
|
2274
|
+
containerRef,
|
|
2275
|
+
range,
|
|
2276
|
+
fillPreviewRange,
|
|
2277
|
+
onFillHandleMouseDown
|
|
2278
|
+
}) {
|
|
2279
|
+
const [rect, setRect] = useState9(void 0);
|
|
2280
|
+
const [previewRect, setPreviewRect] = useState9(void 0);
|
|
2281
|
+
useLayoutEffect2(() => {
|
|
2282
|
+
const container = containerRef.current;
|
|
2283
|
+
const nextRect = range && container ? computeRangeRect(container, range) : void 0;
|
|
2284
|
+
setRect((prev) => rectsEqual(prev, nextRect) ? prev : nextRect);
|
|
2285
|
+
const nextPreviewRect = fillPreviewRange && container ? computeRangeRect(container, fillPreviewRange) : void 0;
|
|
2286
|
+
setPreviewRect((prev) => rectsEqual(prev, nextPreviewRect) ? prev : nextPreviewRect);
|
|
2287
|
+
});
|
|
2288
|
+
if (!rect) return null;
|
|
2289
|
+
return /* @__PURE__ */ jsxs16(Fragment2, { children: [
|
|
2290
|
+
/* @__PURE__ */ jsx27(
|
|
2291
|
+
"div",
|
|
2292
|
+
{
|
|
2293
|
+
"data-testid": "cell-selection-overlay",
|
|
2294
|
+
"aria-hidden": true,
|
|
2295
|
+
className: "pointer-events-none absolute z-20 border-2 border-primary bg-primary/10",
|
|
2296
|
+
style: { top: rect.top, left: rect.left, width: rect.width, height: rect.height }
|
|
2297
|
+
}
|
|
2298
|
+
),
|
|
2299
|
+
previewRect && /* @__PURE__ */ jsx27(
|
|
2300
|
+
"div",
|
|
2301
|
+
{
|
|
2302
|
+
"data-testid": "cell-fill-preview-overlay",
|
|
2303
|
+
"aria-hidden": true,
|
|
2304
|
+
className: "pointer-events-none absolute z-20 border-2 border-dashed border-primary",
|
|
2305
|
+
style: { top: previewRect.top, left: previewRect.left, width: previewRect.width, height: previewRect.height }
|
|
2306
|
+
}
|
|
2307
|
+
),
|
|
2308
|
+
onFillHandleMouseDown && /* @__PURE__ */ jsx27(
|
|
2309
|
+
"div",
|
|
2310
|
+
{
|
|
2311
|
+
"data-testid": "cell-fill-handle",
|
|
2312
|
+
role: "button",
|
|
2313
|
+
"aria-label": "Fill handle: drag to fill adjacent cells",
|
|
2314
|
+
className: "absolute z-30 cursor-crosshair border border-background bg-primary",
|
|
2315
|
+
style: {
|
|
2316
|
+
top: rect.top + rect.height - HANDLE_SIZE / 2,
|
|
2317
|
+
left: rect.left + rect.width - HANDLE_SIZE / 2,
|
|
2318
|
+
width: HANDLE_SIZE,
|
|
2319
|
+
height: HANDLE_SIZE
|
|
2320
|
+
},
|
|
2321
|
+
onMouseDown: (event) => {
|
|
2322
|
+
event.stopPropagation();
|
|
2323
|
+
onFillHandleMouseDown(event);
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
)
|
|
2327
|
+
] });
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
// src/cell-editing/coerce.ts
|
|
2331
|
+
function parseBoolean(raw) {
|
|
2332
|
+
const normalized = raw.trim().toLowerCase();
|
|
2333
|
+
if (normalized === "true" || normalized === "yes" || normalized === "1") return true;
|
|
2334
|
+
if (normalized === "false" || normalized === "no" || normalized === "0") return false;
|
|
2335
|
+
return void 0;
|
|
2336
|
+
}
|
|
2337
|
+
function matchEnumOption(options, raw) {
|
|
2338
|
+
const trimmed = raw.trim();
|
|
2339
|
+
const byValue = options.find((option) => option.value === trimmed);
|
|
2340
|
+
if (byValue) return byValue.value;
|
|
2341
|
+
const lowered = trimmed.toLowerCase();
|
|
2342
|
+
return options.find((option) => option.label.toLowerCase() === lowered)?.value;
|
|
2343
|
+
}
|
|
2344
|
+
function coerceValueForColumn(column, raw) {
|
|
2345
|
+
switch (column.type) {
|
|
2346
|
+
case "string":
|
|
2347
|
+
return { value: raw };
|
|
2348
|
+
case "number":
|
|
2349
|
+
case "currency": {
|
|
2350
|
+
if (raw.trim() === "") return { value: null };
|
|
2351
|
+
const num = toNumber(raw);
|
|
2352
|
+
return num === null ? void 0 : { value: num };
|
|
2353
|
+
}
|
|
2354
|
+
case "boolean": {
|
|
2355
|
+
const bool = parseBoolean(raw);
|
|
2356
|
+
return bool === void 0 ? void 0 : { value: bool };
|
|
2357
|
+
}
|
|
2358
|
+
case "date":
|
|
2359
|
+
case "datetime": {
|
|
2360
|
+
const date = toDate(raw);
|
|
2361
|
+
return date === null ? void 0 : { value: date };
|
|
2362
|
+
}
|
|
2363
|
+
case "enum": {
|
|
2364
|
+
const matched = matchEnumOption(column.options, raw);
|
|
2365
|
+
return matched === void 0 ? void 0 : { value: matched };
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
// src/cell-editing/clipboard.ts
|
|
2371
|
+
function quoteTsvField(field) {
|
|
2372
|
+
if (!/[\t\n\r"]/.test(field)) return field;
|
|
2373
|
+
return `"${field.replace(/"/g, '""')}"`;
|
|
2374
|
+
}
|
|
2375
|
+
function rangeToTsv(range, rows, rowIds, columns, columnIds, getRowId) {
|
|
2376
|
+
const rowIndex = buildIndexMap(rowIds);
|
|
2377
|
+
const columnIndex = buildIndexMap(columnIds);
|
|
2378
|
+
const normalized = normalizeRange(range, rowIndex, columnIndex);
|
|
2379
|
+
if (!normalized) return "";
|
|
2380
|
+
const columnById = new Map(columns.map((column) => [column.id, column]));
|
|
2381
|
+
const rowById = new Map(rows.map((row) => [getRowId(row), row]));
|
|
2382
|
+
const lines = [];
|
|
2383
|
+
for (let r = normalized.rowStart; r <= normalized.rowEnd; r++) {
|
|
2384
|
+
const rowId = rowIds[r];
|
|
2385
|
+
const row = rowId !== void 0 ? rowById.get(rowId) : void 0;
|
|
2386
|
+
const fields = [];
|
|
2387
|
+
for (let c = normalized.colStart; c <= normalized.colEnd; c++) {
|
|
2388
|
+
const columnId = columnIds[c];
|
|
2389
|
+
const column = columnId !== void 0 ? columnById.get(columnId) : void 0;
|
|
2390
|
+
if (!column || row === void 0) {
|
|
2391
|
+
fields.push("");
|
|
2392
|
+
continue;
|
|
2393
|
+
}
|
|
2394
|
+
const value = getColumnValue(column, row);
|
|
2395
|
+
const text = column.formatForClipboard ? column.formatForClipboard(value, row) : defaultFormat(column, value);
|
|
2396
|
+
fields.push(quoteTsvField(text));
|
|
2397
|
+
}
|
|
2398
|
+
lines.push(fields.join(" "));
|
|
2399
|
+
}
|
|
2400
|
+
return lines.join("\n");
|
|
2401
|
+
}
|
|
2402
|
+
function parseTsv(text) {
|
|
2403
|
+
const normalized = text.replace(/\r\n/g, "\n");
|
|
2404
|
+
const rows = [];
|
|
2405
|
+
let row = [];
|
|
2406
|
+
let field = "";
|
|
2407
|
+
let inQuotes = false;
|
|
2408
|
+
let i = 0;
|
|
2409
|
+
while (i < normalized.length) {
|
|
2410
|
+
const ch = normalized[i];
|
|
2411
|
+
if (inQuotes) {
|
|
2412
|
+
if (ch === '"') {
|
|
2413
|
+
if (normalized[i + 1] === '"') {
|
|
2414
|
+
field += '"';
|
|
2415
|
+
i += 2;
|
|
2416
|
+
continue;
|
|
2417
|
+
}
|
|
2418
|
+
inQuotes = false;
|
|
2419
|
+
i++;
|
|
2420
|
+
continue;
|
|
2421
|
+
}
|
|
2422
|
+
field += ch;
|
|
2423
|
+
i++;
|
|
2424
|
+
continue;
|
|
2425
|
+
}
|
|
2426
|
+
if (ch === '"' && field === "") {
|
|
2427
|
+
inQuotes = true;
|
|
2428
|
+
i++;
|
|
2429
|
+
continue;
|
|
2430
|
+
}
|
|
2431
|
+
if (ch === " ") {
|
|
2432
|
+
row.push(field);
|
|
2433
|
+
field = "";
|
|
2434
|
+
i++;
|
|
2435
|
+
continue;
|
|
2436
|
+
}
|
|
2437
|
+
if (ch === "\n") {
|
|
2438
|
+
row.push(field);
|
|
2439
|
+
rows.push(row);
|
|
2440
|
+
row = [];
|
|
2441
|
+
field = "";
|
|
2442
|
+
i++;
|
|
2443
|
+
continue;
|
|
2444
|
+
}
|
|
2445
|
+
field += ch;
|
|
2446
|
+
i++;
|
|
2447
|
+
}
|
|
2448
|
+
row.push(field);
|
|
2449
|
+
rows.push(row);
|
|
2450
|
+
const last = rows[rows.length - 1];
|
|
2451
|
+
if (rows.length > 1 && normalized.endsWith("\n") && last?.length === 1 && last[0] === "") {
|
|
2452
|
+
rows.pop();
|
|
2453
|
+
}
|
|
2454
|
+
return rows;
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
// src/cell-editing/fillHandle.ts
|
|
2458
|
+
function computeFillChanges(sourceRange, finalRange, rowIds, columnIds, columns, getRow) {
|
|
2459
|
+
const rowIndex = buildIndexMap(rowIds);
|
|
2460
|
+
const columnIndex = buildIndexMap(columnIds);
|
|
2461
|
+
const source = normalizeRange(sourceRange, rowIndex, columnIndex);
|
|
2462
|
+
const final = normalizeRange(finalRange, rowIndex, columnIndex);
|
|
2463
|
+
if (!source || !final) return [];
|
|
2464
|
+
const columnById = new Map(columns.map((column) => [column.id, column]));
|
|
2465
|
+
const sourceRowSpan = source.rowEnd - source.rowStart + 1;
|
|
2466
|
+
const sourceColSpan = source.colEnd - source.colStart + 1;
|
|
2467
|
+
function sourceRowFor(targetRow) {
|
|
2468
|
+
if (targetRow >= source.rowStart && targetRow <= source.rowEnd) return targetRow;
|
|
2469
|
+
if (targetRow > source.rowEnd) return source.rowStart + (targetRow - source.rowEnd - 1) % sourceRowSpan;
|
|
2470
|
+
return source.rowEnd - (source.rowStart - targetRow - 1) % sourceRowSpan;
|
|
2471
|
+
}
|
|
2472
|
+
function sourceColFor(targetCol) {
|
|
2473
|
+
if (targetCol >= source.colStart && targetCol <= source.colEnd) return targetCol;
|
|
2474
|
+
if (targetCol > source.colEnd) return source.colStart + (targetCol - source.colEnd - 1) % sourceColSpan;
|
|
2475
|
+
return source.colEnd - (source.colStart - targetCol - 1) % sourceColSpan;
|
|
2476
|
+
}
|
|
2477
|
+
const changes = [];
|
|
2478
|
+
for (let r = final.rowStart; r <= final.rowEnd; r++) {
|
|
2479
|
+
const isNewRow = r < source.rowStart || r > source.rowEnd;
|
|
2480
|
+
for (let c = final.colStart; c <= final.colEnd; c++) {
|
|
2481
|
+
const isNewCol = c < source.colStart || c > source.colEnd;
|
|
2482
|
+
if (!isNewRow && !isNewCol) continue;
|
|
2483
|
+
const targetRowId = rowIds[r];
|
|
2484
|
+
const targetColumnId = columnIds[c];
|
|
2485
|
+
if (targetRowId === void 0 || targetColumnId === void 0) continue;
|
|
2486
|
+
const column = columnById.get(targetColumnId);
|
|
2487
|
+
const targetRow = getRow(targetRowId);
|
|
2488
|
+
if (!column || targetRow === void 0 || !isEditable(column, targetRow)) continue;
|
|
2489
|
+
const sourceRowId = rowIds[sourceRowFor(r)];
|
|
2490
|
+
const sourceColumnId = columnIds[sourceColFor(c)];
|
|
2491
|
+
const sourceRow = sourceRowId !== void 0 ? getRow(sourceRowId) : void 0;
|
|
2492
|
+
const sourceColumn = sourceColumnId !== void 0 ? columnById.get(sourceColumnId) : void 0;
|
|
2493
|
+
if (sourceRow === void 0 || !sourceColumn) continue;
|
|
2494
|
+
changes.push({
|
|
2495
|
+
rowId: targetRowId,
|
|
2496
|
+
row: targetRow,
|
|
2497
|
+
columnId: targetColumnId,
|
|
2498
|
+
previousValue: getColumnValue(column, targetRow),
|
|
2499
|
+
value: getColumnValue(sourceColumn, sourceRow)
|
|
2500
|
+
});
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
return changes;
|
|
2504
|
+
}
|
|
2505
|
+
|
|
1812
2506
|
// src/column-selector/ColumnSelector.tsx
|
|
1813
2507
|
import { Columns3 } from "lucide-react";
|
|
1814
2508
|
import { useEffect as useEffect6 } from "react";
|
|
@@ -1816,11 +2510,11 @@ import { useEffect as useEffect6 } from "react";
|
|
|
1816
2510
|
// src/components/ui/dialog.tsx
|
|
1817
2511
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
1818
2512
|
import { X } from "lucide-react";
|
|
1819
|
-
import { forwardRef as
|
|
1820
|
-
import { jsx as
|
|
2513
|
+
import { forwardRef as forwardRef7 } from "react";
|
|
2514
|
+
import { jsx as jsx28, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1821
2515
|
var Dialog = DialogPrimitive.Root;
|
|
1822
2516
|
var DialogTrigger = DialogPrimitive.Trigger;
|
|
1823
|
-
var DialogOverlay =
|
|
2517
|
+
var DialogOverlay = forwardRef7(({ className, ...props }, ref) => /* @__PURE__ */ jsx28(
|
|
1824
2518
|
DialogPrimitive.Overlay,
|
|
1825
2519
|
{
|
|
1826
2520
|
ref,
|
|
@@ -1829,9 +2523,9 @@ var DialogOverlay = forwardRef6(({ className, ...props }, ref) => /* @__PURE__ *
|
|
|
1829
2523
|
}
|
|
1830
2524
|
));
|
|
1831
2525
|
DialogOverlay.displayName = "DialogOverlay";
|
|
1832
|
-
var DialogContent =
|
|
1833
|
-
/* @__PURE__ */
|
|
1834
|
-
/* @__PURE__ */
|
|
2526
|
+
var DialogContent = forwardRef7(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs17(DialogPrimitive.Portal, { children: [
|
|
2527
|
+
/* @__PURE__ */ jsx28(DialogOverlay, {}),
|
|
2528
|
+
/* @__PURE__ */ jsxs17(
|
|
1835
2529
|
DialogPrimitive.Content,
|
|
1836
2530
|
{
|
|
1837
2531
|
ref,
|
|
@@ -1842,9 +2536,9 @@ var DialogContent = forwardRef6(({ className, children, ...props }, ref) => /* @
|
|
|
1842
2536
|
...props,
|
|
1843
2537
|
children: [
|
|
1844
2538
|
children,
|
|
1845
|
-
/* @__PURE__ */
|
|
1846
|
-
/* @__PURE__ */
|
|
1847
|
-
/* @__PURE__ */
|
|
2539
|
+
/* @__PURE__ */ jsxs17(DialogPrimitive.Close, { className: "absolute right-4 top-4 rounded-sm opacity-70 hover:opacity-100", children: [
|
|
2540
|
+
/* @__PURE__ */ jsx28(X, { className: "h-4 w-4" }),
|
|
2541
|
+
/* @__PURE__ */ jsx28("span", { className: "sr-only", children: "Close" })
|
|
1848
2542
|
] })
|
|
1849
2543
|
]
|
|
1850
2544
|
}
|
|
@@ -1854,10 +2548,10 @@ DialogContent.displayName = "DialogContent";
|
|
|
1854
2548
|
var DialogHeader = ({
|
|
1855
2549
|
className,
|
|
1856
2550
|
...props
|
|
1857
|
-
}) => /* @__PURE__ */
|
|
1858
|
-
var DialogTitle =
|
|
2551
|
+
}) => /* @__PURE__ */ jsx28("div", { className: cn("flex flex-col gap-1.5", className), ...props });
|
|
2552
|
+
var DialogTitle = forwardRef7(({ className, ...props }, ref) => /* @__PURE__ */ jsx28(DialogPrimitive.Title, { ref, className: cn("text-lg font-semibold", className), ...props }));
|
|
1859
2553
|
DialogTitle.displayName = "DialogTitle";
|
|
1860
|
-
var DialogDescription =
|
|
2554
|
+
var DialogDescription = forwardRef7(({ className, ...props }, ref) => /* @__PURE__ */ jsx28(
|
|
1861
2555
|
DialogPrimitive.Description,
|
|
1862
2556
|
{
|
|
1863
2557
|
ref,
|
|
@@ -1925,7 +2619,7 @@ function canHideGroup(columns, visibility, groupColumnIds) {
|
|
|
1925
2619
|
}
|
|
1926
2620
|
|
|
1927
2621
|
// src/column-selector/ColumnSelector.tsx
|
|
1928
|
-
import { jsx as
|
|
2622
|
+
import { jsx as jsx29, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1929
2623
|
function ColumnSelector({
|
|
1930
2624
|
columns,
|
|
1931
2625
|
visibility,
|
|
@@ -1950,20 +2644,20 @@ function ColumnSelector({
|
|
|
1950
2644
|
}
|
|
1951
2645
|
const totalVisible = countVisible(columns, visibility);
|
|
1952
2646
|
const groups = groupColumns(columns);
|
|
1953
|
-
return /* @__PURE__ */
|
|
1954
|
-
/* @__PURE__ */
|
|
1955
|
-
/* @__PURE__ */
|
|
1956
|
-
/* @__PURE__ */
|
|
1957
|
-
/* @__PURE__ */
|
|
1958
|
-
group && /* @__PURE__ */
|
|
2647
|
+
return /* @__PURE__ */ jsxs18(Dialog, { open, onOpenChange, children: [
|
|
2648
|
+
/* @__PURE__ */ jsx29(DialogTrigger, { asChild: true, children: trigger ?? /* @__PURE__ */ jsx29(Button, { variant: "outline", size: "icon", "aria-label": "Choose columns", children: /* @__PURE__ */ jsx29(Columns3, { className: "h-4 w-4", "aria-hidden": true }) }) }),
|
|
2649
|
+
/* @__PURE__ */ jsxs18(DialogContent, { className: "w-fit max-w-[calc(100vw-2rem)] sm:max-w-2xl", children: [
|
|
2650
|
+
/* @__PURE__ */ jsx29(DialogHeader, { children: /* @__PURE__ */ jsx29(DialogTitle, { children: "Columns" }) }),
|
|
2651
|
+
/* @__PURE__ */ jsx29("div", { className: "flex flex-wrap gap-x-8 gap-y-5", children: groups.map(({ group, columns: groupCols }) => /* @__PURE__ */ jsxs18("div", { className: "flex min-w-32 flex-col gap-1.5", children: [
|
|
2652
|
+
group && /* @__PURE__ */ jsx29("span", { className: "border-b pb-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: group }),
|
|
1959
2653
|
groupCols.map((column) => {
|
|
1960
2654
|
const visible = isColumnVisible(visibility, column.id);
|
|
1961
|
-
return /* @__PURE__ */
|
|
2655
|
+
return /* @__PURE__ */ jsxs18(
|
|
1962
2656
|
"label",
|
|
1963
2657
|
{
|
|
1964
2658
|
className: "flex items-center gap-2 rounded-md px-1.5 py-1 text-sm hover:bg-accent/50 focus-within:bg-accent/50",
|
|
1965
2659
|
children: [
|
|
1966
|
-
/* @__PURE__ */
|
|
2660
|
+
/* @__PURE__ */ jsx29(
|
|
1967
2661
|
Checkbox,
|
|
1968
2662
|
{
|
|
1969
2663
|
checked: visible,
|
|
@@ -1995,15 +2689,15 @@ function resolveMenuItems(items, ctx) {
|
|
|
1995
2689
|
|
|
1996
2690
|
// src/menu/ActionsMenu.tsx
|
|
1997
2691
|
import { MoreVertical } from "lucide-react";
|
|
1998
|
-
import { useMemo as
|
|
2692
|
+
import { useMemo as useMemo4 } from "react";
|
|
1999
2693
|
|
|
2000
2694
|
// src/components/ui/dropdown-menu.tsx
|
|
2001
2695
|
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
2002
|
-
import { forwardRef as
|
|
2003
|
-
import { jsx as
|
|
2696
|
+
import { forwardRef as forwardRef8 } from "react";
|
|
2697
|
+
import { jsx as jsx30 } from "react/jsx-runtime";
|
|
2004
2698
|
var DropdownMenu = DropdownMenuPrimitive.Root;
|
|
2005
2699
|
var DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
|
2006
|
-
var DropdownMenuContent =
|
|
2700
|
+
var DropdownMenuContent = forwardRef8(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx30(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx30(
|
|
2007
2701
|
DropdownMenuPrimitive.Content,
|
|
2008
2702
|
{
|
|
2009
2703
|
ref,
|
|
@@ -2016,7 +2710,7 @@ var DropdownMenuContent = forwardRef7(({ className, sideOffset = 4, ...props },
|
|
|
2016
2710
|
}
|
|
2017
2711
|
) }));
|
|
2018
2712
|
DropdownMenuContent.displayName = "DropdownMenuContent";
|
|
2019
|
-
var DropdownMenuItem =
|
|
2713
|
+
var DropdownMenuItem = forwardRef8(({ className, danger, ...props }, ref) => /* @__PURE__ */ jsx30(
|
|
2020
2714
|
DropdownMenuPrimitive.Item,
|
|
2021
2715
|
{
|
|
2022
2716
|
ref,
|
|
@@ -2029,7 +2723,7 @@ var DropdownMenuItem = forwardRef7(({ className, danger, ...props }, ref) => /*
|
|
|
2029
2723
|
}
|
|
2030
2724
|
));
|
|
2031
2725
|
DropdownMenuItem.displayName = "DropdownMenuItem";
|
|
2032
|
-
var DropdownMenuSeparator =
|
|
2726
|
+
var DropdownMenuSeparator = forwardRef8(({ className, ...props }, ref) => /* @__PURE__ */ jsx30(
|
|
2033
2727
|
DropdownMenuPrimitive.Separator,
|
|
2034
2728
|
{
|
|
2035
2729
|
ref,
|
|
@@ -2038,7 +2732,7 @@ var DropdownMenuSeparator = forwardRef7(({ className, ...props }, ref) => /* @__
|
|
|
2038
2732
|
}
|
|
2039
2733
|
));
|
|
2040
2734
|
DropdownMenuSeparator.displayName = "DropdownMenuSeparator";
|
|
2041
|
-
var DropdownMenuLabel =
|
|
2735
|
+
var DropdownMenuLabel = forwardRef8(({ className, ...props }, ref) => /* @__PURE__ */ jsx30(
|
|
2042
2736
|
DropdownMenuPrimitive.Label,
|
|
2043
2737
|
{
|
|
2044
2738
|
ref,
|
|
@@ -2049,16 +2743,16 @@ var DropdownMenuLabel = forwardRef7(({ className, ...props }, ref) => /* @__PURE
|
|
|
2049
2743
|
DropdownMenuLabel.displayName = "DropdownMenuLabel";
|
|
2050
2744
|
|
|
2051
2745
|
// src/menu/ActionsMenu.tsx
|
|
2052
|
-
import { jsx as
|
|
2746
|
+
import { jsx as jsx31, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
2053
2747
|
function ActionsMenu({ items, ctx, triggerLabel }) {
|
|
2054
|
-
const resolved =
|
|
2748
|
+
const resolved = useMemo4(
|
|
2055
2749
|
() => resolveMenuItems(items, ctx),
|
|
2056
2750
|
[items, ctx.row, ctx.selectedRows]
|
|
2057
2751
|
);
|
|
2058
2752
|
if (resolved.length === 0) return null;
|
|
2059
|
-
return /* @__PURE__ */
|
|
2060
|
-
/* @__PURE__ */
|
|
2061
|
-
/* @__PURE__ */
|
|
2753
|
+
return /* @__PURE__ */ jsxs19(DropdownMenu, { children: [
|
|
2754
|
+
/* @__PURE__ */ jsx31(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx31(Button, { variant: "ghost", size: "icon", "aria-label": triggerLabel, onClick: stopRowClick, children: /* @__PURE__ */ jsx31(MoreVertical, { className: "h-4 w-4", "aria-hidden": true }) }) }),
|
|
2755
|
+
/* @__PURE__ */ jsx31(DropdownMenuContent, { align: "end", children: resolved.map((item) => /* @__PURE__ */ jsx31(
|
|
2062
2756
|
DropdownMenuItem,
|
|
2063
2757
|
{
|
|
2064
2758
|
danger: item.danger,
|
|
@@ -2076,25 +2770,25 @@ import { BarsArrowDownIcon, BarsArrowUpIcon, FunnelIcon as FunnelIcon7 } from "@
|
|
|
2076
2770
|
import { columnResizingFeature, columnSizingFeature, flexRender, tableFeatures, useTable } from "@tanstack/react-table";
|
|
2077
2771
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
2078
2772
|
import { ChevronRight, Loader2 } from "lucide-react";
|
|
2079
|
-
import { Fragment as
|
|
2773
|
+
import { Fragment as Fragment3, useEffect as useEffect9, useMemo as useMemo8, useRef as useRef11, useState as useState13 } from "react";
|
|
2080
2774
|
|
|
2081
2775
|
// src/hooks/useGroupExpansion.ts
|
|
2082
|
-
import { useCallback as
|
|
2776
|
+
import { useCallback as useCallback3, useState as useState10 } from "react";
|
|
2083
2777
|
function useGroupExpansion(defaultGroupsExpanded, controlledExpandedGroups, onExpandedGroupsChange) {
|
|
2084
|
-
const [internalExpandedGroups, setInternalExpandedGroups] =
|
|
2778
|
+
const [internalExpandedGroups, setInternalExpandedGroups] = useState10({});
|
|
2085
2779
|
const expandedGroups = controlledExpandedGroups ?? internalExpandedGroups;
|
|
2086
|
-
const updateExpandedGroups =
|
|
2780
|
+
const updateExpandedGroups = useCallback3(
|
|
2087
2781
|
(next) => {
|
|
2088
2782
|
setInternalExpandedGroups(next);
|
|
2089
2783
|
onExpandedGroupsChange?.(next);
|
|
2090
2784
|
},
|
|
2091
2785
|
[onExpandedGroupsChange]
|
|
2092
2786
|
);
|
|
2093
|
-
const isGroupExpanded =
|
|
2787
|
+
const isGroupExpanded = useCallback3(
|
|
2094
2788
|
(key) => expandedGroups[key] ?? defaultGroupsExpanded,
|
|
2095
2789
|
[expandedGroups, defaultGroupsExpanded]
|
|
2096
2790
|
);
|
|
2097
|
-
const toggleGroupExpanded =
|
|
2791
|
+
const toggleGroupExpanded = useCallback3(
|
|
2098
2792
|
(key) => {
|
|
2099
2793
|
updateExpandedGroups({ ...expandedGroups, [key]: !isGroupExpanded(key) });
|
|
2100
2794
|
},
|
|
@@ -2104,11 +2798,11 @@ function useGroupExpansion(defaultGroupsExpanded, controlledExpandedGroups, onEx
|
|
|
2104
2798
|
}
|
|
2105
2799
|
|
|
2106
2800
|
// src/hooks/useStickyGroupHeaderTop.ts
|
|
2107
|
-
import { useLayoutEffect, useRef as
|
|
2801
|
+
import { useLayoutEffect as useLayoutEffect3, useRef as useRef9, useState as useState11 } from "react";
|
|
2108
2802
|
function useStickyGroupHeaderTop(enabled) {
|
|
2109
|
-
const theadRef =
|
|
2110
|
-
const [groupHeaderTop, setGroupHeaderTop] =
|
|
2111
|
-
|
|
2803
|
+
const theadRef = useRef9(null);
|
|
2804
|
+
const [groupHeaderTop, setGroupHeaderTop] = useState11(0);
|
|
2805
|
+
useLayoutEffect3(() => {
|
|
2112
2806
|
if (!enabled) return;
|
|
2113
2807
|
const theadEl = theadRef.current;
|
|
2114
2808
|
if (!theadEl) return;
|
|
@@ -2122,9 +2816,9 @@ function useStickyGroupHeaderTop(enabled) {
|
|
|
2122
2816
|
}
|
|
2123
2817
|
|
|
2124
2818
|
// src/hooks/useVisibleColumns.ts
|
|
2125
|
-
import { useMemo as
|
|
2819
|
+
import { useMemo as useMemo5 } from "react";
|
|
2126
2820
|
function useVisibleColumns(columns, columnVisibility) {
|
|
2127
|
-
return
|
|
2821
|
+
return useMemo5(
|
|
2128
2822
|
() => columnVisibility ? columns.filter((column) => isColumnVisible(columnVisibility, column.id)) : columns,
|
|
2129
2823
|
[columns, columnVisibility]
|
|
2130
2824
|
);
|
|
@@ -2171,20 +2865,20 @@ function processClientData(data, state) {
|
|
|
2171
2865
|
}
|
|
2172
2866
|
|
|
2173
2867
|
// src/grid/useGridState.ts
|
|
2174
|
-
import { useCallback as
|
|
2868
|
+
import { useCallback as useCallback4, useEffect as useEffect8, useMemo as useMemo7, useState as useState12 } from "react";
|
|
2175
2869
|
|
|
2176
2870
|
// src/hooks/useDebouncedCallback.ts
|
|
2177
|
-
import { useEffect as useEffect7, useMemo as
|
|
2871
|
+
import { useEffect as useEffect7, useMemo as useMemo6, useRef as useRef10 } from "react";
|
|
2178
2872
|
function useDebouncedCallback(callback, delayMs) {
|
|
2179
|
-
const callbackRef =
|
|
2873
|
+
const callbackRef = useRef10(callback);
|
|
2180
2874
|
callbackRef.current = callback;
|
|
2181
|
-
const timeoutRef =
|
|
2875
|
+
const timeoutRef = useRef10(void 0);
|
|
2182
2876
|
useEffect7(() => {
|
|
2183
2877
|
return () => {
|
|
2184
2878
|
clearTimeout(timeoutRef.current);
|
|
2185
2879
|
};
|
|
2186
2880
|
}, []);
|
|
2187
|
-
return
|
|
2881
|
+
return useMemo6(() => {
|
|
2188
2882
|
return {
|
|
2189
2883
|
run: (...args) => {
|
|
2190
2884
|
clearTimeout(timeoutRef.current);
|
|
@@ -2209,10 +2903,10 @@ function nextSortDir(current, descFirst) {
|
|
|
2209
2903
|
return void 0;
|
|
2210
2904
|
}
|
|
2211
2905
|
function useGridState(dataSource, initialState, externalState) {
|
|
2212
|
-
const [state, setState] =
|
|
2906
|
+
const [state, setState] = useState12(
|
|
2213
2907
|
() => externalState ?? { ...DEFAULT_STATE, ...initialState }
|
|
2214
2908
|
);
|
|
2215
|
-
const notifyRaw =
|
|
2909
|
+
const notifyRaw = useCallback4(
|
|
2216
2910
|
(next) => {
|
|
2217
2911
|
if (dataSource.mode === "server") dataSource.onStateChange(next);
|
|
2218
2912
|
},
|
|
@@ -2222,7 +2916,7 @@ function useGridState(dataSource, initialState, externalState) {
|
|
|
2222
2916
|
notifyRaw,
|
|
2223
2917
|
FILTER_CHANGE_DEBOUNCE_MS
|
|
2224
2918
|
);
|
|
2225
|
-
const notifyNow =
|
|
2919
|
+
const notifyNow = useCallback4(
|
|
2226
2920
|
(next) => {
|
|
2227
2921
|
cancelDebouncedNotify();
|
|
2228
2922
|
notifyRaw(next);
|
|
@@ -2234,14 +2928,14 @@ function useGridState(dataSource, initialState, externalState) {
|
|
|
2234
2928
|
cancelDebouncedNotify();
|
|
2235
2929
|
setState(externalState);
|
|
2236
2930
|
}, [externalState, cancelDebouncedNotify]);
|
|
2237
|
-
const filtersByColumn =
|
|
2931
|
+
const filtersByColumn = useMemo7(() => {
|
|
2238
2932
|
const map = /* @__PURE__ */ new Map();
|
|
2239
2933
|
for (const child of state.filter?.filters ?? []) {
|
|
2240
2934
|
if ("operator" in child) map.set(fieldKey(child.field), child);
|
|
2241
2935
|
}
|
|
2242
2936
|
return map;
|
|
2243
2937
|
}, [state.filter]);
|
|
2244
|
-
const setColumnFilter =
|
|
2938
|
+
const setColumnFilter = useCallback4(
|
|
2245
2939
|
(columnId, next) => {
|
|
2246
2940
|
setState((prev) => {
|
|
2247
2941
|
const remaining = (prev.filter?.filters ?? []).filter(
|
|
@@ -2259,7 +2953,7 @@ function useGridState(dataSource, initialState, externalState) {
|
|
|
2259
2953
|
},
|
|
2260
2954
|
[notifyDebounced]
|
|
2261
2955
|
);
|
|
2262
|
-
const toggleSort =
|
|
2956
|
+
const toggleSort = useCallback4(
|
|
2263
2957
|
(columnId, additive, descFirst = false) => {
|
|
2264
2958
|
setState((prev) => {
|
|
2265
2959
|
const existing = prev.sort.find((s) => s.field === columnId);
|
|
@@ -2274,7 +2968,7 @@ function useGridState(dataSource, initialState, externalState) {
|
|
|
2274
2968
|
},
|
|
2275
2969
|
[notifyNow]
|
|
2276
2970
|
);
|
|
2277
|
-
const setPage =
|
|
2971
|
+
const setPage = useCallback4(
|
|
2278
2972
|
(page) => {
|
|
2279
2973
|
setState((prev) => {
|
|
2280
2974
|
const nextState = { ...prev, page };
|
|
@@ -2284,7 +2978,7 @@ function useGridState(dataSource, initialState, externalState) {
|
|
|
2284
2978
|
},
|
|
2285
2979
|
[notifyNow]
|
|
2286
2980
|
);
|
|
2287
|
-
const setPageSize =
|
|
2981
|
+
const setPageSize = useCallback4(
|
|
2288
2982
|
(pageSize) => {
|
|
2289
2983
|
setState((prev) => {
|
|
2290
2984
|
const nextState = { ...prev, pageSize, page: 0 };
|
|
@@ -2298,11 +2992,12 @@ function useGridState(dataSource, initialState, externalState) {
|
|
|
2298
2992
|
}
|
|
2299
2993
|
|
|
2300
2994
|
// src/grid/DataGrid.tsx
|
|
2301
|
-
import { Fragment as
|
|
2995
|
+
import { Fragment as Fragment4, jsx as jsx32, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
2302
2996
|
var gridTableFeatures = tableFeatures({ columnSizingFeature, columnResizingFeature });
|
|
2303
2997
|
var DEFAULT_COLUMN_SIZE = 150;
|
|
2304
2998
|
var DETAIL_COLUMN_WIDTH = 44;
|
|
2305
2999
|
var ROW_ACTIONS_COLUMN_WIDTH = 44;
|
|
3000
|
+
var EMPTY_ID_ARRAY = [];
|
|
2306
3001
|
var STRUCTURAL_ZEBRA_BG_CLASS = "bg-[color-mix(in_srgb,var(--color-foreground)_5%,var(--color-background))]";
|
|
2307
3002
|
function pinnedOffsets(visibleColumns, side, getSize, leadingOffset) {
|
|
2308
3003
|
const offsets = /* @__PURE__ */ new Map();
|
|
@@ -2329,13 +3024,13 @@ function computeHeaderRuns(columns) {
|
|
|
2329
3024
|
}
|
|
2330
3025
|
return runs;
|
|
2331
3026
|
}
|
|
2332
|
-
function toTanstackColumns(columns, editingCtxRef) {
|
|
3027
|
+
function toTanstackColumns(columns, editingCtxRef, cellEditingCtxRef) {
|
|
2333
3028
|
return columns.map((column) => ({
|
|
2334
3029
|
id: column.id,
|
|
2335
3030
|
header: column.header,
|
|
2336
3031
|
size: column.width,
|
|
2337
3032
|
accessorFn: (row) => getColumnValue(column, row),
|
|
2338
|
-
cell: (info) => renderEditableCell(column, info.row.original, info.getValue(), editingCtxRef.current)
|
|
3033
|
+
cell: (info) => cellEditingCtxRef.current ? renderCellModeCell(column, info.row.original, info.getValue(), cellEditingCtxRef.current) : renderEditableCell(column, info.row.original, info.getValue(), editingCtxRef.current)
|
|
2339
3034
|
}));
|
|
2340
3035
|
}
|
|
2341
3036
|
function DataGrid({
|
|
@@ -2367,26 +3062,28 @@ function DataGrid({
|
|
|
2367
3062
|
expandedGroups: controlledExpandedGroups,
|
|
2368
3063
|
onExpandedGroupsChange,
|
|
2369
3064
|
zebra = true,
|
|
2370
|
-
editing
|
|
3065
|
+
editing,
|
|
3066
|
+
cellEditing
|
|
2371
3067
|
}) {
|
|
3068
|
+
const hasCellEditing = Boolean(cellEditing) && !groupBy;
|
|
2372
3069
|
const { state, filtersByColumn, setColumnFilter, toggleSort, setPage } = useGridState(
|
|
2373
3070
|
dataSource,
|
|
2374
3071
|
initialState,
|
|
2375
3072
|
gridState
|
|
2376
3073
|
);
|
|
2377
|
-
const [internalSelectedIds, setInternalSelectedIds] =
|
|
3074
|
+
const [internalSelectedIds, setInternalSelectedIds] = useState13(/* @__PURE__ */ new Set());
|
|
2378
3075
|
const selectedIds = controlledSelectedIds ?? internalSelectedIds;
|
|
2379
3076
|
function updateSelectedIds(next) {
|
|
2380
3077
|
setInternalSelectedIds(next);
|
|
2381
3078
|
onSelectedIdsChange?.(next);
|
|
2382
3079
|
}
|
|
2383
|
-
const [internalColumnSizing, setInternalColumnSizing] =
|
|
3080
|
+
const [internalColumnSizing, setInternalColumnSizing] = useState13({});
|
|
2384
3081
|
const columnSizing = controlledColumnSizing ?? internalColumnSizing;
|
|
2385
3082
|
function updateColumnSizing(next) {
|
|
2386
3083
|
setInternalColumnSizing(next);
|
|
2387
3084
|
onColumnSizingChange?.(next);
|
|
2388
3085
|
}
|
|
2389
|
-
const [expandedIds, setExpandedIds] =
|
|
3086
|
+
const [expandedIds, setExpandedIds] = useState13(/* @__PURE__ */ new Set());
|
|
2390
3087
|
function toggleRowExpanded(id) {
|
|
2391
3088
|
setExpandedIds((prev) => {
|
|
2392
3089
|
const next = new Set(prev);
|
|
@@ -2404,13 +3101,17 @@ function DataGrid({
|
|
|
2404
3101
|
editing,
|
|
2405
3102
|
getRowId
|
|
2406
3103
|
);
|
|
3104
|
+
const { ctx: cellEditingCtx, ctxRef: cellEditingCtxRef, applyChanges: applyCellChanges } = useCellEditingState(
|
|
3105
|
+
hasCellEditing ? cellEditing : void 0,
|
|
3106
|
+
getRowId
|
|
3107
|
+
);
|
|
2407
3108
|
const visibleColumns = useVisibleColumns(columns, columnVisibility);
|
|
2408
3109
|
const serverRowCount = dataSource.mode === "server" ? dataSource.rowCount : void 0;
|
|
2409
|
-
const { rows, rowCount } =
|
|
3110
|
+
const { rows, rowCount } = useMemo8(() => {
|
|
2410
3111
|
if (dataSource.mode === "client") return processClientData(dataSource.data, state);
|
|
2411
3112
|
return { rows: dataSource.data, rowCount: serverRowCount ?? 0 };
|
|
2412
3113
|
}, [dataSource.mode, dataSource.data, serverRowCount, state]);
|
|
2413
|
-
const selectedRows =
|
|
3114
|
+
const selectedRows = useMemo8(
|
|
2414
3115
|
() => rows.filter((row) => selectedIds.has(getRowId(row))),
|
|
2415
3116
|
[rows, selectedIds, getRowId]
|
|
2416
3117
|
);
|
|
@@ -2429,8 +3130,11 @@ function DataGrid({
|
|
|
2429
3130
|
}
|
|
2430
3131
|
updateSelectedIds(next);
|
|
2431
3132
|
}
|
|
2432
|
-
const tanstackColumns =
|
|
2433
|
-
|
|
3133
|
+
const tanstackColumns = useMemo8(
|
|
3134
|
+
() => toTanstackColumns(visibleColumns, editingCtxRef, cellEditingCtxRef),
|
|
3135
|
+
[visibleColumns]
|
|
3136
|
+
);
|
|
3137
|
+
const columnById = useMemo8(() => {
|
|
2434
3138
|
const map = /* @__PURE__ */ new Map();
|
|
2435
3139
|
for (const column of visibleColumns) map.set(column.id, column);
|
|
2436
3140
|
return map;
|
|
@@ -2448,6 +3152,9 @@ function DataGrid({
|
|
|
2448
3152
|
updateColumnSizing(next);
|
|
2449
3153
|
}
|
|
2450
3154
|
});
|
|
3155
|
+
function findRowById(rowId) {
|
|
3156
|
+
return table.getRowModel().rowsById[rowId]?.original;
|
|
3157
|
+
}
|
|
2451
3158
|
const pageCount = Math.max(1, Math.ceil(rowCount / state.pageSize));
|
|
2452
3159
|
const loading = loadingProp || (dataSource.mode === "server" ? Boolean(dataSource.loading) : false);
|
|
2453
3160
|
const showSelectionColumn = Boolean(headerActions?.length) || controlledSelectedIds !== void 0;
|
|
@@ -2455,7 +3162,7 @@ function DataGrid({
|
|
|
2455
3162
|
const showDetailColumn = Boolean(renderDetail);
|
|
2456
3163
|
const totalColumnCount = visibleColumns.length + (showDetailColumn ? 1 : 0) + (showSelectionColumn ? 1 : 0) + (showRowActionsColumn ? 1 : 0);
|
|
2457
3164
|
const hasFilterRow = visibleColumns.some((column) => isFilterable(column) && column.filterDisplay === "row");
|
|
2458
|
-
const headerRuns =
|
|
3165
|
+
const headerRuns = useMemo8(() => computeHeaderRuns(visibleColumns), [visibleColumns]);
|
|
2459
3166
|
const hasHeaderGroups = visibleColumns.some((column) => column.headerGroup);
|
|
2460
3167
|
function renderFilterWidget(column) {
|
|
2461
3168
|
const value = filtersByColumn.get(column.id);
|
|
@@ -2465,11 +3172,11 @@ function DataGrid({
|
|
|
2465
3172
|
const columnSize = (columnId) => table.getColumn(columnId)?.getSize() ?? DEFAULT_COLUMN_SIZE;
|
|
2466
3173
|
const leadingStructuralWidth = (showDetailColumn ? DETAIL_COLUMN_WIDTH : 0) + (showSelectionColumn ? SELECTION_COLUMN_WIDTH : 0);
|
|
2467
3174
|
const trailingStructuralWidth = showRowActionsColumn ? ROW_ACTIONS_COLUMN_WIDTH : 0;
|
|
2468
|
-
const leftPinnedOffsets =
|
|
3175
|
+
const leftPinnedOffsets = useMemo8(
|
|
2469
3176
|
() => pinnedOffsets(visibleColumns, "left", columnSize, leadingStructuralWidth),
|
|
2470
3177
|
[visibleColumns, columnSizing, leadingStructuralWidth]
|
|
2471
3178
|
);
|
|
2472
|
-
const rightPinnedOffsets =
|
|
3179
|
+
const rightPinnedOffsets = useMemo8(
|
|
2473
3180
|
() => pinnedOffsets(visibleColumns, "right", columnSize, trailingStructuralWidth),
|
|
2474
3181
|
[visibleColumns, columnSizing, trailingStructuralWidth]
|
|
2475
3182
|
);
|
|
@@ -2517,7 +3224,7 @@ function DataGrid({
|
|
|
2517
3224
|
style: pinnedProps?.style
|
|
2518
3225
|
};
|
|
2519
3226
|
}
|
|
2520
|
-
const bodyCellPropsByColumn =
|
|
3227
|
+
const bodyCellPropsByColumn = useMemo8(() => {
|
|
2521
3228
|
const map = /* @__PURE__ */ new Map();
|
|
2522
3229
|
for (const column of visibleColumns) map.set(column.id, bodyCellClassAndStyle(column));
|
|
2523
3230
|
return map;
|
|
@@ -2533,7 +3240,7 @@ function DataGrid({
|
|
|
2533
3240
|
);
|
|
2534
3241
|
return { className, style: pinnedProps.style };
|
|
2535
3242
|
}
|
|
2536
|
-
const leafHeaderCellPropsByColumn =
|
|
3243
|
+
const leafHeaderCellPropsByColumn = useMemo8(() => {
|
|
2537
3244
|
const map = /* @__PURE__ */ new Map();
|
|
2538
3245
|
for (const column of visibleColumns) {
|
|
2539
3246
|
map.set(
|
|
@@ -2543,7 +3250,7 @@ function DataGrid({
|
|
|
2543
3250
|
}
|
|
2544
3251
|
return map;
|
|
2545
3252
|
}, [visibleColumns, enableColumnResizing, leftPinnedOffsets, rightPinnedOffsets, columnSizing]);
|
|
2546
|
-
const filterHeaderCellPropsByColumn =
|
|
3253
|
+
const filterHeaderCellPropsByColumn = useMemo8(() => {
|
|
2547
3254
|
const map = /* @__PURE__ */ new Map();
|
|
2548
3255
|
for (const column of visibleColumns) {
|
|
2549
3256
|
map.set(column.id, headerCellClassAndStyle(column, "border-b border-border p-2 align-top font-normal"));
|
|
@@ -2551,15 +3258,28 @@ function DataGrid({
|
|
|
2551
3258
|
return map;
|
|
2552
3259
|
}, [visibleColumns, enableColumnResizing, leftPinnedOffsets, rightPinnedOffsets, columnSizing]);
|
|
2553
3260
|
const tableRows = table.getRowModel().rows;
|
|
2554
|
-
const
|
|
2555
|
-
const groupedBuckets = useMemo7(
|
|
3261
|
+
const groupedBuckets = useMemo8(
|
|
2556
3262
|
() => groupBy ? groupRows(tableRows, (row) => groupBy(row.original)) : void 0,
|
|
2557
3263
|
[tableRows, groupBy]
|
|
2558
3264
|
);
|
|
2559
3265
|
const { theadRef, groupHeaderTop } = useStickyGroupHeaderTop(Boolean(groupBy));
|
|
2560
|
-
const
|
|
3266
|
+
const shouldVirtualize = Boolean(virtualize) && tableRows.length > (virtualize?.threshold ?? 100);
|
|
3267
|
+
const flatItems = useMemo8(() => {
|
|
3268
|
+
if (!shouldVirtualize) return [];
|
|
3269
|
+
if (!groupedBuckets) return tableRows.map((row) => ({ kind: "row", row }));
|
|
3270
|
+
const items = [];
|
|
3271
|
+
for (const bucket of groupedBuckets) {
|
|
3272
|
+
const originalRows = bucket.items.map((row) => row.original);
|
|
3273
|
+
items.push({ kind: "header", key: bucket.key, rows: originalRows });
|
|
3274
|
+
if (isGroupExpanded(bucket.key)) {
|
|
3275
|
+
for (const row of bucket.items) items.push({ kind: "row", row });
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
return items;
|
|
3279
|
+
}, [shouldVirtualize, groupedBuckets, tableRows, isGroupExpanded]);
|
|
3280
|
+
const scrollRef = useRef11(null);
|
|
2561
3281
|
const virtualizer = useVirtualizer({
|
|
2562
|
-
count:
|
|
3282
|
+
count: flatItems.length,
|
|
2563
3283
|
getScrollElement: () => scrollRef.current,
|
|
2564
3284
|
estimateSize: () => virtualize?.estimatedRowHeight ?? 40,
|
|
2565
3285
|
overscan: virtualize?.overscan ?? 10,
|
|
@@ -2569,34 +3289,235 @@ function DataGrid({
|
|
|
2569
3289
|
const paddingTop = virtualItems.length > 0 ? virtualItems[0].start : 0;
|
|
2570
3290
|
const paddingBottom = virtualItems.length > 0 ? virtualizer.getTotalSize() - virtualItems[virtualItems.length - 1].end : 0;
|
|
2571
3291
|
const lastVisibleIndex = virtualItems.length > 0 ? virtualItems[virtualItems.length - 1].index : -1;
|
|
2572
|
-
const notifiedForLengthRef =
|
|
3292
|
+
const notifiedForLengthRef = useRef11(-1);
|
|
2573
3293
|
useEffect9(() => {
|
|
2574
3294
|
if (!virtualize?.onEndReached) return;
|
|
2575
|
-
if (lastVisibleIndex < 0 || lastVisibleIndex <
|
|
3295
|
+
if (lastVisibleIndex < 0 || lastVisibleIndex < flatItems.length - 1) return;
|
|
2576
3296
|
if (virtualize.hasMore === false) return;
|
|
2577
3297
|
if (notifiedForLengthRef.current === tableRows.length) return;
|
|
2578
3298
|
notifiedForLengthRef.current = tableRows.length;
|
|
2579
3299
|
virtualize.onEndReached();
|
|
2580
|
-
}, [lastVisibleIndex, tableRows.length, virtualize]);
|
|
2581
|
-
|
|
3300
|
+
}, [lastVisibleIndex, flatItems.length, tableRows.length, virtualize]);
|
|
3301
|
+
const cellEditingRowIds = useMemo8(
|
|
3302
|
+
() => hasCellEditing ? tableRows.map((row) => row.id) : EMPTY_ID_ARRAY,
|
|
3303
|
+
[hasCellEditing, tableRows]
|
|
3304
|
+
);
|
|
3305
|
+
const cellEditingColumnIds = useMemo8(
|
|
3306
|
+
() => hasCellEditing ? visibleColumns.map((column) => column.id) : EMPTY_ID_ARRAY,
|
|
3307
|
+
[hasCellEditing, visibleColumns]
|
|
3308
|
+
);
|
|
3309
|
+
const cellEditingRowIndex = useMemo8(() => buildIndexMap(cellEditingRowIds), [cellEditingRowIds]);
|
|
3310
|
+
const cellSelection = useCellSelection({
|
|
3311
|
+
rowIds: cellEditingRowIds,
|
|
3312
|
+
columnIds: cellEditingColumnIds,
|
|
3313
|
+
onNavigateToRow: (rowId) => {
|
|
3314
|
+
if (!shouldVirtualize) return;
|
|
3315
|
+
const index = cellEditingRowIndex.get(rowId);
|
|
3316
|
+
if (index !== void 0) virtualizer.scrollToIndex(index);
|
|
3317
|
+
}
|
|
3318
|
+
});
|
|
3319
|
+
const pendingDragCellRef = useRef11(null);
|
|
3320
|
+
const dragRafRef = useRef11(null);
|
|
3321
|
+
useEffect9(() => {
|
|
3322
|
+
if (!cellSelection.isDragging) return;
|
|
3323
|
+
function handleMouseMove(event) {
|
|
3324
|
+
const cellEl = event.target?.closest("[data-cell-row]");
|
|
3325
|
+
const rowId = cellEl?.dataset.cellRow;
|
|
3326
|
+
const columnId = cellEl?.dataset.cellCol;
|
|
3327
|
+
if (rowId === void 0 || columnId === void 0) return;
|
|
3328
|
+
pendingDragCellRef.current = { rowId, columnId };
|
|
3329
|
+
if (dragRafRef.current !== null) return;
|
|
3330
|
+
dragRafRef.current = requestAnimationFrame(() => {
|
|
3331
|
+
dragRafRef.current = null;
|
|
3332
|
+
if (pendingDragCellRef.current) cellSelection.updateDrag(pendingDragCellRef.current);
|
|
3333
|
+
});
|
|
3334
|
+
}
|
|
3335
|
+
function handleMouseUp() {
|
|
3336
|
+
cellSelection.endDrag();
|
|
3337
|
+
}
|
|
3338
|
+
window.addEventListener("mousemove", handleMouseMove);
|
|
3339
|
+
window.addEventListener("mouseup", handleMouseUp);
|
|
3340
|
+
return () => {
|
|
3341
|
+
window.removeEventListener("mousemove", handleMouseMove);
|
|
3342
|
+
window.removeEventListener("mouseup", handleMouseUp);
|
|
3343
|
+
if (dragRafRef.current !== null) cancelAnimationFrame(dragRafRef.current);
|
|
3344
|
+
dragRafRef.current = null;
|
|
3345
|
+
};
|
|
3346
|
+
}, [cellSelection.isDragging, cellSelection.updateDrag, cellSelection.endDrag]);
|
|
3347
|
+
const pendingFillCellRef = useRef11(null);
|
|
3348
|
+
const fillRafRef = useRef11(null);
|
|
3349
|
+
useEffect9(() => {
|
|
3350
|
+
if (!cellSelection.isFillDragging) return;
|
|
3351
|
+
function handleMouseMove(event) {
|
|
3352
|
+
const cellEl = event.target?.closest("[data-cell-row]");
|
|
3353
|
+
const rowId = cellEl?.dataset.cellRow;
|
|
3354
|
+
const columnId = cellEl?.dataset.cellCol;
|
|
3355
|
+
if (rowId === void 0 || columnId === void 0) return;
|
|
3356
|
+
pendingFillCellRef.current = { rowId, columnId };
|
|
3357
|
+
if (fillRafRef.current !== null) return;
|
|
3358
|
+
fillRafRef.current = requestAnimationFrame(() => {
|
|
3359
|
+
fillRafRef.current = null;
|
|
3360
|
+
if (pendingFillCellRef.current) cellSelection.updateFillDrag(pendingFillCellRef.current);
|
|
3361
|
+
});
|
|
3362
|
+
}
|
|
3363
|
+
function handleMouseUp() {
|
|
3364
|
+
const result = cellSelection.endFillDrag();
|
|
3365
|
+
if (!result || !cellEditingCtx) return;
|
|
3366
|
+
const changes = computeFillChanges(
|
|
3367
|
+
result.sourceRange,
|
|
3368
|
+
result.finalRange,
|
|
3369
|
+
cellEditingRowIds,
|
|
3370
|
+
cellEditingColumnIds,
|
|
3371
|
+
visibleColumns,
|
|
3372
|
+
findRowById
|
|
3373
|
+
);
|
|
3374
|
+
if (changes.length > 0) applyCellChanges(changes);
|
|
3375
|
+
}
|
|
3376
|
+
window.addEventListener("mousemove", handleMouseMove);
|
|
3377
|
+
window.addEventListener("mouseup", handleMouseUp);
|
|
3378
|
+
return () => {
|
|
3379
|
+
window.removeEventListener("mousemove", handleMouseMove);
|
|
3380
|
+
window.removeEventListener("mouseup", handleMouseUp);
|
|
3381
|
+
if (fillRafRef.current !== null) cancelAnimationFrame(fillRafRef.current);
|
|
3382
|
+
fillRafRef.current = null;
|
|
3383
|
+
};
|
|
3384
|
+
}, [cellSelection.isFillDragging, cellSelection.updateFillDrag, cellSelection.endFillDrag]);
|
|
3385
|
+
const wasEditingCellRef = useRef11(void 0);
|
|
3386
|
+
useEffect9(() => {
|
|
3387
|
+
const wasEditing = wasEditingCellRef.current !== void 0;
|
|
3388
|
+
wasEditingCellRef.current = cellEditingCtx?.editingCell;
|
|
3389
|
+
if (!wasEditing || cellEditingCtx?.editingCell) return;
|
|
3390
|
+
if (document.activeElement === document.body) scrollRef.current?.focus();
|
|
3391
|
+
});
|
|
3392
|
+
function handleFillHandleMouseDown() {
|
|
3393
|
+
cellSelection.startFillDrag();
|
|
3394
|
+
}
|
|
3395
|
+
function handleCellMouseDown(event) {
|
|
3396
|
+
if (!hasCellEditing) return;
|
|
3397
|
+
const cellEl = event.target.closest("[data-cell-row]");
|
|
3398
|
+
const rowId = cellEl?.dataset.cellRow;
|
|
3399
|
+
const columnId = cellEl?.dataset.cellCol;
|
|
3400
|
+
if (rowId === void 0 || columnId === void 0) return;
|
|
3401
|
+
cellSelection.startSelection({ rowId, columnId }, { extend: event.shiftKey });
|
|
3402
|
+
scrollRef.current?.focus();
|
|
3403
|
+
}
|
|
3404
|
+
const CELL_NAV_KEYS = {
|
|
3405
|
+
ArrowUp: "up",
|
|
3406
|
+
ArrowDown: "down",
|
|
3407
|
+
ArrowLeft: "left",
|
|
3408
|
+
ArrowRight: "right"
|
|
3409
|
+
};
|
|
3410
|
+
function resolveSelectedCell() {
|
|
3411
|
+
const focus = cellSelection.selection?.focus;
|
|
3412
|
+
if (!focus) return void 0;
|
|
3413
|
+
const column = columnById.get(focus.columnId);
|
|
3414
|
+
const row = findRowById(focus.rowId);
|
|
3415
|
+
if (!column || row === void 0) return void 0;
|
|
3416
|
+
return { column, row };
|
|
3417
|
+
}
|
|
3418
|
+
function handleCellKeyDown(event) {
|
|
3419
|
+
if (!hasCellEditing || !cellEditingCtx) return;
|
|
3420
|
+
if (cellEditingCtx.editingCell && !cellEditingCtx.consumeJustCommitted()) return;
|
|
3421
|
+
const direction = CELL_NAV_KEYS[event.key];
|
|
3422
|
+
if (direction) {
|
|
3423
|
+
event.preventDefault();
|
|
3424
|
+
cellSelection.moveSelection(direction, { extend: event.shiftKey });
|
|
3425
|
+
return;
|
|
3426
|
+
}
|
|
3427
|
+
if (event.key === "Tab") {
|
|
3428
|
+
event.preventDefault();
|
|
3429
|
+
cellSelection.moveSelection(event.shiftKey ? "left" : "right");
|
|
3430
|
+
return;
|
|
3431
|
+
}
|
|
3432
|
+
if (event.key === "Enter") {
|
|
3433
|
+
event.preventDefault();
|
|
3434
|
+
cellSelection.moveSelection("down");
|
|
3435
|
+
return;
|
|
3436
|
+
}
|
|
3437
|
+
const isPrintableChar = event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey;
|
|
3438
|
+
if (event.key !== "F2" && !isPrintableChar) return;
|
|
3439
|
+
const target = resolveSelectedCell();
|
|
3440
|
+
if (!target || !isEditable(target.column, target.row)) return;
|
|
3441
|
+
event.preventDefault();
|
|
3442
|
+
cellEditingCtx.onBeginEdit(
|
|
3443
|
+
{ rowId: cellEditingCtx.getRowId(target.row), columnId: target.column.id },
|
|
3444
|
+
isPrintableChar ? event.key : void 0
|
|
3445
|
+
);
|
|
3446
|
+
}
|
|
3447
|
+
function handleCellDoubleClick(event) {
|
|
3448
|
+
if (!hasCellEditing || !cellEditingCtx) return;
|
|
3449
|
+
const cellEl = event.target.closest("[data-cell-row]");
|
|
3450
|
+
const rowId = cellEl?.dataset.cellRow;
|
|
3451
|
+
const columnId = cellEl?.dataset.cellCol;
|
|
3452
|
+
if (rowId === void 0 || columnId === void 0) return;
|
|
3453
|
+
const column = columnById.get(columnId);
|
|
3454
|
+
const row = findRowById(rowId);
|
|
3455
|
+
if (!column || row === void 0 || !isEditable(column, row)) return;
|
|
3456
|
+
cellEditingCtx.onBeginEdit({ rowId, columnId });
|
|
3457
|
+
}
|
|
3458
|
+
function handleCopy(event) {
|
|
3459
|
+
if (!hasCellEditing || !cellSelection.selection || cellEditingCtx?.editingCell) return;
|
|
3460
|
+
event.preventDefault();
|
|
3461
|
+
const text = rangeToTsv(cellSelection.selection, rows, cellEditingRowIds, visibleColumns, cellEditingColumnIds, getRowId);
|
|
3462
|
+
event.clipboardData.setData("text/plain", text);
|
|
3463
|
+
}
|
|
3464
|
+
function buildCellChange(row, col, raw) {
|
|
3465
|
+
const rowId = cellEditingRowIds[row];
|
|
3466
|
+
const columnId = cellEditingColumnIds[col];
|
|
3467
|
+
if (rowId === void 0 || columnId === void 0) return void 0;
|
|
3468
|
+
const column = columnById.get(columnId);
|
|
3469
|
+
const rowData = findRowById(rowId);
|
|
3470
|
+
if (!column || rowData === void 0 || !isEditable(column, rowData)) return void 0;
|
|
3471
|
+
const coerced = coerceValueForColumn(column, raw);
|
|
3472
|
+
if (!coerced) return void 0;
|
|
3473
|
+
return { rowId, row: rowData, columnId, previousValue: getColumnValue(column, rowData), value: coerced.value };
|
|
3474
|
+
}
|
|
3475
|
+
function handlePaste(event) {
|
|
3476
|
+
if (!hasCellEditing || !cellSelection.selection || cellEditingCtx?.editingCell) return;
|
|
3477
|
+
const text = event.clipboardData.getData("text/plain");
|
|
3478
|
+
event.preventDefault();
|
|
3479
|
+
const normalized = normalizeRange(cellSelection.selection, cellEditingRowIndex, buildIndexMap(cellEditingColumnIds));
|
|
3480
|
+
if (!normalized) return;
|
|
3481
|
+
const parsed = parseTsv(text);
|
|
3482
|
+
const changes = [];
|
|
3483
|
+
if (parsed.length === 1 && parsed[0]?.length === 1) {
|
|
3484
|
+
const raw = parsed[0][0];
|
|
3485
|
+
for (let r = normalized.rowStart; r <= normalized.rowEnd; r++) {
|
|
3486
|
+
for (let c = normalized.colStart; c <= normalized.colEnd; c++) {
|
|
3487
|
+
const change = buildCellChange(r, c, raw);
|
|
3488
|
+
if (change) changes.push(change);
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
} else {
|
|
3492
|
+
for (let ri = 0; ri < parsed.length; ri++) {
|
|
3493
|
+
const line = parsed[ri];
|
|
3494
|
+
for (let ci = 0; ci < line.length; ci++) {
|
|
3495
|
+
const change = buildCellChange(normalized.rowStart + ri, normalized.colStart + ci, line[ci]);
|
|
3496
|
+
if (change) changes.push(change);
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
if (changes.length > 0) applyCellChanges(changes);
|
|
3501
|
+
}
|
|
3502
|
+
function renderRow(row, measureRef, dataIndex = row.index) {
|
|
2582
3503
|
const isExpanded = expandedIds.has(row.id);
|
|
2583
3504
|
const rowProps = getRowProps?.(row.original);
|
|
2584
3505
|
const rowTestId = getRowTestId?.(row.original) ?? `row-${row.id}`;
|
|
2585
3506
|
const isOddRow = zebra && row.index % 2 === 1;
|
|
2586
|
-
return /* @__PURE__ */
|
|
2587
|
-
/* @__PURE__ */
|
|
3507
|
+
return /* @__PURE__ */ jsxs20("tbody", { ref: measureRef, "data-index": dataIndex, children: [
|
|
3508
|
+
/* @__PURE__ */ jsxs20(
|
|
2588
3509
|
"tr",
|
|
2589
3510
|
{
|
|
2590
3511
|
...rowProps,
|
|
2591
3512
|
"data-testid": rowTestId,
|
|
2592
3513
|
className: rowProps?.className ? cn("divide-x divide-border", rowProps.className, isOddRow && "bg-foreground/5") : `divide-x divide-border${isOddRow ? " bg-foreground/5" : ""}`,
|
|
2593
3514
|
children: [
|
|
2594
|
-
showDetailColumn && /* @__PURE__ */
|
|
3515
|
+
showDetailColumn && /* @__PURE__ */ jsx32(
|
|
2595
3516
|
"td",
|
|
2596
3517
|
{
|
|
2597
3518
|
className: isOddRow ? detailCellProps.classNameOdd : detailCellProps.className,
|
|
2598
3519
|
style: detailCellProps.style,
|
|
2599
|
-
children: /* @__PURE__ */
|
|
3520
|
+
children: /* @__PURE__ */ jsx32(
|
|
2600
3521
|
Button,
|
|
2601
3522
|
{
|
|
2602
3523
|
type: "button",
|
|
@@ -2608,7 +3529,7 @@ function DataGrid({
|
|
|
2608
3529
|
stopRowClick(event);
|
|
2609
3530
|
toggleRowExpanded(row.id);
|
|
2610
3531
|
},
|
|
2611
|
-
children: /* @__PURE__ */
|
|
3532
|
+
children: /* @__PURE__ */ jsx32(
|
|
2612
3533
|
ChevronRight,
|
|
2613
3534
|
{
|
|
2614
3535
|
className: `h-4 w-4 transition-transform${isExpanded ? " rotate-90" : ""}`,
|
|
@@ -2619,12 +3540,12 @@ function DataGrid({
|
|
|
2619
3540
|
)
|
|
2620
3541
|
}
|
|
2621
3542
|
),
|
|
2622
|
-
showSelectionColumn && /* @__PURE__ */
|
|
3543
|
+
showSelectionColumn && /* @__PURE__ */ jsx32(
|
|
2623
3544
|
"td",
|
|
2624
3545
|
{
|
|
2625
3546
|
className: isOddRow ? selectionCellProps.classNameOdd : selectionCellProps.className,
|
|
2626
3547
|
style: selectionCellProps.style,
|
|
2627
|
-
children: /* @__PURE__ */
|
|
3548
|
+
children: /* @__PURE__ */ jsx32(
|
|
2628
3549
|
"input",
|
|
2629
3550
|
{
|
|
2630
3551
|
type: "checkbox",
|
|
@@ -2638,59 +3559,74 @@ function DataGrid({
|
|
|
2638
3559
|
),
|
|
2639
3560
|
row.getAllCells().map((cell) => {
|
|
2640
3561
|
const cellProps = bodyCellPropsByColumn.get(cell.column.id);
|
|
2641
|
-
return /* @__PURE__ */
|
|
3562
|
+
return /* @__PURE__ */ jsx32(
|
|
3563
|
+
"td",
|
|
3564
|
+
{
|
|
3565
|
+
style: cellProps?.style,
|
|
3566
|
+
className: cellProps?.className ?? BODY_TD_BASE_CLASS,
|
|
3567
|
+
"data-cell-row": hasCellEditing ? row.id : void 0,
|
|
3568
|
+
"data-cell-col": hasCellEditing ? cell.column.id : void 0,
|
|
3569
|
+
children: flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
3570
|
+
},
|
|
3571
|
+
cell.id
|
|
3572
|
+
);
|
|
2642
3573
|
}),
|
|
2643
|
-
showRowActionsColumn && rowActions && /* @__PURE__ */
|
|
3574
|
+
showRowActionsColumn && rowActions && /* @__PURE__ */ jsx32(
|
|
2644
3575
|
"td",
|
|
2645
3576
|
{
|
|
2646
3577
|
className: isOddRow ? rowActionsCellProps.classNameOdd : rowActionsCellProps.className,
|
|
2647
3578
|
style: rowActionsCellProps.style,
|
|
2648
|
-
children: /* @__PURE__ */
|
|
3579
|
+
children: /* @__PURE__ */ jsx32(ActionsMenu, { items: rowActions, ctx: { row: row.original }, triggerLabel: `Row actions for ${row.id}` })
|
|
2649
3580
|
}
|
|
2650
3581
|
)
|
|
2651
3582
|
]
|
|
2652
3583
|
}
|
|
2653
3584
|
),
|
|
2654
|
-
showDetailColumn && isExpanded && renderDetail && /* @__PURE__ */
|
|
3585
|
+
showDetailColumn && isExpanded && renderDetail && /* @__PURE__ */ jsx32("tr", { "data-testid": `${rowTestId}-detail`, children: /* @__PURE__ */ jsx32("td", { colSpan: totalColumnCount, className: "border-b border-border p-2", children: renderDetail(row.original) }) })
|
|
2655
3586
|
] }, row.id);
|
|
2656
3587
|
}
|
|
3588
|
+
function renderGroupHeaderTbody(key, rows2, measureRef, dataIndex) {
|
|
3589
|
+
const expanded = isGroupExpanded(key);
|
|
3590
|
+
return /* @__PURE__ */ jsx32("tbody", { ref: measureRef, "data-index": dataIndex, children: /* @__PURE__ */ jsx32("tr", { "data-testid": `group-header-${key}`, children: /* @__PURE__ */ jsx32(
|
|
3591
|
+
"td",
|
|
3592
|
+
{
|
|
3593
|
+
colSpan: totalColumnCount,
|
|
3594
|
+
className: cn("sticky z-20 border-b p-2 font-medium", zebra ? "bg-muted" : "bg-background"),
|
|
3595
|
+
style: { top: groupHeaderTop },
|
|
3596
|
+
children: /* @__PURE__ */ jsxs20(
|
|
3597
|
+
"button",
|
|
3598
|
+
{
|
|
3599
|
+
type: "button",
|
|
3600
|
+
className: "flex w-full items-center gap-1 text-left",
|
|
3601
|
+
onClick: () => toggleGroupExpanded(key),
|
|
3602
|
+
"aria-expanded": expanded,
|
|
3603
|
+
children: [
|
|
3604
|
+
/* @__PURE__ */ jsx32(
|
|
3605
|
+
ChevronRight,
|
|
3606
|
+
{
|
|
3607
|
+
className: `h-4 w-4 shrink-0 transition-transform${expanded ? " rotate-90" : ""}`,
|
|
3608
|
+
"aria-hidden": true
|
|
3609
|
+
}
|
|
3610
|
+
),
|
|
3611
|
+
renderGroupHeader ? renderGroupHeader(key, rows2, expanded) : `${key} (${rows2.length})`
|
|
3612
|
+
]
|
|
3613
|
+
}
|
|
3614
|
+
)
|
|
3615
|
+
}
|
|
3616
|
+
) }) }, `group-${key}`);
|
|
3617
|
+
}
|
|
2657
3618
|
function renderGroupedBucket(bucket) {
|
|
2658
3619
|
const expanded = isGroupExpanded(bucket.key);
|
|
2659
3620
|
const originalRows = bucket.items.map((row) => row.original);
|
|
2660
|
-
return /* @__PURE__ */
|
|
2661
|
-
|
|
2662
|
-
"td",
|
|
2663
|
-
{
|
|
2664
|
-
colSpan: totalColumnCount,
|
|
2665
|
-
className: cn(
|
|
2666
|
-
"sticky z-20 border-b p-2 font-medium",
|
|
2667
|
-
zebra ? "bg-muted" : "bg-background"
|
|
2668
|
-
),
|
|
2669
|
-
style: { top: groupHeaderTop },
|
|
2670
|
-
children: /* @__PURE__ */ jsxs18(
|
|
2671
|
-
"button",
|
|
2672
|
-
{
|
|
2673
|
-
type: "button",
|
|
2674
|
-
className: "flex w-full items-center gap-1 text-left",
|
|
2675
|
-
onClick: () => toggleGroupExpanded(bucket.key),
|
|
2676
|
-
"aria-expanded": expanded,
|
|
2677
|
-
children: [
|
|
2678
|
-
/* @__PURE__ */ jsx28(
|
|
2679
|
-
ChevronRight,
|
|
2680
|
-
{
|
|
2681
|
-
className: `h-4 w-4 shrink-0 transition-transform${expanded ? " rotate-90" : ""}`,
|
|
2682
|
-
"aria-hidden": true
|
|
2683
|
-
}
|
|
2684
|
-
),
|
|
2685
|
-
renderGroupHeader ? renderGroupHeader(bucket.key, originalRows, expanded) : `${bucket.key} (${originalRows.length})`
|
|
2686
|
-
]
|
|
2687
|
-
}
|
|
2688
|
-
)
|
|
2689
|
-
}
|
|
2690
|
-
) }) }),
|
|
3621
|
+
return /* @__PURE__ */ jsxs20(Fragment3, { children: [
|
|
3622
|
+
renderGroupHeaderTbody(bucket.key, originalRows),
|
|
2691
3623
|
expanded && bucket.items.map((row) => renderRow(row))
|
|
2692
3624
|
] }, `group-${bucket.key}`);
|
|
2693
3625
|
}
|
|
3626
|
+
function renderFlatItem(item, index, measureRef) {
|
|
3627
|
+
if (item.kind === "header") return renderGroupHeaderTbody(item.key, item.rows, measureRef, index);
|
|
3628
|
+
return renderRow(item.row, measureRef, index);
|
|
3629
|
+
}
|
|
2694
3630
|
return (
|
|
2695
3631
|
// `h-full`/`min-h-0` here and on the two wrappers below are no-ops unless
|
|
2696
3632
|
// a consumer itself gives <DataGrid> a bounded height (e.g. wraps it in
|
|
@@ -2704,8 +3640,8 @@ function DataGrid({
|
|
|
2704
3640
|
// containers (that one scrolling vertically, this component's own
|
|
2705
3641
|
// `overflow-x-auto` div scrolling horizontally) fighting over which one
|
|
2706
3642
|
// a sticky header/pinned column is actually stuck relative to.
|
|
2707
|
-
/* @__PURE__ */
|
|
2708
|
-
editing && pendingEdits.size > 0 && /* @__PURE__ */
|
|
3643
|
+
/* @__PURE__ */ jsxs20("div", { className: "flex h-full min-h-0 flex-col gap-2", "data-testid": "datagrid", children: [
|
|
3644
|
+
editing && pendingEdits.size > 0 && /* @__PURE__ */ jsx32(
|
|
2709
3645
|
EditingBar,
|
|
2710
3646
|
{
|
|
2711
3647
|
editing,
|
|
@@ -2715,213 +3651,234 @@ function DataGrid({
|
|
|
2715
3651
|
onDiscard: handleDiscardEdits
|
|
2716
3652
|
}
|
|
2717
3653
|
),
|
|
2718
|
-
showSelectionColumn && headerActions && /* @__PURE__ */
|
|
2719
|
-
/* @__PURE__ */
|
|
3654
|
+
showSelectionColumn && headerActions && /* @__PURE__ */ jsxs20("div", { className: "flex shrink-0 items-center justify-between text-sm", children: [
|
|
3655
|
+
/* @__PURE__ */ jsxs20("span", { children: [
|
|
2720
3656
|
selectedRows.length,
|
|
2721
3657
|
" selected"
|
|
2722
3658
|
] }),
|
|
2723
|
-
/* @__PURE__ */
|
|
3659
|
+
/* @__PURE__ */ jsx32(ActionsMenu, { items: headerActions, ctx: { selectedRows }, triggerLabel: "Bulk actions" })
|
|
2724
3660
|
] }),
|
|
2725
|
-
/* @__PURE__ */
|
|
2726
|
-
loading && /* @__PURE__ */
|
|
3661
|
+
/* @__PURE__ */ jsxs20("div", { className: "relative min-h-0 flex-1", children: [
|
|
3662
|
+
loading && /* @__PURE__ */ jsx32(
|
|
2727
3663
|
"div",
|
|
2728
3664
|
{
|
|
2729
3665
|
"data-testid": "datagrid-loading-overlay",
|
|
2730
3666
|
className: "absolute inset-0 z-30 flex items-center justify-center bg-background/60",
|
|
2731
|
-
children: /* @__PURE__ */
|
|
3667
|
+
children: /* @__PURE__ */ jsx32(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground", "aria-hidden": true })
|
|
2732
3668
|
}
|
|
2733
3669
|
),
|
|
2734
|
-
/* @__PURE__ */
|
|
3670
|
+
/* @__PURE__ */ jsxs20(
|
|
2735
3671
|
"div",
|
|
2736
3672
|
{
|
|
2737
3673
|
ref: scrollRef,
|
|
2738
3674
|
"data-testid": testId,
|
|
2739
|
-
className: "h-full overflow-auto rounded-md border",
|
|
3675
|
+
className: cn("h-full overflow-auto rounded-md border", hasCellEditing && "relative"),
|
|
2740
3676
|
style: shouldVirtualize ? { maxHeight: virtualize?.maxBodyHeight ?? 480 } : void 0,
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
const
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
3677
|
+
tabIndex: hasCellEditing ? 0 : void 0,
|
|
3678
|
+
onMouseDown: hasCellEditing ? handleCellMouseDown : void 0,
|
|
3679
|
+
onDoubleClick: hasCellEditing ? handleCellDoubleClick : void 0,
|
|
3680
|
+
onKeyDown: hasCellEditing ? handleCellKeyDown : void 0,
|
|
3681
|
+
onCopy: hasCellEditing ? handleCopy : void 0,
|
|
3682
|
+
onPaste: hasCellEditing ? handlePaste : void 0,
|
|
3683
|
+
children: [
|
|
3684
|
+
/* @__PURE__ */ jsxs20("table", { className: cn("w-full border-collapse text-sm", enableColumnResizing && "table-fixed"), children: [
|
|
3685
|
+
/* @__PURE__ */ jsxs20("thead", { ref: theadRef, className: "bg-muted", children: [
|
|
3686
|
+
table.getHeaderGroups().map((headerGroup) => {
|
|
3687
|
+
const headerById = new Map(headerGroup.headers.map((header) => [header.id, header]));
|
|
3688
|
+
function renderLeafHeaderCell(column, rowSpan) {
|
|
3689
|
+
const header = headerById.get(column.id);
|
|
3690
|
+
if (!header) return null;
|
|
3691
|
+
const sortEntry = state.sort.find((s) => s.field === column.id);
|
|
3692
|
+
const sortable = isSortable(column);
|
|
3693
|
+
const filterable = isFilterable(column) && column.filterDisplay !== "row";
|
|
3694
|
+
const cellProps = leafHeaderCellPropsByColumn.get(column.id);
|
|
3695
|
+
const headerContent = column.renderHeader ? column.renderHeader(column) : column.header;
|
|
3696
|
+
return /* @__PURE__ */ jsxs20("th", { rowSpan, style: cellProps?.style, className: cellProps?.className, children: [
|
|
3697
|
+
/* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-1", children: [
|
|
3698
|
+
sortable ? /* @__PURE__ */ jsxs20(
|
|
3699
|
+
"button",
|
|
3700
|
+
{
|
|
3701
|
+
type: "button",
|
|
3702
|
+
className: "flex items-center gap-1 rounded-sm px-1 hover:bg-accent hover:text-accent-foreground",
|
|
3703
|
+
onClick: (event) => toggleSort(column.id, enableMultiSort && event.shiftKey, column.sortDescFirst === true),
|
|
3704
|
+
children: [
|
|
3705
|
+
headerContent,
|
|
3706
|
+
sortEntry?.dir === "asc" && /* @__PURE__ */ jsx32(BarsArrowUpIcon, { className: "h-3 w-3", "aria-hidden": true }),
|
|
3707
|
+
sortEntry?.dir === "desc" && /* @__PURE__ */ jsx32(BarsArrowDownIcon, { className: "h-3 w-3", "aria-hidden": true })
|
|
3708
|
+
]
|
|
3709
|
+
}
|
|
3710
|
+
) : (
|
|
3711
|
+
// Not wrapped in a `<button>` (unlike the sortable branch above): a
|
|
3712
|
+
// disabled `<button>` swallows pointer events for its entire subtree,
|
|
3713
|
+
// silently breaking any interactive content a column's own `renderHeader`
|
|
3714
|
+
// embeds directly (bare filter inputs, "select/reject all" buttons) —
|
|
3715
|
+
// clicks never reach them at all, even though nothing looks wrong visually.
|
|
3716
|
+
/* @__PURE__ */ jsx32("div", { className: "flex items-center gap-1 px-1", children: headerContent })
|
|
3717
|
+
),
|
|
3718
|
+
filterable && /* @__PURE__ */ jsxs20(Popover, { children: [
|
|
3719
|
+
/* @__PURE__ */ jsx32(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx32(Button, { variant: "ghost", size: "icon", "aria-label": `Filter ${column.header}`, children: /* @__PURE__ */ jsx32(
|
|
3720
|
+
FunnelIcon7,
|
|
3721
|
+
{
|
|
3722
|
+
className: cn(
|
|
3723
|
+
"h-3 w-3",
|
|
3724
|
+
filtersByColumn.get(column.id) !== void 0 ? "opacity-100 text-primary" : "opacity-40"
|
|
3725
|
+
),
|
|
3726
|
+
"aria-hidden": true
|
|
3727
|
+
}
|
|
3728
|
+
) }) }),
|
|
3729
|
+
/* @__PURE__ */ jsx32(PopoverContent, { children: renderFilterWidget(column) })
|
|
3730
|
+
] })
|
|
3731
|
+
] }),
|
|
3732
|
+
enableColumnResizing && /* @__PURE__ */ jsx32(
|
|
3733
|
+
"div",
|
|
2757
3734
|
{
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
3735
|
+
"data-testid": `resize-handle-${column.id}`,
|
|
3736
|
+
onMouseDown: header.getResizeHandler(),
|
|
3737
|
+
onTouchStart: header.getResizeHandler(),
|
|
3738
|
+
className: "absolute right-0 top-0 h-full w-1 cursor-col-resize touch-none select-none hover:bg-accent"
|
|
3739
|
+
}
|
|
3740
|
+
)
|
|
3741
|
+
] }, header.id);
|
|
3742
|
+
}
|
|
3743
|
+
return /* @__PURE__ */ jsxs20(Fragment3, { children: [
|
|
3744
|
+
hasHeaderGroups && /* @__PURE__ */ jsxs20("tr", { "data-testid": "header-group-row", className: "divide-x divide-border", children: [
|
|
3745
|
+
showDetailColumn && /* @__PURE__ */ jsx32(
|
|
3746
|
+
"th",
|
|
3747
|
+
{
|
|
3748
|
+
rowSpan: 2,
|
|
3749
|
+
className: detailHeaderCellProps.className,
|
|
3750
|
+
style: detailHeaderCellProps.style,
|
|
3751
|
+
"aria-hidden": true
|
|
2766
3752
|
}
|
|
2767
|
-
) : (
|
|
2768
|
-
// Not wrapped in a `<button>` (unlike the sortable branch above): a
|
|
2769
|
-
// disabled `<button>` swallows pointer events for its entire subtree,
|
|
2770
|
-
// silently breaking any interactive content a column's own `renderHeader`
|
|
2771
|
-
// embeds directly (bare filter inputs, "select/reject all" buttons) —
|
|
2772
|
-
// clicks never reach them at all, even though nothing looks wrong visually.
|
|
2773
|
-
/* @__PURE__ */ jsx28("div", { className: "flex items-center gap-1 px-1", children: headerContent })
|
|
2774
3753
|
),
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
className: "absolute right-0 top-0 h-full w-1 cursor-col-resize touch-none select-none hover:bg-accent"
|
|
2796
|
-
}
|
|
2797
|
-
)
|
|
2798
|
-
] }, header.id);
|
|
2799
|
-
}
|
|
2800
|
-
return /* @__PURE__ */ jsxs18(Fragment2, { children: [
|
|
2801
|
-
hasHeaderGroups && /* @__PURE__ */ jsxs18("tr", { "data-testid": "header-group-row", className: "divide-x divide-border", children: [
|
|
2802
|
-
showDetailColumn && /* @__PURE__ */ jsx28(
|
|
2803
|
-
"th",
|
|
2804
|
-
{
|
|
2805
|
-
rowSpan: 2,
|
|
2806
|
-
className: detailHeaderCellProps.className,
|
|
2807
|
-
style: detailHeaderCellProps.style,
|
|
2808
|
-
"aria-hidden": true
|
|
2809
|
-
}
|
|
2810
|
-
),
|
|
2811
|
-
showSelectionColumn && /* @__PURE__ */ jsx28(
|
|
2812
|
-
"th",
|
|
2813
|
-
{
|
|
2814
|
-
rowSpan: 2,
|
|
2815
|
-
className: selectionHeaderCellProps.className,
|
|
2816
|
-
style: selectionHeaderCellProps.style,
|
|
2817
|
-
children: /* @__PURE__ */ jsx28(
|
|
2818
|
-
"input",
|
|
3754
|
+
showSelectionColumn && /* @__PURE__ */ jsx32(
|
|
3755
|
+
"th",
|
|
3756
|
+
{
|
|
3757
|
+
rowSpan: 2,
|
|
3758
|
+
className: selectionHeaderCellProps.className,
|
|
3759
|
+
style: selectionHeaderCellProps.style,
|
|
3760
|
+
children: /* @__PURE__ */ jsx32(
|
|
3761
|
+
"input",
|
|
3762
|
+
{
|
|
3763
|
+
type: "checkbox",
|
|
3764
|
+
"aria-label": "Select all rows on this page",
|
|
3765
|
+
checked: allOnPageSelected,
|
|
3766
|
+
onChange: toggleAllOnPageSelected
|
|
3767
|
+
}
|
|
3768
|
+
)
|
|
3769
|
+
}
|
|
3770
|
+
),
|
|
3771
|
+
headerRuns.map(
|
|
3772
|
+
(run) => run.kind === "single" ? renderLeafHeaderCell(run.column, 2) : /* @__PURE__ */ jsx32(
|
|
3773
|
+
"th",
|
|
2819
3774
|
{
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
}
|
|
3775
|
+
colSpan: run.columns.length,
|
|
3776
|
+
className: "border-b border-border p-2 text-center font-medium",
|
|
3777
|
+
children: run.label
|
|
3778
|
+
},
|
|
3779
|
+
`group-${run.label}-${run.columns[0].id}`
|
|
2825
3780
|
)
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
headerRuns.map(
|
|
2829
|
-
(run) => run.kind === "single" ? renderLeafHeaderCell(run.column, 2) : /* @__PURE__ */ jsx28(
|
|
3781
|
+
),
|
|
3782
|
+
showRowActionsColumn && /* @__PURE__ */ jsx32(
|
|
2830
3783
|
"th",
|
|
2831
3784
|
{
|
|
2832
|
-
|
|
2833
|
-
className:
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
3785
|
+
rowSpan: 2,
|
|
3786
|
+
className: rowActionsHeaderCellProps.className,
|
|
3787
|
+
style: rowActionsHeaderCellProps.style,
|
|
3788
|
+
"aria-label": "Row actions"
|
|
3789
|
+
}
|
|
2837
3790
|
)
|
|
2838
|
-
),
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
!hasHeaderGroups && showRowActionsColumn && /* @__PURE__ */ jsx28(
|
|
2880
|
-
"th",
|
|
2881
|
-
{
|
|
2882
|
-
className: rowActionsHeaderCellProps.className,
|
|
2883
|
-
style: rowActionsHeaderCellProps.style,
|
|
2884
|
-
"aria-label": "Row actions"
|
|
2885
|
-
}
|
|
2886
|
-
)
|
|
2887
|
-
] }, headerGroup.id)
|
|
2888
|
-
] }, headerGroup.id);
|
|
2889
|
-
}),
|
|
2890
|
-
hasFilterRow && /* @__PURE__ */ jsxs18("tr", { "data-testid": "filter-row", className: "divide-x divide-border", children: [
|
|
2891
|
-
showDetailColumn && /* @__PURE__ */ jsx28("th", { className: detailHeaderCellProps.className, style: detailHeaderCellProps.style }),
|
|
2892
|
-
showSelectionColumn && /* @__PURE__ */ jsx28(
|
|
2893
|
-
"th",
|
|
2894
|
-
{
|
|
2895
|
-
className: selectionHeaderCellProps.className,
|
|
2896
|
-
style: selectionHeaderCellProps.style
|
|
2897
|
-
}
|
|
2898
|
-
),
|
|
2899
|
-
visibleColumns.map((column) => {
|
|
2900
|
-
const cellProps = filterHeaderCellPropsByColumn.get(column.id);
|
|
2901
|
-
return /* @__PURE__ */ jsx28("th", { style: cellProps?.style, className: cellProps?.className, children: isFilterable(column) && column.filterDisplay === "row" ? renderFilterWidget(column) : null }, column.id);
|
|
3791
|
+
] }, `${headerGroup.id}-groups`),
|
|
3792
|
+
/* @__PURE__ */ jsxs20("tr", { className: "divide-x divide-border", children: [
|
|
3793
|
+
!hasHeaderGroups && showDetailColumn && /* @__PURE__ */ jsx32(
|
|
3794
|
+
"th",
|
|
3795
|
+
{
|
|
3796
|
+
className: detailHeaderCellProps.className,
|
|
3797
|
+
style: detailHeaderCellProps.style,
|
|
3798
|
+
"aria-hidden": true
|
|
3799
|
+
}
|
|
3800
|
+
),
|
|
3801
|
+
!hasHeaderGroups && showSelectionColumn && /* @__PURE__ */ jsx32(
|
|
3802
|
+
"th",
|
|
3803
|
+
{
|
|
3804
|
+
className: selectionHeaderCellProps.className,
|
|
3805
|
+
style: selectionHeaderCellProps.style,
|
|
3806
|
+
children: /* @__PURE__ */ jsx32(
|
|
3807
|
+
"input",
|
|
3808
|
+
{
|
|
3809
|
+
type: "checkbox",
|
|
3810
|
+
"aria-label": "Select all rows on this page",
|
|
3811
|
+
checked: allOnPageSelected,
|
|
3812
|
+
onChange: toggleAllOnPageSelected
|
|
3813
|
+
}
|
|
3814
|
+
)
|
|
3815
|
+
}
|
|
3816
|
+
),
|
|
3817
|
+
headerGroup.headers.map((header) => {
|
|
3818
|
+
const column = columnById.get(header.id);
|
|
3819
|
+
if (!column || hasHeaderGroups && !column.headerGroup) return null;
|
|
3820
|
+
return renderLeafHeaderCell(column);
|
|
3821
|
+
}),
|
|
3822
|
+
!hasHeaderGroups && showRowActionsColumn && /* @__PURE__ */ jsx32(
|
|
3823
|
+
"th",
|
|
3824
|
+
{
|
|
3825
|
+
className: rowActionsHeaderCellProps.className,
|
|
3826
|
+
style: rowActionsHeaderCellProps.style,
|
|
3827
|
+
"aria-label": "Row actions"
|
|
3828
|
+
}
|
|
3829
|
+
)
|
|
3830
|
+
] }, headerGroup.id)
|
|
3831
|
+
] }, headerGroup.id);
|
|
2902
3832
|
}),
|
|
2903
|
-
|
|
2904
|
-
"th",
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
3833
|
+
hasFilterRow && /* @__PURE__ */ jsxs20("tr", { "data-testid": "filter-row", className: "divide-x divide-border", children: [
|
|
3834
|
+
showDetailColumn && /* @__PURE__ */ jsx32("th", { className: detailHeaderCellProps.className, style: detailHeaderCellProps.style }),
|
|
3835
|
+
showSelectionColumn && /* @__PURE__ */ jsx32(
|
|
3836
|
+
"th",
|
|
3837
|
+
{
|
|
3838
|
+
className: selectionHeaderCellProps.className,
|
|
3839
|
+
style: selectionHeaderCellProps.style
|
|
3840
|
+
}
|
|
3841
|
+
),
|
|
3842
|
+
visibleColumns.map((column) => {
|
|
3843
|
+
const cellProps = filterHeaderCellPropsByColumn.get(column.id);
|
|
3844
|
+
return /* @__PURE__ */ jsx32("th", { style: cellProps?.style, className: cellProps?.className, children: isFilterable(column) && column.filterDisplay === "row" ? renderFilterWidget(column) : null }, column.id);
|
|
3845
|
+
}),
|
|
3846
|
+
showRowActionsColumn && /* @__PURE__ */ jsx32(
|
|
3847
|
+
"th",
|
|
3848
|
+
{
|
|
3849
|
+
className: rowActionsHeaderCellProps.className,
|
|
3850
|
+
style: rowActionsHeaderCellProps.style
|
|
3851
|
+
}
|
|
3852
|
+
)
|
|
3853
|
+
] })
|
|
3854
|
+
] }),
|
|
3855
|
+
rows.length === 0 ? /* @__PURE__ */ jsx32("tbody", { children: /* @__PURE__ */ jsx32("tr", { children: /* @__PURE__ */ jsx32("td", { colSpan: totalColumnCount, className: "p-4 text-center text-muted-foreground", children: loading ? "Loading..." : "No results." }) }) }) : shouldVirtualize ? /* @__PURE__ */ jsxs20(Fragment4, { children: [
|
|
3856
|
+
paddingTop > 0 && /* @__PURE__ */ jsx32("tbody", { children: /* @__PURE__ */ jsx32("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx32("td", { colSpan: totalColumnCount, style: { padding: 0, border: "none", height: paddingTop } }) }) }),
|
|
3857
|
+
virtualItems.map(
|
|
3858
|
+
(virtualItem) => renderFlatItem(
|
|
3859
|
+
flatItems[virtualItem.index],
|
|
3860
|
+
virtualItem.index,
|
|
3861
|
+
(el) => virtualizer.measureElement(el)
|
|
3862
|
+
)
|
|
3863
|
+
),
|
|
3864
|
+
paddingBottom > 0 && /* @__PURE__ */ jsx32("tbody", { children: /* @__PURE__ */ jsx32("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx32("td", { colSpan: totalColumnCount, style: { padding: 0, border: "none", height: paddingBottom } }) }) })
|
|
3865
|
+
] }) : groupedBuckets ? groupedBuckets.map((bucket) => renderGroupedBucket(bucket)) : tableRows.map((row) => renderRow(row))
|
|
2911
3866
|
] }),
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
3867
|
+
hasCellEditing && /* @__PURE__ */ jsx32(
|
|
3868
|
+
SelectionOverlay,
|
|
3869
|
+
{
|
|
3870
|
+
containerRef: scrollRef,
|
|
3871
|
+
range: cellSelection.effectiveRange,
|
|
3872
|
+
fillPreviewRange: cellSelection.fillPreviewRange,
|
|
3873
|
+
onFillHandleMouseDown: cellSelection.selection && !cellSelection.isDragging && !cellEditingCtx?.editingCell ? handleFillHandleMouseDown : void 0
|
|
3874
|
+
}
|
|
3875
|
+
)
|
|
3876
|
+
]
|
|
2920
3877
|
}
|
|
2921
3878
|
)
|
|
2922
3879
|
] }),
|
|
2923
|
-
showPagination && /* @__PURE__ */
|
|
2924
|
-
/* @__PURE__ */
|
|
3880
|
+
showPagination && /* @__PURE__ */ jsxs20("div", { className: "flex shrink-0 items-center justify-between text-sm", children: [
|
|
3881
|
+
/* @__PURE__ */ jsxs20("span", { children: [
|
|
2925
3882
|
"Page ",
|
|
2926
3883
|
state.page + 1,
|
|
2927
3884
|
" of ",
|
|
@@ -2932,8 +3889,8 @@ function DataGrid({
|
|
|
2932
3889
|
rowCount === 1 ? "" : "s",
|
|
2933
3890
|
")"
|
|
2934
3891
|
] }),
|
|
2935
|
-
/* @__PURE__ */
|
|
2936
|
-
/* @__PURE__ */
|
|
3892
|
+
/* @__PURE__ */ jsxs20("div", { className: "flex gap-2", children: [
|
|
3893
|
+
/* @__PURE__ */ jsx32(
|
|
2937
3894
|
Button,
|
|
2938
3895
|
{
|
|
2939
3896
|
variant: "outline",
|
|
@@ -2943,7 +3900,7 @@ function DataGrid({
|
|
|
2943
3900
|
children: "Previous"
|
|
2944
3901
|
}
|
|
2945
3902
|
),
|
|
2946
|
-
/* @__PURE__ */
|
|
3903
|
+
/* @__PURE__ */ jsx32(
|
|
2947
3904
|
Button,
|
|
2948
3905
|
{
|
|
2949
3906
|
variant: "outline",
|
|
@@ -2962,7 +3919,7 @@ function DataGrid({
|
|
|
2962
3919
|
// src/tree/TreeDataGrid.tsx
|
|
2963
3920
|
import { useVirtualizer as useVirtualizer2 } from "@tanstack/react-virtual";
|
|
2964
3921
|
import { AlertCircle, ChevronDown as ChevronDown2, ChevronRight as ChevronRight2, Loader2 as Loader22 } from "lucide-react";
|
|
2965
|
-
import { Fragment as
|
|
3922
|
+
import { Fragment as Fragment5, useEffect as useEffect10, useMemo as useMemo10, useRef as useRef12 } from "react";
|
|
2966
3923
|
|
|
2967
3924
|
// src/tree/flattenTree.ts
|
|
2968
3925
|
function flattenTree(roots, accessors, expanded, childrenOverride) {
|
|
@@ -3007,7 +3964,7 @@ function computeSelectionStates(roots, accessors, childrenOverride, selectedIds)
|
|
|
3007
3964
|
}
|
|
3008
3965
|
|
|
3009
3966
|
// src/tree/useTreeState.ts
|
|
3010
|
-
import { useCallback as
|
|
3967
|
+
import { useCallback as useCallback5, useMemo as useMemo9, useState as useState14 } from "react";
|
|
3011
3968
|
function useTreeState({
|
|
3012
3969
|
data,
|
|
3013
3970
|
getRowId,
|
|
@@ -3019,9 +3976,9 @@ function useTreeState({
|
|
|
3019
3976
|
childrenMap: controlledChildrenMap,
|
|
3020
3977
|
onChildrenMapChange
|
|
3021
3978
|
}) {
|
|
3022
|
-
const [internalExpanded, setInternalExpanded] =
|
|
3979
|
+
const [internalExpanded, setInternalExpanded] = useState14({});
|
|
3023
3980
|
const expanded = controlledExpanded ?? internalExpanded;
|
|
3024
|
-
const setExpanded =
|
|
3981
|
+
const setExpanded = useCallback5(
|
|
3025
3982
|
(updater) => {
|
|
3026
3983
|
if (controlledExpanded) {
|
|
3027
3984
|
onExpandedChange?.(updater(controlledExpanded));
|
|
@@ -3031,9 +3988,9 @@ function useTreeState({
|
|
|
3031
3988
|
},
|
|
3032
3989
|
[controlledExpanded, onExpandedChange]
|
|
3033
3990
|
);
|
|
3034
|
-
const [internalChildrenMap, setInternalChildrenMap] =
|
|
3991
|
+
const [internalChildrenMap, setInternalChildrenMap] = useState14(/* @__PURE__ */ new Map());
|
|
3035
3992
|
const childrenMap = controlledChildrenMap ?? internalChildrenMap;
|
|
3036
|
-
const setChildrenMap =
|
|
3993
|
+
const setChildrenMap = useCallback5(
|
|
3037
3994
|
(updater) => {
|
|
3038
3995
|
if (controlledChildrenMap) {
|
|
3039
3996
|
onChildrenMapChange?.(updater(controlledChildrenMap));
|
|
@@ -3043,13 +4000,13 @@ function useTreeState({
|
|
|
3043
4000
|
},
|
|
3044
4001
|
[controlledChildrenMap, onChildrenMapChange]
|
|
3045
4002
|
);
|
|
3046
|
-
const [loadingIds, setLoadingIds] =
|
|
3047
|
-
const [errorIds, setErrorIds] =
|
|
3048
|
-
const resolveHasChildren =
|
|
4003
|
+
const [loadingIds, setLoadingIds] = useState14(/* @__PURE__ */ new Set());
|
|
4004
|
+
const [errorIds, setErrorIds] = useState14(/* @__PURE__ */ new Map());
|
|
4005
|
+
const resolveHasChildren = useMemo9(
|
|
3049
4006
|
() => hasChildren ?? ((row) => (getChildren(row)?.length ?? 0) > 0),
|
|
3050
4007
|
[hasChildren, getChildren]
|
|
3051
4008
|
);
|
|
3052
|
-
const loadChildrenFor =
|
|
4009
|
+
const loadChildrenFor = useCallback5(
|
|
3053
4010
|
async (row) => {
|
|
3054
4011
|
const id = getRowId(row);
|
|
3055
4012
|
const cached = childrenMap.get(id);
|
|
@@ -3082,7 +4039,7 @@ function useTreeState({
|
|
|
3082
4039
|
},
|
|
3083
4040
|
[childrenMap, getChildren, getRowId, onLoadChildren, setChildrenMap]
|
|
3084
4041
|
);
|
|
3085
|
-
const toggleExpand =
|
|
4042
|
+
const toggleExpand = useCallback5(
|
|
3086
4043
|
(row) => {
|
|
3087
4044
|
const id = getRowId(row);
|
|
3088
4045
|
const willExpand = !expanded[id];
|
|
@@ -3094,14 +4051,14 @@ function useTreeState({
|
|
|
3094
4051
|
},
|
|
3095
4052
|
[expanded, getRowId, loadChildrenFor, resolveHasChildren, setExpanded]
|
|
3096
4053
|
);
|
|
3097
|
-
const retry =
|
|
4054
|
+
const retry = useCallback5(
|
|
3098
4055
|
(row) => {
|
|
3099
4056
|
loadChildrenFor(row).catch(() => {
|
|
3100
4057
|
});
|
|
3101
4058
|
},
|
|
3102
4059
|
[loadChildrenFor]
|
|
3103
4060
|
);
|
|
3104
|
-
const expandToLevel =
|
|
4061
|
+
const expandToLevel = useCallback5(
|
|
3105
4062
|
async (level) => {
|
|
3106
4063
|
const nextExpanded = { ...expanded };
|
|
3107
4064
|
async function visit(row, depth) {
|
|
@@ -3124,7 +4081,7 @@ function useTreeState({
|
|
|
3124
4081
|
}
|
|
3125
4082
|
|
|
3126
4083
|
// src/tree/TreeDataGrid.tsx
|
|
3127
|
-
import { Fragment as
|
|
4084
|
+
import { Fragment as Fragment6, jsx as jsx33, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
3128
4085
|
var EMPTY_SELECTED_IDS = /* @__PURE__ */ new Set();
|
|
3129
4086
|
var UNCHECKED_STATE = { checked: false, indeterminate: false };
|
|
3130
4087
|
function renderCell(column, row, editingCtx) {
|
|
@@ -3140,8 +4097,8 @@ function TreeCell({
|
|
|
3140
4097
|
onRetry,
|
|
3141
4098
|
editingCtx
|
|
3142
4099
|
}) {
|
|
3143
|
-
return /* @__PURE__ */
|
|
3144
|
-
flatRow.hasChildren ? /* @__PURE__ */
|
|
4100
|
+
return /* @__PURE__ */ jsxs21("div", { className: "flex items-center gap-1", style: { paddingLeft: flatRow.depth * indentSize }, children: [
|
|
4101
|
+
flatRow.hasChildren ? /* @__PURE__ */ jsx33(
|
|
3145
4102
|
"button",
|
|
3146
4103
|
{
|
|
3147
4104
|
type: "button",
|
|
@@ -3153,14 +4110,14 @@ function TreeCell({
|
|
|
3153
4110
|
"aria-label": flatRow.isExpanded ? "Collapse" : "Expand",
|
|
3154
4111
|
"aria-expanded": flatRow.isExpanded,
|
|
3155
4112
|
className: "flex h-4 w-4 shrink-0 items-center justify-center text-muted-foreground hover:text-foreground disabled:opacity-50",
|
|
3156
|
-
children: isLoading ? /* @__PURE__ */
|
|
4113
|
+
children: isLoading ? /* @__PURE__ */ jsx33(Loader22, { className: "h-3.5 w-3.5 animate-spin", "aria-hidden": "true" }) : flatRow.isExpanded ? /* @__PURE__ */ jsx33(ChevronDown2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) : /* @__PURE__ */ jsx33(ChevronRight2, { className: "h-3.5 w-3.5", "aria-hidden": "true" })
|
|
3157
4114
|
}
|
|
3158
|
-
) : /* @__PURE__ */
|
|
3159
|
-
/* @__PURE__ */
|
|
3160
|
-
flatRow.isExpanded && error ? /* @__PURE__ */
|
|
3161
|
-
/* @__PURE__ */
|
|
4115
|
+
) : /* @__PURE__ */ jsx33("span", { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
|
|
4116
|
+
/* @__PURE__ */ jsx33("span", { className: "min-w-0 flex-1 truncate", children: renderCell(column, flatRow.row, editingCtx) }),
|
|
4117
|
+
flatRow.isExpanded && error ? /* @__PURE__ */ jsxs21("span", { className: "ml-1 flex shrink-0 items-center gap-1 text-xs text-destructive", children: [
|
|
4118
|
+
/* @__PURE__ */ jsx33(AlertCircle, { className: "h-3 w-3", "aria-hidden": "true" }),
|
|
3162
4119
|
"Failed to load.",
|
|
3163
|
-
/* @__PURE__ */
|
|
4120
|
+
/* @__PURE__ */ jsx33("button", { type: "button", className: "underline underline-offset-2", onClick: onRetry, children: "Retry" })
|
|
3164
4121
|
] }) : null
|
|
3165
4122
|
] });
|
|
3166
4123
|
}
|
|
@@ -3197,7 +4154,7 @@ function TreeDataGrid({
|
|
|
3197
4154
|
zebra = true,
|
|
3198
4155
|
editing
|
|
3199
4156
|
}) {
|
|
3200
|
-
const accessors =
|
|
4157
|
+
const accessors = useMemo10(
|
|
3201
4158
|
() => ({ getRowId, getChildren, hasChildren }),
|
|
3202
4159
|
[getRowId, getChildren, hasChildren]
|
|
3203
4160
|
);
|
|
@@ -3213,7 +4170,7 @@ function TreeDataGrid({
|
|
|
3213
4170
|
childrenMap: controlledChildrenMap,
|
|
3214
4171
|
onChildrenMapChange
|
|
3215
4172
|
});
|
|
3216
|
-
const didAutoExpand =
|
|
4173
|
+
const didAutoExpand = useRef12(false);
|
|
3217
4174
|
useEffect10(() => {
|
|
3218
4175
|
if (didAutoExpand.current) return;
|
|
3219
4176
|
didAutoExpand.current = true;
|
|
@@ -3225,11 +4182,11 @@ function TreeDataGrid({
|
|
|
3225
4182
|
controlledExpandedGroups,
|
|
3226
4183
|
onExpandedGroupsChange
|
|
3227
4184
|
);
|
|
3228
|
-
const flatRows =
|
|
4185
|
+
const flatRows = useMemo10(
|
|
3229
4186
|
() => groupBy ? [] : flattenTree(data, accessors, expanded, childrenMap),
|
|
3230
4187
|
[groupBy, data, accessors, expanded, childrenMap]
|
|
3231
4188
|
);
|
|
3232
|
-
const groupedBuckets =
|
|
4189
|
+
const groupedBuckets = useMemo10(
|
|
3233
4190
|
() => groupBy ? groupRows(data, groupBy).map((bucket) => ({
|
|
3234
4191
|
key: bucket.key,
|
|
3235
4192
|
roots: bucket.items,
|
|
@@ -3237,7 +4194,7 @@ function TreeDataGrid({
|
|
|
3237
4194
|
})) : void 0,
|
|
3238
4195
|
[groupBy, data, accessors, expanded, childrenMap]
|
|
3239
4196
|
);
|
|
3240
|
-
const groupedRender =
|
|
4197
|
+
const groupedRender = useMemo10(() => {
|
|
3241
4198
|
if (!groupedBuckets) return void 0;
|
|
3242
4199
|
const startIndexByBucket = /* @__PURE__ */ new Map();
|
|
3243
4200
|
const rows = [];
|
|
@@ -3254,7 +4211,7 @@ function TreeDataGrid({
|
|
|
3254
4211
|
const showSelectionColumn = controlledSelectedIds !== void 0;
|
|
3255
4212
|
const totalColumnCount = visibleColumns.length + (showRowActionsColumn ? 1 : 0) + (showSelectionColumn ? 1 : 0);
|
|
3256
4213
|
const { theadRef, groupHeaderTop } = useStickyGroupHeaderTop(Boolean(groupBy));
|
|
3257
|
-
const selectionStates =
|
|
4214
|
+
const selectionStates = useMemo10(
|
|
3258
4215
|
() => showSelectionColumn ? computeSelectionStates(data, accessors, childrenMap, controlledSelectedIds ?? EMPTY_SELECTED_IDS) : void 0,
|
|
3259
4216
|
[showSelectionColumn, data, accessors, childrenMap, controlledSelectedIds]
|
|
3260
4217
|
);
|
|
@@ -3270,7 +4227,7 @@ function TreeDataGrid({
|
|
|
3270
4227
|
else next.add(id);
|
|
3271
4228
|
onSelectedIdsChange?.(next);
|
|
3272
4229
|
}
|
|
3273
|
-
const selectableFlatRows =
|
|
4230
|
+
const selectableFlatRows = useMemo10(
|
|
3274
4231
|
() => allFlatRows.filter((flatRow) => !(isRowSelectionDisabled?.(flatRow.row) ?? false)),
|
|
3275
4232
|
[allFlatRows, isRowSelectionDisabled]
|
|
3276
4233
|
);
|
|
@@ -3284,12 +4241,12 @@ function TreeDataGrid({
|
|
|
3284
4241
|
}
|
|
3285
4242
|
onSelectedIdsChange?.(next);
|
|
3286
4243
|
}
|
|
3287
|
-
const bodyTdClassByColumn =
|
|
4244
|
+
const bodyTdClassByColumn = useMemo10(() => {
|
|
3288
4245
|
const map = /* @__PURE__ */ new Map();
|
|
3289
4246
|
for (const column of visibleColumns) map.set(column.id, `p-2 ${alignClassName(column)}`);
|
|
3290
4247
|
return map;
|
|
3291
4248
|
}, [visibleColumns]);
|
|
3292
|
-
const scrollRef =
|
|
4249
|
+
const scrollRef = useRef12(null);
|
|
3293
4250
|
const shouldVirtualize = !groupBy && flatRows.length > virtualizeThreshold;
|
|
3294
4251
|
const virtualizer = useVirtualizer2({
|
|
3295
4252
|
count: flatRows.length,
|
|
@@ -3302,7 +4259,7 @@ function TreeDataGrid({
|
|
|
3302
4259
|
const rowProps = getRowProps?.(flatRow.row, flatRow.depth, index);
|
|
3303
4260
|
const rowTestId = getRowTestId?.(flatRow.row) ?? `tree-row-${flatRow.id}`;
|
|
3304
4261
|
const isOddRow = zebra && index % 2 === 1;
|
|
3305
|
-
return /* @__PURE__ */
|
|
4262
|
+
return /* @__PURE__ */ jsxs21(
|
|
3306
4263
|
"tr",
|
|
3307
4264
|
{
|
|
3308
4265
|
ref: measureRef,
|
|
@@ -3315,9 +4272,9 @@ function TreeDataGrid({
|
|
|
3315
4272
|
isOddRow && "bg-foreground/5"
|
|
3316
4273
|
) : `border-b border-border divide-x divide-border${isOddRow ? " bg-foreground/5" : ""}`,
|
|
3317
4274
|
children: [
|
|
3318
|
-
showSelectionColumn && /* @__PURE__ */
|
|
4275
|
+
showSelectionColumn && /* @__PURE__ */ jsx33("td", { className: "p-1", style: { width: SELECTION_COLUMN_WIDTH }, children: (() => {
|
|
3319
4276
|
const state = resolveSelectionState(flatRow.row, flatRow.id);
|
|
3320
|
-
return /* @__PURE__ */
|
|
4277
|
+
return /* @__PURE__ */ jsx33(
|
|
3321
4278
|
Checkbox,
|
|
3322
4279
|
{
|
|
3323
4280
|
"aria-label": `Select row ${flatRow.id}`,
|
|
@@ -3328,7 +4285,7 @@ function TreeDataGrid({
|
|
|
3328
4285
|
}
|
|
3329
4286
|
);
|
|
3330
4287
|
})() }),
|
|
3331
|
-
visibleColumns.map((column) => /* @__PURE__ */
|
|
4288
|
+
visibleColumns.map((column) => /* @__PURE__ */ jsx33("td", { className: bodyTdClassByColumn.get(column.id), children: column.id === treeColId ? /* @__PURE__ */ jsx33(
|
|
3332
4289
|
TreeCell,
|
|
3333
4290
|
{
|
|
3334
4291
|
flatRow,
|
|
@@ -3341,7 +4298,7 @@ function TreeDataGrid({
|
|
|
3341
4298
|
editingCtx: editingState.ctx
|
|
3342
4299
|
}
|
|
3343
4300
|
) : renderCell(column, flatRow.row, editingState.ctx) }, column.id)),
|
|
3344
|
-
showRowActionsColumn && rowActions && /* @__PURE__ */
|
|
4301
|
+
showRowActionsColumn && rowActions && /* @__PURE__ */ jsx33("td", { className: "p-2", children: /* @__PURE__ */ jsx33(
|
|
3345
4302
|
ActionsMenu,
|
|
3346
4303
|
{
|
|
3347
4304
|
items: rowActions,
|
|
@@ -3357,8 +4314,8 @@ function TreeDataGrid({
|
|
|
3357
4314
|
const virtualItems = shouldVirtualize ? virtualizer.getVirtualItems() : [];
|
|
3358
4315
|
const paddingTop = virtualItems.length > 0 ? virtualItems[0].start : 0;
|
|
3359
4316
|
const paddingBottom = virtualItems.length > 0 ? virtualizer.getTotalSize() - virtualItems[virtualItems.length - 1].end : 0;
|
|
3360
|
-
return /* @__PURE__ */
|
|
3361
|
-
editing && editingState.pendingEdits.size > 0 && /* @__PURE__ */
|
|
4317
|
+
return /* @__PURE__ */ jsxs21("div", { className: "flex flex-col gap-2", "data-testid": "tree-datagrid", children: [
|
|
4318
|
+
editing && editingState.pendingEdits.size > 0 && /* @__PURE__ */ jsx33(
|
|
3362
4319
|
EditingBar,
|
|
3363
4320
|
{
|
|
3364
4321
|
editing,
|
|
@@ -3369,15 +4326,15 @@ function TreeDataGrid({
|
|
|
3369
4326
|
testIdPrefix: "tree-datagrid"
|
|
3370
4327
|
}
|
|
3371
4328
|
),
|
|
3372
|
-
/* @__PURE__ */
|
|
4329
|
+
/* @__PURE__ */ jsx33(
|
|
3373
4330
|
"div",
|
|
3374
4331
|
{
|
|
3375
4332
|
ref: scrollRef,
|
|
3376
4333
|
className: shouldVirtualize ? "overflow-y-auto" : void 0,
|
|
3377
4334
|
style: shouldVirtualize ? { maxHeight: maxBodyHeight } : void 0,
|
|
3378
|
-
children: /* @__PURE__ */
|
|
3379
|
-
/* @__PURE__ */
|
|
3380
|
-
showSelectionColumn && /* @__PURE__ */
|
|
4335
|
+
children: /* @__PURE__ */ jsxs21("table", { className: "w-full border-collapse text-sm", children: [
|
|
4336
|
+
/* @__PURE__ */ jsx33("thead", { ref: theadRef, children: /* @__PURE__ */ jsxs21("tr", { className: "divide-x divide-border", children: [
|
|
4337
|
+
showSelectionColumn && /* @__PURE__ */ jsx33("th", { className: "border-b border-border p-1", style: { width: SELECTION_COLUMN_WIDTH }, children: /* @__PURE__ */ jsx33(
|
|
3381
4338
|
Checkbox,
|
|
3382
4339
|
{
|
|
3383
4340
|
"aria-label": "Select all visible rows",
|
|
@@ -3385,7 +4342,7 @@ function TreeDataGrid({
|
|
|
3385
4342
|
onCheckedChange: toggleAllVisibleSelected
|
|
3386
4343
|
}
|
|
3387
4344
|
) }),
|
|
3388
|
-
visibleColumns.map((column) => /* @__PURE__ */
|
|
4345
|
+
visibleColumns.map((column) => /* @__PURE__ */ jsx33(
|
|
3389
4346
|
"th",
|
|
3390
4347
|
{
|
|
3391
4348
|
style: column.width ? { width: column.width } : void 0,
|
|
@@ -3394,12 +4351,12 @@ function TreeDataGrid({
|
|
|
3394
4351
|
},
|
|
3395
4352
|
column.id
|
|
3396
4353
|
)),
|
|
3397
|
-
showRowActionsColumn && /* @__PURE__ */
|
|
4354
|
+
showRowActionsColumn && /* @__PURE__ */ jsx33("th", { className: "border-b border-border p-2", "aria-label": "Row actions" })
|
|
3398
4355
|
] }) }),
|
|
3399
|
-
/* @__PURE__ */
|
|
4356
|
+
/* @__PURE__ */ jsx33("tbody", { children: data.length === 0 ? /* @__PURE__ */ jsx33("tr", { children: /* @__PURE__ */ jsx33("td", { colSpan: totalColumnCount, className: "p-4 text-center text-muted-foreground", children: "No results." }) }) : groupedBuckets ? groupedBuckets.map((bucket) => {
|
|
3400
4357
|
const groupExpanded = isGroupExpanded(bucket.key);
|
|
3401
|
-
return /* @__PURE__ */
|
|
3402
|
-
/* @__PURE__ */
|
|
4358
|
+
return /* @__PURE__ */ jsxs21(Fragment5, { children: [
|
|
4359
|
+
/* @__PURE__ */ jsx33("tr", { "data-testid": `group-header-${bucket.key}`, children: /* @__PURE__ */ jsx33(
|
|
3403
4360
|
"td",
|
|
3404
4361
|
{
|
|
3405
4362
|
colSpan: totalColumnCount,
|
|
@@ -3408,7 +4365,7 @@ function TreeDataGrid({
|
|
|
3408
4365
|
zebra ? "bg-muted" : "bg-background"
|
|
3409
4366
|
),
|
|
3410
4367
|
style: { top: groupHeaderTop },
|
|
3411
|
-
children: /* @__PURE__ */
|
|
4368
|
+
children: /* @__PURE__ */ jsxs21(
|
|
3412
4369
|
"button",
|
|
3413
4370
|
{
|
|
3414
4371
|
type: "button",
|
|
@@ -3416,7 +4373,7 @@ function TreeDataGrid({
|
|
|
3416
4373
|
onClick: () => toggleGroupExpanded(bucket.key),
|
|
3417
4374
|
"aria-expanded": groupExpanded,
|
|
3418
4375
|
children: [
|
|
3419
|
-
/* @__PURE__ */
|
|
4376
|
+
/* @__PURE__ */ jsx33(
|
|
3420
4377
|
ChevronRight2,
|
|
3421
4378
|
{
|
|
3422
4379
|
className: `h-4 w-4 shrink-0 transition-transform${groupExpanded ? " rotate-90" : ""}`,
|
|
@@ -3434,12 +4391,12 @@ function TreeDataGrid({
|
|
|
3434
4391
|
return bucket.flatRows.map((flatRow, i) => renderRow(flatRow, startIndex + i));
|
|
3435
4392
|
})()
|
|
3436
4393
|
] }, `group-${bucket.key}`);
|
|
3437
|
-
}) : shouldVirtualize ? /* @__PURE__ */
|
|
3438
|
-
paddingTop > 0 && /* @__PURE__ */
|
|
4394
|
+
}) : shouldVirtualize ? /* @__PURE__ */ jsxs21(Fragment6, { children: [
|
|
4395
|
+
paddingTop > 0 && /* @__PURE__ */ jsx33("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx33("td", { colSpan: totalColumnCount, style: { padding: 0, border: "none", height: paddingTop } }) }),
|
|
3439
4396
|
virtualItems.map(
|
|
3440
4397
|
(virtualItem) => renderRow(flatRows[virtualItem.index], virtualItem.index, (el) => virtualizer.measureElement(el))
|
|
3441
4398
|
),
|
|
3442
|
-
paddingBottom > 0 && /* @__PURE__ */
|
|
4399
|
+
paddingBottom > 0 && /* @__PURE__ */ jsx33("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx33(
|
|
3443
4400
|
"td",
|
|
3444
4401
|
{
|
|
3445
4402
|
colSpan: totalColumnCount,
|
|
@@ -3464,40 +4421,54 @@ export {
|
|
|
3464
4421
|
EditingBar,
|
|
3465
4422
|
EnumEditor,
|
|
3466
4423
|
EnumFilter,
|
|
4424
|
+
MultilineStringEditor,
|
|
3467
4425
|
NumberComparisonFilter,
|
|
3468
4426
|
NumberEditor,
|
|
3469
4427
|
NumberHistogramFilter,
|
|
3470
4428
|
NumberRangeFilter,
|
|
4429
|
+
SelectionOverlay,
|
|
3471
4430
|
StringEditor,
|
|
3472
4431
|
StringFilter,
|
|
3473
4432
|
TreeDataGrid,
|
|
3474
4433
|
UNARY_OPERATORS,
|
|
3475
4434
|
alignClassName,
|
|
4435
|
+
buildIndexMap,
|
|
3476
4436
|
canHideColumn,
|
|
3477
4437
|
canHideGroup,
|
|
4438
|
+
cellsInRange,
|
|
4439
|
+
coerceValueForColumn,
|
|
4440
|
+
computeFillChanges,
|
|
3478
4441
|
countVisible,
|
|
3479
4442
|
defaultAlign,
|
|
3480
4443
|
defaultFormat,
|
|
3481
4444
|
editErrorId,
|
|
3482
4445
|
evaluateFilter,
|
|
4446
|
+
extendRangeForFill,
|
|
3483
4447
|
facetedNumberValues,
|
|
3484
4448
|
fieldKey,
|
|
3485
4449
|
flattenTree,
|
|
3486
4450
|
getColumnValue,
|
|
3487
4451
|
groupColumns,
|
|
4452
|
+
isCellInRange,
|
|
3488
4453
|
isColumnVisible,
|
|
3489
4454
|
isCompositeFilterDescriptor,
|
|
3490
4455
|
isEditable,
|
|
3491
4456
|
isFilterDescriptor,
|
|
3492
4457
|
isFilterable,
|
|
3493
4458
|
isSortable,
|
|
4459
|
+
normalizeRange,
|
|
4460
|
+
parseTsv,
|
|
3494
4461
|
processClientData,
|
|
4462
|
+
rangeToTsv,
|
|
4463
|
+
renderCellModeCell,
|
|
3495
4464
|
renderDefaultEditWidget,
|
|
3496
4465
|
renderDefaultFilterWidget,
|
|
3497
4466
|
renderEditableCell,
|
|
3498
4467
|
resolveMenuItems,
|
|
3499
4468
|
storageKeyFor,
|
|
3500
4469
|
toDate,
|
|
4470
|
+
useCellEditingState,
|
|
4471
|
+
useCellSelection,
|
|
3501
4472
|
useDebouncedCallback,
|
|
3502
4473
|
useEditingState,
|
|
3503
4474
|
useGridState,
|