@bmsuisse/datagrid 0.25.0 → 0.25.2

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
@@ -1994,7 +1994,7 @@ function DataGrid({
1994
1994
  return { style: column.width ? { width: column.width } : void 0 };
1995
1995
  }
1996
1996
  function structuralCellProps(side, offset, width, area = "body") {
1997
- const base = "border-b border-border p-1 sticky z-10";
1997
+ const base = area === "header" ? "border-b border-border p-1 sticky top-0 z-20" : "border-b border-border p-1 sticky z-10";
1998
1998
  const bg = area === "header" ? "bg-muted" : "bg-background";
1999
1999
  const bgOdd = area === "header" ? "bg-muted" : STRUCTURAL_ZEBRA_BG_CLASS;
2000
2000
  return {
@@ -2031,16 +2031,24 @@ function DataGrid({
2031
2031
  for (const column of visibleColumns) map.set(column.id, bodyCellClassAndStyle(column));
2032
2032
  return map;
2033
2033
  }, [visibleColumns, enableColumnResizing, leftPinnedOffsets, rightPinnedOffsets, columnSizing]);
2034
- function headerCellClassAndStyle(column, base) {
2034
+ function headerCellClassAndStyle(column, base, options = {}) {
2035
2035
  const pinnedProps = pinnedCellProps(column, "header");
2036
2036
  const align = alignClassName(column);
2037
- const className = pinnedProps.className ? `${base} ${align} ${pinnedProps.className}` : `${base} ${align}`;
2037
+ const className = cn(
2038
+ base,
2039
+ align,
2040
+ pinnedProps.className,
2041
+ options.sticky && "sticky top-0 z-20 bg-muted"
2042
+ );
2038
2043
  return { className, style: pinnedProps.style };
2039
2044
  }
2040
2045
  const leafHeaderCellPropsByColumn = useMemo6(() => {
2041
2046
  const map = /* @__PURE__ */ new Map();
2042
2047
  for (const column of visibleColumns) {
2043
- map.set(column.id, headerCellClassAndStyle(column, "relative border-b border-border p-2 font-medium"));
2048
+ map.set(
2049
+ column.id,
2050
+ headerCellClassAndStyle(column, "relative border-b border-border p-2 font-medium", { sticky: true })
2051
+ );
2044
2052
  }
2045
2053
  return map;
2046
2054
  }, [visibleColumns, enableColumnResizing, leftPinnedOffsets, rightPinnedOffsets, columnSizing]);
@@ -2180,248 +2188,262 @@ function DataGrid({
2180
2188
  expanded && bucket.items.map((row) => renderRow(row))
2181
2189
  ] }, `group-${bucket.key}`);
2182
2190
  }
2183
- return /* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-2", "data-testid": "datagrid", children: [
2184
- showSelectionColumn && headerActions && /* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-between text-sm", children: [
2185
- /* @__PURE__ */ jsxs12("span", { children: [
2186
- selectedRows.length,
2187
- " selected"
2191
+ return (
2192
+ // `h-full`/`min-h-0` here and on the two wrappers below are no-ops unless
2193
+ // a consumer itself gives <DataGrid> a bounded height (e.g. wraps it in
2194
+ // its own `flex-1 min-h-0` container) -- percentage heights fall back to
2195
+ // `auto` with no definite ancestor height, so every existing natural-
2196
+ // height usage renders unchanged. Wrap it in a bounded container, though,
2197
+ // and this cascades down to the actual scrolling div below instead of
2198
+ // silently rendering at full unbounded content height, which used to
2199
+ // force any consumer wanting a bounded, page-filling grid to add its own
2200
+ // *second* `overflow-auto` wrapper around <DataGrid> -- two nested scroll
2201
+ // containers (that one scrolling vertically, this component's own
2202
+ // `overflow-x-auto` div scrolling horizontally) fighting over which one
2203
+ // a sticky header/pinned column is actually stuck relative to.
2204
+ /* @__PURE__ */ jsxs12("div", { className: "flex h-full min-h-0 flex-col gap-2", "data-testid": "datagrid", children: [
2205
+ showSelectionColumn && headerActions && /* @__PURE__ */ jsxs12("div", { className: "flex shrink-0 items-center justify-between text-sm", children: [
2206
+ /* @__PURE__ */ jsxs12("span", { children: [
2207
+ selectedRows.length,
2208
+ " selected"
2209
+ ] }),
2210
+ /* @__PURE__ */ jsx19(ActionsMenu, { items: headerActions, ctx: { selectedRows }, triggerLabel: "Bulk actions" })
2188
2211
  ] }),
2189
- /* @__PURE__ */ jsx19(ActionsMenu, { items: headerActions, ctx: { selectedRows }, triggerLabel: "Bulk actions" })
2190
- ] }),
2191
- /* @__PURE__ */ jsxs12("div", { className: "relative", children: [
2192
- loading && /* @__PURE__ */ jsx19(
2193
- "div",
2194
- {
2195
- "data-testid": "datagrid-loading-overlay",
2196
- className: "absolute inset-0 z-10 flex items-center justify-center bg-background/60",
2197
- children: /* @__PURE__ */ jsx19(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground", "aria-hidden": true })
2198
- }
2199
- ),
2200
- /* @__PURE__ */ jsx19(
2201
- "div",
2202
- {
2203
- ref: scrollRef,
2204
- "data-testid": testId,
2205
- className: cn("overflow-x-auto rounded-md border", shouldVirtualize && "overflow-y-auto"),
2206
- style: shouldVirtualize ? { maxHeight: virtualize?.maxBodyHeight ?? 480 } : void 0,
2207
- children: /* @__PURE__ */ jsxs12("table", { className: cn("w-full border-collapse text-sm", enableColumnResizing && "table-fixed"), children: [
2208
- /* @__PURE__ */ jsxs12("thead", { className: "bg-muted", children: [
2209
- table.getHeaderGroups().map((headerGroup) => {
2210
- const headerById = new Map(headerGroup.headers.map((header) => [header.id, header]));
2211
- function renderLeafHeaderCell(column, rowSpan) {
2212
- const header = headerById.get(column.id);
2213
- if (!header) return null;
2214
- const sortEntry = state.sort.find((s) => s.field === column.id);
2215
- const sortable = isSortable(column);
2216
- const filterable = isFilterable(column) && column.filterDisplay !== "row";
2217
- const cellProps = leafHeaderCellPropsByColumn.get(column.id);
2218
- const headerContent = column.renderHeader ? column.renderHeader(column) : column.header;
2219
- return /* @__PURE__ */ jsxs12("th", { rowSpan, style: cellProps?.style, className: cellProps?.className, children: [
2220
- /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1", children: [
2221
- sortable ? /* @__PURE__ */ jsxs12(
2222
- "button",
2212
+ /* @__PURE__ */ jsxs12("div", { className: "relative min-h-0 flex-1", children: [
2213
+ loading && /* @__PURE__ */ jsx19(
2214
+ "div",
2215
+ {
2216
+ "data-testid": "datagrid-loading-overlay",
2217
+ className: "absolute inset-0 z-30 flex items-center justify-center bg-background/60",
2218
+ children: /* @__PURE__ */ jsx19(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground", "aria-hidden": true })
2219
+ }
2220
+ ),
2221
+ /* @__PURE__ */ jsx19(
2222
+ "div",
2223
+ {
2224
+ ref: scrollRef,
2225
+ "data-testid": testId,
2226
+ className: "h-full overflow-auto rounded-md border",
2227
+ style: shouldVirtualize ? { maxHeight: virtualize?.maxBodyHeight ?? 480 } : void 0,
2228
+ children: /* @__PURE__ */ jsxs12("table", { className: cn("w-full border-collapse text-sm", enableColumnResizing && "table-fixed"), children: [
2229
+ /* @__PURE__ */ jsxs12("thead", { className: "bg-muted", children: [
2230
+ table.getHeaderGroups().map((headerGroup) => {
2231
+ const headerById = new Map(headerGroup.headers.map((header) => [header.id, header]));
2232
+ function renderLeafHeaderCell(column, rowSpan) {
2233
+ const header = headerById.get(column.id);
2234
+ if (!header) return null;
2235
+ const sortEntry = state.sort.find((s) => s.field === column.id);
2236
+ const sortable = isSortable(column);
2237
+ const filterable = isFilterable(column) && column.filterDisplay !== "row";
2238
+ const cellProps = leafHeaderCellPropsByColumn.get(column.id);
2239
+ const headerContent = column.renderHeader ? column.renderHeader(column) : column.header;
2240
+ return /* @__PURE__ */ jsxs12("th", { rowSpan, style: cellProps?.style, className: cellProps?.className, children: [
2241
+ /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1", children: [
2242
+ sortable ? /* @__PURE__ */ jsxs12(
2243
+ "button",
2244
+ {
2245
+ type: "button",
2246
+ className: "flex items-center gap-1 rounded-sm px-1 hover:bg-accent hover:text-accent-foreground",
2247
+ onClick: (event) => toggleSort(column.id, enableMultiSort && event.shiftKey, column.sortDescFirst === true),
2248
+ children: [
2249
+ headerContent,
2250
+ sortEntry?.dir === "asc" && /* @__PURE__ */ jsx19(BarsArrowUpIcon, { className: "h-3 w-3", "aria-hidden": true }),
2251
+ sortEntry?.dir === "desc" && /* @__PURE__ */ jsx19(BarsArrowDownIcon, { className: "h-3 w-3", "aria-hidden": true })
2252
+ ]
2253
+ }
2254
+ ) : (
2255
+ // Not wrapped in a `<button>` (unlike the sortable branch above): a
2256
+ // disabled `<button>` swallows pointer events for its entire subtree,
2257
+ // silently breaking any interactive content a column's own `renderHeader`
2258
+ // embeds directly (bare filter inputs, "select/reject all" buttons) —
2259
+ // clicks never reach them at all, even though nothing looks wrong visually.
2260
+ /* @__PURE__ */ jsx19("div", { className: "flex items-center gap-1 px-1", children: headerContent })
2261
+ ),
2262
+ filterable && /* @__PURE__ */ jsxs12(Popover, { children: [
2263
+ /* @__PURE__ */ jsx19(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx19(Button, { variant: "ghost", size: "icon", "aria-label": `Filter ${column.header}`, children: /* @__PURE__ */ jsx19(
2264
+ FunnelIcon7,
2265
+ {
2266
+ className: cn(
2267
+ "h-3 w-3",
2268
+ filtersByColumn.get(column.id) !== void 0 ? "opacity-100 text-primary" : "opacity-40"
2269
+ ),
2270
+ "aria-hidden": true
2271
+ }
2272
+ ) }) }),
2273
+ /* @__PURE__ */ jsx19(PopoverContent, { children: renderFilterWidget(column) })
2274
+ ] })
2275
+ ] }),
2276
+ enableColumnResizing && /* @__PURE__ */ jsx19(
2277
+ "div",
2278
+ {
2279
+ "data-testid": `resize-handle-${column.id}`,
2280
+ onMouseDown: header.getResizeHandler(),
2281
+ onTouchStart: header.getResizeHandler(),
2282
+ className: "absolute right-0 top-0 h-full w-1 cursor-col-resize touch-none select-none hover:bg-accent"
2283
+ }
2284
+ )
2285
+ ] }, header.id);
2286
+ }
2287
+ return /* @__PURE__ */ jsxs12(Fragment2, { children: [
2288
+ hasHeaderGroups && /* @__PURE__ */ jsxs12("tr", { "data-testid": "header-group-row", className: "divide-x divide-border", children: [
2289
+ showDetailColumn && /* @__PURE__ */ jsx19(
2290
+ "th",
2223
2291
  {
2224
- type: "button",
2225
- className: "flex items-center gap-1 rounded-sm px-1 hover:bg-accent hover:text-accent-foreground",
2226
- onClick: (event) => toggleSort(column.id, enableMultiSort && event.shiftKey, column.sortDescFirst === true),
2227
- children: [
2228
- headerContent,
2229
- sortEntry?.dir === "asc" && /* @__PURE__ */ jsx19(BarsArrowUpIcon, { className: "h-3 w-3", "aria-hidden": true }),
2230
- sortEntry?.dir === "desc" && /* @__PURE__ */ jsx19(BarsArrowDownIcon, { className: "h-3 w-3", "aria-hidden": true })
2231
- ]
2292
+ rowSpan: 2,
2293
+ className: detailHeaderCellProps.className,
2294
+ style: detailHeaderCellProps.style,
2295
+ "aria-hidden": true
2232
2296
  }
2233
- ) : (
2234
- // Not wrapped in a `<button>` (unlike the sortable branch above): a
2235
- // disabled `<button>` swallows pointer events for its entire subtree,
2236
- // silently breaking any interactive content a column's own `renderHeader`
2237
- // embeds directly (bare filter inputs, "select/reject all" buttons) —
2238
- // clicks never reach them at all, even though nothing looks wrong visually.
2239
- /* @__PURE__ */ jsx19("div", { className: "flex items-center gap-1 px-1", children: headerContent })
2240
2297
  ),
2241
- filterable && /* @__PURE__ */ jsxs12(Popover, { children: [
2242
- /* @__PURE__ */ jsx19(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx19(Button, { variant: "ghost", size: "icon", "aria-label": `Filter ${column.header}`, children: /* @__PURE__ */ jsx19(
2243
- FunnelIcon7,
2244
- {
2245
- className: cn(
2246
- "h-3 w-3",
2247
- filtersByColumn.get(column.id) !== void 0 ? "opacity-100 text-primary" : "opacity-40"
2248
- ),
2249
- "aria-hidden": true
2250
- }
2251
- ) }) }),
2252
- /* @__PURE__ */ jsx19(PopoverContent, { children: renderFilterWidget(column) })
2253
- ] })
2254
- ] }),
2255
- enableColumnResizing && /* @__PURE__ */ jsx19(
2256
- "div",
2257
- {
2258
- "data-testid": `resize-handle-${column.id}`,
2259
- onMouseDown: header.getResizeHandler(),
2260
- onTouchStart: header.getResizeHandler(),
2261
- className: "absolute right-0 top-0 h-full w-1 cursor-col-resize touch-none select-none hover:bg-accent"
2262
- }
2263
- )
2264
- ] }, header.id);
2265
- }
2266
- return /* @__PURE__ */ jsxs12(Fragment2, { children: [
2267
- hasHeaderGroups && /* @__PURE__ */ jsxs12("tr", { "data-testid": "header-group-row", className: "divide-x divide-border", children: [
2268
- showDetailColumn && /* @__PURE__ */ jsx19(
2269
- "th",
2270
- {
2271
- rowSpan: 2,
2272
- className: detailHeaderCellProps.className,
2273
- style: detailHeaderCellProps.style,
2274
- "aria-hidden": true
2275
- }
2276
- ),
2277
- showSelectionColumn && /* @__PURE__ */ jsx19(
2278
- "th",
2279
- {
2280
- rowSpan: 2,
2281
- className: selectionHeaderCellProps.className,
2282
- style: selectionHeaderCellProps.style,
2283
- children: /* @__PURE__ */ jsx19(
2284
- "input",
2298
+ showSelectionColumn && /* @__PURE__ */ jsx19(
2299
+ "th",
2300
+ {
2301
+ rowSpan: 2,
2302
+ className: selectionHeaderCellProps.className,
2303
+ style: selectionHeaderCellProps.style,
2304
+ children: /* @__PURE__ */ jsx19(
2305
+ "input",
2306
+ {
2307
+ type: "checkbox",
2308
+ "aria-label": "Select all rows on this page",
2309
+ checked: allOnPageSelected,
2310
+ onChange: toggleAllOnPageSelected
2311
+ }
2312
+ )
2313
+ }
2314
+ ),
2315
+ headerRuns.map(
2316
+ (run) => run.kind === "single" ? renderLeafHeaderCell(run.column, 2) : /* @__PURE__ */ jsx19(
2317
+ "th",
2285
2318
  {
2286
- type: "checkbox",
2287
- "aria-label": "Select all rows on this page",
2288
- checked: allOnPageSelected,
2289
- onChange: toggleAllOnPageSelected
2290
- }
2319
+ colSpan: run.columns.length,
2320
+ className: "border-b border-border p-2 text-center font-medium",
2321
+ children: run.label
2322
+ },
2323
+ `group-${run.label}-${run.columns[0].id}`
2291
2324
  )
2292
- }
2293
- ),
2294
- headerRuns.map(
2295
- (run) => run.kind === "single" ? renderLeafHeaderCell(run.column, 2) : /* @__PURE__ */ jsx19(
2325
+ ),
2326
+ showRowActionsColumn && /* @__PURE__ */ jsx19(
2296
2327
  "th",
2297
2328
  {
2298
- colSpan: run.columns.length,
2299
- className: "border-b border-border p-2 text-center font-medium",
2300
- children: run.label
2301
- },
2302
- `group-${run.label}-${run.columns[0].id}`
2329
+ rowSpan: 2,
2330
+ className: rowActionsHeaderCellProps.className,
2331
+ style: rowActionsHeaderCellProps.style,
2332
+ "aria-label": "Row actions"
2333
+ }
2303
2334
  )
2304
- ),
2305
- showRowActionsColumn && /* @__PURE__ */ jsx19(
2306
- "th",
2307
- {
2308
- rowSpan: 2,
2309
- className: rowActionsHeaderCellProps.className,
2310
- style: rowActionsHeaderCellProps.style,
2311
- "aria-label": "Row actions"
2312
- }
2313
- )
2314
- ] }, `${headerGroup.id}-groups`),
2315
- /* @__PURE__ */ jsxs12("tr", { className: "divide-x divide-border", children: [
2316
- !hasHeaderGroups && showDetailColumn && /* @__PURE__ */ jsx19(
2317
- "th",
2318
- {
2319
- className: detailHeaderCellProps.className,
2320
- style: detailHeaderCellProps.style,
2321
- "aria-hidden": true
2322
- }
2323
- ),
2324
- !hasHeaderGroups && showSelectionColumn && /* @__PURE__ */ jsx19(
2325
- "th",
2326
- {
2327
- className: selectionHeaderCellProps.className,
2328
- style: selectionHeaderCellProps.style,
2329
- children: /* @__PURE__ */ jsx19(
2330
- "input",
2331
- {
2332
- type: "checkbox",
2333
- "aria-label": "Select all rows on this page",
2334
- checked: allOnPageSelected,
2335
- onChange: toggleAllOnPageSelected
2336
- }
2337
- )
2338
- }
2339
- ),
2340
- headerGroup.headers.map((header) => {
2341
- const column = columnById.get(header.id);
2342
- if (!column || hasHeaderGroups && !column.headerGroup) return null;
2343
- return renderLeafHeaderCell(column);
2344
- }),
2345
- !hasHeaderGroups && showRowActionsColumn && /* @__PURE__ */ jsx19(
2346
- "th",
2347
- {
2348
- className: rowActionsHeaderCellProps.className,
2349
- style: rowActionsHeaderCellProps.style,
2350
- "aria-label": "Row actions"
2351
- }
2352
- )
2353
- ] }, headerGroup.id)
2354
- ] }, headerGroup.id);
2355
- }),
2356
- hasFilterRow && /* @__PURE__ */ jsxs12("tr", { "data-testid": "filter-row", className: "divide-x divide-border", children: [
2357
- showDetailColumn && /* @__PURE__ */ jsx19("th", { className: detailHeaderCellProps.className, style: detailHeaderCellProps.style }),
2358
- showSelectionColumn && /* @__PURE__ */ jsx19(
2359
- "th",
2360
- {
2361
- className: selectionHeaderCellProps.className,
2362
- style: selectionHeaderCellProps.style
2363
- }
2364
- ),
2365
- visibleColumns.map((column) => {
2366
- const cellProps = filterHeaderCellPropsByColumn.get(column.id);
2367
- return /* @__PURE__ */ jsx19("th", { style: cellProps?.style, className: cellProps?.className, children: isFilterable(column) && column.filterDisplay === "row" ? renderFilterWidget(column) : null }, column.id);
2335
+ ] }, `${headerGroup.id}-groups`),
2336
+ /* @__PURE__ */ jsxs12("tr", { className: "divide-x divide-border", children: [
2337
+ !hasHeaderGroups && showDetailColumn && /* @__PURE__ */ jsx19(
2338
+ "th",
2339
+ {
2340
+ className: detailHeaderCellProps.className,
2341
+ style: detailHeaderCellProps.style,
2342
+ "aria-hidden": true
2343
+ }
2344
+ ),
2345
+ !hasHeaderGroups && showSelectionColumn && /* @__PURE__ */ jsx19(
2346
+ "th",
2347
+ {
2348
+ className: selectionHeaderCellProps.className,
2349
+ style: selectionHeaderCellProps.style,
2350
+ children: /* @__PURE__ */ jsx19(
2351
+ "input",
2352
+ {
2353
+ type: "checkbox",
2354
+ "aria-label": "Select all rows on this page",
2355
+ checked: allOnPageSelected,
2356
+ onChange: toggleAllOnPageSelected
2357
+ }
2358
+ )
2359
+ }
2360
+ ),
2361
+ headerGroup.headers.map((header) => {
2362
+ const column = columnById.get(header.id);
2363
+ if (!column || hasHeaderGroups && !column.headerGroup) return null;
2364
+ return renderLeafHeaderCell(column);
2365
+ }),
2366
+ !hasHeaderGroups && showRowActionsColumn && /* @__PURE__ */ jsx19(
2367
+ "th",
2368
+ {
2369
+ className: rowActionsHeaderCellProps.className,
2370
+ style: rowActionsHeaderCellProps.style,
2371
+ "aria-label": "Row actions"
2372
+ }
2373
+ )
2374
+ ] }, headerGroup.id)
2375
+ ] }, headerGroup.id);
2368
2376
  }),
2369
- showRowActionsColumn && /* @__PURE__ */ jsx19(
2370
- "th",
2371
- {
2372
- className: rowActionsHeaderCellProps.className,
2373
- style: rowActionsHeaderCellProps.style
2374
- }
2375
- )
2376
- ] })
2377
- ] }),
2378
- 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: [
2379
- 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 } }) }) }),
2380
- virtualItems.map(
2381
- (virtualItem) => renderRow(tableRows[virtualItem.index], (el) => virtualizer.measureElement(el))
2382
- ),
2383
- 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 } }) }) })
2384
- ] }) : groupedBuckets ? groupedBuckets.map((bucket) => renderGroupedBucket(bucket)) : tableRows.map((row) => renderRow(row))
2385
- ] })
2386
- }
2387
- )
2388
- ] }),
2389
- showPagination && /* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-between text-sm", children: [
2390
- /* @__PURE__ */ jsxs12("span", { children: [
2391
- "Page ",
2392
- state.page + 1,
2393
- " of ",
2394
- pageCount,
2395
- " (",
2396
- rowCount,
2397
- " row",
2398
- rowCount === 1 ? "" : "s",
2399
- ")"
2400
- ] }),
2401
- /* @__PURE__ */ jsxs12("div", { className: "flex gap-2", children: [
2402
- /* @__PURE__ */ jsx19(
2403
- Button,
2404
- {
2405
- variant: "outline",
2406
- size: "sm",
2407
- disabled: state.page <= 0,
2408
- onClick: () => setPage(state.page - 1),
2409
- children: "Previous"
2410
- }
2411
- ),
2412
- /* @__PURE__ */ jsx19(
2413
- Button,
2414
- {
2415
- variant: "outline",
2416
- size: "sm",
2417
- disabled: state.page >= pageCount - 1,
2418
- onClick: () => setPage(state.page + 1),
2419
- children: "Next"
2377
+ hasFilterRow && /* @__PURE__ */ jsxs12("tr", { "data-testid": "filter-row", className: "divide-x divide-border", children: [
2378
+ showDetailColumn && /* @__PURE__ */ jsx19("th", { className: detailHeaderCellProps.className, style: detailHeaderCellProps.style }),
2379
+ showSelectionColumn && /* @__PURE__ */ jsx19(
2380
+ "th",
2381
+ {
2382
+ className: selectionHeaderCellProps.className,
2383
+ style: selectionHeaderCellProps.style
2384
+ }
2385
+ ),
2386
+ visibleColumns.map((column) => {
2387
+ const cellProps = filterHeaderCellPropsByColumn.get(column.id);
2388
+ return /* @__PURE__ */ jsx19("th", { style: cellProps?.style, className: cellProps?.className, children: isFilterable(column) && column.filterDisplay === "row" ? renderFilterWidget(column) : null }, column.id);
2389
+ }),
2390
+ showRowActionsColumn && /* @__PURE__ */ jsx19(
2391
+ "th",
2392
+ {
2393
+ className: rowActionsHeaderCellProps.className,
2394
+ style: rowActionsHeaderCellProps.style
2395
+ }
2396
+ )
2397
+ ] })
2398
+ ] }),
2399
+ 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: [
2400
+ 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 } }) }) }),
2401
+ virtualItems.map(
2402
+ (virtualItem) => renderRow(tableRows[virtualItem.index], (el) => virtualizer.measureElement(el))
2403
+ ),
2404
+ 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 } }) }) })
2405
+ ] }) : groupedBuckets ? groupedBuckets.map((bucket) => renderGroupedBucket(bucket)) : tableRows.map((row) => renderRow(row))
2406
+ ] })
2420
2407
  }
