@bmsuisse/datagrid 0.25.3 → 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
@@ -1235,6 +1235,10 @@ function isSortable(column) {
1235
1235
  function isFilterable(column) {
1236
1236
  return column.filterable === true;
1237
1237
  }
1238
+ function isEditable(column, row) {
1239
+ if (!column.editable) return false;
1240
+ return typeof column.editable === "function" ? column.editable(row) : true;
1241
+ }
1238
1242
 
1239
1243
  // src/filter/facetedValues.ts
1240
1244
  function withoutColumn(filter, columnId) {
@@ -1365,18 +1369,458 @@ function renderDefaultFilterWidget(column, value, onChange) {
1365
1369
  }
1366
1370
  }
1367
1371
 
1372
+ // src/edit/widget-types.ts
1373
+ function editErrorId(rowId, columnId) {
1374
+ return `edit-error-${rowId}-${columnId}`;
1375
+ }
1376
+
1377
+ // src/edit/EditFieldError.tsx
1378
+ import { jsx as jsx15 } from "react/jsx-runtime";
1379
+ function EditFieldError({ id, message }) {
1380
+ return /* @__PURE__ */ jsx15("span", { id, className: "mt-0.5 block text-xs text-destructive", children: message });
1381
+ }
1382
+
1383
+ // src/edit/BooleanEditor.tsx
1384
+ import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
1385
+ function toBoolean(value) {
1386
+ if (typeof value === "boolean") return value;
1387
+ if (typeof value === "string") return value.trim().toLowerCase() === "true";
1388
+ if (typeof value === "number") return value !== 0;
1389
+ return false;
1390
+ }
1391
+ function BooleanEditor({
1392
+ column,
1393
+ rowId,
1394
+ value,
1395
+ onChange,
1396
+ error,
1397
+ autoFocus
1398
+ }) {
1399
+ const errorId = editErrorId(rowId, column.id);
1400
+ return (
1401
+ // No `gap-*` here (unlike a plain flex stack) — `EditFieldError`'s own
1402
+ // `mt-0.5` already provides the same spacing every other editor gets;
1403
+ // adding both would double it for this one editor only.
1404
+ /* @__PURE__ */ jsxs9("div", { className: "flex flex-col items-center", children: [
1405
+ /* @__PURE__ */ jsx16(
1406
+ Checkbox,
1407
+ {
1408
+ "aria-label": `Edit ${column.header}`,
1409
+ "aria-invalid": error ? true : void 0,
1410
+ "aria-describedby": error ? errorId : void 0,
1411
+ "data-testid": `edit-${rowId}-${column.id}`,
1412
+ autoFocus,
1413
+ checked: toBoolean(value),
1414
+ onCheckedChange: (next) => onChange(next === true)
1415
+ }
1416
+ ),
1417
+ error && /* @__PURE__ */ jsx16(EditFieldError, { id: errorId, message: error })
1418
+ ] })
1419
+ );
1420
+ }
1421
+
1422
+ // src/edit/DateEditor.tsx
1423
+ import { format as formatDate, parseISO as parseISO4 } from "date-fns";
1424
+ import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
1425
+ function inputValueOf(value, columnType) {
1426
+ const date = toDate(value);
1427
+ if (!date) return "";
1428
+ return columnType === "datetime" ? formatDate(date, "yyyy-MM-dd'T'HH:mm:ss") : formatDate(date, "yyyy-MM-dd");
1429
+ }
1430
+ function DateEditor({
1431
+ column,
1432
+ rowId,
1433
+ value,
1434
+ onChange,
1435
+ error,
1436
+ autoFocus
1437
+ }) {
1438
+ const errorId = editErrorId(rowId, column.id);
1439
+ return /* @__PURE__ */ jsxs10("div", { children: [
1440
+ /* @__PURE__ */ jsx17(
1441
+ Input,
1442
+ {
1443
+ type: column.type === "datetime" ? "datetime-local" : "date",
1444
+ step: column.type === "datetime" ? 1 : void 0,
1445
+ className: "h-8",
1446
+ "aria-label": `Edit ${column.header}`,
1447
+ "aria-invalid": error ? true : void 0,
1448
+ "aria-describedby": error ? errorId : void 0,
1449
+ "data-testid": `edit-${rowId}-${column.id}`,
1450
+ autoFocus,
1451
+ value: inputValueOf(value, column.type),
1452
+ onChange: (event) => onChange(event.target.value === "" ? null : parseISO4(event.target.value))
1453
+ }
1454
+ ),
1455
+ error && /* @__PURE__ */ jsx17(EditFieldError, { id: errorId, message: error })
1456
+ ] });
1457
+ }
1458
+
1459
+ // src/edit/EnumEditor.tsx
1460
+ import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
1461
+ function EnumEditor({
1462
+ column,
1463
+ rowId,
1464
+ value,
1465
+ onChange,
1466
+ error,
1467
+ autoFocus
1468
+ }) {
1469
+ const errorId = editErrorId(rowId, column.id);
1470
+ return /* @__PURE__ */ jsxs11("div", { children: [
1471
+ /* @__PURE__ */ jsxs11(Select, { value: value === null || value === void 0 ? "" : String(value), onValueChange: onChange, children: [
1472
+ /* @__PURE__ */ jsx18(
1473
+ SelectTrigger,
1474
+ {
1475
+ className: "h-8",
1476
+ "aria-label": `Edit ${column.header}`,
1477
+ "aria-invalid": error ? true : void 0,
1478
+ "aria-describedby": error ? errorId : void 0,
1479
+ "data-testid": `edit-${rowId}-${column.id}`,
1480
+ autoFocus,
1481
+ children: /* @__PURE__ */ jsx18(SelectValue, {})
1482
+ }
1483
+ ),
1484
+ /* @__PURE__ */ jsx18(SelectContent, { children: column.options.map((option) => /* @__PURE__ */ jsx18(SelectItem, { value: option.value, children: option.label }, option.value)) })
1485
+ ] }),
1486
+ error && /* @__PURE__ */ jsx18(EditFieldError, { id: errorId, message: error })
1487
+ ] });
1488
+ }
1489
+
1490
+ // src/edit/NumberEditor.tsx
1491
+ import { useEffect as useEffect4, useRef as useRef4, useState as useState5 } from "react";
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
+ }
1496
+ function NumberEditor({
1497
+ column,
1498
+ rowId,
1499
+ value,
1500
+ onChange,
1501
+ error,
1502
+ autoFocus
1503
+ }) {
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]);
1512
+ return /* @__PURE__ */ jsxs12("div", { children: [
1513
+ /* @__PURE__ */ jsx19(
1514
+ Input,
1515
+ {
1516
+ type: "number",
1517
+ className: "h-8",
1518
+ "aria-label": `Edit ${column.header}`,
1519
+ "aria-invalid": error ? true : void 0,
1520
+ "aria-describedby": error ? errorId : void 0,
1521
+ "data-testid": `edit-${rowId}-${column.id}`,
1522
+ autoFocus,
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
+ }
1530
+ }
1531
+ ),
1532
+ error && /* @__PURE__ */ jsx19(EditFieldError, { id: errorId, message: error })
1533
+ ] });
1534
+ }
1535
+
1536
+ // src/edit/StringEditor.tsx
1537
+ import { jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
1538
+ function StringEditor({
1539
+ column,
1540
+ rowId,
1541
+ value,
1542
+ onChange,
1543
+ error,
1544
+ autoFocus
1545
+ }) {
1546
+ const errorId = editErrorId(rowId, column.id);
1547
+ return /* @__PURE__ */ jsxs13("div", { children: [
1548
+ /* @__PURE__ */ jsx20(
1549
+ Input,
1550
+ {
1551
+ className: "h-8",
1552
+ "aria-label": `Edit ${column.header}`,
1553
+ "aria-invalid": error ? true : void 0,
1554
+ "aria-describedby": error ? errorId : void 0,
1555
+ "data-testid": `edit-${rowId}-${column.id}`,
1556
+ autoFocus,
1557
+ value: value === null || value === void 0 ? "" : String(value),
1558
+ onChange: (event) => onChange(event.target.value)
1559
+ }
1560
+ ),
1561
+ error && /* @__PURE__ */ jsx20(EditFieldError, { id: errorId, message: error })
1562
+ ] });
1563
+ }
1564
+
1565
+ // src/edit/registry.tsx
1566
+ import { jsx as jsx21 } from "react/jsx-runtime";
1567
+ function renderDefaultEditWidget(column, rowId, value, onChange, error, autoFocus) {
1568
+ switch (column.type) {
1569
+ case "string":
1570
+ return /* @__PURE__ */ jsx21(StringEditor, { column, rowId, value, onChange, error, autoFocus });
1571
+ case "enum":
1572
+ return /* @__PURE__ */ jsx21(EnumEditor, { column, rowId, value, onChange, error, autoFocus });
1573
+ case "boolean":
1574
+ return /* @__PURE__ */ jsx21(BooleanEditor, { column, rowId, value, onChange, error, autoFocus });
1575
+ case "number":
1576
+ case "currency":
1577
+ return /* @__PURE__ */ jsx21(NumberEditor, { column, rowId, value, onChange, error, autoFocus });
1578
+ case "date":
1579
+ case "datetime":
1580
+ return /* @__PURE__ */ jsx21(DateEditor, { column, rowId, value, onChange, error, autoFocus });
1581
+ }
1582
+ }
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
+
1368
1812
  // src/column-selector/ColumnSelector.tsx
1369
1813
  import { Columns3 } from "lucide-react";
1370
- import { useEffect as useEffect4 } from "react";
1814
+ import { useEffect as useEffect6 } from "react";
1371
1815
 
1372
1816
  // src/components/ui/dialog.tsx
1373
1817
  import * as DialogPrimitive from "@radix-ui/react-dialog";
1374
1818
  import { X } from "lucide-react";
1375
1819
  import { forwardRef as forwardRef6 } from "react";
1376
- import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
1820
+ import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
1377
1821
  var Dialog = DialogPrimitive.Root;
1378
1822
  var DialogTrigger = DialogPrimitive.Trigger;
1379
- var DialogOverlay = forwardRef6(({ className, ...props }, ref) => /* @__PURE__ */ jsx15(
1823
+ var DialogOverlay = forwardRef6(({ className, ...props }, ref) => /* @__PURE__ */ jsx24(
1380
1824
  DialogPrimitive.Overlay,
1381
1825
  {
1382
1826
  ref,
@@ -1385,9 +1829,9 @@ var DialogOverlay = forwardRef6(({ className, ...props }, ref) => /* @__PURE__ *
1385
1829
  }
1386
1830
  ));
1387
1831
  DialogOverlay.displayName = "DialogOverlay";
1388
- var DialogContent = forwardRef6(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs9(DialogPrimitive.Portal, { children: [
1389
- /* @__PURE__ */ jsx15(DialogOverlay, {}),
1390
- /* @__PURE__ */ jsxs9(
1832
+ var DialogContent = forwardRef6(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs15(DialogPrimitive.Portal, { children: [
1833
+ /* @__PURE__ */ jsx24(DialogOverlay, {}),
1834
+ /* @__PURE__ */ jsxs15(
1391
1835
  DialogPrimitive.Content,
1392
1836
  {
1393
1837
  ref,
@@ -1398,9 +1842,9 @@ var DialogContent = forwardRef6(({ className, children, ...props }, ref) => /* @
1398
1842
  ...props,
1399
1843
  children: [
1400
1844
  children,
1401
- /* @__PURE__ */ jsxs9(DialogPrimitive.Close, { className: "absolute right-4 top-4 rounded-sm opacity-70 hover:opacity-100", children: [
1402
- /* @__PURE__ */ jsx15(X, { className: "h-4 w-4" }),
1403
- /* @__PURE__ */ jsx15("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" })
1404
1848
  ] })
1405
1849
  ]
1406
1850
  }
@@ -1410,10 +1854,10 @@ DialogContent.displayName = "DialogContent";
1410
1854
  var DialogHeader = ({
1411
1855
  className,
1412
1856
  ...props
1413
- }) => /* @__PURE__ */ jsx15("div", { className: cn("flex flex-col gap-1.5", className), ...props });
1414
- var DialogTitle = forwardRef6(({ className, ...props }, ref) => /* @__PURE__ */ jsx15(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 }));
1415
1859
  DialogTitle.displayName = "DialogTitle";
1416
- var DialogDescription = forwardRef6(({ className, ...props }, ref) => /* @__PURE__ */ jsx15(
1860
+ var DialogDescription = forwardRef6(({ className, ...props }, ref) => /* @__PURE__ */ jsx24(
1417
1861
  DialogPrimitive.Description,
1418
1862
  {
1419
1863
  ref,
@@ -1481,7 +1925,7 @@ function canHideGroup(columns, visibility, groupColumnIds) {
1481
1925
  }
1482
1926
 
1483
1927
  // src/column-selector/ColumnSelector.tsx
1484
- import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
1928
+ import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
1485
1929
  function ColumnSelector({
1486
1930
  columns,
1487
1931
  visibility,
@@ -1491,7 +1935,7 @@ function ColumnSelector({
1491
1935
  open,
1492
1936
  onOpenChange
1493
1937
  }) {
1494
- useEffect4(() => {
1938
+ useEffect6(() => {
1495
1939
  if (!persistKey) return;
1496
1940
  const restored = readPersistedVisibility(persistKey);
1497
1941
  if (restored) onVisibilityChange({ ...visibility, ...restored });
@@ -1506,20 +1950,20 @@ function ColumnSelector({
1506
1950
  }
1507
1951
  const totalVisible = countVisible(columns, visibility);
1508
1952
  const groups = groupColumns(columns);
1509
- return /* @__PURE__ */ jsxs10(Dialog, { open, onOpenChange, children: [
1510
- /* @__PURE__ */ jsx16(DialogTrigger, { asChild: true, children: trigger ?? /* @__PURE__ */ jsx16(Button, { variant: "outline", size: "icon", "aria-label": "Choose columns", children: /* @__PURE__ */ jsx16(Columns3, { className: "h-4 w-4", "aria-hidden": true }) }) }),
1511
- /* @__PURE__ */ jsxs10(DialogContent, { className: "w-fit max-w-[calc(100vw-2rem)] sm:max-w-2xl", children: [
1512
- /* @__PURE__ */ jsx16(DialogHeader, { children: /* @__PURE__ */ jsx16(DialogTitle, { children: "Columns" }) }),
1513
- /* @__PURE__ */ jsx16("div", { className: "flex flex-wrap gap-x-8 gap-y-5", children: groups.map(({ group, columns: groupCols }) => /* @__PURE__ */ jsxs10("div", { className: "flex min-w-32 flex-col gap-1.5", children: [
1514
- group && /* @__PURE__ */ jsx16("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 }),
1515
1959
  groupCols.map((column) => {
1516
1960
  const visible = isColumnVisible(visibility, column.id);
1517
- return /* @__PURE__ */ jsxs10(
1961
+ return /* @__PURE__ */ jsxs16(
1518
1962
  "label",
1519
1963
  {
1520
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",
1521
1965
  children: [
1522
- /* @__PURE__ */ jsx16(
1966
+ /* @__PURE__ */ jsx25(
1523
1967
  Checkbox,
1524
1968
  {
1525
1969
  checked: visible,
@@ -1556,10 +2000,10 @@ import { useMemo as useMemo3 } from "react";
1556
2000
  // src/components/ui/dropdown-menu.tsx
1557
2001
  import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
1558
2002
  import { forwardRef as forwardRef7 } from "react";
1559
- import { jsx as jsx17 } from "react/jsx-runtime";
2003
+ import { jsx as jsx26 } from "react/jsx-runtime";
1560
2004
  var DropdownMenu = DropdownMenuPrimitive.Root;
1561
2005
  var DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
1562
- var DropdownMenuContent = forwardRef7(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx17(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx17(
2006
+ var DropdownMenuContent = forwardRef7(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx26(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx26(
1563
2007
  DropdownMenuPrimitive.Content,
1564
2008
  {
1565
2009
  ref,
@@ -1572,7 +2016,7 @@ var DropdownMenuContent = forwardRef7(({ className, sideOffset = 4, ...props },
1572
2016
  }
1573
2017
  ) }));
1574
2018
  DropdownMenuContent.displayName = "DropdownMenuContent";
1575
- var DropdownMenuItem = forwardRef7(({ className, danger, ...props }, ref) => /* @__PURE__ */ jsx17(
2019
+ var DropdownMenuItem = forwardRef7(({ className, danger, ...props }, ref) => /* @__PURE__ */ jsx26(
1576
2020
  DropdownMenuPrimitive.Item,
1577
2021
  {
1578
2022
  ref,
@@ -1585,7 +2029,7 @@ var DropdownMenuItem = forwardRef7(({ className, danger, ...props }, ref) => /*
1585
2029
  }
1586
2030
  ));
1587
2031
  DropdownMenuItem.displayName = "DropdownMenuItem";
1588
- var DropdownMenuSeparator = forwardRef7(({ className, ...props }, ref) => /* @__PURE__ */ jsx17(
2032
+ var DropdownMenuSeparator = forwardRef7(({ className, ...props }, ref) => /* @__PURE__ */ jsx26(
1589
2033
  DropdownMenuPrimitive.Separator,
1590
2034
  {
1591
2035
  ref,
@@ -1594,7 +2038,7 @@ var DropdownMenuSeparator = forwardRef7(({ className, ...props }, ref) => /* @__
1594
2038
  }
1595
2039
  ));
1596
2040
  DropdownMenuSeparator.displayName = "DropdownMenuSeparator";
1597
- var DropdownMenuLabel = forwardRef7(({ className, ...props }, ref) => /* @__PURE__ */ jsx17(
2041
+ var DropdownMenuLabel = forwardRef7(({ className, ...props }, ref) => /* @__PURE__ */ jsx26(
1598
2042
  DropdownMenuPrimitive.Label,
1599
2043
  {
1600
2044
  ref,
@@ -1605,16 +2049,16 @@ var DropdownMenuLabel = forwardRef7(({ className, ...props }, ref) => /* @__PURE
1605
2049
  DropdownMenuLabel.displayName = "DropdownMenuLabel";
1606
2050
 
1607
2051
  // src/menu/ActionsMenu.tsx
1608
- import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
2052
+ import { jsx as jsx27, jsxs as jsxs17 } from "react/jsx-runtime";
1609
2053
  function ActionsMenu({ items, ctx, triggerLabel }) {
1610
2054
  const resolved = useMemo3(
1611
2055
  () => resolveMenuItems(items, ctx),
1612
2056
  [items, ctx.row, ctx.selectedRows]
1613
2057
  );
1614
2058
  if (resolved.length === 0) return null;
1615
- return /* @__PURE__ */ jsxs11(DropdownMenu, { children: [
1616
- /* @__PURE__ */ jsx18(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx18(Button, { variant: "ghost", size: "icon", "aria-label": triggerLabel, onClick: stopRowClick, children: /* @__PURE__ */ jsx18(MoreVertical, { className: "h-4 w-4", "aria-hidden": true }) }) }),
1617
- /* @__PURE__ */ jsx18(DropdownMenuContent, { align: "end", children: resolved.map((item) => /* @__PURE__ */ jsx18(
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(
1618
2062
  DropdownMenuItem,
1619
2063
  {
1620
2064
  danger: item.danger,
@@ -1632,7 +2076,7 @@ import { BarsArrowDownIcon, BarsArrowUpIcon, FunnelIcon as FunnelIcon7 } from "@
1632
2076
  import { columnResizingFeature, columnSizingFeature, flexRender, tableFeatures, useTable } from "@tanstack/react-table";
1633
2077
  import { useVirtualizer } from "@tanstack/react-virtual";
1634
2078
  import { ChevronRight, Loader2 } from "lucide-react";
1635
- 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";
1636
2080
 
1637
2081
  // src/lib/structuralColumns.ts
1638
2082
  var SELECTION_COLUMN_WIDTH = 44;
@@ -1675,15 +2119,15 @@ function processClientData(data, state) {
1675
2119
  }
1676
2120
 
1677
2121
  // src/grid/useGridState.ts
1678
- 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";
1679
2123
 
1680
2124
  // src/hooks/useDebouncedCallback.ts
1681
- 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";
1682
2126
  function useDebouncedCallback(callback, delayMs) {
1683
- const callbackRef = useRef4(callback);
2127
+ const callbackRef = useRef6(callback);
1684
2128
  callbackRef.current = callback;
1685
- const timeoutRef = useRef4(void 0);
1686
- useEffect5(() => {
2129
+ const timeoutRef = useRef6(void 0);
2130
+ useEffect7(() => {
1687
2131
  return () => {
1688
2132
  clearTimeout(timeoutRef.current);
1689
2133
  };
@@ -1713,7 +2157,7 @@ function nextSortDir(current, descFirst) {
1713
2157
  return void 0;
1714
2158
  }
1715
2159
  function useGridState(dataSource, initialState, externalState) {
1716
- const [state, setState] = useState5(
2160
+ const [state, setState] = useState7(
1717
2161
  () => externalState ?? { ...DEFAULT_STATE, ...initialState }
1718
2162
  );
1719
2163
  const notifyRaw = useCallback2(
@@ -1733,7 +2177,7 @@ function useGridState(dataSource, initialState, externalState) {
1733
2177
  },
1734
2178
  [notifyRaw, cancelDebouncedNotify]
1735
2179
  );
1736
- useEffect6(() => {
2180
+ useEffect8(() => {
1737
2181
  if (!externalState) return;
1738
2182
  cancelDebouncedNotify();
1739
2183
  setState(externalState);
@@ -1802,7 +2246,7 @@ function useGridState(dataSource, initialState, externalState) {
1802
2246
  }
1803
2247
 
1804
2248
  // src/grid/DataGrid.tsx
1805
- import { Fragment as Fragment3, jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
2249
+ import { Fragment as Fragment3, jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
1806
2250
  var gridTableFeatures = tableFeatures({ columnSizingFeature, columnResizingFeature });
1807
2251
  var DEFAULT_COLUMN_SIZE = 150;
1808
2252
  var DETAIL_COLUMN_WIDTH = 44;
@@ -1833,13 +2277,13 @@ function computeHeaderRuns(columns) {
1833
2277
  }
1834
2278
  return runs;
1835
2279
  }
1836
- function toTanstackColumns(columns) {
2280
+ function toTanstackColumns(columns, editingCtxRef) {
1837
2281
  return columns.map((column) => ({
1838
2282
  id: column.id,
1839
2283
  header: column.header,
1840
2284
  size: column.width,
1841
2285
  accessorFn: (row) => getColumnValue(column, row),
1842
- cell: (info) => column.cell ? column.cell(info.getValue(), info.row.original) : defaultFormat(column, info.getValue())
2286
+ cell: (info) => renderEditableCell(column, info.row.original, info.getValue(), editingCtxRef.current)
1843
2287
  }));
1844
2288
  }
1845
2289
  function DataGrid({
@@ -1870,26 +2314,27 @@ function DataGrid({
1870
2314
  defaultGroupsExpanded = true,
1871
2315
  expandedGroups: controlledExpandedGroups,
1872
2316
  onExpandedGroupsChange,
1873
- zebra = true
2317
+ zebra = true,
2318
+ editing
1874
2319
  }) {
1875
2320
  const { state, filtersByColumn, setColumnFilter, toggleSort, setPage } = useGridState(
1876
2321
  dataSource,
1877
2322
  initialState,
1878
2323
  gridState
1879
2324
  );
1880
- const [internalSelectedIds, setInternalSelectedIds] = useState6(/* @__PURE__ */ new Set());
2325
+ const [internalSelectedIds, setInternalSelectedIds] = useState8(/* @__PURE__ */ new Set());
1881
2326
  const selectedIds = controlledSelectedIds ?? internalSelectedIds;
1882
2327
  function updateSelectedIds(next) {
1883
2328
  setInternalSelectedIds(next);
1884
2329
  onSelectedIdsChange?.(next);
1885
2330
  }
1886
- const [internalColumnSizing, setInternalColumnSizing] = useState6({});
2331
+ const [internalColumnSizing, setInternalColumnSizing] = useState8({});
1887
2332
  const columnSizing = controlledColumnSizing ?? internalColumnSizing;
1888
2333
  function updateColumnSizing(next) {
1889
2334
  setInternalColumnSizing(next);
1890
2335
  onColumnSizingChange?.(next);
1891
2336
  }
1892
- const [expandedIds, setExpandedIds] = useState6(/* @__PURE__ */ new Set());
2337
+ const [expandedIds, setExpandedIds] = useState8(/* @__PURE__ */ new Set());
1893
2338
  function toggleRowExpanded(id) {
1894
2339
  setExpandedIds((prev) => {
1895
2340
  const next = new Set(prev);
@@ -1898,7 +2343,7 @@ function DataGrid({
1898
2343
  return next;
1899
2344
  });
1900
2345
  }
1901
- const [internalExpandedGroups, setInternalExpandedGroups] = useState6({});
2346
+ const [internalExpandedGroups, setInternalExpandedGroups] = useState8({});
1902
2347
  const expandedGroupsState = controlledExpandedGroups ?? internalExpandedGroups;
1903
2348
  function updateExpandedGroups(next) {
1904
2349
  setInternalExpandedGroups(next);
@@ -1910,6 +2355,10 @@ function DataGrid({
1910
2355
  function toggleGroupExpanded(key) {
1911
2356
  updateExpandedGroups({ ...expandedGroupsState, [key]: !isGroupExpanded(key) });
1912
2357
  }
2358
+ const { pendingEdits, editErrors, ctxRef: editingCtxRef, handleSaveEdits, handleDiscardEdits } = useEditingState(
2359
+ editing,
2360
+ getRowId
2361
+ );
1913
2362
  const visibleColumns = useMemo6(
1914
2363
  () => columnVisibility ? columns.filter((column) => isColumnVisible(columnVisibility, column.id)) : columns,
1915
2364
  [columns, columnVisibility]
@@ -1938,7 +2387,7 @@ function DataGrid({
1938
2387
  }
1939
2388
  updateSelectedIds(next);
1940
2389
  }
1941
- const tanstackColumns = useMemo6(() => toTanstackColumns(visibleColumns), [visibleColumns]);
2390
+ const tanstackColumns = useMemo6(() => toTanstackColumns(visibleColumns, editingCtxRef), [visibleColumns]);
1942
2391
  const columnById = useMemo6(() => {
1943
2392
  const map = /* @__PURE__ */ new Map();
1944
2393
  for (const column of visibleColumns) map.set(column.id, column);
@@ -2065,8 +2514,8 @@ function DataGrid({
2065
2514
  () => groupBy ? groupRows(tableRows, (row) => groupBy(row.original)) : void 0,
2066
2515
  [tableRows, groupBy]
2067
2516
  );
2068
- const theadRef = useRef5(null);
2069
- const [groupHeaderTop, setGroupHeaderTop] = useState6(0);
2517
+ const theadRef = useRef7(null);
2518
+ const [groupHeaderTop, setGroupHeaderTop] = useState8(0);
2070
2519
  useLayoutEffect(() => {
2071
2520
  if (!groupBy) return;
2072
2521
  const theadEl = theadRef.current;
@@ -2077,7 +2526,7 @@ function DataGrid({
2077
2526
  observer.observe(theadEl);
2078
2527
  return () => observer.disconnect();
2079
2528
  }, [groupBy]);
2080
- const scrollRef = useRef5(null);
2529
+ const scrollRef = useRef7(null);
2081
2530
  const virtualizer = useVirtualizer({
2082
2531
  count: tableRows.length,
2083
2532
  getScrollElement: () => scrollRef.current,
@@ -2089,8 +2538,8 @@ function DataGrid({
2089
2538
  const paddingTop = virtualItems.length > 0 ? virtualItems[0].start : 0;
2090
2539
  const paddingBottom = virtualItems.length > 0 ? virtualizer.getTotalSize() - virtualItems[virtualItems.length - 1].end : 0;
2091
2540
  const lastVisibleIndex = virtualItems.length > 0 ? virtualItems[virtualItems.length - 1].index : -1;
2092
- const notifiedForLengthRef = useRef5(-1);
2093
- useEffect7(() => {
2541
+ const notifiedForLengthRef = useRef7(-1);
2542
+ useEffect9(() => {
2094
2543
  if (!virtualize?.onEndReached) return;
2095
2544
  if (lastVisibleIndex < 0 || lastVisibleIndex < tableRows.length - 1) return;
2096
2545
  if (virtualize.hasMore === false) return;
@@ -2103,20 +2552,20 @@ function DataGrid({
2103
2552
  const rowProps = getRowProps?.(row.original);
2104
2553
  const rowTestId = getRowTestId?.(row.original) ?? `row-${row.id}`;
2105
2554
  const isOddRow = zebra && row.index % 2 === 1;
2106
- return /* @__PURE__ */ jsxs12("tbody", { ref: measureRef, "data-index": row.index, children: [
2107
- /* @__PURE__ */ jsxs12(
2555
+ return /* @__PURE__ */ jsxs18("tbody", { ref: measureRef, "data-index": row.index, children: [
2556
+ /* @__PURE__ */ jsxs18(
2108
2557
  "tr",
2109
2558
  {
2110
2559
  ...rowProps,
2111
2560
  "data-testid": rowTestId,
2112
2561
  className: rowProps?.className ? cn("divide-x divide-border", rowProps.className, isOddRow && "bg-foreground/5") : `divide-x divide-border${isOddRow ? " bg-foreground/5" : ""}`,
2113
2562
  children: [
2114
- showDetailColumn && /* @__PURE__ */ jsx19(
2563
+ showDetailColumn && /* @__PURE__ */ jsx28(
2115
2564
  "td",
2116
2565
  {
2117
2566
  className: isOddRow ? detailCellProps.classNameOdd : detailCellProps.className,
2118
2567
  style: detailCellProps.style,
2119
- children: /* @__PURE__ */ jsx19(
2568
+ children: /* @__PURE__ */ jsx28(
2120
2569
  Button,
2121
2570
  {
2122
2571
  type: "button",
@@ -2128,7 +2577,7 @@ function DataGrid({
2128
2577
  stopRowClick(event);
2129
2578
  toggleRowExpanded(row.id);
2130
2579
  },
2131
- children: /* @__PURE__ */ jsx19(
2580
+ children: /* @__PURE__ */ jsx28(
2132
2581
  ChevronRight,
2133
2582
  {
2134
2583
  className: `h-4 w-4 transition-transform${isExpanded ? " rotate-90" : ""}`,
@@ -2139,12 +2588,12 @@ function DataGrid({
2139
2588
  )
2140
2589
  }
2141
2590
  ),
2142
- showSelectionColumn && /* @__PURE__ */ jsx19(
2591
+ showSelectionColumn && /* @__PURE__ */ jsx28(
2143
2592
  "td",
2144
2593
  {
2145
2594
  className: isOddRow ? selectionCellProps.classNameOdd : selectionCellProps.className,
2146
2595
  style: selectionCellProps.style,
2147
- children: /* @__PURE__ */ jsx19(
2596
+ children: /* @__PURE__ */ jsx28(
2148
2597
  "input",
2149
2598
  {
2150
2599
  type: "checkbox",
@@ -2158,27 +2607,27 @@ function DataGrid({
2158
2607
  ),
2159
2608
  row.getAllCells().map((cell) => {
2160
2609
  const cellProps = bodyCellPropsByColumn.get(cell.column.id);
2161
- return /* @__PURE__ */ jsx19("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);
2162
2611
  }),
2163
- showRowActionsColumn && rowActions && /* @__PURE__ */ jsx19(
2612
+ showRowActionsColumn && rowActions && /* @__PURE__ */ jsx28(
2164
2613
  "td",
2165
2614
  {
2166
2615
  className: isOddRow ? rowActionsCellProps.classNameOdd : rowActionsCellProps.className,
2167
2616
  style: rowActionsCellProps.style,
2168
- children: /* @__PURE__ */ jsx19(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}` })
2169
2618
  }
2170
2619
  )
2171
2620
  ]
2172
2621
  }
2173
2622
  ),
2174
- showDetailColumn && isExpanded && renderDetail && /* @__PURE__ */ jsx19("tr", { "data-testid": `${rowTestId}-detail`, children: /* @__PURE__ */ jsx19("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) }) })
2175
2624
  ] }, row.id);
2176
2625
  }
2177
2626
  function renderGroupedBucket(bucket) {
2178
2627
  const expanded = isGroupExpanded(bucket.key);
2179
2628
  const originalRows = bucket.items.map((row) => row.original);
2180
- return /* @__PURE__ */ jsxs12(Fragment2, { children: [
2181
- /* @__PURE__ */ jsx19("tbody", { children: /* @__PURE__ */ jsx19("tr", { "data-testid": `group-header-${bucket.key}`, children: /* @__PURE__ */ jsx19(
2629
+ return /* @__PURE__ */ jsxs18(Fragment2, { children: [
2630
+ /* @__PURE__ */ jsx28("tbody", { children: /* @__PURE__ */ jsx28("tr", { "data-testid": `group-header-${bucket.key}`, children: /* @__PURE__ */ jsx28(
2182
2631
  "td",
2183
2632
  {
2184
2633
  colSpan: totalColumnCount,
@@ -2187,7 +2636,7 @@ function DataGrid({
2187
2636
  zebra ? "bg-muted" : "bg-background"
2188
2637
  ),
2189
2638
  style: { top: groupHeaderTop },
2190
- children: /* @__PURE__ */ jsxs12(
2639
+ children: /* @__PURE__ */ jsxs18(
2191
2640
  "button",
2192
2641
  {
2193
2642
  type: "button",
@@ -2195,7 +2644,7 @@ function DataGrid({
2195
2644
  onClick: () => toggleGroupExpanded(bucket.key),
2196
2645
  "aria-expanded": expanded,
2197
2646
  children: [
2198
- /* @__PURE__ */ jsx19(
2647
+ /* @__PURE__ */ jsx28(
2199
2648
  ChevronRight,
2200
2649
  {
2201
2650
  className: `h-4 w-4 shrink-0 transition-transform${expanded ? " rotate-90" : ""}`,
@@ -2224,32 +2673,42 @@ function DataGrid({
2224
2673
  // containers (that one scrolling vertically, this component's own
2225
2674
  // `overflow-x-auto` div scrolling horizontally) fighting over which one
2226
2675
  // a sticky header/pinned column is actually stuck relative to.
2227
- /* @__PURE__ */ jsxs12("div", { className: "flex h-full min-h-0 flex-col gap-2", "data-testid": "datagrid", children: [
2228
- showSelectionColumn && headerActions && /* @__PURE__ */ jsxs12("div", { className: "flex shrink-0 items-center justify-between text-sm", children: [
2229
- /* @__PURE__ */ jsxs12("span", { children: [
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,
2679
+ {
2680
+ editing,
2681
+ pendingRowCount: pendingEdits.size,
2682
+ editErrors,
2683
+ onSave: () => void handleSaveEdits(),
2684
+ onDiscard: handleDiscardEdits
2685
+ }
2686
+ ),
2687
+ showSelectionColumn && headerActions && /* @__PURE__ */ jsxs18("div", { className: "flex shrink-0 items-center justify-between text-sm", children: [
2688
+ /* @__PURE__ */ jsxs18("span", { children: [
2230
2689
  selectedRows.length,
2231
2690
  " selected"
2232
2691
  ] }),
2233
- /* @__PURE__ */ jsx19(ActionsMenu, { items: headerActions, ctx: { selectedRows }, triggerLabel: "Bulk actions" })
2692
+ /* @__PURE__ */ jsx28(ActionsMenu, { items: headerActions, ctx: { selectedRows }, triggerLabel: "Bulk actions" })
2234
2693
  ] }),
2235
- /* @__PURE__ */ jsxs12("div", { className: "relative min-h-0 flex-1", children: [
2236
- loading && /* @__PURE__ */ jsx19(
2694
+ /* @__PURE__ */ jsxs18("div", { className: "relative min-h-0 flex-1", children: [
2695
+ loading && /* @__PURE__ */ jsx28(
2237
2696
  "div",
2238
2697
  {
2239
2698
  "data-testid": "datagrid-loading-overlay",
2240
2699
  className: "absolute inset-0 z-30 flex items-center justify-center bg-background/60",
2241
- children: /* @__PURE__ */ jsx19(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 })
2242
2701
  }
2243
2702
  ),
2244
- /* @__PURE__ */ jsx19(
2703
+ /* @__PURE__ */ jsx28(
2245
2704
  "div",
2246
2705
  {
2247
2706
  ref: scrollRef,
2248
2707
  "data-testid": testId,
2249
2708
  className: "h-full overflow-auto rounded-md border",
2250
2709
  style: shouldVirtualize ? { maxHeight: virtualize?.maxBodyHeight ?? 480 } : void 0,
2251
- children: /* @__PURE__ */ jsxs12("table", { className: cn("w-full border-collapse text-sm", enableColumnResizing && "table-fixed"), children: [
2252
- /* @__PURE__ */ jsxs12("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: [
2253
2712
  table.getHeaderGroups().map((headerGroup) => {
2254
2713
  const headerById = new Map(headerGroup.headers.map((header) => [header.id, header]));
2255
2714
  function renderLeafHeaderCell(column, rowSpan) {
@@ -2260,9 +2719,9 @@ function DataGrid({
2260
2719
  const filterable = isFilterable(column) && column.filterDisplay !== "row";
2261
2720
  const cellProps = leafHeaderCellPropsByColumn.get(column.id);
2262
2721
  const headerContent = column.renderHeader ? column.renderHeader(column) : column.header;
2263
- return /* @__PURE__ */ jsxs12("th", { rowSpan, style: cellProps?.style, className: cellProps?.className, children: [
2264
- /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1", children: [
2265
- sortable ? /* @__PURE__ */ jsxs12(
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(
2266
2725
  "button",
2267
2726
  {
2268
2727
  type: "button",
@@ -2270,8 +2729,8 @@ function DataGrid({
2270
2729
  onClick: (event) => toggleSort(column.id, enableMultiSort && event.shiftKey, column.sortDescFirst === true),
2271
2730
  children: [
2272
2731
  headerContent,
2273
- sortEntry?.dir === "asc" && /* @__PURE__ */ jsx19(BarsArrowUpIcon, { className: "h-3 w-3", "aria-hidden": true }),
2274
- sortEntry?.dir === "desc" && /* @__PURE__ */ jsx19(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 })
2275
2734
  ]
2276
2735
  }
2277
2736
  ) : (
@@ -2280,10 +2739,10 @@ function DataGrid({
2280
2739
  // silently breaking any interactive content a column's own `renderHeader`
2281
2740
  // embeds directly (bare filter inputs, "select/reject all" buttons) —
2282
2741
  // clicks never reach them at all, even though nothing looks wrong visually.
2283
- /* @__PURE__ */ jsx19("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 })
2284
2743
  ),
2285
- filterable && /* @__PURE__ */ jsxs12(Popover, { children: [
2286
- /* @__PURE__ */ jsx19(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx19(Button, { variant: "ghost", size: "icon", "aria-label": `Filter ${column.header}`, children: /* @__PURE__ */ jsx19(
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(
2287
2746
  FunnelIcon7,
2288
2747
  {
2289
2748
  className: cn(
@@ -2293,10 +2752,10 @@ function DataGrid({
2293
2752
  "aria-hidden": true
2294
2753
  }
2295
2754
  ) }) }),
2296
- /* @__PURE__ */ jsx19(PopoverContent, { children: renderFilterWidget(column) })
2755
+ /* @__PURE__ */ jsx28(PopoverContent, { children: renderFilterWidget(column) })
2297
2756
  ] })
2298
2757
  ] }),
2299
- enableColumnResizing && /* @__PURE__ */ jsx19(
2758
+ enableColumnResizing && /* @__PURE__ */ jsx28(
2300
2759
  "div",
2301
2760
  {
2302
2761
  "data-testid": `resize-handle-${column.id}`,
@@ -2307,9 +2766,9 @@ function DataGrid({
2307
2766
  )
2308
2767
  ] }, header.id);
2309
2768
  }
2310
- return /* @__PURE__ */ jsxs12(Fragment2, { children: [
2311
- hasHeaderGroups && /* @__PURE__ */ jsxs12("tr", { "data-testid": "header-group-row", className: "divide-x divide-border", children: [
2312
- showDetailColumn && /* @__PURE__ */ jsx19(
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(
2313
2772
  "th",
2314
2773
  {
2315
2774
  rowSpan: 2,
@@ -2318,13 +2777,13 @@ function DataGrid({
2318
2777
  "aria-hidden": true
2319
2778
  }
2320
2779
  ),
2321
- showSelectionColumn && /* @__PURE__ */ jsx19(
2780
+ showSelectionColumn && /* @__PURE__ */ jsx28(
2322
2781
  "th",
2323
2782
  {
2324
2783
  rowSpan: 2,
2325
2784
  className: selectionHeaderCellProps.className,
2326
2785
  style: selectionHeaderCellProps.style,
2327
- children: /* @__PURE__ */ jsx19(
2786
+ children: /* @__PURE__ */ jsx28(
2328
2787
  "input",
2329
2788
  {
2330
2789
  type: "checkbox",
@@ -2336,7 +2795,7 @@ function DataGrid({
2336
2795
  }
2337
2796
  ),
2338
2797
  headerRuns.map(
2339
- (run) => run.kind === "single" ? renderLeafHeaderCell(run.column, 2) : /* @__PURE__ */ jsx19(
2798
+ (run) => run.kind === "single" ? renderLeafHeaderCell(run.column, 2) : /* @__PURE__ */ jsx28(
2340
2799
  "th",
2341
2800
  {
2342
2801
  colSpan: run.columns.length,
@@ -2346,7 +2805,7 @@ function DataGrid({
2346
2805
  `group-${run.label}-${run.columns[0].id}`
2347
2806
  )
2348
2807
  ),
2349
- showRowActionsColumn && /* @__PURE__ */ jsx19(
2808
+ showRowActionsColumn && /* @__PURE__ */ jsx28(
2350
2809
  "th",
2351
2810
  {
2352
2811
  rowSpan: 2,
@@ -2356,8 +2815,8 @@ function DataGrid({
2356
2815
  }
2357
2816
  )
2358
2817
  ] }, `${headerGroup.id}-groups`),
2359
- /* @__PURE__ */ jsxs12("tr", { className: "divide-x divide-border", children: [
2360
- !hasHeaderGroups && showDetailColumn && /* @__PURE__ */ jsx19(
2818
+ /* @__PURE__ */ jsxs18("tr", { className: "divide-x divide-border", children: [
2819
+ !hasHeaderGroups && showDetailColumn && /* @__PURE__ */ jsx28(
2361
2820
  "th",
2362
2821
  {
2363
2822
  className: detailHeaderCellProps.className,
@@ -2365,12 +2824,12 @@ function DataGrid({
2365
2824
  "aria-hidden": true
2366
2825
  }
2367
2826
  ),
2368
- !hasHeaderGroups && showSelectionColumn && /* @__PURE__ */ jsx19(
2827
+ !hasHeaderGroups && showSelectionColumn && /* @__PURE__ */ jsx28(
2369
2828
  "th",
2370
2829
  {
2371
2830
  className: selectionHeaderCellProps.className,
2372
2831
  style: selectionHeaderCellProps.style,
2373
- children: /* @__PURE__ */ jsx19(
2832
+ children: /* @__PURE__ */ jsx28(
2374
2833
  "input",
2375
2834
  {
2376
2835
  type: "checkbox",
@@ -2386,7 +2845,7 @@ function DataGrid({
2386
2845
  if (!column || hasHeaderGroups && !column.headerGroup) return null;
2387
2846
  return renderLeafHeaderCell(column);
2388
2847
  }),
2389
- !hasHeaderGroups && showRowActionsColumn && /* @__PURE__ */ jsx19(
2848
+ !hasHeaderGroups && showRowActionsColumn && /* @__PURE__ */ jsx28(
2390
2849
  "th",
2391
2850
  {
2392
2851
  className: rowActionsHeaderCellProps.className,
@@ -2397,9 +2856,9 @@ function DataGrid({
2397
2856
  ] }, headerGroup.id)
2398
2857
  ] }, headerGroup.id);
2399
2858
  }),
2400
- hasFilterRow && /* @__PURE__ */ jsxs12("tr", { "data-testid": "filter-row", className: "divide-x divide-border", children: [
2401
- showDetailColumn && /* @__PURE__ */ jsx19("th", { className: detailHeaderCellProps.className, style: detailHeaderCellProps.style }),
2402
- showSelectionColumn && /* @__PURE__ */ jsx19(
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(
2403
2862
  "th",
2404
2863
  {
2405
2864
  className: selectionHeaderCellProps.className,
@@ -2408,9 +2867,9 @@ function DataGrid({
2408
2867
  ),
2409
2868
  visibleColumns.map((column) => {
2410
2869
  const cellProps = filterHeaderCellPropsByColumn.get(column.id);
2411
- return /* @__PURE__ */ jsx19("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);
2412
2871
  }),
2413
- showRowActionsColumn && /* @__PURE__ */ jsx19(
2872
+ showRowActionsColumn && /* @__PURE__ */ jsx28(
2414
2873
  "th",
2415
2874
  {
2416
2875
  className: rowActionsHeaderCellProps.className,
@@ -2419,19 +2878,19 @@ function DataGrid({
2419
2878
  )
2420
2879
  ] })
2421
2880
  ] }),
2422
- rows.length === 0 ? /* @__PURE__ */ jsx19("tbody", { children: /* @__PURE__ */ jsx19("tr", { children: /* @__PURE__ */ jsx19("td", { colSpan: totalColumnCount, className: "p-4 text-center text-muted-foreground", children: loading ? "Loading..." : "No results." }) }) }) : shouldVirtualize ? /* @__PURE__ */ jsxs12(Fragment3, { children: [
2423
- paddingTop > 0 && /* @__PURE__ */ jsx19("tbody", { children: /* @__PURE__ */ jsx19("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx19("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 } }) }) }),
2424
2883
  virtualItems.map(
2425
2884
  (virtualItem) => renderRow(tableRows[virtualItem.index], (el) => virtualizer.measureElement(el))
2426
2885
  ),
2427
- paddingBottom > 0 && /* @__PURE__ */ jsx19("tbody", { children: /* @__PURE__ */ jsx19("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx19("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 } }) }) })
2428
2887
  ] }) : groupedBuckets ? groupedBuckets.map((bucket) => renderGroupedBucket(bucket)) : tableRows.map((row) => renderRow(row))
2429
2888
  ] })
2430
2889
  }
2431
2890
  )
2432
2891
  ] }),
2433
- showPagination && /* @__PURE__ */ jsxs12("div", { className: "flex shrink-0 items-center justify-between text-sm", children: [
2434
- /* @__PURE__ */ jsxs12("span", { children: [
2892
+ showPagination && /* @__PURE__ */ jsxs18("div", { className: "flex shrink-0 items-center justify-between text-sm", children: [
2893
+ /* @__PURE__ */ jsxs18("span", { children: [
2435
2894
  "Page ",
2436
2895
  state.page + 1,
2437
2896
  " of ",
@@ -2442,8 +2901,8 @@ function DataGrid({
2442
2901
  rowCount === 1 ? "" : "s",
2443
2902
  ")"
2444
2903
  ] }),
2445
- /* @__PURE__ */ jsxs12("div", { className: "flex gap-2", children: [
2446
- /* @__PURE__ */ jsx19(
2904
+ /* @__PURE__ */ jsxs18("div", { className: "flex gap-2", children: [
2905
+ /* @__PURE__ */ jsx28(
2447
2906
  Button,
2448
2907
  {
2449
2908
  variant: "outline",
@@ -2453,7 +2912,7 @@ function DataGrid({
2453
2912
  children: "Previous"
2454
2913
  }
2455
2914
  ),
2456
- /* @__PURE__ */ jsx19(
2915
+ /* @__PURE__ */ jsx28(
2457
2916
  Button,
2458
2917
  {
2459
2918
  variant: "outline",
@@ -2472,7 +2931,7 @@ function DataGrid({
2472
2931
  // src/tree/TreeDataGrid.tsx
2473
2932
  import { useVirtualizer as useVirtualizer2 } from "@tanstack/react-virtual";
2474
2933
  import { AlertCircle, ChevronDown as ChevronDown2, ChevronRight as ChevronRight2, Loader2 as Loader22 } from "lucide-react";
2475
- 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";
2476
2935
 
2477
2936
  // src/tree/flattenTree.ts
2478
2937
  function flattenTree(roots, accessors, expanded, childrenOverride) {
@@ -2517,7 +2976,7 @@ function computeSelectionStates(roots, accessors, childrenOverride, selectedIds)
2517
2976
  }
2518
2977
 
2519
2978
  // src/tree/useTreeState.ts
2520
- 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";
2521
2980
  function useTreeState({
2522
2981
  data,
2523
2982
  getRowId,
@@ -2529,7 +2988,7 @@ function useTreeState({
2529
2988
  childrenMap: controlledChildrenMap,
2530
2989
  onChildrenMapChange
2531
2990
  }) {
2532
- const [internalExpanded, setInternalExpanded] = useState7({});
2991
+ const [internalExpanded, setInternalExpanded] = useState9({});
2533
2992
  const expanded = controlledExpanded ?? internalExpanded;
2534
2993
  const setExpanded = useCallback3(
2535
2994
  (updater) => {
@@ -2541,7 +3000,7 @@ function useTreeState({
2541
3000
  },
2542
3001
  [controlledExpanded, onExpandedChange]
2543
3002
  );
2544
- const [internalChildrenMap, setInternalChildrenMap] = useState7(/* @__PURE__ */ new Map());
3003
+ const [internalChildrenMap, setInternalChildrenMap] = useState9(/* @__PURE__ */ new Map());
2545
3004
  const childrenMap = controlledChildrenMap ?? internalChildrenMap;
2546
3005
  const setChildrenMap = useCallback3(
2547
3006
  (updater) => {
@@ -2553,8 +3012,8 @@ function useTreeState({
2553
3012
  },
2554
3013
  [controlledChildrenMap, onChildrenMapChange]
2555
3014
  );
2556
- const [loadingIds, setLoadingIds] = useState7(/* @__PURE__ */ new Set());
2557
- const [errorIds, setErrorIds] = useState7(/* @__PURE__ */ new Map());
3015
+ const [loadingIds, setLoadingIds] = useState9(/* @__PURE__ */ new Set());
3016
+ const [errorIds, setErrorIds] = useState9(/* @__PURE__ */ new Map());
2558
3017
  const resolveHasChildren = useMemo7(
2559
3018
  () => hasChildren ?? ((row) => (getChildren(row)?.length ?? 0) > 0),
2560
3019
  [hasChildren, getChildren]
@@ -2634,12 +3093,11 @@ function useTreeState({
2634
3093
  }
2635
3094
 
2636
3095
  // src/tree/TreeDataGrid.tsx
2637
- import { Fragment as Fragment4, jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
3096
+ import { Fragment as Fragment4, jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
2638
3097
  var EMPTY_SELECTED_IDS = /* @__PURE__ */ new Set();
2639
3098
  var UNCHECKED_STATE = { checked: false, indeterminate: false };
2640
- function renderCell(column, row) {
2641
- const value = getColumnValue(column, row);
2642
- 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);
2643
3101
  }
2644
3102
  function TreeCell({
2645
3103
  flatRow,
@@ -2648,10 +3106,11 @@ function TreeCell({
2648
3106
  isLoading,
2649
3107
  error,
2650
3108
  onToggle,
2651
- onRetry
3109
+ onRetry,
3110
+ editingCtx
2652
3111
  }) {
2653
- return /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-1", style: { paddingLeft: flatRow.depth * indentSize }, children: [
2654
- flatRow.hasChildren ? /* @__PURE__ */ jsx20(
3112
+ return /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-1", style: { paddingLeft: flatRow.depth * indentSize }, children: [
3113
+ flatRow.hasChildren ? /* @__PURE__ */ jsx29(
2655
3114
  "button",
2656
3115
  {
2657
3116
  type: "button",
@@ -2663,14 +3122,14 @@ function TreeCell({
2663
3122
  "aria-label": flatRow.isExpanded ? "Collapse" : "Expand",
2664
3123
  "aria-expanded": flatRow.isExpanded,
2665
3124
  className: "flex h-4 w-4 shrink-0 items-center justify-center text-muted-foreground hover:text-foreground disabled:opacity-50",
2666
- children: isLoading ? /* @__PURE__ */ jsx20(Loader22, { className: "h-3.5 w-3.5 animate-spin", "aria-hidden": "true" }) : flatRow.isExpanded ? /* @__PURE__ */ jsx20(ChevronDown2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) : /* @__PURE__ */ jsx20(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" })
2667
3126
  }
2668
- ) : /* @__PURE__ */ jsx20("span", { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
2669
- /* @__PURE__ */ jsx20("span", { className: "truncate", children: renderCell(column, flatRow.row) }),
2670
- flatRow.isExpanded && error ? /* @__PURE__ */ jsxs13("span", { className: "ml-1 flex shrink-0 items-center gap-1 text-xs text-destructive", children: [
2671
- /* @__PURE__ */ jsx20(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" }),
2672
3131
  "Failed to load.",
2673
- /* @__PURE__ */ jsx20("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" })
2674
3133
  ] }) : null
2675
3134
  ] });
2676
3135
  }
@@ -2698,12 +3157,14 @@ function TreeDataGrid({
2698
3157
  onSelectedIdsChange,
2699
3158
  getRowSelectionState,
2700
3159
  isRowSelectionDisabled,
2701
- zebra = true
3160
+ zebra = true,
3161
+ editing
2702
3162
  }) {
2703
3163
  const accessors = useMemo8(
2704
3164
  () => ({ getRowId, getChildren, hasChildren }),
2705
3165
  [getRowId, getChildren, hasChildren]
2706
3166
  );
3167
+ const editingState = useEditingState(editing, getRowId);
2707
3168
  const { expanded, childrenMap, loadingIds, errorIds, toggleExpand, retry, expandToLevel } = useTreeState({
2708
3169
  data,
2709
3170
  getRowId,
@@ -2715,8 +3176,8 @@ function TreeDataGrid({
2715
3176
  childrenMap: controlledChildrenMap,
2716
3177
  onChildrenMapChange
2717
3178
  });
2718
- const didAutoExpand = useRef6(false);
2719
- useEffect8(() => {
3179
+ const didAutoExpand = useRef8(false);
3180
+ useEffect10(() => {
2720
3181
  if (didAutoExpand.current) return;
2721
3182
  didAutoExpand.current = true;
2722
3183
  if (initialExpandedLevel > 0) void expandToLevel(initialExpandedLevel);
@@ -2764,7 +3225,7 @@ function TreeDataGrid({
2764
3225
  for (const column of columns) map.set(column.id, `p-2 ${alignClassName(column)}`);
2765
3226
  return map;
2766
3227
  }, [columns]);
2767
- const scrollRef = useRef6(null);
3228
+ const scrollRef = useRef8(null);
2768
3229
  const shouldVirtualize = flatRows.length > virtualizeThreshold;
2769
3230
  const virtualizer = useVirtualizer2({
2770
3231
  count: flatRows.length,
@@ -2777,7 +3238,7 @@ function TreeDataGrid({
2777
3238
  const rowProps = getRowProps?.(flatRow.row, flatRow.depth, index);
2778
3239
  const rowTestId = getRowTestId?.(flatRow.row) ?? `tree-row-${flatRow.id}`;
2779
3240
  const isOddRow = zebra && index % 2 === 1;
2780
- return /* @__PURE__ */ jsxs13(
3241
+ return /* @__PURE__ */ jsxs19(
2781
3242
  "tr",
2782
3243
  {
2783
3244
  ref: measureRef,
@@ -2790,9 +3251,9 @@ function TreeDataGrid({
2790
3251
  isOddRow && "bg-foreground/5"
2791
3252
  ) : `border-b border-border divide-x divide-border${isOddRow ? " bg-foreground/5" : ""}`,
2792
3253
  children: [
2793
- showSelectionColumn && /* @__PURE__ */ jsx20("td", { className: "p-1", style: { width: SELECTION_COLUMN_WIDTH }, children: (() => {
3254
+ showSelectionColumn && /* @__PURE__ */ jsx29("td", { className: "p-1", style: { width: SELECTION_COLUMN_WIDTH }, children: (() => {
2794
3255
  const state = resolveSelectionState(flatRow.row, flatRow.id);
2795
- return /* @__PURE__ */ jsx20(
3256
+ return /* @__PURE__ */ jsx29(
2796
3257
  Checkbox,
2797
3258
  {
2798
3259
  "aria-label": `Select row ${flatRow.id}`,
@@ -2803,7 +3264,7 @@ function TreeDataGrid({
2803
3264
  }
2804
3265
  );
2805
3266
  })() }),
2806
- columns.map((column) => /* @__PURE__ */ jsx20("td", { className: bodyTdClassByColumn.get(column.id), children: column.id === treeColId ? /* @__PURE__ */ jsx20(
3267
+ columns.map((column) => /* @__PURE__ */ jsx29("td", { className: bodyTdClassByColumn.get(column.id), children: column.id === treeColId ? /* @__PURE__ */ jsx29(
2807
3268
  TreeCell,
2808
3269
  {
2809
3270
  flatRow,
@@ -2812,10 +3273,11 @@ function TreeDataGrid({
2812
3273
  isLoading: loadingIds.has(flatRow.id),
2813
3274
  error: errorIds.get(flatRow.id),
2814
3275
  onToggle: () => toggleExpand(flatRow.row),
2815
- onRetry: () => retry(flatRow.row)
3276
+ onRetry: () => retry(flatRow.row),
3277
+ editingCtx: editingState.ctx
2816
3278
  }
2817
- ) : renderCell(column, flatRow.row) }, column.id)),
2818
- showRowActionsColumn && rowActions && /* @__PURE__ */ jsx20("td", { className: "p-2", children: /* @__PURE__ */ jsx20(
3279
+ ) : renderCell(column, flatRow.row, editingState.ctx) }, column.id)),
3280
+ showRowActionsColumn && rowActions && /* @__PURE__ */ jsx29("td", { className: "p-2", children: /* @__PURE__ */ jsx29(
2819
3281
  ActionsMenu,
2820
3282
  {
2821
3283
  items: rowActions,
@@ -2831,60 +3293,80 @@ function TreeDataGrid({
2831
3293
  const virtualItems = shouldVirtualize ? virtualizer.getVirtualItems() : [];
2832
3294
  const paddingTop = virtualItems.length > 0 ? virtualItems[0].start : 0;
2833
3295
  const paddingBottom = virtualItems.length > 0 ? virtualizer.getTotalSize() - virtualItems[virtualItems.length - 1].end : 0;
2834
- return /* @__PURE__ */ jsx20("div", { className: "flex flex-col gap-2", "data-testid": "tree-datagrid", children: /* @__PURE__ */ jsx20(
2835
- "div",
2836
- {
2837
- ref: scrollRef,
2838
- className: shouldVirtualize ? "overflow-y-auto" : void 0,
2839
- style: shouldVirtualize ? { maxHeight: maxBodyHeight } : void 0,
2840
- children: /* @__PURE__ */ jsxs13("table", { className: "w-full border-collapse text-sm", children: [
2841
- /* @__PURE__ */ jsx20("thead", { children: /* @__PURE__ */ jsxs13("tr", { className: "divide-x divide-border", children: [
2842
- showSelectionColumn && /* @__PURE__ */ jsx20("th", { className: "border-b border-border p-1", style: { width: SELECTION_COLUMN_WIDTH }, children: /* @__PURE__ */ jsx20(
2843
- Checkbox,
2844
- {
2845
- "aria-label": "Select all visible rows",
2846
- checked: allVisibleSelected,
2847
- onCheckedChange: toggleAllVisibleSelected
2848
- }
2849
- ) }),
2850
- columns.map((column) => /* @__PURE__ */ jsx20(
2851
- "th",
2852
- {
2853
- style: column.width ? { width: column.width } : void 0,
2854
- className: `border-b border-border p-2 font-medium ${alignClassName(column)}`,
2855
- children: column.header
2856
- },
2857
- column.id
2858
- )),
2859
- showRowActionsColumn && /* @__PURE__ */ jsx20("th", { className: "border-b border-border p-2", "aria-label": "Row actions" })
2860
- ] }) }),
2861
- /* @__PURE__ */ jsx20("tbody", { children: flatRows.length === 0 ? /* @__PURE__ */ jsx20("tr", { children: /* @__PURE__ */ jsx20("td", { colSpan: totalColumnCount, className: "p-4 text-center text-muted-foreground", children: "No results." }) }) : shouldVirtualize ? /* @__PURE__ */ jsxs13(Fragment4, { children: [
2862
- paddingTop > 0 && /* @__PURE__ */ jsx20("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx20("td", { colSpan: totalColumnCount, style: { padding: 0, border: "none", height: paddingTop } }) }),
2863
- virtualItems.map(
2864
- (virtualItem) => renderRow(flatRows[virtualItem.index], virtualItem.index, (el) => virtualizer.measureElement(el))
2865
- ),
2866
- paddingBottom > 0 && /* @__PURE__ */ jsx20("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx20(
2867
- "td",
2868
- {
2869
- colSpan: totalColumnCount,
2870
- style: { padding: 0, border: "none", height: paddingBottom }
2871
- }
2872
- ) })
2873
- ] }) : flatRows.map((flatRow, index) => renderRow(flatRow, index)) })
2874
- ] })
2875
- }
2876
- ) });
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
+ ] });
2877
3352
  }
2878
3353
  export {
2879
3354
  ActionsMenu,
3355
+ BooleanEditor,
2880
3356
  BooleanFilter,
2881
3357
  ColumnSelector,
2882
3358
  DataGrid,
3359
+ DateEditor,
2883
3360
  DateRangeFilter,
3361
+ EditFieldError,
3362
+ EditingBar,
3363
+ EnumEditor,
2884
3364
  EnumFilter,
2885
3365
  NumberComparisonFilter,
3366
+ NumberEditor,
2886
3367
  NumberHistogramFilter,
2887
3368
  NumberRangeFilter,
3369
+ StringEditor,
2888
3370
  StringFilter,
2889
3371
  TreeDataGrid,
2890
3372
  UNARY_OPERATORS,
@@ -2894,6 +3376,7 @@ export {
2894
3376
  countVisible,
2895
3377
  defaultAlign,
2896
3378
  defaultFormat,
3379
+ editErrorId,
2897
3380
  evaluateFilter,
2898
3381
  facetedNumberValues,
2899
3382
  fieldKey,
@@ -2902,14 +3385,19 @@ export {
2902
3385
  groupColumns,
2903
3386
  isColumnVisible,
2904
3387
  isCompositeFilterDescriptor,
3388
+ isEditable,
2905
3389
  isFilterDescriptor,
2906
3390
  isFilterable,
2907
3391
  isSortable,
2908
3392
  processClientData,
3393
+ renderDefaultEditWidget,
2909
3394
  renderDefaultFilterWidget,
3395
+ renderEditableCell,
2910
3396
  resolveMenuItems,
2911
3397
  storageKeyFor,
3398
+ toDate,
2912
3399
  useDebouncedCallback,
3400
+ useEditingState,
2913
3401
  useGridState,
2914
3402
  useTreeState
2915
3403
  };