@bmsuisse/datagrid 0.26.0 → 0.28.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,59 @@ 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, useMemo as useMemo7, useRef as useRef8, useState as useState10 } from "react";
2080
+
2081
+ // src/hooks/useGroupExpansion.ts
2082
+ import { useCallback as useCallback2, useState as useState7 } from "react";
2083
+ function useGroupExpansion(defaultGroupsExpanded, controlledExpandedGroups, onExpandedGroupsChange) {
2084
+ const [internalExpandedGroups, setInternalExpandedGroups] = useState7({});
2085
+ const expandedGroups = controlledExpandedGroups ?? internalExpandedGroups;
2086
+ const updateExpandedGroups = useCallback2(
2087
+ (next) => {
2088
+ setInternalExpandedGroups(next);
2089
+ onExpandedGroupsChange?.(next);
2090
+ },
2091
+ [onExpandedGroupsChange]
2092
+ );
2093
+ const isGroupExpanded = useCallback2(
2094
+ (key) => expandedGroups[key] ?? defaultGroupsExpanded,
2095
+ [expandedGroups, defaultGroupsExpanded]
2096
+ );
2097
+ const toggleGroupExpanded = useCallback2(
2098
+ (key) => {
2099
+ updateExpandedGroups({ ...expandedGroups, [key]: !isGroupExpanded(key) });
2100
+ },
2101
+ [expandedGroups, isGroupExpanded, updateExpandedGroups]
2102
+ );
2103
+ return { expandedGroups, isGroupExpanded, toggleGroupExpanded };
2104
+ }
2105
+
2106
+ // src/hooks/useStickyGroupHeaderTop.ts
2107
+ import { useLayoutEffect, useRef as useRef6, useState as useState8 } from "react";
2108
+ function useStickyGroupHeaderTop(enabled) {
2109
+ const theadRef = useRef6(null);
2110
+ const [groupHeaderTop, setGroupHeaderTop] = useState8(0);
2111
+ useLayoutEffect(() => {
2112
+ if (!enabled) return;
2113
+ const theadEl = theadRef.current;
2114
+ if (!theadEl) return;
2115
+ const updateHeight = () => setGroupHeaderTop(theadEl.getBoundingClientRect().height);
2116
+ updateHeight();
2117
+ const observer = new ResizeObserver(updateHeight);
2118
+ observer.observe(theadEl);
2119
+ return () => observer.disconnect();
2120
+ }, [enabled]);
2121
+ return { theadRef, groupHeaderTop };
2122
+ }
2123
+
2124
+ // src/hooks/useVisibleColumns.ts
2125
+ import { useMemo as useMemo4 } from "react";
2126
+ function useVisibleColumns(columns, columnVisibility) {
2127
+ return useMemo4(
2128
+ () => columnVisibility ? columns.filter((column) => isColumnVisible(columnVisibility, column.id)) : columns,
2129
+ [columns, columnVisibility]
2130
+ );
2131
+ }
1836
2132
 
1837
2133
  // src/lib/structuralColumns.ts
1838
2134
  var SELECTION_COLUMN_WIDTH = 44;
@@ -1875,20 +2171,20 @@ function processClientData(data, state) {
1875
2171
  }
1876
2172
 
1877
2173
  // src/grid/useGridState.ts
1878
- import { useCallback as useCallback2, useEffect as useEffect6, useMemo as useMemo5, useState as useState5 } from "react";
2174
+ import { useCallback as useCallback3, useEffect as useEffect8, useMemo as useMemo6, useState as useState9 } from "react";
1879
2175
 
1880
2176
  // src/hooks/useDebouncedCallback.ts
