@svgrid/grid 2.6.21 → 2.7.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/CHANGELOG.md +150 -88
- package/README.md +221 -199
- package/dist/FlexRender.svelte +96 -96
- package/dist/GridFooter.svelte +181 -181
- package/dist/GridMenus.svelte +201 -18
- package/dist/SvAutoComplete.svelte +169 -165
- package/dist/SvAvatar.svelte +75 -75
- package/dist/SvCalendar.svelte +503 -503
- package/dist/SvCarousel.svelte +141 -141
- package/dist/SvCheckBox.svelte +102 -102
- package/dist/SvCircularProgress.svelte +109 -109
- package/dist/SvColorInput.svelte +181 -181
- package/dist/SvComboBox.svelte +279 -275
- package/dist/SvContextMenu.svelte +116 -116
- package/dist/SvCountryInput.svelte +163 -163
- package/dist/SvDateRangeInput.svelte +25 -3
- package/dist/SvDrawer.svelte +254 -254
- package/dist/SvDropDownList.svelte +378 -374
- package/dist/SvDurationInput.svelte +126 -126
- package/dist/SvField.svelte +293 -293
- package/dist/SvForm.svelte +437 -437
- package/dist/SvGrid.controller.svelte.d.ts +61 -9
- package/dist/SvGrid.controller.svelte.js +363 -123
- package/dist/SvGrid.css +222 -1
- package/dist/SvGrid.helpers.d.ts +55 -0
- package/dist/SvGrid.helpers.js +83 -0
- package/dist/SvGrid.svelte +255 -56
- package/dist/SvGrid.types.d.ts +146 -1
- package/dist/SvGridCellEditor.svelte +744 -662
- package/dist/SvGridChart.svelte +1716 -1716
- package/dist/SvGridChartPanel.svelte +485 -485
- package/dist/SvGridChartView.svelte +70 -70
- package/dist/SvGridDropdown.svelte +728 -728
- package/dist/SvGridSelect.svelte +270 -266
- package/dist/SvGridSelect.svelte.d.ts +1 -1
- package/dist/SvGroupCell.svelte +116 -116
- package/dist/SvListBox.svelte +334 -326
- package/dist/SvMaskedInput.svelte +122 -122
- package/dist/SvMenu.svelte +124 -124
- package/dist/SvMenuList.svelte +146 -146
- package/dist/SvMultiSelect.svelte +293 -289
- package/dist/SvMultiSelect.svelte.d.ts +1 -1
- package/dist/SvNumberInput.svelte +172 -168
- package/dist/SvOtpInput.svelte +158 -158
- package/dist/SvPasswordInput.svelte +151 -151
- package/dist/SvPhoneInput.svelte +133 -133
- package/dist/SvPopover.svelte +197 -197
- package/dist/SvProgress.svelte +116 -116
- package/dist/SvRadioGroup.svelte +107 -107
- package/dist/SvRating.svelte +112 -112
- package/dist/SvResult.svelte +73 -73
- package/dist/SvRichCell.svelte +125 -0
- package/dist/SvRichCell.svelte.d.ts +15 -0
- package/dist/SvRichText.svelte +211 -211
- package/dist/SvRowGroupPanel.svelte +170 -170
- package/dist/SvScrollArea.svelte +61 -61
- package/dist/SvSlider.svelte +203 -203
- package/dist/SvSwitchButton.svelte +108 -108
- package/dist/SvTagsInput.svelte +115 -115
- package/dist/SvTextArea.svelte +5 -1
- package/dist/SvTextInput.svelte +147 -147
- package/dist/SvTimePicker.svelte +245 -245
- package/dist/SvToaster.svelte +159 -159
- package/dist/SvToggleButton.svelte +85 -85
- package/dist/SvTooltip.svelte +161 -161
- package/dist/SvTour.svelte +208 -208
- package/dist/SvTree.svelte +444 -444
- package/dist/SvTreeSelect.svelte +239 -235
- package/dist/SvTreeSelect.svelte.d.ts +1 -1
- package/dist/builtin-editors.d.ts +2 -2
- package/dist/builtin-editors.js +19 -3
- package/dist/cdn/GridMenus-C4oLsJyj.js +600 -0
- package/dist/cdn/GridMenus-yEivjNwl.js +601 -0
- package/dist/cdn/SvDateRangeInput-Bh0A0JkF.js +199 -0
- package/dist/cdn/SvDateRangeInput-DflbiP7N.js +198 -0
- package/dist/cdn/SvDateTimePicker-BWwpfB_o.js +812 -0
- package/dist/cdn/SvDateTimePicker-Bivn8dAP.js +816 -0
- package/dist/cdn/SvGridCellEditor-AXL8cHGO.js +550 -0
- package/dist/cdn/SvGridCellEditor-Ba7rY3eu.js +549 -0
- package/dist/cdn/{SvGridChart-BwVos976.js → SvGridChart-Bs2GIR2Q.js} +1 -1
- package/dist/cdn/{SvGridChart-BEJmNNx9.js → SvGridChart-CwhFz7GV.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-BOknOkrP.js → SvGridChartPanel-1WXuStzM.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-D2PjII4O.js → SvGridChartPanel-e5_nqNP9.js} +1 -1
- package/dist/cdn/{SvGridChartView-00LPUHL1.js → SvGridChartView-SmPW10dI.js} +1 -1
- package/dist/cdn/{SvGridChartView-BhUEJ5ki.js → SvGridChartView-eSPuJE6g.js} +1 -1
- package/dist/cdn/{SvGridDropdown-D0VdjeR8.js → SvGridDropdown-B1ZcLpgs.js} +1 -1
- package/dist/cdn/{SvGridDropdown-D13MtJ2j.js → SvGridDropdown-CtdbHcIS.js} +1 -1
- package/dist/cdn/column-resize-DsfNXMom.js +102 -0
- package/dist/cdn/date-format-CcP1tafP.js +1395 -0
- package/dist/cdn/date-format-DZT7T1wf.js +1393 -0
- package/dist/cdn/{editor-registry-CiI0xlKg.js → editor-registry-BhgE3S84.js} +51 -25
- package/dist/cdn/row-resize-BRcimkUT.js +95 -0
- package/dist/cdn/{src-BYq-qyrp.js → src-COC0vnpg.js} +7846 -7317
- package/dist/cdn/{src-DBel9wRZ.js → src-pYGV2rMR.js} +10033 -9504
- package/dist/cdn/svgrid.js +15 -11
- package/dist/cdn/svgrid.svelte-external.js +15 -11
- package/dist/cell-values.d.ts +16 -0
- package/dist/cell-values.js +28 -0
- package/dist/chart-export.js +8 -8
- package/dist/clipboard.d.ts +22 -0
- package/dist/clipboard.js +405 -27
- package/dist/column-groups.js +1 -1
- package/dist/column-resize.d.ts +46 -0
- package/dist/column-resize.js +205 -0
- package/dist/columns.d.ts +0 -3
- package/dist/columns.js +0 -57
- package/dist/core.d.ts +19 -4
- package/dist/core.js +460 -119
- package/dist/editing.js +12 -7
- package/dist/editors/cell-editors.d.ts +11 -0
- package/dist/editors/cell-editors.js +18 -1
- package/dist/filtering/excel-filters.js +28 -0
- package/dist/grid-messages.d.ts +15 -0
- package/dist/grid-messages.js +15 -0
- package/dist/group-display.d.ts +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +10 -0
- package/dist/list-option.d.ts +6 -0
- package/dist/menus.js +41 -8
- package/dist/row-resize.d.ts +11 -0
- package/dist/row-resize.js +7 -1
- package/dist/sanitize-html.d.ts +37 -0
- package/dist/sanitize-html.js +234 -0
- package/dist/selection-bar-view.svelte.d.ts +25 -0
- package/dist/selection-bar-view.svelte.js +13 -0
- package/dist/selection.d.ts +2 -1
- package/dist/selection.js +133 -8
- package/dist/spreadsheet.d.ts +1 -1
- package/dist/spreadsheet.js +1 -1
- package/package.json +11 -11
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +181 -181
- package/src/GridMenus.svelte +201 -18
- package/src/SvAutoComplete.svelte +169 -165
- package/src/SvAvatar.svelte +75 -75
- package/src/SvCalendar.svelte +503 -503
- package/src/SvCalendar.test.ts +226 -226
- package/src/SvCarousel.svelte +141 -141
- package/src/SvCheckBox.svelte +102 -102
- package/src/SvCircularProgress.svelte +109 -109
- package/src/SvColorInput.svelte +181 -181
- package/src/SvComboBox.svelte +279 -275
- package/src/SvContextMenu.svelte +116 -116
- package/src/SvCountryInput.svelte +163 -163
- package/src/SvDateRangeInput.svelte +25 -3
- package/src/SvDrawer.svelte +254 -254
- package/src/SvDropDownList.svelte +378 -374
- package/src/SvDurationInput.svelte +126 -126
- package/src/SvField.svelte +293 -293
- package/src/SvForm.svelte +437 -437
- package/src/SvForm.test.ts +411 -411
- package/src/SvGrid.controller.svelte.ts +404 -133
- package/src/SvGrid.css +222 -1
- package/src/SvGrid.helpers.ts +87 -0
- package/src/SvGrid.svelte +255 -56
- package/src/SvGrid.types.ts +2092 -1940
- package/src/SvGridCellEditor.svelte +744 -662
- package/src/SvGridChart.svelte +1716 -1716
- package/src/SvGridChartPanel.svelte +485 -485
- package/src/SvGridChartView.svelte +70 -70
- package/src/SvGridDropdown.svelte +728 -728
- package/src/SvGridSelect.svelte +270 -266
- package/src/SvGroupCell.svelte +116 -116
- package/src/SvListBox.svelte +334 -326
- package/src/SvMaskedInput.svelte +122 -122
- package/src/SvMenu.svelte +124 -124
- package/src/SvMenu.test.ts +97 -97
- package/src/SvMenuList.svelte +146 -146
- package/src/SvMultiSelect.svelte +293 -289
- package/src/SvNumberInput.svelte +172 -168
- package/src/SvOtpInput.svelte +158 -158
- package/src/SvPasswordInput.svelte +151 -151
- package/src/SvPhoneInput.svelte +133 -133
- package/src/SvPopover.svelte +197 -197
- package/src/SvProgress.svelte +116 -116
- package/src/SvRadioGroup.svelte +107 -107
- package/src/SvRating.svelte +112 -112
- package/src/SvResult.svelte +73 -73
- package/src/SvRichCell.svelte +125 -0
- package/src/SvRichText.svelte +211 -211
- package/src/SvRowGroupPanel.svelte +170 -170
- package/src/SvScrollArea.svelte +61 -61
- package/src/SvSlider.svelte +203 -203
- package/src/SvSwitchButton.svelte +108 -108
- package/src/SvTagsInput.svelte +115 -115
- package/src/SvTextArea.svelte +5 -1
- package/src/SvTextInput.svelte +147 -147
- package/src/SvTimePicker.svelte +245 -245
- package/src/SvToaster.svelte +159 -159
- package/src/SvToaster.test.ts +95 -95
- package/src/SvToggleButton.svelte +85 -85
- package/src/SvTooltip.svelte +161 -161
- package/src/SvTour.svelte +208 -208
- package/src/SvTree.svelte +444 -444
- package/src/SvTreeSelect.svelte +239 -235
- package/src/a11y/dismissable.test.ts +119 -119
- package/src/a11y/dismissable.ts +114 -114
- package/src/a11y.contract.test.ts +49 -49
- package/src/a11y.test.ts +59 -59
- package/src/a11y.ts +61 -61
- package/src/ai.test.ts +502 -502
- package/src/ai.ts +1419 -1419
- package/src/build-api.coverage.test.ts +633 -633
- package/src/build-api.ts +846 -846
- package/src/builtin-editors.grid.test.ts +83 -83
- package/src/builtin-editors.test.ts +18 -1
- package/src/builtin-editors.ts +19 -3
- package/src/cell-formatting.ts +171 -171
- package/src/cell-render.test.ts +513 -513
- package/src/cell-render.ts +496 -496
- package/src/cell-values.ts +148 -118
- package/src/chart-export.ts +202 -202
- package/src/chart-view.svelte.ts +36 -36
- package/src/chart.ts +2321 -2321
- package/src/clipboard.test.ts +93 -20
- package/src/clipboard.ts +433 -33
- package/src/collaboration.test.ts +104 -104
- package/src/collaboration.ts +167 -167
- package/src/column-groups.ts +78 -78
- package/src/column-resize.test.ts +381 -0
- package/src/column-resize.ts +227 -0
- package/src/columns.test.ts +0 -103
- package/src/columns.ts +0 -58
- package/src/core.aggregate.test.ts +134 -0
- package/src/core.filter.test.ts +156 -0
- package/src/core.grouping.test.ts +146 -0
- package/src/core.performance.test.ts +30 -30
- package/src/core.row-shape.test.ts +119 -0
- package/src/core.rowmodel-cache.test.ts +121 -0
- package/src/core.sort.test.ts +293 -0
- package/src/core.ts +1865 -1468
- package/src/createAutocomplete.svelte.ts +132 -132
- package/src/createCombobox.svelte.ts +191 -191
- package/src/createCountryInput.svelte.ts +157 -157
- package/src/createDropdownList.svelte.ts +168 -168
- package/src/createForm.svelte.ts +386 -386
- package/src/createGrid.svelte.ts +42 -42
- package/src/createGrid.test.ts +10 -10
- package/src/createGridState.svelte.ts +17 -17
- package/src/createListbox.svelte.ts +250 -250
- package/src/createMenu.svelte.ts +224 -224
- package/src/createPopoverSelect.svelte.ts +213 -213
- package/src/createSlider.svelte.ts +191 -191
- package/src/createTooltip.svelte.ts +144 -144
- package/src/createTree.svelte.ts +322 -322
- package/src/date-string-column.test.ts +87 -0
- package/src/datetime/date-core.ts +206 -206
- package/src/datetime/date-restrict.ts +61 -61
- package/src/datetime/timezone.ts +135 -135
- package/src/dock-manager-model.ts +596 -596
- package/src/dock-model.ts +374 -374
- package/src/editing.test.ts +974 -949
- package/src/editing.ts +609 -606
- package/src/editor-block-prop.test.ts +132 -0
- package/src/editor-contract.ts +171 -171
- package/src/editor-registry.grid.test.ts +144 -144
- package/src/editor-registry.ts +122 -122
- package/src/editors/cell-editors.ts +27 -1
- package/src/export-data-api.test.ts +126 -126
- package/src/export-format.test.ts +107 -107
- package/src/export-format.ts +601 -601
- package/src/filter-operators.ts +160 -160
- package/src/filtering/excel-filters.ts +325 -295
- package/src/filtering/normalize-fast-path.test.ts +104 -0
- package/src/flex-render.ts +3 -3
- package/src/form-field.ts +127 -127
- package/src/grid-messages.ts +34 -0
- package/src/group-display.test.ts +167 -167
- package/src/group-display.ts +200 -200
- package/src/headless.ts +87 -87
- package/src/index.ts +25 -1
- package/src/js-scroller.svelte.ts +173 -173
- package/src/keyboard-handlers.ts +270 -270
- package/src/keyboard.test.ts +59 -59
- package/src/keyboard.ts +97 -97
- package/src/list-nav.test.ts +49 -49
- package/src/list-nav.ts +29 -29
- package/src/list-option.test.ts +56 -56
- package/src/list-option.ts +179 -173
- package/src/menus.test.ts +43 -0
- package/src/menus.ts +597 -567
- package/src/merge-objects.ts +48 -48
- package/src/move-cells.test.ts +850 -0
- package/src/number-editor-literal.test.ts +84 -0
- package/src/number-editor.grid.test.ts +160 -0
- package/src/overlays.test.ts +90 -90
- package/src/positioning.ts +268 -268
- package/src/render-component.ts +28 -28
- package/src/resize-props.test.ts +361 -0
- package/src/row-drag-touch.ts +211 -211
- package/src/row-drag.test.ts +353 -353
- package/src/row-drag.ts +415 -415
- package/src/row-resize.test.ts +524 -493
- package/src/row-resize.ts +228 -210
- package/src/sanitize-html.test.ts +182 -0
- package/src/sanitize-html.ts +235 -0
- package/src/scheduler-ical.ts +181 -181
- package/src/scheduler-model.test.ts +562 -562
- package/src/scheduler-model.ts +873 -873
- package/src/selection-bar-view.svelte.ts +36 -0
- package/src/selection.test.ts +900 -743
- package/src/selection.ts +135 -8
- package/src/server-data-source.test.ts +383 -383
- package/src/server-data-source.ts +469 -469
- package/src/sparkline.test.ts +68 -68
- package/src/sparkline.ts +169 -169
- package/src/spreadsheet.test.ts +488 -488
- package/src/spreadsheet.ts +312 -312
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/summaries.ts +113 -113
- package/src/svgrid-wrapper.types.ts +563 -563
- package/src/svgrid.async-editor-options.test.ts +273 -273
- package/src/svgrid.auto-row-height.test.ts +204 -204
- package/src/svgrid.behavior.test.ts +910 -910
- package/src/svgrid.charting.test.ts +534 -534
- package/src/svgrid.comments-autocomplete.test.ts +127 -127
- package/src/svgrid.context-menu.test.ts +147 -147
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.filter-depth.test.ts +163 -163
- package/src/svgrid.filter-menu-listbox.svelte.test.ts +377 -331
- package/src/svgrid.filter-menu-scroll.test.ts +112 -112
- package/src/svgrid.grand-total.test.ts +188 -188
- package/src/svgrid.group-display-mode.test.ts +171 -171
- package/src/svgrid.group-footers.test.ts +121 -121
- package/src/svgrid.group-pagination.test.ts +153 -153
- package/src/svgrid.in-suggest-snapshot.svelte.test.ts +91 -0
- package/src/svgrid.menu-scroll-close.test.ts +204 -0
- package/src/svgrid.new-features.wrapper.test.ts +251 -251
- package/src/svgrid.selection-bar-seam.test.ts +185 -0
- package/src/svgrid.tree-data.test.ts +186 -186
- package/src/svgrid.upsell-license.test.ts +117 -0
- package/src/svgrid.wrapper.test.ts +63 -63
- package/src/svgriddropdown.async-panel.svelte.test.ts +195 -195
- package/src/test-setup.ts +62 -62
- package/src/themes/index.ts +288 -288
- package/src/toast-store.svelte.ts +250 -250
- package/src/toast-store.test.ts +147 -147
- package/src/tree-row-model.test.ts +168 -168
- package/src/ui-buttons.test.ts +144 -144
- package/src/ui-inputs.test.ts +118 -118
- package/src/ui-localization.test.ts +113 -113
- package/src/ui-range.test.ts +70 -70
- package/src/ui-selection.test.ts +155 -155
- package/src/ui-tier1.test.ts +142 -142
- package/src/virtual.test.ts +88 -88
- package/src/virtualization/column-virtualizer.test.ts +27 -27
- package/src/virtualization/column-virtualizer.ts +30 -30
- package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
- package/src/virtualization/types.ts +30 -30
- package/src/virtualization/virtualizer.test.ts +47 -47
- package/src/virtualization/virtualizer.ts +322 -322
- package/dist/cdn/GridMenus-BfTAKn84.js +0 -488
- package/dist/cdn/GridMenus-C3bJd7w8.js +0 -489
- package/dist/cdn/SvDateTimePicker-CHnUkWcH.js +0 -2206
- package/dist/cdn/SvDateTimePicker-CRQH_U7E.js +0 -2201
- package/dist/cdn/SvGridCellEditor-BGUCzuPB.js +0 -523
- package/dist/cdn/SvGridCellEditor-G8IMWIws.js +0 -522
|
@@ -390,6 +390,29 @@ export function createSvGridController<
|
|
|
390
390
|
targetRow: number;
|
|
391
391
|
targetCol: number;
|
|
392
392
|
} | null>(null);
|
|
393
|
+
/** An in-flight Excel-style range MOVE (drag the selection's border to
|
|
394
|
+
* relocate its values). Same source rectangle as `fillDrag`, plus where
|
|
395
|
+
* inside that rectangle the pointer grabbed it - the drop offset is
|
|
396
|
+
* `target - grab`, which is what lets a drag started on the range's LEFT
|
|
397
|
+
* edge land the range where the pointer is rather than jumping it.
|
|
398
|
+
* `copy` mirrors the Ctrl / Cmd key and is re-read on every move, so the
|
|
399
|
+
* user can change their mind mid-drag exactly as in a spreadsheet. */
|
|
400
|
+
let moveDrag = $state<{
|
|
401
|
+
sourceMinRow: number;
|
|
402
|
+
sourceMaxRow: number;
|
|
403
|
+
sourceMinCol: number;
|
|
404
|
+
sourceMaxCol: number;
|
|
405
|
+
grabRow: number;
|
|
406
|
+
grabCol: number;
|
|
407
|
+
targetRow: number;
|
|
408
|
+
targetCol: number;
|
|
409
|
+
copy: boolean;
|
|
410
|
+
} | null>(null);
|
|
411
|
+
/** True while the pointer hovers the grab strip on a selection border, so
|
|
412
|
+
* the root can switch to a move cursor. Kept as its own flag rather than
|
|
413
|
+
* recomputed per cell: it flips at most twice per border crossing and only
|
|
414
|
+
* one attribute on one element reads it. */
|
|
415
|
+
let moveGrabHover = $state(false);
|
|
393
416
|
let activeAtPointerDown: { rowIndex: number; colIndex: number } | null = null;
|
|
394
417
|
let editingCell = $state<CellEditState>(null);
|
|
395
418
|
// Full-row editing: the row currently in whole-row edit + its per-column
|
|
@@ -456,9 +479,6 @@ export function createSvGridController<
|
|
|
456
479
|
let editorSelectAll = true;
|
|
457
480
|
/** Per-column width overrides set by the resize handles. */
|
|
458
481
|
let columnWidths = $state<Record<string, number>>({});
|
|
459
|
-
let resizingColumnId = $state<string | null>(null);
|
|
460
|
-
let resizeStartX = 0;
|
|
461
|
-
let resizeStartWidth = 0;
|
|
462
482
|
const MIN_COLUMN_WIDTH = 40;
|
|
463
483
|
/** Columns pinned to the left or right edge of the grid (sticky positioning).
|
|
464
484
|
* Seeded from `props.initialColumnPinning` so demos / tests can show the
|
|
@@ -475,7 +495,28 @@ export function createSvGridController<
|
|
|
475
495
|
let dataStateVersion = $state(0);
|
|
476
496
|
const selectionColumnWidth = 44;
|
|
477
497
|
const rowNumberColumnWidth = $derived(props.rowNumberWidth ?? 56);
|
|
478
|
-
|
|
498
|
+
// A per-row `rowHeight` function already supplies heights, so auto-measuring
|
|
499
|
+
// would fight it. Fixed numbers are fine - they become the pre-measure estimate.
|
|
500
|
+
const autoRowHeightOn = $derived(
|
|
501
|
+
props.autoRowHeight === true && typeof props.rowHeight !== "function",
|
|
502
|
+
);
|
|
503
|
+
// Auto height measures the row from its content, so a dragged height would be
|
|
504
|
+
// overwritten on the next measurement pass. Let auto height win rather than
|
|
505
|
+
// having the two fight.
|
|
506
|
+
const rowResizeOn = $derived(props.rowResize === true && !autoRowHeightOn);
|
|
507
|
+
/**
|
|
508
|
+
* The row-number gutter. Explicit `showRowNumbers` always wins; otherwise
|
|
509
|
+
* `rowResize` brings it in, because a resizable row needs a row header to
|
|
510
|
+
* grab - that is where the drag strip lives, and it is where a spreadsheet
|
|
511
|
+
* puts it. Dragging the edge of a data cell instead works but reads as an
|
|
512
|
+
* accident.
|
|
513
|
+
*
|
|
514
|
+
* Both derived above are declared here rather than beside the rest of the
|
|
515
|
+
* row-resize state further down: a `$derived` referenced before its
|
|
516
|
+
* declaration in a .svelte.ts silently compiles to an empty getter, so this
|
|
517
|
+
* gate would have read `undefined` and the gutter would never appear.
|
|
518
|
+
*/
|
|
519
|
+
const showRowNumbersEffective = $derived(props.showRowNumbers ?? rowResizeOn);
|
|
479
520
|
let columnMenuFor = $state<string | null>(null);
|
|
480
521
|
let columnMenuTab = $state<"general" | "filter" | "columns">("general");
|
|
481
522
|
let columnMenuPos = $state<MenuPosition>({ x: 0, y: 0 });
|
|
@@ -499,11 +540,11 @@ export function createSvGridController<
|
|
|
499
540
|
let commentEditFor = $state<{ rowId: string; columnId: string; x: number; y: number } | null>(null);
|
|
500
541
|
let commentDraft = $state("");
|
|
501
542
|
let valueFilters = $state<Record<string, Set<string>>>({});
|
|
502
|
-
const viewportWidth = $derived.by(()
|
|
543
|
+
const viewportWidth = $derived.by(function viewportWidth_d() {
|
|
503
544
|
viewportVersion;
|
|
504
545
|
return scrollContainer ? scrollContainer.clientWidth : 0;
|
|
505
546
|
});
|
|
506
|
-
const viewportHeight = $derived.by(()
|
|
547
|
+
const viewportHeight = $derived.by(function viewportHeight_d() {
|
|
507
548
|
viewportVersion;
|
|
508
549
|
return scrollContainer ? scrollContainer.clientHeight : 0;
|
|
509
550
|
});
|
|
@@ -529,7 +570,7 @@ export function createSvGridController<
|
|
|
529
570
|
const hb = column.columnDef?.hideBelow;
|
|
530
571
|
return hb != null && viewportWidth > 0 && viewportWidth < hb;
|
|
531
572
|
}
|
|
532
|
-
const scrollMetrics = $derived.by(()
|
|
573
|
+
const scrollMetrics = $derived.by(function scrollMetrics_d() {
|
|
533
574
|
scrollVersion;
|
|
534
575
|
viewportVersion;
|
|
535
576
|
// Track the virtualizers' versions too so when data loads or row /
|
|
@@ -563,7 +604,7 @@ export function createSvGridController<
|
|
|
563
604
|
* - virtualizer.getTotalSize(): correct at initial load (before first paint)
|
|
564
605
|
* - scrollMetrics.scrollHeight: correct after detail rows expand (DOM is live,
|
|
565
606
|
* ResizeObserver on gridRootEl already bumps scrollVersion at that point) */
|
|
566
|
-
const hasVerticalOverflow = $derived.by(()
|
|
607
|
+
const hasVerticalOverflow = $derived.by(function hasVerticalOverflow_d() {
|
|
567
608
|
virtualizer.version;
|
|
568
609
|
const virtualizerSize = virtualizer.getTotalSize();
|
|
569
610
|
// scrollMetrics.scrollHeight is 0 before initial paint; once the table
|
|
@@ -608,6 +649,14 @@ export function createSvGridController<
|
|
|
608
649
|
(props.selectionMode ?? "both") === "both"),
|
|
609
650
|
);
|
|
610
651
|
|
|
652
|
+
// Range drag-and-drop rides on cell selection: there is no range to grab
|
|
653
|
+
// without it, so it can never be on alone. Default ON, matching the fill
|
|
654
|
+
// handle - both are spreadsheet affordances users arrive expecting, and
|
|
655
|
+
// neither is reachable until something is actually selected.
|
|
656
|
+
const moveCellsEffective = $derived(
|
|
657
|
+
(props.moveCells ?? true) && enableCellSelectionEffective,
|
|
658
|
+
);
|
|
659
|
+
|
|
611
660
|
// The aggregate footer row. `summary` is the shortcut alias, so it wins over
|
|
612
661
|
// the fine-grained prop - the same precedence `selectable` uses.
|
|
613
662
|
//
|
|
@@ -856,7 +905,7 @@ export function createSvGridController<
|
|
|
856
905
|
// Declared here rather than beside the other pagination/state derivations:
|
|
857
906
|
// the auto-group column pipeline below reads it, and a `const` used above its
|
|
858
907
|
// declaration is a type error even though `$derived` is lazy enough at runtime.
|
|
859
|
-
const groupingColumns = $derived.by(()
|
|
908
|
+
const groupingColumns = $derived.by(function groupingColumns_d() {
|
|
860
909
|
gridStateVersion;
|
|
861
910
|
return grid.getState().grouping ?? [];
|
|
862
911
|
});
|
|
@@ -881,7 +930,7 @@ export function createSvGridController<
|
|
|
881
930
|
* any other column. They carry no `field` - their cell content is resolved
|
|
882
931
|
* from the row's group state at render time.
|
|
883
932
|
*/
|
|
884
|
-
const autoGroupColumns = $derived.by(()
|
|
933
|
+
const autoGroupColumns = $derived.by(function autoGroupColumns_d() {
|
|
885
934
|
if (!groupColumnMode) return [] as Column<TData>[];
|
|
886
935
|
const sourceById = new Map(grid.getAllColumns().map((c) => [c.id, c]));
|
|
887
936
|
return autoGroupSpec.autoColumns.map((spec) => {
|
|
@@ -950,7 +999,7 @@ export function createSvGridController<
|
|
|
950
999
|
};
|
|
951
1000
|
}
|
|
952
1001
|
|
|
953
|
-
const allColumns = $derived.by(()
|
|
1002
|
+
const allColumns = $derived.by(function allColumns_d() {
|
|
954
1003
|
let raw = grid
|
|
955
1004
|
.getAllColumns()
|
|
956
1005
|
.filter(
|
|
@@ -996,7 +1045,7 @@ export function createSvGridController<
|
|
|
996
1045
|
});
|
|
997
1046
|
|
|
998
1047
|
/** Header groups reordered to match {@link allColumns}. */
|
|
999
|
-
const headerGroups = $derived.by(()
|
|
1048
|
+
const headerGroups = $derived.by(function headerGroups_d() {
|
|
1000
1049
|
const base = grid.getHeaderGroups();
|
|
1001
1050
|
if (!base.length) return base;
|
|
1002
1051
|
const byId = new Map(
|
|
@@ -1054,7 +1103,7 @@ export function createSvGridController<
|
|
|
1054
1103
|
collapsible: boolean;
|
|
1055
1104
|
collapsed: boolean;
|
|
1056
1105
|
};
|
|
1057
|
-
const groupHeaderRows = $derived.by(()
|
|
1106
|
+
const groupHeaderRows = $derived.by(function groupHeaderRows_d() {
|
|
1058
1107
|
const userCols: Array<ColumnDef<any, TData>> =
|
|
1059
1108
|
(props.columns as unknown as Array<ColumnDef<any, TData>>) ?? [];
|
|
1060
1109
|
|
|
@@ -1184,7 +1233,7 @@ export function createSvGridController<
|
|
|
1184
1233
|
});
|
|
1185
1234
|
|
|
1186
1235
|
/** Cumulative pixel offsets for left- and right-pinned columns. */
|
|
1187
|
-
const pinnedOffsets = $derived.by(()
|
|
1236
|
+
const pinnedOffsets = $derived.by(function pinnedOffsets_d() {
|
|
1188
1237
|
const rowNumberWidth = showRowNumbersEffective ? rowNumberColumnWidth : 0;
|
|
1189
1238
|
const selectionWidth = showRowSelectionEffective ? selectionColumnWidth : 0;
|
|
1190
1239
|
const left: Record<string, number> = {};
|
|
@@ -1241,7 +1290,7 @@ export function createSvGridController<
|
|
|
1241
1290
|
);
|
|
1242
1291
|
// Per-column numeric min/max, needed only by colorScale / dataBar formats.
|
|
1243
1292
|
// Lazy: this derived never runs unless `conditionalFormats` is set.
|
|
1244
|
-
const conditionalColumnStats = $derived.by(()
|
|
1293
|
+
const conditionalColumnStats = $derived.by(function conditionalColumnStats_d() {
|
|
1245
1294
|
const map = new Map<string, ColumnStat>();
|
|
1246
1295
|
const formats = props.conditionalFormats;
|
|
1247
1296
|
if (!formats?.length || !formatsNeedingStats(formats)) return map;
|
|
@@ -1288,7 +1337,7 @@ export function createSvGridController<
|
|
|
1288
1337
|
|
|
1289
1338
|
|
|
1290
1339
|
|
|
1291
|
-
const sortDirectionByColumn = $derived.by(()
|
|
1340
|
+
const sortDirectionByColumn = $derived.by(function sortDirectionByColumn_d() {
|
|
1292
1341
|
gridStateVersion;
|
|
1293
1342
|
const directions: Record<string, false | "asc" | "desc"> = {};
|
|
1294
1343
|
for (const column of allColumns)
|
|
@@ -1297,7 +1346,7 @@ export function createSvGridController<
|
|
|
1297
1346
|
});
|
|
1298
1347
|
|
|
1299
1348
|
|
|
1300
|
-
const paginationState = $derived.by(()
|
|
1349
|
+
const paginationState = $derived.by(function paginationState_d() {
|
|
1301
1350
|
gridStateVersion;
|
|
1302
1351
|
return grid.getState().pagination ?? { pageIndex: 0, pageSize: 10 };
|
|
1303
1352
|
});
|
|
@@ -1308,34 +1357,41 @@ export function createSvGridController<
|
|
|
1308
1357
|
* compute the correct "X to Y of Z" range and total page count when
|
|
1309
1358
|
* filters reduce the dataset.
|
|
1310
1359
|
*/
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1360
|
+
/**
|
|
1361
|
+
* The per-column filter stages: the menu's (up to two) conditions, then the
|
|
1362
|
+
* set-filter checklists. Split out of `allRowsBeforePagination` so the facet
|
|
1363
|
+
* list can run the SAME pipeline with its own column left out - a checklist
|
|
1364
|
+
* that offers values excluded by the other columns' filters sends you to an
|
|
1365
|
+
* empty grid. Re-implementing the stages beside the original would have let
|
|
1366
|
+
* the two drift.
|
|
1367
|
+
*
|
|
1368
|
+
* `excludeColumnId` omits that column's own filters, which is what "every
|
|
1369
|
+
* other filter" means for the column whose menu is open.
|
|
1370
|
+
*/
|
|
1371
|
+
function applyColumnFilters(
|
|
1372
|
+
inputRows: Array<Row<TData>>,
|
|
1373
|
+
excludeColumnId?: string,
|
|
1374
|
+
): Array<Row<TData>> {
|
|
1375
|
+
let rows = inputRows;
|
|
1376
|
+
/**
|
|
1377
|
+
* Resolve a column ONCE and return a reader for it.
|
|
1378
|
+
*
|
|
1379
|
+
* `getRowColumnValue` finds the column with a linear scan over every
|
|
1380
|
+
* column, which is fine for a one-off read and wrong inside a loop over
|
|
1381
|
+
* 100,000 rows - it made each filtered column cost O(rows x columns).
|
|
1382
|
+
* The id is fixed for the life of a filter, so the scan is hoisted here and
|
|
1383
|
+
* the row loop gets a closure that only reads.
|
|
1384
|
+
*
|
|
1385
|
+
* Falls back to the row's own accessor when the id names no column, which
|
|
1386
|
+
* is also what `getRowColumnValue` does - group rows override
|
|
1387
|
+
* `getCellValueByColumnId` to return their aggregate.
|
|
1388
|
+
*/
|
|
1389
|
+
const makeColumnReader = (columnId: string) => {
|
|
1390
|
+
const column = allColumns.find((entry: any) => entry.id === columnId);
|
|
1391
|
+
return column
|
|
1392
|
+
? (row: Row<TData>) => getColumnBaseValue(row, column)
|
|
1393
|
+
: (row: Row<TData>) => row.getCellValueByColumnId(columnId);
|
|
1394
|
+
};
|
|
1339
1395
|
|
|
1340
1396
|
// A single condition is "active" if it has the value(s) it needs.
|
|
1341
1397
|
const condActive = (op: FilterOperator, value: string, valueTo?: string): boolean => {
|
|
@@ -1358,7 +1414,8 @@ export function createSvGridController<
|
|
|
1358
1414
|
{ locale: (props.filterLocale ?? props.localization?.locale) },
|
|
1359
1415
|
);
|
|
1360
1416
|
// A column filter is active if either of its (up to two) conditions is.
|
|
1361
|
-
const menuFilters = Object.entries(filterMenuValues).filter(([
|
|
1417
|
+
const menuFilters = Object.entries(filterMenuValues).filter(([id, f]) => {
|
|
1418
|
+
if (id === excludeColumnId) return false;
|
|
1362
1419
|
const a = condActive(f.operator, f.value, f.valueTo);
|
|
1363
1420
|
const b = !!f.operator2 && condActive(f.operator2, f.value2 ?? "", f.valueTo2);
|
|
1364
1421
|
return a || b;
|
|
@@ -1368,6 +1425,11 @@ export function createSvGridController<
|
|
|
1368
1425
|
// become compiled predicates before a single row is tested.
|
|
1369
1426
|
const compiledMenuFilters = menuFilters.map(([columnId, f]) => ({
|
|
1370
1427
|
columnId,
|
|
1428
|
+
// The column, resolved once. `getRowColumnValue` looks it up with a
|
|
1429
|
+
// linear scan of every column, and the id is fixed per filter, so
|
|
1430
|
+
// leaving that inside the row loop made it O(rows x columns) per
|
|
1431
|
+
// filtered column for no reason.
|
|
1432
|
+
readValue: makeColumnReader(columnId),
|
|
1371
1433
|
join: f.join,
|
|
1372
1434
|
a: condActive(f.operator, f.value, f.valueTo)
|
|
1373
1435
|
? compileCond(columnId, f.operator, f.value, f.valueTo)
|
|
@@ -1377,8 +1439,8 @@ export function createSvGridController<
|
|
|
1377
1439
|
: null,
|
|
1378
1440
|
}));
|
|
1379
1441
|
rows = rows.filter((row) =>
|
|
1380
|
-
compiledMenuFilters.every(({
|
|
1381
|
-
const cellValue =
|
|
1442
|
+
compiledMenuFilters.every(({ readValue, join, a, b }) => {
|
|
1443
|
+
const cellValue = readValue(row);
|
|
1382
1444
|
const ra = a ? a(cellValue) : null;
|
|
1383
1445
|
const rb = b ? b(cellValue) : null;
|
|
1384
1446
|
if (ra === null) return rb ?? true;
|
|
@@ -1388,7 +1450,9 @@ export function createSvGridController<
|
|
|
1388
1450
|
);
|
|
1389
1451
|
}
|
|
1390
1452
|
|
|
1391
|
-
const valueFilterEntries = Object.entries(valueFilters)
|
|
1453
|
+
const valueFilterEntries = Object.entries(valueFilters).filter(
|
|
1454
|
+
([id]) => id !== excludeColumnId,
|
|
1455
|
+
);
|
|
1392
1456
|
if (valueFilterEntries.length) {
|
|
1393
1457
|
// Resolve bucket defs up front so we don't re-hit the derived map
|
|
1394
1458
|
// for every row × column combination. Columns without bucketing
|
|
@@ -1396,11 +1460,12 @@ export function createSvGridController<
|
|
|
1396
1460
|
const bucketEntries = valueFilterEntries.map(([columnId, allowed]) => ({
|
|
1397
1461
|
columnId,
|
|
1398
1462
|
allowed,
|
|
1463
|
+
readValue: makeColumnReader(columnId),
|
|
1399
1464
|
buckets: facetBucketsByColumn.get(columnId) ?? null,
|
|
1400
1465
|
}));
|
|
1401
1466
|
rows = rows.filter((row) =>
|
|
1402
|
-
bucketEntries.every(({
|
|
1403
|
-
const raw =
|
|
1467
|
+
bucketEntries.every(({ allowed, buckets, readValue }) => {
|
|
1468
|
+
const raw = readValue(row);
|
|
1404
1469
|
if (buckets) {
|
|
1405
1470
|
// Range-bucketed filter: find which bucket this row's value
|
|
1406
1471
|
// falls into and check whether that bucket's label is allowed.
|
|
@@ -1417,6 +1482,40 @@ export function createSvGridController<
|
|
|
1417
1482
|
);
|
|
1418
1483
|
}
|
|
1419
1484
|
|
|
1485
|
+
return rows;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
const allRowsBeforePagination = $derived.by(function allRowsBeforePagination_d() {
|
|
1489
|
+
// Depend on dataStateVersion (row-model-affecting store changes) NOT
|
|
1490
|
+
// gridStateVersion - so moving the active cell / selection does not force
|
|
1491
|
+
// this O(rows) pipeline to re-run. Filter-input state (globalFilter etc.)
|
|
1492
|
+
// and internalData are read below and tracked as their own dependencies.
|
|
1493
|
+
dataStateVersion;
|
|
1494
|
+
// Touch internalData + internalColumns so the row model re-derives when
|
|
1495
|
+
// the consumer replaces the data array (e.g. via a "Reset" button).
|
|
1496
|
+
void internalData;
|
|
1497
|
+
void internalColumns;
|
|
1498
|
+
const rawRows = grid.getRowModel().rows;
|
|
1499
|
+
// External-filter mode: the consumer fetched / pre-filtered the rows
|
|
1500
|
+
// themselves (server-side data sources). Skip every local filter pass
|
|
1501
|
+
// so the data isn't double-filtered against the visible page.
|
|
1502
|
+
if (externalFilterEnabled) return rawRows;
|
|
1503
|
+
|
|
1504
|
+
let rows = rawRows;
|
|
1505
|
+
if (globalFilter.trim()) {
|
|
1506
|
+
const needle = normalizeForFilter(globalFilter, (props.filterLocale ?? props.localization?.locale));
|
|
1507
|
+
rows = rows.filter((row) =>
|
|
1508
|
+
row
|
|
1509
|
+
.getAllCells()
|
|
1510
|
+
.some((cell) =>
|
|
1511
|
+
normalizeForFilter(String(cell.getValue() ?? ""), (props.filterLocale ?? props.localization?.locale))
|
|
1512
|
+
.includes(needle),
|
|
1513
|
+
),
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
rows = applyColumnFilters(rows);
|
|
1518
|
+
|
|
1420
1519
|
// --- Advanced filter (Pro) ---------------------------------------------
|
|
1421
1520
|
// Runs LAST for two reasons: it evaluates over the smallest row set, and
|
|
1422
1521
|
// its aggregates (`SUM(amount) > 1000`) fold over what the user is
|
|
@@ -1458,7 +1557,7 @@ export function createSvGridController<
|
|
|
1458
1557
|
* is active we page by DATA rows and reprint each page's ancestor banners.
|
|
1459
1558
|
* Null when grouping is off, so the flat path stays a cheap slice.
|
|
1460
1559
|
*/
|
|
1461
|
-
const groupedPage = $derived.by(()
|
|
1560
|
+
const groupedPage = $derived.by(function groupedPage_d() {
|
|
1462
1561
|
if (!paginationEnabled || externalPaginationEnabled) return null;
|
|
1463
1562
|
if (!groupingColumns.length) return null;
|
|
1464
1563
|
const { pageIndex, pageSize } = paginationState;
|
|
@@ -1525,7 +1624,7 @@ export function createSvGridController<
|
|
|
1525
1624
|
* the full dataset rather than the current page (see the comment above
|
|
1526
1625
|
* `_rowModels`).
|
|
1527
1626
|
*/
|
|
1528
|
-
const allRows = $derived.by(()
|
|
1627
|
+
const allRows = $derived.by(function allRows_d() {
|
|
1529
1628
|
const paged = (() => {
|
|
1530
1629
|
const rows = allRowsBeforePagination;
|
|
1531
1630
|
// External pagination: `data` already IS the current page - never slice.
|
|
@@ -1598,11 +1697,53 @@ export function createSvGridController<
|
|
|
1598
1697
|
const paginationPageSize = $derived(
|
|
1599
1698
|
externalPaginationEnabled ? (props.pageSize ?? 10) : paginationState.pageSize,
|
|
1600
1699
|
);
|
|
1601
|
-
const rowSelectionState = $derived.by(()
|
|
1700
|
+
const rowSelectionState = $derived.by(function rowSelectionState_d() {
|
|
1602
1701
|
gridStateVersion;
|
|
1603
1702
|
return grid.getState().rowSelection ?? {};
|
|
1604
1703
|
});
|
|
1605
1704
|
|
|
1705
|
+
// ---- Selection bar ----------------------------------------------------
|
|
1706
|
+
// The floating bulk-action bar. Off unless asked for: it overlaps a row, and
|
|
1707
|
+
// what belongs on it is app-specific. The RENDERER is Enterprise and arrives
|
|
1708
|
+
// through the selection-bar-view seam; everything here is free.
|
|
1709
|
+
const selectionBarOn = $derived(props.selectionBar !== undefined && props.selectionBar !== false);
|
|
1710
|
+
/** The prop in its three shapes - `true`, an action array, or a full config -
|
|
1711
|
+
* normalised to the config object the renderer reads. */
|
|
1712
|
+
const selectionBarConfig = $derived.by(function selectionBarConfig_d() {
|
|
1713
|
+
const raw = props.selectionBar;
|
|
1714
|
+
if (Array.isArray(raw)) return { actions: raw };
|
|
1715
|
+
if (raw && typeof raw === "object") return raw as Record<string, unknown>;
|
|
1716
|
+
return {};
|
|
1717
|
+
});
|
|
1718
|
+
const selectionBarActions = $derived(
|
|
1719
|
+
(selectionBarConfig.actions as unknown[] | undefined) ?? [],
|
|
1720
|
+
);
|
|
1721
|
+
const selectionBarPosition = $derived(
|
|
1722
|
+
selectionBarConfig.position === "top" ? "top" : "bottom",
|
|
1723
|
+
);
|
|
1724
|
+
const selectionBarMaxVisible = $derived(
|
|
1725
|
+
typeof selectionBarConfig.maxVisible === "number" && selectionBarConfig.maxVisible > 0
|
|
1726
|
+
? selectionBarConfig.maxVisible
|
|
1727
|
+
: 6,
|
|
1728
|
+
);
|
|
1729
|
+
const selectionBarHideClear = $derived(selectionBarConfig.hideClear === true);
|
|
1730
|
+
/** Selected rows in DISPLAY order, plus their ids - what every action on the
|
|
1731
|
+
* bar receives, and what its count shows. Built from `allRows` rather than
|
|
1732
|
+
* the selection map so the order matches what the user is looking at. */
|
|
1733
|
+
const selectionBarTarget = $derived.by(function selectionBarTarget_d() {
|
|
1734
|
+
const rows: TData[] = [];
|
|
1735
|
+
const ids: string[] = [];
|
|
1736
|
+
if (!selectionBarOn) return { rows, ids };
|
|
1737
|
+
for (const row of allRows) {
|
|
1738
|
+
if (isGroupRow(row)) continue;
|
|
1739
|
+
if (rowSelectionState[row.id]) {
|
|
1740
|
+
rows.push(row.original as TData);
|
|
1741
|
+
ids.push(row.id);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
return { rows, ids };
|
|
1745
|
+
});
|
|
1746
|
+
|
|
1606
1747
|
// Forward selection changes to the consumer. Skips the very first invocation
|
|
1607
1748
|
// (the initial empty state) so consumers don't get a spurious callback on mount.
|
|
1608
1749
|
let lastSelectionSerialized = "";
|
|
@@ -1654,7 +1795,7 @@ export function createSvGridController<
|
|
|
1654
1795
|
? props.statusBar.aggregates
|
|
1655
1796
|
: (["count", "sum", "avg", "min", "max"] as const),
|
|
1656
1797
|
);
|
|
1657
|
-
const statusBarStats = $derived.by(()
|
|
1798
|
+
const statusBarStats = $derived.by(function statusBarStats_d() {
|
|
1658
1799
|
if (!statusBarEnabled) return null;
|
|
1659
1800
|
const a = selectionRange.anchor;
|
|
1660
1801
|
const f = selectionRange.focus;
|
|
@@ -1706,7 +1847,7 @@ export function createSvGridController<
|
|
|
1706
1847
|
const toolPanelEnabled = $derived(props.toolPanel === true);
|
|
1707
1848
|
// Every column (including hidden ones) in the user's current order, so the
|
|
1708
1849
|
// panel can toggle/reorder anything. Group columns are flagged live.
|
|
1709
|
-
const toolPanelColumns = $derived.by(()
|
|
1850
|
+
const toolPanelColumns = $derived.by(function toolPanelColumns_d() {
|
|
1710
1851
|
gridStateVersion;
|
|
1711
1852
|
const all = grid.getAllColumns();
|
|
1712
1853
|
if (!userColumnOrder.length) return all;
|
|
@@ -1743,7 +1884,7 @@ export function createSvGridController<
|
|
|
1743
1884
|
// unsaid, and why saying it would make the grid talk over itself.
|
|
1744
1885
|
|
|
1745
1886
|
/** Data rows before any local filtering - the denominator in "12 of 250". */
|
|
1746
|
-
const unfilteredRowTotal = $derived.by(()
|
|
1887
|
+
const unfilteredRowTotal = $derived.by(function unfilteredRowTotal_d() {
|
|
1747
1888
|
dataStateVersion;
|
|
1748
1889
|
void internalData;
|
|
1749
1890
|
return grid.getRowModel().rows.length;
|
|
@@ -1840,7 +1981,7 @@ export function createSvGridController<
|
|
|
1840
1981
|
props.onPivotModeChange?.(next);
|
|
1841
1982
|
}
|
|
1842
1983
|
const pivotActive = $derived(!!pivotConfig && pivotModeOn && hasPivotEngine());
|
|
1843
|
-
const pivotResult = $derived.by(()
|
|
1984
|
+
const pivotResult = $derived.by(function pivotResult_d() {
|
|
1844
1985
|
if (!pivotActive || !pivotConfig) return null;
|
|
1845
1986
|
const engine = getPivotEngine();
|
|
1846
1987
|
if (!engine) return null;
|
|
@@ -1944,7 +2085,7 @@ export function createSvGridController<
|
|
|
1944
2085
|
const h = col.columnDef.header;
|
|
1945
2086
|
return typeof h === "string" && h ? h : (col.columnDef.field ?? col.id);
|
|
1946
2087
|
};
|
|
1947
|
-
const chartableColumns = $derived.by(()
|
|
2088
|
+
const chartableColumns = $derived.by(function chartableColumns_d() {
|
|
1948
2089
|
const dims: Array<{ id: string; field: string; label: string }> = [];
|
|
1949
2090
|
const measures: Array<{ id: string; field: string; label: string }> = [];
|
|
1950
2091
|
for (const col of allColumns) {
|
|
@@ -1956,7 +2097,7 @@ export function createSvGridController<
|
|
|
1956
2097
|
}
|
|
1957
2098
|
return { dims, measures };
|
|
1958
2099
|
});
|
|
1959
|
-
const chartColumnDefaults = $derived.by(()
|
|
2100
|
+
const chartColumnDefaults = $derived.by(function chartColumnDefaults_d() {
|
|
1960
2101
|
const { dims, measures } = chartableColumns;
|
|
1961
2102
|
let dimId = dims[0]?.id ?? null;
|
|
1962
2103
|
let seriesId: string | null = null;
|
|
@@ -2315,7 +2456,7 @@ export function createSvGridController<
|
|
|
2315
2456
|
typeof props.containerHeight === "number" ? props.containerHeight : 520,
|
|
2316
2457
|
);
|
|
2317
2458
|
|
|
2318
|
-
const virtualRows = $derived.by(()
|
|
2459
|
+
const virtualRows = $derived.by(function virtualRows_d() {
|
|
2319
2460
|
virtualizer.version;
|
|
2320
2461
|
const items = virtualizer.getVirtualItems();
|
|
2321
2462
|
if (items.length || allRows.length === 0) return items;
|
|
@@ -2330,7 +2471,7 @@ export function createSvGridController<
|
|
|
2330
2471
|
props.overscan ?? 8,
|
|
2331
2472
|
);
|
|
2332
2473
|
});
|
|
2333
|
-
const virtualRowTotalSize = $derived.by(()
|
|
2474
|
+
const virtualRowTotalSize = $derived.by(function virtualRowTotalSize_d() {
|
|
2334
2475
|
virtualizer.version;
|
|
2335
2476
|
const size = virtualizer.getTotalSize();
|
|
2336
2477
|
// Keep the scroll height honest during the pre-measurement render, so the
|
|
@@ -2389,7 +2530,7 @@ export function createSvGridController<
|
|
|
2389
2530
|
// OWN committed scroll offset - not the live DOM scrollTop - so the spacer
|
|
2390
2531
|
// shift and the rendered window are always computed from the same state and
|
|
2391
2532
|
// can never skew by a frame (which would jitter at extreme scale).
|
|
2392
|
-
const rowOffsetAdjustment = $derived.by(()
|
|
2533
|
+
const rowOffsetAdjustment = $derived.by(function rowOffsetAdjustment_d() {
|
|
2393
2534
|
if (!rowScrollScalingActive) return 0;
|
|
2394
2535
|
virtualizer.version;
|
|
2395
2536
|
const logical = virtualizer.getState().scrollOffset;
|
|
@@ -2401,11 +2542,11 @@ export function createSvGridController<
|
|
|
2401
2542
|
const rowBottomSpacer = $derived(
|
|
2402
2543
|
Math.max(rowDomTotalSize - (virtualRowEnd - rowOffsetAdjustment), 0),
|
|
2403
2544
|
);
|
|
2404
|
-
const virtualColumns = $derived.by(()
|
|
2545
|
+
const virtualColumns = $derived.by(function virtualColumns_d() {
|
|
2405
2546
|
columnVirtualizerVersion;
|
|
2406
2547
|
return columnVirtualizer.getVirtualItems();
|
|
2407
2548
|
});
|
|
2408
|
-
const virtualColumnTotalSize = $derived.by(()
|
|
2549
|
+
const virtualColumnTotalSize = $derived.by(function virtualColumnTotalSize_d() {
|
|
2409
2550
|
columnVirtualizerVersion;
|
|
2410
2551
|
return columnVirtualizer.getTotalSize();
|
|
2411
2552
|
});
|
|
@@ -2421,7 +2562,7 @@ export function createSvGridController<
|
|
|
2421
2562
|
});
|
|
2422
2563
|
};
|
|
2423
2564
|
|
|
2424
|
-
const renderedColumnItems = $derived.by(()
|
|
2565
|
+
const renderedColumnItems = $derived.by(function renderedColumnItems_d() {
|
|
2425
2566
|
if (!columnVirtualizationEnabled) return allColumnItems();
|
|
2426
2567
|
// Column virtualization is ON by default and its virtualizer, like the row
|
|
2427
2568
|
// one, only learns `count` from an effect - which never runs on a server.
|
|
@@ -2466,7 +2607,7 @@ export function createSvGridController<
|
|
|
2466
2607
|
.map((item) => ({ item, column: allColumns[item.index] }))
|
|
2467
2608
|
.filter(hasRenderedColumn),
|
|
2468
2609
|
);
|
|
2469
|
-
const totalColumnWidth = $derived.by(()
|
|
2610
|
+
const totalColumnWidth = $derived.by(function totalColumnWidth_d() {
|
|
2470
2611
|
if (columnVirtualizationEnabled) return virtualColumnTotalSize;
|
|
2471
2612
|
let total = 0;
|
|
2472
2613
|
for (const column of allColumns) total += getColumnWidth(column.id);
|
|
@@ -2481,7 +2622,7 @@ export function createSvGridController<
|
|
|
2481
2622
|
* column instead is reactive to both `columnWidths` and
|
|
2482
2623
|
* `fittedColumnWidths`, so the overflow decision settles in the same
|
|
2483
2624
|
* render where fit-scaling lands - no race, no scrollbar flash. */
|
|
2484
|
-
const hasHorizontalOverflow = $derived.by(()
|
|
2625
|
+
const hasHorizontalOverflow = $derived.by(function hasHorizontalOverflow_d() {
|
|
2485
2626
|
const fixedCols =
|
|
2486
2627
|
(showRowNumbersEffective ? rowNumberColumnWidth : 0) +
|
|
2487
2628
|
(showRowSelectionEffective ? selectionColumnWidth : 0);
|
|
@@ -2514,7 +2655,7 @@ export function createSvGridController<
|
|
|
2514
2655
|
* everything stays pixel-aligned. When virtualization is off this is a
|
|
2515
2656
|
* pass-through.
|
|
2516
2657
|
*/
|
|
2517
|
-
const groupHeaderRowsWindowed = $derived.by(()
|
|
2658
|
+
const groupHeaderRowsWindowed = $derived.by(function groupHeaderRowsWindowed_d() {
|
|
2518
2659
|
const base = groupHeaderRows;
|
|
2519
2660
|
if (!columnVirtualizationEnabled || base.length === 0) return base;
|
|
2520
2661
|
const items = renderedColumnItems;
|
|
@@ -2537,14 +2678,14 @@ export function createSvGridController<
|
|
|
2537
2678
|
});
|
|
2538
2679
|
});
|
|
2539
2680
|
|
|
2540
|
-
const activeCell = $derived.by(()
|
|
2681
|
+
const activeCell = $derived.by(function activeCell_d() {
|
|
2541
2682
|
gridStateVersion;
|
|
2542
2683
|
return (
|
|
2543
2684
|
grid.getState().activeCell ?? { rowIndex: 0, colIndex: 0, cellId: null }
|
|
2544
2685
|
);
|
|
2545
2686
|
});
|
|
2546
2687
|
|
|
2547
|
-
const activeDescendantId = $derived.by(()
|
|
2688
|
+
const activeDescendantId = $derived.by(function activeDescendantId_d() {
|
|
2548
2689
|
const active = activeCell;
|
|
2549
2690
|
const inRows = active.rowIndex >= 0 && active.rowIndex < allRows.length;
|
|
2550
2691
|
const inCols = active.colIndex >= 0 && active.colIndex < allColumns.length;
|
|
@@ -2594,7 +2735,7 @@ export function createSvGridController<
|
|
|
2594
2735
|
* `null` above the cell limit - that case stays on the rAF-deferred effect,
|
|
2595
2736
|
* which keeps a huge grid painting before it totals.
|
|
2596
2737
|
*/
|
|
2597
|
-
const eagerSummaries = $derived.by(()
|
|
2738
|
+
const eagerSummaries = $derived.by(function eagerSummaries_d() {
|
|
2598
2739
|
if (!summarize) return null;
|
|
2599
2740
|
if (!rowSummariesEnabled) return null;
|
|
2600
2741
|
const rows = allRows;
|
|
@@ -2746,15 +2887,41 @@ export function createSvGridController<
|
|
|
2746
2887
|
// is the reactive signal instead, bumped only when a height actually changes.
|
|
2747
2888
|
const measuredRowHeights = new Map<number, number>();
|
|
2748
2889
|
let autoRowHeightVersion = $state(0);
|
|
2749
|
-
// A per-row `rowHeight` function already supplies heights, so auto-measuring
|
|
2750
|
-
// would fight it. Fixed numbers are fine - they become the pre-measure estimate.
|
|
2751
|
-
const autoRowHeightOn = $derived(
|
|
2752
|
-
props.autoRowHeight === true && typeof props.rowHeight !== "function",
|
|
2753
|
-
);
|
|
2754
2890
|
const autoRowHeightFallback = $derived(
|
|
2755
2891
|
typeof props.rowHeight === "number" ? props.rowHeight : 30,
|
|
2756
2892
|
);
|
|
2757
2893
|
|
|
2894
|
+
// ----- Interactive row resize (`rowResize` prop) -----
|
|
2895
|
+
// Heights the user has dragged, by row index. A plain Map plus a version
|
|
2896
|
+
// counter, exactly like `measuredRowHeights` above and for the same reason:
|
|
2897
|
+
// the reset effect below has to read this collection to know whether there is
|
|
2898
|
+
// anything to clear, and if the collection were reactive that read would make
|
|
2899
|
+
// every resize re-trigger the reset and wipe the height it just stored.
|
|
2900
|
+
const rowResizeHeights = new Map<number, number>();
|
|
2901
|
+
let rowResizeVersion = $state(0);
|
|
2902
|
+
|
|
2903
|
+
function setRowResizeHeight(index: number, height: number): void {
|
|
2904
|
+
if (!rowResizeOn || !Number.isFinite(index) || height <= 0) return;
|
|
2905
|
+
rowResizeHeights.set(index, Math.round(height));
|
|
2906
|
+
rowResizeVersion += 1;
|
|
2907
|
+
}
|
|
2908
|
+
/** A user-dragged height for this row, or undefined when it has not been
|
|
2909
|
+
* resized. Read by the view AND by the virtualizer's `estimateSize`. */
|
|
2910
|
+
function rowResizeHeightPx(index: number): number | undefined {
|
|
2911
|
+
return rowResizeOn ? rowResizeHeights.get(index) : undefined;
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
// A row index means a different row once the data changes, so keeping the
|
|
2915
|
+
// heights would size new rows by the old ones - the same reasoning as the
|
|
2916
|
+
// measured-height reset below.
|
|
2917
|
+
$effect(() => {
|
|
2918
|
+
void allRows.length;
|
|
2919
|
+
void internalData;
|
|
2920
|
+
if (rowResizeHeights.size === 0) return;
|
|
2921
|
+
rowResizeHeights.clear();
|
|
2922
|
+
rowResizeVersion += 1;
|
|
2923
|
+
});
|
|
2924
|
+
|
|
2758
2925
|
/** Report a row's measured height. Called by the view's measuring action.
|
|
2759
2926
|
* Sub-pixel churn is ignored so a fractional layout can't loop. */
|
|
2760
2927
|
function reportRowHeight(index: number, height: number): void {
|
|
@@ -2825,11 +2992,20 @@ export function createSvGridController<
|
|
|
2825
2992
|
// the estimate for rows that have not rendered yet. Touch the version so a
|
|
2826
2993
|
// new measurement re-runs this effect (the Map itself is not reactive).
|
|
2827
2994
|
autoRowHeightVersion;
|
|
2828
|
-
|
|
2995
|
+
// Touch the version so a resize re-runs this effect: the virtualizer has to
|
|
2996
|
+
// know the new size or every row below the resized one is positioned
|
|
2997
|
+
// against a stale offset.
|
|
2998
|
+
rowResizeVersion;
|
|
2999
|
+
const base = autoRowHeightOn
|
|
2829
3000
|
? (index: number) => measuredRowHeights.get(index) ?? autoRowHeightFallback
|
|
2830
3001
|
: typeof rh === "function"
|
|
2831
3002
|
? rh
|
|
2832
3003
|
: (rh ?? 30);
|
|
3004
|
+
const estimateSize = rowResizeOn
|
|
3005
|
+
? (index: number) =>
|
|
3006
|
+
rowResizeHeights.get(index) ??
|
|
3007
|
+
(typeof base === "function" ? base(index) : base)
|
|
3008
|
+
: base;
|
|
2833
3009
|
virtualizer.setOptions({
|
|
2834
3010
|
count: allRows.length,
|
|
2835
3011
|
estimateSize,
|
|
@@ -2956,7 +3132,7 @@ export function createSvGridController<
|
|
|
2956
3132
|
|
|
2957
3133
|
|
|
2958
3134
|
|
|
2959
|
-
const headerSelectionState = $derived.by(()
|
|
3135
|
+
const headerSelectionState = $derived.by(function headerSelectionState_d() {
|
|
2960
3136
|
gridStateVersion;
|
|
2961
3137
|
const selectable = allRows.filter((row) => !isGroupRow(row));
|
|
2962
3138
|
if (!selectable.length) return "none";
|
|
@@ -2991,7 +3167,7 @@ export function createSvGridController<
|
|
|
2991
3167
|
* Returns `null` when fit scaling is not in effect (off, no room, total
|
|
2992
3168
|
* already >= target). Callers then fall back to the base width.
|
|
2993
3169
|
*/
|
|
2994
|
-
const fittedColumnWidths = $derived.by(()
|
|
3170
|
+
const fittedColumnWidths = $derived.by(function fittedColumnWidths_d() {
|
|
2995
3171
|
// Track viewport size (not scrollVersion) so we don't recompute on
|
|
2996
3172
|
// every scroll - only when the container actually resizes.
|
|
2997
3173
|
viewportVersion;
|
|
@@ -3057,8 +3233,6 @@ export function createSvGridController<
|
|
|
3057
3233
|
});
|
|
3058
3234
|
|
|
3059
3235
|
|
|
3060
|
-
let resizePendingWidth = 0;
|
|
3061
|
-
let resizeRaf: number | null = null;
|
|
3062
3236
|
|
|
3063
3237
|
|
|
3064
3238
|
|
|
@@ -3070,7 +3244,7 @@ export function createSvGridController<
|
|
|
3070
3244
|
/** Where the fill handle should render: the bottom-right cell of the
|
|
3071
3245
|
* selection range (or the active cell if there's no range). Returns
|
|
3072
3246
|
* null when cell selection is off or there is no anchored selection. */
|
|
3073
|
-
const fillHandleCell = $derived.by(()
|
|
3247
|
+
const fillHandleCell = $derived.by(function fillHandleCell_d() {
|
|
3074
3248
|
if (!(props.enableCellSelection ?? false)) return null;
|
|
3075
3249
|
const anchor = selectionRange.anchor;
|
|
3076
3250
|
const focus = selectionRange.focus;
|
|
@@ -3175,7 +3349,7 @@ export function createSvGridController<
|
|
|
3175
3349
|
* reused by both the facet UI and the row filter. Computing them lazily
|
|
3176
3350
|
* in a $derived means columns with no filter menu open and no active
|
|
3177
3351
|
* filter never pay the iteration cost. */
|
|
3178
|
-
const facetBucketsByColumn = $derived.by(()
|
|
3352
|
+
const facetBucketsByColumn = $derived.by(function facetBucketsByColumn_d() {
|
|
3179
3353
|
const map = new Map<string, Array<FacetBucket>>();
|
|
3180
3354
|
for (const column of allColumns) {
|
|
3181
3355
|
const meta = isBucketableColumn(column);
|
|
@@ -3220,22 +3394,60 @@ export function createSvGridController<
|
|
|
3220
3394
|
* column id (not just the open menu) so the filter row can drive it too.
|
|
3221
3395
|
*/
|
|
3222
3396
|
function facetValuesForColumn(columnId: string): Array<string> {
|
|
3397
|
+
return facetEntriesForColumn(columnId).map((entry) => entry.value);
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
/**
|
|
3401
|
+
* The values a column's checklist offers, with the row count behind each.
|
|
3402
|
+
*
|
|
3403
|
+
* Scoped to the rows passing every OTHER column's filters, not to the whole
|
|
3404
|
+
* dataset. Offering the full set means a checklist full of values that
|
|
3405
|
+
* cannot appear: filter Country to Germany and the City list still showed
|
|
3406
|
+
* every city on file, most of them a one-click route to an empty grid.
|
|
3407
|
+
* The column's OWN filter is excluded, so unticking a value never makes the
|
|
3408
|
+
* rest of the list vanish underneath the pointer.
|
|
3409
|
+
*/
|
|
3410
|
+
function facetEntriesForColumn(
|
|
3411
|
+
columnId: string,
|
|
3412
|
+
): Array<{ value: string; count: number | null }> {
|
|
3223
3413
|
if (!columnId) return [];
|
|
3224
|
-
// Server-provided distinct values win (fetched + cached above).
|
|
3225
|
-
|
|
3414
|
+
// Server-provided distinct values win (fetched + cached above). The grid
|
|
3415
|
+
// has not seen the rows behind them, so there is nothing to count.
|
|
3416
|
+
if (props.serverFilterValues)
|
|
3417
|
+
return (serverFacetValues[columnId] ?? []).map((value) => ({
|
|
3418
|
+
value,
|
|
3419
|
+
count: null,
|
|
3420
|
+
}));
|
|
3226
3421
|
const column = allColumns.find((entry) => entry.id === columnId);
|
|
3227
3422
|
if (!column) return [];
|
|
3228
|
-
|
|
3423
|
+
|
|
3424
|
+
const scope = applyColumnFilters(grid.getRowModel().rows, columnId);
|
|
3229
3425
|
const buckets = facetBucketsByColumn.get(columnId);
|
|
3230
|
-
if (buckets)
|
|
3426
|
+
if (buckets) {
|
|
3427
|
+
// Range-bucketed facets for numeric / date columns with many values.
|
|
3428
|
+
const counts = new Map<string, number>(buckets.map((b) => [b.label, 0]));
|
|
3429
|
+
for (const row of scope) {
|
|
3430
|
+
const num = rawToNumber(getColumnBaseValue(row, column), buckets[0]!.isDate);
|
|
3431
|
+
if (!Number.isFinite(num)) continue;
|
|
3432
|
+
for (const bucket of buckets) {
|
|
3433
|
+
if (isInBucket(num, bucket)) {
|
|
3434
|
+
counts.set(bucket.label, (counts.get(bucket.label) ?? 0) + 1);
|
|
3435
|
+
break;
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
return buckets.map((b) => ({ value: b.label, count: counts.get(b.label) ?? 0 }));
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3231
3442
|
// Default: distinct-value facets.
|
|
3232
|
-
const
|
|
3233
|
-
for (const
|
|
3234
|
-
|
|
3443
|
+
const counts = new Map<string, number>();
|
|
3444
|
+
for (const row of scope) {
|
|
3445
|
+
const value = String(getColumnBaseValue(row, column) ?? "");
|
|
3446
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
3235
3447
|
}
|
|
3236
|
-
return Array.from(
|
|
3237
|
-
a.localeCompare(b, undefined, { numeric: true })
|
|
3238
|
-
|
|
3448
|
+
return Array.from(counts.keys())
|
|
3449
|
+
.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }))
|
|
3450
|
+
.map((value) => ({ value, count: counts.get(value) ?? 0 }));
|
|
3239
3451
|
}
|
|
3240
3452
|
|
|
3241
3453
|
/**
|
|
@@ -3257,12 +3469,14 @@ export function createSvGridController<
|
|
|
3257
3469
|
// out a fresh empty array/set on every read.
|
|
3258
3470
|
const EMPTY_FACETS: Array<string> = [];
|
|
3259
3471
|
const EMPTY_FACET_SET: ReadonlySet<string> = new Set<string>();
|
|
3472
|
+
const EMPTY_FACET_ENTRIES: Array<{ value: string; count: number | null }> = [];
|
|
3260
3473
|
let facetCache: {
|
|
3261
3474
|
columnId: string;
|
|
3262
3475
|
source: Array<string> | null;
|
|
3263
3476
|
values: Array<string>;
|
|
3477
|
+
counts: Map<string, number>;
|
|
3264
3478
|
} | null = null;
|
|
3265
|
-
const columnMenuFacetValues = $derived.by(()
|
|
3479
|
+
const columnMenuFacetValues = $derived.by(function columnMenuFacetValues_d() {
|
|
3266
3480
|
// The funnel popover drives via `filterMenuFor`; the column menu's Filter
|
|
3267
3481
|
// tab drives via `columnMenuFor`. Support whichever is open.
|
|
3268
3482
|
const columnId = filterMenuFor ?? columnMenuFor;
|
|
@@ -3276,10 +3490,14 @@ export function createSvGridController<
|
|
|
3276
3490
|
if (facetCache?.columnId === columnId && facetCache.source === source) {
|
|
3277
3491
|
return facetCache.values;
|
|
3278
3492
|
}
|
|
3493
|
+
const entries = untrack(() => facetEntriesForColumn(columnId));
|
|
3279
3494
|
facetCache = {
|
|
3280
3495
|
columnId,
|
|
3281
3496
|
source,
|
|
3282
|
-
values:
|
|
3497
|
+
values: entries.map((e) => e.value),
|
|
3498
|
+
counts: new Map(
|
|
3499
|
+
entries.flatMap((e) => (e.count == null ? [] : [[e.value, e.count] as const])),
|
|
3500
|
+
),
|
|
3283
3501
|
};
|
|
3284
3502
|
return facetCache.values;
|
|
3285
3503
|
});
|
|
@@ -3292,7 +3510,7 @@ export function createSvGridController<
|
|
|
3292
3510
|
if (!(filterMenuFor ?? columnMenuFor)) facetCache = null;
|
|
3293
3511
|
});
|
|
3294
3512
|
|
|
3295
|
-
const columnMenuVisibleFacets = $derived.by(()
|
|
3513
|
+
const columnMenuVisibleFacets = $derived.by(function columnMenuVisibleFacets_d() {
|
|
3296
3514
|
const query = columnMenuSearch.trim().toLowerCase();
|
|
3297
3515
|
if (!query) return columnMenuFacetValues;
|
|
3298
3516
|
return columnMenuFacetValues.filter((value) =>
|
|
@@ -3301,44 +3519,73 @@ export function createSvGridController<
|
|
|
3301
3519
|
});
|
|
3302
3520
|
|
|
3303
3521
|
/** Visible facets as listbox options ((Blanks) label for the empty value). */
|
|
3304
|
-
const columnMenuFacetOptions = $derived(
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3522
|
+
const columnMenuFacetOptions = $derived.by(function columnMenuFacetOptions_d() {
|
|
3523
|
+
// Reading the values first materializes the snapshot (and its counts).
|
|
3524
|
+
const values = columnMenuVisibleFacets;
|
|
3525
|
+
const counts = facetCache?.counts;
|
|
3526
|
+
return values.map((value) => {
|
|
3527
|
+
const count = counts?.get(value);
|
|
3528
|
+
return {
|
|
3529
|
+
value,
|
|
3530
|
+
label: value === "" ? "(Blanks)" : value,
|
|
3531
|
+
// How many rows this value would keep, given the other columns'
|
|
3532
|
+
// filters. Absent in server-values mode, where the grid never sees
|
|
3533
|
+
// the rows behind the list.
|
|
3534
|
+
hint: count == null ? undefined : String(count),
|
|
3535
|
+
};
|
|
3536
|
+
});
|
|
3537
|
+
});
|
|
3310
3538
|
|
|
3311
3539
|
/**
|
|
3312
3540
|
* Checked facets for the open menu. An ABSENT `valueFilters` entry means
|
|
3313
3541
|
* "everything checked", so that case materializes the set once here rather
|
|
3314
3542
|
* than every consumer re-deriving it.
|
|
3315
3543
|
*/
|
|
3316
|
-
const columnMenuSelectedFacets = $derived.by(()
|
|
3544
|
+
const columnMenuSelectedFacets = $derived.by(function columnMenuSelectedFacets_d() {
|
|
3317
3545
|
const columnId = filterMenuFor ?? columnMenuFor;
|
|
3318
3546
|
if (!columnId) return EMPTY_FACET_SET;
|
|
3319
3547
|
return valueFilters[columnId] ?? new Set(columnMenuFacetValues);
|
|
3320
3548
|
});
|
|
3321
3549
|
|
|
3550
|
+
/**
|
|
3551
|
+
* The offered values, in their own derived so the QUERY does not re-run the
|
|
3552
|
+
* scan. It used to sit in `inSuggestValues` beside the `inSuggestQuery`
|
|
3553
|
+
* read, so every keystroke in a chip input rebuilt the distinct-value set -
|
|
3554
|
+
* and once the scan started narrowing itself against the other columns'
|
|
3555
|
+
* filters, every keystroke ran the filter pipeline too. Split out, it
|
|
3556
|
+
* re-runs only when the dropdown moves to another column.
|
|
3557
|
+
*/
|
|
3558
|
+
const inSuggestEntries = $derived.by(function inSuggestEntries_d() {
|
|
3559
|
+
const columnId = inSuggestFor;
|
|
3560
|
+
if (!columnId) return EMPTY_FACET_ENTRIES;
|
|
3561
|
+
return untrack(() => facetEntriesForColumn(columnId));
|
|
3562
|
+
});
|
|
3563
|
+
|
|
3322
3564
|
// Distinct values offered in the `in` / `notIn` suggestions dropdown for the
|
|
3323
3565
|
// active chip input, narrowed by whatever the user has typed. Uncapped - the
|
|
3324
3566
|
// dropdown windows its rows, so a high-cardinality column costs a list of
|
|
3325
3567
|
// strings, not thousands of nodes.
|
|
3326
|
-
const inSuggestValues = $derived.by(()
|
|
3327
|
-
if (!inSuggestFor) return EMPTY_FACETS;
|
|
3568
|
+
const inSuggestValues = $derived.by(function inSuggestValues_d() {
|
|
3328
3569
|
const query = inSuggestQuery.trim().toLowerCase();
|
|
3329
|
-
const all =
|
|
3330
|
-
return
|
|
3331
|
-
? all.filter((
|
|
3332
|
-
|
|
3570
|
+
const all = inSuggestEntries;
|
|
3571
|
+
return (
|
|
3572
|
+
query ? all.filter((e) => e.value.toLowerCase().includes(query)) : all
|
|
3573
|
+
).map((e) => e.value);
|
|
3333
3574
|
});
|
|
3334
3575
|
|
|
3335
3576
|
/** `in` / `notIn` suggestions as listbox options. */
|
|
3336
|
-
const inSuggestOptions = $derived(
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3577
|
+
const inSuggestOptions = $derived.by(function inSuggestOptions_d() {
|
|
3578
|
+
const query = inSuggestQuery.trim().toLowerCase();
|
|
3579
|
+
const shown = query
|
|
3580
|
+
? inSuggestEntries.filter((e) => e.value.toLowerCase().includes(query))
|
|
3581
|
+
: inSuggestEntries;
|
|
3582
|
+
return shown.map((e) => ({
|
|
3583
|
+
value: e.value,
|
|
3584
|
+
label: e.value === "" ? "(Blanks)" : e.value,
|
|
3585
|
+
// Same match count the menu's checklist shows.
|
|
3586
|
+
hint: e.count == null ? undefined : String(e.count),
|
|
3587
|
+
}));
|
|
3588
|
+
});
|
|
3342
3589
|
|
|
3343
3590
|
|
|
3344
3591
|
|
|
@@ -3405,6 +3652,10 @@ export function createSvGridController<
|
|
|
3405
3652
|
set isDraggingSelection(v) { isDraggingSelection = v as never; },
|
|
3406
3653
|
get fillDrag() { return fillDrag; },
|
|
3407
3654
|
set fillDrag(v) { fillDrag = v as never; },
|
|
3655
|
+
get moveDrag() { return moveDrag; },
|
|
3656
|
+
set moveDrag(v) { moveDrag = v as never; },
|
|
3657
|
+
get moveGrabHover() { return moveGrabHover; },
|
|
3658
|
+
set moveGrabHover(v) { moveGrabHover = v as never; },
|
|
3408
3659
|
get activeAtPointerDown() { return activeAtPointerDown; },
|
|
3409
3660
|
set activeAtPointerDown(v) { activeAtPointerDown = v as never; },
|
|
3410
3661
|
get editingCell() { return editingCell; },
|
|
@@ -3441,12 +3692,6 @@ export function createSvGridController<
|
|
|
3441
3692
|
set editorSelectAll(v) { editorSelectAll = v as never; },
|
|
3442
3693
|
get columnWidths() { return columnWidths; },
|
|
3443
3694
|
set columnWidths(v) { columnWidths = v as never; },
|
|
3444
|
-
get resizingColumnId() { return resizingColumnId; },
|
|
3445
|
-
set resizingColumnId(v) { resizingColumnId = v as never; },
|
|
3446
|
-
get resizeStartX() { return resizeStartX; },
|
|
3447
|
-
set resizeStartX(v) { resizeStartX = v as never; },
|
|
3448
|
-
get resizeStartWidth() { return resizeStartWidth; },
|
|
3449
|
-
set resizeStartWidth(v) { resizeStartWidth = v as never; },
|
|
3450
3695
|
get MIN_COLUMN_WIDTH() { return MIN_COLUMN_WIDTH; },
|
|
3451
3696
|
get columnPinning() { return columnPinning; },
|
|
3452
3697
|
set columnPinning(v) { columnPinning = v as never; },
|
|
@@ -3514,6 +3759,7 @@ export function createSvGridController<
|
|
|
3514
3759
|
get showRowSelectionEffective() { return showRowSelectionEffective; },
|
|
3515
3760
|
get rowSummariesEnabled() { return rowSummariesEnabled; },
|
|
3516
3761
|
get enableCellSelectionEffective() { return enableCellSelectionEffective; },
|
|
3762
|
+
get moveCellsEffective() { return moveCellsEffective; },
|
|
3517
3763
|
get flushScheduledScrollSync() { return flushScheduledScrollSync; },
|
|
3518
3764
|
get scheduleScrollSync() { return scheduleScrollSync; },
|
|
3519
3765
|
get internalData() { return internalData; },
|
|
@@ -3595,6 +3841,12 @@ export function createSvGridController<
|
|
|
3595
3841
|
get allRowsBeforePagination() { return allRowsBeforePagination; },
|
|
3596
3842
|
get allRows() { return allRows; },
|
|
3597
3843
|
get rowSelectionState() { return rowSelectionState; },
|
|
3844
|
+
get selectionBarOn() { return selectionBarOn; },
|
|
3845
|
+
get selectionBarActions() { return selectionBarActions; },
|
|
3846
|
+
get selectionBarPosition() { return selectionBarPosition; },
|
|
3847
|
+
get selectionBarMaxVisible() { return selectionBarMaxVisible; },
|
|
3848
|
+
get selectionBarHideClear() { return selectionBarHideClear; },
|
|
3849
|
+
get selectionBarTarget() { return selectionBarTarget; },
|
|
3598
3850
|
get lastSelectionSerialized() { return lastSelectionSerialized; },
|
|
3599
3851
|
set lastSelectionSerialized(v) { lastSelectionSerialized = v as never; },
|
|
3600
3852
|
get lastCellRangeSerialized() { return lastCellRangeSerialized; },
|
|
@@ -3765,6 +4017,15 @@ export function createSvGridController<
|
|
|
3765
4017
|
autoRowHeightVersion;
|
|
3766
4018
|
return (index: number): number | undefined => measuredRowHeights.get(index);
|
|
3767
4019
|
},
|
|
4020
|
+
/** True when the `rowResize` prop is on and nothing overrides it. */
|
|
4021
|
+
get rowResizeOn() { return rowResizeOn; },
|
|
4022
|
+
/** Record the height the user dragged a row to. */
|
|
4023
|
+
get setRowResizeHeight() { return setRowResizeHeight; },
|
|
4024
|
+
/** A row's dragged height, or undefined when it has not been resized. */
|
|
4025
|
+
get rowResizeHeightPx() {
|
|
4026
|
+
rowResizeVersion;
|
|
4027
|
+
return rowResizeHeightPx;
|
|
4028
|
+
},
|
|
3768
4029
|
get virtualRowTotalSize() { return virtualRowTotalSize; },
|
|
3769
4030
|
get virtualRowStart() { return virtualRowStart; },
|
|
3770
4031
|
get virtualRowEnd() { return virtualRowEnd; },
|
|
@@ -3827,13 +4088,6 @@ export function createSvGridController<
|
|
|
3827
4088
|
get getColumnBaseWidth() { return getColumnBaseWidth; },
|
|
3828
4089
|
get fittedColumnWidths() { return fittedColumnWidths; },
|
|
3829
4090
|
get getColumnWidth() { return getColumnWidth; },
|
|
3830
|
-
get resizePendingWidth() { return resizePendingWidth; },
|
|
3831
|
-
set resizePendingWidth(v) { resizePendingWidth = v as never; },
|
|
3832
|
-
get resizeRaf() { return resizeRaf; },
|
|
3833
|
-
set resizeRaf(v) { resizeRaf = v as never; },
|
|
3834
|
-
get startColumnResize() { return startColumnResize; },
|
|
3835
|
-
get onColumnResizeMove() { return onColumnResizeMove; },
|
|
3836
|
-
get endColumnResize() { return endColumnResize; },
|
|
3837
4091
|
get setSelection() { return setSelection; },
|
|
3838
4092
|
get extendSelection() { return extendSelection; },
|
|
3839
4093
|
get isCellInSelectedRange() { return isCellInSelectedRange; },
|
|
@@ -3841,8 +4095,18 @@ export function createSvGridController<
|
|
|
3841
4095
|
get getSelectionRects() { return getSelectionRects; },
|
|
3842
4096
|
get fillHandleCell() { return fillHandleCell; },
|
|
3843
4097
|
get isInFillPreview() { return isInFillPreview; },
|
|
4098
|
+
get dragPreviewRect() { return dragPreviewRect; },
|
|
3844
4099
|
get fillMarqueeEdges() { return fillMarqueeEdges; },
|
|
3845
4100
|
get findColumnById() { return findColumnById; },
|
|
4101
|
+
/**
|
|
4102
|
+
* Whether this column may be resized by the user, from its
|
|
4103
|
+
* `ColumnDef.resizable`. Defaults to true, so a column only opts out by
|
|
4104
|
+
* saying so; the grid-wide `columnResize` prop gates all of them above
|
|
4105
|
+
* this. Used by the resize action and by the column menu's Autosize item.
|
|
4106
|
+
*/
|
|
4107
|
+
columnResizable(columnId: string): boolean {
|
|
4108
|
+
return findColumnById(columnId)?.columnDef?.resizable !== false;
|
|
4109
|
+
},
|
|
3846
4110
|
get readCellRaw() { return readCellRaw; },
|
|
3847
4111
|
get writeCellRaw() { return writeCellRaw; },
|
|
3848
4112
|
get applyFillPattern() { return applyFillPattern; },
|
|
@@ -3850,6 +4114,14 @@ export function createSvGridController<
|
|
|
3850
4114
|
get startFillDrag() { return startFillDrag; },
|
|
3851
4115
|
get onFillPointerMove() { return onFillPointerMove; },
|
|
3852
4116
|
get onFillPointerUp() { return onFillPointerUp; },
|
|
4117
|
+
get isOnMoveGrabStrip() { return isOnMoveGrabStrip; },
|
|
4118
|
+
get startMoveDrag() { return startMoveDrag; },
|
|
4119
|
+
get onMovePointerMove() { return onMovePointerMove; },
|
|
4120
|
+
get onMovePointerUp() { return onMovePointerUp; },
|
|
4121
|
+
get trackEdgeScroll() { return trackEdgeScroll; },
|
|
4122
|
+
get stopEdgeScroll() { return stopEdgeScroll; },
|
|
4123
|
+
get applyMoveRange() { return applyMoveRange; },
|
|
4124
|
+
get moveDestRect() { return moveDestRect; },
|
|
3853
4125
|
get toggleBooleanCell() { return toggleBooleanCell; },
|
|
3854
4126
|
get onCellPointerDown() { return onCellPointerDown; },
|
|
3855
4127
|
get onCellPointerEnter() { return onCellPointerEnter; },
|
|
@@ -3952,12 +4224,12 @@ export function createSvGridController<
|
|
|
3952
4224
|
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<TFeatures, TData>(ctx);
|
|
3953
4225
|
const { cellConditionalFormat, computeRowClass, computeCellClass, computeCellTooltip, computeCellValidity, computeCellNote, getColumnEditorOptions, areEditorOptionsLoading, formatListCellValue, formatCellValue, formatPinnedValue, computePinnedCellClass } = createCellRender<TFeatures, TData>(ctx);
|
|
3954
4226
|
const { isCellEditable, isCellEditableAt, getRowColumnValue, getCellDisplayValue, startEditingWithChar, startEditing, stopEditing, startFullRowEdit, setFullRowDraft, commitFullRowEdit, cancelFullRowEdit, saveEditingCell, applyHistoryStep, updateEditingCellValue, onEditorKeyDown, commitAndMoveByTab, focusOnMount, onCellDoubleClick, pasteFromClipboard, onGridPaste } = createEditing<TFeatures, TData>(ctx);
|
|
3955
|
-
const { isRowSelected, toggleRowSelectionById, toggleSelectAllRows, setActiveCell, scrollActiveCellIntoView, setSelection, extendSelection, isCellInSelectedRange, getCellRangeEdges, getSelectionRects, isInFillPreview, fillMarqueeEdges, findColumnById, onCellPointerDown, onCellPointerEnter, endDragSelection, onWindowPointerMove, onCellClick, emitCellDoubleClick } = createSelection<TFeatures, TData>(ctx);
|
|
3956
|
-
const { cellPinStyle, isColumnPinned, getCurrentColumnOrder, emitColumnOrder, setColumnOrderInternal, applyColumnDrop, onColumnHeaderDragStart, onColumnHeaderDragOver, onColumnHeaderDragLeave, onColumnHeaderDrop, onColumnHeaderDragEnd, pinColumnLeft, pinColumnRight, unpinColumn, toggleColumnVisibleInPanel, moveColumnInPanel, toggleGroupInPanel, getColumnBaseWidth, getColumnWidth,
|
|
4227
|
+
const { isRowSelected, toggleRowSelectionById, toggleSelectAllRows, setActiveCell, scrollActiveCellIntoView, setSelection, extendSelection, isCellInSelectedRange, getCellRangeEdges, getSelectionRects, isInFillPreview, dragPreviewRect, fillMarqueeEdges, findColumnById, onCellPointerDown, onCellPointerEnter, endDragSelection, onWindowPointerMove, onCellClick, emitCellDoubleClick } = createSelection<TFeatures, TData>(ctx);
|
|
4228
|
+
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<TFeatures, TData>(ctx);
|
|
3957
4229
|
const { onRowDragStart, onRowDragOver, onRowDragLeave, onRowDrop, onRowsContainerDragOver, onRowsContainerDrop, onRowDragEnd, onRowPointerDown, destroyRowDrag } = createRowDrag<TFeatures, TData>(ctx);
|
|
3958
4230
|
const { register: registerAlignedGrid, broadcastScroll: broadcastAlignedScroll, broadcastWidths: broadcastAlignedWidths } = createAlignedGrids<TFeatures, TData>(ctx);
|
|
3959
4231
|
const { buildApi } = createGridApi<TFeatures, TData>(ctx);
|
|
3960
|
-
const { readCellRaw, writeCellRaw, applyFillPattern, clearSelectedCellValues, startFillDrag, onFillPointerMove, onFillPointerUp, toggleBooleanCell, copySelectionToClipboard, clearSelectedCells, cutSelectionToClipboard } = createClipboard(ctx);
|
|
4232
|
+
const { readCellRaw, writeCellRaw, applyFillPattern, clearSelectedCellValues, startFillDrag, onFillPointerMove, onFillPointerUp, isOnMoveGrabStrip, startMoveDrag, onMovePointerMove, onMovePointerUp, applyMoveRange, moveDestRect, trackEdgeScroll, stopEdgeScroll, toggleBooleanCell, copySelectionToClipboard, clearSelectedCells, cutSelectionToClipboard } = createClipboard(ctx);
|
|
3961
4233
|
|
|
3962
4234
|
// Dev-time configuration checks. Silent misconfiguration was the grid's
|
|
3963
4235
|
// biggest usability gap - a misspelled `field` rendered a column of blank
|
|
@@ -4030,7 +4302,6 @@ export function createSvGridController<
|
|
|
4030
4302
|
// this grid still owns (#68). Each of these is a no-op when idle.
|
|
4031
4303
|
$effect(() => {
|
|
4032
4304
|
return () => {
|
|
4033
|
-
endColumnResize();
|
|
4034
4305
|
hideTooltip();
|
|
4035
4306
|
destroyRowDrag();
|
|
4036
4307
|
};
|