@bmsuisse/datagrid 0.27.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2076,7 +2076,59 @@ import { BarsArrowDownIcon, BarsArrowUpIcon, FunnelIcon as FunnelIcon7 } from "@
2076
2076
  import { columnResizingFeature, columnSizingFeature, flexRender, tableFeatures, useTable } from "@tanstack/react-table";
2077
2077
  import { useVirtualizer } from "@tanstack/react-virtual";
2078
2078
  import { ChevronRight, Loader2 } from "lucide-react";
2079
- import { Fragment as Fragment2, useEffect as useEffect9, useLayoutEffect, useMemo as useMemo6, useRef as useRef7, useState as useState8 } from "react";
2079
+ import { Fragment as Fragment2, useEffect as useEffect9, useMemo as useMemo7, useRef as useRef8, useState as useState10 } from "react";
2080
+
2081
+ // src/hooks/useGroupExpansion.ts
2082
+ import { useCallback as useCallback2, useState as useState7 } from "react";
2083
+ function useGroupExpansion(defaultGroupsExpanded, controlledExpandedGroups, onExpandedGroupsChange) {
2084
+ const [internalExpandedGroups, setInternalExpandedGroups] = useState7({});
2085
+ const expandedGroups = controlledExpandedGroups ?? internalExpandedGroups;
2086
+ const updateExpandedGroups = useCallback2(
2087
+ (next) => {
2088
+ setInternalExpandedGroups(next);
2089
+ onExpandedGroupsChange?.(next);
2090
+ },
2091
+ [onExpandedGroupsChange]
2092
+ );
2093
+ const isGroupExpanded = useCallback2(
2094
+ (key) => expandedGroups[key] ?? defaultGroupsExpanded,
2095
+ [expandedGroups, defaultGroupsExpanded]
2096
+ );
2097
+ const toggleGroupExpanded = useCallback2(
2098
+ (key) => {
2099
+ updateExpandedGroups({ ...expandedGroups, [key]: !isGroupExpanded(key) });
2100
+ },
2101
+ [expandedGroups, isGroupExpanded, updateExpandedGroups]
2102
+ );
2103
+ return { expandedGroups, isGroupExpanded, toggleGroupExpanded };
2104
+ }
2105
+
2106
+ // src/hooks/useStickyGroupHeaderTop.ts
2107
+ import { useLayoutEffect, useRef as useRef6, useState as useState8 } from "react";
2108
+ function useStickyGroupHeaderTop(enabled) {
2109
+ const theadRef = useRef6(null);
2110
+ const [groupHeaderTop, setGroupHeaderTop] = useState8(0);
2111
+ useLayoutEffect(() => {
2112
+ if (!enabled) return;
2113
+ const theadEl = theadRef.current;
2114
+ if (!theadEl) return;
2115
+ const updateHeight = () => setGroupHeaderTop(theadEl.getBoundingClientRect().height);
2116
+ updateHeight();
2117
+ const observer = new ResizeObserver(updateHeight);
2118
+ observer.observe(theadEl);
2119
+ return () => observer.disconnect();
2120
+ }, [enabled]);
2121
+ return { theadRef, groupHeaderTop };
2122
+ }
2123
+
2124
+ // src/hooks/useVisibleColumns.ts
2125
+ import { useMemo as useMemo4 } from "react";
2126
+ function useVisibleColumns(columns, columnVisibility) {
2127
+ return useMemo4(
2128
+ () => columnVisibility ? columns.filter((column) => isColumnVisible(columnVisibility, column.id)) : columns,
2129
+ [columns, columnVisibility]
2130
+ );
2131
+ }
2080
2132
 
2081
2133
  // src/lib/structuralColumns.ts
2082
2134
  var SELECTION_COLUMN_WIDTH = 44;
@@ -2119,20 +2171,20 @@ function processClientData(data, state) {
2119
2171
  }
2120
2172
 
2121
2173
  // src/grid/useGridState.ts
2122
- import { useCallback as useCallback2, useEffect as useEffect8, useMemo as useMemo5, useState as useState7 } from "react";
2174
+ import { useCallback as useCallback3, useEffect as useEffect8, useMemo as useMemo6, useState as useState9 } from "react";
2123
2175
 
2124
2176
  // src/hooks/useDebouncedCallback.ts