1881
- import { useEffect as useEffect5, useMemo as useMemo4, useRef as useRef4 } from "react";
2177
+ import { useEffect as useEffect7, useMemo as useMemo5, useRef as useRef7 } from "react";
1882
2178
  function useDebouncedCallback(callback, delayMs) {
1883
- const callbackRef = useRef4(callback);
2179
+ const callbackRef = useRef7(callback);
1884
2180
  callbackRef.current = callback;
1885
- const timeoutRef = useRef4(void 0);
1886
- useEffect5(() => {
2181
+ const timeoutRef = useRef7(void 0);
2182
+ useEffect7(() => {
1887
2183
  return () => {
1888
2184
  clearTimeout(timeoutRef.current);
1889
2185
  };
1890
2186
  }, []);
1891
- return useMemo4(() => {
2187
+ return useMemo5(() => {
1892
2188
  return {
1893
2189
  run: (...args) => {
1894
2190
  clearTimeout(timeoutRef.current);
@@ -1913,10 +2209,10 @@ function nextSortDir(current, descFirst) {
1913
2209
  return void 0;
1914
2210
  }
1915
2211
  function useGridState(dataSource, initialState, externalState) {
1916
- const [state, setState] = useState5(
2212
+ const [state, setState] = useState9(
1917
2213
  () => externalState ?? { ...DEFAULT_STATE, ...initialState }
1918
2214
  );
1919
- const notifyRaw = useCallback2(
2215
+ const notifyRaw = useCallback3(
1920
2216
  (next) => {
1921
2217
  if (dataSource.mode === "server") dataSource.onStateChange(next);
1922
2218
  },
@@ -1926,26 +2222,26 @@ function useGridState(dataSource, initialState, externalState) {
1926
2222
  notifyRaw,
1927
2223
  FILTER_CHANGE_DEBOUNCE_MS
1928
2224
  );
1929
- const notifyNow = useCallback2(
2225
+ const notifyNow = useCallback3(
1930
2226
  (next) => {
1931
2227
  cancelDebouncedNotify();
1932
2228
  notifyRaw(next);
1933
2229
  },
1934
2230
  [notifyRaw, cancelDebouncedNotify]
1935
2231
  );
1936
- useEffect6(() => {
2232
+ useEffect8(() => {
1937
2233
  if (!externalState) return;
1938
2234
  cancelDebouncedNotify();
1939
2235
  setState(externalState);
1940
2236
  }, [externalState, cancelDebouncedNotify]);
1941
- const filtersByColumn = useMemo5(() => {
2237
+ const filtersByColumn = useMemo6(() => {
1942
2238
  const map = /* @__PURE__ */ new Map();
1943
2239
  for (const child of state.filter?.filters ?? []) {
1944
2240
  if ("operator" in child) map.set(fieldKey(child.field), child);
1945
2241
  }
1946
2242
  return map;
1947
2243
  }, [state.filter]);
1948
- const setColumnFilter = useCallback2(
2244
+ const setColumnFilter = useCallback3(
1949
2245
  (columnId, next) => {
1950
2246
  setState((prev) => {
1951
2247
  const remaining = (prev.filter?.filters ?? []).filter(
@@ -1963,7 +2259,7 @@ function useGridState(dataSource, initialState, externalState) {
1963
2259
  },
1964
2260
  [notifyDebounced]
1965
2261
  );
1966
- const toggleSort = useCallback2(
2262
+ const toggleSort = useCallback3(
1967
2263
  (columnId, additive, descFirst = false) => {
1968
2264
  setState((prev) => {
1969
2265
  const existing = prev.sort.find((s) => s.field === columnId);
@@ -1978,7 +2274,7 @@ function useGridState(dataSource, initialState, externalState) {
1978
2274
  },
1979
2275
  [notifyNow]
1980
2276
  );
1981
- const setPage = useCallback2(
2277
+ const setPage = useCallback3(
1982
2278
  (page) => {
1983
2279
  setState((prev) => {
1984
2280
  const nextState = { ...prev, page };
@@ -1988,7 +2284,7 @@ function useGridState(dataSource, initialState, externalState) {
1988
2284
  },
1989
2285
  [notifyNow]
1990
2286
  );
1991
- const setPageSize = useCallback2(
2287
+ const setPageSize = useCallback3(
1992
2288
  (pageSize) => {
1993
2289
  setState((prev) => {
1994
2290
  const nextState = { ...prev, pageSize, page: 0 };
@@ -2002,7 +2298,7 @@ function useGridState(dataSource, initialState, externalState) {
2002
2298
  }
2003
2299
 
2004
2300
  // src/grid/DataGrid.tsx
2005
- import { Fragment as Fragment3, jsx as jsx26, jsxs as jsxs17 } from "react/jsx-runtime";
2301
+ import { Fragment as Fragment3, jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
2006
2302
  var gridTableFeatures = tableFeatures({ columnSizingFeature, columnResizingFeature });
2007
2303
  var DEFAULT_COLUMN_SIZE = 150;
2008
2304
  var DETAIL_COLUMN_WIDTH = 44;
@@ -2039,71 +2335,9 @@ function toTanstackColumns(columns, editingCtxRef) {
2039
2335
  header: column.header,
2040
2336
  size: column.width,
2041
2337
  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
- }
2338
+ cell: (info) => renderEditableCell(column, info.row.original, info.getValue(), editingCtxRef.current)
2091
2339
  }));
2092
2340
  }
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
2341
  function DataGrid({
2108
2342
  columns,
2109
2343
  dataSource,
@@ -2140,19 +2374,19 @@ function DataGrid({
2140
2374
  initialState,
2141
2375
  gridState
2142
2376
  );
2143
- const [internalSelectedIds, setInternalSelectedIds] = useState6(/* @__PURE__ */ new Set());
2377
+ const [internalSelectedIds, setInternalSelectedIds] = useState10(/* @__PURE__ */ new Set());
2144
2378
  const selectedIds = controlledSelectedIds ?? internalSelectedIds;
2145
2379
  function updateSelectedIds(next) {
2146
2380
  setInternalSelectedIds(next);
2147
2381
  onSelectedIdsChange?.(next);
2148
2382
  }
2149
- const [internalColumnSizing, setInternalColumnSizing] = useState6({});
2383
+ const [internalColumnSizing, setInternalColumnSizing] = useState10({});
2150
2384
  const columnSizing = controlledColumnSizing ?? internalColumnSizing;
2151
2385
  function updateColumnSizing(next) {
2152
2386
  setInternalColumnSizing(next);
2153
2387
  onColumnSizingChange?.(next);
2154
2388
  }
2155
- const [expandedIds, setExpandedIds] = useState6(/* @__PURE__ */ new Set());
2389
+ const [expandedIds, setExpandedIds] = useState10(/* @__PURE__ */ new Set());
2156
2390
  function toggleRowExpanded(id) {
2157
2391
  setExpandedIds((prev) => {
2158
2392
  const next = new Set(prev);
@@ -2161,124 +2395,22 @@ function DataGrid({
2161
2395
  return next;
2162
2396
  });
2163
2397
  }
2164
- const [internalExpandedGroups, setInternalExpandedGroups] = useState6({});
2165
- const expandedGroupsState = controlledExpandedGroups ?? internalExpandedGroups;
2166
- function updateExpandedGroups(next) {
2167
- setInternalExpandedGroups(next);
2168
- onExpandedGroupsChange?.(next);
2169
- }
2170
- function isGroupExpanded(key) {
2171
- return expandedGroupsState[key] ?? defaultGroupsExpanded;
2172
- }
2173
- function toggleGroupExpanded(key) {
2174
- updateExpandedGroups({ ...expandedGroupsState, [key]: !isGroupExpanded(key) });
2175
- }
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;
2272
- const visibleColumns = useMemo6(
2273
- () => columnVisibility ? columns.filter((column) => isColumnVisible(columnVisibility, column.id)) : columns,
2274
- [columns, columnVisibility]
2398
+ const { isGroupExpanded, toggleGroupExpanded } = useGroupExpansion(
2399
+ defaultGroupsExpanded,
2400
+ controlledExpandedGroups,
2401
+ onExpandedGroupsChange
2402
+ );
2403
+ const { pendingEdits, editErrors, ctxRef: editingCtxRef, handleSaveEdits, handleDiscardEdits } = useEditingState(
2404
+ editing,
2405
+ getRowId
2275
2406
  );
2407
+ const visibleColumns = useVisibleColumns(columns, columnVisibility);
2276
2408
  const serverRowCount = dataSource.mode === "server" ? dataSource.rowCount : void 0;
2277
- const { rows, rowCount } = useMemo6(() => {
2409
+ const { rows, rowCount } = useMemo7(() => {
2278
2410
  if (dataSource.mode === "client") return processClientData(dataSource.data, state);
2279
2411
  return { rows: dataSource.data, rowCount: serverRowCount ?? 0 };
2280
2412
  }, [dataSource.mode, dataSource.data, serverRowCount, state]);
2281
- const selectedRows = useMemo6(
2413
+ const selectedRows = useMemo7(
2282
2414
  () => rows.filter((row) => selectedIds.has(getRowId(row))),
2283
2415
  [rows, selectedIds, getRowId]
2284
2416
  );
@@ -2297,8 +2429,8 @@ function DataGrid({
2297
2429
  }
2298
2430
  updateSelectedIds(next);
2299
2431
  }
2300
- const tanstackColumns = useMemo6(() => toTanstackColumns(visibleColumns, editingCtxRef), [visibleColumns]);
2301
- const columnById = useMemo6(() => {
2432
+ const tanstackColumns = useMemo7(() => toTanstackColumns(visibleColumns, editingCtxRef), [visibleColumns]);
2433
+ const columnById = useMemo7(() => {
2302
2434
  const map = /* @__PURE__ */ new Map();
2303
2435
  for (const column of visibleColumns) map.set(column.id, column);
2304
2436
  return map;
@@ -2323,7 +2455,7 @@ function DataGrid({
2323
2455
  const showDetailColumn = Boolean(renderDetail);
2324
2456
  const totalColumnCount = visibleColumns.length + (showDetailColumn ? 1 : 0) + (showSelectionColumn ? 1 : 0) + (showRowActionsColumn ? 1 : 0);
2325
2457
  const hasFilterRow = visibleColumns.some((column) => isFilterable(column) && column.filterDisplay === "row");
2326
- const headerRuns = useMemo6(() => computeHeaderRuns(visibleColumns), [visibleColumns]);
2458
+ const headerRuns = useMemo7(() => computeHeaderRuns(visibleColumns), [visibleColumns]);
2327
2459
  const hasHeaderGroups = visibleColumns.some((column) => column.headerGroup);
2328
2460
  function renderFilterWidget(column) {
2329
2461
  const value = filtersByColumn.get(column.id);
@@ -2333,11 +2465,11 @@ function DataGrid({
2333
2465
  const columnSize = (columnId) => table.getColumn(columnId)?.getSize() ?? DEFAULT_COLUMN_SIZE;
2334
2466
  const leadingStructuralWidth = (showDetailColumn ? DETAIL_COLUMN_WIDTH : 0) + (showSelectionColumn ? SELECTION_COLUMN_WIDTH : 0);
2335
2467
  const trailingStructuralWidth = showRowActionsColumn ? ROW_ACTIONS_COLUMN_WIDTH : 0;
2336
- const leftPinnedOffsets = useMemo6(
2468
+ const leftPinnedOffsets = useMemo7(
2337
2469
  () => pinnedOffsets(visibleColumns, "left", columnSize, leadingStructuralWidth),
2338
2470
  [visibleColumns, columnSizing, leadingStructuralWidth]
2339
2471
  );
2340
- const rightPinnedOffsets = useMemo6(
2472
+ const rightPinnedOffsets = useMemo7(
2341
2473
  () => pinnedOffsets(visibleColumns, "right", columnSize, trailingStructuralWidth),
2342
2474
  [visibleColumns, columnSizing, trailingStructuralWidth]
2343
2475
  );
@@ -2385,7 +2517,7 @@ function DataGrid({
2385
2517
  style: pinnedProps?.style
2386
2518
  };
2387
2519
  }
2388
- const bodyCellPropsByColumn = useMemo6(() => {
2520
+ const bodyCellPropsByColumn = useMemo7(() => {
2389
2521
  const map = /* @__PURE__ */ new Map();
2390
2522
  for (const column of visibleColumns) map.set(column.id, bodyCellClassAndStyle(column));
2391
2523
  return map;
@@ -2401,7 +2533,7 @@ function DataGrid({
2401
2533
  );
2402
2534
  return { className, style: pinnedProps.style };
2403
2535
  }
2404
- const leafHeaderCellPropsByColumn = useMemo6(() => {
2536
+ const leafHeaderCellPropsByColumn = useMemo7(() => {
2405
2537
  const map = /* @__PURE__ */ new Map();
2406
2538
  for (const column of visibleColumns) {
2407
2539
  map.set(
@@ -2411,7 +2543,7 @@ function DataGrid({
2411
2543
  }
2412
2544
  return map;
2413
2545
  }, [visibleColumns, enableColumnResizing, leftPinnedOffsets, rightPinnedOffsets, columnSizing]);
2414
- const filterHeaderCellPropsByColumn = useMemo6(() => {
2546
+ const filterHeaderCellPropsByColumn = useMemo7(() => {
2415
2547
  const map = /* @__PURE__ */ new Map();
2416
2548
  for (const column of visibleColumns) {
2417
2549
  map.set(column.id, headerCellClassAndStyle(column, "border-b border-border p-2 align-top font-normal"));
@@ -2420,23 +2552,12 @@ function DataGrid({
2420
2552
  }, [visibleColumns, enableColumnResizing, leftPinnedOffsets, rightPinnedOffsets, columnSizing]);
2421
2553
  const tableRows = table.getRowModel().rows;
2422
2554
  const shouldVirtualize = Boolean(virtualize) && !groupBy && tableRows.length > (virtualize?.threshold ?? 100);
2423
- const groupedBuckets = useMemo6(
2555
+ const groupedBuckets = useMemo7(
2424
2556
  () => groupBy ? groupRows(tableRows, (row) => groupBy(row.original)) : void 0,
2425
2557
  [tableRows, groupBy]
2426
2558
  );
2427
- const theadRef = useRef5(null);
2428
- const [groupHeaderTop, setGroupHeaderTop] = useState6(0);
2429
- useLayoutEffect(() => {
2430
- if (!groupBy) return;
2431
- const theadEl = theadRef.current;
2432
- if (!theadEl) return;
2433
- const updateHeight = () => setGroupHeaderTop(theadEl.getBoundingClientRect().height);
2434
- updateHeight();
2435
- const observer = new ResizeObserver(updateHeight);
2436
- observer.observe(theadEl);
2437
- return () => observer.disconnect();
2438
- }, [groupBy]);
2439
- const scrollRef = useRef5(null);
2559
+ const { theadRef, groupHeaderTop } = useStickyGroupHeaderTop(Boolean(groupBy));
2560
+ const scrollRef = useRef8(null);
2440
2561
  const virtualizer = useVirtualizer({
2441
2562
  count: tableRows.length,
2442
2563
  getScrollElement: () => scrollRef.current,
@@ -2448,8 +2569,8 @@ function DataGrid({
2448
2569
  const paddingTop = virtualItems.length > 0 ? virtualItems[0].start : 0;
2449
2570
  const paddingBottom = virtualItems.length > 0 ? virtualizer.getTotalSize() - virtualItems[virtualItems.length - 1].end : 0;
2450
2571
  const lastVisibleIndex = virtualItems.length > 0 ? virtualItems[virtualItems.length - 1].index : -1;
2451
- const notifiedForLengthRef = useRef5(-1);
2452
- useEffect7(() => {
2572
+ const notifiedForLengthRef = useRef8(-1);
2573
+ useEffect9(() => {
2453
2574
  if (!virtualize?.onEndReached) return;
2454
2575
  if (lastVisibleIndex < 0 || lastVisibleIndex < tableRows.length - 1) return;
2455
2576
  if (virtualize.hasMore === false) return;
@@ -2462,20 +2583,20 @@ function DataGrid({
2462
2583
  const rowProps = getRowProps?.(row.original);
2463
2584
  const rowTestId = getRowTestId?.(row.original) ?? `row-${row.id}`;
2464
2585
  const isOddRow = zebra && row.index % 2 === 1;
2465
- return /* @__PURE__ */ jsxs17("tbody", { ref: measureRef, "data-index": row.index, children: [
2466
- /* @__PURE__ */ jsxs17(
2586
+ return /* @__PURE__ */ jsxs18("tbody", { ref: measureRef, "data-index": row.index, children: [
2587
+ /* @__PURE__ */ jsxs18(
2467
2588
  "tr",
2468
2589
  {
2469
2590
  ...rowProps,
2470
2591
  "data-testid": rowTestId,
2471
2592
  className: rowProps?.className ? cn("divide-x divide-border", rowProps.className, isOddRow && "bg-foreground/5") : `divide-x divide-border${isOddRow ? " bg-foreground/5" : ""}`,
2472
2593
  children: [
2473
- showDetailColumn && /* @__PURE__ */ jsx26(
2594
+ showDetailColumn && /* @__PURE__ */ jsx28(
2474
2595
  "td",
2475
2596
  {
2476
2597
  className: isOddRow ? detailCellProps.classNameOdd : detailCellProps.className,
2477
2598
  style: detailCellProps.style,
2478
- children: /* @__PURE__ */ jsx26(
2599
+ children: /* @__PURE__ */ jsx28(
2479
2600
  Button,
2480
2601
  {
2481
2602
  type: "button",
@@ -2487,7 +2608,7 @@ function DataGrid({
2487
2608
  stopRowClick(event);
2488
2609
  toggleRowExpanded(row.id);
2489
2610
  },
2490
- children: /* @__PURE__ */ jsx26(
2611
+ children: /* @__PURE__ */ jsx28(
2491
2612
  ChevronRight,
2492
2613
  {
2493
2614
  className: `h-4 w-4 transition-transform${isExpanded ? " rotate-90" : ""}`,
@@ -2498,12 +2619,12 @@ function DataGrid({
2498
2619
  )
2499
2620
  }
2500
2621
  ),
2501
- showSelectionColumn && /* @__PURE__ */ jsx26(
2622
+ showSelectionColumn && /* @__PURE__ */ jsx28(
2502
2623
  "td",
2503
2624
  {
2504
2625
  className: isOddRow ? selectionCellProps.classNameOdd : selectionCellProps.className,
2505
2626
  style: selectionCellProps.style,
2506
- children: /* @__PURE__ */ jsx26(
2627
+ children: /* @__PURE__ */ jsx28(
2507
2628
  "input",
2508
2629
  {
2509
2630
  type: "checkbox",
@@ -2517,27 +2638,27 @@ function DataGrid({
2517
2638
  ),
2518
2639
  row.getAllCells().map((cell) => {
2519
2640
  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);
2641
+ 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
2642
  }),
2522
- showRowActionsColumn && rowActions && /* @__PURE__ */ jsx26(
2643
+ showRowActionsColumn && rowActions && /* @__PURE__ */ jsx28(
2523
2644
  "td",
2524
2645
  {
2525
2646
  className: isOddRow ? rowActionsCellProps.classNameOdd : rowActionsCellProps.className,
2526
2647
  style: rowActionsCellProps.style,
2527
- children: /* @__PURE__ */ jsx26(ActionsMenu, { items: rowActions, ctx: { row: row.original }, triggerLabel: `Row actions for ${row.id}` })
2648
+ children: /* @__PURE__ */ jsx28(ActionsMenu, { items: rowActions, ctx: { row: row.original }, triggerLabel: `Row actions for ${row.id}` })
2528
2649
  }
2529
2650
  )
2530
2651
  ]
2531
2652
  }
2532
2653
  ),
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) }) })
2654
+ 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
2655
  ] }, row.id);
2535
2656
  }
2536
2657
  function renderGroupedBucket(bucket) {
2537
2658
  const expanded = isGroupExpanded(bucket.key);
2538
2659
  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(
2660
+ return /* @__PURE__ */ jsxs18(Fragment2, { children: [
2661
+ /* @__PURE__ */ jsx28("tbody", { children: /* @__PURE__ */ jsx28("tr", { "data-testid": `group-header-${bucket.key}`, children: /* @__PURE__ */ jsx28(
2541
2662
  "td",
2542
2663
  {
2543
2664
  colSpan: totalColumnCount,
@@ -2546,7 +2667,7 @@ function DataGrid({
2546
2667
  zebra ? "bg-muted" : "bg-background"
2547
2668
  ),
2548
2669
  style: { top: groupHeaderTop },
2549
- children: /* @__PURE__ */ jsxs17(
2670
+ children: /* @__PURE__ */ jsxs18(
2550
2671
  "button",
2551
2672
  {
2552
2673
  type: "button",
@@ -2554,7 +2675,7 @@ function DataGrid({
2554
2675
  onClick: () => toggleGroupExpanded(bucket.key),
2555
2676
  "aria-expanded": expanded,
2556
2677
  children: [
2557
- /* @__PURE__ */ jsx26(
2678
+ /* @__PURE__ */ jsx28(
2558
2679
  ChevronRight,
2559
2680
  {
2560
2681
  className: `h-4 w-4 shrink-0 transition-transform${expanded ? " rotate-90" : ""}`,
@@ -2583,81 +2704,42 @@ function DataGrid({
2583
2704
  // containers (that one scrolling vertically, this component's own
2584
2705
  // `overflow-x-auto` div scrolling horizontally) fighting over which one
2585
2706
  // 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",
2707
+ /* @__PURE__ */ jsxs18("div", { className: "flex h-full min-h-0 flex-col gap-2", "data-testid": "datagrid", children: [
2708
+ editing && pendingEdits.size > 0 && /* @__PURE__ */ jsx28(
2709
+ EditingBar,
2589
2710
  {
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
- ]
2711
+ editing,
2712
+ pendingRowCount: pendingEdits.size,
2713
+ editErrors,
2714
+ onSave: () => void handleSaveEdits(),
2715
+ onDiscard: handleDiscardEdits
2634
2716
  }
2635
2717
  ),
2636
- showSelectionColumn && headerActions && /* @__PURE__ */ jsxs17("div", { className: "flex shrink-0 items-center justify-between text-sm", children: [
2637
- /* @__PURE__ */ jsxs17("span", { children: [
2718
+ showSelectionColumn && headerActions && /* @__PURE__ */ jsxs18("div", { className: "flex shrink-0 items-center justify-between text-sm", children: [
2719
+ /* @__PURE__ */ jsxs18("span", { children: [
2638
2720
  selectedRows.length,
2639
2721
  " selected"
2640
2722
  ] }),
2641
- /* @__PURE__ */ jsx26(ActionsMenu, { items: headerActions, ctx: { selectedRows }, triggerLabel: "Bulk actions" })
2723
+ /* @__PURE__ */ jsx28(ActionsMenu, { items: headerActions, ctx: { selectedRows }, triggerLabel: "Bulk actions" })
2642
2724
  ] }),
2643
- /* @__PURE__ */ jsxs17("div", { className: "relative min-h-0 flex-1", children: [
2644
- loading && /* @__PURE__ */ jsx26(
2725
+ /* @__PURE__ */ jsxs18("div", { className: "relative min-h-0 flex-1", children: [
2726
+ loading && /* @__PURE__ */ jsx28(
2645
2727
  "div",
2646
2728
  {
2647
2729
  "data-testid": "datagrid-loading-overlay",
2648
2730
  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 })
2731
+ children: /* @__PURE__ */ jsx28(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground", "aria-hidden": true })
2650
2732
  }
2651
2733
  ),
2652
- /* @__PURE__ */ jsx26(
2734
+ /* @__PURE__ */ jsx28(
2653
2735
  "div",
2654
2736
  {
2655
2737
  ref: scrollRef,
2656
2738
  "data-testid": testId,
2657
2739
  className: "h-full overflow-auto rounded-md border",
2658
2740
  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: [
2741
+ children: /* @__PURE__ */ jsxs18("table", { className: cn("w-full border-collapse text-sm", enableColumnResizing && "table-fixed"), children: [
2742
+ /* @__PURE__ */ jsxs18("thead", { ref: theadRef, className: "bg-muted", children: [
2661
2743
  table.getHeaderGroups().map((headerGroup) => {
2662
2744
  const headerById = new Map(headerGroup.headers.map((header) => [header.id, header]));
2663
2745
  function renderLeafHeaderCell(column, rowSpan) {
@@ -2668,9 +2750,9 @@ function DataGrid({
2668
2750
  const filterable = isFilterable(column) && column.filterDisplay !== "row";
2669
2751
  const cellProps = leafHeaderCellPropsByColumn.get(column.id);
2670
2752
  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(
2753
+ return /* @__PURE__ */ jsxs18("th", { rowSpan, style: cellProps?.style, className: cellProps?.className, children: [
2754
+ /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-1", children: [
2755
+ sortable ? /* @__PURE__ */ jsxs18(
2674
2756
  "button",
2675
2757
  {
2676
2758
  type: "button",
@@ -2678,8 +2760,8 @@ function DataGrid({
2678
2760
  onClick: (event) => toggleSort(column.id, enableMultiSort && event.shiftKey, column.sortDescFirst === true),
2679
2761
  children: [
2680
2762
  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 })
2763
+ sortEntry?.dir === "asc" && /* @__PURE__ */ jsx28(BarsArrowUpIcon, { className: "h-3 w-3", "aria-hidden": true }),
2764
+ sortEntry?.dir === "desc" && /* @__PURE__ */ jsx28(BarsArrowDownIcon, { className: "h-3 w-3", "aria-hidden": true })
2683
2765
  ]
2684
2766
  }
2685
2767
  ) : (
@@ -2688,10 +2770,10 @@ function DataGrid({
2688
2770
  // silently breaking any interactive content a column's own `renderHeader`
2689
2771
  // embeds directly (bare filter inputs, "select/reject all" buttons) —
2690
2772
  // 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 })
2773
+ /* @__PURE__ */ jsx28("div", { className: "flex items-center gap-1 px-1", children: headerContent })
2692
2774
  ),
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(
2775
+ filterable && /* @__PURE__ */ jsxs18(Popover, { children: [
2776
+ /* @__PURE__ */ jsx28(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx28(Button, { variant: "ghost", size: "icon", "aria-label": `Filter ${column.header}`, children: /* @__PURE__ */ jsx28(
2695
2777
  FunnelIcon7,
2696
2778
  {
2697
2779
  className: cn(
@@ -2701,10 +2783,10 @@ function DataGrid({
2701
2783
  "aria-hidden": true
2702
2784
  }
2703
2785
  ) }) }),
2704
- /* @__PURE__ */ jsx26(PopoverContent, { children: renderFilterWidget(column) })
2786
+ /* @__PURE__ */ jsx28(PopoverContent, { children: renderFilterWidget(column) })
2705
2787
  ] })
2706
2788
  ] }),
2707
- enableColumnResizing && /* @__PURE__ */ jsx26(
2789
+ enableColumnResizing && /* @__PURE__ */ jsx28(
2708
2790
  "div",
2709
2791
  {
2710
2792
  "data-testid": `resize-handle-${column.id}`,
@@ -2715,9 +2797,9 @@ function DataGrid({
2715
2797
  )
2716
2798
  ] }, header.id);
2717
2799
  }
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(
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(
2721
2803
  "th",
2722
2804
  {
2723
2805
  rowSpan: 2,
@@ -2726,13 +2808,13 @@ function DataGrid({
2726
2808
  "aria-hidden": true
2727
2809
  }
2728
2810
  ),
2729
- showSelectionColumn && /* @__PURE__ */ jsx26(
2811
+ showSelectionColumn && /* @__PURE__ */ jsx28(
2730
2812
  "th",
2731
2813
  {
2732
2814
  rowSpan: 2,
2733
2815
  className: selectionHeaderCellProps.className,
2734
2816
  style: selectionHeaderCellProps.style,
2735
- children: /* @__PURE__ */ jsx26(
2817
+ children: /* @__PURE__ */ jsx28(
2736
2818
  "input",
2737
2819
  {
2738
2820
  type: "checkbox",
@@ -2744,7 +2826,7 @@ function DataGrid({
2744
2826
  }
2745
2827
  ),
2746
2828
  headerRuns.map(
2747
- (run) => run.kind === "single" ? renderLeafHeaderCell(run.column, 2) : /* @__PURE__ */ jsx26(
2829
+ (run) => run.kind === "single" ? renderLeafHeaderCell(run.column, 2) : /* @__PURE__ */ jsx28(
2748
2830
  "th",
2749
2831
  {
2750
2832
  colSpan: run.columns.length,
@@ -2754,7 +2836,7 @@ function DataGrid({
2754
2836
  `group-${run.label}-${run.columns[0].id}`
2755
2837
  )
2756
2838
  ),
2757
- showRowActionsColumn && /* @__PURE__ */ jsx26(
2839
+ showRowActionsColumn && /* @__PURE__ */ jsx28(
2758
2840
  "th",
2759
2841
  {
2760
2842
  rowSpan: 2,
@@ -2764,8 +2846,8 @@ function DataGrid({
2764
2846
  }
2765
2847
  )
2766
2848
  ] }, `${headerGroup.id}-groups`),
2767
- /* @__PURE__ */ jsxs17("tr", { className: "divide-x divide-border", children: [
2768
- !hasHeaderGroups && showDetailColumn && /* @__PURE__ */ jsx26(
2849
+ /* @__PURE__ */ jsxs18("tr", { className: "divide-x divide-border", children: [
2850
+ !hasHeaderGroups && showDetailColumn && /* @__PURE__ */ jsx28(
2769
2851
  "th",
2770
2852
  {
2771
2853
  className: detailHeaderCellProps.className,
@@ -2773,12 +2855,12 @@ function DataGrid({
2773
2855
  "aria-hidden": true
2774
2856
  }
2775
2857
  ),
2776
- !hasHeaderGroups && showSelectionColumn && /* @__PURE__ */ jsx26(
2858
+ !hasHeaderGroups && showSelectionColumn && /* @__PURE__ */ jsx28(
2777
2859
  "th",
2778
2860
  {
2779
2861
  className: selectionHeaderCellProps.className,
2780
2862
  style: selectionHeaderCellProps.style,
2781
- children: /* @__PURE__ */ jsx26(
2863
+ children: /* @__PURE__ */ jsx28(
2782
2864
  "input",
2783
2865
  {
2784
2866
  type: "checkbox",
@@ -2794,7 +2876,7 @@ function DataGrid({
2794
2876
  if (!column || hasHeaderGroups && !column.headerGroup) return null;
2795
2877
  return renderLeafHeaderCell(column);
2796
2878
  }),
2797
- !hasHeaderGroups && showRowActionsColumn && /* @__PURE__ */ jsx26(
2879
+ !hasHeaderGroups && showRowActionsColumn && /* @__PURE__ */ jsx28(
2798
2880
  "th",
2799
2881
  {
2800
2882
  className: rowActionsHeaderCellProps.className,
@@ -2805,9 +2887,9 @@ function DataGrid({
2805
2887
  ] }, headerGroup.id)
2806
2888
  ] }, headerGroup.id);
2807
2889
  }),
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(
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(
2811
2893
  "th",
2812
2894
  {
2813
2895
  className: selectionHeaderCellProps.className,
@@ -2816,9 +2898,9 @@ function DataGrid({
2816
2898
  ),
2817
2899
  visibleColumns.map((column) => {
2818
2900
  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);
2901
+ return /* @__PURE__ */ jsx28("th", { style: cellProps?.style, className: cellProps?.className, children: isFilterable(column) && column.filterDisplay === "row" ? renderFilterWidget(column) : null }, column.id);
2820
2902
  }),
2821
- showRowActionsColumn && /* @__PURE__ */ jsx26(
2903
+ showRowActionsColumn && /* @__PURE__ */ jsx28(
2822
2904
  "th",
2823
2905
  {
2824
2906
  className: rowActionsHeaderCellProps.className,
@@ -2827,19 +2909,19 @@ function DataGrid({
2827
2909
  )
2828
2910
  ] })
2829
2911
  ] }),
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 } }) }) }),
2912
+ 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: [
2913
+ 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
2914
  virtualItems.map(
2833
2915
  (virtualItem) => renderRow(tableRows[virtualItem.index], (el) => virtualizer.measureElement(el))
2834
2916
  ),
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 } }) }) })
2917
+ 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
2918
  ] }) : groupedBuckets ? groupedBuckets.map((bucket) => renderGroupedBucket(bucket)) : tableRows.map((row) => renderRow(row))
2837
2919
  ] })
2838
2920
  }
2839
2921
  )
2840
2922
  ] }),
2841
- showPagination && /* @__PURE__ */ jsxs17("div", { className: "flex shrink-0 items-center justify-between text-sm", children: [
2842
- /* @__PURE__ */ jsxs17("span", { children: [
2923
+ showPagination && /* @__PURE__ */ jsxs18("div", { className: "flex shrink-0 items-center justify-between text-sm", children: [
2924
+ /* @__PURE__ */ jsxs18("span", { children: [
2843
2925
  "Page ",
2844
2926
  state.page + 1,
2845
2927
  " of ",
@@ -2850,8 +2932,8 @@ function DataGrid({
2850
2932
  rowCount === 1 ? "" : "s",
2851
2933
  ")"
2852
2934
  ] }),
2853
- /* @__PURE__ */ jsxs17("div", { className: "flex gap-2", children: [
2854
- /* @__PURE__ */ jsx26(
2935
+ /* @__PURE__ */ jsxs18("div", { className: "flex gap-2", children: [
2936
+ /* @__PURE__ */ jsx28(
2855
2937
  Button,
2856
2938
  {
2857
2939
  variant: "outline",
@@ -2861,7 +2943,7 @@ function DataGrid({
2861
2943
  children: "Previous"
2862
2944
  }
2863
2945
  ),
2864
- /* @__PURE__ */ jsx26(
2946
+ /* @__PURE__ */ jsx28(
2865
2947
  Button,
2866
2948
  {
2867
2949
  variant: "outline",
@@ -2880,7 +2962,7 @@ function DataGrid({
2880
2962
  // src/tree/TreeDataGrid.tsx
2881
2963
  import { useVirtualizer as useVirtualizer2 } from "@tanstack/react-virtual";
2882
2964
  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";
2965
+ import { Fragment as Fragment4, useEffect as useEffect10, useMemo as useMemo9, useRef as useRef9 } from "react";
2884
2966
 
2885
2967
  // src/tree/flattenTree.ts
2886
2968
  function flattenTree(roots, accessors, expanded, childrenOverride) {
@@ -2925,7 +3007,7 @@ function computeSelectionStates(roots, accessors, childrenOverride, selectedIds)
2925
3007
  }
2926
3008
 
2927
3009
  // src/tree/useTreeState.ts
2928
- import { useCallback as useCallback3, useMemo as useMemo7, useState as useState7 } from "react";
3010
+ import { useCallback as useCallback4, useMemo as useMemo8, useState as useState11 } from "react";
2929
3011
  function useTreeState({
2930
3012
  data,
2931
3013
  getRowId,
@@ -2937,9 +3019,9 @@ function useTreeState({
2937
3019
  childrenMap: controlledChildrenMap,
2938
3020
  onChildrenMapChange
2939
3021
  }) {
2940
- const [internalExpanded, setInternalExpanded] = useState7({});
3022
+ const [internalExpanded, setInternalExpanded] = useState11({});
2941
3023
  const expanded = controlledExpanded ?? internalExpanded;
2942
- const setExpanded = useCallback3(
3024
+ const setExpanded = useCallback4(
2943
3025
  (updater) => {
2944
3026
  if (controlledExpanded) {
2945
3027
  onExpandedChange?.(updater(controlledExpanded));
@@ -2949,9 +3031,9 @@ function useTreeState({
2949
3031
  },
2950
3032
  [controlledExpanded, onExpandedChange]
2951
3033
  );
2952
- const [internalChildrenMap, setInternalChildrenMap] = useState7(/* @__PURE__ */ new Map());
3034
+ const [internalChildrenMap, setInternalChildrenMap] = useState11(/* @__PURE__ */ new Map());
2953
3035
  const childrenMap = controlledChildrenMap ?? internalChildrenMap;
2954
- const setChildrenMap = useCallback3(
3036
+ const setChildrenMap = useCallback4(
2955
3037
  (updater) => {
2956
3038
  if (controlledChildrenMap) {
2957
3039
  onChildrenMapChange?.(updater(controlledChildrenMap));
@@ -2961,13 +3043,13 @@ function useTreeState({
2961
3043
  },
2962
3044
  [controlledChildrenMap, onChildrenMapChange]
2963
3045
  );
2964
- const [loadingIds, setLoadingIds] = useState7(/* @__PURE__ */ new Set());
2965
- const [errorIds, setErrorIds] = useState7(/* @__PURE__ */ new Map());
2966
- const resolveHasChildren = useMemo7(
3046
+ const [loadingIds, setLoadingIds] = useState11(/* @__PURE__ */ new Set());
3047
+ const [errorIds, setErrorIds] = useState11(/* @__PURE__ */ new Map());
3048
+ const resolveHasChildren = useMemo8(
2967
3049
  () => hasChildren ?? ((row) => (getChildren(row)?.length ?? 0) > 0),
2968
3050
  [hasChildren, getChildren]
2969
3051
  );
2970
- const loadChildrenFor = useCallback3(
3052
+ const loadChildrenFor = useCallback4(
2971
3053
  async (row) => {
2972
3054
  const id = getRowId(row);
2973
3055
  const cached = childrenMap.get(id);
@@ -3000,7 +3082,7 @@ function useTreeState({
3000
3082
  },
3001
3083
  [childrenMap, getChildren, getRowId, onLoadChildren, setChildrenMap]
3002
3084
  );
3003
- const toggleExpand = useCallback3(
3085
+ const toggleExpand = useCallback4(
3004
3086
  (row) => {
3005
3087
  const id = getRowId(row);
3006
3088
  const willExpand = !expanded[id];
@@ -3012,14 +3094,14 @@ function useTreeState({
3012
3094
  },
3013
3095
  [expanded, getRowId, loadChildrenFor, resolveHasChildren, setExpanded]
3014
3096
  );
3015
- const retry = useCallback3(
3097
+ const retry = useCallback4(
3016
3098
  (row) => {
3017
3099
  loadChildrenFor(row).catch(() => {
3018
3100
  });
3019
3101
  },
3020
3102
  [loadChildrenFor]
3021
3103
  );
3022
- const expandToLevel = useCallback3(
3104
+ const expandToLevel = useCallback4(
3023
3105
  async (level) => {
3024
3106
  const nextExpanded = { ...expanded };
3025
3107
  async function visit(row, depth) {
@@ -3042,12 +3124,11 @@ function useTreeState({
3042
3124
  }
3043
3125
 
3044
3126
  // src/tree/TreeDataGrid.tsx
3045
- import { Fragment as Fragment4, jsx as jsx27, jsxs as jsxs18 } from "react/jsx-runtime";
3127
+ import { Fragment as Fragment5, jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
3046
3128
  var EMPTY_SELECTED_IDS = /* @__PURE__ */ new Set();
3047
3129
  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);
3130
+ function renderCell(column, row, editingCtx) {
3131
+ return renderEditableCell(column, row, getColumnValue(column, row), editingCtx);
3051
3132
  }
3052
3133
  function TreeCell({
3053
3134
  flatRow,
@@ -3056,10 +3137,11 @@ function TreeCell({
3056
3137
  isLoading,
3057
3138
  error,
3058
3139
  onToggle,
3059
- onRetry
3140
+ onRetry,
3141
+ editingCtx
3060
3142
  }) {
3061
- return /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-1", style: { paddingLeft: flatRow.depth * indentSize }, children: [
3062
- flatRow.hasChildren ? /* @__PURE__ */ jsx27(
3143
+ return /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-1", style: { paddingLeft: flatRow.depth * indentSize }, children: [
3144
+ flatRow.hasChildren ? /* @__PURE__ */ jsx29(
3063
3145
  "button",
3064
3146
  {
3065
3147
  type: "button",
@@ -3071,14 +3153,14 @@ function TreeCell({
3071
3153
  "aria-label": flatRow.isExpanded ? "Collapse" : "Expand",
3072
3154
  "aria-expanded": flatRow.isExpanded,
3073
3155
  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" })
3156
+ 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
3157
  }
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" }),
3158
+ ) : /* @__PURE__ */ jsx29("span", { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
3159
+ /* @__PURE__ */ jsx29("span", { className: "min-w-0 flex-1 truncate", children: renderCell(column, flatRow.row, editingCtx) }),
3160
+ flatRow.isExpanded && error ? /* @__PURE__ */ jsxs19("span", { className: "ml-1 flex shrink-0 items-center gap-1 text-xs text-destructive", children: [
3161
+ /* @__PURE__ */ jsx29(AlertCircle, { className: "h-3 w-3", "aria-hidden": "true" }),
3080
3162
  "Failed to load.",
3081
- /* @__PURE__ */ jsx27("button", { type: "button", className: "underline underline-offset-2", onClick: onRetry, children: "Retry" })
3163
+ /* @__PURE__ */ jsx29("button", { type: "button", className: "underline underline-offset-2", onClick: onRetry, children: "Retry" })
3082
3164
  ] }) : null
3083
3165
  ] });
3084
3166
  }
@@ -3086,6 +3168,7 @@ function TreeDataGrid({
3086
3168
  columns,
3087
3169
  data,
3088
3170
  treeColumnId,
3171
+ columnVisibility,
3089
3172
  getRowId,
3090
3173
  getChildren,
3091
3174
  hasChildren,
@@ -3106,12 +3189,19 @@ function TreeDataGrid({
3106
3189
  onSelectedIdsChange,
3107
3190
  getRowSelectionState,
3108
3191
  isRowSelectionDisabled,
3109
- zebra = true
3192
+ groupBy,
3193
+ renderGroupHeader,
3194
+ defaultGroupsExpanded = true,
3195
+ expandedGroups: controlledExpandedGroups,
3196
+ onExpandedGroupsChange,
3197
+ zebra = true,
3198
+ editing
3110
3199
  }) {
3111
- const accessors = useMemo8(
3200
+ const accessors = useMemo9(
3112
3201
  () => ({ getRowId, getChildren, hasChildren }),
3113
3202
  [getRowId, getChildren, hasChildren]
3114
3203
  );
3204
+ const editingState = useEditingState(editing, getRowId);
3115
3205
  const { expanded, childrenMap, loadingIds, errorIds, toggleExpand, retry, expandToLevel } = useTreeState({
3116
3206
  data,
3117
3207
  getRowId,
@@ -3123,21 +3213,48 @@ function TreeDataGrid({
3123
3213
  childrenMap: controlledChildrenMap,
3124
3214
  onChildrenMapChange
3125
3215
  });
3126
- const didAutoExpand = useRef6(false);
3127
- useEffect8(() => {
3216
+ const didAutoExpand = useRef9(false);
3217
+ useEffect10(() => {
3128
3218
  if (didAutoExpand.current) return;
3129
3219
  didAutoExpand.current = true;
3130
3220
  if (initialExpandedLevel > 0) void expandToLevel(initialExpandedLevel);
3131
3221
  }, []);
3132
- const flatRows = useMemo8(
3133
- () => flattenTree(data, accessors, expanded, childrenMap),
3134
- [data, accessors, expanded, childrenMap]
3222
+ const visibleColumns = useVisibleColumns(columns, columnVisibility);
3223
+ const { isGroupExpanded, toggleGroupExpanded } = useGroupExpansion(
3224
+ defaultGroupsExpanded,
3225
+ controlledExpandedGroups,
3226
+ onExpandedGroupsChange
3135
3227
  );
3228
+ const flatRows = useMemo9(
3229
+ () => groupBy ? [] : flattenTree(data, accessors, expanded, childrenMap),
3230
+ [groupBy, data, accessors, expanded, childrenMap]
3231
+ );
3232
+ const groupedBuckets = useMemo9(
3233
+ () => groupBy ? groupRows(data, groupBy).map((bucket) => ({
3234
+ key: bucket.key,
3235
+ roots: bucket.items,
3236
+ flatRows: flattenTree(bucket.items, accessors, expanded, childrenMap)
3237
+ })) : void 0,
3238
+ [groupBy, data, accessors, expanded, childrenMap]
3239
+ );
3240
+ const groupedRender = useMemo9(() => {
3241
+ if (!groupedBuckets) return void 0;
3242
+ const startIndexByBucket = /* @__PURE__ */ new Map();
3243
+ const rows = [];
3244
+ for (const bucket of groupedBuckets) {
3245
+ if (!isGroupExpanded(bucket.key)) continue;
3246
+ startIndexByBucket.set(bucket.key, rows.length);
3247
+ rows.push(...bucket.flatRows);
3248
+ }
3249
+ return { startIndexByBucket, rows };
3250
+ }, [groupedBuckets, isGroupExpanded]);
3251
+ const allFlatRows = groupedRender ? groupedRender.rows : flatRows;
3136
3252
  const treeColId = treeColumnId ?? columns[0]?.id;
3137
3253
  const showRowActionsColumn = Boolean(rowActions?.length);
3138
3254
  const showSelectionColumn = controlledSelectedIds !== void 0;
3139
- const totalColumnCount = columns.length + (showRowActionsColumn ? 1 : 0) + (showSelectionColumn ? 1 : 0);
3140
- const selectionStates = useMemo8(
3255
+ const totalColumnCount = visibleColumns.length + (showRowActionsColumn ? 1 : 0) + (showSelectionColumn ? 1 : 0);
3256
+ const { theadRef, groupHeaderTop } = useStickyGroupHeaderTop(Boolean(groupBy));
3257
+ const selectionStates = useMemo9(
3141
3258
  () => showSelectionColumn ? computeSelectionStates(data, accessors, childrenMap, controlledSelectedIds ?? EMPTY_SELECTED_IDS) : void 0,
3142
3259
  [showSelectionColumn, data, accessors, childrenMap, controlledSelectedIds]
3143
3260
  );
@@ -3153,9 +3270,9 @@ function TreeDataGrid({
3153
3270
  else next.add(id);
3154
3271
  onSelectedIdsChange?.(next);
3155
3272
  }
3156
- const selectableFlatRows = useMemo8(
3157
- () => flatRows.filter((flatRow) => !(isRowSelectionDisabled?.(flatRow.row) ?? false)),
3158
- [flatRows, isRowSelectionDisabled]
3273
+ const selectableFlatRows = useMemo9(
3274
+ () => allFlatRows.filter((flatRow) => !(isRowSelectionDisabled?.(flatRow.row) ?? false)),
3275
+ [allFlatRows, isRowSelectionDisabled]
3159
3276
  );
3160
3277
  const allVisibleSelected = showSelectionColumn && selectableFlatRows.length > 0 && selectableFlatRows.every((flatRow) => controlledSelectedIds.has(flatRow.id));
3161
3278
  function toggleAllVisibleSelected() {
@@ -3167,13 +3284,13 @@ function TreeDataGrid({
3167
3284
  }
3168
3285
  onSelectedIdsChange?.(next);
3169
3286
  }
3170
- const bodyTdClassByColumn = useMemo8(() => {
3287
+ const bodyTdClassByColumn = useMemo9(() => {
3171
3288
  const map = /* @__PURE__ */ new Map();
3172
- for (const column of columns) map.set(column.id, `p-2 ${alignClassName(column)}`);
3289
+ for (const column of visibleColumns) map.set(column.id, `p-2 ${alignClassName(column)}`);
3173
3290
  return map;
3174
- }, [columns]);
3175
- const scrollRef = useRef6(null);
3176
- const shouldVirtualize = flatRows.length > virtualizeThreshold;
3291
+ }, [visibleColumns]);
3292
+ const scrollRef = useRef9(null);
3293
+ const shouldVirtualize = !groupBy && flatRows.length > virtualizeThreshold;
3177
3294
  const virtualizer = useVirtualizer2({
3178
3295
  count: flatRows.length,
3179
3296
  getScrollElement: () => scrollRef.current,
@@ -3185,7 +3302,7 @@ function TreeDataGrid({
3185
3302
  const rowProps = getRowProps?.(flatRow.row, flatRow.depth, index);
3186
3303
  const rowTestId = getRowTestId?.(flatRow.row) ?? `tree-row-${flatRow.id}`;
3187
3304
  const isOddRow = zebra && index % 2 === 1;
3188
- return /* @__PURE__ */ jsxs18(
3305
+ return /* @__PURE__ */ jsxs19(
3189
3306
  "tr",
3190
3307
  {
3191
3308
  ref: measureRef,
@@ -3198,9 +3315,9 @@ function TreeDataGrid({
3198
3315
  isOddRow && "bg-foreground/5"
3199
3316
  ) : `border-b border-border divide-x divide-border${isOddRow ? " bg-foreground/5" : ""}`,
3200
3317
  children: [
3201
- showSelectionColumn && /* @__PURE__ */ jsx27("td", { className: "p-1", style: { width: SELECTION_COLUMN_WIDTH }, children: (() => {
3318
+ showSelectionColumn && /* @__PURE__ */ jsx29("td", { className: "p-1", style: { width: SELECTION_COLUMN_WIDTH }, children: (() => {
3202
3319
  const state = resolveSelectionState(flatRow.row, flatRow.id);
3203
- return /* @__PURE__ */ jsx27(
3320
+ return /* @__PURE__ */ jsx29(
3204
3321
  Checkbox,
3205
3322
  {
3206
3323
  "aria-label": `Select row ${flatRow.id}`,
@@ -3211,7 +3328,7 @@ function TreeDataGrid({
3211
3328
  }
3212
3329
  );
3213
3330
  })() }),
3214
- columns.map((column) => /* @__PURE__ */ jsx27("td", { className: bodyTdClassByColumn.get(column.id), children: column.id === treeColId ? /* @__PURE__ */ jsx27(
3331
+ visibleColumns.map((column) => /* @__PURE__ */ jsx29("td", { className: bodyTdClassByColumn.get(column.id), children: column.id === treeColId ? /* @__PURE__ */ jsx29(
3215
3332
  TreeCell,
3216
3333
  {
3217
3334
  flatRow,
@@ -3220,10 +3337,11 @@ function TreeDataGrid({
3220
3337
  isLoading: loadingIds.has(flatRow.id),
3221
3338
  error: errorIds.get(flatRow.id),
3222
3339
  onToggle: () => toggleExpand(flatRow.row),
3223
- onRetry: () => retry(flatRow.row)
3340
+ onRetry: () => retry(flatRow.row),
3341
+ editingCtx: editingState.ctx
3224
3342
  }
3225
- ) : renderCell(column, flatRow.row) }, column.id)),
3226
- showRowActionsColumn && rowActions && /* @__PURE__ */ jsx27("td", { className: "p-2", children: /* @__PURE__ */ jsx27(
3343
+ ) : renderCell(column, flatRow.row, editingState.ctx) }, column.id)),
3344
+ showRowActionsColumn && rowActions && /* @__PURE__ */ jsx29("td", { className: "p-2", children: /* @__PURE__ */ jsx29(
3227
3345
  ActionsMenu,
3228
3346
  {
3229
3347
  items: rowActions,
@@ -3239,49 +3357,100 @@ function TreeDataGrid({
3239
3357
  const virtualItems = shouldVirtualize ? virtualizer.getVirtualItems() : [];
3240
3358
  const paddingTop = virtualItems.length > 0 ? virtualItems[0].start : 0;
3241
3359
  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
- ) });
3360
+ return /* @__PURE__ */ jsxs19("div", { className: "flex flex-col gap-2", "data-testid": "tree-datagrid", children: [
3361
+ editing && editingState.pendingEdits.size > 0 && /* @__PURE__ */ jsx29(
3362
+ EditingBar,
3363
+ {
3364
+ editing,
3365
+ pendingRowCount: editingState.pendingEdits.size,
3366
+ editErrors: editingState.editErrors,
3367
+ onSave: () => void editingState.handleSaveEdits(),
3368
+ onDiscard: editingState.handleDiscardEdits,
3369
+ testIdPrefix: "tree-datagrid"
3370
+ }
3371
+ ),
3372
+ /* @__PURE__ */ jsx29(
3373
+ "div",
3374
+ {
3375
+ ref: scrollRef,
3376
+ className: shouldVirtualize ? "overflow-y-auto" : void 0,
3377
+ style: shouldVirtualize ? { maxHeight: maxBodyHeight } : void 0,
3378
+ children: /* @__PURE__ */ jsxs19("table", { className: "w-full border-collapse text-sm", children: [
3379
+ /* @__PURE__ */ jsx29("thead", { ref: theadRef, children: /* @__PURE__ */ jsxs19("tr", { className: "divide-x divide-border", children: [
3380
+ showSelectionColumn && /* @__PURE__ */ jsx29("th", { className: "border-b border-border p-1", style: { width: SELECTION_COLUMN_WIDTH }, children: /* @__PURE__ */ jsx29(
3381
+ Checkbox,
3382
+ {
3383
+ "aria-label": "Select all visible rows",
3384
+ checked: allVisibleSelected,
3385
+ onCheckedChange: toggleAllVisibleSelected
3386
+ }
3387
+ ) }),
3388
+ visibleColumns.map((column) => /* @__PURE__ */ jsx29(
3389
+ "th",
3390
+ {
3391
+ style: column.width ? { width: column.width } : void 0,
3392
+ className: `border-b border-border p-2 font-medium ${alignClassName(column)}`,
3393
+ children: column.header
3394
+ },
3395
+ column.id
3396
+ )),
3397
+ showRowActionsColumn && /* @__PURE__ */ jsx29("th", { className: "border-b border-border p-2", "aria-label": "Row actions" })
3398
+ ] }) }),
3399
+ /* @__PURE__ */ jsx29("tbody", { children: data.length === 0 ? /* @__PURE__ */ jsx29("tr", { children: /* @__PURE__ */ jsx29("td", { colSpan: totalColumnCount, className: "p-4 text-center text-muted-foreground", children: "No results." }) }) : groupedBuckets ? groupedBuckets.map((bucket) => {
3400
+ const groupExpanded = isGroupExpanded(bucket.key);
3401
+ return /* @__PURE__ */ jsxs19(Fragment4, { children: [
3402
+ /* @__PURE__ */ jsx29("tr", { "data-testid": `group-header-${bucket.key}`, children: /* @__PURE__ */ jsx29(
3403
+ "td",
3404
+ {
3405
+ colSpan: totalColumnCount,
3406
+ className: cn(
3407
+ "sticky z-20 border-b p-2 font-medium",
3408
+ zebra ? "bg-muted" : "bg-background"
3409
+ ),
3410
+ style: { top: groupHeaderTop },
3411
+ children: /* @__PURE__ */ jsxs19(
3412
+ "button",
3413
+ {
3414
+ type: "button",
3415
+ className: "flex w-full items-center gap-1 text-left",
3416
+ onClick: () => toggleGroupExpanded(bucket.key),
3417
+ "aria-expanded": groupExpanded,
3418
+ children: [
3419
+ /* @__PURE__ */ jsx29(
3420
+ ChevronRight2,
3421
+ {
3422
+ className: `h-4 w-4 shrink-0 transition-transform${groupExpanded ? " rotate-90" : ""}`,
3423
+ "aria-hidden": true
3424
+ }
3425
+ ),
3426
+ renderGroupHeader ? renderGroupHeader(bucket.key, bucket.roots, groupExpanded) : `${bucket.key} (${bucket.roots.length})`
3427
+ ]
3428
+ }
3429
+ )
3430
+ }
3431
+ ) }),
3432
+ groupExpanded && (() => {
3433
+ const startIndex = groupedRender?.startIndexByBucket.get(bucket.key) ?? 0;
3434
+ return bucket.flatRows.map((flatRow, i) => renderRow(flatRow, startIndex + i));
3435
+ })()
3436
+ ] }, `group-${bucket.key}`);
3437
+ }) : shouldVirtualize ? /* @__PURE__ */ jsxs19(Fragment5, { children: [
3438
+ paddingTop > 0 && /* @__PURE__ */ jsx29("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx29("td", { colSpan: totalColumnCount, style: { padding: 0, border: "none", height: paddingTop } }) }),
3439
+ virtualItems.map(
3440
+ (virtualItem) => renderRow(flatRows[virtualItem.index], virtualItem.index, (el) => virtualizer.measureElement(el))
3441
+ ),
3442
+ paddingBottom > 0 && /* @__PURE__ */ jsx29("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx29(
3443
+ "td",
3444
+ {
3445
+ colSpan: totalColumnCount,
3446
+ style: { padding: 0, border: "none", height: paddingBottom }
3447
+ }
3448
+ ) })
3449
+ ] }) : flatRows.map((flatRow, index) => renderRow(flatRow, index)) })
3450
+ ] })
3451
+ }
3452
+ )
3453
+ ] });
3285
3454
  }
3286
3455
  export {
3287
3456
  ActionsMenu,
@@ -3292,6 +3461,7 @@ export {
3292
3461
  DateEditor,
3293
3462
  DateRangeFilter,
3294
3463
  EditFieldError,
3464
+ EditingBar,
3295
3465
  EnumEditor,
3296
3466
  EnumFilter,
3297
3467
  NumberComparisonFilter,
@@ -3324,11 +3494,16 @@ export {
3324
3494
  processClientData,
3325
3495
  renderDefaultEditWidget,
3326
3496
  renderDefaultFilterWidget,
3497
+ renderEditableCell,
3327
3498
  resolveMenuItems,
3328
3499
  storageKeyFor,
3329
3500
  toDate,
3330
3501
  useDebouncedCallback,
3502
+ useEditingState,
3331
3503
  useGridState,
3332
- useTreeState
3504
+ useGroupExpansion,
3505
+ useStickyGroupHeaderTop,
3506
+ useTreeState,
3507
+ useVisibleColumns
3333
3508
  };
3334
3509
  //# sourceMappingURL=index.js.map