@svgrid/grid 2.6.21 → 2.6.23

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.
Files changed (151) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +22 -0
  3. package/dist/GridMenus.svelte +201 -18
  4. package/dist/SvAutoComplete.svelte +4 -0
  5. package/dist/SvComboBox.svelte +5 -1
  6. package/dist/SvDateRangeInput.svelte +25 -3
  7. package/dist/SvDropDownList.svelte +4 -0
  8. package/dist/SvForm.svelte +8 -8
  9. package/dist/SvGrid.controller.svelte.d.ts +61 -9
  10. package/dist/SvGrid.controller.svelte.js +363 -123
  11. package/dist/SvGrid.css +222 -1
  12. package/dist/SvGrid.helpers.d.ts +55 -0
  13. package/dist/SvGrid.helpers.js +83 -0
  14. package/dist/SvGrid.svelte +255 -56
  15. package/dist/SvGrid.types.d.ts +146 -1
  16. package/dist/SvGridCellEditor.svelte +99 -17
  17. package/dist/SvGridSelect.svelte +6 -2
  18. package/dist/SvGridSelect.svelte.d.ts +1 -1
  19. package/dist/SvListBox.svelte +8 -0
  20. package/dist/SvMultiSelect.svelte +6 -2
  21. package/dist/SvMultiSelect.svelte.d.ts +1 -1
  22. package/dist/SvNumberInput.svelte +4 -0
  23. package/dist/SvRichCell.svelte +125 -0
  24. package/dist/SvRichCell.svelte.d.ts +15 -0
  25. package/dist/SvTextArea.svelte +5 -1
  26. package/dist/SvTreeSelect.svelte +6 -2
  27. package/dist/SvTreeSelect.svelte.d.ts +1 -1
  28. package/dist/builtin-editors.d.ts +2 -2
  29. package/dist/builtin-editors.js +19 -3
  30. package/dist/cdn/GridMenus-BzWXQjv3.js +600 -0
  31. package/dist/cdn/GridMenus-xr_rHx8F.js +601 -0
  32. package/dist/cdn/SvDateRangeInput-CaOuMs8O.js +198 -0
  33. package/dist/cdn/SvDateRangeInput-DMLKmGEc.js +199 -0
  34. package/dist/cdn/SvDateTimePicker-CCbDZNZB.js +816 -0
  35. package/dist/cdn/SvDateTimePicker-sonaH0oh.js +812 -0
  36. package/dist/cdn/SvGridCellEditor-BAPSC7EL.js +550 -0
  37. package/dist/cdn/SvGridCellEditor-BwzmUWtL.js +549 -0
  38. package/dist/cdn/column-resize-DsfNXMom.js +102 -0
  39. package/dist/cdn/date-format-BNii4zeD.js +1393 -0
  40. package/dist/cdn/date-format-BnnHlqGw.js +1395 -0
  41. package/dist/cdn/{editor-registry-CiI0xlKg.js → editor-registry-BhgE3S84.js} +51 -25
  42. package/dist/cdn/row-resize-BRcimkUT.js +95 -0
  43. package/dist/cdn/{src-DBel9wRZ.js → src-DGo7IHug.js} +10017 -9488
  44. package/dist/cdn/{src-BYq-qyrp.js → src-skQN5eqh.js} +7829 -7300
  45. package/dist/cdn/svgrid.js +14 -10
  46. package/dist/cdn/svgrid.svelte-external.js +14 -10
  47. package/dist/cell-values.d.ts +16 -0
  48. package/dist/cell-values.js +28 -0
  49. package/dist/clipboard.d.ts +22 -0
  50. package/dist/clipboard.js +405 -27
  51. package/dist/column-groups.js +1 -1
  52. package/dist/column-resize.d.ts +46 -0
  53. package/dist/column-resize.js +205 -0
  54. package/dist/columns.d.ts +0 -3
  55. package/dist/columns.js +0 -57
  56. package/dist/core.d.ts +19 -4
  57. package/dist/core.js +460 -119
  58. package/dist/editing.js +12 -7
  59. package/dist/editors/cell-editors.d.ts +11 -0
  60. package/dist/editors/cell-editors.js +18 -1
  61. package/dist/filtering/excel-filters.js +28 -0
  62. package/dist/grid-messages.d.ts +15 -0
  63. package/dist/grid-messages.js +15 -0
  64. package/dist/group-display.d.ts +1 -1
  65. package/dist/index.d.ts +6 -1
  66. package/dist/index.js +10 -0
  67. package/dist/list-option.d.ts +6 -0
  68. package/dist/menus.js +41 -8
  69. package/dist/row-resize.d.ts +11 -0
  70. package/dist/row-resize.js +7 -1
  71. package/dist/sanitize-html.d.ts +37 -0
  72. package/dist/sanitize-html.js +234 -0
  73. package/dist/selection-bar-view.svelte.d.ts +25 -0
  74. package/dist/selection-bar-view.svelte.js +13 -0
  75. package/dist/selection.d.ts +2 -1
  76. package/dist/selection.js +133 -8
  77. package/dist/spreadsheet.d.ts +1 -1
  78. package/dist/spreadsheet.js +1 -1
  79. package/package.json +1 -1
  80. package/src/GridMenus.svelte +201 -18
  81. package/src/SvAutoComplete.svelte +4 -0
  82. package/src/SvComboBox.svelte +5 -1
  83. package/src/SvDateRangeInput.svelte +25 -3
  84. package/src/SvDropDownList.svelte +4 -0
  85. package/src/SvForm.svelte +8 -8
  86. package/src/SvGrid.controller.svelte.ts +404 -133
  87. package/src/SvGrid.css +222 -1
  88. package/src/SvGrid.helpers.ts +87 -0
  89. package/src/SvGrid.svelte +255 -56
  90. package/src/SvGrid.types.ts +153 -1
  91. package/src/SvGridCellEditor.svelte +99 -17
  92. package/src/SvGridSelect.svelte +6 -2
  93. package/src/SvListBox.svelte +8 -0
  94. package/src/SvMultiSelect.svelte +6 -2
  95. package/src/SvNumberInput.svelte +4 -0
  96. package/src/SvRichCell.svelte +125 -0
  97. package/src/SvTextArea.svelte +5 -1
  98. package/src/SvTreeSelect.svelte +6 -2
  99. package/src/builtin-editors.test.ts +18 -1
  100. package/src/builtin-editors.ts +19 -3
  101. package/src/cell-values.ts +30 -0
  102. package/src/clipboard.test.ts +93 -20
  103. package/src/clipboard.ts +433 -33
  104. package/src/column-groups.ts +1 -1
  105. package/src/column-resize.test.ts +381 -0
  106. package/src/column-resize.ts +227 -0
  107. package/src/columns.test.ts +0 -103
  108. package/src/columns.ts +0 -58
  109. package/src/core.aggregate.test.ts +134 -0
  110. package/src/core.filter.test.ts +156 -0
  111. package/src/core.grouping.test.ts +146 -0
  112. package/src/core.row-shape.test.ts +119 -0
  113. package/src/core.rowmodel-cache.test.ts +121 -0
  114. package/src/core.sort.test.ts +293 -0
  115. package/src/core.ts +516 -119
  116. package/src/date-string-column.test.ts +87 -0
  117. package/src/editing.test.ts +25 -0
  118. package/src/editing.ts +12 -9
  119. package/src/editor-block-prop.test.ts +132 -0
  120. package/src/editors/cell-editors.ts +27 -1
  121. package/src/filtering/excel-filters.ts +30 -0
  122. package/src/filtering/normalize-fast-path.test.ts +104 -0
  123. package/src/grid-messages.ts +34 -0
  124. package/src/group-display.ts +1 -1
  125. package/src/index.ts +25 -1
  126. package/src/list-option.ts +6 -0
  127. package/src/menus.test.ts +43 -0
  128. package/src/menus.ts +38 -8
  129. package/src/move-cells.test.ts +850 -0
  130. package/src/number-editor-literal.test.ts +84 -0
  131. package/src/number-editor.grid.test.ts +160 -0
  132. package/src/resize-props.test.ts +361 -0
  133. package/src/row-resize.test.ts +31 -0
  134. package/src/row-resize.ts +21 -3
  135. package/src/sanitize-html.test.ts +182 -0
  136. package/src/sanitize-html.ts +235 -0
  137. package/src/selection-bar-view.svelte.ts +36 -0
  138. package/src/selection.test.ts +157 -0
  139. package/src/selection.ts +135 -8
  140. package/src/spreadsheet.ts +1 -1
  141. package/src/svgrid.filter-menu-listbox.svelte.test.ts +56 -10
  142. package/src/svgrid.in-suggest-snapshot.svelte.test.ts +91 -0
  143. package/src/svgrid.menu-scroll-close.test.ts +204 -0
  144. package/src/svgrid.selection-bar-seam.test.ts +185 -0
  145. package/src/svgrid.upsell-license.test.ts +117 -0
  146. package/dist/cdn/GridMenus-BfTAKn84.js +0 -488
  147. package/dist/cdn/GridMenus-C3bJd7w8.js +0 -489
  148. package/dist/cdn/SvDateTimePicker-CHnUkWcH.js +0 -2206
  149. package/dist/cdn/SvDateTimePicker-CRQH_U7E.js +0 -2201
  150. package/dist/cdn/SvGridCellEditor-BGUCzuPB.js +0 -523
  151. package/dist/cdn/SvGridCellEditor-G8IMWIws.js +0 -522