2421
2408
  )
2409
+ ] }),
2410
+ showPagination && /* @__PURE__ */ jsxs12("div", { className: "flex shrink-0 items-center justify-between text-sm", children: [
2411
+ /* @__PURE__ */ jsxs12("span", { children: [
2412
+ "Page ",
2413
+ state.page + 1,
2414
+ " of ",
2415
+ pageCount,
2416
+ " (",
2417
+ rowCount,
2418
+ " row",
2419
+ rowCount === 1 ? "" : "s",
2420
+ ")"
2421
+ ] }),
2422
+ /* @__PURE__ */ jsxs12("div", { className: "flex gap-2", children: [
2423
+ /* @__PURE__ */ jsx19(
2424
+ Button,
2425
+ {
2426
+ variant: "outline",
2427
+ size: "sm",
2428
+ disabled: state.page <= 0,
2429
+ onClick: () => setPage(state.page - 1),
2430
+ children: "Previous"
2431
+ }
2432
+ ),
2433
+ /* @__PURE__ */ jsx19(
2434
+ Button,
2435
+ {
2436
+ variant: "outline",
2437
+ size: "sm",
2438
+ disabled: state.page >= pageCount - 1,
2439
+ onClick: () => setPage(state.page + 1),
2440
+ children: "Next"
2441
+ }
2442
+ )
2443
+ ] })
2422
2444
  ] })
