@bmsuisse/datagrid 0.23.1 → 0.23.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -772,7 +772,7 @@ interface DataGridProps<TRow> {
772
772
  * (`"server"`). Column filter UI defaults to the type-appropriate widget
773
773
  * from `src/filter` unless a column supplies its own `cell` renderer.
774
774
  */
775
- declare function DataGrid<TRow extends RowData>({ columns, dataSource, getRowId, rowActions, headerActions, enableMultiSort, initialState, columnVisibility, renderDetail, enableColumnResizing, columnResizeMode, columnSizing, onColumnSizingChange, virtualize, showPagination, getRowProps, getRowTestId, gridState, selectedIds: controlledSelectedIds, onSelectedIdsChange, testId, loading: loadingProp, }: DataGridProps<TRow>): ReactElement;
775
+ declare function DataGrid<TRow extends RowData>({ columns, dataSource, getRowId, rowActions, headerActions, enableMultiSort, initialState, columnVisibility, renderDetail, enableColumnResizing, columnResizeMode, columnSizing: controlledColumnSizing, onColumnSizingChange, virtualize, showPagination, getRowProps, getRowTestId, gridState, selectedIds: controlledSelectedIds, onSelectedIdsChange, testId, loading: loadingProp, }: DataGridProps<TRow>): ReactElement;
776
776
 
777
777
  interface GridStateController {
778
778
  state: GridState;
package/dist/index.js CHANGED
@@ -192,7 +192,8 @@ var buttonVariants = cva(
192
192
  var Button = forwardRef(
193
193
  ({ className, variant, size, asChild = false, ...props }, ref) => {
194
194
  const Comp = asChild ? Slot : "button";
195
- return /* @__PURE__ */ jsx(Comp, { className: cn(buttonVariants({ variant, size, className })), ref, ...props });
195
+ const variantClass = buttonVariants({ variant, size });
196
+ return /* @__PURE__ */ jsx(Comp, { className: className ? cn(variantClass, className) : variantClass, ref, ...props });
196
197
  }
197
198
  );
198
199
  Button.displayName = "Button";
@@ -648,6 +649,15 @@ function toNumber(value) {
648
649
  }
649
650
  return null;
650
651
  }
652
+ var currencyFormatters = /* @__PURE__ */ new Map();
653
+ function currencyFormatter(currency) {
654
+ let formatter = currencyFormatters.get(currency);
655
+ if (!formatter) {
656
+ formatter = new Intl.NumberFormat(void 0, { style: "currency", currency });
657
+ currencyFormatters.set(currency, formatter);
658
+ }
659
+ return formatter;
660
+ }
651
661
  function defaultFormat(column, value) {
652
662
  if (value === null || value === void 0) return "";
653
663
  switch (column.type) {
@@ -660,10 +670,7 @@ function defaultFormat(column, value) {
660
670
  case "currency": {
661
671
  const num = toNumber(value);
662
672
  if (num === null) return String(value);
663
- return new Intl.NumberFormat(void 0, {
664
- style: "currency",
665
- currency: column.currency ?? "USD"
666
- }).format(num);
673
+ return currencyFormatter(column.currency ?? "USD").format(num);
667
674
  }
668
675
  case "date": {
669
676
  const date = toDate(value);
@@ -1508,6 +1515,7 @@ function resolveMenuItems(items, ctx) {
1508
1515
 
1509
1516
  // src/menu/ActionsMenu.tsx
1510
1517
  import { MoreVertical } from "lucide-react";
1518
+ import { useMemo as useMemo3 } from "react";
1511
1519
 
1512
1520
  // src/components/ui/dropdown-menu.tsx
1513
1521
  import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
@@ -1563,7 +1571,10 @@ DropdownMenuLabel.displayName = "DropdownMenuLabel";
1563
1571
  // src/menu/ActionsMenu.tsx
1564
1572
  import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
1565
1573
  function ActionsMenu({ items, ctx, triggerLabel }) {
1566
- const resolved = resolveMenuItems(items, ctx);
1574
+ const resolved = useMemo3(
1575
+ () => resolveMenuItems(items, ctx),
1576
+ [items, ctx.row, ctx.selectedRows]
1577
+ );
1567
1578
  if (resolved.length === 0) return null;
1568
1579
  return /* @__PURE__ */ jsxs11(DropdownMenu, { children: [
1569
1580
  /* @__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 }) }) }),
@@ -1585,7 +1596,7 @@ import { BarsArrowDownIcon, BarsArrowUpIcon, FunnelIcon as FunnelIcon7 } from "@
1585
1596
  import { columnResizingFeature, columnSizingFeature, flexRender, tableFeatures, useTable } from "@tanstack/react-table";
1586
1597
  import { useVirtualizer } from "@tanstack/react-virtual";
1587
1598
  import { ChevronRight, Loader2 } from "lucide-react";
1588
- import { Fragment as Fragment2, useEffect as useEffect7, useMemo as useMemo5, useRef as useRef5, useState as useState6 } from "react";
1599
+ import { Fragment as Fragment2, useEffect as useEffect7, useMemo as useMemo6, useRef as useRef5, useState as useState6 } from "react";
1589
1600
 
1590
1601
  // src/grid/processClientData.ts
1591
1602
  function makeComparator(sort) {
@@ -1613,10 +1624,10 @@ function processClientData(data, state) {
1613
1624
  }
1614
1625
 
1615
1626
  // src/grid/useGridState.ts
1616
- import { useCallback as useCallback2, useEffect as useEffect6, useMemo as useMemo4, useState as useState5 } from "react";
1627
+ import { useCallback as useCallback2, useEffect as useEffect6, useMemo as useMemo5, useState as useState5 } from "react";
1617
1628
 
1618
1629
  // src/hooks/useDebouncedCallback.ts
1619
- import { useEffect as useEffect5, useMemo as useMemo3, useRef as useRef4 } from "react";
1630
+ import { useEffect as useEffect5, useMemo as useMemo4, useRef as useRef4 } from "react";
1620
1631
  function useDebouncedCallback(callback, delayMs) {
1621
1632
  const callbackRef = useRef4(callback);
1622
1633
  callbackRef.current = callback;
@@ -1626,7 +1637,7 @@ function useDebouncedCallback(callback, delayMs) {
1626
1637
  clearTimeout(timeoutRef.current);
1627
1638
  };
1628
1639
  }, []);
1629
- return useMemo3(() => {
1640
+ return useMemo4(() => {
1630
1641
  return {
1631
1642
  run: (...args) => {
1632
1643
  clearTimeout(timeoutRef.current);
@@ -1676,7 +1687,7 @@ function useGridState(dataSource, initialState, externalState) {
1676
1687
  cancelDebouncedNotify();
1677
1688
  setState(externalState);
1678
1689
  }, [externalState, cancelDebouncedNotify]);
1679
- const filtersByColumn = useMemo4(() => {
1690
+ const filtersByColumn = useMemo5(() => {
1680
1691
  const map = /* @__PURE__ */ new Map();
1681
1692
  for (const child of state.filter?.filters ?? []) {
1682
1693
  if ("operator" in child) map.set(fieldKey(child.field), child);
@@ -1743,9 +1754,12 @@ function useGridState(dataSource, initialState, externalState) {
1743
1754
  import { Fragment as Fragment3, jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
1744
1755
  var gridTableFeatures = tableFeatures({ columnSizingFeature, columnResizingFeature });
1745
1756
  var DEFAULT_COLUMN_SIZE = 150;
1746
- function pinnedOffsets(visibleColumns, side, getSize) {
1757
+ var DETAIL_COLUMN_WIDTH = 44;
1758
+ var SELECTION_COLUMN_WIDTH = 44;
1759
+ var ROW_ACTIONS_COLUMN_WIDTH = 44;
1760
+ function pinnedOffsets(visibleColumns, side, getSize, leadingOffset) {
1747
1761
  const offsets = /* @__PURE__ */ new Map();
1748
- let cursor = 0;
1762
+ let cursor = leadingOffset;
1749
1763
  const ordered = side === "left" ? visibleColumns : [...visibleColumns].reverse();
1750
1764
  for (const column of ordered) {
1751
1765
  if (column.pinned !== side) continue;
@@ -1789,7 +1803,7 @@ function DataGrid({
1789
1803
  renderDetail,
1790
1804
  enableColumnResizing = false,
1791
1805
  columnResizeMode = "onChange",
1792
- columnSizing,
1806
+ columnSizing: controlledColumnSizing,
1793
1807
  onColumnSizingChange,
1794
1808
  virtualize,
1795
1809
  showPagination = true,
@@ -1812,6 +1826,12 @@ function DataGrid({
1812
1826
  setInternalSelectedIds(next);
1813
1827
  onSelectedIdsChange?.(next);
1814
1828
  }
1829
+ const [internalColumnSizing, setInternalColumnSizing] = useState6({});
1830
+ const columnSizing = controlledColumnSizing ?? internalColumnSizing;
1831
+ function updateColumnSizing(next) {
1832
+ setInternalColumnSizing(next);
1833
+ onColumnSizingChange?.(next);
1834
+ }
1815
1835
  const [expandedIds, setExpandedIds] = useState6(/* @__PURE__ */ new Set());
1816
1836
  function toggleRowExpanded(id) {
1817
1837
  setExpandedIds((prev) => {
@@ -1821,16 +1841,16 @@ function DataGrid({
1821
1841
  return next;
1822
1842
  });
1823
1843
  }
1824
- const visibleColumns = useMemo5(
1844
+ const visibleColumns = useMemo6(
1825
1845
  () => columnVisibility ? columns.filter((column) => isColumnVisible(columnVisibility, column.id)) : columns,
1826
1846
  [columns, columnVisibility]
1827
1847
  );
1828
1848
  const serverRowCount = dataSource.mode === "server" ? dataSource.rowCount : void 0;
1829
- const { rows, rowCount } = useMemo5(() => {
1849
+ const { rows, rowCount } = useMemo6(() => {
1830
1850
  if (dataSource.mode === "client") return processClientData(dataSource.data, state);
1831
1851
  return { rows: dataSource.data, rowCount: serverRowCount ?? 0 };
1832
1852
  }, [dataSource.mode, dataSource.data, serverRowCount, state]);
1833
- const selectedRows = useMemo5(
1853
+ const selectedRows = useMemo6(
1834
1854
  () => rows.filter((row) => selectedIds.has(getRowId(row))),
1835
1855
  [rows, selectedIds, getRowId]
1836
1856
  );
@@ -1849,8 +1869,8 @@ function DataGrid({
1849
1869
  }
1850
1870
  updateSelectedIds(next);
1851
1871
  }
1852
- const tanstackColumns = useMemo5(() => toTanstackColumns(visibleColumns), [visibleColumns]);
1853
- const columnById = useMemo5(() => {
1872
+ const tanstackColumns = useMemo6(() => toTanstackColumns(visibleColumns), [visibleColumns]);
1873
+ const columnById = useMemo6(() => {
1854
1874
  const map = /* @__PURE__ */ new Map();
1855
1875
  for (const column of visibleColumns) map.set(column.id, column);
1856
1876
  return map;
@@ -1862,13 +1882,11 @@ function DataGrid({
1862
1882
  getRowId,
1863
1883
  enableColumnResizing,
1864
1884
  columnResizeMode,
1865
- ...columnSizing ? {
1866
- state: { columnSizing },
1867
- onColumnSizingChange: (updater) => {
1868
- const next = typeof updater === "function" ? updater(columnSizing) : updater;
1869
- onColumnSizingChange?.(next);
1870
- }
1871
- } : {}
1885
+ state: { columnSizing },
1886
+ onColumnSizingChange: (updater) => {
1887
+ const next = typeof updater === "function" ? updater(columnSizing) : updater;
1888
+ updateColumnSizing(next);
1889
+ }
1872
1890
  });
1873
1891
  const pageCount = Math.max(1, Math.ceil(rowCount / state.pageSize));
1874
1892
  const loading = loadingProp || (dataSource.mode === "server" ? Boolean(dataSource.loading) : false);
@@ -1877,7 +1895,7 @@ function DataGrid({
1877
1895
  const showDetailColumn = Boolean(renderDetail);
1878
1896
  const totalColumnCount = visibleColumns.length + (showDetailColumn ? 1 : 0) + (showSelectionColumn ? 1 : 0) + (showRowActionsColumn ? 1 : 0);
1879
1897
  const hasFilterRow = visibleColumns.some((column) => isFilterable(column) && column.filterDisplay === "row");
1880
- const headerRuns = useMemo5(() => computeHeaderRuns(visibleColumns), [visibleColumns]);
1898
+ const headerRuns = useMemo6(() => computeHeaderRuns(visibleColumns), [visibleColumns]);
1881
1899
  const hasHeaderGroups = visibleColumns.some((column) => column.headerGroup);
1882
1900
  function renderFilterWidget(column) {
1883
1901
  const value = filtersByColumn.get(column.id);
@@ -1885,19 +1903,79 @@ function DataGrid({
1885
1903
  return column.renderFilter ? column.renderFilter(value, onChange, state.filter) : renderDefaultFilterWidget(column, value, onChange);
1886
1904
  }
1887
1905
  const columnSize = (columnId) => table.getColumn(columnId)?.getSize() ?? DEFAULT_COLUMN_SIZE;
1888
- const leftPinnedOffsets = pinnedOffsets(visibleColumns, "left", columnSize);
1889
- const rightPinnedOffsets = pinnedOffsets(visibleColumns, "right", columnSize);
1906
+ const leadingStructuralWidth = (showDetailColumn ? DETAIL_COLUMN_WIDTH : 0) + (showSelectionColumn ? SELECTION_COLUMN_WIDTH : 0);
1907
+ const trailingStructuralWidth = showRowActionsColumn ? ROW_ACTIONS_COLUMN_WIDTH : 0;
1908
+ const leftPinnedOffsets = useMemo6(
1909
+ () => pinnedOffsets(visibleColumns, "left", columnSize, leadingStructuralWidth),
1910
+ [visibleColumns, columnSizing, leadingStructuralWidth]
1911
+ );
1912
+ const rightPinnedOffsets = useMemo6(
1913
+ () => pinnedOffsets(visibleColumns, "right", columnSize, trailingStructuralWidth),
1914
+ [visibleColumns, columnSizing, trailingStructuralWidth]
1915
+ );
1890
1916
  function pinnedCellProps(column, area = "body") {
1891
1917
  const bg = area === "header" ? "bg-muted" : "bg-background";
1892
1918
  if (column.pinned === "left") {
1893
- return { className: cn("sticky z-10", bg), style: { left: leftPinnedOffsets.get(column.id), width: columnSize(column.id) } };
1919
+ return { className: `sticky z-10 ${bg}`, style: { left: leftPinnedOffsets.get(column.id), width: columnSize(column.id) } };
1894
1920
  }
1895
1921
  if (column.pinned === "right") {
1896
- return { className: cn("sticky z-10", bg), style: { right: rightPinnedOffsets.get(column.id), width: columnSize(column.id) } };
1922
+ return { className: `sticky z-10 ${bg}`, style: { right: rightPinnedOffsets.get(column.id), width: columnSize(column.id) } };
1897
1923
  }
1898
1924
  if (enableColumnResizing) return { style: { width: columnSize(column.id) } };
1899
1925
  return { style: column.width ? { width: column.width } : void 0 };
1900
1926
  }
1927
+ function structuralCellProps(side, offset, width, area = "body") {
1928
+ const bg = area === "header" ? "bg-muted" : "bg-background";
1929
+ return { className: `border-b p-1 sticky z-10 ${bg}`, style: { [side]: offset, width } };
1930
+ }
1931
+ const detailCellProps = structuralCellProps("left", 0, DETAIL_COLUMN_WIDTH);
1932
+ const selectionCellProps = structuralCellProps(
1933
+ "left",
1934
+ showDetailColumn ? DETAIL_COLUMN_WIDTH : 0,
1935
+ SELECTION_COLUMN_WIDTH
1936
+ );
1937
+ const rowActionsCellProps = structuralCellProps("right", 0, ROW_ACTIONS_COLUMN_WIDTH);
1938
+ const detailHeaderCellProps = structuralCellProps("left", 0, DETAIL_COLUMN_WIDTH, "header");
1939
+ const selectionHeaderCellProps = structuralCellProps(
1940
+ "left",
1941
+ showDetailColumn ? DETAIL_COLUMN_WIDTH : 0,
1942
+ SELECTION_COLUMN_WIDTH,
1943
+ "header"
1944
+ );
1945
+ const rowActionsHeaderCellProps = structuralCellProps("right", 0, ROW_ACTIONS_COLUMN_WIDTH, "header");
1946
+ const BODY_TD_BASE_CLASS = "border-b p-2";
1947
+ function bodyCellClassAndStyle(column) {
1948
+ const pinnedProps = column.pinned || enableColumnResizing ? pinnedCellProps(column) : void 0;
1949
+ return {
1950
+ className: pinnedProps?.className ? `${BODY_TD_BASE_CLASS} ${pinnedProps.className}` : BODY_TD_BASE_CLASS,
1951
+ style: pinnedProps?.style
1952
+ };
1953
+ }
1954
+ const bodyCellPropsByColumn = useMemo6(() => {
1955
+ const map = /* @__PURE__ */ new Map();
1956
+ for (const column of visibleColumns) map.set(column.id, bodyCellClassAndStyle(column));
1957
+ return map;
1958
+ }, [visibleColumns, enableColumnResizing, leftPinnedOffsets, rightPinnedOffsets, columnSizing]);
1959
+ function headerCellClassAndStyle(column, base) {
1960
+ const pinnedProps = pinnedCellProps(column, "header");
1961
+ const align = alignClassName(column);
1962
+ const className = pinnedProps.className ? `${base} ${align} ${pinnedProps.className}` : `${base} ${align}`;
1963
+ return { className, style: pinnedProps.style };
1964
+ }
1965
+ const leafHeaderCellPropsByColumn = useMemo6(() => {
1966
+ const map = /* @__PURE__ */ new Map();
1967
+ for (const column of visibleColumns) {
1968
+ map.set(column.id, headerCellClassAndStyle(column, "relative border-b p-2 font-medium"));
1969
+ }
1970
+ return map;
1971
+ }, [visibleColumns, enableColumnResizing, leftPinnedOffsets, rightPinnedOffsets, columnSizing]);
1972
+ const filterHeaderCellPropsByColumn = useMemo6(() => {
1973
+ const map = /* @__PURE__ */ new Map();
1974
+ for (const column of visibleColumns) {
1975
+ map.set(column.id, headerCellClassAndStyle(column, "border-b p-2 align-top font-normal"));
1976
+ }
1977
+ return map;
1978
+ }, [visibleColumns, enableColumnResizing, leftPinnedOffsets, rightPinnedOffsets, columnSizing]);
1901
1979
  const tableRows = table.getRowModel().rows;
1902
1980
  const shouldVirtualize = Boolean(virtualize) && tableRows.length > (virtualize?.threshold ?? 100);
1903
1981
  const scrollRef = useRef5(null);
@@ -1931,22 +2009,30 @@ function DataGrid({
1931
2009
  {
1932
2010
  ...rowProps,
1933
2011
  "data-testid": rowTestId,
1934
- className: cn(rowProps?.className, row.index % 2 === 1 && "bg-foreground/5"),
2012
+ className: rowProps?.className ? cn(rowProps.className, row.index % 2 === 1 && "bg-foreground/5") : row.index % 2 === 1 ? "bg-foreground/5" : void 0,
1935
2013
  children: [
1936
- showDetailColumn && /* @__PURE__ */ jsx19("td", { className: "border-b p-2", children: /* @__PURE__ */ jsx19(
1937
- "button",
2014
+ showDetailColumn && /* @__PURE__ */ jsx19("td", { className: detailCellProps.className, style: detailCellProps.style, children: /* @__PURE__ */ jsx19(
2015
+ Button,
1938
2016
  {
1939
2017
  type: "button",
2018
+ variant: "ghost",
2019
+ size: "icon",
1940
2020
  "aria-label": `${isExpanded ? "Collapse" : "Expand"} row ${row.id}`,
1941
2021
  "aria-expanded": isExpanded,
1942
2022
  onClick: (event) => {
1943
2023
  stopRowClick(event);
1944
2024
  toggleRowExpanded(row.id);
1945
2025
  },
1946
- children: /* @__PURE__ */ jsx19(ChevronRight, { className: cn("h-4 w-4 transition-transform", isExpanded && "rotate-90"), "aria-hidden": true })
2026
+ children: /* @__PURE__ */ jsx19(
2027
+ ChevronRight,
2028
+ {
2029
+ className: `h-4 w-4 transition-transform${isExpanded ? " rotate-90" : ""}`,
2030
+ "aria-hidden": true
2031
+ }
2032
+ )
1947
2033
  }
1948
2034
  ) }),
1949
- showSelectionColumn && /* @__PURE__ */ jsx19("td", { className: "border-b p-2", children: /* @__PURE__ */ jsx19(
2035
+ showSelectionColumn && /* @__PURE__ */ jsx19("td", { className: selectionCellProps.className, style: selectionCellProps.style, children: /* @__PURE__ */ jsx19(
1950
2036
  "input",
1951
2037
  {
1952
2038
  type: "checkbox",
@@ -1957,11 +2043,10 @@ function DataGrid({
1957
2043
  }
1958
2044
  ) }),
1959
2045
  row.getAllCells().map((cell) => {
1960
- const cellColumn = columnById.get(cell.column.id);
1961
- const pinnedProps = cellColumn && (cellColumn.pinned || enableColumnResizing) ? pinnedCellProps(cellColumn) : void 0;
1962
- return /* @__PURE__ */ jsx19("td", { style: pinnedProps?.style, className: cn("border-b p-2", pinnedProps?.className), children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id);
2046
+ const cellProps = bodyCellPropsByColumn.get(cell.column.id);
2047
+ return /* @__PURE__ */ jsx19("td", { style: cellProps?.style, className: cellProps?.className ?? BODY_TD_BASE_CLASS, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id);
1963
2048
  }),
1964
- showRowActionsColumn && rowActions && /* @__PURE__ */ jsx19("td", { className: "border-b p-2", children: /* @__PURE__ */ jsx19(ActionsMenu, { items: rowActions, ctx: { row: row.original }, triggerLabel: `Row actions for ${row.id}` }) })
2049
+ showRowActionsColumn && rowActions && /* @__PURE__ */ jsx19("td", { className: rowActionsCellProps.className, style: rowActionsCellProps.style, children: /* @__PURE__ */ jsx19(ActionsMenu, { items: rowActions, ctx: { row: row.original }, triggerLabel: `Row actions for ${row.id}` }) })
1965
2050
  ]
1966
2051
  }
1967
2052
  ),
@@ -1992,7 +2077,7 @@ function DataGrid({
1992
2077
  "data-testid": testId,
1993
2078
  className: cn("overflow-x-auto rounded-md border", shouldVirtualize && "overflow-y-auto"),
1994
2079
  style: shouldVirtualize ? { maxHeight: virtualize?.maxBodyHeight ?? 480 } : void 0,
1995
- children: /* @__PURE__ */ jsxs12("table", { className: "w-full border-collapse text-sm", children: [
2080
+ children: /* @__PURE__ */ jsxs12("table", { className: cn("w-full border-collapse text-sm", enableColumnResizing && "table-fixed"), children: [
1996
2081
  /* @__PURE__ */ jsxs12("thead", { className: "bg-muted", children: [
1997
2082
  table.getHeaderGroups().map((headerGroup) => {
1998
2083
  const headerById = new Map(headerGroup.headers.map((header) => [header.id, header]));
@@ -2002,63 +2087,70 @@ function DataGrid({
2002
2087
  const sortEntry = state.sort.find((s) => s.field === column.id);
2003
2088
  const sortable = isSortable(column);
2004
2089
  const filterable = isFilterable(column) && column.filterDisplay !== "row";
2005
- const pinnedProps = pinnedCellProps(column, "header");
2006
- return /* @__PURE__ */ jsxs12(
2007
- "th",
2008
- {
2009
- rowSpan,
2010
- style: pinnedProps.style,
2011
- className: cn("relative border-b p-2 font-medium", alignClassName(column), pinnedProps.className),
2012
- children: [
2013
- /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1", children: [
2014
- /* @__PURE__ */ jsxs12(
2015
- "button",
2016
- {
2017
- type: "button",
2018
- className: cn(
2019
- "flex items-center gap-1 rounded-sm px-1",
2020
- sortable && "hover:bg-accent hover:text-accent-foreground"
2021
- ),
2022
- disabled: !sortable,
2023
- onClick: (event) => sortable && toggleSort(column.id, enableMultiSort && event.shiftKey, column.sortDescFirst === true),
2024
- children: [
2025
- column.renderHeader ? column.renderHeader(column) : column.header,
2026
- sortable && sortEntry?.dir === "asc" && /* @__PURE__ */ jsx19(BarsArrowUpIcon, { className: "h-3 w-3", "aria-hidden": true }),
2027
- sortable && sortEntry?.dir === "desc" && /* @__PURE__ */ jsx19(BarsArrowDownIcon, { className: "h-3 w-3", "aria-hidden": true })
2028
- ]
2029
- }
2090
+ const cellProps = leafHeaderCellPropsByColumn.get(column.id);
2091
+ return /* @__PURE__ */ jsxs12("th", { rowSpan, style: cellProps?.style, className: cellProps?.className, children: [
2092
+ /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1", children: [
2093
+ /* @__PURE__ */ jsxs12(
2094
+ "button",
2095
+ {
2096
+ type: "button",
2097
+ className: cn(
2098
+ "flex items-center gap-1 rounded-sm px-1",
2099
+ sortable && "hover:bg-accent hover:text-accent-foreground"
2030
2100
  ),
2031
- filterable && /* @__PURE__ */ jsxs12(Popover, { children: [
2032
- /* @__PURE__ */ jsx19(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx19(Button, { variant: "ghost", size: "icon", "aria-label": `Filter ${column.header}`, children: /* @__PURE__ */ jsx19(FunnelIcon7, { className: "h-3 w-3", "aria-hidden": true }) }) }),
2033
- /* @__PURE__ */ jsx19(PopoverContent, { children: renderFilterWidget(column) })
2034
- ] })
2035
- ] }),
2036
- enableColumnResizing && /* @__PURE__ */ jsx19(
2037
- "div",
2038
- {
2039
- "data-testid": `resize-handle-${column.id}`,
2040
- onMouseDown: header.getResizeHandler(),
2041
- onTouchStart: header.getResizeHandler(),
2042
- className: "absolute right-0 top-0 h-full w-1 cursor-col-resize touch-none select-none hover:bg-accent"
2043
- }
2044
- )
2045
- ]
2046
- },
2047
- header.id
2048
- );
2101
+ disabled: !sortable,
2102
+ onClick: (event) => sortable && toggleSort(column.id, enableMultiSort && event.shiftKey, column.sortDescFirst === true),
2103
+ children: [
2104
+ column.renderHeader ? column.renderHeader(column) : column.header,
2105
+ sortable && sortEntry?.dir === "asc" && /* @__PURE__ */ jsx19(BarsArrowUpIcon, { className: "h-3 w-3", "aria-hidden": true }),
2106
+ sortable && sortEntry?.dir === "desc" && /* @__PURE__ */ jsx19(BarsArrowDownIcon, { className: "h-3 w-3", "aria-hidden": true })
2107
+ ]
2108
+ }
2109
+ ),
2110
+ filterable && /* @__PURE__ */ jsxs12(Popover, { children: [
2111
+ /* @__PURE__ */ jsx19(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx19(Button, { variant: "ghost", size: "icon", "aria-label": `Filter ${column.header}`, children: /* @__PURE__ */ jsx19(FunnelIcon7, { className: "h-3 w-3", "aria-hidden": true }) }) }),
2112
+ /* @__PURE__ */ jsx19(PopoverContent, { children: renderFilterWidget(column) })
2113
+ ] })
2114
+ ] }),
2115
+ enableColumnResizing && /* @__PURE__ */ jsx19(
2116
+ "div",
2117
+ {
2118
+ "data-testid": `resize-handle-${column.id}`,
2119
+ onMouseDown: header.getResizeHandler(),
2120
+ onTouchStart: header.getResizeHandler(),
2121
+ className: "absolute right-0 top-0 h-full w-1 cursor-col-resize touch-none select-none hover:bg-accent"
2122
+ }
2123
+ )
2124
+ ] }, header.id);
2049
2125
  }
2050
2126
  return /* @__PURE__ */ jsxs12(Fragment2, { children: [
2051
2127
  hasHeaderGroups && /* @__PURE__ */ jsxs12("tr", { "data-testid": "header-group-row", children: [
2052
- showDetailColumn && /* @__PURE__ */ jsx19("th", { rowSpan: 2, className: "border-b p-2", "aria-hidden": true }),
2053
- showSelectionColumn && /* @__PURE__ */ jsx19("th", { rowSpan: 2, className: "border-b p-2", children: /* @__PURE__ */ jsx19(
2054
- "input",
2128
+ showDetailColumn && /* @__PURE__ */ jsx19(
2129
+ "th",
2055
2130
  {
2056
- type: "checkbox",
2057
- "aria-label": "Select all rows on this page",
2058
- checked: allOnPageSelected,
2059
- onChange: toggleAllOnPageSelected
2131
+ rowSpan: 2,
2132
+ className: detailHeaderCellProps.className,
2133
+ style: detailHeaderCellProps.style,
2134
+ "aria-hidden": true
2135
+ }
2136
+ ),
2137
+ showSelectionColumn && /* @__PURE__ */ jsx19(
2138
+ "th",
2139
+ {
2140
+ rowSpan: 2,
2141
+ className: selectionHeaderCellProps.className,
2142
+ style: selectionHeaderCellProps.style,
2143
+ children: /* @__PURE__ */ jsx19(
2144
+ "input",
2145
+ {
2146
+ type: "checkbox",
2147
+ "aria-label": "Select all rows on this page",
2148
+ checked: allOnPageSelected,
2149
+ onChange: toggleAllOnPageSelected
2150
+ }
2151
+ )
2060
2152
  }
2061
- ) }),
2153
+ ),
2062
2154
  headerRuns.map(
2063
2155
  (run) => run.kind === "single" ? renderLeafHeaderCell(run.column, 2) : /* @__PURE__ */ jsx19(
2064
2156
  "th",
@@ -2070,44 +2162,77 @@ function DataGrid({
2070
2162
  `group-${run.label}-${run.columns[0].id}`
2071
2163
  )
2072
2164
  ),
2073
- showRowActionsColumn && /* @__PURE__ */ jsx19("th", { rowSpan: 2, className: "border-b p-2", "aria-label": "Row actions" })
2165
+ showRowActionsColumn && /* @__PURE__ */ jsx19(
2166
+ "th",
2167
+ {
2168
+ rowSpan: 2,
2169
+ className: rowActionsHeaderCellProps.className,
2170
+ style: rowActionsHeaderCellProps.style,
2171
+ "aria-label": "Row actions"
2172
+ }
2173
+ )
2074
2174
  ] }, `${headerGroup.id}-groups`),
2075
2175
  /* @__PURE__ */ jsxs12("tr", { children: [
2076
- !hasHeaderGroups && showDetailColumn && /* @__PURE__ */ jsx19("th", { className: "border-b p-2", "aria-hidden": true }),
2077
- !hasHeaderGroups && showSelectionColumn && /* @__PURE__ */ jsx19("th", { className: "border-b p-2", children: /* @__PURE__ */ jsx19(
2078
- "input",
2176
+ !hasHeaderGroups && showDetailColumn && /* @__PURE__ */ jsx19(
2177
+ "th",
2178
+ {
2179
+ className: detailHeaderCellProps.className,
2180
+ style: detailHeaderCellProps.style,
2181
+ "aria-hidden": true
2182
+ }
2183
+ ),
2184
+ !hasHeaderGroups && showSelectionColumn && /* @__PURE__ */ jsx19(
2185
+ "th",
2079
2186
  {
2080
- type: "checkbox",
2081
- "aria-label": "Select all rows on this page",
2082
- checked: allOnPageSelected,
2083
- onChange: toggleAllOnPageSelected
2187
+ className: selectionHeaderCellProps.className,
2188
+ style: selectionHeaderCellProps.style,
2189
+ children: /* @__PURE__ */ jsx19(
2190
+ "input",
2191
+ {
2192
+ type: "checkbox",
2193
+ "aria-label": "Select all rows on this page",
2194
+ checked: allOnPageSelected,
2195
+ onChange: toggleAllOnPageSelected
2196
+ }
2197
+ )
2084
2198
  }
2085
- ) }),
2199
+ ),
2086
2200
  headerGroup.headers.map((header) => {
2087
2201
  const column = columnById.get(header.id);
2088
2202
  if (!column || hasHeaderGroups && !column.headerGroup) return null;
2089
2203
  return renderLeafHeaderCell(column);
2090
2204
  }),
2091
- !hasHeaderGroups && showRowActionsColumn && /* @__PURE__ */ jsx19("th", { className: "border-b p-2", "aria-label": "Row actions" })
2205
+ !hasHeaderGroups && showRowActionsColumn && /* @__PURE__ */ jsx19(
2206
+ "th",
2207
+ {
2208
+ className: rowActionsHeaderCellProps.className,
2209
+ style: rowActionsHeaderCellProps.style,
2210
+ "aria-label": "Row actions"
2211
+ }
2212
+ )
2092
2213
  ] }, headerGroup.id)
2093
2214
  ] }, headerGroup.id);
2094
2215
  }),
2095
2216
  hasFilterRow && /* @__PURE__ */ jsxs12("tr", { "data-testid": "filter-row", children: [
2096
- showDetailColumn && /* @__PURE__ */ jsx19("th", { className: "border-b p-2" }),
2097
- showSelectionColumn && /* @__PURE__ */ jsx19("th", { className: "border-b p-2" }),
2217
+ showDetailColumn && /* @__PURE__ */ jsx19("th", { className: detailHeaderCellProps.className, style: detailHeaderCellProps.style }),
2218
+ showSelectionColumn && /* @__PURE__ */ jsx19(
2219
+ "th",
2220
+ {
2221
+ className: selectionHeaderCellProps.className,
2222
+ style: selectionHeaderCellProps.style
2223
+ }
2224
+ ),
2098
2225
  visibleColumns.map((column) => {
2099
- const pinnedProps = pinnedCellProps(column, "header");
2100
- return /* @__PURE__ */ jsx19(
2101
- "th",
2102
- {
2103
- style: pinnedProps.style,
2104
- className: cn("border-b p-2 align-top font-normal", alignClassName(column), pinnedProps.className),
2105
- children: isFilterable(column) && column.filterDisplay === "row" ? renderFilterWidget(column) : null
2106
- },
2107
- column.id
2108
- );
2226
+ const cellProps = filterHeaderCellPropsByColumn.get(column.id);
2227
+ return /* @__PURE__ */ jsx19("th", { style: cellProps?.style, className: cellProps?.className, children: isFilterable(column) && column.filterDisplay === "row" ? renderFilterWidget(column) : null }, column.id);
2109
2228
  }),
2110
- showRowActionsColumn && /* @__PURE__ */ jsx19("th", { className: "border-b p-2" })
2229
+ showRowActionsColumn && /* @__PURE__ */ jsx19(
2230
+ "th",
2231
+ {
2232
+ className: rowActionsHeaderCellProps.className,
2233
+ style: rowActionsHeaderCellProps.style
2234
+ }
2235
+ )
2111
2236
  ] })
2112
2237
  ] }),
2113
2238
  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: [
@@ -2162,7 +2287,7 @@ function DataGrid({
2162
2287
  // src/tree/TreeDataGrid.tsx
2163
2288
  import { useVirtualizer as useVirtualizer2 } from "@tanstack/react-virtual";
2164
2289
  import { AlertCircle, ChevronDown as ChevronDown2, ChevronRight as ChevronRight2, Loader2 as Loader22 } from "lucide-react";
2165
- import { useEffect as useEffect8, useMemo as useMemo7, useRef as useRef6 } from "react";
2290
+ import { useEffect as useEffect8, useMemo as useMemo8, useRef as useRef6 } from "react";
2166
2291
 
2167
2292
  // src/tree/flattenTree.ts
2168
2293
  function flattenTree(roots, accessors, expanded, childrenOverride) {
@@ -2182,7 +2307,7 @@ function flattenTree(roots, accessors, expanded, childrenOverride) {
2182
2307
  }
2183
2308
 
2184
2309
  // src/tree/useTreeState.ts
2185
- import { useCallback as useCallback3, useMemo as useMemo6, useState as useState7 } from "react";
2310
+ import { useCallback as useCallback3, useMemo as useMemo7, useState as useState7 } from "react";
2186
2311
  function useTreeState({
2187
2312
  data,
2188
2313
  getRowId,
@@ -2207,7 +2332,7 @@ function useTreeState({
2207
2332
  const [childrenMap, setChildrenMap] = useState7(/* @__PURE__ */ new Map());
2208
2333
  const [loadingIds, setLoadingIds] = useState7(/* @__PURE__ */ new Set());
2209
2334
  const [errorIds, setErrorIds] = useState7(/* @__PURE__ */ new Map());
2210
- const resolveHasChildren = useMemo6(
2335
+ const resolveHasChildren = useMemo7(
2211
2336
  () => hasChildren ?? ((row) => (getChildren(row)?.length ?? 0) > 0),
2212
2337
  [hasChildren, getChildren]
2213
2338
  );
@@ -2343,7 +2468,7 @@ function TreeDataGrid({
2343
2468
  estimatedRowHeight = 40,
2344
2469
  maxBodyHeight = 480
2345
2470
  }) {
2346
- const accessors = useMemo7(
2471
+ const accessors = useMemo8(
2347
2472
  () => ({ getRowId, getChildren, hasChildren }),
2348
2473
  [getRowId, getChildren, hasChildren]
2349
2474
  );
@@ -2362,13 +2487,18 @@ function TreeDataGrid({
2362
2487
  didAutoExpand.current = true;
2363
2488
  if (initialExpandedLevel > 0) void expandToLevel(initialExpandedLevel);
2364
2489
  }, []);
2365
- const flatRows = useMemo7(
2490
+ const flatRows = useMemo8(
2366
2491
  () => flattenTree(data, accessors, expanded, childrenMap),
2367
2492
  [data, accessors, expanded, childrenMap]
2368
2493
  );
2369
2494
  const treeColId = treeColumnId ?? columns[0]?.id;
2370
2495
  const showRowActionsColumn = Boolean(rowActions?.length);
2371
2496
  const totalColumnCount = columns.length + (showRowActionsColumn ? 1 : 0);
2497
+ const bodyTdClassByColumn = useMemo8(() => {
2498
+ const map = /* @__PURE__ */ new Map();
2499
+ for (const column of columns) map.set(column.id, `p-2 ${alignClassName(column)}`);
2500
+ return map;
2501
+ }, [columns]);
2372
2502
  const scrollRef = useRef6(null);
2373
2503
  const shouldVirtualize = flatRows.length > virtualizeThreshold;
2374
2504
  const virtualizer = useVirtualizer2({
@@ -2387,9 +2517,9 @@ function TreeDataGrid({
2387
2517
  ref: measureRef,
2388
2518
  ...rowProps,
2389
2519
  "data-testid": rowTestId,
2390
- className: cn("border-b", rowProps?.className, index % 2 === 1 && "bg-foreground/5"),
2520
+ className: rowProps?.className ? cn("border-b", rowProps.className, index % 2 === 1 && "bg-foreground/5") : `border-b${index % 2 === 1 ? " bg-foreground/5" : ""}`,
2391
2521
  children: [
2392
- columns.map((column) => /* @__PURE__ */ jsx20("td", { className: cn("p-2", alignClassName(column)), children: column.id === treeColId ? /* @__PURE__ */ jsx20(
2522
+ columns.map((column) => /* @__PURE__ */ jsx20("td", { className: bodyTdClassByColumn.get(column.id), children: column.id === treeColId ? /* @__PURE__ */ jsx20(
2393
2523
  TreeCell,
2394
2524
  {
2395
2525
  flatRow,
@@ -2429,7 +2559,7 @@ function TreeDataGrid({
2429
2559
  "th",
2430
2560
  {
2431
2561
  style: column.width ? { width: column.width } : void 0,
2432
- className: cn("border-b p-2 font-medium", alignClassName(column)),
2562
+ className: `border-b p-2 font-medium ${alignClassName(column)}`,
2433
2563
  children: column.header
2434
2564
  },
2435
2565
  column.id