@@ -225,6 +225,19 @@ export function createSvGridController(rawProps, domIdBase) {
225
225
  * preview" overlay on cells between the source range and the pointer
226
226
  * cell; on pointerup we extrapolate the source pattern into them. */
227
227
  let fillDrag = $state(null);
228
+ /** An in-flight Excel-style range MOVE (drag the selection's border to
229
+ * relocate its values). Same source rectangle as `fillDrag`, plus where
230
+ * inside that rectangle the pointer grabbed it - the drop offset is
231
+ * `target - grab`, which is what lets a drag started on the range's LEFT
232
+ * edge land the range where the pointer is rather than jumping it.
233
+ * `copy` mirrors the Ctrl / Cmd key and is re-read on every move, so the
234
+ * user can change their mind mid-drag exactly as in a spreadsheet. */
235
+ let moveDrag = $state(null);
236
+ /** True while the pointer hovers the grab strip on a selection border, so
237
+ * the root can switch to a move cursor. Kept as its own flag rather than
238
+ * recomputed per cell: it flips at most twice per border crossing and only
239
+ * one attribute on one element reads it. */
240
+ let moveGrabHover = $state(false);
228
241
  let activeAtPointerDown = null;
229
242
  let editingCell = $state(null);
230
243
  // Full-row editing: the row currently in whole-row edit + its per-column
@@ -275,9 +288,6 @@ export function createSvGridController(rawProps, domIdBase) {
275
288
  let editorSelectAll = true;
276
289
  /** Per-column width overrides set by the resize handles. */
277
290
  let columnWidths = $state({});
278
- let resizingColumnId = $state(null);
279
- let resizeStartX = 0;
280
- let resizeStartWidth = 0;
281
291
  const MIN_COLUMN_WIDTH = 40;
282
292
  /** Columns pinned to the left or right edge of the grid (sticky positioning).
283
293
  * Seeded from `props.initialColumnPinning` so demos / tests can show the
@@ -294,7 +304,26 @@ export function createSvGridController(rawProps, domIdBase) {
294
304
  let dataStateVersion = $state(0);
295
305
  const selectionColumnWidth = 44;
296
306
  const rowNumberColumnWidth = $derived(props.rowNumberWidth ?? 56);
297
- const showRowNumbersEffective = $derived(props.showRowNumbers ?? false);
307
+ // A per-row `rowHeight` function already supplies heights, so auto-measuring
308
+ // would fight it. Fixed numbers are fine - they become the pre-measure estimate.
309
+ const autoRowHeightOn = $derived(props.autoRowHeight === true && typeof props.rowHeight !== "function");
310
+ // Auto height measures the row from its content, so a dragged height would be
311
+ // overwritten on the next measurement pass. Let auto height win rather than
312
+ // having the two fight.
313
+ const rowResizeOn = $derived(props.rowResize === true && !autoRowHeightOn);
314
+ /**
315
+ * The row-number gutter. Explicit `showRowNumbers` always wins; otherwise
316
+ * `rowResize` brings it in, because a resizable row needs a row header to
317
+ * grab - that is where the drag strip lives, and it is where a spreadsheet
318
+ * puts it. Dragging the edge of a data cell instead works but reads as an
319
+ * accident.
320
+ *
321
+ * Both derived above are declared here rather than beside the rest of the
322
+ * row-resize state further down: a `$derived` referenced before its
323
+ * declaration in a .svelte.ts silently compiles to an empty getter, so this
324
+ * gate would have read `undefined` and the gutter would never appear.
325
+ */
326
+ const showRowNumbersEffective = $derived(props.showRowNumbers ?? rowResizeOn);
298
327
  let columnMenuFor = $state(null);
299
328
  let columnMenuTab = $state("general");
300
329
  let columnMenuPos = $state({ x: 0, y: 0 });
@@ -318,11 +347,11 @@ export function createSvGridController(rawProps, domIdBase) {
318
347
  let commentEditFor = $state(null);
319
348
  let commentDraft = $state("");
320
349
  let valueFilters = $state({});
321
- const viewportWidth = $derived.by(() => {
350
+ const viewportWidth = $derived.by(function viewportWidth_d() {
322
351
  viewportVersion;
323
352
  return scrollContainer ? scrollContainer.clientWidth : 0;
324
353
  });
325
- const viewportHeight = $derived.by(() => {
354
+ const viewportHeight = $derived.by(function viewportHeight_d() {
326
355
  viewportVersion;
327
356
  return scrollContainer ? scrollContainer.clientHeight : 0;
328
357
  });
@@ -344,7 +373,7 @@ export function createSvGridController(rawProps, domIdBase) {
344
373
  const hb = column.columnDef?.hideBelow;
345
374
  return hb != null && viewportWidth > 0 && viewportWidth < hb;
346
375
  }
347
- const scrollMetrics = $derived.by(() => {
376
+ const scrollMetrics = $derived.by(function scrollMetrics_d() {
348
377
  scrollVersion;
349
378
  viewportVersion;
350
379
  // Track the virtualizers' versions too so when data loads or row /
@@ -378,7 +407,7 @@ export function createSvGridController(rawProps, domIdBase) {
378
407
  * - virtualizer.getTotalSize(): correct at initial load (before first paint)
379
408
  * - scrollMetrics.scrollHeight: correct after detail rows expand (DOM is live,
380
409
  * ResizeObserver on gridRootEl already bumps scrollVersion at that point) */
381
- const hasVerticalOverflow = $derived.by(() => {
410
+ const hasVerticalOverflow = $derived.by(function hasVerticalOverflow_d() {
382
411
  virtualizer.version;
383
412
  const virtualizerSize = virtualizer.getTotalSize();
384
413
  // scrollMetrics.scrollHeight is 0 before initial paint; once the table
@@ -408,6 +437,11 @@ export function createSvGridController(rawProps, domIdBase) {
408
437
  props.enableCellSelection ??
409
438
  ((props.selectionMode ?? "both") === "cell" ||
410
439
  (props.selectionMode ?? "both") === "both"));
440
+ // Range drag-and-drop rides on cell selection: there is no range to grab
441
+ // without it, so it can never be on alone. Default ON, matching the fill
442
+ // handle - both are spreadsheet affordances users arrive expecting, and
443
+ // neither is reachable until something is actually selected.
444
+ const moveCellsEffective = $derived((props.moveCells ?? true) && enableCellSelectionEffective);
411
445
  // The aggregate footer row. `summary` is the shortcut alias, so it wins over
412
446
  // the fine-grained prop - the same precedence `selectable` uses.
413
447
  //
@@ -636,7 +670,7 @@ export function createSvGridController(rawProps, domIdBase) {
636
670
  // Declared here rather than beside the other pagination/state derivations:
637
671
  // the auto-group column pipeline below reads it, and a `const` used above its
638
672
  // declaration is a type error even though `$derived` is lazy enough at runtime.
639
- const groupingColumns = $derived.by(() => {
673
+ const groupingColumns = $derived.by(function groupingColumns_d() {
640
674
  gridStateVersion;
641
675
  return grid.getState().grouping ?? [];
642
676
  });
@@ -657,7 +691,7 @@ export function createSvGridController(rawProps, domIdBase) {
657
691
  * any other column. They carry no `field` - their cell content is resolved
658
692
  * from the row's group state at render time.
659
693
  */
660
- const autoGroupColumns = $derived.by(() => {
694
+ const autoGroupColumns = $derived.by(function autoGroupColumns_d() {
661
695
  if (!groupColumnMode)
662
696
  return [];
663
697
  const sourceById = new Map(grid.getAllColumns().map((c) => [c.id, c]));
@@ -723,7 +757,7 @@ export function createSvGridController(rawProps, domIdBase) {
723
757
  depth: columnId === "__autoGroup" ? depth : 0,
724
758
  };
725
759
  }
726
- const allColumns = $derived.by(() => {
760
+ const allColumns = $derived.by(function allColumns_d() {
727
761
  let raw = grid
728
762
  .getAllColumns()
729
763
  .filter((column) => !hiddenColumns[column.id] &&
@@ -771,7 +805,7 @@ export function createSvGridController(rawProps, domIdBase) {
771
805
  return [...left, ...unpinned, ...right];
772
806
  });
773
807
  /** Header groups reordered to match {@link allColumns}. */
774
- const headerGroups = $derived.by(() => {
808
+ const headerGroups = $derived.by(function headerGroups_d() {
775
809
  const base = grid.getHeaderGroups();
776
810
  if (!base.length)
777
811
  return base;
@@ -797,7 +831,7 @@ export function createSvGridController(rawProps, domIdBase) {
797
831
  }
798
832
  return [{ id: base[0].id, headers }];
799
833
  });
800
- const groupHeaderRows = $derived.by(() => {
834
+ const groupHeaderRows = $derived.by(function groupHeaderRows_d() {
801
835
  const userCols = props.columns ?? [];
802
836
  // 1. Find max depth in the user-provided column tree.
803
837
  function maxDepth(defs) {
@@ -905,7 +939,7 @@ export function createSvGridController(rawProps, domIdBase) {
905
939
  return rows;
906
940
  });
907
941
  /** Cumulative pixel offsets for left- and right-pinned columns. */
908
- const pinnedOffsets = $derived.by(() => {
942
+ const pinnedOffsets = $derived.by(function pinnedOffsets_d() {
909
943
  const rowNumberWidth = showRowNumbersEffective ? rowNumberColumnWidth : 0;
910
944
  const selectionWidth = showRowSelectionEffective ? selectionColumnWidth : 0;
911
945
  const left = {};
@@ -950,7 +984,7 @@ export function createSvGridController(rawProps, domIdBase) {
950
984
  const hasConditionalFormats = $derived((props.conditionalFormats?.length ?? 0) > 0);
951
985
  // Per-column numeric min/max, needed only by colorScale / dataBar formats.
952
986
  // Lazy: this derived never runs unless `conditionalFormats` is set.
953
- const conditionalColumnStats = $derived.by(() => {
987
+ const conditionalColumnStats = $derived.by(function conditionalColumnStats_d() {
954
988
  const map = new Map();
955
989
  const formats = props.conditionalFormats;
956
990
  if (!formats?.length || !formatsNeedingStats(formats))
@@ -990,14 +1024,14 @@ export function createSvGridController(rawProps, domIdBase) {
990
1024
  }
991
1025
  return map;
992
1026
  });
993
- const sortDirectionByColumn = $derived.by(() => {
1027
+ const sortDirectionByColumn = $derived.by(function sortDirectionByColumn_d() {
994
1028
  gridStateVersion;
995
1029
  const directions = {};
996
1030
  for (const column of allColumns)
997
1031
  directions[column.id] = column.getIsSorted();
998
1032
  return directions;
999
1033
  });
1000
- const paginationState = $derived.by(() => {
1034
+ const paginationState = $derived.by(function paginationState_d() {
1001
1035
  gridStateVersion;
1002
1036
  return grid.getState().pagination ?? { pageIndex: 0, pageSize: 10 };
1003
1037
  });
@@ -1006,30 +1040,38 @@ export function createSvGridController(rawProps, domIdBase) {
1006
1040
  * compute the correct "X to Y of Z" range and total page count when
1007
1041
  * filters reduce the dataset.
1008
1042
  */
1009
- const allRowsBeforePagination = $derived.by(() => {
1010
- // Depend on dataStateVersion (row-model-affecting store changes) NOT
1011
- // gridStateVersion - so moving the active cell / selection does not force
1012
- // this O(rows) pipeline to re-run. Filter-input state (globalFilter etc.)
1013
- // and internalData are read below and tracked as their own dependencies.
1014
- dataStateVersion;
1015
- // Touch internalData + internalColumns so the row model re-derives when
1016
- // the consumer replaces the data array (e.g. via a "Reset" button).
1017
- void internalData;
1018
- void internalColumns;
1019
- const rawRows = grid.getRowModel().rows;
1020
- // External-filter mode: the consumer fetched / pre-filtered the rows
1021
- // themselves (server-side data sources). Skip every local filter pass
1022
- // so the data isn't double-filtered against the visible page.
1023
- if (externalFilterEnabled)
1024
- return rawRows;
1025
- let rows = rawRows;
1026
- if (globalFilter.trim()) {
1027
- const needle = normalizeForFilter(globalFilter, (props.filterLocale ?? props.localization?.locale));
1028
- rows = rows.filter((row) => row
1029
- .getAllCells()
1030
- .some((cell) => normalizeForFilter(String(cell.getValue() ?? ""), (props.filterLocale ?? props.localization?.locale))
1031
- .includes(needle)));
1032
- }
1043
+ /**
1044
+ * The per-column filter stages: the menu's (up to two) conditions, then the
1045
+ * set-filter checklists. Split out of `allRowsBeforePagination` so the facet
1046
+ * list can run the SAME pipeline with its own column left out - a checklist
1047
+ * that offers values excluded by the other columns' filters sends you to an
1048
+ * empty grid. Re-implementing the stages beside the original would have let
1049
+ * the two drift.
1050
+ *
1051
+ * `excludeColumnId` omits that column's own filters, which is what "every
1052
+ * other filter" means for the column whose menu is open.
1053
+ */
1054
+ function applyColumnFilters(inputRows, excludeColumnId) {
1055
+ let rows = inputRows;
1056
+ /**
1057
+ * Resolve a column ONCE and return a reader for it.
1058
+ *
1059
+ * `getRowColumnValue` finds the column with a linear scan over every
1060
+ * column, which is fine for a one-off read and wrong inside a loop over
1061
+ * 100,000 rows - it made each filtered column cost O(rows x columns).
1062
+ * The id is fixed for the life of a filter, so the scan is hoisted here and
1063
+ * the row loop gets a closure that only reads.
1064
+ *
1065
+ * Falls back to the row's own accessor when the id names no column, which
1066
+ * is also what `getRowColumnValue` does - group rows override
1067
+ * `getCellValueByColumnId` to return their aggregate.
1068
+ */
1069
+ const makeColumnReader = (columnId) => {
1070
+ const column = allColumns.find((entry) => entry.id === columnId);
1071
+ return column
1072
+ ? (row) => getColumnBaseValue(row, column)
1073
+ : (row) => row.getCellValueByColumnId(columnId);
1074
+ };
1033
1075
  // A single condition is "active" if it has the value(s) it needs.
1034
1076
  const condActive = (op, value, valueTo) => {
1035
1077
  if (op === "isBlank" || op === "isNotBlank")
@@ -1044,7 +1086,9 @@ export function createSvGridController(rawProps, domIdBase) {
1044
1086
  // redundant passes.
1045
1087
  const compileCond = (columnId, op, value, valueTo) => compileExcelFilter({ id: columnId, operator: op, value, valueTo: op === "between" ? valueTo : undefined }, { locale: (props.filterLocale ?? props.localization?.locale) });
1046
1088
  // A column filter is active if either of its (up to two) conditions is.
1047
- const menuFilters = Object.entries(filterMenuValues).filter(([_, f]) => {
1089
+ const menuFilters = Object.entries(filterMenuValues).filter(([id, f]) => {
1090
+ if (id === excludeColumnId)
1091
+ return false;
1048
1092
  const a = condActive(f.operator, f.value, f.valueTo);
1049
1093
  const b = !!f.operator2 && condActive(f.operator2, f.value2 ?? "", f.valueTo2);
1050
1094
  return a || b;
@@ -1054,6 +1098,11 @@ export function createSvGridController(rawProps, domIdBase) {
1054
1098
  // become compiled predicates before a single row is tested.
1055
1099
  const compiledMenuFilters = menuFilters.map(([columnId, f]) => ({
1056
1100
  columnId,
1101
+ // The column, resolved once. `getRowColumnValue` looks it up with a
1102
+ // linear scan of every column, and the id is fixed per filter, so
1103
+ // leaving that inside the row loop made it O(rows x columns) per
1104
+ // filtered column for no reason.
1105
+ readValue: makeColumnReader(columnId),
1057
1106
  join: f.join,
1058
1107
  a: condActive(f.operator, f.value, f.valueTo)
1059
1108
  ? compileCond(columnId, f.operator, f.value, f.valueTo)
@@ -1062,8 +1111,8 @@ export function createSvGridController(rawProps, domIdBase) {
1062
1111
  ? compileCond(columnId, f.operator2, f.value2 ?? "", f.valueTo2)
1063
1112
  : null,
1064
1113
  }));
1065
- rows = rows.filter((row) => compiledMenuFilters.every(({ columnId, join, a, b }) => {
1066
- const cellValue = getRowColumnValue(row, columnId);
1114
+ rows = rows.filter((row) => compiledMenuFilters.every(({ readValue, join, a, b }) => {
1115
+ const cellValue = readValue(row);
1067
1116
  const ra = a ? a(cellValue) : null;
1068
1117
  const rb = b ? b(cellValue) : null;
1069
1118
  if (ra === null)
@@ -1073,7 +1122,7 @@ export function createSvGridController(rawProps, domIdBase) {
1073
1122
  return join === "OR" ? ra || rb : ra && rb;
1074
1123
  }));
1075
1124
  }
1076
- const valueFilterEntries = Object.entries(valueFilters);
1125
+ const valueFilterEntries = Object.entries(valueFilters).filter(([id]) => id !== excludeColumnId);
1077
1126
  if (valueFilterEntries.length) {
1078
1127
  // Resolve bucket defs up front so we don't re-hit the derived map
1079
1128
  // for every row × column combination. Columns without bucketing
@@ -1081,10 +1130,11 @@ export function createSvGridController(rawProps, domIdBase) {
1081
1130
  const bucketEntries = valueFilterEntries.map(([columnId, allowed]) => ({
1082
1131
  columnId,
1083
1132
  allowed,
1133
+ readValue: makeColumnReader(columnId),
1084
1134
  buckets: facetBucketsByColumn.get(columnId) ?? null,
1085
1135
  }));
1086
- rows = rows.filter((row) => bucketEntries.every(({ columnId, allowed, buckets }) => {
1087
- const raw = getRowColumnValue(row, columnId);
1136
+ rows = rows.filter((row) => bucketEntries.every(({ allowed, buckets, readValue }) => {
1137
+ const raw = readValue(row);
1088
1138
  if (buckets) {
1089
1139
  // Range-bucketed filter: find which bucket this row's value
1090
1140
  // falls into and check whether that bucket's label is allowed.
@@ -1101,6 +1151,33 @@ export function createSvGridController(rawProps, domIdBase) {
1101
1151
  return allowed.has(String(raw ?? ""));
1102
1152
  }));
1103
1153
  }
1154
+ return rows;
1155
+ }
1156
+ const allRowsBeforePagination = $derived.by(function allRowsBeforePagination_d() {
1157
+ // Depend on dataStateVersion (row-model-affecting store changes) NOT
1158
+ // gridStateVersion - so moving the active cell / selection does not force
1159
+ // this O(rows) pipeline to re-run. Filter-input state (globalFilter etc.)
1160
+ // and internalData are read below and tracked as their own dependencies.
1161
+ dataStateVersion;
1162
+ // Touch internalData + internalColumns so the row model re-derives when
1163
+ // the consumer replaces the data array (e.g. via a "Reset" button).
1164
+ void internalData;
1165
+ void internalColumns;
1166
+ const rawRows = grid.getRowModel().rows;
1167
+ // External-filter mode: the consumer fetched / pre-filtered the rows
1168
+ // themselves (server-side data sources). Skip every local filter pass
1169
+ // so the data isn't double-filtered against the visible page.
1170
+ if (externalFilterEnabled)
1171
+ return rawRows;
1172
+ let rows = rawRows;
1173
+ if (globalFilter.trim()) {
1174
+ const needle = normalizeForFilter(globalFilter, (props.filterLocale ?? props.localization?.locale));
1175
+ rows = rows.filter((row) => row
1176
+ .getAllCells()
1177
+ .some((cell) => normalizeForFilter(String(cell.getValue() ?? ""), (props.filterLocale ?? props.localization?.locale))
1178
+ .includes(needle)));
1179
+ }
1180
+ rows = applyColumnFilters(rows);
1104
1181
  // --- Advanced filter (Pro) ---------------------------------------------
1105
1182
  // Runs LAST for two reasons: it evaluates over the smallest row set, and
1106
1183
  // its aggregates (`SUM(amount) > 1000`) fold over what the user is
@@ -1142,7 +1219,7 @@ export function createSvGridController(rawProps, domIdBase) {
1142
1219
  * is active we page by DATA rows and reprint each page's ancestor banners.
1143
1220
  * Null when grouping is off, so the flat path stays a cheap slice.
1144
1221
  */
1145
- const groupedPage = $derived.by(() => {
1222
+ const groupedPage = $derived.by(function groupedPage_d() {
1146
1223
  if (!paginationEnabled || externalPaginationEnabled)
1147
1224
  return null;
1148
1225
  if (!groupingColumns.length)
@@ -1211,7 +1288,7 @@ export function createSvGridController(rawProps, domIdBase) {
1211
1288
  * the full dataset rather than the current page (see the comment above
1212
1289
  * `_rowModels`).
1213
1290
  */
1214
- const allRows = $derived.by(() => {
1291
+ const allRows = $derived.by(function allRows_d() {
1215
1292
  const paged = (() => {
1216
1293
  const rows = allRowsBeforePagination;
1217
1294
  // External pagination: `data` already IS the current page - never slice.
@@ -1278,10 +1355,49 @@ export function createSvGridController(rawProps, domIdBase) {
1278
1355
  const paginationTotalRows = $derived(externalPaginationEnabled ? (props.rowCount ?? 0) : paginatedRowTotal);
1279
1356
  const paginationPageIndex = $derived(externalPaginationEnabled ? (props.pageIndex ?? 0) : paginationState.pageIndex);
1280
1357
  const paginationPageSize = $derived(externalPaginationEnabled ? (props.pageSize ?? 10) : paginationState.pageSize);
1281
- const rowSelectionState = $derived.by(() => {
1358
+ const rowSelectionState = $derived.by(function rowSelectionState_d() {
1282
1359
  gridStateVersion;
1283
1360
  return grid.getState().rowSelection ?? {};
1284
1361
  });
1362
+ // ---- Selection bar ----------------------------------------------------
1363
+ // The floating bulk-action bar. Off unless asked for: it overlaps a row, and
1364
+ // what belongs on it is app-specific. The RENDERER is Enterprise and arrives
1365
+ // through the selection-bar-view seam; everything here is free.
1366
+ const selectionBarOn = $derived(props.selectionBar !== undefined && props.selectionBar !== false);
1367
+ /** The prop in its three shapes - `true`, an action array, or a full config -
1368
+ * normalised to the config object the renderer reads. */
1369
+ const selectionBarConfig = $derived.by(function selectionBarConfig_d() {
1370
+ const raw = props.selectionBar;
1371
+ if (Array.isArray(raw))
1372
+ return { actions: raw };
1373
+ if (raw && typeof raw === "object")
1374
+ return raw;
1375
+ return {};
1376
+ });
1377
+ const selectionBarActions = $derived(selectionBarConfig.actions ?? []);
1378
+ const selectionBarPosition = $derived(selectionBarConfig.position === "top" ? "top" : "bottom");
1379
+ const selectionBarMaxVisible = $derived(typeof selectionBarConfig.maxVisible === "number" && selectionBarConfig.maxVisible > 0
1380
+ ? selectionBarConfig.maxVisible
1381
+ : 6);
1382
+ const selectionBarHideClear = $derived(selectionBarConfig.hideClear === true);
1383
+ /** Selected rows in DISPLAY order, plus their ids - what every action on the
1384
+ * bar receives, and what its count shows. Built from `allRows` rather than
1385
+ * the selection map so the order matches what the user is looking at. */
1386
+ const selectionBarTarget = $derived.by(function selectionBarTarget_d() {
1387
+ const rows = [];
1388
+ const ids = [];
1389
+ if (!selectionBarOn)
1390
+ return { rows, ids };
1391
+ for (const row of allRows) {
1392
+ if (isGroupRow(row))
1393
+ continue;
1394
+ if (rowSelectionState[row.id]) {
1395
+ rows.push(row.original);
1396
+ ids.push(row.id);
1397
+ }
1398
+ }
1399
+ return { rows, ids };
1400
+ });
1285
1401
  // Forward selection changes to the consumer. Skips the very first invocation
1286
1402
  // (the initial empty state) so consumers don't get a spurious callback on mount.
1287
1403
  let lastSelectionSerialized = "";
@@ -1331,7 +1447,7 @@ export function createSvGridController(rawProps, domIdBase) {
1331
1447
  const statusBarAggregates = $derived(typeof props.statusBar === "object" && props.statusBar.aggregates
1332
1448
  ? props.statusBar.aggregates
1333
1449
  : ["count", "sum", "avg", "min", "max"]);
1334
- const statusBarStats = $derived.by(() => {
1450
+ const statusBarStats = $derived.by(function statusBarStats_d() {
1335
1451
  if (!statusBarEnabled)
1336
1452
  return null;
1337
1453
  const a = selectionRange.anchor;
@@ -1390,7 +1506,7 @@ export function createSvGridController(rawProps, domIdBase) {
1390
1506
  const toolPanelEnabled = $derived(props.toolPanel === true);
1391
1507
  // Every column (including hidden ones) in the user's current order, so the
1392
1508
  // panel can toggle/reorder anything. Group columns are flagged live.
1393
- const toolPanelColumns = $derived.by(() => {
1509
+ const toolPanelColumns = $derived.by(function toolPanelColumns_d() {
1394
1510
  gridStateVersion;
1395
1511
  const all = grid.getAllColumns();
1396
1512
  if (!userColumnOrder.length)
@@ -1428,7 +1544,7 @@ export function createSvGridController(rawProps, domIdBase) {
1428
1544
  // reveals. `a11y/grid-announcements.ts` documents what is deliberately left
1429
1545
  // unsaid, and why saying it would make the grid talk over itself.
1430
1546
  /** Data rows before any local filtering - the denominator in "12 of 250". */
1431
- const unfilteredRowTotal = $derived.by(() => {
1547
+ const unfilteredRowTotal = $derived.by(function unfilteredRowTotal_d() {
1432
1548
  dataStateVersion;
1433
1549
  void internalData;
1434
1550
  return grid.getRowModel().rows.length;
@@ -1506,7 +1622,7 @@ export function createSvGridController(rawProps, domIdBase) {
1506
1622
  props.onPivotModeChange?.(next);
1507
1623
  }
1508
1624
  const pivotActive = $derived(!!pivotConfig && pivotModeOn && hasPivotEngine());
1509
- const pivotResult = $derived.by(() => {
1625
+ const pivotResult = $derived.by(function pivotResult_d() {
1510
1626
  if (!pivotActive || !pivotConfig)
1511
1627
  return null;
1512
1628
  const engine = getPivotEngine();
@@ -1584,7 +1700,7 @@ export function createSvGridController(rawProps, domIdBase) {
1584
1700
  const h = col.columnDef.header;
1585
1701
  return typeof h === "string" && h ? h : (col.columnDef.field ?? col.id);
1586
1702
  };
1587
- const chartableColumns = $derived.by(() => {
1703
+ const chartableColumns = $derived.by(function chartableColumns_d() {
1588
1704
  const dims = [];
1589
1705
  const measures = [];
1590
1706
  for (const col of allColumns) {
@@ -1599,7 +1715,7 @@ export function createSvGridController(rawProps, domIdBase) {
1599
1715
  }
1600
1716
  return { dims, measures };
1601
1717
  });
1602
- const chartColumnDefaults = $derived.by(() => {
1718
+ const chartColumnDefaults = $derived.by(function chartColumnDefaults_d() {
1603
1719
  const { dims, measures } = chartableColumns;
1604
1720
  let dimId = dims[0]?.id ?? null;
1605
1721
  let seriesId = null;
@@ -1972,7 +2088,7 @@ export function createSvGridController(rawProps, domIdBase) {
1972
2088
  // the virtualizer's own (real) numbers take over.
1973
2089
  const preMeasureRowHeight = $derived(typeof props.rowHeight === "number" ? props.rowHeight : 30);
1974
2090
  const preMeasureViewport = $derived(typeof props.containerHeight === "number" ? props.containerHeight : 520);
1975
- const virtualRows = $derived.by(() => {
2091
+ const virtualRows = $derived.by(function virtualRows_d() {
1976
2092
  virtualizer.version;
1977
2093
  const items = virtualizer.getVirtualItems();
1978
2094
  if (items.length || allRows.length === 0)
@@ -1983,7 +2099,7 @@ export function createSvGridController(rawProps, domIdBase) {
1983
2099
  // only, and it matches what the server produced, so hydration is clean.
1984
2100
  return buildPreMeasureItems(allRows.length, preMeasureRowHeight, preMeasureViewport, props.overscan ?? 8);
1985
2101
  });
1986
- const virtualRowTotalSize = $derived.by(() => {
2102
+ const virtualRowTotalSize = $derived.by(function virtualRowTotalSize_d() {
1987
2103
  virtualizer.version;
1988
2104
  const size = virtualizer.getTotalSize();
1989
2105
  // Keep the scroll height honest during the pre-measurement render, so the
@@ -2031,7 +2147,7 @@ export function createSvGridController(rawProps, domIdBase) {
2031
2147
  // OWN committed scroll offset - not the live DOM scrollTop - so the spacer
2032
2148
  // shift and the rendered window are always computed from the same state and
2033
2149
  // can never skew by a frame (which would jitter at extreme scale).
2034
- const rowOffsetAdjustment = $derived.by(() => {
2150
+ const rowOffsetAdjustment = $derived.by(function rowOffsetAdjustment_d() {
2035
2151
  if (!rowScrollScalingActive)
2036
2152
  return 0;
2037
2153
  virtualizer.version;
@@ -2042,11 +2158,11 @@ export function createSvGridController(rawProps, domIdBase) {
2042
2158
  // virtualRowStart / virtualRowBottomSpacer.
2043
2159
  const rowTopSpacer = $derived(Math.max(virtualRowStart - rowOffsetAdjustment, 0));
2044
2160
  const rowBottomSpacer = $derived(Math.max(rowDomTotalSize - (virtualRowEnd - rowOffsetAdjustment), 0));
2045
- const virtualColumns = $derived.by(() => {
2161
+ const virtualColumns = $derived.by(function virtualColumns_d() {
2046
2162
  columnVirtualizerVersion;
2047
2163
  return columnVirtualizer.getVirtualItems();
2048
2164
  });
2049
- const virtualColumnTotalSize = $derived.by(() => {
2165
+ const virtualColumnTotalSize = $derived.by(function virtualColumnTotalSize_d() {
2050
2166
  columnVirtualizerVersion;
2051
2167
  return columnVirtualizer.getTotalSize();
2052
2168
  });
@@ -2061,7 +2177,7 @@ export function createSvGridController(rawProps, domIdBase) {
2061
2177
  return item;
2062
2178
  });
2063
2179
  };
2064
- const renderedColumnItems = $derived.by(() => {
2180
+ const renderedColumnItems = $derived.by(function renderedColumnItems_d() {
2065
2181
  if (!columnVirtualizationEnabled)
2066
2182
  return allColumnItems();
2067
2183
  // Column virtualization is ON by default and its virtualizer, like the row
@@ -2107,7 +2223,7 @@ export function createSvGridController(rawProps, domIdBase) {
2107
2223
  const renderedColumns = $derived.by(() => renderedColumnItems
2108
2224
  .map((item) => ({ item, column: allColumns[item.index] }))
2109
2225
  .filter(hasRenderedColumn));
2110
- const totalColumnWidth = $derived.by(() => {
2226
+ const totalColumnWidth = $derived.by(function totalColumnWidth_d() {
2111
2227
  if (columnVirtualizationEnabled)
2112
2228
  return virtualColumnTotalSize;
2113
2229
  let total = 0;
@@ -2124,7 +2240,7 @@ export function createSvGridController(rawProps, domIdBase) {
2124
2240
  * column instead is reactive to both `columnWidths` and
2125
2241
  * `fittedColumnWidths`, so the overflow decision settles in the same
2126
2242
  * render where fit-scaling lands - no race, no scrollbar flash. */
2127
- const hasHorizontalOverflow = $derived.by(() => {
2243
+ const hasHorizontalOverflow = $derived.by(function hasHorizontalOverflow_d() {
2128
2244
  const fixedCols = (showRowNumbersEffective ? rowNumberColumnWidth : 0) +
2129
2245
  (showRowSelectionEffective ? selectionColumnWidth : 0);
2130
2246
  let total = fixedCols;
@@ -2150,7 +2266,7 @@ export function createSvGridController(rawProps, domIdBase) {
2150
2266
  * everything stays pixel-aligned. When virtualization is off this is a
2151
2267
  * pass-through.
2152
2268
  */
2153
- const groupHeaderRowsWindowed = $derived.by(() => {
2269
+ const groupHeaderRowsWindowed = $derived.by(function groupHeaderRowsWindowed_d() {
2154
2270
  const base = groupHeaderRows;
2155
2271
  if (!columnVirtualizationEnabled || base.length === 0)
2156
2272
  return base;
@@ -2176,11 +2292,11 @@ export function createSvGridController(rawProps, domIdBase) {
2176
2292
  return { ...row, cells };
2177
2293
  });
2178
2294
  });
2179
- const activeCell = $derived.by(() => {
2295
+ const activeCell = $derived.by(function activeCell_d() {
2180
2296
  gridStateVersion;
2181
2297
  return (grid.getState().activeCell ?? { rowIndex: 0, colIndex: 0, cellId: null });
2182
2298
  });
2183
- const activeDescendantId = $derived.by(() => {
2299
+ const activeDescendantId = $derived.by(function activeDescendantId_d() {
2184
2300
  const active = activeCell;
2185
2301
  const inRows = active.rowIndex >= 0 && active.rowIndex < allRows.length;
2186
2302
  const inCols = active.colIndex >= 0 && active.colIndex < allColumns.length;
@@ -2222,7 +2338,7 @@ export function createSvGridController(rawProps, domIdBase) {
2222
2338
  * `null` above the cell limit - that case stays on the rAF-deferred effect,
2223
2339
  * which keeps a huge grid painting before it totals.
2224
2340
  */
2225
- const eagerSummaries = $derived.by(() => {
2341
+ const eagerSummaries = $derived.by(function eagerSummaries_d() {
2226
2342
  if (!summarize)
2227
2343
  return null;
2228
2344
  if (!rowSummariesEnabled)
@@ -2375,10 +2491,37 @@ export function createSvGridController(rawProps, domIdBase) {
2375
2491
  // is the reactive signal instead, bumped only when a height actually changes.
2376
2492
  const measuredRowHeights = new Map();
2377
2493
  let autoRowHeightVersion = $state(0);
2378
- // A per-row `rowHeight` function already supplies heights, so auto-measuring
2379
- // would fight it. Fixed numbers are fine - they become the pre-measure estimate.
2380
- const autoRowHeightOn = $derived(props.autoRowHeight === true && typeof props.rowHeight !== "function");
2381
2494
  const autoRowHeightFallback = $derived(typeof props.rowHeight === "number" ? props.rowHeight : 30);
2495
+ // ----- Interactive row resize (`rowResize` prop) -----
2496
+ // Heights the user has dragged, by row index. A plain Map plus a version
2497
+ // counter, exactly like `measuredRowHeights` above and for the same reason:
2498
+ // the reset effect below has to read this collection to know whether there is
2499
+ // anything to clear, and if the collection were reactive that read would make
2500
+ // every resize re-trigger the reset and wipe the height it just stored.
2501
+ const rowResizeHeights = new Map();
2502
+ let rowResizeVersion = $state(0);
2503
+ function setRowResizeHeight(index, height) {
2504
+ if (!rowResizeOn || !Number.isFinite(index) || height <= 0)
2505
+ return;
2506
+ rowResizeHeights.set(index, Math.round(height));
2507
+ rowResizeVersion += 1;
2508
+ }
2509
+ /** A user-dragged height for this row, or undefined when it has not been
2510
+ * resized. Read by the view AND by the virtualizer's `estimateSize`. */
2511
+ function rowResizeHeightPx(index) {
2512
+ return rowResizeOn ? rowResizeHeights.get(index) : undefined;
2513
+ }
2514
+ // A row index means a different row once the data changes, so keeping the
2515
+ // heights would size new rows by the old ones - the same reasoning as the
2516
+ // measured-height reset below.
2517
+ $effect(() => {
2518
+ void allRows.length;
2519
+ void internalData;
2520
+ if (rowResizeHeights.size === 0)
2521
+ return;
2522
+ rowResizeHeights.clear();
2523
+ rowResizeVersion += 1;
2524
+ });
2382
2525
  /** Report a row's measured height. Called by the view's measuring action.
2383
2526
  * Sub-pixel churn is ignored so a fractional layout can't loop. */
2384
2527
  function reportRowHeight(index, height) {
@@ -2450,11 +2593,19 @@ export function createSvGridController(rawProps, domIdBase) {
2450
2593
  // the estimate for rows that have not rendered yet. Touch the version so a
2451
2594
  // new measurement re-runs this effect (the Map itself is not reactive).
2452
2595
  autoRowHeightVersion;
2453
- const estimateSize = autoRowHeightOn
2596
+ // Touch the version so a resize re-runs this effect: the virtualizer has to
2597
+ // know the new size or every row below the resized one is positioned
2598
+ // against a stale offset.
2599
+ rowResizeVersion;
2600
+ const base = autoRowHeightOn
2454
2601
  ? (index) => measuredRowHeights.get(index) ?? autoRowHeightFallback
2455
2602
  : typeof rh === "function"
2456
2603
  ? rh
2457
2604
  : (rh ?? 30);
2605
+ const estimateSize = rowResizeOn
2606
+ ? (index) => rowResizeHeights.get(index) ??
2607
+ (typeof base === "function" ? base(index) : base)
2608
+ : base;
2458
2609
  virtualizer.setOptions({
2459
2610
  count: allRows.length,
2460
2611
  estimateSize,
@@ -2552,7 +2703,7 @@ export function createSvGridController(rawProps, domIdBase) {
2552
2703
  // Lets the resolver answer from cache without re-invoking the source, so an
2553
2704
  // async source fires one request per row instead of one per render.
2554
2705
  const asyncEditorColumns = new Set();
2555
- const headerSelectionState = $derived.by(() => {
2706
+ const headerSelectionState = $derived.by(function headerSelectionState_d() {
2556
2707
  gridStateVersion;
2557
2708
  const selectable = allRows.filter((row) => !isGroupRow(row));
2558
2709
  if (!selectable.length)
@@ -2585,7 +2736,7 @@ export function createSvGridController(rawProps, domIdBase) {
2585
2736
  * Returns `null` when fit scaling is not in effect (off, no room, total
2586
2737
  * already >= target). Callers then fall back to the base width.
2587
2738
  */
2588
- const fittedColumnWidths = $derived.by(() => {
2739
+ const fittedColumnWidths = $derived.by(function fittedColumnWidths_d() {
2589
2740
  // Track viewport size (not scrollVersion) so we don't recompute on
2590
2741
  // every scroll - only when the container actually resizes.
2591
2742
  viewportVersion;
@@ -2650,12 +2801,10 @@ export function createSvGridController(rawProps, domIdBase) {
2650
2801
  widths[lastId] = Math.max(MIN_COLUMN_WIDTH, scalableTarget - runningSum);
2651
2802
  return widths;
2652
2803
  });
2653
- let resizePendingWidth = 0;
2654
- let resizeRaf = null;
2655
2804
  /** Where the fill handle should render: the bottom-right cell of the
2656
2805
  * selection range (or the active cell if there's no range). Returns
2657
2806
  * null when cell selection is off or there is no anchored selection. */
2658
- const fillHandleCell = $derived.by(() => {
2807
+ const fillHandleCell = $derived.by(function fillHandleCell_d() {
2659
2808
  if (!(props.enableCellSelection ?? false))
2660
2809
  return null;
2661
2810
  const anchor = selectionRange.anchor;
@@ -2699,7 +2848,7 @@ export function createSvGridController(rawProps, domIdBase) {
2699
2848
  * reused by both the facet UI and the row filter. Computing them lazily
2700
2849
  * in a $derived means columns with no filter menu open and no active
2701
2850
  * filter never pay the iteration cost. */
2702
- const facetBucketsByColumn = $derived.by(() => {
2851
+ const facetBucketsByColumn = $derived.by(function facetBucketsByColumn_d() {
2703
2852
  const map = new Map();
2704
2853
  for (const column of allColumns) {
2705
2854
  const meta = isBucketableColumn(column);
@@ -2747,24 +2896,58 @@ export function createSvGridController(rawProps, domIdBase) {
2747
2896
  * column id (not just the open menu) so the filter row can drive it too.
2748
2897
  */
2749
2898
  function facetValuesForColumn(columnId) {
2899
+ return facetEntriesForColumn(columnId).map((entry) => entry.value);
2900
+ }
2901
+ /**
2902
+ * The values a column's checklist offers, with the row count behind each.
2903
+ *
2904
+ * Scoped to the rows passing every OTHER column's filters, not to the whole
2905
+ * dataset. Offering the full set means a checklist full of values that
2906
+ * cannot appear: filter Country to Germany and the City list still showed
2907
+ * every city on file, most of them a one-click route to an empty grid.
2908
+ * The column's OWN filter is excluded, so unticking a value never makes the
2909
+ * rest of the list vanish underneath the pointer.
2910
+ */
2911
+ function facetEntriesForColumn(columnId) {
2750
2912
  if (!columnId)
2751
2913
  return [];
2752
- // Server-provided distinct values win (fetched + cached above).
2914
+ // Server-provided distinct values win (fetched + cached above). The grid
2915
+ // has not seen the rows behind them, so there is nothing to count.
2753
2916
  if (props.serverFilterValues)
2754
- return serverFacetValues[columnId] ?? [];
2917
+ return (serverFacetValues[columnId] ?? []).map((value) => ({
2918
+ value,
2919
+ count: null,
2920
+ }));
2755
2921
  const column = allColumns.find((entry) => entry.id === columnId);
2756
2922
  if (!column)
2757
2923
  return [];
2758
- // Range-bucketed facets for numeric / date columns with many values.
2924
+ const scope = applyColumnFilters(grid.getRowModel().rows, columnId);
2759
2925
  const buckets = facetBucketsByColumn.get(columnId);
2760
- if (buckets)
2761
- return buckets.map((b) => b.label);
2926
+ if (buckets) {
2927
+ // Range-bucketed facets for numeric / date columns with many values.
2928
+ const counts = new Map(buckets.map((b) => [b.label, 0]));
2929
+ for (const row of scope) {
2930
+ const num = rawToNumber(getColumnBaseValue(row, column), buckets[0].isDate);
2931
+ if (!Number.isFinite(num))
2932
+ continue;
2933
+ for (const bucket of buckets) {
2934
+ if (isInBucket(num, bucket)) {
2935
+ counts.set(bucket.label, (counts.get(bucket.label) ?? 0) + 1);
2936
+ break;
2937
+ }
2938
+ }
2939
+ }
2940
+ return buckets.map((b) => ({ value: b.label, count: counts.get(b.label) ?? 0 }));
2941
+ }
2762
2942
  // Default: distinct-value facets.
2763
- const seen = new Set();
2764
- for (const rowData of props.data) {
2765
- seen.add(String(getColumnAccessorValue(rowData, column) ?? ""));
2943
+ const counts = new Map();
2944
+ for (const row of scope) {
2945
+ const value = String(getColumnBaseValue(row, column) ?? "");
2946
+ counts.set(value, (counts.get(value) ?? 0) + 1);
2766
2947
  }
2767
- return Array.from(seen).sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
2948
+ return Array.from(counts.keys())
2949
+ .sort((a, b) => a.localeCompare(b, undefined, { numeric: true }))
2950
+ .map((value) => ({ value, count: counts.get(value) ?? 0 }));
2768
2951
  }
2769
2952
  /**
2770
2953
  * Facet values for the open menu, SNAPSHOTTED when it opens.
@@ -2785,8 +2968,9 @@ export function createSvGridController(rawProps, domIdBase) {
2785
2968
  // out a fresh empty array/set on every read.
2786
2969
  const EMPTY_FACETS = [];
2787
2970
  const EMPTY_FACET_SET = new Set();
2971
+ const EMPTY_FACET_ENTRIES = [];
2788
2972
  let facetCache = null;
2789
- const columnMenuFacetValues = $derived.by(() => {
2973
+ const columnMenuFacetValues = $derived.by(function columnMenuFacetValues_d() {
2790
2974
  // The funnel popover drives via `filterMenuFor`; the column menu's Filter
2791
2975
  // tab drives via `columnMenuFor`. Support whichever is open.
2792
2976
  const columnId = filterMenuFor ?? columnMenuFor;
@@ -2801,10 +2985,12 @@ export function createSvGridController(rawProps, domIdBase) {
2801
2985
  if (facetCache?.columnId === columnId && facetCache.source === source) {
2802
2986
  return facetCache.values;
2803
2987
  }
2988
+ const entries = untrack(() => facetEntriesForColumn(columnId));
2804
2989
  facetCache = {
2805
2990
  columnId,
2806
2991
  source,
2807
- values: untrack(() => facetValuesForColumn(columnId)),
2992
+ values: entries.map((e) => e.value),
2993
+ counts: new Map(entries.flatMap((e) => (e.count == null ? [] : [[e.value, e.count]]))),
2808
2994
  };
2809
2995
  return facetCache.values;
2810
2996
  });
@@ -2816,46 +3002,76 @@ export function createSvGridController(rawProps, domIdBase) {
2816
3002
  if (!(filterMenuFor ?? columnMenuFor))
2817
3003
  facetCache = null;
2818
3004
  });
2819
- const columnMenuVisibleFacets = $derived.by(() => {
3005
+ const columnMenuVisibleFacets = $derived.by(function columnMenuVisibleFacets_d() {
2820
3006
  const query = columnMenuSearch.trim().toLowerCase();
2821
3007
  if (!query)
2822
3008
  return columnMenuFacetValues;
2823
3009
  return columnMenuFacetValues.filter((value) => value.toLowerCase().includes(query));
2824
3010
  });
2825
3011
  /** Visible facets as listbox options ((Blanks) label for the empty value). */
2826
- const columnMenuFacetOptions = $derived(columnMenuVisibleFacets.map((value) => ({
2827
- value,
2828
- label: value === "" ? "(Blanks)" : value,
2829
- })));
3012
+ const columnMenuFacetOptions = $derived.by(function columnMenuFacetOptions_d() {
3013
+ // Reading the values first materializes the snapshot (and its counts).
3014
+ const values = columnMenuVisibleFacets;
3015
+ const counts = facetCache?.counts;
3016
+ return values.map((value) => {
3017
+ const count = counts?.get(value);
3018
+ return {
3019
+ value,
3020
+ label: value === "" ? "(Blanks)" : value,
3021
+ // How many rows this value would keep, given the other columns'
3022
+ // filters. Absent in server-values mode, where the grid never sees
3023
+ // the rows behind the list.
3024
+ hint: count == null ? undefined : String(count),
3025
+ };
3026
+ });
3027
+ });
2830
3028
  /**
2831
3029
  * Checked facets for the open menu. An ABSENT `valueFilters` entry means
2832
3030
  * "everything checked", so that case materializes the set once here rather
2833
3031
  * than every consumer re-deriving it.
2834
3032
  */
2835
- const columnMenuSelectedFacets = $derived.by(() => {
3033
+ const columnMenuSelectedFacets = $derived.by(function columnMenuSelectedFacets_d() {
2836
3034
  const columnId = filterMenuFor ?? columnMenuFor;
2837
3035
  if (!columnId)
2838
3036
  return EMPTY_FACET_SET;
2839
3037
  return valueFilters[columnId] ?? new Set(columnMenuFacetValues);
2840
3038
  });
3039
+ /**
3040
+ * The offered values, in their own derived so the QUERY does not re-run the
3041
+ * scan. It used to sit in `inSuggestValues` beside the `inSuggestQuery`
3042
+ * read, so every keystroke in a chip input rebuilt the distinct-value set -
3043
+ * and once the scan started narrowing itself against the other columns'
3044
+ * filters, every keystroke ran the filter pipeline too. Split out, it
3045
+ * re-runs only when the dropdown moves to another column.
3046
+ */
3047
+ const inSuggestEntries = $derived.by(function inSuggestEntries_d() {
3048
+ const columnId = inSuggestFor;
3049
+ if (!columnId)
3050
+ return EMPTY_FACET_ENTRIES;
3051
+ return untrack(() => facetEntriesForColumn(columnId));
3052
+ });
2841
3053
  // Distinct values offered in the `in` / `notIn` suggestions dropdown for the
2842
3054
  // active chip input, narrowed by whatever the user has typed. Uncapped - the
2843
3055
  // dropdown windows its rows, so a high-cardinality column costs a list of
2844
3056
  // strings, not thousands of nodes.
2845
- const inSuggestValues = $derived.by(() => {
2846
- if (!inSuggestFor)
2847
- return EMPTY_FACETS;
3057
+ const inSuggestValues = $derived.by(function inSuggestValues_d() {
2848
3058
  const query = inSuggestQuery.trim().toLowerCase();
2849
- const all = facetValuesForColumn(inSuggestFor);
2850
- return query
2851
- ? all.filter((value) => value.toLowerCase().includes(query))
2852
- : all;
3059
+ const all = inSuggestEntries;
3060
+ return (query ? all.filter((e) => e.value.toLowerCase().includes(query)) : all).map((e) => e.value);
2853
3061
  });
2854
3062
  /** `in` / `notIn` suggestions as listbox options. */
2855
- const inSuggestOptions = $derived(inSuggestValues.map((value) => ({
2856
- value,
2857
- label: value === "" ? "(Blanks)" : value,
2858
- })));
3063
+ const inSuggestOptions = $derived.by(function inSuggestOptions_d() {
3064
+ const query = inSuggestQuery.trim().toLowerCase();
3065
+ const shown = query
3066
+ ? inSuggestEntries.filter((e) => e.value.toLowerCase().includes(query))
3067
+ : inSuggestEntries;
3068
+ return shown.map((e) => ({
3069
+ value: e.value,
3070
+ label: e.value === "" ? "(Blanks)" : e.value,
3071
+ // Same match count the menu's checklist shows.
3072
+ hint: e.count == null ? undefined : String(e.count),
3073
+ }));
3074
+ });
2859
3075
  // Fire onApiReady exactly once when the grid is first ready. Wrapping in
2860
3076
  // an effect that tracks `props.onApiReady` was racy - every parent render
2861
3077
  // creates a new inline arrow, the effect re-fired, and any synchronous
@@ -2914,6 +3130,10 @@ export function createSvGridController(rawProps, domIdBase) {
2914
3130
  set isDraggingSelection(v) { isDraggingSelection = v; },
2915
3131
  get fillDrag() { return fillDrag; },
2916
3132
  set fillDrag(v) { fillDrag = v; },
3133
+ get moveDrag() { return moveDrag; },
3134
+ set moveDrag(v) { moveDrag = v; },
3135
+ get moveGrabHover() { return moveGrabHover; },
3136
+ set moveGrabHover(v) { moveGrabHover = v; },
2917
3137
  get activeAtPointerDown() { return activeAtPointerDown; },
2918
3138
  set activeAtPointerDown(v) { activeAtPointerDown = v; },
2919
3139
  get editingCell() { return editingCell; },
@@ -2950,12 +3170,6 @@ export function createSvGridController(rawProps, domIdBase) {
2950
3170
  set editorSelectAll(v) { editorSelectAll = v; },
2951
3171
  get columnWidths() { return columnWidths; },
2952
3172
  set columnWidths(v) { columnWidths = v; },
2953
- get resizingColumnId() { return resizingColumnId; },
2954
- set resizingColumnId(v) { resizingColumnId = v; },
2955
- get resizeStartX() { return resizeStartX; },
2956
- set resizeStartX(v) { resizeStartX = v; },
2957
- get resizeStartWidth() { return resizeStartWidth; },
2958
- set resizeStartWidth(v) { resizeStartWidth = v; },
2959
3173
  get MIN_COLUMN_WIDTH() { return MIN_COLUMN_WIDTH; },
2960
3174
  get columnPinning() { return columnPinning; },
2961
3175
  set columnPinning(v) { columnPinning = v; },
@@ -3023,6 +3237,7 @@ export function createSvGridController(rawProps, domIdBase) {
3023
3237
  get showRowSelectionEffective() { return showRowSelectionEffective; },
3024
3238
  get rowSummariesEnabled() { return rowSummariesEnabled; },
3025
3239
  get enableCellSelectionEffective() { return enableCellSelectionEffective; },
3240
+ get moveCellsEffective() { return moveCellsEffective; },
3026
3241
  get flushScheduledScrollSync() { return flushScheduledScrollSync; },
3027
3242
  get scheduleScrollSync() { return scheduleScrollSync; },
3028
3243
  get internalData() { return internalData; },
@@ -3104,6 +3319,12 @@ export function createSvGridController(rawProps, domIdBase) {
3104
3319
  get allRowsBeforePagination() { return allRowsBeforePagination; },
3105
3320
  get allRows() { return allRows; },
3106
3321
  get rowSelectionState() { return rowSelectionState; },
3322
+ get selectionBarOn() { return selectionBarOn; },
3323
+ get selectionBarActions() { return selectionBarActions; },
3324
+ get selectionBarPosition() { return selectionBarPosition; },
3325
+ get selectionBarMaxVisible() { return selectionBarMaxVisible; },
3326
+ get selectionBarHideClear() { return selectionBarHideClear; },
3327
+ get selectionBarTarget() { return selectionBarTarget; },
3107
3328
  get lastSelectionSerialized() { return lastSelectionSerialized; },
3108
3329
  set lastSelectionSerialized(v) { lastSelectionSerialized = v; },
3109
3330
  get lastCellRangeSerialized() { return lastCellRangeSerialized; },
@@ -3278,6 +3499,15 @@ export function createSvGridController(rawProps, domIdBase) {
3278
3499
  autoRowHeightVersion;
3279
3500
  return (index) => measuredRowHeights.get(index);
3280
3501
  },
3502
+ /** True when the `rowResize` prop is on and nothing overrides it. */
3503
+ get rowResizeOn() { return rowResizeOn; },
3504
+ /** Record the height the user dragged a row to. */
3505
+ get setRowResizeHeight() { return setRowResizeHeight; },
3506
+ /** A row's dragged height, or undefined when it has not been resized. */
3507
+ get rowResizeHeightPx() {
3508
+ rowResizeVersion;
3509
+ return rowResizeHeightPx;
3510
+ },
3281
3511
  get virtualRowTotalSize() { return virtualRowTotalSize; },
3282
3512
  get virtualRowStart() { return virtualRowStart; },
3283
3513
  get virtualRowEnd() { return virtualRowEnd; },
@@ -3340,13 +3570,6 @@ export function createSvGridController(rawProps, domIdBase) {
3340
3570
  get getColumnBaseWidth() { return getColumnBaseWidth; },
3341
3571
  get fittedColumnWidths() { return fittedColumnWidths; },
3342
3572
  get getColumnWidth() { return getColumnWidth; },
3343
- get resizePendingWidth() { return resizePendingWidth; },
3344
- set resizePendingWidth(v) { resizePendingWidth = v; },
3345
- get resizeRaf() { return resizeRaf; },
3346
- set resizeRaf(v) { resizeRaf = v; },
3347
- get startColumnResize() { return startColumnResize; },
3348
- get onColumnResizeMove() { return onColumnResizeMove; },
3349
- get endColumnResize() { return endColumnResize; },
3350
3573
  get setSelection() { return setSelection; },
3351
3574
  get extendSelection() { return extendSelection; },
3352
3575
  get isCellInSelectedRange() { return isCellInSelectedRange; },
@@ -3354,8 +3577,18 @@ export function createSvGridController(rawProps, domIdBase) {
3354
3577
  get getSelectionRects() { return getSelectionRects; },
3355
3578
  get fillHandleCell() { return fillHandleCell; },
3356
3579
  get isInFillPreview() { return isInFillPreview; },
3580
+ get dragPreviewRect() { return dragPreviewRect; },
3357
3581
  get fillMarqueeEdges() { return fillMarqueeEdges; },
3358
3582
  get findColumnById() { return findColumnById; },
3583
+ /**
3584
+ * Whether this column may be resized by the user, from its
3585
+ * `ColumnDef.resizable`. Defaults to true, so a column only opts out by
3586
+ * saying so; the grid-wide `columnResize` prop gates all of them above
3587
+ * this. Used by the resize action and by the column menu's Autosize item.
3588
+ */
3589
+ columnResizable(columnId) {
3590
+ return findColumnById(columnId)?.columnDef?.resizable !== false;
3591
+ },
3359
3592
  get readCellRaw() { return readCellRaw; },
3360
3593
  get writeCellRaw() { return writeCellRaw; },
3361
3594
  get applyFillPattern() { return applyFillPattern; },
@@ -3363,6 +3596,14 @@ export function createSvGridController(rawProps, domIdBase) {
3363
3596
  get startFillDrag() { return startFillDrag; },
3364
3597
  get onFillPointerMove() { return onFillPointerMove; },
3365
3598
  get onFillPointerUp() { return onFillPointerUp; },
3599
+ get isOnMoveGrabStrip() { return isOnMoveGrabStrip; },
3600
+ get startMoveDrag() { return startMoveDrag; },
3601
+ get onMovePointerMove() { return onMovePointerMove; },
3602
+ get onMovePointerUp() { return onMovePointerUp; },
3603
+ get trackEdgeScroll() { return trackEdgeScroll; },
3604
+ get stopEdgeScroll() { return stopEdgeScroll; },
3605
+ get applyMoveRange() { return applyMoveRange; },
3606
+ get moveDestRect() { return moveDestRect; },
3366
3607
  get toggleBooleanCell() { return toggleBooleanCell; },
3367
3608
  get onCellPointerDown() { return onCellPointerDown; },
3368
3609
  get onCellPointerEnter() { return onCellPointerEnter; },
@@ -3464,12 +3705,12 @@ export function createSvGridController(rawProps, domIdBase) {
3464
3705
  const { updateFilterRow, updateFilterOperator, updateFilterMenuValue, updateFilterMenuValueTo, addFilterToken, removeFilterToken, toggleFilterToken, toggleCheckboxWithKeyboard, isColumnFiltered, closeMenus, openInSuggest, closeInSuggest, openChooseColumns, openColumnMenu, openFilterMenu, openOperatorMenu, sortColumnFromMenu, clearColumnSort, groupByColumnFromMenu, clearGroupingFromMenu, isFacetChecked, toggleFacetValue, setFacetSelection, setFilterTokens, isAllFacetsChecked, toggleAllFacets, clearColumnFilter, changePage, goToPage, setPageSize, openContextMenu, closeContextMenu, contextMenuItems, saveComment, removeComment, closeCommentEditor } = createMenus(ctx);
3465
3706
  const { cellConditionalFormat, computeRowClass, computeCellClass, computeCellTooltip, computeCellValidity, computeCellNote, getColumnEditorOptions, areEditorOptionsLoading, formatListCellValue, formatCellValue, formatPinnedValue, computePinnedCellClass } = createCellRender(ctx);
3466
3707
  const { isCellEditable, isCellEditableAt, getRowColumnValue, getCellDisplayValue, startEditingWithChar, startEditing, stopEditing, startFullRowEdit, setFullRowDraft, commitFullRowEdit, cancelFullRowEdit, saveEditingCell, applyHistoryStep, updateEditingCellValue, onEditorKeyDown, commitAndMoveByTab, focusOnMount, onCellDoubleClick, pasteFromClipboard, onGridPaste } = createEditing(ctx);
3467
- const { isRowSelected, toggleRowSelectionById, toggleSelectAllRows, setActiveCell, scrollActiveCellIntoView, setSelection, extendSelection, isCellInSelectedRange, getCellRangeEdges, getSelectionRects, isInFillPreview, fillMarqueeEdges, findColumnById, onCellPointerDown, onCellPointerEnter, endDragSelection, onWindowPointerMove, onCellClick, emitCellDoubleClick } = createSelection(ctx);
3468
- const { cellPinStyle, isColumnPinned, getCurrentColumnOrder, emitColumnOrder, setColumnOrderInternal, applyColumnDrop, onColumnHeaderDragStart, onColumnHeaderDragOver, onColumnHeaderDragLeave, onColumnHeaderDrop, onColumnHeaderDragEnd, pinColumnLeft, pinColumnRight, unpinColumn, toggleColumnVisibleInPanel, moveColumnInPanel, toggleGroupInPanel, getColumnBaseWidth, getColumnWidth, startColumnResize, onColumnResizeMove, endColumnResize, measureText, autosizeColumn, autosizeAllColumns, resetColumns } = createColumns(ctx);
3708
+ const { isRowSelected, toggleRowSelectionById, toggleSelectAllRows, setActiveCell, scrollActiveCellIntoView, setSelection, extendSelection, isCellInSelectedRange, getCellRangeEdges, getSelectionRects, isInFillPreview, dragPreviewRect, fillMarqueeEdges, findColumnById, onCellPointerDown, onCellPointerEnter, endDragSelection, onWindowPointerMove, onCellClick, emitCellDoubleClick } = createSelection(ctx);
3709
+ const { cellPinStyle, isColumnPinned, getCurrentColumnOrder, emitColumnOrder, setColumnOrderInternal, applyColumnDrop, onColumnHeaderDragStart, onColumnHeaderDragOver, onColumnHeaderDragLeave, onColumnHeaderDrop, onColumnHeaderDragEnd, pinColumnLeft, pinColumnRight, unpinColumn, toggleColumnVisibleInPanel, moveColumnInPanel, toggleGroupInPanel, getColumnBaseWidth, getColumnWidth, measureText, autosizeColumn, autosizeAllColumns, resetColumns } = createColumns(ctx);
3469
3710
  const { onRowDragStart, onRowDragOver, onRowDragLeave, onRowDrop, onRowsContainerDragOver, onRowsContainerDrop, onRowDragEnd, onRowPointerDown, destroyRowDrag } = createRowDrag(ctx);
3470
3711
  const { register: registerAlignedGrid, broadcastScroll: broadcastAlignedScroll, broadcastWidths: broadcastAlignedWidths } = createAlignedGrids(ctx);
3471
3712
  const { buildApi } = createGridApi(ctx);
3472
- const { readCellRaw, writeCellRaw, applyFillPattern, clearSelectedCellValues, startFillDrag, onFillPointerMove, onFillPointerUp, toggleBooleanCell, copySelectionToClipboard, clearSelectedCells, cutSelectionToClipboard } = createClipboard(ctx);
3713
+ const { readCellRaw, writeCellRaw, applyFillPattern, clearSelectedCellValues, startFillDrag, onFillPointerMove, onFillPointerUp, isOnMoveGrabStrip, startMoveDrag, onMovePointerMove, onMovePointerUp, applyMoveRange, moveDestRect, trackEdgeScroll, stopEdgeScroll, toggleBooleanCell, copySelectionToClipboard, clearSelectedCells, cutSelectionToClipboard } = createClipboard(ctx);
3473
3714
  // Dev-time configuration checks. Silent misconfiguration was the grid's
3474
3715
  // biggest usability gap - a misspelled `field` rendered a column of blank
3475
3716
  // cells and printed nothing, and an inert `pageSize` was simply ignored.
@@ -3541,7 +3782,6 @@ export function createSvGridController(rawProps, domIdBase) {
3541
3782
  // this grid still owns (#68). Each of these is a no-op when idle.
3542
3783
  $effect(() => {
3543
3784
  return () => {
3544
- endColumnResize();
3545
3785
  hideTooltip();
3546
3786
  destroyRowDrag();
3547
3787
  };