2423
2445
  ] })
2424
- ] });
2446
+ );
2425
2447
  }
2426
2448
 
2427
2449
  // src/tree/TreeDataGrid.tsx
@@ -2480,7 +2502,9 @@ function useTreeState({
2480
2502
  hasChildren,
2481
2503
  onLoadChildren,
2482
2504
  expanded: controlledExpanded,
2483
- onExpandedChange
2505
+ onExpandedChange,
2506
+ childrenMap: controlledChildrenMap,
2507
+ onChildrenMapChange
2484
2508
  }) {
2485
2509
  const [internalExpanded, setInternalExpanded] = useState7({});
2486
2510
  const expanded = controlledExpanded ?? internalExpanded;
@@ -2494,7 +2518,18 @@ function useTreeState({
2494
2518
  },
2495
2519
  [controlledExpanded, onExpandedChange]
2496
2520
  );
2497
- const [childrenMap, setChildrenMap] = useState7(/* @__PURE__ */ new Map());
2521
+ const [internalChildrenMap, setInternalChildrenMap] = useState7(/* @__PURE__ */ new Map());
2522
+ const childrenMap = controlledChildrenMap ?? internalChildrenMap;
2523
+ const setChildrenMap = useCallback3(
2524
+ (updater) => {
2525
+ if (controlledChildrenMap) {
2526
+ onChildrenMapChange?.(updater(controlledChildrenMap));
2527
+ } else {
2528
+ setInternalChildrenMap((prev) => new Map(updater(prev)));
2529
+ }
2530
+ },
2531
+ [controlledChildrenMap, onChildrenMapChange]
2532
+ );
2498
2533
  const [loadingIds, setLoadingIds] = useState7(/* @__PURE__ */ new Set());
2499
2534
  const [errorIds, setErrorIds] = useState7(/* @__PURE__ */ new Map());
2500
2535
  const resolveHasChildren = useMemo7(
@@ -2532,7 +2567,7 @@ function useTreeState({
2532
2567
  });
2533
2568
  }
2534
2569
  },
