@bmsuisse/datagrid 0.26.0 → 0.27.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.js CHANGED
@@ -1488,7 +1488,11 @@ function EnumEditor({
1488
1488
  }
1489
1489
 
1490
1490
  // src/edit/NumberEditor.tsx
1491
+ import { useEffect as useEffect4, useRef as useRef4, useState as useState5 } from "react";
1491
1492
  import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
1493
+ function textFromValue(value) {
1494
+ return value === null || value === void 0 ? "" : String(value);
1495
+ }
1492
1496
  function NumberEditor({
1493
1497
  column,
1494
1498
  rowId,
@@ -1498,6 +1502,13 @@ function NumberEditor({
1498
1502
  autoFocus
1499
1503
  }) {
1500
1504
  const errorId = editErrorId(rowId, column.id);
1505
+ const [text, setText] = useState5(() => textFromValue(value));
1506
+ const lastEmittedRef = useRef4(value);
1507
+ useEffect4(() => {
1508
+ if (value === lastEmittedRef.current) return;
1509
+ lastEmittedRef.current = value;
1510
+ setText(textFromValue(value));
1511
+ }, [value]);
1501
1512
  return /* @__PURE__ */ jsxs12("div", { children: [
1502
1513
  /* @__PURE__ */ jsx19(
1503
1514
  Input,
@@ -1509,8 +1520,13 @@ function NumberEditor({
1509
1520
  "aria-describedby": error ? errorId : void 0,
1510
1521
  "data-testid": `edit-${rowId}-${column.id}`,
1511
1522
  autoFocus,
1512
- value: value === null || value === void 0 ? "" : String(value),
1513
- onChange: (event) => onChange(event.target.value === "" ? null : Number(event.target.value))
1523
+ value: text,
1524
+ onChange: (event) => {
1525
+ setText(event.target.value);
1526
+ const next = event.target.value === "" ? null : Number(event.target.value);
1527
+ lastEmittedRef.current = next;
1528
+ onChange(next);
1529
+ }
1514
1530
  }
1515
1531
  ),
1516
1532
  error && /* @__PURE__ */ jsx19(EditFieldError, { id: errorId, message: error })
@@ -1565,18 +1581,246 @@ function renderDefaultEditWidget(column, rowId, value, onChange, error, autoFocu
1565
1581
  }
1566
1582
  }
1567
1583
 
1584
+ // src/edit/editingState.ts
1585
+ import { useEffect as useEffect5, useRef as useRef5, useState as useState6 } from "react";
1586
+ function editValuesEqual(a, b) {
1587
+ if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
1588
+ return a === b;
1589
+ }
1590
+ function useEditingState(editing, getRowId) {
1591
+ const [pendingEdits, setPendingEdits] = useState6(/* @__PURE__ */ new Map());
1592
+ const [editErrors, setEditErrors] = useState6(/* @__PURE__ */ new Map());
1593
+ const hasEditErrors = editErrors.size > 0;
1594
+ const [activeRowId, setActiveRowId] = useState6(null);
1595
+ const [autoFocusTarget, setAutoFocusTarget] = useState6(null);
1596
+ function activateRow(rowId, columnId) {
1597
+ setActiveRowId(rowId);
1598
+ setAutoFocusTarget({ rowId, columnId });
1599
+ }
1600
+ useEffect5(() => {
1601
+ if (autoFocusTarget) setAutoFocusTarget(null);
1602
+ }, [autoFocusTarget]);
1603
+ function commitEdit(column, row, value) {
1604
+ const rowId = getRowId(row);
1605
+ const baseline = getColumnValue(column, row);
1606
+ setPendingEdits((prev) => {
1607
+ const next = new Map(prev);
1608
+ const values = new Map(next.get(rowId)?.values);
1609
+ if (editValuesEqual(value, baseline)) values.delete(column.id);
1610
+ else values.set(column.id, value);
1611
+ if (values.size === 0) next.delete(rowId);
1612
+ else next.set(rowId, { row, values });
1613
+ return next;
1614
+ });
1615
+ const message = column.validateEdit?.(value, row);
1616
+ setEditErrors((prev) => {
1617
+ const next = new Map(prev);
1618
+ const rowErrors = new Map(next.get(rowId));
1619
+ if (message) rowErrors.set(column.id, message);
1620
+ else rowErrors.delete(column.id);
1621
+ if (rowErrors.size === 0) next.delete(rowId);
1622
+ else next.set(rowId, rowErrors);
1623
+ return next;
1624
+ });
1625
+ }
1626
+ async function handleSaveEdits() {
1627
+ if (!editing || pendingEdits.size === 0) return;
1628
+ const snapshot = pendingEdits;
1629
+ const edited = [...snapshot.entries()].map(([rowId, entry]) => ({
1630
+ rowId,
1631
+ row: entry.row,
1632
+ values: Object.fromEntries(entry.values)
1633
+ }));
1634
+ try {
1635
+ await editing.onSave(edited);
1636
+ } catch {
1637
+ return;
1638
+ }
1639
+ const snapshotRowIds = new Set(snapshot.keys());
1640
+ const survivedRowIds = /* @__PURE__ */ new Set();
1641
+ const clearedErrorKeys = [];
1642
+ setPendingEdits((prev) => {
1643
+ const next = new Map(prev);
1644
+ for (const [rowId, snapshotEntry] of snapshot) {
1645
+ const current = next.get(rowId);
1646
+ if (!current) continue;
1647
+ const values = new Map(current.values);
1648
+ for (const [columnId, snapshotValue] of snapshotEntry.values) {
1649
+ if (values.has(columnId) && editValuesEqual(values.get(columnId), snapshotValue)) {
1650
+ values.delete(columnId);
1651
+ clearedErrorKeys.push({ rowId, columnId });
1652
+ }
1653
+ }
1654
+ if (values.size === 0) {
1655
+ next.delete(rowId);
1656
+ } else {
1657
+ next.set(rowId, { row: current.row, values });
1658
+ survivedRowIds.add(rowId);
1659
+ }
1660
+ }
1661
+ return next;
1662
+ });
1663
+ setEditErrors((prev) => {
1664
+ if (clearedErrorKeys.length === 0) return prev;
1665
+ const next = new Map(prev);
1666
+ for (const { rowId, columnId } of clearedErrorKeys) {
1667
+ const rowErrors = next.get(rowId);
1668
+ if (!rowErrors) continue;
1669
+ const nextRowErrors = new Map(rowErrors);
1670
+ nextRowErrors.delete(columnId);
1671
+ if (nextRowErrors.size === 0) next.delete(rowId);
1672
+ else next.set(rowId, nextRowErrors);
1673
+ }
1674
+ return next;
1675
+ });
1676
+ setActiveRowId(
1677
+ (prev) => prev !== null && snapshotRowIds.has(prev) && !survivedRowIds.has(prev) ? null : prev
1678
+ );
1679
+ }
1680
+ function handleDiscardEdits() {
1681
+ setPendingEdits(/* @__PURE__ */ new Map());
1682
+ setEditErrors(/* @__PURE__ */ new Map());
1683
+ setActiveRowId(null);
1684
+ editing?.onDiscard?.();
1685
+ }
1686
+ const ctx = editing ? { pendingEdits, editErrors, activeRowId, autoFocusTarget, getRowId, onEdit: commitEdit, onActivateRow: activateRow } : void 0;
1687
+ const ctxRef = useRef5(void 0);
1688
+ ctxRef.current = ctx;
1689
+ return { pendingEdits, editErrors, hasEditErrors, ctx, ctxRef, handleSaveEdits, handleDiscardEdits };
1690
+ }
1691
+
1692
+ // src/edit/renderEditableCell.tsx
1693
+ import { jsx as jsx22 } from "react/jsx-runtime";
1694
+ function renderEditableCell(column, row, rawValue, ctx) {
1695
+ const staticContent = (value2) => column.cell ? column.cell(value2, row) : defaultFormat(column, value2);
1696
+ if (!ctx || !isEditable(column, row)) return staticContent(rawValue);
1697
+ const rowId = ctx.getRowId(row);
1698
+ const pendingRow = ctx.pendingEdits.get(rowId);
1699
+ const value = pendingRow?.values.has(column.id) ? pendingRow.values.get(column.id) : rawValue;
1700
+ if (ctx.activeRowId === rowId) {
1701
+ const error = ctx.editErrors.get(rowId)?.get(column.id);
1702
+ const autoFocus = ctx.autoFocusTarget?.rowId === rowId && ctx.autoFocusTarget.columnId === column.id;
1703
+ const onChange = (next) => ctx.onEdit(column, row, next);
1704
+ const editor = column.renderEditCell ? column.renderEditCell(value, row, onChange, error, autoFocus) : renderDefaultEditWidget(column, rowId, value, onChange, error, autoFocus);
1705
+ return /* @__PURE__ */ jsx22(
1706
+ "div",
1707
+ {
1708
+ onKeyDown: (event) => {
1709
+ if (event.key !== "Escape") return;
1710
+ if (!event.currentTarget.contains(event.target)) return;
1711
+ event.stopPropagation();
1712
+ ctx.onEdit(column, row, getColumnValue(column, row));
1713
+ },
1714
+ children: editor
1715
+ }
1716
+ );
1717
+ }
1718
+ return /* @__PURE__ */ jsx22(
1719
+ "span",
1720
+ {
1721
+ role: "button",
1722
+ tabIndex: 0,
1723
+ "data-testid": `cell-${rowId}-${column.id}`,
1724
+ className: "block w-full cursor-text",
1725
+ onClick: (event) => {
1726
+ stopRowClick(event);
1727
+ ctx.onActivateRow(rowId, column.id);
1728
+ },
1729
+ onKeyDown: (event) => {
1730
+ if (event.key !== "Enter" && event.key !== " ") return;
1731
+ event.preventDefault();
1732
+ stopRowClick(event);
1733
+ ctx.onActivateRow(rowId, column.id);
1734
+ },
1735
+ children: staticContent(value)
1736
+ }
1737
+ );
1738
+ }
1739
+
1740
+ // src/edit/EditingBar.tsx
1741
+ import { jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
1742
+ function resolveEditLabel(label, changedRowCount, fallback) {
1743
+ if (label === void 0) return fallback(changedRowCount);
1744
+ return typeof label === "function" ? label(changedRowCount) : label;
1745
+ }
1746
+ function describeErrorRows(editErrors) {
1747
+ const rowIds = [...editErrors.keys()];
1748
+ const shown = rowIds.slice(0, 3);
1749
+ const suffix = rowIds.length > shown.length ? `, and ${rowIds.length - shown.length} more` : "";
1750
+ return `row${rowIds.length === 1 ? "" : "s"} ${shown.join(", ")}${suffix}`;
1751
+ }
1752
+ function EditingBar({
1753
+ editing,
1754
+ pendingRowCount,
1755
+ editErrors,
1756
+ onSave,
1757
+ onDiscard,
1758
+ testIdPrefix = "datagrid"
1759
+ }) {
1760
+ const hasEditErrors = editErrors.size > 0;
1761
+ return /* @__PURE__ */ jsxs14(
1762
+ "div",
1763
+ {
1764
+ "data-testid": `${testIdPrefix}-edit-bar`,
1765
+ 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
+ children: [
1767
+ /* @__PURE__ */ jsxs14("span", { "aria-live": "polite", children: [
1768
+ pendingRowCount,
1769
+ " row",
1770
+ pendingRowCount === 1 ? "" : "s",
1771
+ " changed",
1772
+ hasEditErrors && /* @__PURE__ */ jsxs14("span", { className: "ml-2 text-destructive", children: [
1773
+ "Fix the highlighted errors before saving (",
1774
+ describeErrorRows(editErrors),
1775
+ ")."
1776
+ ] })
1777
+ ] }),
1778
+ /* @__PURE__ */ jsxs14("div", { className: "flex gap-2", children: [
1779
+ /* @__PURE__ */ jsx23(
1780
+ Button,
1781
+ {
1782
+ type: "button",
1783
+ variant: "outline",
1784
+ size: "sm",
1785
+ "data-testid": `${testIdPrefix}-discard-edits`,
1786
+ disabled: editing.saving,
1787
+ onClick: onDiscard,
1788
+ children: resolveEditLabel(editing.discardLabel, pendingRowCount, () => "Discard")
1789
+ }
1790
+ ),
1791
+ /* @__PURE__ */ jsx23(
1792
+ Button,
1793
+ {
1794
+ type: "button",
1795
+ size: "sm",
1796
+ "data-testid": `${testIdPrefix}-save-edits`,
1797
+ disabled: editing.saving || hasEditErrors,
1798
+ onClick: onSave,
1799
+ children: resolveEditLabel(
1800
+ editing.saveLabel,
1801
+ pendingRowCount,
1802
+ (count) => `Save ${count} change${count === 1 ? "" : "s"}`
1803
+ )
1804
+ }
1805
+ )
1806
+ ] })
1807
+ ]
1808
+ }
1809
+ );
1810
+ }
1811
+
1568
1812
  // src/column-selector/ColumnSelector.tsx
1569
1813
  import { Columns3 } from "lucide-react";
1570
- import { useEffect as useEffect4 } from "react";
1814
+ import { useEffect as useEffect6 } from "react";
1571
1815
 
1572
1816
  // src/components/ui/dialog.tsx
1573
1817
  import * as DialogPrimitive from "@radix-ui/react-dialog";
1574
1818
  import { X } from "lucide-react";
1575
1819
  import { forwardRef as forwardRef6 } from "react";
1576
- import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
1820
+ import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
1577
1821
  var Dialog = DialogPrimitive.Root;
1578
1822
  var DialogTrigger = DialogPrimitive.Trigger;
1579
- var DialogOverlay = forwardRef6(({ className, ...props }, ref) => /* @__PURE__ */ jsx22(
1823
+ var DialogOverlay = forwardRef6(({ className, ...props }, ref) => /* @__PURE__ */ jsx24(
1580
1824
  DialogPrimitive.Overlay,
1581
1825
  {
1582
1826
  ref,
@@ -1585,9 +1829,9 @@ var DialogOverlay = forwardRef6(({ className, ...props }, ref) => /* @__PURE__ *
1585
1829
  }
1586
1830
  ));
1587
1831
  DialogOverlay.displayName = "DialogOverlay";
1588
- var DialogContent = forwardRef6(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs14(DialogPrimitive.Portal, { children: [
1589
- /* @__PURE__ */ jsx22(DialogOverlay, {}),
1590
- /* @__PURE__ */ jsxs14(
1832
+ var DialogContent = forwardRef6(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs15(DialogPrimitive.Portal, { children: [
1833
+ /* @__PURE__ */ jsx24(DialogOverlay, {}),
1834
+ /* @__PURE__ */ jsxs15(
1591
1835
  DialogPrimitive.Content,
1592
1836
  {
1593
1837
  ref,
@@ -1598,9 +1842,9 @@ var DialogContent = forwardRef6(({ className, children, ...props }, ref) => /* @
1598
1842
  ...props,
1599
1843
  children: [
1600
1844
  children,
1601
- /* @__PURE__ */ jsxs14(DialogPrimitive.Close, { className: "absolute right-4 top-4 rounded-sm opacity-70 hover:opacity-100", children: [
1602
- /* @__PURE__ */ jsx22(X, { className: "h-4 w-4" }),
1603
- /* @__PURE__ */ jsx22("span", { className: "sr-only", children: "Close" })
1845
+ /* @__PURE__ */ jsxs15(DialogPrimitive.Close, { className: "absolute right-4 top-4 rounded-sm opacity-70 hover:opacity-100", children: [
1846
+ /* @__PURE__ */ jsx24(X, { className: "h-4 w-4" }),
1847
+ /* @__PURE__ */ jsx24("span", { className: "sr-only", children: "Close" })
1604
1848
  ] })
1605
1849
  ]
1606
1850
  }
@@ -1610,10 +1854,10 @@ DialogContent.displayName = "DialogContent";
1610
1854
  var DialogHeader = ({
1611
1855
  className,
1612
1856
  ...props
1613
- }) => /* @__PURE__ */ jsx22("div", { className: cn("flex flex-col gap-1.5", className), ...props });
1614
- var DialogTitle = forwardRef6(({ className, ...props }, ref) => /* @__PURE__ */ jsx22(DialogPrimitive.Title, { ref, className: cn("text-lg font-semibold", className), ...props }));
1857
+ }) => /* @__PURE__ */ jsx24("div", { className: cn("flex flex-col gap-1.5", className), ...props });
1858
+ var DialogTitle = forwardRef6(({ className, ...props }, ref) => /* @__PURE__ */ jsx24(DialogPrimitive.Title, { ref, className: cn("text-lg font-semibold", className), ...props }));
1615
1859
  DialogTitle.displayName = "DialogTitle";
1616
- var DialogDescription = forwardRef6(({ className, ...props }, ref) => /* @__PURE__ */ jsx22(
1860
+ var DialogDescription = forwardRef6(({ className, ...props }, ref) => /* @__PURE__ */ jsx24(
1617
1861
  DialogPrimitive.Description,
1618
1862
  {
1619
1863
  ref,
@@ -1681,7 +1925,7 @@ function canHideGroup(columns, visibility, groupColumnIds) {
1681
1925
  }
1682
1926
 
1683
1927
  // src/column-selector/ColumnSelector.tsx
1684
- import { jsx as jsx23, jsxs as jsxs15 } from "react/jsx-runtime";
1928
+ import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
1685
1929
  function ColumnSelector({
1686
1930
  columns,
1687
1931
  visibility,
@@ -1691,7 +1935,7 @@ function ColumnSelector({
1691
1935
  open,
1692
1936
  onOpenChange
1693
1937
  }) {
1694
- useEffect4(() => {
1938
+ useEffect6(() => {
1695
1939
  if (!persistKey) return;
1696
1940
  const restored = readPersistedVisibility(persistKey);
1697
1941
  if (restored) onVisibilityChange({ ...visibility, ...restored });
@@ -1706,20 +1950,20 @@ function ColumnSelector({
1706
1950
  }
1707
1951
  const totalVisible = countVisible(columns, visibility);
1708
1952
  const groups = groupColumns(columns);
1709
- return /* @__PURE__ */ jsxs15(Dialog, { open, onOpenChange, children: [
1710
- /* @__PURE__ */ jsx23(DialogTrigger, { asChild: true, children: trigger ?? /* @__PURE__ */ jsx23(Button, { variant: "outline", size: "icon", "aria-label": "Choose columns", children: /* @__PURE__ */ jsx23(Columns3, { className: "h-4 w-4", "aria-hidden": true }) }) }),
1711
- /* @__PURE__ */ jsxs15(DialogContent, { className: "w-fit max-w-[calc(100vw-2rem)] sm:max-w-2xl", children: [
1712
- /* @__PURE__ */ jsx23(DialogHeader, { children: /* @__PURE__ */ jsx23(DialogTitle, { children: "Columns" }) }),
1713
- /* @__PURE__ */ jsx23("div", { className: "flex flex-wrap gap-x-8 gap-y-5", children: groups.map(({ group, columns: groupCols }) => /* @__PURE__ */ jsxs15("div", { className: "flex min-w-32 flex-col gap-1.5", children: [
1714
- group && /* @__PURE__ */ jsx23("span", { className: "border-b pb-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: group }),
1953
+ return /* @__PURE__ */ jsxs16(Dialog, { open, onOpenChange, children: [
1954
+ /* @__PURE__ */ jsx25(DialogTrigger, { asChild: true, children: trigger ?? /* @__PURE__ */ jsx25(Button, { variant: "outline", size: "icon", "aria-label": "Choose columns", children: /* @__PURE__ */ jsx25(Columns3, { className: "h-4 w-4", "aria-hidden": true }) }) }),
1955
+ /* @__PURE__ */ jsxs16(DialogContent, { className: "w-fit max-w-[calc(100vw-2rem)] sm:max-w-2xl", children: [
1956
+ /* @__PURE__ */ jsx25(DialogHeader, { children: /* @__PURE__ */ jsx25(DialogTitle, { children: "Columns" }) }),
1957
+ /* @__PURE__ */ jsx25("div", { className: "flex flex-wrap gap-x-8 gap-y-5", children: groups.map(({ group, columns: groupCols }) => /* @__PURE__ */ jsxs16("div", { className: "flex min-w-32 flex-col gap-1.5", children: [
1958
+ group && /* @__PURE__ */ jsx25("span", { className: "border-b pb-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: group }),
1715
1959
  groupCols.map((column) => {
1716
1960
  const visible = isColumnVisible(visibility, column.id);
1717
- return /* @__PURE__ */ jsxs15(
1961
+ return /* @__PURE__ */ jsxs16(
1718
1962
  "label",
1719
1963
  {
1720
1964
  className: "flex items-center gap-2 rounded-md px-1.5 py-1 text-sm hover:bg-accent/50 focus-within:bg-accent/50",
1721
1965
  children: [
1722
- /* @__PURE__ */ jsx23(
1966
+ /* @__PURE__ */ jsx25(
1723
1967
  Checkbox,
1724
1968
  {
1725
1969
  checked: visible,
@@ -1756,10 +2000,10 @@ import { useMemo as useMemo3 } from "react";
1756
2000
  // src/components/ui/dropdown-menu.tsx
1757
2001
  import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
1758
2002
  import { forwardRef as forwardRef7 } from "react";
1759
- import { jsx as jsx24 } from "react/jsx-runtime";
2003
+ import { jsx as jsx26 } from "react/jsx-runtime";
1760
2004
  var DropdownMenu = DropdownMenuPrimitive.Root;
1761
2005
  var DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
1762
- var DropdownMenuContent = forwardRef7(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx24(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx24(
2006
+ var DropdownMenuContent = forwardRef7(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx26(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx26(
1763
2007
  DropdownMenuPrimitive.Content,
1764
2008
  {
1765
2009
  ref,
@@ -1772,7 +2016,7 @@ var DropdownMenuContent = forwardRef7(({ className, sideOffset = 4, ...props },
1772
2016
  }
1773
2017
  ) }));
1774
2018
  DropdownMenuContent.displayName = "DropdownMenuContent";
1775
- var DropdownMenuItem = forwardRef7(({ className, danger, ...props }, ref) => /* @__PURE__ */ jsx24(
2019
+ var DropdownMenuItem = forwardRef7(({ className, danger, ...props }, ref) => /* @__PURE__ */ jsx26(
1776
2020
  DropdownMenuPrimitive.Item,
1777
2021
  {
1778
2022
  ref,
@@ -1785,7 +2029,7 @@ var DropdownMenuItem = forwardRef7(({ className, danger, ...props }, ref) => /*
1785
2029
  }
1786
2030
  ));
1787
2031
  DropdownMenuItem.displayName = "DropdownMenuItem";
1788
- var DropdownMenuSeparator = forwardRef7(({ className, ...props }, ref) => /* @__PURE__ */ jsx24(
2032
+ var DropdownMenuSeparator = forwardRef7(({ className, ...props }, ref) => /* @__PURE__ */ jsx26(
1789
2033
  DropdownMenuPrimitive.Separator,
1790
2034
  {
1791
2035
  ref,
@@ -1794,7 +2038,7 @@ var DropdownMenuSeparator = forwardRef7(({ className, ...props }, ref) => /* @__
1794
2038
  }
1795
2039
  ));
1796
2040
  DropdownMenuSeparator.displayName = "DropdownMenuSeparator";
1797
- var DropdownMenuLabel = forwardRef7(({ className, ...props }, ref) => /* @__PURE__ */ jsx24(
2041
+ var DropdownMenuLabel = forwardRef7(({ className, ...props }, ref) => /* @__PURE__ */ jsx26(
1798
2042
  DropdownMenuPrimitive.Label,
1799
2043
  {
1800
2044
  ref,
@@ -1805,16 +2049,16 @@ var DropdownMenuLabel = forwardRef7(({ className, ...props }, ref) => /* @__PURE
1805
2049
  DropdownMenuLabel.displayName = "DropdownMenuLabel";
1806
2050
 
1807
2051
  // src/menu/ActionsMenu.tsx
1808
- import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
2052
+ import { jsx as jsx27, jsxs as jsxs17 } from "react/jsx-runtime";
1809
2053
  function ActionsMenu({ items, ctx, triggerLabel }) {
1810
2054
  const resolved = useMemo3(
1811
2055
  () => resolveMenuItems(items, ctx),
1812
2056
  [items, ctx.row, ctx.selectedRows]
1813
2057
  );
1814
2058
  if (resolved.length === 0) return null;
1815
- return /* @__PURE__ */ jsxs16(DropdownMenu, { children: [
1816
- /* @__PURE__ */ jsx25(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx25(Button, { variant: "ghost", size: "icon", "aria-label": triggerLabel, onClick: stopRowClick, children: /* @__PURE__ */ jsx25(MoreVertical, { className: "h-4 w-4", "aria-hidden": true }) }) }),
1817
- /* @__PURE__ */ jsx25(DropdownMenuContent, { align: "end", children: resolved.map((item) => /* @__PURE__ */ jsx25(
2059
+ return /* @__PURE__ */ jsxs17(DropdownMenu, { children: [
2060
+ /* @__PURE__ */ jsx27(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx27(Button, { variant: "ghost", size: "icon", "aria-label": triggerLabel, onClick: stopRowClick, children: /* @__PURE__ */ jsx27(MoreVertical, { className: "h-4 w-4", "aria-hidden": true }) }) }),
2061
+ /* @__PURE__ */ jsx27(DropdownMenuContent, { align: "end", children: resolved.map((item) => /* @__PURE__ */ jsx27(
1818
2062
  DropdownMenuItem,
1819
2063
  {
1820
2064
  danger: item.danger,
@@ -1832,7 +2076,7 @@ import { BarsArrowDownIcon, BarsArrowUpIcon, FunnelIcon as FunnelIcon7 } from "@
1832
2076
  import { columnResizingFeature, columnSizingFeature, flexRender, tableFeatures, useTable } from "@tanstack/react-table";
1833
2077
  import { useVirtualizer } from "@tanstack/react-virtual";
1834
2078
  import { ChevronRight, Loader2 } from "lucide-react";
1835
- import { Fragment as Fragment2, useEffect as useEffect7, useLayoutEffect, useMemo as useMemo6, useRef as useRef5, useState as useState6 } from "react";
2079
+ import { Fragment as Fragment2, useEffect as useEffect9, useLayoutEffect, useMemo as useMemo6, useRef as useRef7, useState as useState8 } from "react";
1836
2080
 
1837
2081
  // src/lib/structuralColumns.ts
1838
2082
  var SELECTION_COLUMN_WIDTH = 44;
@@ -1875,15 +2119,15 @@ function processClientData(data, state) {
1875
2119
  }
1876
2120
 
1877
2121
  // src/grid/useGridState.ts
1878
- import { useCallback as useCallback2, useEffect as useEffect6, useMemo as useMemo5, useState as useState5 } from "react";
2122
+ import { useCallback as useCallback2, useEffect as useEffect8, useMemo as useMemo5, useState as useState7 } from "react";
1879
2123
 
1880
2124
  // src/hooks/useDebouncedCallback.ts
1881
- import { useEffect as useEffect5, useMemo as useMemo4, useRef as useRef4 } from "react";
2125
+ import { useEffect as useEffect7, useMemo as useMemo4, useRef as useRef6 } from "react";
1882
2126
  function useDebouncedCallback(callback, delayMs) {
1883
- const callbackRef = useRef4(callback);
2127
+ const callbackRef = useRef6(callback);
1884
2128
  callbackRef.current = callback;
1885
- const timeoutRef = useRef4(void 0);
1886
- useEffect5(() => {
2129
+ const timeoutRef = useRef6(void 0);
2130
+ useEffect7(() => {
1887
2131
  return () => {
1888
2132
  clearTimeout(timeoutRef.current);
1889
2133
  };
@@ -1913,7 +2157,7 @@ function nextSortDir(current, descFirst) {
1913
2157
  return void 0;
1914
2158
  }
1915
2159
  function useGridState(dataSource, initialState, externalState) {
1916
- const [state, setState] = useState5(
2160
+ const [state, setState] = useState7(
1917
2161
  () => externalState ?? { ...DEFAULT_STATE, ...initialState }
1918
2162
  );
1919
2163
  const notifyRaw = useCallback2(
@@ -1933,7 +2177,7 @@ function useGridState(dataSource, initialState, externalState) {
1933
2177
  },
1934
2178
  [notifyRaw, cancelDebouncedNotify]
1935
2179
  );
1936
- useEffect6(() => {
2180
+ useEffect8(() => {
1937
2181
  if (!externalState) return;
1938
2182
  cancelDebouncedNotify();
1939
2183
  setState(externalState);
@@ -2002,7 +2246,7 @@ function useGridState(dataSource, initialState, externalState) {
2002
2246
  }
2003
2247
 
2004
2248
  // src/grid/DataGrid.tsx
2005
- import { Fragment as Fragment3, jsx as jsx26, jsxs as jsxs17 } from "react/jsx-runtime";
2249
+ import { Fragment as Fragment3, jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
2006
2250
  var gridTableFeatures = tableFeatures({ columnSizingFeature, columnResizingFeature });
2007
2251
  var DEFAULT_COLUMN_SIZE = 150;
2008
2252
  var DETAIL_COLUMN_WIDTH = 44;
@@ -2039,71 +2283,9 @@ function toTanstackColumns(columns, editingCtxRef) {
2039
2283
  header: column.header,
2040
2284
  size: column.width,
2041
2285
  accessorFn: (row) => getColumnValue(column, row),
2042
- cell: (info) => {
2043
- const row = info.row.original;
2044
- const editingCtx = editingCtxRef.current;
2045
- const staticContent = (value) => column.cell ? column.cell(value, row) : defaultFormat(column, value);
2046
- if (editingCtx && isEditable(column, row)) {
2047
- const rowId = editingCtx.getRowId(row);
2048
- const pendingRow = editingCtx.pendingEdits.get(rowId);
2049
- const value = pendingRow?.values.has(column.id) ? pendingRow.values.get(column.id) : info.getValue();
2050
- if (editingCtx.activeRowId === rowId) {
2051
- const error = editingCtx.editErrors.get(rowId)?.get(column.id);
2052
- const autoFocus = editingCtx.autoFocusTarget?.rowId === rowId && editingCtx.autoFocusTarget.columnId === column.id;
2053
- const onChange = (next) => editingCtx.onEdit(column, row, next);
2054
- const editor = column.renderEditCell ? column.renderEditCell(value, row, onChange, error, autoFocus) : renderDefaultEditWidget(column, rowId, value, onChange, error, autoFocus);
2055
- return /* @__PURE__ */ jsx26(
2056
- "div",
2057
- {
2058
- onKeyDown: (event) => {
2059
- if (event.key !== "Escape") return;
2060
- if (!event.currentTarget.contains(event.target)) return;
2061
- event.stopPropagation();
2062
- editingCtx.onEdit(column, row, getColumnValue(column, row));
2063
- },
2064
- children: editor
2065
- }
2066
- );
2067
- }
2068
- return /* @__PURE__ */ jsx26(
2069
- "span",
2070
- {
2071
- role: "button",
2072
- tabIndex: 0,
2073
- "data-testid": `cell-${rowId}-${column.id}`,
2074
- className: "block w-full cursor-text",
2075
- onClick: (event) => {
2076
- stopRowClick(event);
2077
- editingCtx.onActivateRow(rowId, column.id);
2078
- },
2079
- onKeyDown: (event) => {
2080
- if (event.key !== "Enter" && event.key !== " ") return;
2081
- event.preventDefault();
2082
- stopRowClick(event);
2083
- editingCtx.onActivateRow(rowId, column.id);
2084
- },
2085
- children: staticContent(value)
2086
- }
2087
- );
2088
- }
2089
- return staticContent(info.getValue());
2090
- }
2286
+ cell: (info) => renderEditableCell(column, info.row.original, info.getValue(), editingCtxRef.current)
2091
2287
  }));
2092
2288
  }
2093
- function editValuesEqual(a, b) {
2094
- if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
2095
- return a === b;
2096
- }
2097
- function resolveEditLabel(label, changedRowCount, fallback) {
2098
- if (label === void 0) return fallback(changedRowCount);
2099
- return typeof label === "function" ? label(changedRowCount) : label;
2100
- }
2101
- function describeErrorRows(editErrors) {
2102
- const rowIds = [...editErrors.keys()];
2103
- const shown = rowIds.slice(0, 3);
2104
- const suffix = rowIds.length > shown.length ? `, and ${rowIds.length - shown.length} more` : "";
2105
- return `row${rowIds.length === 1 ? "" : "s"} ${shown.join(", ")}${suffix}`;
2106
- }
2107
2289
  function DataGrid({
2108
2290
  columns,
2109
2291
  dataSource,
@@ -2140,19 +2322,19 @@ function DataGrid({
2140
2322
  initialState,
2141
2323
  gridState
2142
2324
  );
2143
- const [internalSelectedIds, setInternalSelectedIds] = useState6(/* @__PURE__ */ new Set());
2325
+ const [internalSelectedIds, setInternalSelectedIds] = useState8(/* @__PURE__ */ new Set());
2144
2326
  const selectedIds = controlledSelectedIds ?? internalSelectedIds;
2145
2327
  function updateSelectedIds(next) {
2146
2328
  setInternalSelectedIds(next);
2147
2329
  onSelectedIdsChange?.(next);
2148
2330
  }
2149
- const [internalColumnSizing, setInternalColumnSizing] = useState6({});
2331
+ const [internalColumnSizing, setInternalColumnSizing] = useState8({});
2150
2332
  const columnSizing = controlledColumnSizing ?? internalColumnSizing;
2151
2333
  function updateColumnSizing(next) {
2152
2334
  setInternalColumnSizing(next);
2153
2335
  onColumnSizingChange?.(next);
2154
2336
  }
2155
- const [expandedIds, setExpandedIds] = useState6(/* @__PURE__ */ new Set());
2337
+ const [expandedIds, setExpandedIds] = useState8(/* @__PURE__ */ new Set());
2156
2338
  function toggleRowExpanded(id) {
2157
2339
  setExpandedIds((prev) => {
2158
2340
  const next = new Set(prev);
@@ -2161,7 +2343,7 @@ function DataGrid({
2161
2343
  return next;
2162
2344
  });
2163
2345
  }
2164
- const [internalExpandedGroups, setInternalExpandedGroups] = useState6({});
2346
+ const [internalExpandedGroups, setInternalExpandedGroups] = useState8({});
2165
2347
  const expandedGroupsState = controlledExpandedGroups ?? internalExpandedGroups;
2166
2348
  function updateExpandedGroups(next) {
2167
2349
  setInternalExpandedGroups(next);
@@ -2173,102 +2355,10 @@ function DataGrid({
2173
2355
  function toggleGroupExpanded(key) {
2174
2356
  updateExpandedGroups({ ...expandedGroupsState, [key]: !isGroupExpanded(key) });
2175
2357
  }
2176
- const [pendingEdits, setPendingEdits] = useState6(/* @__PURE__ */ new Map());
2177
- const [editErrors, setEditErrors] = useState6(/* @__PURE__ */ new Map());
2178
- const hasEditErrors = editErrors.size > 0;
2179
- const [activeRowId, setActiveRowId] = useState6(null);
2180
- const [autoFocusTarget, setAutoFocusTarget] = useState6(null);
2181
- function activateRow(rowId, columnId) {
2182
- setActiveRowId(rowId);
2183
- setAutoFocusTarget({ rowId, columnId });
2184
- }
2185
- useEffect7(() => {
2186
- if (autoFocusTarget) setAutoFocusTarget(null);
2187
- }, [autoFocusTarget]);
2188
- function commitEdit(column, row, value) {
2189
- const rowId = getRowId(row);
2190
- const baseline = getColumnValue(column, row);
2191
- setPendingEdits((prev) => {
2192
- const next = new Map(prev);
2193
- const values = new Map(next.get(rowId)?.values);
2194
- if (editValuesEqual(value, baseline)) values.delete(column.id);
2195
- else values.set(column.id, value);
2196
- if (values.size === 0) next.delete(rowId);
2197
- else next.set(rowId, { row, values });
2198
- return next;
2199
- });
2200
- const message = column.validateEdit?.(value, row);
2201
- setEditErrors((prev) => {
2202
- const next = new Map(prev);
2203
- const rowErrors = new Map(next.get(rowId));
2204
- if (message) rowErrors.set(column.id, message);
2205
- else rowErrors.delete(column.id);
2206
- if (rowErrors.size === 0) next.delete(rowId);
2207
- else next.set(rowId, rowErrors);
2208
- return next;
2209
- });
2210
- }
2211
- async function handleSaveEdits() {
2212
- if (!editing || pendingEdits.size === 0) return;
2213
- const snapshot = pendingEdits;
2214
- const edited = [...snapshot.entries()].map(([rowId, entry]) => ({
2215
- rowId,
2216
- row: entry.row,
2217
- values: Object.fromEntries(entry.values)
2218
- }));
2219
- try {
2220
- await editing.onSave(edited);
2221
- } catch {
2222
- return;
2223
- }
2224
- const snapshotRowIds = new Set(snapshot.keys());
2225
- const survivedRowIds = /* @__PURE__ */ new Set();
2226
- const clearedErrorKeys = [];
2227
- setPendingEdits((prev) => {
2228
- const next = new Map(prev);
2229
- for (const [rowId, snapshotEntry] of snapshot) {
2230
- const current = next.get(rowId);
2231
- if (!current) continue;
2232
- const values = new Map(current.values);
2233
- for (const [columnId, snapshotValue] of snapshotEntry.values) {
2234
- if (values.has(columnId) && editValuesEqual(values.get(columnId), snapshotValue)) {
2235
- values.delete(columnId);
2236
- clearedErrorKeys.push({ rowId, columnId });
2237
- }
2238
- }
2239
- if (values.size === 0) next.delete(rowId);
2240
- else {
2241
- next.set(rowId, { row: current.row, values });
2242
- survivedRowIds.add(rowId);
2243
- }
2244
- }
2245
- return next;
2246
- });
2247
- setEditErrors((prev) => {
2248
- if (clearedErrorKeys.length === 0) return prev;
2249
- const next = new Map(prev);
2250
- for (const { rowId, columnId } of clearedErrorKeys) {
2251
- const rowErrors = next.get(rowId);
2252
- if (!rowErrors) continue;
2253
- const nextRowErrors = new Map(rowErrors);
2254
- nextRowErrors.delete(columnId);
2255
- if (nextRowErrors.size === 0) next.delete(rowId);
2256
- else next.set(rowId, nextRowErrors);
2257
- }
2258
- return next;
2259
- });
2260
- setActiveRowId(
2261
- (prev) => prev !== null && snapshotRowIds.has(prev) && !survivedRowIds.has(prev) ? null : prev
2262
- );
2263
- }
2264
- function handleDiscardEdits() {
2265
- setPendingEdits(/* @__PURE__ */ new Map());
2266
- setEditErrors(/* @__PURE__ */ new Map());
2267
- setActiveRowId(null);
2268
- editing?.onDiscard?.();
2269
- }
2270
- const editingCtxRef = useRef5(void 0);
2271
- editingCtxRef.current = editing ? { pendingEdits, editErrors, activeRowId, autoFocusTarget, getRowId, onEdit: commitEdit, onActivateRow: activateRow } : void 0;
2358
+ const { pendingEdits, editErrors, ctxRef: editingCtxRef, handleSaveEdits, handleDiscardEdits } = useEditingState(
2359
+ editing,
2360
+ getRowId
2361
+ );
2272
2362
  const visibleColumns = useMemo6(
2273
2363
  () => columnVisibility ? columns.filter((column) => isColumnVisible(columnVisibility, column.id)) : columns,
2274
2364
  [columns, columnVisibility]
@@ -2424,8 +2514,8 @@ function DataGrid({
2424
2514
  () => groupBy ? groupRows(tableRows, (row) => groupBy(row.original)) : void 0,
2425
2515
  [tableRows, groupBy]
2426
2516
  );
2427
- const theadRef = useRef5(null);
2428
- const [groupHeaderTop, setGroupHeaderTop] = useState6(0);
2517
+ const theadRef = useRef7(null);
2518
+ const [groupHeaderTop, setGroupHeaderTop] = useState8(0);
2429
2519
  useLayoutEffect(() => {
2430
2520
  if (!groupBy) return;
2431
2521
  const theadEl = theadRef.current;
@@ -2436,7 +2526,7 @@ function DataGrid({
2436
2526
  observer.observe(theadEl);
2437
2527
  return () => observer.disconnect();
2438
2528
  }, [groupBy]);
2439
- const scrollRef = useRef5(null);
2529
+ const scrollRef = useRef7(null);
2440
2530
  const virtualizer = useVirtualizer({
2441
2531
  count: tableRows.length,
2442
2532
  getScrollElement: () => scrollRef.current,
@@ -2448,8 +2538,8 @@ function DataGrid({
2448
2538
  const paddingTop = virtualItems.length > 0 ? virtualItems[0].start : 0;
2449
2539
  const paddingBottom = virtualItems.length > 0 ? virtualizer.getTotalSize() - virtualItems[virtualItems.length - 1].end : 0;
2450
2540
  const lastVisibleIndex = virtualItems.length > 0 ? virtualItems[virtualItems.length - 1].index : -1;
2451
- const notifiedForLengthRef = useRef5(-1);
2452
- useEffect7(() => {
2541
+ const notifiedForLengthRef = useRef7(-1);
2542
+ useEffect9(() => {
2453
2543
  if (!virtualize?.onEndReached) return;
2454
2544
  if (lastVisibleIndex < 0 || lastVisibleIndex < tableRows.length - 1) return;
2455
2545
  if (virtualize.hasMore === false) return;
@@ -2462,20 +2552,20 @@ function DataGrid({
2462
2552
  const rowProps = getRowProps?.(row.original);
2463
2553
  const rowTestId = getRowTestId?.(row.original) ?? `row-${row.id}`;
2464
2554
  const isOddRow = zebra && row.index % 2 === 1;
2465
- return /* @__PURE__ */ jsxs17("tbody", { ref: measureRef, "data-index": row.index, children: [
2466
- /* @__PURE__ */ jsxs17(
2555
+ return /* @__PURE__ */ jsxs18("tbody", { ref: measureRef, "data-index": row.index, children: [
2556
+ /* @__PURE__ */ jsxs18(
2467
2557
  "tr",
2468
2558
  {
2469
2559
  ...rowProps,
2470
2560
  "data-testid": rowTestId,
2471
2561
  className: rowProps?.className ? cn("divide-x divide-border", rowProps.className, isOddRow && "bg-foreground/5") : `divide-x divide-border${isOddRow ? " bg-foreground/5" : ""}`,
2472
2562
  children: [
2473
- showDetailColumn && /* @__PURE__ */ jsx26(
2563
+ showDetailColumn && /* @__PURE__ */ jsx28(
2474
2564
  "td",
2475
2565
  {
2476
2566
  className: isOddRow ? detailCellProps.classNameOdd : detailCellProps.className,
2477
2567
  style: detailCellProps.style,
2478
- children: /* @__PURE__ */ jsx26(
2568
+ children: /* @__PURE__ */ jsx28(
2479
2569
  Button,
2480
2570
  {
2481
2571
  type: "button",
@@ -2487,7 +2577,7 @@ function DataGrid({
2487
2577
  stopRowClick(event);
2488
2578
  toggleRowExpanded(row.id);
2489
2579
  },
2490
- children: /* @__PURE__ */ jsx26(
2580
+ children: /* @__PURE__ */ jsx28(
2491
2581
  ChevronRight,
2492
2582
  {
2493
2583
  className: `h-4 w-4 transition-transform${isExpanded ? " rotate-90" : ""}`,
@@ -2498,12 +2588,12 @@ function DataGrid({
2498
2588
  )
2499
2589
  }
2500
2590
  ),
2501
- showSelectionColumn && /* @__PURE__ */ jsx26(
2591
+ showSelectionColumn && /* @__PURE__ */ jsx28(
2502
2592
  "td",
2503
2593
  {
2504
2594
  className: isOddRow ? selectionCellProps.classNameOdd : selectionCellProps.className,
2505
2595
  style: selectionCellProps.style,
2506
- children: /* @__PURE__ */ jsx26(
2596
+ children: /* @__PURE__ */ jsx28(
2507
2597
  "input",
2508
2598
  {
2509
2599
  type: "checkbox",
@@ -2517,27 +2607,27 @@ function DataGrid({
2517
2607
  ),
2518
2608
  row.getAllCells().map((cell) => {
2519
2609
  const cellProps = bodyCellPropsByColumn.get(cell.column.id);
2520
- return /* @__PURE__ */ jsx26("td", { style: cellProps?.style, className: cellProps?.className ?? BODY_TD_BASE_CLASS, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id);
2610
+ return /* @__PURE__ */ jsx28("td", { style: cellProps?.style, className: cellProps?.className ?? BODY_TD_BASE_CLASS, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id);
2521
2611
  }),
2522
- showRowActionsColumn && rowActions && /* @__PURE__ */ jsx26(
2612
+ showRowActionsColumn && rowActions && /* @__PURE__ */ jsx28(
2523
2613
  "td",
2524
2614
  {
2525
2615
  className: isOddRow ? rowActionsCellProps.classNameOdd : rowActionsCellProps.className,
2526
2616
  style: rowActionsCellProps.style,
2527
- children: /* @__PURE__ */ jsx26(ActionsMenu, { items: rowActions, ctx: { row: row.original }, triggerLabel: `Row actions for ${row.id}` })
2617
+ children: /* @__PURE__ */ jsx28(ActionsMenu, { items: rowActions, ctx: { row: row.original }, triggerLabel: `Row actions for ${row.id}` })
2528
2618
  }
2529
2619
  )
2530
2620
  ]
2531
2621
  }
2532
2622
  ),
2533
- showDetailColumn && isExpanded && renderDetail && /* @__PURE__ */ jsx26("tr", { "data-testid": `${rowTestId}-detail`, children: /* @__PURE__ */ jsx26("td", { colSpan: totalColumnCount, className: "border-b border-border p-2", children: renderDetail(row.original) }) })
2623
+ showDetailColumn && isExpanded && renderDetail && /* @__PURE__ */ jsx28("tr", { "data-testid": `${rowTestId}-detail`, children: /* @__PURE__ */ jsx28("td", { colSpan: totalColumnCount, className: "border-b border-border p-2", children: renderDetail(row.original) }) })
2534
2624
  ] }, row.id);
2535
2625
  }
2536
2626
  function renderGroupedBucket(bucket) {
2537
2627
  const expanded = isGroupExpanded(bucket.key);
2538
2628
  const originalRows = bucket.items.map((row) => row.original);
2539
- return /* @__PURE__ */ jsxs17(Fragment2, { children: [
2540
- /* @__PURE__ */ jsx26("tbody", { children: /* @__PURE__ */ jsx26("tr", { "data-testid": `group-header-${bucket.key}`, children: /* @__PURE__ */ jsx26(
2629
+ return /* @__PURE__ */ jsxs18(Fragment2, { children: [
2630
+ /* @__PURE__ */ jsx28("tbody", { children: /* @__PURE__ */ jsx28("tr", { "data-testid": `group-header-${bucket.key}`, children: /* @__PURE__ */ jsx28(
2541
2631
  "td",
2542
2632
  {
2543
2633
  colSpan: totalColumnCount,
@@ -2546,7 +2636,7 @@ function DataGrid({
2546
2636
  zebra ? "bg-muted" : "bg-background"
2547
2637
  ),
2548
2638
  style: { top: groupHeaderTop },
2549
- children: /* @__PURE__ */ jsxs17(
2639
+ children: /* @__PURE__ */ jsxs18(
2550
2640
  "button",
2551
2641
  {
2552
2642
  type: "button",
@@ -2554,7 +2644,7 @@ function DataGrid({
2554
2644
  onClick: () => toggleGroupExpanded(bucket.key),
2555
2645
  "aria-expanded": expanded,
2556
2646
  children: [
2557
- /* @__PURE__ */ jsx26(
2647
+ /* @__PURE__ */ jsx28(
2558
2648
  ChevronRight,
2559
2649
  {
2560
2650
  className: `h-4 w-4 shrink-0 transition-transform${expanded ? " rotate-90" : ""}`,
@@ -2583,81 +2673,42 @@ function DataGrid({
2583
2673
  // containers (that one scrolling vertically, this component's own
2584
2674
  // `overflow-x-auto` div scrolling horizontally) fighting over which one
2585
2675
  // a sticky header/pinned column is actually stuck relative to.
2586
- /* @__PURE__ */ jsxs17("div", { className: "flex h-full min-h-0 flex-col gap-2", "data-testid": "datagrid", children: [
2587
- editing && pendingEdits.size > 0 && /* @__PURE__ */ jsxs17(
2588
- "div",
2676
+ /* @__PURE__ */ jsxs18("div", { className: "flex h-full min-h-0 flex-col gap-2", "data-testid": "datagrid", children: [
2677
+ editing && pendingEdits.size > 0 && /* @__PURE__ */ jsx28(
2678
+ EditingBar,
2589
2679
  {
2590
- "data-testid": "datagrid-edit-bar",
2591
- className: "flex shrink-0 flex-wrap items-center justify-between gap-2 rounded-md border bg-muted/50 px-3 py-2 text-sm",
2592
- children: [
2593
- /* @__PURE__ */ jsxs17("span", { "aria-live": "polite", children: [
2594
- pendingEdits.size,
2595
- " row",
2596
- pendingEdits.size === 1 ? "" : "s",
2597
- " changed",
2598
- hasEditErrors && /* @__PURE__ */ jsxs17("span", { className: "ml-2 text-destructive", children: [
2599
- "Fix the highlighted errors before saving (",
2600
- describeErrorRows(editErrors),
2601
- ")."
2602
- ] })
2603
- ] }),
2604
- /* @__PURE__ */ jsxs17("div", { className: "flex gap-2", children: [
2605
- /* @__PURE__ */ jsx26(
2606
- Button,
2607
- {
2608
- type: "button",
2609
- variant: "outline",
2610
- size: "sm",
2611
- "data-testid": "datagrid-discard-edits",
2612
- disabled: editing.saving,
2613
- onClick: handleDiscardEdits,
2614
- children: resolveEditLabel(editing.discardLabel, pendingEdits.size, () => "Discard")
2615
- }
2616
- ),
2617
- /* @__PURE__ */ jsx26(
2618
- Button,
2619
- {
2620
- type: "button",
2621
- size: "sm",
2622
- "data-testid": "datagrid-save-edits",
2623
- disabled: editing.saving || hasEditErrors,
2624
- onClick: () => void handleSaveEdits(),
2625
- children: resolveEditLabel(
2626
- editing.saveLabel,
2627
- pendingEdits.size,
2628
- (count) => `Save ${count} change${count === 1 ? "" : "s"}`
2629
- )
2630
- }
2631
- )
2632
- ] })
2633
- ]
2680
+ editing,
2681
+ pendingRowCount: pendingEdits.size,
2682
+ editErrors,
2683
+ onSave: () => void handleSaveEdits(),
2684
+ onDiscard: handleDiscardEdits
2634
2685
  }
2635
2686
  ),
2636
- showSelectionColumn && headerActions && /* @__PURE__ */ jsxs17("div", { className: "flex shrink-0 items-center justify-between text-sm", children: [
2637
- /* @__PURE__ */ jsxs17("span", { children: [
2687
+ showSelectionColumn && headerActions && /* @__PURE__ */ jsxs18("div", { className: "flex shrink-0 items-center justify-between text-sm", children: [
2688
+ /* @__PURE__ */ jsxs18("span", { children: [
2638
2689
  selectedRows.length,
2639
2690
  " selected"
2640
2691
  ] }),
2641
- /* @__PURE__ */ jsx26(ActionsMenu, { items: headerActions, ctx: { selectedRows }, triggerLabel: "Bulk actions" })
2692
+ /* @__PURE__ */ jsx28(ActionsMenu, { items: headerActions, ctx: { selectedRows }, triggerLabel: "Bulk actions" })
2642
2693
  ] }),
2643
- /* @__PURE__ */ jsxs17("div", { className: "relative min-h-0 flex-1", children: [
2644
- loading && /* @__PURE__ */ jsx26(
2694
+ /* @__PURE__ */ jsxs18("div", { className: "relative min-h-0 flex-1", children: [
2695
+ loading && /* @__PURE__ */ jsx28(
2645
2696
  "div",
2646
2697
  {
2647
2698
  "data-testid": "datagrid-loading-overlay",
2648
2699
  className: "absolute inset-0 z-30 flex items-center justify-center bg-background/60",
2649
- children: /* @__PURE__ */ jsx26(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground", "aria-hidden": true })
2700
+ children: /* @__PURE__ */ jsx28(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground", "aria-hidden": true })
2650
2701
  }
2651
2702
  ),
2652
- /* @__PURE__ */ jsx26(
2703
+ /* @__PURE__ */ jsx28(
2653
2704
  "div",
2654
2705
  {
2655
2706
  ref: scrollRef,
2656
2707
  "data-testid": testId,
2657
2708
  className: "h-full overflow-auto rounded-md border",
2658
2709
  style: shouldVirtualize ? { maxHeight: virtualize?.maxBodyHeight ?? 480 } : void 0,
2659
- children: /* @__PURE__ */ jsxs17("table", { className: cn("w-full border-collapse text-sm", enableColumnResizing && "table-fixed"), children: [
2660
- /* @__PURE__ */ jsxs17("thead", { ref: theadRef, className: "bg-muted", children: [
2710
+ children: /* @__PURE__ */ jsxs18("table", { className: cn("w-full border-collapse text-sm", enableColumnResizing && "table-fixed"), children: [
2711
+ /* @__PURE__ */ jsxs18("thead", { ref: theadRef, className: "bg-muted", children: [
2661
2712
  table.getHeaderGroups().map((headerGroup) => {
2662
2713
  const headerById = new Map(headerGroup.headers.map((header) => [header.id, header]));
2663
2714
  function renderLeafHeaderCell(column, rowSpan) {
@@ -2668,9 +2719,9 @@ function DataGrid({
2668
2719
  const filterable = isFilterable(column) && column.filterDisplay !== "row";
2669
2720
  const cellProps = leafHeaderCellPropsByColumn.get(column.id);
2670
2721
  const headerContent = column.renderHeader ? column.renderHeader(column) : column.header;
2671
- return /* @__PURE__ */ jsxs17("th", { rowSpan, style: cellProps?.style, className: cellProps?.className, children: [
2672
- /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-1", children: [
2673
- sortable ? /* @__PURE__ */ jsxs17(
2722
+ return /* @__PURE__ */ jsxs18("th", { rowSpan, style: cellProps?.style, className: cellProps?.className, children: [
2723
+ /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-1", children: [
2724
+ sortable ? /* @__PURE__ */ jsxs18(
2674
2725
  "button",
2675
2726
  {
2676
2727
  type: "button",
@@ -2678,8 +2729,8 @@ function DataGrid({
2678
2729
  onClick: (event) => toggleSort(column.id, enableMultiSort && event.shiftKey, column.sortDescFirst === true),
2679
2730
  children: [
2680
2731
  headerContent,
2681
- sortEntry?.dir === "asc" && /* @__PURE__ */ jsx26(BarsArrowUpIcon, { className: "h-3 w-3", "aria-hidden": true }),
2682
- sortEntry?.dir === "desc" && /* @__PURE__ */ jsx26(BarsArrowDownIcon, { className: "h-3 w-3", "aria-hidden": true })
2732
+ sortEntry?.dir === "asc" && /* @__PURE__ */ jsx28(BarsArrowUpIcon, { className: "h-3 w-3", "aria-hidden": true }),
2733
+ sortEntry?.dir === "desc" && /* @__PURE__ */ jsx28(BarsArrowDownIcon, { className: "h-3 w-3", "aria-hidden": true })
2683
2734
  ]
2684
2735
  }
2685
2736
  ) : (
@@ -2688,10 +2739,10 @@ function DataGrid({
2688
2739
  // silently breaking any interactive content a column's own `renderHeader`
2689
2740
  // embeds directly (bare filter inputs, "select/reject all" buttons) —
2690
2741
  // clicks never reach them at all, even though nothing looks wrong visually.
2691
- /* @__PURE__ */ jsx26("div", { className: "flex items-center gap-1 px-1", children: headerContent })
2742
+ /* @__PURE__ */ jsx28("div", { className: "flex items-center gap-1 px-1", children: headerContent })
2692
2743
  ),
2693
- filterable && /* @__PURE__ */ jsxs17(Popover, { children: [
2694
- /* @__PURE__ */ jsx26(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx26(Button, { variant: "ghost", size: "icon", "aria-label": `Filter ${column.header}`, children: /* @__PURE__ */ jsx26(
2744
+ filterable && /* @__PURE__ */ jsxs18(Popover, { children: [
2745
+ /* @__PURE__ */ jsx28(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx28(Button, { variant: "ghost", size: "icon", "aria-label": `Filter ${column.header}`, children: /* @__PURE__ */ jsx28(
2695
2746
  FunnelIcon7,
2696
2747
  {
2697
2748
  className: cn(
@@ -2701,10 +2752,10 @@ function DataGrid({
2701
2752
  "aria-hidden": true
2702
2753
  }
2703
2754
  ) }) }),
2704
- /* @__PURE__ */ jsx26(PopoverContent, { children: renderFilterWidget(column) })
2755
+ /* @__PURE__ */ jsx28(PopoverContent, { children: renderFilterWidget(column) })
2705
2756
  ] })
2706
2757
  ] }),
2707
- enableColumnResizing && /* @__PURE__ */ jsx26(
2758
+ enableColumnResizing && /* @__PURE__ */ jsx28(
2708
2759
  "div",
2709
2760
  {
2710
2761
  "data-testid": `resize-handle-${column.id}`,
@@ -2715,9 +2766,9 @@ function DataGrid({
2715
2766
  )
2716
2767
  ] }, header.id);
2717
2768
  }
2718
- return /* @__PURE__ */ jsxs17(Fragment2, { children: [
2719
- hasHeaderGroups && /* @__PURE__ */ jsxs17("tr", { "data-testid": "header-group-row", className: "divide-x divide-border", children: [
2720
- showDetailColumn && /* @__PURE__ */ jsx26(
2769
+ return /* @__PURE__ */ jsxs18(Fragment2, { children: [
2770
+ hasHeaderGroups && /* @__PURE__ */ jsxs18("tr", { "data-testid": "header-group-row", className: "divide-x divide-border", children: [
2771
+ showDetailColumn && /* @__PURE__ */ jsx28(
2721
2772
  "th",
2722
2773
  {
2723
2774
  rowSpan: 2,
@@ -2726,13 +2777,13 @@ function DataGrid({
2726
2777
  "aria-hidden": true
2727
2778
  }
2728
2779
  ),
2729
- showSelectionColumn && /* @__PURE__ */ jsx26(
2780
+ showSelectionColumn && /* @__PURE__ */ jsx28(
2730
2781
  "th",
2731
2782
  {
2732
2783
  rowSpan: 2,
2733
2784
  className: selectionHeaderCellProps.className,
2734
2785
  style: selectionHeaderCellProps.style,
2735
- children: /* @__PURE__ */ jsx26(
2786
+ children: /* @__PURE__ */ jsx28(
2736
2787
  "input",
2737
2788
  {
2738
2789
  type: "checkbox",
@@ -2744,7 +2795,7 @@ function DataGrid({
2744
2795
  }
2745
2796
  ),
2746
2797
  headerRuns.map(
2747
- (run) => run.kind === "single" ? renderLeafHeaderCell(run.column, 2) : /* @__PURE__ */ jsx26(
2798
+ (run) => run.kind === "single" ? renderLeafHeaderCell(run.column, 2) : /* @__PURE__ */ jsx28(
2748
2799
  "th",
2749
2800
  {
2750
2801
  colSpan: run.columns.length,
@@ -2754,7 +2805,7 @@ function DataGrid({
2754
2805
  `group-${run.label}-${run.columns[0].id}`
2755
2806
  )
2756
2807
  ),
2757
- showRowActionsColumn && /* @__PURE__ */ jsx26(
2808
+ showRowActionsColumn && /* @__PURE__ */ jsx28(
2758
2809
  "th",
2759
2810
  {
2760
2811
  rowSpan: 2,
@@ -2764,8 +2815,8 @@ function DataGrid({
2764
2815
  }
2765
2816
  )
2766
2817
  ] }, `${headerGroup.id}-groups`),
2767
- /* @__PURE__ */ jsxs17("tr", { className: "divide-x divide-border", children: [
2768
- !hasHeaderGroups && showDetailColumn && /* @__PURE__ */ jsx26(
2818
+ /* @__PURE__ */ jsxs18("tr", { className: "divide-x divide-border", children: [
2819
+ !hasHeaderGroups && showDetailColumn && /* @__PURE__ */ jsx28(
2769
2820
  "th",
2770
2821
  {
2771
2822
  className: detailHeaderCellProps.className,
@@ -2773,12 +2824,12 @@ function DataGrid({
2773
2824
  "aria-hidden": true
2774
2825
  }
2775
2826
  ),
2776
- !hasHeaderGroups && showSelectionColumn && /* @__PURE__ */ jsx26(
2827
+ !hasHeaderGroups && showSelectionColumn && /* @__PURE__ */ jsx28(
2777
2828
  "th",
2778
2829
  {
2779
2830
  className: selectionHeaderCellProps.className,
2780
2831
  style: selectionHeaderCellProps.style,
2781
- children: /* @__PURE__ */ jsx26(
2832
+ children: /* @__PURE__ */ jsx28(
2782
2833
  "input",
2783
2834
  {
2784
2835
  type: "checkbox",
@@ -2794,7 +2845,7 @@ function DataGrid({
2794
2845
  if (!column || hasHeaderGroups && !column.headerGroup) return null;
2795
2846
  return renderLeafHeaderCell(column);
2796
2847
  }),
2797
- !hasHeaderGroups && showRowActionsColumn && /* @__PURE__ */ jsx26(
2848
+ !hasHeaderGroups && showRowActionsColumn && /* @__PURE__ */ jsx28(
2798
2849
  "th",
2799
2850
  {
2800
2851
  className: rowActionsHeaderCellProps.className,
@@ -2805,9 +2856,9 @@ function DataGrid({
2805
2856
  ] }, headerGroup.id)
2806
2857
  ] }, headerGroup.id);
2807
2858
  }),
2808
- hasFilterRow && /* @__PURE__ */ jsxs17("tr", { "data-testid": "filter-row", className: "divide-x divide-border", children: [
2809
- showDetailColumn && /* @__PURE__ */ jsx26("th", { className: detailHeaderCellProps.className, style: detailHeaderCellProps.style }),
2810
- showSelectionColumn && /* @__PURE__ */ jsx26(
2859
+ hasFilterRow && /* @__PURE__ */ jsxs18("tr", { "data-testid": "filter-row", className: "divide-x divide-border", children: [
2860
+ showDetailColumn && /* @__PURE__ */ jsx28("th", { className: detailHeaderCellProps.className, style: detailHeaderCellProps.style }),
2861
+ showSelectionColumn && /* @__PURE__ */ jsx28(
2811
2862
  "th",
2812
2863
  {
2813
2864
  className: selectionHeaderCellProps.className,
@@ -2816,9 +2867,9 @@ function DataGrid({
2816
2867
  ),
2817
2868
  visibleColumns.map((column) => {
2818
2869
  const cellProps = filterHeaderCellPropsByColumn.get(column.id);
2819
- return /* @__PURE__ */ jsx26("th", { style: cellProps?.style, className: cellProps?.className, children: isFilterable(column) && column.filterDisplay === "row" ? renderFilterWidget(column) : null }, column.id);
2870
+ return /* @__PURE__ */ jsx28("th", { style: cellProps?.style, className: cellProps?.className, children: isFilterable(column) && column.filterDisplay === "row" ? renderFilterWidget(column) : null }, column.id);
2820
2871
  }),
2821
- showRowActionsColumn && /* @__PURE__ */ jsx26(
2872
+ showRowActionsColumn && /* @__PURE__ */ jsx28(
2822
2873
  "th",
2823
2874
  {
2824
2875
  className: rowActionsHeaderCellProps.className,
@@ -2827,19 +2878,19 @@ function DataGrid({
2827
2878
  )
2828
2879
  ] })
2829
2880
  ] }),
2830
- rows.length === 0 ? /* @__PURE__ */ jsx26("tbody", { children: /* @__PURE__ */ jsx26("tr", { children: /* @__PURE__ */ jsx26("td", { colSpan: totalColumnCount, className: "p-4 text-center text-muted-foreground", children: loading ? "Loading..." : "No results." }) }) }) : shouldVirtualize ? /* @__PURE__ */ jsxs17(Fragment3, { children: [
2831
- paddingTop > 0 && /* @__PURE__ */ jsx26("tbody", { children: /* @__PURE__ */ jsx26("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx26("td", { colSpan: totalColumnCount, style: { padding: 0, border: "none", height: paddingTop } }) }) }),
2881
+ rows.length === 0 ? /* @__PURE__ */ jsx28("tbody", { children: /* @__PURE__ */ jsx28("tr", { children: /* @__PURE__ */ jsx28("td", { colSpan: totalColumnCount, className: "p-4 text-center text-muted-foreground", children: loading ? "Loading..." : "No results." }) }) }) : shouldVirtualize ? /* @__PURE__ */ jsxs18(Fragment3, { children: [
2882
+ paddingTop > 0 && /* @__PURE__ */ jsx28("tbody", { children: /* @__PURE__ */ jsx28("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx28("td", { colSpan: totalColumnCount, style: { padding: 0, border: "none", height: paddingTop } }) }) }),
2832
2883
  virtualItems.map(
2833
2884
  (virtualItem) => renderRow(tableRows[virtualItem.index], (el) => virtualizer.measureElement(el))
2834
2885
  ),
2835
- paddingBottom > 0 && /* @__PURE__ */ jsx26("tbody", { children: /* @__PURE__ */ jsx26("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx26("td", { colSpan: totalColumnCount, style: { padding: 0, border: "none", height: paddingBottom } }) }) })
2886
+ paddingBottom > 0 && /* @__PURE__ */ jsx28("tbody", { children: /* @__PURE__ */ jsx28("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx28("td", { colSpan: totalColumnCount, style: { padding: 0, border: "none", height: paddingBottom } }) }) })
2836
2887
  ] }) : groupedBuckets ? groupedBuckets.map((bucket) => renderGroupedBucket(bucket)) : tableRows.map((row) => renderRow(row))
2837
2888
  ] })
2838
2889
  }
2839
2890
  )
2840
2891
  ] }),
2841
- showPagination && /* @__PURE__ */ jsxs17("div", { className: "flex shrink-0 items-center justify-between text-sm", children: [
2842
- /* @__PURE__ */ jsxs17("span", { children: [
2892
+ showPagination && /* @__PURE__ */ jsxs18("div", { className: "flex shrink-0 items-center justify-between text-sm", children: [
2893
+ /* @__PURE__ */ jsxs18("span", { children: [
2843
2894
  "Page ",
2844
2895
  state.page + 1,
2845
2896
  " of ",
@@ -2850,8 +2901,8 @@ function DataGrid({
2850
2901
  rowCount === 1 ? "" : "s",
2851
2902
  ")"
2852
2903
  ] }),
2853
- /* @__PURE__ */ jsxs17("div", { className: "flex gap-2", children: [
2854
- /* @__PURE__ */ jsx26(
2904
+ /* @__PURE__ */ jsxs18("div", { className: "flex gap-2", children: [
2905
+ /* @__PURE__ */ jsx28(
2855
2906
  Button,
2856
2907
  {
2857
2908
  variant: "outline",
@@ -2861,7 +2912,7 @@ function DataGrid({
2861
2912
  children: "Previous"
2862
2913
  }
2863
2914
  ),
2864
- /* @__PURE__ */ jsx26(
2915
+ /* @__PURE__ */ jsx28(
2865
2916
  Button,
2866
2917
  {
2867
2918
  variant: "outline",
@@ -2880,7 +2931,7 @@ function DataGrid({
2880
2931
  // src/tree/TreeDataGrid.tsx
2881
2932
  import { useVirtualizer as useVirtualizer2 } from "@tanstack/react-virtual";
2882
2933
  import { AlertCircle, ChevronDown as ChevronDown2, ChevronRight as ChevronRight2, Loader2 as Loader22 } from "lucide-react";
2883
- import { useEffect as useEffect8, useMemo as useMemo8, useRef as useRef6 } from "react";
2934
+ import { useEffect as useEffect10, useMemo as useMemo8, useRef as useRef8 } from "react";
2884
2935
 
2885
2936
  // src/tree/flattenTree.ts
2886
2937
  function flattenTree(roots, accessors, expanded, childrenOverride) {
@@ -2925,7 +2976,7 @@ function computeSelectionStates(roots, accessors, childrenOverride, selectedIds)
2925
2976
  }
2926
2977
 
2927
2978
  // src/tree/useTreeState.ts
2928
- import { useCallback as useCallback3, useMemo as useMemo7, useState as useState7 } from "react";
2979
+ import { useCallback as useCallback3, useMemo as useMemo7, useState as useState9 } from "react";
2929
2980
  function useTreeState({
2930
2981
  data,
2931
2982
  getRowId,
@@ -2937,7 +2988,7 @@ function useTreeState({
2937
2988
  childrenMap: controlledChildrenMap,
2938
2989
  onChildrenMapChange
2939
2990
  }) {
2940
- const [internalExpanded, setInternalExpanded] = useState7({});
2991
+ const [internalExpanded, setInternalExpanded] = useState9({});
2941
2992
  const expanded = controlledExpanded ?? internalExpanded;
2942
2993
  const setExpanded = useCallback3(
2943
2994
  (updater) => {
@@ -2949,7 +3000,7 @@ function useTreeState({
2949
3000
  },
2950
3001
  [controlledExpanded, onExpandedChange]
2951
3002
  );
2952
- const [internalChildrenMap, setInternalChildrenMap] = useState7(/* @__PURE__ */ new Map());
3003
+ const [internalChildrenMap, setInternalChildrenMap] = useState9(/* @__PURE__ */ new Map());
2953
3004
  const childrenMap = controlledChildrenMap ?? internalChildrenMap;
2954
3005
  const setChildrenMap = useCallback3(
2955
3006
  (updater) => {
@@ -2961,8 +3012,8 @@ function useTreeState({
2961
3012
  },
2962
3013
  [controlledChildrenMap, onChildrenMapChange]
2963
3014
  );
2964
- const [loadingIds, setLoadingIds] = useState7(/* @__PURE__ */ new Set());
2965
- const [errorIds, setErrorIds] = useState7(/* @__PURE__ */ new Map());
3015
+ const [loadingIds, setLoadingIds] = useState9(/* @__PURE__ */ new Set());
3016
+ const [errorIds, setErrorIds] = useState9(/* @__PURE__ */ new Map());
2966
3017
  const resolveHasChildren = useMemo7(
2967
3018
  () => hasChildren ?? ((row) => (getChildren(row)?.length ?? 0) > 0),
2968
3019
  [hasChildren, getChildren]
@@ -3042,12 +3093,11 @@ function useTreeState({
3042
3093
  }
3043
3094
 
3044
3095
  // src/tree/TreeDataGrid.tsx
3045
- import { Fragment as Fragment4, jsx as jsx27, jsxs as jsxs18 } from "react/jsx-runtime";
3096
+ import { Fragment as Fragment4, jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
3046
3097
  var EMPTY_SELECTED_IDS = /* @__PURE__ */ new Set();
3047
3098
  var UNCHECKED_STATE = { checked: false, indeterminate: false };
3048
- function renderCell(column, row) {
3049
- const value = getColumnValue(column, row);
3050
- return column.cell ? column.cell(value, row) : defaultFormat(column, value);
3099
+ function renderCell(column, row, editingCtx) {
3100
+ return renderEditableCell(column, row, getColumnValue(column, row), editingCtx);
3051
3101
  }
3052
3102
  function TreeCell({
3053
3103
  flatRow,
@@ -3056,10 +3106,11 @@ function TreeCell({
3056
3106
  isLoading,
3057
3107
  error,
3058
3108
  onToggle,
3059
- onRetry
3109
+ onRetry,
3110
+ editingCtx
3060
3111
  }) {
3061
- return /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-1", style: { paddingLeft: flatRow.depth * indentSize }, children: [
3062
- flatRow.hasChildren ? /* @__PURE__ */ jsx27(
3112
+ return /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-1", style: { paddingLeft: flatRow.depth * indentSize }, children: [
3113
+ flatRow.hasChildren ? /* @__PURE__ */ jsx29(
3063
3114
  "button",
3064
3115
  {
3065
3116
  type: "button",
@@ -3071,14 +3122,14 @@ function TreeCell({
3071
3122
  "aria-label": flatRow.isExpanded ? "Collapse" : "Expand",
3072
3123
  "aria-expanded": flatRow.isExpanded,
3073
3124
  className: "flex h-4 w-4 shrink-0 items-center justify-center text-muted-foreground hover:text-foreground disabled:opacity-50",
3074
- children: isLoading ? /* @__PURE__ */ jsx27(Loader22, { className: "h-3.5 w-3.5 animate-spin", "aria-hidden": "true" }) : flatRow.isExpanded ? /* @__PURE__ */ jsx27(ChevronDown2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) : /* @__PURE__ */ jsx27(ChevronRight2, { className: "h-3.5 w-3.5", "aria-hidden": "true" })
3125
+ children: isLoading ? /* @__PURE__ */ jsx29(Loader22, { className: "h-3.5 w-3.5 animate-spin", "aria-hidden": "true" }) : flatRow.isExpanded ? /* @__PURE__ */ jsx29(ChevronDown2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) : /* @__PURE__ */ jsx29(ChevronRight2, { className: "h-3.5 w-3.5", "aria-hidden": "true" })
3075
3126
  }
3076
- ) : /* @__PURE__ */ jsx27("span", { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
3077
- /* @__PURE__ */ jsx27("span", { className: "truncate", children: renderCell(column, flatRow.row) }),
3078
- flatRow.isExpanded && error ? /* @__PURE__ */ jsxs18("span", { className: "ml-1 flex shrink-0 items-center gap-1 text-xs text-destructive", children: [
3079
- /* @__PURE__ */ jsx27(AlertCircle, { className: "h-3 w-3", "aria-hidden": "true" }),
3127
+ ) : /* @__PURE__ */ jsx29("span", { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
3128
+ /* @__PURE__ */ jsx29("span", { className: "min-w-0 flex-1 truncate", children: renderCell(column, flatRow.row, editingCtx) }),
3129
+ flatRow.isExpanded && error ? /* @__PURE__ */ jsxs19("span", { className: "ml-1 flex shrink-0 items-center gap-1 text-xs text-destructive", children: [
3130
+ /* @__PURE__ */ jsx29(AlertCircle, { className: "h-3 w-3", "aria-hidden": "true" }),
3080
3131
  "Failed to load.",
3081
- /* @__PURE__ */ jsx27("button", { type: "button", className: "underline underline-offset-2", onClick: onRetry, children: "Retry" })
3132
+ /* @__PURE__ */ jsx29("button", { type: "button", className: "underline underline-offset-2", onClick: onRetry, children: "Retry" })
3082
3133
  ] }) : null
3083
3134
  ] });
3084
3135
  }
@@ -3106,12 +3157,14 @@ function TreeDataGrid({
3106
3157
  onSelectedIdsChange,
3107
3158
  getRowSelectionState,
3108
3159
  isRowSelectionDisabled,
3109
- zebra = true
3160
+ zebra = true,
3161
+ editing
3110
3162
  }) {
3111
3163
  const accessors = useMemo8(
3112
3164
  () => ({ getRowId, getChildren, hasChildren }),
3113
3165
  [getRowId, getChildren, hasChildren]
3114
3166
  );
3167
+ const editingState = useEditingState(editing, getRowId);
3115
3168
  const { expanded, childrenMap, loadingIds, errorIds, toggleExpand, retry, expandToLevel } = useTreeState({
3116
3169
  data,
3117
3170
  getRowId,
@@ -3123,8 +3176,8 @@ function TreeDataGrid({
3123
3176
  childrenMap: controlledChildrenMap,
3124
3177
  onChildrenMapChange
3125
3178
  });
3126
- const didAutoExpand = useRef6(false);
3127
- useEffect8(() => {
3179
+ const didAutoExpand = useRef8(false);
3180
+ useEffect10(() => {
3128
3181
  if (didAutoExpand.current) return;
3129
3182
  didAutoExpand.current = true;
3130
3183
  if (initialExpandedLevel > 0) void expandToLevel(initialExpandedLevel);
@@ -3172,7 +3225,7 @@ function TreeDataGrid({
3172
3225
  for (const column of columns) map.set(column.id, `p-2 ${alignClassName(column)}`);
3173
3226
  return map;
3174
3227
  }, [columns]);
3175
- const scrollRef = useRef6(null);
3228
+ const scrollRef = useRef8(null);
3176
3229
  const shouldVirtualize = flatRows.length > virtualizeThreshold;
3177
3230
  const virtualizer = useVirtualizer2({
3178
3231
  count: flatRows.length,
@@ -3185,7 +3238,7 @@ function TreeDataGrid({
3185
3238
  const rowProps = getRowProps?.(flatRow.row, flatRow.depth, index);
3186
3239
  const rowTestId = getRowTestId?.(flatRow.row) ?? `tree-row-${flatRow.id}`;
3187
3240
  const isOddRow = zebra && index % 2 === 1;
3188
- return /* @__PURE__ */ jsxs18(
3241
+ return /* @__PURE__ */ jsxs19(
3189
3242
  "tr",
3190
3243
  {
3191
3244
  ref: measureRef,
@@ -3198,9 +3251,9 @@ function TreeDataGrid({
3198
3251
  isOddRow && "bg-foreground/5"
3199
3252
  ) : `border-b border-border divide-x divide-border${isOddRow ? " bg-foreground/5" : ""}`,
3200
3253
  children: [
3201
- showSelectionColumn && /* @__PURE__ */ jsx27("td", { className: "p-1", style: { width: SELECTION_COLUMN_WIDTH }, children: (() => {
3254
+ showSelectionColumn && /* @__PURE__ */ jsx29("td", { className: "p-1", style: { width: SELECTION_COLUMN_WIDTH }, children: (() => {
3202
3255
  const state = resolveSelectionState(flatRow.row, flatRow.id);
3203
- return /* @__PURE__ */ jsx27(
3256
+ return /* @__PURE__ */ jsx29(
3204
3257
  Checkbox,
3205
3258
  {
3206
3259
  "aria-label": `Select row ${flatRow.id}`,
@@ -3211,7 +3264,7 @@ function TreeDataGrid({
3211
3264
  }
3212
3265
  );
3213
3266
  })() }),
3214
- columns.map((column) => /* @__PURE__ */ jsx27("td", { className: bodyTdClassByColumn.get(column.id), children: column.id === treeColId ? /* @__PURE__ */ jsx27(
3267
+ columns.map((column) => /* @__PURE__ */ jsx29("td", { className: bodyTdClassByColumn.get(column.id), children: column.id === treeColId ? /* @__PURE__ */ jsx29(
3215
3268
  TreeCell,
3216
3269
  {
3217
3270
  flatRow,
@@ -3220,10 +3273,11 @@ function TreeDataGrid({
3220
3273
  isLoading: loadingIds.has(flatRow.id),
3221
3274
  error: errorIds.get(flatRow.id),
3222
3275
  onToggle: () => toggleExpand(flatRow.row),
3223
- onRetry: () => retry(flatRow.row)
3276
+ onRetry: () => retry(flatRow.row),
3277
+ editingCtx: editingState.ctx
3224
3278
  }
3225
- ) : renderCell(column, flatRow.row) }, column.id)),
3226
- showRowActionsColumn && rowActions && /* @__PURE__ */ jsx27("td", { className: "p-2", children: /* @__PURE__ */ jsx27(
3279
+ ) : renderCell(column, flatRow.row, editingState.ctx) }, column.id)),
3280
+ showRowActionsColumn && rowActions && /* @__PURE__ */ jsx29("td", { className: "p-2", children: /* @__PURE__ */ jsx29(
3227
3281
  ActionsMenu,
3228
3282
  {
3229
3283
  items: rowActions,
@@ -3239,49 +3293,62 @@ function TreeDataGrid({
3239
3293
  const virtualItems = shouldVirtualize ? virtualizer.getVirtualItems() : [];
3240
3294
  const paddingTop = virtualItems.length > 0 ? virtualItems[0].start : 0;
3241
3295
  const paddingBottom = virtualItems.length > 0 ? virtualizer.getTotalSize() - virtualItems[virtualItems.length - 1].end : 0;
3242
- return /* @__PURE__ */ jsx27("div", { className: "flex flex-col gap-2", "data-testid": "tree-datagrid", children: /* @__PURE__ */ jsx27(
3243
- "div",
3244
- {
3245
- ref: scrollRef,
3246
- className: shouldVirtualize ? "overflow-y-auto" : void 0,
3247
- style: shouldVirtualize ? { maxHeight: maxBodyHeight } : void 0,
3248
- children: /* @__PURE__ */ jsxs18("table", { className: "w-full border-collapse text-sm", children: [
3249
- /* @__PURE__ */ jsx27("thead", { children: /* @__PURE__ */ jsxs18("tr", { className: "divide-x divide-border", children: [
3250
- showSelectionColumn && /* @__PURE__ */ jsx27("th", { className: "border-b border-border p-1", style: { width: SELECTION_COLUMN_WIDTH }, children: /* @__PURE__ */ jsx27(
3251
- Checkbox,
3252
- {
3253
- "aria-label": "Select all visible rows",
3254
- checked: allVisibleSelected,
3255
- onCheckedChange: toggleAllVisibleSelected
3256
- }
3257
- ) }),
3258
- columns.map((column) => /* @__PURE__ */ jsx27(
3259
- "th",
3260
- {
3261
- style: column.width ? { width: column.width } : void 0,
3262
- className: `border-b border-border p-2 font-medium ${alignClassName(column)}`,
3263
- children: column.header
3264
- },
3265
- column.id
3266
- )),
3267
- showRowActionsColumn && /* @__PURE__ */ jsx27("th", { className: "border-b border-border p-2", "aria-label": "Row actions" })
3268
- ] }) }),
3269
- /* @__PURE__ */ jsx27("tbody", { children: flatRows.length === 0 ? /* @__PURE__ */ jsx27("tr", { children: /* @__PURE__ */ jsx27("td", { colSpan: totalColumnCount, className: "p-4 text-center text-muted-foreground", children: "No results." }) }) : shouldVirtualize ? /* @__PURE__ */ jsxs18(Fragment4, { children: [
3270
- paddingTop > 0 && /* @__PURE__ */ jsx27("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx27("td", { colSpan: totalColumnCount, style: { padding: 0, border: "none", height: paddingTop } }) }),
3271
- virtualItems.map(
3272
- (virtualItem) => renderRow(flatRows[virtualItem.index], virtualItem.index, (el) => virtualizer.measureElement(el))
3273
- ),
3274
- paddingBottom > 0 && /* @__PURE__ */ jsx27("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx27(
3275
- "td",
3276
- {
3277
- colSpan: totalColumnCount,
3278
- style: { padding: 0, border: "none", height: paddingBottom }
3279
- }
3280
- ) })
3281
- ] }) : flatRows.map((flatRow, index) => renderRow(flatRow, index)) })
3282
- ] })
3283
- }
3284
- ) });
3296
+ return /* @__PURE__ */ jsxs19("div", { className: "flex flex-col gap-2", "data-testid": "tree-datagrid", children: [
3297
+ editing && editingState.pendingEdits.size > 0 && /* @__PURE__ */ jsx29(
3298
+ EditingBar,
3299
+ {
3300
+ editing,
3301
+ pendingRowCount: editingState.pendingEdits.size,
3302
+ editErrors: editingState.editErrors,
3303
+ onSave: () => void editingState.handleSaveEdits(),
3304
+ onDiscard: editingState.handleDiscardEdits,
3305
+ testIdPrefix: "tree-datagrid"
3306
+ }
3307
+ ),
3308
+ /* @__PURE__ */ jsx29(
3309
+ "div",
3310
+ {
3311
+ ref: scrollRef,
3312
+ className: shouldVirtualize ? "overflow-y-auto" : void 0,
3313
+ style: shouldVirtualize ? { maxHeight: maxBodyHeight } : void 0,
3314
+ children: /* @__PURE__ */ jsxs19("table", { className: "w-full border-collapse text-sm", children: [
3315
+ /* @__PURE__ */ jsx29("thead", { children: /* @__PURE__ */ jsxs19("tr", { className: "divide-x divide-border", children: [
3316
+ showSelectionColumn && /* @__PURE__ */ jsx29("th", { className: "border-b border-border p-1", style: { width: SELECTION_COLUMN_WIDTH }, children: /* @__PURE__ */ jsx29(
3317
+ Checkbox,
3318
+ {
3319
+ "aria-label": "Select all visible rows",
3320
+ checked: allVisibleSelected,
3321
+ onCheckedChange: toggleAllVisibleSelected
3322
+ }
3323
+ ) }),
3324
+ columns.map((column) => /* @__PURE__ */ jsx29(
3325
+ "th",
3326
+ {
3327
+ style: column.width ? { width: column.width } : void 0,
3328
+ className: `border-b border-border p-2 font-medium ${alignClassName(column)}`,
3329
+ children: column.header
3330
+ },
3331
+ column.id
3332
+ )),
3333
+ showRowActionsColumn && /* @__PURE__ */ jsx29("th", { className: "border-b border-border p-2", "aria-label": "Row actions" })
3334
+ ] }) }),
3335
+ /* @__PURE__ */ jsx29("tbody", { children: flatRows.length === 0 ? /* @__PURE__ */ jsx29("tr", { children: /* @__PURE__ */ jsx29("td", { colSpan: totalColumnCount, className: "p-4 text-center text-muted-foreground", children: "No results." }) }) : shouldVirtualize ? /* @__PURE__ */ jsxs19(Fragment4, { children: [
3336
+ paddingTop > 0 && /* @__PURE__ */ jsx29("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx29("td", { colSpan: totalColumnCount, style: { padding: 0, border: "none", height: paddingTop } }) }),
3337
+ virtualItems.map(
3338
+ (virtualItem) => renderRow(flatRows[virtualItem.index], virtualItem.index, (el) => virtualizer.measureElement(el))
3339
+ ),
3340
+ paddingBottom > 0 && /* @__PURE__ */ jsx29("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx29(
3341
+ "td",
3342
+ {
3343
+ colSpan: totalColumnCount,
3344
+ style: { padding: 0, border: "none", height: paddingBottom }
3345
+ }
3346
+ ) })
3347
+ ] }) : flatRows.map((flatRow, index) => renderRow(flatRow, index)) })
3348
+ ] })
3349
+ }
3350
+ )
3351
+ ] });
3285
3352
  }
3286
3353
  export {
3287
3354
  ActionsMenu,
@@ -3292,6 +3359,7 @@ export {
3292
3359
  DateEditor,
3293
3360
  DateRangeFilter,
3294
3361
  EditFieldError,
3362
+ EditingBar,
3295
3363
  EnumEditor,
3296
3364
  EnumFilter,
3297
3365
  NumberComparisonFilter,
@@ -3324,10 +3392,12 @@ export {
3324
3392
  processClientData,
3325
3393
  renderDefaultEditWidget,
3326
3394
  renderDefaultFilterWidget,
3395
+ renderEditableCell,
3327
3396
  resolveMenuItems,
3328
3397
  storageKeyFor,
3329
3398
  toDate,
3330
3399
  useDebouncedCallback,
3400
+ useEditingState,
3331
3401
  useGridState,
3332
3402
  useTreeState
3333
3403
  };