2125
- import { useEffect as useEffect7, useMemo as useMemo4, useRef as useRef6 } from "react";
2177
+ import { useEffect as useEffect7, useMemo as useMemo5, useRef as useRef7 } from "react";
2126
2178
  function useDebouncedCallback(callback, delayMs) {
2127
- const callbackRef = useRef6(callback);
2179
+ const callbackRef = useRef7(callback);
2128
2180
  callbackRef.current = callback;
2129
- const timeoutRef = useRef6(void 0);
2181
+ const timeoutRef = useRef7(void 0);
2130
2182
  useEffect7(() => {
2131
2183
  return () => {
2132
2184
  clearTimeout(timeoutRef.current);
2133
2185
  };
2134
2186
  }, []);
2135
- return useMemo4(() => {
2187
+ return useMemo5(() => {
2136
2188
  return {
2137
2189
  run: (...args) => {
2138
2190
  clearTimeout(timeoutRef.current);
@@ -2157,10 +2209,10 @@ function nextSortDir(current, descFirst) {
2157
2209
  return void 0;
2158
2210
  }
2159
2211
  function useGridState(dataSource, initialState, externalState) {
2160
- const [state, setState] = useState7(
2212
+ const [state, setState] = useState9(
2161
2213
  () => externalState ?? { ...DEFAULT_STATE, ...initialState }
2162
2214
  );
2163
- const notifyRaw = useCallback2(
2215
+ const notifyRaw = useCallback3(
2164
2216
  (next) => {
2165
2217
  if (dataSource.mode === "server") dataSource.onStateChange(next);
2166
2218
  },
@@ -2170,7 +2222,7 @@ function useGridState(dataSource, initialState, externalState) {
2170
2222
  notifyRaw,
2171
2223
  FILTER_CHANGE_DEBOUNCE_MS
2172
2224
  );
2173
- const notifyNow = useCallback2(
2225
+ const notifyNow = useCallback3(
2174
2226
  (next) => {
2175
2227
  cancelDebouncedNotify();
2176
2228
  notifyRaw(next);
@@ -2182,14 +2234,14 @@ function useGridState(dataSource, initialState, externalState) {
2182
2234
  cancelDebouncedNotify();
2183
2235
  setState(externalState);
2184
2236
  }, [externalState, cancelDebouncedNotify]);
2185
- const filtersByColumn = useMemo5(() => {
2237
+ const filtersByColumn = useMemo6(() => {
2186
2238
  const map = /* @__PURE__ */ new Map();
2187
2239
  for (const child of state.filter?.filters ?? []) {
2188
2240
  if ("operator" in child) map.set(fieldKey(child.field), child);
2189
2241
  }
2190
2242
  return map;
2191
2243
  }, [state.filter]);
2192
- const setColumnFilter = useCallback2(
2244
+ const setColumnFilter = useCallback3(
2193
2245
  (columnId, next) => {
2194
2246
  setState((prev) => {
2195
2247
  const remaining = (prev.filter?.filters ?? []).filter(
@@ -2207,7 +2259,7 @@ function useGridState(dataSource, initialState, externalState) {
2207
2259
  },
2208
2260
  [notifyDebounced]
2209
2261
  );
2210
- const toggleSort = useCallback2(
2262
+ const toggleSort = useCallback3(
2211
2263
  (columnId, additive, descFirst = false) => {
2212
2264
  setState((prev) => {
2213
2265
  const existing = prev.sort.find((s) => s.field === columnId);
@@ -2222,7 +2274,7 @@ function useGridState(dataSource, initialState, externalState) {
2222
2274
  },
2223
2275
  [notifyNow]
2224
2276
  );
2225
- const setPage = useCallback2(
2277
+ const setPage = useCallback3(
2226
2278
  (page) => {
2227
2279
  setState((prev) => {
2228
2280
  const nextState = { ...prev, page };
@@ -2232,7 +2284,7 @@ function useGridState(dataSource, initialState, externalState) {
2232
2284
  },
2233
2285
  [notifyNow]
2234
2286
  );
2235
- const setPageSize = useCallback2(
2287
+ const setPageSize = useCallback3(
2236
2288
  (pageSize) => {
2237
2289
  setState((prev) => {
2238
2290
  const nextState = { ...prev, pageSize, page: 0 };
@@ -2322,19 +2374,19 @@ function DataGrid({
2322
2374
  initialState,
2323
2375
  gridState
2324
2376
  );
2325
- const [internalSelectedIds, setInternalSelectedIds] = useState8(/* @__PURE__ */ new Set());
2377
+ const [internalSelectedIds, setInternalSelectedIds] = useState10(/* @__PURE__ */ new Set());
2326
2378
  const selectedIds = controlledSelectedIds ?? internalSelectedIds;
2327
2379
  function updateSelectedIds(next) {
2328
2380
  setInternalSelectedIds(next);
2329
2381
  onSelectedIdsChange?.(next);
2330
2382
  }
2331
- const [internalColumnSizing, setInternalColumnSizing] = useState8({});
2383
+ const [internalColumnSizing, setInternalColumnSizing] = useState10({});
2332
2384
  const columnSizing = controlledColumnSizing ?? internalColumnSizing;
2333
2385
  function updateColumnSizing(next) {
2334
2386
  setInternalColumnSizing(next);
2335
2387
  onColumnSizingChange?.(next);
2336
2388
  }
2337
- const [expandedIds, setExpandedIds] = useState8(/* @__PURE__ */ new Set());
2389
+ const [expandedIds, setExpandedIds] = useState10(/* @__PURE__ */ new Set());
2338
2390
  function toggleRowExpanded(id) {
2339
2391
  setExpandedIds((prev) => {
2340
2392
  const next = new Set(prev);
@@ -2343,32 +2395,22 @@ function DataGrid({
2343
2395
  return next;
2344
2396
  });
2345
2397
  }
2346
- const [internalExpandedGroups, setInternalExpandedGroups] = useState8({});
2347
- const expandedGroupsState = controlledExpandedGroups ?? internalExpandedGroups;
2348
- function updateExpandedGroups(next) {
2349
- setInternalExpandedGroups(next);
2350
- onExpandedGroupsChange?.(next);
2351
- }
2352
- function isGroupExpanded(key) {
2353
- return expandedGroupsState[key] ?? defaultGroupsExpanded;
2354
- }
2355
- function toggleGroupExpanded(key) {
2356
- updateExpandedGroups({ ...expandedGroupsState, [key]: !isGroupExpanded(key) });
2357
- }
2398
+ const { isGroupExpanded, toggleGroupExpanded } = useGroupExpansion(
2399
+ defaultGroupsExpanded,
2400
+ controlledExpandedGroups,
2401
+ onExpandedGroupsChange
2402
+ );
2358
2403
  const { pendingEdits, editErrors, ctxRef: editingCtxRef, handleSaveEdits, handleDiscardEdits } = useEditingState(
2359
2404
  editing,
2360
2405
  getRowId
2361
2406
  );
2362
- const visibleColumns = useMemo6(
2363
- () => columnVisibility ? columns.filter((column) => isColumnVisible(columnVisibility, column.id)) : columns,
2364
- [columns, columnVisibility]
2365
- );
2407
+ const visibleColumns = useVisibleColumns(columns, columnVisibility);
2366
2408
  const serverRowCount = dataSource.mode === "server" ? dataSource.rowCount : void 0;
2367
- const { rows, rowCount } = useMemo6(() => {
2409
+ const { rows, rowCount } = useMemo7(() => {
2368
2410
  if (dataSource.mode === "client") return processClientData(dataSource.data, state);
2369
2411
  return { rows: dataSource.data, rowCount: serverRowCount ?? 0 };
2370
2412
  }, [dataSource.mode, dataSource.data, serverRowCount, state]);
2371
- const selectedRows = useMemo6(
2413
+ const selectedRows = useMemo7(
2372
2414
  () => rows.filter((row) => selectedIds.has(getRowId(row))),
2373
2415
  [rows, selectedIds, getRowId]
2374
2416
  );
@@ -2387,8 +2429,8 @@ function DataGrid({
2387
2429
  }
2388
2430
  updateSelectedIds(next);
2389
2431
  }
2390
- const tanstackColumns = useMemo6(() => toTanstackColumns(visibleColumns, editingCtxRef), [visibleColumns]);
2391
- const columnById = useMemo6(() => {
2432
+ const tanstackColumns = useMemo7(() => toTanstackColumns(visibleColumns, editingCtxRef), [visibleColumns]);
2433
+ const columnById = useMemo7(() => {
2392
2434
  const map = /* @__PURE__ */ new Map();
2393
2435
  for (const column of visibleColumns) map.set(column.id, column);
2394
2436
  return map;
@@ -2413,7 +2455,7 @@ function DataGrid({
2413
2455
  const showDetailColumn = Boolean(renderDetail);
2414
2456
  const totalColumnCount = visibleColumns.length + (showDetailColumn ? 1 : 0) + (showSelectionColumn ? 1 : 0) + (showRowActionsColumn ? 1 : 0);
2415
2457
  const hasFilterRow = visibleColumns.some((column) => isFilterable(column) && column.filterDisplay === "row");
2416
- const headerRuns = useMemo6(() => computeHeaderRuns(visibleColumns), [visibleColumns]);
2458
+ const headerRuns = useMemo7(() => computeHeaderRuns(visibleColumns), [visibleColumns]);
2417
2459
  const hasHeaderGroups = visibleColumns.some((column) => column.headerGroup);
2418
2460
  function renderFilterWidget(column) {
2419
2461
  const value = filtersByColumn.get(column.id);
@@ -2423,11 +2465,11 @@ function DataGrid({
2423
2465
  const columnSize = (columnId) => table.getColumn(columnId)?.getSize() ?? DEFAULT_COLUMN_SIZE;
2424
2466
  const leadingStructuralWidth = (showDetailColumn ? DETAIL_COLUMN_WIDTH : 0) + (showSelectionColumn ? SELECTION_COLUMN_WIDTH : 0);
2425
2467
  const trailingStructuralWidth = showRowActionsColumn ? ROW_ACTIONS_COLUMN_WIDTH : 0;
2426
- const leftPinnedOffsets = useMemo6(
2468
+ const leftPinnedOffsets = useMemo7(
2427
2469
  () => pinnedOffsets(visibleColumns, "left", columnSize, leadingStructuralWidth),
2428
2470
  [visibleColumns, columnSizing, leadingStructuralWidth]
2429
2471
  );
2430
- const rightPinnedOffsets = useMemo6(
2472
+ const rightPinnedOffsets = useMemo7(
2431
2473
  () => pinnedOffsets(visibleColumns, "right", columnSize, trailingStructuralWidth),
2432
2474
  [visibleColumns, columnSizing, trailingStructuralWidth]
2433
2475
  );
@@ -2475,7 +2517,7 @@ function DataGrid({
2475
2517
  style: pinnedProps?.style
2476
2518
  };
2477
2519
  }
2478
- const bodyCellPropsByColumn = useMemo6(() => {
2520
+ const bodyCellPropsByColumn = useMemo7(() => {
2479
2521
  const map = /* @__PURE__ */ new Map();
2480
2522
  for (const column of visibleColumns) map.set(column.id, bodyCellClassAndStyle(column));
2481
2523
  return map;
@@ -2491,7 +2533,7 @@ function DataGrid({
2491
2533
  );
2492
2534
  return { className, style: pinnedProps.style };
2493
2535
  }
2494
- const leafHeaderCellPropsByColumn = useMemo6(() => {
2536
+ const leafHeaderCellPropsByColumn = useMemo7(() => {
2495
2537
  const map = /* @__PURE__ */ new Map();
2496
2538
  for (const column of visibleColumns) {
2497
2539
  map.set(
@@ -2501,7 +2543,7 @@ function DataGrid({
2501
2543
  }
2502
2544
  return map;
2503
2545
  }, [visibleColumns, enableColumnResizing, leftPinnedOffsets, rightPinnedOffsets, columnSizing]);
2504
- const filterHeaderCellPropsByColumn = useMemo6(() => {
2546
+ const filterHeaderCellPropsByColumn = useMemo7(() => {
2505
2547
  const map = /* @__PURE__ */ new Map();
2506
2548
  for (const column of visibleColumns) {
2507
2549
  map.set(column.id, headerCellClassAndStyle(column, "border-b border-border p-2 align-top font-normal"));
@@ -2510,23 +2552,12 @@ function DataGrid({
2510
2552
  }, [visibleColumns, enableColumnResizing, leftPinnedOffsets, rightPinnedOffsets, columnSizing]);
2511
2553
  const tableRows = table.getRowModel().rows;
2512
2554
  const shouldVirtualize = Boolean(virtualize) && !groupBy && tableRows.length > (virtualize?.threshold ?? 100);
2513
- const groupedBuckets = useMemo6(
2555
+ const groupedBuckets = useMemo7(
2514
2556
  () => groupBy ? groupRows(tableRows, (row) => groupBy(row.original)) : void 0,
2515
2557
  [tableRows, groupBy]
2516
2558
  );
2517
- const theadRef = useRef7(null);
2518
- const [groupHeaderTop, setGroupHeaderTop] = useState8(0);
2519
- useLayoutEffect(() => {
2520
- if (!groupBy) return;
2521
- const theadEl = theadRef.current;
2522
- if (!theadEl) return;
2523
- const updateHeight = () => setGroupHeaderTop(theadEl.getBoundingClientRect().height);
2524
- updateHeight();
2525
- const observer = new ResizeObserver(updateHeight);
2526
- observer.observe(theadEl);
2527
- return () => observer.disconnect();
2528
- }, [groupBy]);
2529
- const scrollRef = useRef7(null);
2559
+ const { theadRef, groupHeaderTop } = useStickyGroupHeaderTop(Boolean(groupBy));
2560
+ const scrollRef = useRef8(null);
2530
2561
  const virtualizer = useVirtualizer({
2531
2562
  count: tableRows.length,
2532
2563
  getScrollElement: () => scrollRef.current,
@@ -2538,7 +2569,7 @@ function DataGrid({
2538
2569
  const paddingTop = virtualItems.length > 0 ? virtualItems[0].start : 0;
2539
2570
  const paddingBottom = virtualItems.length > 0 ? virtualizer.getTotalSize() - virtualItems[virtualItems.length - 1].end : 0;
2540
2571
  const lastVisibleIndex = virtualItems.length > 0 ? virtualItems[virtualItems.length - 1].index : -1;
2541
- const notifiedForLengthRef = useRef7(-1);
2572
+ const notifiedForLengthRef = useRef8(-1);
2542
2573
  useEffect9(() => {
2543
2574
  if (!virtualize?.onEndReached) return;
2544
2575
  if (lastVisibleIndex < 0 || lastVisibleIndex < tableRows.length - 1) return;
@@ -2931,7 +2962,7 @@ function DataGrid({
2931
2962
  // src/tree/TreeDataGrid.tsx
2932
2963
  import { useVirtualizer as useVirtualizer2 } from "@tanstack/react-virtual";
2933
2964
  import { AlertCircle, ChevronDown as ChevronDown2, ChevronRight as ChevronRight2, Loader2 as Loader22 } from "lucide-react";
2934
- import { useEffect as useEffect10, useMemo as useMemo8, useRef as useRef8 } from "react";
2965
+ import { Fragment as Fragment4, useEffect as useEffect10, useMemo as useMemo9, useRef as useRef9 } from "react";
2935
2966
 
2936
2967
  // src/tree/flattenTree.ts
2937
2968
  function flattenTree(roots, accessors, expanded, childrenOverride) {
@@ -2976,7 +3007,7 @@ function computeSelectionStates(roots, accessors, childrenOverride, selectedIds)
2976
3007
  }
2977
3008
 
2978
3009
  // src/tree/useTreeState.ts
2979
- import { useCallback as useCallback3, useMemo as useMemo7, useState as useState9 } from "react";
3010
+ import { useCallback as useCallback4, useMemo as useMemo8, useState as useState11 } from "react";
2980
3011
  function useTreeState({
2981
3012
  data,
2982
3013
  getRowId,
@@ -2988,9 +3019,9 @@ function useTreeState({
2988
3019
  childrenMap: controlledChildrenMap,
2989
3020
  onChildrenMapChange
2990
3021
  }) {
2991
- const [internalExpanded, setInternalExpanded] = useState9({});
3022
+ const [internalExpanded, setInternalExpanded] = useState11({});
2992
3023
  const expanded = controlledExpanded ?? internalExpanded;
2993
- const setExpanded = useCallback3(
3024
+ const setExpanded = useCallback4(
2994
3025
  (updater) => {
2995
3026
  if (controlledExpanded) {
2996
3027
  onExpandedChange?.(updater(controlledExpanded));
@@ -3000,9 +3031,9 @@ function useTreeState({
3000
3031
  },
3001
3032
  [controlledExpanded, onExpandedChange]
3002
3033
  );
3003
- const [internalChildrenMap, setInternalChildrenMap] = useState9(/* @__PURE__ */ new Map());
3034
+ const [internalChildrenMap, setInternalChildrenMap] = useState11(/* @__PURE__ */ new Map());
3004
3035
  const childrenMap = controlledChildrenMap ?? internalChildrenMap;
3005
- const setChildrenMap = useCallback3(
3036
+ const setChildrenMap = useCallback4(
3006
3037
  (updater) => {
3007
3038
  if (controlledChildrenMap) {
3008
3039
  onChildrenMapChange?.(updater(controlledChildrenMap));
@@ -3012,13 +3043,13 @@ function useTreeState({
3012
3043
  },
3013
3044
  [controlledChildrenMap, onChildrenMapChange]
3014
3045
  );
3015
- const [loadingIds, setLoadingIds] = useState9(/* @__PURE__ */ new Set());
3016
- const [errorIds, setErrorIds] = useState9(/* @__PURE__ */ new Map());
3017
- const resolveHasChildren = useMemo7(
3046
+ const [loadingIds, setLoadingIds] = useState11(/* @__PURE__ */ new Set());
3047
+ const [errorIds, setErrorIds] = useState11(/* @__PURE__ */ new Map());
3048
+ const resolveHasChildren = useMemo8(
3018
3049
  () => hasChildren ?? ((row) => (getChildren(row)?.length ?? 0) > 0),
3019
3050
  [hasChildren, getChildren]
3020
3051
  );
3021
- const loadChildrenFor = useCallback3(
3052
+ const loadChildrenFor = useCallback4(
3022
3053
  async (row) => {
3023
3054
  const id = getRowId(row);
3024
3055
  const cached = childrenMap.get(id);
@@ -3051,7 +3082,7 @@ function useTreeState({
3051
3082
  },
3052
3083
  [childrenMap, getChildren, getRowId, onLoadChildren, setChildrenMap]
3053
3084
  );
3054
- const toggleExpand = useCallback3(
3085
+ const toggleExpand = useCallback4(
3055
3086
  (row) => {
3056
3087
  const id = getRowId(row);
3057
3088
  const willExpand = !expanded[id];
@@ -3063,14 +3094,14 @@ function useTreeState({
3063
3094
  },
3064
3095
  [expanded, getRowId, loadChildrenFor, resolveHasChildren, setExpanded]
3065
3096
  );
3066
- const retry = useCallback3(
3097
+ const retry = useCallback4(
3067
3098
  (row) => {
3068
3099
  loadChildrenFor(row).catch(() => {
3069
3100
  });
3070
3101
  },
3071
3102
  [loadChildrenFor]
3072
3103
  );
3073
- const expandToLevel = useCallback3(
3104
+ const expandToLevel = useCallback4(
3074
3105
  async (level) => {
3075
3106
  const nextExpanded = { ...expanded };
3076
3107
  async function visit(row, depth) {
@@ -3093,7 +3124,7 @@ function useTreeState({
3093
3124
  }
3094
3125
 
3095
3126
  // src/tree/TreeDataGrid.tsx
3096
- import { Fragment as Fragment4, jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
3127
+ import { Fragment as Fragment5, jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
3097
3128
  var EMPTY_SELECTED_IDS = /* @__PURE__ */ new Set();
3098
3129
  var UNCHECKED_STATE = { checked: false, indeterminate: false };
3099
3130
  function renderCell(column, row, editingCtx) {
@@ -3137,6 +3168,7 @@ function TreeDataGrid({
3137
3168
  columns,
3138
3169
  data,
3139
3170
  treeColumnId,
3171
+ columnVisibility,
3140
3172
  getRowId,
3141
3173
  getChildren,
3142
3174
  hasChildren,
@@ -3157,10 +3189,15 @@ function TreeDataGrid({
3157
3189
  onSelectedIdsChange,
3158
3190
  getRowSelectionState,
3159
3191
  isRowSelectionDisabled,
3192
+ groupBy,
3193
+ renderGroupHeader,
3194
+ defaultGroupsExpanded = true,
3195
+ expandedGroups: controlledExpandedGroups,
3196
+ onExpandedGroupsChange,
3160
3197
  zebra = true,
3161
3198
  editing
3162
3199
  }) {
3163
- const accessors = useMemo8(
3200
+ const accessors = useMemo9(
3164
3201
  () => ({ getRowId, getChildren, hasChildren }),
3165
3202
  [getRowId, getChildren, hasChildren]
3166
3203
  );
@@ -3176,21 +3213,48 @@ function TreeDataGrid({
3176
3213
  childrenMap: controlledChildrenMap,
3177
3214
  onChildrenMapChange
3178
3215
  });
3179
- const didAutoExpand = useRef8(false);
3216
+ const didAutoExpand = useRef9(false);
3180
3217
  useEffect10(() => {
3181
3218
  if (didAutoExpand.current) return;
3182
3219
  didAutoExpand.current = true;
3183
3220
  if (initialExpandedLevel > 0) void expandToLevel(initialExpandedLevel);
3184
3221
  }, []);
3185
- const flatRows = useMemo8(
3186
- () => flattenTree(data, accessors, expanded, childrenMap),
3187
- [data, accessors, expanded, childrenMap]
3222
+ const visibleColumns = useVisibleColumns(columns, columnVisibility);
3223
+ const { isGroupExpanded, toggleGroupExpanded } = useGroupExpansion(
3224
+ defaultGroupsExpanded,
3225
+ controlledExpandedGroups,
3226
+ onExpandedGroupsChange
3227
+ );
3228
+ const flatRows = useMemo9(
3229
+ () => groupBy ? [] : flattenTree(data, accessors, expanded, childrenMap),
3230
+ [groupBy, data, accessors, expanded, childrenMap]
3231
+ );
3232
+ const groupedBuckets = useMemo9(
3233
+ () => groupBy ? groupRows(data, groupBy).map((bucket) => ({
3234
+ key: bucket.key,
3235
+ roots: bucket.items,
3236
+ flatRows: flattenTree(bucket.items, accessors, expanded, childrenMap)
3237
+ })) : void 0,
3238
+ [groupBy, data, accessors, expanded, childrenMap]
3188
3239
  );
3240
+ const groupedRender = useMemo9(() => {
3241
+ if (!groupedBuckets) return void 0;
3242
+ const startIndexByBucket = /* @__PURE__ */ new Map();
3243
+ const rows = [];
3244
+ for (const bucket of groupedBuckets) {
3245
+ if (!isGroupExpanded(bucket.key)) continue;
3246
+ startIndexByBucket.set(bucket.key, rows.length);
3247
+ rows.push(...bucket.flatRows);
3248
+ }
3249
+ return { startIndexByBucket, rows };
3250
+ }, [groupedBuckets, isGroupExpanded]);
3251
+ const allFlatRows = groupedRender ? groupedRender.rows : flatRows;
3189
3252
  const treeColId = treeColumnId ?? columns[0]?.id;
3190
3253
  const showRowActionsColumn = Boolean(rowActions?.length);
3191
3254
  const showSelectionColumn = controlledSelectedIds !== void 0;
3192
- const totalColumnCount = columns.length + (showRowActionsColumn ? 1 : 0) + (showSelectionColumn ? 1 : 0);
3193
- const selectionStates = useMemo8(
3255
+ const totalColumnCount = visibleColumns.length + (showRowActionsColumn ? 1 : 0) + (showSelectionColumn ? 1 : 0);
3256
+ const { theadRef, groupHeaderTop } = useStickyGroupHeaderTop(Boolean(groupBy));
3257
+ const selectionStates = useMemo9(
3194
3258
  () => showSelectionColumn ? computeSelectionStates(data, accessors, childrenMap, controlledSelectedIds ?? EMPTY_SELECTED_IDS) : void 0,
3195
3259
  [showSelectionColumn, data, accessors, childrenMap, controlledSelectedIds]
3196
3260
  );
@@ -3206,9 +3270,9 @@ function TreeDataGrid({
3206
3270
  else next.add(id);
3207
3271
  onSelectedIdsChange?.(next);
3208
3272
  }
3209
- const selectableFlatRows = useMemo8(
3210
- () => flatRows.filter((flatRow) => !(isRowSelectionDisabled?.(flatRow.row) ?? false)),
3211
- [flatRows, isRowSelectionDisabled]
3273
+ const selectableFlatRows = useMemo9(
3274
+ () => allFlatRows.filter((flatRow) => !(isRowSelectionDisabled?.(flatRow.row) ?? false)),
3275
+ [allFlatRows, isRowSelectionDisabled]
3212
3276
  );
3213
3277
  const allVisibleSelected = showSelectionColumn && selectableFlatRows.length > 0 && selectableFlatRows.every((flatRow) => controlledSelectedIds.has(flatRow.id));
3214
3278
  function toggleAllVisibleSelected() {
@@ -3220,13 +3284,13 @@ function TreeDataGrid({
3220
3284
  }
3221
3285
  onSelectedIdsChange?.(next);
3222
3286
  }
3223
- const bodyTdClassByColumn = useMemo8(() => {
3287
+ const bodyTdClassByColumn = useMemo9(() => {
3224
3288
  const map = /* @__PURE__ */ new Map();
3225
- for (const column of columns) map.set(column.id, `p-2 ${alignClassName(column)}`);
3289
+ for (const column of visibleColumns) map.set(column.id, `p-2 ${alignClassName(column)}`);
3226
3290
  return map;
3227
- }, [columns]);
3228
- const scrollRef = useRef8(null);
3229
- const shouldVirtualize = flatRows.length > virtualizeThreshold;
3291
+ }, [visibleColumns]);
3292
+ const scrollRef = useRef9(null);
3293
+ const shouldVirtualize = !groupBy && flatRows.length > virtualizeThreshold;
3230
3294
  const virtualizer = useVirtualizer2({
3231
3295
  count: flatRows.length,
3232
3296
  getScrollElement: () => scrollRef.current,
@@ -3264,7 +3328,7 @@ function TreeDataGrid({
3264
3328
  }
3265
3329
  );
3266
3330
  })() }),
3267
- columns.map((column) => /* @__PURE__ */ jsx29("td", { className: bodyTdClassByColumn.get(column.id), children: column.id === treeColId ? /* @__PURE__ */ jsx29(
3331
+ visibleColumns.map((column) => /* @__PURE__ */ jsx29("td", { className: bodyTdClassByColumn.get(column.id), children: column.id === treeColId ? /* @__PURE__ */ jsx29(
3268
3332
  TreeCell,
3269
3333
  {
3270
3334
  flatRow,
@@ -3312,7 +3376,7 @@ function TreeDataGrid({
3312
3376
  className: shouldVirtualize ? "overflow-y-auto" : void 0,
3313
3377
  style: shouldVirtualize ? { maxHeight: maxBodyHeight } : void 0,
3314
3378
  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: [
3379
+ /* @__PURE__ */ jsx29("thead", { ref: theadRef, children: /* @__PURE__ */ jsxs19("tr", { className: "divide-x divide-border", children: [
3316
3380
  showSelectionColumn && /* @__PURE__ */ jsx29("th", { className: "border-b border-border p-1", style: { width: SELECTION_COLUMN_WIDTH }, children: /* @__PURE__ */ jsx29(
3317
3381
  Checkbox,
3318
3382
  {
@@ -3321,7 +3385,7 @@ function TreeDataGrid({
3321
3385
  onCheckedChange: toggleAllVisibleSelected
3322
3386
  }
3323
3387
  ) }),
3324
- columns.map((column) => /* @__PURE__ */ jsx29(
3388
+ visibleColumns.map((column) => /* @__PURE__ */ jsx29(
3325
3389
  "th",
3326
3390
  {
3327
3391
  style: column.width ? { width: column.width } : void 0,
@@ -3332,7 +3396,45 @@ function TreeDataGrid({
3332
3396
  )),
3333
3397
  showRowActionsColumn && /* @__PURE__ */ jsx29("th", { className: "border-b border-border p-2", "aria-label": "Row actions" })
3334
3398
  ] }) }),
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: [
3399
+ /* @__PURE__ */ jsx29("tbody", { children: data.length === 0 ? /* @__PURE__ */ jsx29("tr", { children: /* @__PURE__ */ jsx29("td", { colSpan: totalColumnCount, className: "p-4 text-center text-muted-foreground", children: "No results." }) }) : groupedBuckets ? groupedBuckets.map((bucket) => {
3400
+ const groupExpanded = isGroupExpanded(bucket.key);
3401
+ return /* @__PURE__ */ jsxs19(Fragment4, { children: [
3402
+ /* @__PURE__ */ jsx29("tr", { "data-testid": `group-header-${bucket.key}`, children: /* @__PURE__ */ jsx29(
3403
+ "td",
3404
+ {
3405
+ colSpan: totalColumnCount,
3406
+ className: cn(
3407
+ "sticky z-20 border-b p-2 font-medium",
3408
+ zebra ? "bg-muted" : "bg-background"
3409
+ ),
3410
+ style: { top: groupHeaderTop },
3411
+ children: /* @__PURE__ */ jsxs19(
3412
+ "button",
3413
+ {
3414
+ type: "button",
3415
+ className: "flex w-full items-center gap-1 text-left",
3416
+ onClick: () => toggleGroupExpanded(bucket.key),
3417
+ "aria-expanded": groupExpanded,
3418
+ children: [
3419
+ /* @__PURE__ */ jsx29(
3420
+ ChevronRight2,
3421
+ {
3422
+ className: `h-4 w-4 shrink-0 transition-transform${groupExpanded ? " rotate-90" : ""}`,
3423
+ "aria-hidden": true
3424
+ }
3425
+ ),
3426
+ renderGroupHeader ? renderGroupHeader(bucket.key, bucket.roots, groupExpanded) : `${bucket.key} (${bucket.roots.length})`
3427
+ ]
3428
+ }
3429
+ )
3430
+ }
3431
+ ) }),
3432
+ groupExpanded && (() => {
3433
+ const startIndex = groupedRender?.startIndexByBucket.get(bucket.key) ?? 0;
3434
+ return bucket.flatRows.map((flatRow, i) => renderRow(flatRow, startIndex + i));
3435
+ })()
3436
+ ] }, `group-${bucket.key}`);
3437
+ }) : shouldVirtualize ? /* @__PURE__ */ jsxs19(Fragment5, { children: [
3336
3438
  paddingTop > 0 && /* @__PURE__ */ jsx29("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx29("td", { colSpan: totalColumnCount, style: { padding: 0, border: "none", height: paddingTop } }) }),
3337
3439
  virtualItems.map(
3338
3440
  (virtualItem) => renderRow(flatRows[virtualItem.index], virtualItem.index, (el) => virtualizer.measureElement(el))
@@ -3399,6 +3501,9 @@ export {
3399
3501
  useDebouncedCallback,
3400
3502
  useEditingState,
3401
3503
  useGridState,
3402
- useTreeState
3504
+ useGroupExpansion,
3505
+ useStickyGroupHeaderTop,
3506
+ useTreeState,
3507
+ useVisibleColumns
3403
3508
  };
3404
3509
  //# sourceMappingURL=index.js.map