@bmsuisse/datagrid 0.31.1 → 0.32.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
@@ -2272,7 +2272,7 @@ function renderCellModeCell(column, row, rawValue, ctx) {
2272
2272
  }
2273
2273
 
2274
2274
  // src/cell-editing/SelectionOverlay.tsx
2275
- import { useLayoutEffect as useLayoutEffect2, useState as useState9 } from "react";
2275
+ import { useLayoutEffect as useLayoutEffect2, useRef as useRef9, useState as useState9 } from "react";
2276
2276
  import { Fragment as Fragment2, jsx as jsx27, jsxs as jsxs16 } from "react/jsx-runtime";
2277
2277
  function cellSelector(rowId, columnId) {
2278
2278
  const escape = (value) => typeof CSS !== "undefined" && CSS.escape ? CSS.escape(value) : value.replace(/["\\]/g, "\\$&");
@@ -2305,13 +2305,27 @@ function SelectionOverlay({
2305
2305
  }) {
2306
2306
  const [rect, setRect] = useState9(void 0);
2307
2307
  const [previewRect, setPreviewRect] = useState9(void 0);
2308
- useLayoutEffect2(() => {
2308
+ const recomputeRef = useRef9(() => {
2309
+ });
2310
+ recomputeRef.current = () => {
2309
2311
  const container = containerRef.current;
2310
2312
  const nextRect = range && container ? computeRangeRect(container, range) : void 0;
2311
2313
  setRect((prev) => rectsEqual(prev, nextRect) ? prev : nextRect);
2312
2314
  const nextPreviewRect = fillPreviewRange && container ? computeRangeRect(container, fillPreviewRange) : void 0;
2313
2315
  setPreviewRect((prev) => rectsEqual(prev, nextPreviewRect) ? prev : nextPreviewRect);
2316
+ };
2317
+ useLayoutEffect2(() => {
2318
+ recomputeRef.current();
2314
2319
  });
2320
+ useLayoutEffect2(() => {
2321
+ const container = containerRef.current;
2322
+ if (!container) return;
2323
+ function handleScroll() {
2324
+ recomputeRef.current();
2325
+ }
2326
+ container.addEventListener("scroll", handleScroll, { passive: true });
2327
+ return () => container.removeEventListener("scroll", handleScroll);
2328
+ }, [containerRef.current]);
2315
2329
  if (!rect) return null;
2316
2330
  return /* @__PURE__ */ jsxs16(Fragment2, { children: [
2317
2331
  /* @__PURE__ */ jsx27(
@@ -2803,7 +2817,7 @@ import { BarsArrowDownIcon, BarsArrowUpIcon, FunnelIcon as FunnelIcon7 } from "@
2803
2817
  import { columnResizingFeature, columnSizingFeature, flexRender, tableFeatures, useTable } from "@tanstack/react-table";
2804
2818
  import { useVirtualizer } from "@tanstack/react-virtual";
2805
2819
  import { ChevronRight, Loader2 } from "lucide-react";
2806
- import { Fragment as Fragment3, useEffect as useEffect10, useMemo as useMemo8, useRef as useRef11, useState as useState14 } from "react";
2820
+ import { Fragment as Fragment3, useEffect as useEffect10, useMemo as useMemo8, useRef as useRef12, useState as useState14 } from "react";
2807
2821
 
2808
2822
  // src/cell-editing/renderAlwaysEditCell.tsx
2809
2823
  import {
@@ -2942,9 +2956,9 @@ function useGroupExpansion(defaultGroupsExpanded, controlledExpandedGroups, onEx
2942
2956
  }
2943
2957
 
2944
2958
  // src/hooks/useStickyGroupHeaderTop.ts
2945
- import { useLayoutEffect as useLayoutEffect3, useRef as useRef9, useState as useState12 } from "react";
2959
+ import { useLayoutEffect as useLayoutEffect3, useRef as useRef10, useState as useState12 } from "react";
2946
2960
  function useStickyGroupHeaderTop(enabled) {
2947
- const theadRef = useRef9(null);
2961
+ const theadRef = useRef10(null);
2948
2962
  const [groupHeaderTop, setGroupHeaderTop] = useState12(0);
2949
2963
  useLayoutEffect3(() => {
2950
2964
  if (!enabled) return;
@@ -3012,11 +3026,11 @@ function processClientData(data, state) {
3012
3026
  import { useCallback as useCallback4, useEffect as useEffect9, useMemo as useMemo7, useState as useState13 } from "react";
3013
3027
 
3014
3028
  // src/hooks/useDebouncedCallback.ts
3015
- import { useEffect as useEffect8, useMemo as useMemo6, useRef as useRef10 } from "react";
3029
+ import { useEffect as useEffect8, useMemo as useMemo6, useRef as useRef11 } from "react";
3016
3030
  function useDebouncedCallback(callback, delayMs) {
3017
- const callbackRef = useRef10(callback);
3031
+ const callbackRef = useRef11(callback);
3018
3032
  callbackRef.current = callback;
3019
- const timeoutRef = useRef10(void 0);
3033
+ const timeoutRef = useRef11(void 0);
3020
3034
  useEffect8(() => {
3021
3035
  return () => {
3022
3036
  clearTimeout(timeoutRef.current);
@@ -3141,6 +3155,12 @@ function useGridState(dataSource, initialState, externalState, showPagination =
3141
3155
  import { Fragment as Fragment4, jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
3142
3156
  var gridTableFeatures = tableFeatures({ columnSizingFeature, columnResizingFeature });
3143
3157
  var DEFAULT_COLUMN_SIZE = 150;
3158
+ var MIN_COLUMN_SIZE = 40;
3159
+ function sortDirToAriaSort(dir) {
3160
+ if (dir === "asc") return "ascending";
3161
+ if (dir === "desc") return "descending";
3162
+ return "none";
3163
+ }
3144
3164
  var DETAIL_COLUMN_WIDTH = 44;
3145
3165
  var ROW_ACTIONS_COLUMN_WIDTH = 44;
3146
3166
  var EMPTY_ID_ARRAY = [];
@@ -3269,7 +3289,7 @@ function DataGrid({
3269
3289
  hasCellEditing ? cellEditing : void 0,
3270
3290
  getRowId
3271
3291
  );
3272
- const atomicGestureRef = useRef11(void 0);
3292
+ const atomicGestureRef = useRef12(void 0);
3273
3293
  const visibleColumns = useVisibleColumns(columns, columnVisibility);
3274
3294
  const serverRowCount = dataSource.mode === "server" ? dataSource.rowCount : void 0;
3275
3295
  const { rows, rowCount } = useMemo8(() => {
@@ -3423,7 +3443,7 @@ function DataGrid({
3423
3443
  return map;
3424
3444
  }, [visibleColumns, enableColumnResizing, leftPinnedOffsets, rightPinnedOffsets, columnSizing]);
3425
3445
  const tableRows = table.getRowModel().rows;
3426
- const truncationWarnedRef = useRef11(false);
3446
+ const truncationWarnedRef = useRef12(false);
3427
3447
  useEffect10(() => {
3428
3448
  if (process.env.NODE_ENV === "production") return;
3429
3449
  if (showPagination) return;
@@ -3454,7 +3474,7 @@ function DataGrid({
3454
3474
  }
3455
3475
  return items;
3456
3476
  }, [shouldVirtualize, groupedBuckets, tableRows, isGroupExpanded]);
3457
- const scrollRef = useRef11(null);
3477
+ const scrollRef = useRef12(null);
3458
3478
  const virtualizer = useVirtualizer({
3459
3479
  count: flatItems.length,
3460
3480
  getScrollElement: () => scrollRef.current,
@@ -3466,7 +3486,10 @@ function DataGrid({
3466
3486
  const paddingTop = virtualItems.length > 0 ? virtualItems[0].start : 0;
3467
3487
  const paddingBottom = virtualItems.length > 0 ? virtualizer.getTotalSize() - virtualItems[virtualItems.length - 1].end : 0;
3468
3488
  const lastVisibleIndex = virtualItems.length > 0 ? virtualItems[virtualItems.length - 1].index : -1;
3469
- const notifiedForLengthRef = useRef11(-1);
3489
+ const notifiedForLengthRef = useRef12(-1);
3490
+ useEffect10(() => {
3491
+ notifiedForLengthRef.current = -1;
3492
+ }, [state.filter, state.sort]);
3470
3493
  useEffect10(() => {
3471
3494
  if (!virtualize?.onEndReached) return;
3472
3495
  if (lastVisibleIndex < 0 || lastVisibleIndex < flatItems.length - 1) return;
@@ -3493,8 +3516,8 @@ function DataGrid({
3493
3516
  if (index !== void 0) virtualizer.scrollToIndex(index);
3494
3517
  }
3495
3518
  });
3496
- const clickCellRef = useRef11(null);
3497
- const dragMovedRef = useRef11(false);
3519
+ const clickCellRef = useRef12(null);
3520
+ const dragMovedRef = useRef12(false);
3498
3521
  const [pendingOpenEnumCell, setPendingOpenEnumCell] = useState14(void 0);
3499
3522
  atomicGestureRef.current = {
3500
3523
  shouldOpenEnum: (rowId, columnId) => pendingOpenEnumCell?.rowId === rowId && pendingOpenEnumCell?.columnId === columnId,
@@ -3518,8 +3541,8 @@ function DataGrid({
3518
3541
  if (!column || row === void 0 || !isEditable(column, row)) return;
3519
3542
  cellEditingCtx.onBeginEdit(cell);
3520
3543
  }
3521
- const pendingDragCellRef = useRef11(null);
3522
- const dragRafRef = useRef11(null);
3544
+ const pendingDragCellRef = useRef12(null);
3545
+ const dragRafRef = useRef12(null);
3523
3546
  useEffect10(() => {
3524
3547
  if (!cellSelection.isDragging) return;
3525
3548
  function handleMouseMove(event) {
@@ -3559,8 +3582,8 @@ function DataGrid({
3559
3582
  dragRafRef.current = null;
3560
3583
  };
3561
3584
  }, [cellSelection.isDragging, cellSelection.updateDrag, cellSelection.endDrag]);
3562
- const pendingFillCellRef = useRef11(null);
3563
- const fillRafRef = useRef11(null);
3585
+ const pendingFillCellRef = useRef12(null);
3586
+ const fillRafRef = useRef12(null);
3564
3587
  useEffect10(() => {
3565
3588
  if (!cellSelection.isFillDragging) return;
3566
3589
  function handleMouseMove(event) {
@@ -3597,7 +3620,7 @@ function DataGrid({
3597
3620
  fillRafRef.current = null;
3598
3621
  };
3599
3622
  }, [cellSelection.isFillDragging, cellSelection.updateFillDrag, cellSelection.endFillDrag]);
3600
- const wasEditingCellRef = useRef11(void 0);
3623
+ const wasEditingCellRef = useRef12(void 0);
3601
3624
  useEffect10(() => {
3602
3625
  const wasEditing = wasEditingCellRef.current !== void 0;
3603
3626
  wasEditingCellRef.current = cellEditingCtx?.editingCell;
@@ -3911,52 +3934,84 @@ function DataGrid({
3911
3934
  const filterable = isFilterable(column) && column.filterDisplay !== "row";
3912
3935
  const cellProps = leafHeaderCellPropsByColumn.get(column.id);
3913
3936
  const headerContent = column.renderHeader ? column.renderHeader(column) : column.header;
3914
- return /* @__PURE__ */ jsxs20("th", { rowSpan, style: cellProps?.style, className: cellProps?.className, children: [
3915
- /* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-1", children: [
3916
- sortable ? /* @__PURE__ */ jsxs20(
3917
- "button",
3918
- {
3919
- type: "button",
3920
- className: "flex items-center gap-1 rounded-sm px-1 hover:bg-accent hover:text-accent-foreground",
3921
- onClick: (event) => toggleSort(column.id, enableMultiSort && event.shiftKey, column.sortDescFirst === true),
3922
- children: [
3923
- headerContent,
3924
- sortEntry?.dir === "asc" && /* @__PURE__ */ jsx33(BarsArrowUpIcon, { className: "h-3 w-3", "aria-hidden": true }),
3925
- sortEntry?.dir === "desc" && /* @__PURE__ */ jsx33(BarsArrowDownIcon, { className: "h-3 w-3", "aria-hidden": true })
3926
- ]
3927
- }
3928
- ) : (
3929
- // Not wrapped in a `<button>` (unlike the sortable branch above): a
3930
- // disabled `<button>` swallows pointer events for its entire subtree,
3931
- // silently breaking any interactive content a column's own `renderHeader`
3932
- // embeds directly (bare filter inputs, "select/reject all" buttons) —
3933
- // clicks never reach them at all, even though nothing looks wrong visually.
3934
- /* @__PURE__ */ jsx33("div", { className: "flex items-center gap-1 px-1", children: headerContent })
3935
- ),
3936
- filterable && /* @__PURE__ */ jsxs20(Popover, { children: [
3937
- /* @__PURE__ */ jsx33(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx33(Button, { variant: "ghost", size: "icon", "aria-label": `Filter ${column.header}`, children: /* @__PURE__ */ jsx33(
3938
- FunnelIcon7,
3937
+ return /* @__PURE__ */ jsxs20(
3938
+ "th",
3939
+ {
3940
+ rowSpan,
3941
+ style: cellProps?.style,
3942
+ className: cellProps?.className,
3943
+ "aria-sort": sortable ? sortDirToAriaSort(sortEntry?.dir) : void 0,
3944
+ "aria-label": column.header,
3945
+ children: [
3946
+ /* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-1", children: [
3947
+ sortable ? /* @__PURE__ */ jsxs20(
3948
+ "button",
3949
+ {
3950
+ type: "button",
3951
+ "data-testid": `sort-button-${column.id}`,
3952
+ className: "flex items-center gap-1 rounded-sm px-1 hover:bg-accent hover:text-accent-foreground",
3953
+ onClick: (event) => toggleSort(column.id, enableMultiSort && event.shiftKey, column.sortDescFirst === true),
3954
+ children: [
3955
+ headerContent,
3956
+ sortEntry?.dir === "asc" && /* @__PURE__ */ jsx33(BarsArrowUpIcon, { className: "h-3 w-3", "aria-hidden": true }),
3957
+ sortEntry?.dir === "desc" && /* @__PURE__ */ jsx33(BarsArrowDownIcon, { className: "h-3 w-3", "aria-hidden": true })
3958
+ ]
3959
+ }
3960
+ ) : (
3961
+ // Not wrapped in a `<button>` (unlike the sortable branch above): a
3962
+ // disabled `<button>` swallows pointer events for its entire subtree,
3963
+ // silently breaking any interactive content a column's own `renderHeader`
3964
+ // embeds directly (bare filter inputs, "select/reject all" buttons) —
3965
+ // clicks never reach them at all, even though nothing looks wrong visually.
3966
+ /* @__PURE__ */ jsx33("div", { className: "flex items-center gap-1 px-1", children: headerContent })
3967
+ ),
3968
+ filterable && /* @__PURE__ */ jsxs20(Popover, { children: [
3969
+ /* @__PURE__ */ jsx33(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx33(
3970
+ Button,
3971
+ {
3972
+ variant: "ghost",
3973
+ size: "icon",
3974
+ "data-testid": `filter-trigger-${column.id}`,
3975
+ "aria-label": `Filter ${column.header}`,
3976
+ children: /* @__PURE__ */ jsx33(
3977
+ FunnelIcon7,
3978
+ {
3979
+ className: cn(
3980
+ "h-3 w-3",
3981
+ filtersByColumn.get(column.id) !== void 0 ? "opacity-100 text-primary" : "opacity-40"
3982
+ ),
3983
+ "aria-hidden": true
3984
+ }
3985
+ )
3986
+ }
3987
+ ) }),
3988
+ /* @__PURE__ */ jsx33(PopoverContent, { children: renderFilterWidget(column) })
3989
+ ] })
3990
+ ] }),
3991
+ enableColumnResizing && /* @__PURE__ */ jsx33(
3992
+ "div",
3939
3993
  {
3940
- className: cn(
3941
- "h-3 w-3",
3942
- filtersByColumn.get(column.id) !== void 0 ? "opacity-100 text-primary" : "opacity-40"
3943
- ),
3944
- "aria-hidden": true
3994
+ "data-testid": `resize-handle-${column.id}`,
3995
+ role: "separator",
3996
+ "aria-orientation": "vertical",
3997
+ "aria-label": `Resize ${column.header} column`,
3998
+ tabIndex: 0,
3999
+ onMouseDown: header.getResizeHandler(),
4000
+ onTouchStart: header.getResizeHandler(),
4001
+ onKeyDown: (event) => {
4002
+ if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
4003
+ event.preventDefault();
4004
+ const delta = event.key === "ArrowRight" ? 10 : -10;
4005
+ const nextSize = Math.max(MIN_COLUMN_SIZE, columnSize(column.id) + delta);
4006
+ updateColumnSizing({ ...columnSizing, [column.id]: nextSize });
4007
+ },
4008
+ className: "absolute right-0 top-0 h-full w-1 cursor-col-resize touch-none select-none hover:bg-accent focus-visible:bg-accent focus-visible:outline-none"
3945
4009
  }
3946
- ) }) }),
3947
- /* @__PURE__ */ jsx33(PopoverContent, { children: renderFilterWidget(column) })
3948
- ] })
3949
- ] }),
3950
- enableColumnResizing && /* @__PURE__ */ jsx33(
3951
- "div",
3952
- {
3953
- "data-testid": `resize-handle-${column.id}`,
3954
- onMouseDown: header.getResizeHandler(),
3955
- onTouchStart: header.getResizeHandler(),
3956
- className: "absolute right-0 top-0 h-full w-1 cursor-col-resize touch-none select-none hover:bg-accent"
3957
- }
3958
- )
3959
- ] }, header.id);
4010
+ )
4011
+ ]
4012
+ },
4013
+ header.id
4014
+ );
3960
4015
  }
3961
4016
  return /* @__PURE__ */ jsxs20(Fragment3, { children: [
3962
4017
  hasHeaderGroups && /* @__PURE__ */ jsxs20("tr", { "data-testid": "header-group-row", className: "divide-x divide-border", children: [
@@ -4137,7 +4192,7 @@ function DataGrid({
4137
4192
  // src/tree/TreeDataGrid.tsx
4138
4193
  import { useVirtualizer as useVirtualizer2 } from "@tanstack/react-virtual";
4139
4194
  import { AlertCircle, ChevronDown as ChevronDown2, ChevronRight as ChevronRight2, Loader2 as Loader22 } from "lucide-react";
4140
- import { Fragment as Fragment5, useEffect as useEffect11, useMemo as useMemo10, useRef as useRef12 } from "react";
4195
+ import { Fragment as Fragment5, useEffect as useEffect11, useMemo as useMemo10, useRef as useRef13 } from "react";
4141
4196
 
4142
4197
  // src/tree/flattenTree.ts
4143
4198
  function flattenTree(roots, accessors, expanded, childrenOverride) {
@@ -4388,7 +4443,7 @@ function TreeDataGrid({
4388
4443
  childrenMap: controlledChildrenMap,
4389
4444
  onChildrenMapChange
4390
4445
  });
4391
- const didAutoExpand = useRef12(false);
4446
+ const didAutoExpand = useRef13(false);
4392
4447
  useEffect11(() => {
4393
4448
  if (didAutoExpand.current) return;
4394
4449
  didAutoExpand.current = true;
@@ -4464,7 +4519,7 @@ function TreeDataGrid({
4464
4519
  for (const column of visibleColumns) map.set(column.id, `p-2 ${alignClassName(column)}`);
4465
4520
  return map;
4466
4521
  }, [visibleColumns]);
4467
- const scrollRef = useRef12(null);
4522
+ const scrollRef = useRef13(null);
4468
4523
  const shouldVirtualize = !groupBy && flatRows.length > virtualizeThreshold;
4469
4524
  const virtualizer = useVirtualizer2({
4470
4525
  count: flatRows.length,