2535
- [childrenMap, getChildren, getRowId, onLoadChildren]
2570
+ [childrenMap, getChildren, getRowId, onLoadChildren, setChildrenMap]
2536
2571
  );
2537
2572
  const toggleExpand = useCallback3(
2538
2573
  (row) => {
@@ -2627,6 +2662,8 @@ function TreeDataGrid({
2627
2662
  initialExpandedLevel = 0,
2628
2663
  expanded: controlledExpanded,
2629
2664
  onExpandedChange,
2665
+ childrenMap: controlledChildrenMap,
2666
+ onChildrenMapChange,
2630
2667
  indentSize = 20,
2631
2668
  getRowProps,
2632
2669
  getRowTestId,
@@ -2651,7 +2688,9 @@ function TreeDataGrid({
2651
2688
  hasChildren,
2652
2689
  onLoadChildren,
2653
2690
  expanded: controlledExpanded,
2654
- onExpandedChange
2691
+ onExpandedChange,
2692
+ childrenMap: controlledChildrenMap,
2693
+ onChildrenMapChange
2655
2694
  });
2656
2695
  const didAutoExpand = useRef6(false);
2657
2696
  useEffect8(() => {
@@ -2720,6 +2759,7 @@ function TreeDataGrid({
2720
2759
  {
2721
2760
  ref: measureRef,
2722
2761
  ...rowProps,
2762
+ "data-index": index,
2723
2763
  "data-testid": rowTestId,
2724
2764
  className: rowProps?.className ? cn(
2725
2765
  "border-b border-border divide-x divide-border",