@svgrid/grid 3.0.3 → 4.0.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/README.md +5 -4
- package/dist/GridMenus.svelte +44 -2
- package/dist/SvChartMenu.svelte +69 -0
- package/dist/SvChartMenu.svelte.d.ts +11 -0
- package/dist/SvChartPanes.svelte +113 -0
- package/dist/SvChartPanes.svelte.d.ts +20 -0
- package/dist/SvGrid.controller.svelte.d.ts +122 -41
- package/dist/SvGrid.controller.svelte.js +845 -207
- package/dist/SvGrid.css +178 -2
- package/dist/SvGrid.svelte +467 -516
- package/dist/SvGrid.types.d.ts +315 -12
- package/dist/SvGridCellEditor.svelte +17 -0
- package/dist/SvGridChart.svelte +2257 -329
- package/dist/SvGridChart.svelte.d.ts +6 -106
- package/dist/SvGridChart.types.d.ts +338 -0
- package/dist/SvGridChart.types.js +1 -0
- package/dist/SvGridChartBuilder.svelte +323 -0
- package/dist/SvGridChartBuilder.svelte.d.ts +34 -0
- package/dist/SvGridChartPanel.svelte +221 -275
- package/dist/SvGridChartPickers.svelte +417 -0
- package/dist/SvGridChartPickers.svelte.d.ts +70 -0
- package/dist/SvGridChartView.svelte +13 -14
- package/dist/SvModal.svelte +7 -1
- package/dist/SvModal.svelte.d.ts +5 -0
- package/dist/ai.d.ts +40 -8
- package/dist/ai.js +135 -24
- package/dist/aria.d.ts +11 -0
- package/dist/build-api.js +119 -28
- package/dist/cdn/GridMenus-CLI93jke.js +635 -0
- package/dist/cdn/{GridMenus-DhdPF700.js → GridMenus-CwooL3QA.js} +240 -207
- package/dist/cdn/SvChartMenu-Bl6PBbkT.js +58 -0
- package/dist/cdn/SvChartMenu-FBSMINA6.js +59 -0
- package/dist/cdn/{SvDateRangeInput-DMLKmGEc.js → SvDateRangeInput-BeU_TkbQ.js} +15 -14
- package/dist/cdn/{SvDateRangeInput-CaOuMs8O.js → SvDateRangeInput-yX8vzleW.js} +38 -37
- package/dist/cdn/{SvDateTimePicker-sonaH0oh.js → SvDateTimePicker-DQwt4UAs.js} +139 -180
- package/dist/cdn/SvDateTimePicker-vNU6bZ-q.js +775 -0
- package/dist/cdn/{SvGridCellEditor-CjEJMJKc.js → SvGridCellEditor-B1p-vCK5.js} +188 -178
- package/dist/cdn/{SvGridCellEditor-BWnTi2N7.js → SvGridCellEditor-D_0q4xAu.js} +98 -88
- package/dist/cdn/SvGridChart-C3EWAZaM.js +3574 -0
- package/dist/cdn/SvGridChart-DzLkSwCH.js +3572 -0
- package/dist/cdn/SvGridChartBuilder-BE2T1ykB.js +783 -0
- package/dist/cdn/SvGridChartBuilder-CfII62sZ.js +784 -0
- package/dist/cdn/SvGridChartPanel-CCX5_Wgd.js +675 -0
- package/dist/cdn/SvGridChartPanel-xf4sWVTo.js +699 -0
- package/dist/cdn/SvGridChartView-CfuXmY5I.js +48 -0
- package/dist/cdn/SvGridChartView-DYwabQWj.js +47 -0
- package/dist/cdn/SvMenuList-CLN8OuIK.js +452 -0
- package/dist/cdn/SvMenuList-DkGhNKjf.js +451 -0
- package/dist/cdn/SvModal-BAE-pjZX.js +396 -0
- package/dist/cdn/SvModal-CIZWCcad.js +397 -0
- package/dist/cdn/chart-Dz7SqMXH.js +4341 -0
- package/dist/cdn/chart-export-BXqueeWJ.js +135 -0
- package/dist/cdn/chart-export-pdf-DONFUnNY.js +110 -0
- package/dist/cdn/chart-panel-messages-CUbf2R4i.js +1052 -0
- package/dist/cdn/chart-panel-messages-CmNrMdsr.js +1053 -0
- package/dist/cdn/chart-summary-BJW_tg_X.js +138 -0
- package/dist/cdn/chart-trend-CaN9mDEV.js +50 -0
- package/dist/cdn/chart-validate-HcOVFAfJ.js +188 -0
- package/dist/cdn/{column-resize-DsfNXMom.js → column-resize-DpmOLfRp.js} +3 -3
- package/dist/cdn/{date-format-BnnHlqGw.js → date-format-CtquV-p3.js} +583 -603
- package/dist/cdn/{date-format-BNii4zeD.js → date-format-D6KFzU_W.js} +347 -367
- package/dist/cdn/dismissable-DAHetSNk.js +44 -0
- package/dist/cdn/editor-contract-LXgQJAgd.js +22 -0
- package/dist/cdn/focus-trap-BBOQYBma.js +65 -0
- package/dist/cdn/{row-resize-BRcimkUT.js → row-resize-niQCp040.js} +44 -36
- package/dist/cdn/server-block-cache-CGoWz-87.js +237 -0
- package/dist/cdn/{src-D6XvA1Ij.js → src-B6HXdfHY.js} +10448 -10049
- package/dist/cdn/{src-Bugf5XlI.js → src-C6Iowdvs.js} +6908 -6509
- package/dist/cdn/svgrid.js +27 -15
- package/dist/cdn/svgrid.svelte-external.js +27 -15
- package/dist/cdn/validate-CchXzyrX.js +76 -0
- package/dist/chart-axes.d.ts +53 -0
- package/dist/chart-axes.js +351 -0
- package/dist/chart-cartesian.d.ts +88 -0
- package/dist/chart-cartesian.js +1862 -0
- package/dist/chart-decimate.d.ts +51 -0
- package/dist/chart-decimate.js +199 -0
- package/dist/chart-export-pdf.d.ts +47 -0
- package/dist/chart-export-pdf.js +187 -0
- package/dist/chart-export.d.ts +2 -1
- package/dist/chart-export.js +18 -3
- package/dist/chart-financial.d.ts +91 -0
- package/dist/chart-financial.js +175 -0
- package/dist/chart-flow.d.ts +10 -0
- package/dist/chart-flow.js +298 -0
- package/dist/chart-format.d.ts +18 -0
- package/dist/chart-format.js +65 -0
- package/dist/chart-grid.d.ts +8 -0
- package/dist/chart-grid.js +190 -0
- package/dist/chart-hierarchy.d.ts +17 -0
- package/dist/chart-hierarchy.js +146 -0
- package/dist/chart-indicators.d.ts +115 -0
- package/dist/chart-indicators.js +389 -0
- package/dist/chart-messages.d.ts +95 -0
- package/dist/chart-messages.js +92 -0
- package/dist/chart-motion.d.ts +16 -0
- package/dist/chart-motion.js +141 -0
- package/dist/chart-panel-messages.d.ts +265 -0
- package/dist/chart-panel-messages.js +285 -0
- package/dist/chart-pivot.d.ts +87 -0
- package/dist/chart-pivot.js +156 -0
- package/dist/chart-polar.d.ts +25 -0
- package/dist/chart-polar.js +700 -0
- package/dist/chart-samples.d.ts +16 -0
- package/dist/chart-samples.js +153 -0
- package/dist/chart-scale.d.ts +113 -0
- package/dist/chart-scale.js +405 -0
- package/dist/chart-stats.d.ts +111 -0
- package/dist/chart-stats.js +419 -0
- package/dist/chart-stream.d.ts +45 -0
- package/dist/chart-stream.js +91 -0
- package/dist/chart-summary.d.ts +27 -0
- package/dist/chart-summary.js +205 -0
- package/dist/chart-sync.svelte.d.ts +32 -0
- package/dist/chart-sync.svelte.js +23 -0
- package/dist/chart-table.d.ts +35 -0
- package/dist/chart-table.js +165 -0
- package/dist/chart-trend.d.ts +19 -0
- package/dist/chart-trend.js +61 -0
- package/dist/chart-types.d.ts +1456 -0
- package/dist/chart-types.js +6 -0
- package/dist/chart-validate.d.ts +43 -0
- package/dist/chart-validate.js +272 -0
- package/dist/chart-zoom.d.ts +40 -0
- package/dist/chart-zoom.js +145 -0
- package/dist/chart.d.ts +186 -865
- package/dist/chart.js +460 -2204
- package/dist/clipboard.d.ts +1 -0
- package/dist/clipboard.js +137 -18
- package/dist/column-resize.d.ts +3 -0
- package/dist/column-resize.js +4 -1
- package/dist/columns.js +3 -0
- package/dist/command-context.d.ts +19 -0
- package/dist/command-context.js +116 -0
- package/dist/core.d.ts +7 -0
- package/dist/core.js +36 -13
- package/dist/createPopoverSelect.svelte.d.ts +2 -2
- package/dist/editing.d.ts +2 -8
- package/dist/editing.js +272 -33
- package/dist/fill-patterns.d.ts +0 -5
- package/dist/fill-patterns.js +56 -2
- package/dist/grid-icons.d.ts +2 -2
- package/dist/grid-icons.js +2 -2
- package/dist/grid-messages.d.ts +12 -3
- package/dist/grid-messages.js +7 -0
- package/dist/history.d.ts +83 -0
- package/dist/history.js +133 -0
- package/dist/index.d.ts +27 -10
- package/dist/index.js +22 -5
- package/dist/keyboard-handlers.js +88 -31
- package/dist/keyboard.d.ts +55 -0
- package/dist/keyboard.js +132 -0
- package/dist/menus.d.ts +3 -1
- package/dist/menus.js +7 -2
- package/dist/merges.d.ts +97 -0
- package/dist/merges.js +147 -0
- package/dist/row-model.d.ts +133 -0
- package/dist/row-model.js +16 -0
- package/dist/row-resize.d.ts +3 -0
- package/dist/row-resize.js +14 -0
- package/dist/scroll-sync.d.ts +1 -0
- package/dist/scroll-sync.js +24 -0
- package/dist/selection.d.ts +18 -1
- package/dist/selection.js +138 -18
- package/dist/server-block-cache.d.ts +214 -0
- package/dist/server-block-cache.js +531 -0
- package/dist/server-data-source.d.ts +240 -6
- package/dist/server-data-source.js +220 -20
- package/dist/server.d.ts +13 -0
- package/dist/server.js +13 -0
- package/dist/shortcut-registry.d.ts +145 -0
- package/dist/shortcut-registry.js +44 -0
- package/dist/svgrid-wrapper.types.d.ts +213 -22
- package/dist/validate.d.ts +2 -0
- package/dist/validate.js +8 -3
- package/dist/virtualization/virtualizer.js +38 -17
- package/package.json +11 -1
- package/src/GridMenus.svelte +44 -2
- package/src/SvChartMenu.svelte +69 -0
- package/src/SvChartPanes.svelte +113 -0
- package/src/SvChartPanes.test.ts +66 -0
- package/src/SvGrid.controller.svelte.ts +818 -179
- package/src/SvGrid.css +178 -2
- package/src/SvGrid.svelte +467 -516
- package/src/SvGrid.types.ts +293 -11
- package/src/SvGridCellEditor.svelte +17 -0
- package/src/SvGridChart.svelte +2257 -329
- package/src/SvGridChart.test.ts +1419 -1
- package/src/SvGridChart.types.ts +336 -0
- package/src/SvGridChartBuilder.svelte +323 -0
- package/src/SvGridChartPanel.svelte +221 -275
- package/src/SvGridChartPickers.svelte +417 -0
- package/src/SvGridChartView.svelte +13 -14
- package/src/SvModal.svelte +7 -1
- package/src/SvModal.test.ts +9 -0
- package/src/ai.test.ts +57 -2
- package/src/ai.ts +166 -22
- package/src/aria.d.ts +11 -0
- package/src/build-api.coverage.test.ts +184 -0
- package/src/build-api.ts +118 -22
- package/src/chart-axes.test.ts +573 -0
- package/src/chart-axes.ts +342 -0
- package/src/chart-cartesian.ts +1863 -0
- package/src/chart-decimate.test.ts +249 -0
- package/src/chart-decimate.ts +186 -0
- package/src/chart-export-pdf.test.ts +91 -0
- package/src/chart-export-pdf.ts +207 -0
- package/src/chart-export.test.ts +14 -0
- package/src/chart-export.ts +13 -3
- package/src/chart-financial.ts +208 -0
- package/src/chart-flow.ts +286 -0
- package/src/chart-format.ts +67 -0
- package/src/chart-grid.ts +179 -0
- package/src/chart-hierarchy.ts +135 -0
- package/src/chart-indicators.test.ts +443 -0
- package/src/chart-indicators.ts +384 -0
- package/src/chart-messages.test.ts +21 -0
- package/src/chart-messages.ts +184 -0
- package/src/chart-motion.test.ts +71 -0
- package/src/chart-motion.ts +148 -0
- package/src/chart-panel-messages.test.ts +29 -0
- package/src/chart-panel-messages.ts +550 -0
- package/src/chart-pivot.test.ts +54 -0
- package/src/chart-pivot.ts +225 -0
- package/src/chart-polar.ts +674 -0
- package/src/chart-samples.ts +106 -0
- package/src/chart-scale.ts +394 -0
- package/src/chart-series-types.test.ts +714 -0
- package/src/chart-stats.ts +409 -0
- package/src/chart-stream.test.ts +75 -0
- package/src/chart-stream.ts +114 -0
- package/src/chart-summary.test.ts +90 -0
- package/src/chart-summary.ts +197 -0
- package/src/chart-sync.svelte.ts +41 -0
- package/src/chart-table.test.ts +76 -0
- package/src/chart-table.ts +178 -0
- package/src/chart-trend.ts +47 -0
- package/src/chart-types.ts +1309 -0
- package/src/chart-validate.test.ts +100 -0
- package/src/chart-validate.ts +247 -0
- package/src/chart-zoom.test.ts +137 -0
- package/src/chart-zoom.ts +124 -0
- package/src/chart.coverage.test.ts +81 -0
- package/src/chart.test.ts +56 -3
- package/src/chart.ts +557 -2784
- package/src/clipboard.test.ts +151 -0
- package/src/clipboard.ts +145 -16
- package/src/column-resize.ts +7 -1
- package/src/columns.ts +2 -0
- package/src/command-context.test.ts +200 -0
- package/src/command-context.ts +137 -0
- package/src/core.rowmodel-cache.test.ts +51 -0
- package/src/core.ts +40 -12
- package/src/editing.test.ts +281 -3
- package/src/editing.ts +264 -38
- package/src/fill-patterns.test.ts +35 -0
- package/src/fill-patterns.ts +56 -2
- package/src/grid-icons.ts +2 -2
- package/src/grid-messages.ts +21 -3
- package/src/history.test.ts +196 -0
- package/src/history.ts +162 -0
- package/src/icon-seam.test.ts +5 -6
- package/src/index.ts +169 -12
- package/src/keyboard-handlers.coverage.test.ts +126 -6
- package/src/keyboard-handlers.ts +91 -30
- package/src/keyboard-shortcuts.seam.test.ts +200 -0
- package/src/keyboard.test.ts +124 -1
- package/src/keyboard.ts +142 -0
- package/src/menus.test.ts +26 -0
- package/src/menus.ts +10 -3
- package/src/merges.test.ts +112 -0
- package/src/merges.ts +168 -0
- package/src/pivot.grid.test.ts +40 -5
- package/src/row-model.ts +146 -0
- package/src/row-resize.test.ts +44 -0
- package/src/row-resize.ts +17 -0
- package/src/scroll-sync.test.ts +42 -1
- package/src/scroll-sync.ts +25 -0
- package/src/selection.multi-range.test.ts +34 -0
- package/src/selection.test.ts +33 -2
- package/src/selection.ts +143 -21
- package/src/server-block-cache.test.ts +645 -0
- package/src/server-block-cache.ts +677 -0
- package/src/server-data-source.infinite.test.ts +343 -0
- package/src/server-data-source.ts +450 -27
- package/src/server.ts +47 -0
- package/src/shortcut-registry.test.ts +97 -0
- package/src/shortcut-registry.ts +181 -0
- package/src/svgrid-wrapper.types.ts +184 -13
- package/src/svgrid.behavior.test.ts +135 -0
- package/src/svgrid.charting.test.ts +513 -3
- package/src/svgrid.context-menu.test.ts +31 -0
- package/src/svgrid.new-features.wrapper.test.ts +10 -3
- package/src/svgrid.row-model-prop.svelte.test.ts +352 -0
- package/src/svgrid.row-model-seam.svelte.test.ts +289 -0
- package/src/validate.test.ts +10 -0
- package/src/validate.ts +10 -3
- package/src/virtualization/virtualizer.ts +39 -17
- package/dist/SvGroupCell.svelte +0 -141
- package/dist/SvGroupCell.svelte.d.ts +0 -49
- package/dist/SvRowGroupPanel.svelte +0 -186
- package/dist/SvRowGroupPanel.svelte.d.ts +0 -25
- package/dist/cdn/GridMenus-CHOMnW-c.js +0 -602
- package/dist/cdn/SvDateTimePicker-CCbDZNZB.js +0 -816
- package/dist/cdn/SvGridChart-1PMSp2LZ.js +0 -1481
- package/dist/cdn/SvGridChart-D02avhyr.js +0 -1480
- package/dist/cdn/SvGridChartPanel-DJnViOv4.js +0 -834
- package/dist/cdn/SvGridChartPanel-vPTNerFZ.js +0 -810
- package/dist/cdn/SvGridChartView-C--HhdUo.js +0 -56
- package/dist/cdn/SvGridChartView-KyyFG-wY.js +0 -55
- package/dist/cdn/chart-DLg3_zTQ.js +0 -1652
- package/dist/cdn/validate-_CDJzgIo.js +0 -75
- package/dist/server-group-model.d.ts +0 -98
- package/dist/server-group-model.js +0 -263
- package/src/SvGroupCell.svelte +0 -141
- package/src/SvRowGroupPanel.svelte +0 -186
- package/src/server-group-model.test.ts +0 -294
- package/src/server-group-model.ts +0 -370
|
@@ -6,6 +6,8 @@ import "./sv-grid-scrollbar.js";
|
|
|
6
6
|
import { computeColumnStat, formatsNeedingStats, formatNeedsStats, } from "./conditional-formatting.js";
|
|
7
7
|
import { rawToNumber, } from "./SvGrid.helpers.js";
|
|
8
8
|
import { createFeatures } from "./features.js";
|
|
9
|
+
import { pushHistory } from "./history.js";
|
|
10
|
+
import { buildMergeIndex } from "./merges.js";
|
|
9
11
|
import { createScrollSync, } from "./scroll-sync.js";
|
|
10
12
|
import { createKeyboard, } from "./keyboard-handlers.js";
|
|
11
13
|
import { createSummaries, } from "./summaries.js";
|
|
@@ -169,6 +171,60 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
169
171
|
// preserves reference identity for non-overridden keys, which the columns-identity
|
|
170
172
|
// column cache and the `data`/`columns` sync effects rely on.
|
|
171
173
|
let optionOverrides = $state({});
|
|
174
|
+
/**
|
|
175
|
+
* A `rowModel` answers for the props it can, so a server-backed grid is
|
|
176
|
+
* one prop rather than a dozen.
|
|
177
|
+
*
|
|
178
|
+
* Bumped by the model itself through `subscribe` (see the effect below),
|
|
179
|
+
* which is what makes `getRows()` and `isLoading()` - plain method calls,
|
|
180
|
+
* invisible to the reactive system - re-read when the model changes.
|
|
181
|
+
*/
|
|
182
|
+
let rowModelVersion = $state(0);
|
|
183
|
+
const rowModelProps = $derived.by(function rowModelProps_d() {
|
|
184
|
+
const model = rawProps.rowModel;
|
|
185
|
+
if (!model)
|
|
186
|
+
return null;
|
|
187
|
+
rowModelVersion; // read, so a notification re-derives
|
|
188
|
+
const paging = model.pagination;
|
|
189
|
+
return {
|
|
190
|
+
data: model.getRows(),
|
|
191
|
+
loading: model.isLoading(),
|
|
192
|
+
loadingOverlay: true,
|
|
193
|
+
getRowId: model.getRowId,
|
|
194
|
+
// A model that sorts or filters server-side is telling the grid not
|
|
195
|
+
// to do it locally; one that does neither leaves the grid in charge.
|
|
196
|
+
externalSort: model.setSort ? true : undefined,
|
|
197
|
+
externalFilter: model.setFilter ? true : undefined,
|
|
198
|
+
onSortingChange: model.setSort ? (s) => model.setSort(s) : undefined,
|
|
199
|
+
onFiltersChange: model.setFilter ? (f) => model.setFilter(f) : undefined,
|
|
200
|
+
onVisibleRangeChange: model.setViewport
|
|
201
|
+
? (r) => model.setViewport(r.startIndex, r.endIndex)
|
|
202
|
+
: undefined,
|
|
203
|
+
rowPlaceholder: model.rowPlaceholder,
|
|
204
|
+
onRetryRow: model.retryRow,
|
|
205
|
+
serverGroup: model.group,
|
|
206
|
+
rowSelectionModel: model.selection,
|
|
207
|
+
serverFilterValues: model.filterValues
|
|
208
|
+
? (columnId) => model.filterValues(columnId)
|
|
209
|
+
: undefined,
|
|
210
|
+
pinnedTopRows: model.pinnedTopRows,
|
|
211
|
+
pinnedBottomRows: model.pinnedBottomRows,
|
|
212
|
+
pivotResultColumns: model.pivotResultColumns ?? undefined,
|
|
213
|
+
externalPagination: paging ? true : undefined,
|
|
214
|
+
rowCount: paging?.rowCount,
|
|
215
|
+
pageIndex: paging?.pageIndex,
|
|
216
|
+
pageSize: paging?.pageSize,
|
|
217
|
+
pageSizeOptions: paging?.pageSizes,
|
|
218
|
+
onPaginationChange: paging
|
|
219
|
+
? (p) => {
|
|
220
|
+
if (p.pageSize !== paging.pageSize)
|
|
221
|
+
paging.setPageSize(p.pageSize);
|
|
222
|
+
else
|
|
223
|
+
paging.setPage(p.pageIndex);
|
|
224
|
+
}
|
|
225
|
+
: undefined,
|
|
226
|
+
};
|
|
227
|
+
});
|
|
172
228
|
const props = new Proxy(rawProps, {
|
|
173
229
|
// Read the override as a PROPERTY (tracked by `$state`), not via `in` (the `has`
|
|
174
230
|
// trap is not tracked) - so a reactive read of `props.X` re-runs when an override
|
|
@@ -177,7 +233,23 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
177
233
|
// undefined too. Whole-object reassignment on write makes the signal fire.
|
|
178
234
|
get: (t, k) => {
|
|
179
235
|
const v = typeof k === "string" ? optionOverrides[k] : undefined;
|
|
180
|
-
|
|
236
|
+
if (v !== undefined)
|
|
237
|
+
return v;
|
|
238
|
+
// Pivot mode swaps the column list: `pivotResultColumns` (the prop, or
|
|
239
|
+
// the row model's) stands in for `columns` while it is set, and
|
|
240
|
+
// `columns` itself stays the app's own list for when pivot ends.
|
|
241
|
+
if (k === "columns") {
|
|
242
|
+
const pivot = Reflect.get(t, "pivotResultColumns") ??
|
|
243
|
+
rowModelProps?.pivotResultColumns;
|
|
244
|
+
if (pivot)
|
|
245
|
+
return pivot;
|
|
246
|
+
}
|
|
247
|
+
const own = Reflect.get(t, k);
|
|
248
|
+
// A prop written on the grid always beats the row model, so adopting
|
|
249
|
+
// `rowModel` is never all-or-nothing.
|
|
250
|
+
if (own !== undefined || typeof k !== "string")
|
|
251
|
+
return own;
|
|
252
|
+
return rowModelProps?.[k];
|
|
181
253
|
},
|
|
182
254
|
});
|
|
183
255
|
// Resolved capability gates. Capabilities are OFF by default - a bare
|
|
@@ -215,7 +287,12 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
215
287
|
let pendingScrollTop = 0;
|
|
216
288
|
let pendingScrollLeft = 0;
|
|
217
289
|
let scrollSyncRaf = null;
|
|
218
|
-
|
|
290
|
+
// `$state.raw`: every writer replaces the whole object, and every rendered
|
|
291
|
+
// cell's selection derived reads it. Under a deep proxy each of those ~300
|
|
292
|
+
// deriveds tracked the nested anchor/focus row/col signals separately (six
|
|
293
|
+
// dependencies per cell to mark and re-check on every arrow key) where one
|
|
294
|
+
// will do.
|
|
295
|
+
let selectionRange = $state.raw({ anchor: null, focus: null });
|
|
219
296
|
// Extra committed ranges for multi-range (Ctrl+drag) selection. The
|
|
220
297
|
// `selectionRange` above is always the ACTIVE range being manipulated; these
|
|
221
298
|
// are the finished ones. Full selection = these + the active range.
|
|
@@ -243,7 +320,12 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
243
320
|
// Full-row editing: the row currently in whole-row edit + its per-column
|
|
244
321
|
// draft (keyed by column id). Null when not in full-row mode.
|
|
245
322
|
let fullRowEdit = $state(null);
|
|
246
|
-
|
|
323
|
+
// `$state.raw`, not `$state`: every writer replaces the whole object, and
|
|
324
|
+
// the render path asks `key in editedCellValues` once per rendered cell. On
|
|
325
|
+
// a deep proxy that `in` check, run inside an effect, creates and keeps a
|
|
326
|
+
// per-key signal for every cell that ever scrolls into view - a leak that
|
|
327
|
+
// grows with rows x columns and a Map lookup per cell per render.
|
|
328
|
+
let editedCellValues = $state.raw({});
|
|
247
329
|
const UNDO_LIMIT = 200;
|
|
248
330
|
let history = $state([]);
|
|
249
331
|
/** Index in `history` of the LAST applied step. -1 means "nothing applied".
|
|
@@ -253,6 +335,11 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
253
335
|
/** Bumps on every undo / redo / record so $derived consumers can
|
|
254
336
|
* observe via the api without subscribing to history directly. */
|
|
255
337
|
let historyVersion = $state(0);
|
|
338
|
+
/** Set while a `runHistoryGroup` call is on the stack, so every step pushed
|
|
339
|
+
* inside one action shares a group id and undoes together. Deliberately not
|
|
340
|
+
* $state: it is written and read synchronously inside a single call and
|
|
341
|
+
* nothing renders from it. */
|
|
342
|
+
let historyGroupId = undefined;
|
|
256
343
|
let tooltip = $state(null);
|
|
257
344
|
let tooltipTimer = null;
|
|
258
345
|
// ---- Find-in-grid ----------------------------------------------------
|
|
@@ -288,6 +375,26 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
288
375
|
let editorSelectAll = true;
|
|
289
376
|
/** Per-column width overrides set by the resize handles. */
|
|
290
377
|
let columnWidths = $state({});
|
|
378
|
+
/**
|
|
379
|
+
* Columns folded to nothing, the way a sheet hides a column: the column
|
|
380
|
+
* keeps its index, its cells and its width for later, and takes no room.
|
|
381
|
+
* Distinct from `hiddenColumns`, which takes a column out of the model
|
|
382
|
+
* and shifts every index after it; a spreadsheet's formulas and formats
|
|
383
|
+
* are keyed by index and could not follow that.
|
|
384
|
+
*/
|
|
385
|
+
let collapsedColumns = $state({});
|
|
386
|
+
function setColumnCollapsed(columnId, collapsed) {
|
|
387
|
+
if (!!collapsedColumns[columnId] === collapsed)
|
|
388
|
+
return;
|
|
389
|
+
if (collapsed) {
|
|
390
|
+
collapsedColumns = { ...collapsedColumns, [columnId]: true };
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
const next = { ...collapsedColumns };
|
|
394
|
+
delete next[columnId];
|
|
395
|
+
collapsedColumns = next;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
291
398
|
const MIN_COLUMN_WIDTH = 40;
|
|
292
399
|
/** Columns pinned to the left or right edge of the grid (sticky positioning).
|
|
293
400
|
* Seeded from `props.initialColumnPinning` so demos / tests can show the
|
|
@@ -373,26 +480,53 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
373
480
|
const hb = column.columnDef?.hideBelow;
|
|
374
481
|
return hb != null && viewportWidth > 0 && viewportWidth < hb;
|
|
375
482
|
}
|
|
376
|
-
|
|
483
|
+
/**
|
|
484
|
+
* Live scroll geometry of the scroll container (feeds the custom
|
|
485
|
+
* scrollbars and `hasVerticalOverflow`).
|
|
486
|
+
*
|
|
487
|
+
* Measured from a plain `$effect`, NOT inside a `$derived`, on purpose.
|
|
488
|
+
* `scrollTop` / `scrollHeight` / `scrollWidth` reads force a synchronous
|
|
489
|
+
* layout whenever the tree is dirty. As a derived this was pulled by the
|
|
490
|
+
* shell's `class:sv-grid-has-vscroll` at the very start of every scroll
|
|
491
|
+
* flush - before the recycled rows had been rewritten - so it forced a
|
|
492
|
+
* full table layout that the row writes then invalidated again, and the
|
|
493
|
+
* browser laid the table out a second time before painting. Two table
|
|
494
|
+
* layouts per arrow key on a large grid. An effect runs after the flush
|
|
495
|
+
* has written the DOM, so the reads below land on a tree the browser has
|
|
496
|
+
* to lay out anyway, and the pre-paint pass is incremental.
|
|
497
|
+
*
|
|
498
|
+
* The version reads keep the same triggers the derived had: a scroll
|
|
499
|
+
* tick, a shell resize, and either virtualizer's window/size change (data
|
|
500
|
+
* load, row / column count change) - without those the scrollbar kept a
|
|
501
|
+
* stale `content-size` ≈ 0, its hidden-check tripped, it set
|
|
502
|
+
* `pointer-events: none`, and the user could not drag it.
|
|
503
|
+
*/
|
|
504
|
+
let scrollMetrics = $state.raw({
|
|
505
|
+
scrollTop: 0,
|
|
506
|
+
scrollLeft: 0,
|
|
507
|
+
scrollHeight: 0,
|
|
508
|
+
scrollWidth: 0,
|
|
509
|
+
});
|
|
510
|
+
$effect(function scrollMetrics_e() {
|
|
377
511
|
scrollVersion;
|
|
378
512
|
viewportVersion;
|
|
379
|
-
// Track the virtualizers' versions too so when data loads or row /
|
|
380
|
-
// column counts change, scrollMetrics re-reads the DOM's grown
|
|
381
|
-
// scrollHeight / scrollWidth. Without these deps the scrollbar
|
|
382
|
-
// receives a stale `content-size` ≈ 0, its hidden-check trips, it
|
|
383
|
-
// sets `pointer-events: none`, and the user can't drag it. The
|
|
384
|
-
// identifiers below are declared further down - derived callbacks
|
|
385
|
-
// run lazily, so by the time this fires they're in scope.
|
|
386
513
|
virtualizer.version;
|
|
387
514
|
columnVirtualizerVersion;
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
515
|
+
const el = scrollContainer;
|
|
516
|
+
if (!el)
|
|
517
|
+
return;
|
|
518
|
+
const scrollTop = el.scrollTop;
|
|
519
|
+
const scrollLeft = el.scrollLeft;
|
|
520
|
+
const scrollHeight = el.scrollHeight;
|
|
521
|
+
const scrollWidth = el.scrollWidth;
|
|
522
|
+
const prev = untrack(() => scrollMetrics);
|
|
523
|
+
if (prev.scrollTop === scrollTop &&
|
|
524
|
+
prev.scrollLeft === scrollLeft &&
|
|
525
|
+
prev.scrollHeight === scrollHeight &&
|
|
526
|
+
prev.scrollWidth === scrollWidth) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
scrollMetrics = { scrollTop, scrollLeft, scrollHeight, scrollWidth };
|
|
396
530
|
});
|
|
397
531
|
/** Vertical overflow from the virtualizer's authoritative total size,
|
|
398
532
|
* NOT from `scrollMetrics.scrollHeight` alone. Reading DOM dimensions
|
|
@@ -454,7 +588,10 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
454
588
|
// re-synced whenever the parent passes a new array; the imperative API
|
|
455
589
|
// mutates these so add/remove operations don't need a callback round-trip.
|
|
456
590
|
// svelte-ignore state_referenced_locally
|
|
457
|
-
|
|
591
|
+
// `data` is optional on the type (a `rowModel` may supply the rows
|
|
592
|
+
// instead), so every read falls back to empty rather than to undefined.
|
|
593
|
+
const EMPTY_DATA = [];
|
|
594
|
+
let internalData = $state.raw(props.data ?? EMPTY_DATA);
|
|
458
595
|
// Resolve `cellDataType` / `inferColumnTypes` into concrete editorType +
|
|
459
596
|
// format defaults once, up front, so every downstream reader sees a normal
|
|
460
597
|
// column. Explicit fields on the ColumnDef always win.
|
|
@@ -499,7 +636,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
499
636
|
// accumulated cell-edit overrides - otherwise `getCellDisplayValue`
|
|
500
637
|
// would keep returning the old edited values from `editedCellValues`
|
|
501
638
|
// even though the underlying data has been replaced.
|
|
502
|
-
internalData = props.data;
|
|
639
|
+
internalData = props.data ?? EMPTY_DATA;
|
|
503
640
|
editedCellValues = {};
|
|
504
641
|
});
|
|
505
642
|
$effect(() => {
|
|
@@ -732,6 +869,20 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
732
869
|
*
|
|
733
870
|
* Returns null when the cell should render nothing.
|
|
734
871
|
*/
|
|
872
|
+
/**
|
|
873
|
+
* Why a row has no data yet, or null when it does.
|
|
874
|
+
*
|
|
875
|
+
* Group rows are never placeholders, so the check is skipped for them:
|
|
876
|
+
* their `original` is a synthetic bucket, not a row the consumer handed
|
|
877
|
+
* us, and passing it to `rowPlaceholder` would ask a question about a row
|
|
878
|
+
* that does not exist.
|
|
879
|
+
*/
|
|
880
|
+
function placeholderStateOf(row) {
|
|
881
|
+
const ask = props.rowPlaceholder;
|
|
882
|
+
if (!ask || isGroupRow(row))
|
|
883
|
+
return null;
|
|
884
|
+
return ask(row.original, row.index);
|
|
885
|
+
}
|
|
735
886
|
function autoGroupCell(row, columnId) {
|
|
736
887
|
if (!isGroupRow(row))
|
|
737
888
|
return null;
|
|
@@ -1355,8 +1506,14 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1355
1506
|
const paginationTotalRows = $derived(externalPaginationEnabled ? (props.rowCount ?? 0) : paginatedRowTotal);
|
|
1356
1507
|
const paginationPageIndex = $derived(externalPaginationEnabled ? (props.pageIndex ?? 0) : paginationState.pageIndex);
|
|
1357
1508
|
const paginationPageSize = $derived(externalPaginationEnabled ? (props.pageSize ?? 10) : paginationState.pageSize);
|
|
1509
|
+
// Tracks `dataStateVersion`, which the store subscription bumps when the
|
|
1510
|
+
// rowSelection slice (among others) changes - NOT the catch-all
|
|
1511
|
+
// `gridStateVersion`, which also bumps on every active-cell move. Every
|
|
1512
|
+
// rendered row and cell reads this (`sv-grid-row-selected`, `aria-selected`),
|
|
1513
|
+
// so on the catch-all each arrow key re-marked and re-checked ~400 effects
|
|
1514
|
+
// to find the same object.
|
|
1358
1515
|
const rowSelectionState = $derived.by(function rowSelectionState_d() {
|
|
1359
|
-
|
|
1516
|
+
dataStateVersion;
|
|
1360
1517
|
return grid.getState().rowSelection ?? {};
|
|
1361
1518
|
});
|
|
1362
1519
|
// ---- Selection bar ----------------------------------------------------
|
|
@@ -1388,16 +1545,44 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1388
1545
|
const ids = [];
|
|
1389
1546
|
if (!selectionBarOn)
|
|
1390
1547
|
return { rows, ids };
|
|
1548
|
+
// Under an external selection model the loaded rows are the ones the
|
|
1549
|
+
// model says are selected; rows that never loaded are not in `allRows`
|
|
1550
|
+
// and are counted by `selectionBarCount` below instead. This walks
|
|
1551
|
+
// every loaded row, so it runs on every block a row model delivers:
|
|
1552
|
+
// a model that can say "nothing is selected" spares the walk, and the
|
|
1553
|
+
// props it needs are read once, not through the proxy per row.
|
|
1554
|
+
const model = props.rowSelectionModel;
|
|
1555
|
+
if (model && model.selectedCount?.() === 0)
|
|
1556
|
+
return { rows, ids };
|
|
1557
|
+
const ask = props.rowPlaceholder;
|
|
1558
|
+
const isPlaceholder = ask
|
|
1559
|
+
? (row) => !isGroupRow(row) && !!ask(row.original, row.index)
|
|
1560
|
+
: () => false;
|
|
1391
1561
|
for (const row of allRows) {
|
|
1392
|
-
if (isGroupRow(row))
|
|
1562
|
+
if (isGroupRow(row) || isPlaceholder(row))
|
|
1393
1563
|
continue;
|
|
1394
|
-
|
|
1564
|
+
const selected = model
|
|
1565
|
+
? model.isSelected(row.id, row.original)
|
|
1566
|
+
: !!rowSelectionState[row.id];
|
|
1567
|
+
if (selected) {
|
|
1395
1568
|
rows.push(row.original);
|
|
1396
1569
|
ids.push(row.id);
|
|
1397
1570
|
}
|
|
1398
1571
|
}
|
|
1399
1572
|
return { rows, ids };
|
|
1400
1573
|
});
|
|
1574
|
+
/**
|
|
1575
|
+
* What the selection bar's count chip says. A model that stores the rule
|
|
1576
|
+
* ("everything except these") knows about rows the grid never loaded, so
|
|
1577
|
+
* its number wins over counting ticks on screen; `null` from it means it
|
|
1578
|
+
* cannot say, and the bar falls back to the loaded count.
|
|
1579
|
+
*/
|
|
1580
|
+
const selectionBarCount = $derived.by(function selectionBarCount_d() {
|
|
1581
|
+
const fromModel = props.rowSelectionModel?.selectedCount?.();
|
|
1582
|
+
return fromModel ?? selectionBarTarget.ids.length;
|
|
1583
|
+
});
|
|
1584
|
+
/** Whether the bar has anything to show: the model's count when it has one, else the loaded ticks. */
|
|
1585
|
+
const selectionBarVisible = $derived(selectionBarOn && selectionBarCount > 0);
|
|
1401
1586
|
// Forward selection changes to the consumer. Skips the very first invocation
|
|
1402
1587
|
// (the initial empty state) so consumers don't get a spurious callback on mount.
|
|
1403
1588
|
let lastSelectionSerialized = "";
|
|
@@ -1652,7 +1837,9 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1652
1837
|
let chartMaximized = $state(false);
|
|
1653
1838
|
let chartFloatRect = $state.raw(null);
|
|
1654
1839
|
let chartAiHandler = $state(null);
|
|
1840
|
+
let chartExplainHandler = $state(null);
|
|
1655
1841
|
let chartSeq = 0;
|
|
1842
|
+
const isZoomWindow = (v) => !!v && typeof v === "object" && Number.isInteger(v.i0) && Number.isInteger(v.i1);
|
|
1656
1843
|
const makeChart = (title) => ({
|
|
1657
1844
|
id: `chart-${chartSeq++}`,
|
|
1658
1845
|
title,
|
|
@@ -1671,10 +1858,50 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1671
1858
|
logScale: null,
|
|
1672
1859
|
timeAxis: null,
|
|
1673
1860
|
valueFormat: null,
|
|
1861
|
+
bucket: null,
|
|
1862
|
+
bins: null,
|
|
1863
|
+
funnelShape: null,
|
|
1864
|
+
candleStyle: null,
|
|
1865
|
+
zoom: null,
|
|
1866
|
+
ohlc: null,
|
|
1867
|
+
indicators: [],
|
|
1868
|
+
format: null,
|
|
1869
|
+
frozen: null,
|
|
1674
1870
|
});
|
|
1871
|
+
// The saved view (`GridState.charts[i]`) names four fields differently
|
|
1872
|
+
// from the tab; everything else is the tab's own key.
|
|
1873
|
+
const TAB_ALIASES = { dimensionId: "dimension", measureId: "measure", seriesId: "series", measure2Id: "measure2" };
|
|
1874
|
+
/** A tab as the saved view carries it, without the runtime id. */
|
|
1875
|
+
const tabToState = (c) => {
|
|
1876
|
+
const out = {};
|
|
1877
|
+
for (const k in c)
|
|
1878
|
+
if (k !== "id")
|
|
1879
|
+
out[TAB_ALIASES[k] ?? k] = k === "seriesId" ? (c.seriesId ?? null) : c[k];
|
|
1880
|
+
return out;
|
|
1881
|
+
};
|
|
1882
|
+
/** A tab from a saved-view snapshot: a fresh tab with every known field
|
|
1883
|
+
* the snapshot carries, and the shape-checked ones dropped when malformed. */
|
|
1884
|
+
const tabFromState = (c, i) => {
|
|
1885
|
+
const t = makeChart(typeof c.title === "string" ? c.title : `Chart ${i + 1}`);
|
|
1886
|
+
for (const k in t) {
|
|
1887
|
+
const v = c[TAB_ALIASES[k] ?? k];
|
|
1888
|
+
if (k !== "id" && k !== "title" && v !== undefined)
|
|
1889
|
+
t[k] = v;
|
|
1890
|
+
}
|
|
1891
|
+
if (!isZoomWindow(t.zoom))
|
|
1892
|
+
t.zoom = null;
|
|
1893
|
+
if (!Array.isArray(t.indicators))
|
|
1894
|
+
t.indicators = [];
|
|
1895
|
+
if (typeof t.bins !== "number")
|
|
1896
|
+
t.bins = null;
|
|
1897
|
+
if (!t.frozen || typeof t.frozen !== "object" || !t.frozen.spec)
|
|
1898
|
+
t.frozen = null;
|
|
1899
|
+
return t;
|
|
1900
|
+
};
|
|
1675
1901
|
// svelte-ignore state_referenced_locally
|
|
1676
1902
|
let charts = $state([makeChart("Chart 1")]);
|
|
1677
1903
|
let activeChartIndex = $state(0);
|
|
1904
|
+
let savedCharts = $state([]);
|
|
1678
1905
|
const activeChart = $derived((charts[Math.min(activeChartIndex, charts.length - 1)] ?? charts[0]));
|
|
1679
1906
|
const chartType = $derived(activeChart.type);
|
|
1680
1907
|
const chartReduce = $derived(activeChart.reduce);
|
|
@@ -1685,6 +1912,10 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1685
1912
|
const chartDataLabels = $derived(activeChart.dataLabels);
|
|
1686
1913
|
const chartLogScale = $derived(activeChart.logScale);
|
|
1687
1914
|
const chartTimeAxis = $derived(activeChart.timeAxis);
|
|
1915
|
+
const chartBucket = $derived(activeChart.bucket);
|
|
1916
|
+
const chartBins = $derived(activeChart.bins);
|
|
1917
|
+
const chartFunnelShape = $derived(activeChart.funnelShape);
|
|
1918
|
+
const chartCandleStyle = $derived(activeChart.candleStyle);
|
|
1688
1919
|
const chartValueFormat = $derived(activeChart.valueFormat);
|
|
1689
1920
|
const asMeasureList = (m) => m == null ? [] : Array.isArray(m) ? m : [m];
|
|
1690
1921
|
const isNumericColumn = (col) => {
|
|
@@ -1802,9 +2033,14 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1802
2033
|
const label = col ? columnLabel(col) : "";
|
|
1803
2034
|
if (!label)
|
|
1804
2035
|
return undefined;
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
:
|
|
2036
|
+
const word = {
|
|
2037
|
+
sum: "Sum", avg: "Average", min: "Min", max: "Max", median: "Median",
|
|
2038
|
+
first: "First", last: "Last", countDistinct: "Distinct",
|
|
2039
|
+
};
|
|
2040
|
+
if (chartReduce === "count")
|
|
2041
|
+
return "Count";
|
|
2042
|
+
const w = word[chartReduce] ?? (chartReduce.startsWith("p") ? `P${chartReduce.slice(1)}` : "Sum");
|
|
2043
|
+
return `${w} of ${label}`;
|
|
1808
2044
|
});
|
|
1809
2045
|
/**
|
|
1810
2046
|
* Does this column hold dates? Declared type first, then a probe of the
|
|
@@ -1830,6 +2066,34 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1830
2066
|
return false;
|
|
1831
2067
|
}
|
|
1832
2068
|
const chartDimensionIsDate = $derived.by(() => columnIsDate(allColumns.find((c) => c.id === effectiveChartDimensionId)));
|
|
2069
|
+
// The chart derivation engine (rowsToChartSpec) is loaded lazily the first time
|
|
2070
|
+
// charting is enabled, keeping chart.ts (~11 KB gzip) out of the base <SvGrid>
|
|
2071
|
+
// bundle - it rides in the same lazy chunk as the chart panel. `chartSpec` stays
|
|
2072
|
+
// null until it resolves, a tick after the panel itself starts loading (which it
|
|
2073
|
+
// already does). The `getAggregate` server path uses the local bucketsToSpec and
|
|
2074
|
+
// does not need the engine.
|
|
2075
|
+
// The whole module, not just `rowsToChartSpec`: the panel can now reach
|
|
2076
|
+
// every chart type, and the ones that do not read a categories-by-series
|
|
2077
|
+
// grid (scatter, gauge, treemap, calendar, sankey) need their own builders
|
|
2078
|
+
// from the same lazy chunk. Same `import()`, same boundary.
|
|
2079
|
+
let chartEngine = $state(null);
|
|
2080
|
+
$effect(() => {
|
|
2081
|
+
if (chartingEnabled && !chartEngine)
|
|
2082
|
+
import("./chart.js").then((m) => (chartEngine = m));
|
|
2083
|
+
});
|
|
2084
|
+
const chartIsOhlc = $derived(chartType === "candlestick" || chartType === "ohlc");
|
|
2085
|
+
/** The price columns: what the reader picked, else the engine's guess
|
|
2086
|
+
* from the column names (nothing until the engine chunk has loaded). */
|
|
2087
|
+
const effectiveChartOhlc = $derived(chartEngine
|
|
2088
|
+
? chartEngine.guessOhlcColumns(chartableColumns.measures, activeChart.ohlc)
|
|
2089
|
+
: { open: null, high: null, low: null, close: null, volume: null, ...activeChart.ohlc });
|
|
2090
|
+
/** The date column a candlestick reads: the picked dimension when it is a
|
|
2091
|
+
* date, else the first date column. */
|
|
2092
|
+
const effectiveChartOhlcDateId = $derived(chartDimensionIsDate ? effectiveChartDimensionId : (chartableColumns.dates[0]?.id ?? null));
|
|
2093
|
+
const chartIndicators = $derived(activeChart.indicators);
|
|
2094
|
+
/** The panes under a price chart and the overlay on it, split by the engine. */
|
|
2095
|
+
const chartIndicatorSplit = $derived(chartIsOhlc && chartEngine ? chartEngine.splitPanelIndicators(chartIndicators) : { panes: [], overlay: null });
|
|
2096
|
+
const chartIndicatorPanes = $derived(chartIndicatorSplit.panes);
|
|
1833
2097
|
const chartRows = $derived.by(() => {
|
|
1834
2098
|
const rects = getSelectionRects();
|
|
1835
2099
|
const pushLeaf = (out, row) => {
|
|
@@ -1883,53 +2147,6 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1883
2147
|
chartServerBuckets = [];
|
|
1884
2148
|
});
|
|
1885
2149
|
});
|
|
1886
|
-
const bucketsToSpec = (buckets, type, hasSeries) => {
|
|
1887
|
-
const cats = [];
|
|
1888
|
-
const catIdx = new Map();
|
|
1889
|
-
const ensureCat = (c) => {
|
|
1890
|
-
let i = catIdx.get(c);
|
|
1891
|
-
if (i === undefined) {
|
|
1892
|
-
i = cats.length;
|
|
1893
|
-
catIdx.set(c, i);
|
|
1894
|
-
cats.push(c);
|
|
1895
|
-
}
|
|
1896
|
-
return i;
|
|
1897
|
-
};
|
|
1898
|
-
const seriesMap = new Map();
|
|
1899
|
-
for (const b of buckets) {
|
|
1900
|
-
const ci = ensureCat(b.category);
|
|
1901
|
-
const key = hasSeries ? (b.series ?? "") : "value";
|
|
1902
|
-
let arr = seriesMap.get(key);
|
|
1903
|
-
if (!arr) {
|
|
1904
|
-
arr = [];
|
|
1905
|
-
seriesMap.set(key, arr);
|
|
1906
|
-
}
|
|
1907
|
-
arr[ci] = (arr[ci] ?? 0) + b.value;
|
|
1908
|
-
}
|
|
1909
|
-
return {
|
|
1910
|
-
type,
|
|
1911
|
-
categories: cats,
|
|
1912
|
-
series: [...seriesMap.entries()].map(([label, values]) => ({
|
|
1913
|
-
label,
|
|
1914
|
-
values: cats.map((_, i) => values[i] ?? 0),
|
|
1915
|
-
})),
|
|
1916
|
-
};
|
|
1917
|
-
};
|
|
1918
|
-
// The chart derivation engine (rowsToChartSpec) is loaded lazily the first time
|
|
1919
|
-
// charting is enabled, keeping chart.ts (~11 KB gzip) out of the base <SvGrid>
|
|
1920
|
-
// bundle - it rides in the same lazy chunk as the chart panel. `chartSpec` stays
|
|
1921
|
-
// null until it resolves, a tick after the panel itself starts loading (which it
|
|
1922
|
-
// already does). The `getAggregate` server path uses the local bucketsToSpec and
|
|
1923
|
-
// does not need the engine.
|
|
1924
|
-
// The whole module, not just `rowsToChartSpec`: the panel can now reach
|
|
1925
|
-
// every chart type, and the ones that do not read a categories-by-series
|
|
1926
|
-
// grid (scatter, gauge, treemap, calendar, sankey) need their own builders
|
|
1927
|
-
// from the same lazy chunk. Same `import()`, same boundary.
|
|
1928
|
-
let chartEngine = $state(null);
|
|
1929
|
-
$effect(() => {
|
|
1930
|
-
if (chartingEnabled && !chartEngine)
|
|
1931
|
-
import("./chart.js").then((m) => (chartEngine = m));
|
|
1932
|
-
});
|
|
1933
2150
|
/** Put the chart's numbers in the same locale as the grid's. A grid that set
|
|
1934
2151
|
* `localization.locale` has already said which locale its data is in, and a
|
|
1935
2152
|
* chart of that data reading in a different one is a bug nobody would think
|
|
@@ -1942,13 +2159,13 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1942
2159
|
if (chartCfg?.currency)
|
|
1943
2160
|
spec.currency = chartCfg.currency;
|
|
1944
2161
|
};
|
|
1945
|
-
const
|
|
2162
|
+
const chartSpecLive = $derived.by(() => {
|
|
1946
2163
|
if (!chartingEnabled || !chartCfg)
|
|
1947
2164
|
return null;
|
|
1948
2165
|
if (chartCfg.getAggregate) {
|
|
1949
|
-
if (!chartServerBuckets)
|
|
2166
|
+
if (!chartServerBuckets || !chartEngine)
|
|
1950
2167
|
return null;
|
|
1951
|
-
const spec =
|
|
2168
|
+
const spec = chartEngine.bucketsToChartSpec(chartServerBuckets, chartType, !!effectiveChartSeriesId);
|
|
1952
2169
|
if (effectiveChartStacked || chartCfg.stacked100)
|
|
1953
2170
|
spec.stacked = true;
|
|
1954
2171
|
if (chartCfg.stacked100)
|
|
@@ -1957,6 +2174,8 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1957
2174
|
spec.palette = chartCfg.palette;
|
|
1958
2175
|
if (effectiveChartTimeAxis)
|
|
1959
2176
|
spec.xType = "time";
|
|
2177
|
+
else if (chartBucket && chartDimensionIsDate)
|
|
2178
|
+
spec.xType = "ordinal-time";
|
|
1960
2179
|
if (effectiveChartLogScale)
|
|
1961
2180
|
spec.yScale = "log";
|
|
1962
2181
|
if (effectiveChartValueFormat)
|
|
@@ -1966,55 +2185,110 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1966
2185
|
}
|
|
1967
2186
|
if (chartCfg.buildSpec)
|
|
1968
2187
|
return chartCfg.buildSpec(chartRows) ?? null;
|
|
1969
|
-
const category = fieldOf(effectiveChartDimensionId);
|
|
1970
|
-
const values = effectiveChartMeasureIds.map((id) => fieldOf(id)).filter((f) => !!f);
|
|
1971
|
-
if (!category || !values.length)
|
|
1972
|
-
return null;
|
|
1973
2188
|
// Engine not loaded yet (lazy): render nothing until it resolves.
|
|
1974
2189
|
if (!chartEngine)
|
|
1975
2190
|
return null;
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
//
|
|
1979
|
-
//
|
|
1980
|
-
//
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
category
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2191
|
+
// Shared by the direct and aggregated paths so the two cannot drift apart
|
|
2192
|
+
// again - which is how these came to be skipped for scatter, gauge and
|
|
2193
|
+
// boxplot in the first place. Declared here, at the derived's own scope:
|
|
2194
|
+
// a function declaration inside the `else` block below is block-scoped,
|
|
2195
|
+
// and the aggregated path calls it after that block has closed.
|
|
2196
|
+
function applyChartValueSettings(spec) {
|
|
2197
|
+
if (effectiveChartTimeAxis)
|
|
2198
|
+
spec.xType = "time";
|
|
2199
|
+
if (effectiveChartLogScale)
|
|
2200
|
+
spec.yScale = "log";
|
|
2201
|
+
if (effectiveChartValueFormat)
|
|
2202
|
+
spec.valueFormat = effectiveChartValueFormat;
|
|
2203
|
+
applyChartLocale(spec);
|
|
2204
|
+
return spec;
|
|
2205
|
+
}
|
|
2206
|
+
// Pivot mode: the pivot on screen is the chart, rows as categories and
|
|
2207
|
+
// columns as series; the pickers have nothing to choose.
|
|
2208
|
+
let spec;
|
|
2209
|
+
if (pivotResult) {
|
|
2210
|
+
spec = chartEngine.pivotResultToChartSpec(pivotResult, {
|
|
2211
|
+
type: chartType, stacked: effectiveChartStacked, stacked100: effectiveChartStacked100,
|
|
2212
|
+
measures: pivotConfig.values,
|
|
2213
|
+
...(effectiveChartPalette ? { palette: effectiveChartPalette } : {}),
|
|
2214
|
+
});
|
|
2215
|
+
}
|
|
2216
|
+
else {
|
|
2217
|
+
const category = fieldOf(effectiveChartDimensionId);
|
|
2218
|
+
const values = effectiveChartMeasureIds.map((id) => fieldOf(id)).filter((f) => !!f);
|
|
2219
|
+
if (!chartIsOhlc && (!category || !values.length))
|
|
2220
|
+
return null;
|
|
2221
|
+
const seriesField = fieldOf(effectiveChartSeriesId);
|
|
2222
|
+
const rowsForChart = chartRows;
|
|
2223
|
+
// Some types read the rows directly rather than a grouped grid: a scatter
|
|
2224
|
+
// point is one row, a gauge has no category axis at all, and a box plot
|
|
2225
|
+
// needs the whole sample per group rather than one reduced number. The
|
|
2226
|
+
// engine owns that dispatch - naming each type here would put them in the
|
|
2227
|
+
// base bundle, which every grid pays for whether or not it ever charts.
|
|
2228
|
+
// Everything else falls through to the aggregation below and is reshaped
|
|
2229
|
+
// afterwards, so reduce / sort / topN / "Other" keep working for all of it.
|
|
2230
|
+
const ohlcOpts = chartIsOhlc
|
|
2231
|
+
? chartEngine.ohlcDirectOptions(fieldOf, effectiveChartOhlcDateId, effectiveChartOhlc, { bucket: chartBucket, candleStyle: chartCandleStyle })
|
|
2232
|
+
: null;
|
|
2233
|
+
const direct = chartEngine.rowsToDirectSpec(chartType, rowsForChart, {
|
|
2234
|
+
...(ohlcOpts ?? {}),
|
|
2235
|
+
category: ohlcOpts?.category ?? category,
|
|
2236
|
+
...(values[0] ? { value: values[0] } : {}),
|
|
2237
|
+
...(fieldOf(effectiveChartMeasure2Id) ? { value2: fieldOf(effectiveChartMeasure2Id) } : {}),
|
|
2238
|
+
...(seriesField ? { series: seriesField } : {}),
|
|
2239
|
+
reduce: chartReduce,
|
|
2240
|
+
...(chartBins ? { bins: chartBins } : {}),
|
|
2241
|
+
...(effectiveChartPalette ? { palette: effectiveChartPalette } : {}),
|
|
2242
|
+
});
|
|
2243
|
+
// Number formatting and the scale/axis-type settings are about how values
|
|
2244
|
+
// READ, so they apply to a scatter or a gauge exactly as they do to a bar
|
|
2245
|
+
// chart. They used to live only on the aggregated path below, so setting
|
|
2246
|
+
// `valueFormat` or `logScale` on a direct type silently did nothing.
|
|
2247
|
+
//
|
|
2248
|
+
// The series-shaped settings (trend, averageLine, seriesTypes) stay on the
|
|
2249
|
+
// aggregated path: a direct spec does not have the series shape they read.
|
|
2250
|
+
if (direct) {
|
|
2251
|
+
if (chartIsOhlc) {
|
|
2252
|
+
// Overlays ride on the price series; the panes are composed by the
|
|
2253
|
+
// panel body from `chartIndicatorPanes`.
|
|
2254
|
+
const overlay = chartIndicatorSplit.overlay;
|
|
2255
|
+
if (overlay && direct.series[0])
|
|
2256
|
+
direct.series[0].overlay = overlay;
|
|
2257
|
+
}
|
|
2258
|
+
return applyChartValueSettings(direct);
|
|
2259
|
+
}
|
|
2260
|
+
// A direct type that could not build (scatter with no Y picked yet, for
|
|
2261
|
+
// instance) must not fall through to the bar-chart path and draw the wrong
|
|
2262
|
+
// chart - it renders the panel's empty state instead.
|
|
2263
|
+
if (chartType === "scatter" || chartType === "gauge" || chartType === "boxplot" ||
|
|
2264
|
+
chartType === "histogram" || chartType === "range-bar" || chartType === "range-area" ||
|
|
2265
|
+
chartType === "dumbbell" || chartType === "bullet" || chartIsOhlc)
|
|
2266
|
+
return null;
|
|
2267
|
+
spec = chartEngine.rowsToChartSpec(rowsForChart, {
|
|
2268
|
+
type: chartType,
|
|
2269
|
+
category: category,
|
|
2270
|
+
value: values.length === 1 ? values[0] : values,
|
|
2271
|
+
...(seriesField ? { series: seriesField } : {}),
|
|
2272
|
+
reduce: chartReduce,
|
|
2273
|
+
...(chartBucket && chartDimensionIsDate ? { bucket: chartBucket } : {}),
|
|
2274
|
+
stacked: effectiveChartStacked || effectiveChartStacked100,
|
|
2275
|
+
stacked100: effectiveChartStacked100,
|
|
2276
|
+
...(effectiveChartPalette ? { palette: effectiveChartPalette } : {}),
|
|
2277
|
+
...(chartCfg.topN ? { topN: chartCfg.topN } : {}),
|
|
2278
|
+
...(chartCfg.otherLabel ? { otherLabel: chartCfg.otherLabel } : {}),
|
|
2279
|
+
...(chartCfg.sort ? { sort: chartCfg.sort } : {}),
|
|
2280
|
+
});
|
|
2281
|
+
}
|
|
2013
2282
|
// Reshape the aggregated grid into whatever this type actually reads.
|
|
2014
2283
|
// These run AFTER the aggregation so grouping, reduce, sort, topN and the
|
|
2015
2284
|
// "Other" bucket apply to them exactly as they do to a bar chart.
|
|
2016
|
-
if (chartType === "treemap")
|
|
2285
|
+
if (chartType === "treemap" || chartType === "sunburst")
|
|
2017
2286
|
spec.treemap = chartEngine.specToTreemap(spec);
|
|
2287
|
+
else if (chartType === "chord") {
|
|
2288
|
+
const flow = chartEngine.specToSankey(spec);
|
|
2289
|
+
spec.sankeyNodes = flow.nodes;
|
|
2290
|
+
spec.sankeyLinks = flow.links;
|
|
2291
|
+
}
|
|
2018
2292
|
else if (chartType === "calendar")
|
|
2019
2293
|
spec.calendarValues = chartEngine.specToCalendar(spec);
|
|
2020
2294
|
else if (chartType === "sankey") {
|
|
@@ -2024,13 +2298,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2024
2298
|
}
|
|
2025
2299
|
if (effectiveChartOrientation === "horizontal")
|
|
2026
2300
|
spec.orientation = "horizontal";
|
|
2027
|
-
|
|
2028
|
-
spec.xType = "time";
|
|
2029
|
-
if (effectiveChartLogScale)
|
|
2030
|
-
spec.yScale = "log";
|
|
2031
|
-
if (effectiveChartValueFormat)
|
|
2032
|
-
spec.valueFormat = effectiveChartValueFormat;
|
|
2033
|
-
applyChartLocale(spec);
|
|
2301
|
+
applyChartValueSettings(spec);
|
|
2034
2302
|
if (chartType !== "pie" && spec.orientation !== "horizontal" && !spec.yAxisTitle && chartAutoYAxisTitle) {
|
|
2035
2303
|
spec.yAxisTitle = chartAutoYAxisTitle;
|
|
2036
2304
|
}
|
|
@@ -2043,6 +2311,16 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2043
2311
|
spec.annotations = chartCfg.annotations;
|
|
2044
2312
|
if (chartCfg.patternFallback)
|
|
2045
2313
|
spec.patternFallback = true;
|
|
2314
|
+
if (chartFunnelShape && chartType === "funnel")
|
|
2315
|
+
spec.funnelShape = chartFunnelShape;
|
|
2316
|
+
if (chartCandleStyle && (chartType === "candlestick" || chartType === "ohlc"))
|
|
2317
|
+
spec.candleStyle = chartCandleStyle;
|
|
2318
|
+
// A stream is a stacked area on a wiggle baseline; the picker's Stacked
|
|
2319
|
+
// box has nothing to add, so the offset is applied here outright.
|
|
2320
|
+
if (chartType === "stream") {
|
|
2321
|
+
spec.stacked = true;
|
|
2322
|
+
spec.stackOffset = "wiggle";
|
|
2323
|
+
}
|
|
2046
2324
|
const refLines = [...(chartCfg.referenceLines ?? [])];
|
|
2047
2325
|
if (chartCfg.averageLine && spec.series[0]?.values.length) {
|
|
2048
2326
|
const vals = spec.series[0].values;
|
|
@@ -2052,8 +2330,10 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2052
2330
|
if (refLines.length)
|
|
2053
2331
|
spec.referenceLines = refLines;
|
|
2054
2332
|
for (const s of spec.series) {
|
|
2055
|
-
if (chartCfg.trend)
|
|
2056
|
-
|
|
2333
|
+
if (chartCfg.trend) {
|
|
2334
|
+
const t = chartCfg.trend;
|
|
2335
|
+
s.overlay = t === "sma" || t === "ema" ? `${t}:7` : t === "poly" ? "poly:2" : t;
|
|
2336
|
+
}
|
|
2057
2337
|
if (chartCfg.smooth)
|
|
2058
2338
|
s.smooth = true;
|
|
2059
2339
|
if (chartCfg.seriesTypes?.[s.label])
|
|
@@ -2063,6 +2343,51 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2063
2343
|
}
|
|
2064
2344
|
return spec;
|
|
2065
2345
|
});
|
|
2346
|
+
/**
|
|
2347
|
+
* What the panel renders: an unlinked chart's frozen snapshot, else the live
|
|
2348
|
+
* derivation with the builder's format applied. The format runs through
|
|
2349
|
+
* the lazy engine so its code stays out of the base bundle.
|
|
2350
|
+
*/
|
|
2351
|
+
const chartSpec = $derived.by(() => {
|
|
2352
|
+
if (activeChart.frozen)
|
|
2353
|
+
return activeChart.frozen.spec;
|
|
2354
|
+
const live = chartSpecLive;
|
|
2355
|
+
if (!live || !activeChart.format || !chartEngine)
|
|
2356
|
+
return live;
|
|
2357
|
+
return chartEngine.applyChartFormat(live, activeChart.format);
|
|
2358
|
+
});
|
|
2359
|
+
let announcedFirstChart = false;
|
|
2360
|
+
$effect(() => {
|
|
2361
|
+
if (!chartPanelOpen || announcedFirstChart || !chartCfg?.onChartCreated)
|
|
2362
|
+
return;
|
|
2363
|
+
announcedFirstChart = true;
|
|
2364
|
+
const c = charts[0];
|
|
2365
|
+
untrack(() => chartCfg?.onChartCreated?.({ index: 0, title: c.title, type: c.type, spec: null }));
|
|
2366
|
+
});
|
|
2367
|
+
// Lifecycle: one `onChartChanged` per burst of spec changes.
|
|
2368
|
+
let chartChangeTimer = null;
|
|
2369
|
+
$effect(() => {
|
|
2370
|
+
const cb = chartCfg?.onChartChanged;
|
|
2371
|
+
const spec = chartSpec;
|
|
2372
|
+
const index = activeChartIndex;
|
|
2373
|
+
if (!cb)
|
|
2374
|
+
return;
|
|
2375
|
+
untrack(() => {
|
|
2376
|
+
if (chartChangeTimer)
|
|
2377
|
+
clearTimeout(chartChangeTimer);
|
|
2378
|
+
chartChangeTimer = setTimeout(() => {
|
|
2379
|
+
chartChangeTimer = null;
|
|
2380
|
+
const c = charts[index] ?? activeChart;
|
|
2381
|
+
cb({ index, title: c.title, type: c.type, spec });
|
|
2382
|
+
}, 150);
|
|
2383
|
+
});
|
|
2384
|
+
return () => {
|
|
2385
|
+
if (chartChangeTimer) {
|
|
2386
|
+
clearTimeout(chartChangeTimer);
|
|
2387
|
+
chartChangeTimer = null;
|
|
2388
|
+
}
|
|
2389
|
+
};
|
|
2390
|
+
});
|
|
2066
2391
|
/**
|
|
2067
2392
|
* Can a click on this chart be turned back into a grid filter?
|
|
2068
2393
|
*
|
|
@@ -2071,11 +2396,16 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2071
2396
|
* SOURCE column by a target value and empty the grid), a gauge (no category
|
|
2072
2397
|
* at all), or a scatter (the mark carries a point label, not a group).
|
|
2073
2398
|
*/
|
|
2074
|
-
const chartCrossFilterable = $derived(chartType !== "sankey" && chartType !== "gauge" && chartType !== "scatter"
|
|
2399
|
+
const chartCrossFilterable = $derived(chartType !== "sankey" && chartType !== "gauge" && chartType !== "scatter" &&
|
|
2400
|
+
// A chord's arcs are both ends of a flow, a sunburst's are a hierarchy
|
|
2401
|
+
// and a histogram's are bins: none maps a click back to one dimension value.
|
|
2402
|
+
chartType !== "chord" && chartType !== "sunburst" && chartType !== "histogram");
|
|
2403
|
+
/** The column a chart click filters: the picked dimension, or in pivot mode the innermost row dimension when it is a grid column. */
|
|
2404
|
+
const chartFilterDimId = $derived(pivotResult ? (chartEngine?.pivotFilterColumn(pivotConfig.rows, chartableColumns.dims, fieldOf) ?? null) : effectiveChartDimensionId);
|
|
2075
2405
|
function applyChartCrossFilter(category) {
|
|
2076
2406
|
if (!chartCrossFilterable)
|
|
2077
2407
|
return;
|
|
2078
|
-
const dimId =
|
|
2408
|
+
const dimId = chartFilterDimId;
|
|
2079
2409
|
if (!dimId)
|
|
2080
2410
|
return;
|
|
2081
2411
|
const existing = valueFilters[dimId];
|
|
@@ -2087,7 +2417,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2087
2417
|
valueFilters = { ...valueFilters, [dimId]: next };
|
|
2088
2418
|
}
|
|
2089
2419
|
function clearChartCrossFilter() {
|
|
2090
|
-
const dimId =
|
|
2420
|
+
const dimId = chartFilterDimId;
|
|
2091
2421
|
if (!dimId)
|
|
2092
2422
|
return;
|
|
2093
2423
|
const next = { ...valueFilters };
|
|
@@ -2204,6 +2534,59 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2204
2534
|
const virtualRowStart = $derived.by(() => virtualRows[0]?.start ?? 0);
|
|
2205
2535
|
const virtualRowEnd = $derived.by(() => virtualRows[virtualRows.length - 1]?.end ?? 0);
|
|
2206
2536
|
const virtualRowBottomSpacer = $derived.by(() => Math.max(virtualRowTotalSize - virtualRowEnd, 0));
|
|
2537
|
+
// A row model tells us when to re-read it. Subscribing here (rather than
|
|
2538
|
+
// polling, or asking the consumer to wrap the model in a rune) is what lets
|
|
2539
|
+
// `getRows()` be a plain method call on a plain object.
|
|
2540
|
+
$effect(() => {
|
|
2541
|
+
const model = rawProps.rowModel;
|
|
2542
|
+
if (!model)
|
|
2543
|
+
return;
|
|
2544
|
+
return model.subscribe(() => {
|
|
2545
|
+
rowModelVersion += 1;
|
|
2546
|
+
});
|
|
2547
|
+
});
|
|
2548
|
+
// The index range on screen, for data sources that fetch by block.
|
|
2549
|
+
// `virtualRowStart` / `virtualRowEnd` above are PIXEL offsets; a fetcher
|
|
2550
|
+
// needs row numbers, and needs them whether or not virtualization is on.
|
|
2551
|
+
const visibleRowRange = $derived.by(function visibleRowRange_d() {
|
|
2552
|
+
if (!rowVirtualizationEnabled) {
|
|
2553
|
+
return { startIndex: 0, endIndex: Math.max(0, allRows.length - 1) };
|
|
2554
|
+
}
|
|
2555
|
+
const first = virtualRows[0];
|
|
2556
|
+
const last = virtualRows[virtualRows.length - 1];
|
|
2557
|
+
if (!first || !last)
|
|
2558
|
+
return { startIndex: 0, endIndex: 0 };
|
|
2559
|
+
return { startIndex: first.index, endIndex: last.index };
|
|
2560
|
+
});
|
|
2561
|
+
// Emitted from an effect rather than the derived so a consumer that
|
|
2562
|
+
// fetches (and therefore changes `data`) cannot re-enter the derivation.
|
|
2563
|
+
let lastReportedRange = "";
|
|
2564
|
+
$effect(() => {
|
|
2565
|
+
const report = props.onVisibleRangeChange;
|
|
2566
|
+
if (!report)
|
|
2567
|
+
return;
|
|
2568
|
+
const range = visibleRowRange;
|
|
2569
|
+
const key = range.startIndex + ":" + range.endIndex;
|
|
2570
|
+
if (key === lastReportedRange)
|
|
2571
|
+
return;
|
|
2572
|
+
lastReportedRange = key;
|
|
2573
|
+
report(range);
|
|
2574
|
+
});
|
|
2575
|
+
// A paging row model that asks for `autoPageSize` gets as many rows as
|
|
2576
|
+
// the body shows without scrolling, re-measured as the viewport changes.
|
|
2577
|
+
$effect(() => {
|
|
2578
|
+
rowModelVersion;
|
|
2579
|
+
const paging = rawProps.rowModel?.pagination;
|
|
2580
|
+
if (!paging?.autoPageSize)
|
|
2581
|
+
return;
|
|
2582
|
+
const height = viewportHeight;
|
|
2583
|
+
if (height <= 0)
|
|
2584
|
+
return;
|
|
2585
|
+
const rowPx = rowSizePxOf(0) || 30;
|
|
2586
|
+
const fit = Math.max(1, Math.floor(height / rowPx));
|
|
2587
|
+
if (fit !== paging.pageSize)
|
|
2588
|
+
paging.setPageSize(fit);
|
|
2589
|
+
});
|
|
2207
2590
|
// --- Huge-list scroll scaling -----------------------------------------
|
|
2208
2591
|
// Browsers cap how tall a single element may be, and mobile caps sit well
|
|
2209
2592
|
// below desktop. Past a few hundred thousand rows the true content height
|
|
@@ -2246,9 +2629,50 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2246
2629
|
const logical = virtualizer.getState().scrollOffset;
|
|
2247
2630
|
return logical - rowScrollScaling.logicalToDom(logical);
|
|
2248
2631
|
});
|
|
2632
|
+
/**
|
|
2633
|
+
* The first `frozenRows` rows are rendered ahead of the virtual window
|
|
2634
|
+
* and stick under the header. The virtualizer still counts them, so its
|
|
2635
|
+
* offsets stay in row-index space and nothing downstream has to translate;
|
|
2636
|
+
* the window skips them, and the top spacer gives back the height they
|
|
2637
|
+
* already occupy in the flow.
|
|
2638
|
+
*/
|
|
2639
|
+
const frozenRowCount = $derived(Math.max(0, Math.min(Math.floor(props.frozenRows ?? 0), allRows.length)));
|
|
2640
|
+
const frozenRowList = $derived(frozenRowCount ? allRows.slice(0, frozenRowCount) : []);
|
|
2641
|
+
/** The merged cells, indexed; null (the usual case) costs nothing. */
|
|
2642
|
+
const mergeIndex = $derived(buildMergeIndex(props.mergedCells));
|
|
2643
|
+
const frozenBandHeight = $derived.by(() => {
|
|
2644
|
+
if (!frozenRowCount)
|
|
2645
|
+
return 0;
|
|
2646
|
+
rowResizeVersion;
|
|
2647
|
+
autoRowHeightVersion;
|
|
2648
|
+
let total = 0;
|
|
2649
|
+
for (let i = 0; i < frozenRowCount; i += 1)
|
|
2650
|
+
total += rowSizePxOf(i);
|
|
2651
|
+
return total;
|
|
2652
|
+
});
|
|
2653
|
+
/** The declared or dragged height of one row; measured under auto height. */
|
|
2654
|
+
function rowSizePxOf(i) {
|
|
2655
|
+
if (isRowCollapsed(i))
|
|
2656
|
+
return 0;
|
|
2657
|
+
if (autoRowHeightOn)
|
|
2658
|
+
return measuredRowHeights.get(i) ?? autoRowHeightFallback;
|
|
2659
|
+
const own = rowResizeHeightPx(i);
|
|
2660
|
+
if (own != null)
|
|
2661
|
+
return own;
|
|
2662
|
+
const rh = props.rowHeight;
|
|
2663
|
+
return typeof rh === "function" ? rh(i) : (rh ?? 30);
|
|
2664
|
+
}
|
|
2665
|
+
/** Where frozen row i sticks: under the header and the frozen rows above it. */
|
|
2666
|
+
function frozenRowTop(i) {
|
|
2667
|
+
let top = headerHeight;
|
|
2668
|
+
for (let k = 0; k < i; k += 1)
|
|
2669
|
+
top += rowSizePxOf(k);
|
|
2670
|
+
return top;
|
|
2671
|
+
}
|
|
2249
2672
|
// Spacer heights in DOM space. With scaling inert these equal the original
|
|
2250
|
-
// virtualRowStart / virtualRowBottomSpacer.
|
|
2251
|
-
|
|
2673
|
+
// virtualRowStart / virtualRowBottomSpacer. The frozen band is in the flow
|
|
2674
|
+
// before the spacer whatever the window shows, so it comes off the spacer.
|
|
2675
|
+
const rowTopSpacer = $derived(Math.max(virtualRowStart - rowOffsetAdjustment - frozenBandHeight, 0));
|
|
2252
2676
|
const rowBottomSpacer = $derived(Math.max(rowDomTotalSize - (virtualRowEnd - rowOffsetAdjustment), 0));
|
|
2253
2677
|
const virtualColumns = $derived.by(function virtualColumns_d() {
|
|
2254
2678
|
columnVirtualizerVersion;
|
|
@@ -2585,35 +3009,117 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2585
3009
|
let autoRowHeightVersion = $state(0);
|
|
2586
3010
|
const autoRowHeightFallback = $derived(typeof props.rowHeight === "number" ? props.rowHeight : 30);
|
|
2587
3011
|
// ----- Interactive row resize (`rowResize` prop) -----
|
|
2588
|
-
// Heights the user has dragged,
|
|
2589
|
-
//
|
|
2590
|
-
//
|
|
2591
|
-
//
|
|
2592
|
-
//
|
|
3012
|
+
// Heights the user has dragged, or set through `api.setRowHeight`, keyed
|
|
3013
|
+
// by ROW ID so they belong to the row rather than to a slot: the height
|
|
3014
|
+
// follows its row through a sort or a filter and survives the data being
|
|
3015
|
+
// replaced. They used to be keyed by index and wiped on every data change,
|
|
3016
|
+
// which in a spreadsheet meant every keystroke, so a row dragged taller
|
|
3017
|
+
// snapped back the moment the next cell was typed. A plain Map plus a
|
|
3018
|
+
// version counter, like `measuredRowHeights` above: the readers touch the
|
|
3019
|
+
// version, and the Map itself stays out of the reactive graph.
|
|
2593
3020
|
const rowResizeHeights = new Map();
|
|
2594
3021
|
let rowResizeVersion = $state(0);
|
|
2595
|
-
|
|
2596
|
-
|
|
3022
|
+
/**
|
|
3023
|
+
* Rows folded to nothing, the row-side twin of `collapsedColumns`: the
|
|
3024
|
+
* row keeps its index and its cells and takes no height. Keyed by row id
|
|
3025
|
+
* like the heights and announced through the same version counter, so
|
|
3026
|
+
* every reader of a row's size sees the change.
|
|
3027
|
+
*/
|
|
3028
|
+
const collapsedRows = new Set();
|
|
3029
|
+
function isRowCollapsed(index) {
|
|
3030
|
+
rowResizeVersion;
|
|
3031
|
+
const id = allRows[index]?.id;
|
|
3032
|
+
return id != null && collapsedRows.has(id);
|
|
3033
|
+
}
|
|
3034
|
+
function setRowCollapsed(index, collapsed) {
|
|
3035
|
+
const id = allRows[index]?.id;
|
|
3036
|
+
if (id == null)
|
|
3037
|
+
return;
|
|
3038
|
+
if (collapsed === collapsedRows.has(id))
|
|
2597
3039
|
return;
|
|
2598
|
-
|
|
3040
|
+
if (collapsed)
|
|
3041
|
+
collapsedRows.add(id);
|
|
3042
|
+
else
|
|
3043
|
+
collapsedRows.delete(id);
|
|
2599
3044
|
rowResizeVersion += 1;
|
|
2600
3045
|
}
|
|
2601
|
-
/**
|
|
2602
|
-
*
|
|
2603
|
-
|
|
2604
|
-
|
|
3046
|
+
/**
|
|
3047
|
+
* A size the user changed by hand is one undo step, as it is in a
|
|
3048
|
+
* spreadsheet: Ctrl+Z after a column drag puts the width back. Recorded
|
|
3049
|
+
* only for the user's gestures; the api's own setters record nothing, so
|
|
3050
|
+
* a consumer restoring saved sizes does not fill the history.
|
|
3051
|
+
*/
|
|
3052
|
+
function recordSizeUndo(undo, redo) {
|
|
3053
|
+
pushHistory(ctx, [
|
|
3054
|
+
{ rowId: "", columnId: "", field: "", before: undefined, after: undefined, custom: { undo, redo } },
|
|
3055
|
+
]);
|
|
2605
3056
|
}
|
|
2606
|
-
//
|
|
2607
|
-
//
|
|
2608
|
-
//
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
3057
|
+
// The undo and redo of a size step report through the same callbacks as
|
|
3058
|
+
// the gesture did: a consumer keeping the sizes (a sheet saving its
|
|
3059
|
+
// document) would otherwise hold the width the user dragged to while the
|
|
3060
|
+
// grid showed the one Ctrl+Z put back.
|
|
3061
|
+
function resizeColumnByUser(columnId, width, before) {
|
|
3062
|
+
const apply = (w) => {
|
|
3063
|
+
columnWidths = { ...columnWidths, [columnId]: w };
|
|
3064
|
+
props.onColumnResize?.({ columnId, width: w });
|
|
3065
|
+
};
|
|
3066
|
+
apply(width);
|
|
3067
|
+
if (before !== width)
|
|
3068
|
+
recordSizeUndo(() => apply(before), () => apply(width));
|
|
3069
|
+
}
|
|
3070
|
+
function autosizeColumnByUser(columnId) {
|
|
3071
|
+
const before = getColumnWidth(columnId);
|
|
3072
|
+
autosizeColumn(columnId);
|
|
3073
|
+
const width = getColumnWidth(columnId);
|
|
3074
|
+
const apply = (w) => {
|
|
3075
|
+
columnWidths = { ...columnWidths, [columnId]: w };
|
|
3076
|
+
props.onColumnResize?.({ columnId, width: w });
|
|
3077
|
+
};
|
|
3078
|
+
if (before !== width)
|
|
3079
|
+
recordSizeUndo(() => apply(before), () => apply(width));
|
|
3080
|
+
else
|
|
3081
|
+
props.onColumnResize?.({ columnId, width });
|
|
3082
|
+
}
|
|
3083
|
+
function resizeRowByUser(index, height) {
|
|
3084
|
+
const before = rowResizeHeightPx(index) ?? null;
|
|
3085
|
+
const apply = (h) => {
|
|
3086
|
+
setRowResizeHeight(index, h);
|
|
3087
|
+
props.onRowResize?.({ rowIndex: index, height: h });
|
|
3088
|
+
};
|
|
3089
|
+
apply(height);
|
|
3090
|
+
if (before !== height)
|
|
3091
|
+
recordSizeUndo(() => apply(before), () => apply(height));
|
|
3092
|
+
}
|
|
3093
|
+
function setRowResizeHeight(index, height) {
|
|
3094
|
+
const id = allRows[index]?.id;
|
|
3095
|
+
if (id == null)
|
|
2613
3096
|
return;
|
|
2614
|
-
|
|
3097
|
+
if (height == null) {
|
|
3098
|
+
if (!rowResizeHeights.delete(id))
|
|
3099
|
+
return;
|
|
3100
|
+
}
|
|
3101
|
+
else {
|
|
3102
|
+
if (!Number.isFinite(height) || height <= 0)
|
|
3103
|
+
return;
|
|
3104
|
+
rowResizeHeights.set(id, Math.round(height));
|
|
3105
|
+
}
|
|
2615
3106
|
rowResizeVersion += 1;
|
|
2616
|
-
}
|
|
3107
|
+
}
|
|
3108
|
+
/** A row's own height, or undefined when it has none. Read by the view AND
|
|
3109
|
+
* by the virtualizer's `estimateSize`. Auto height wins over it: the
|
|
3110
|
+
* content decides the height there, and a manual one would be overwritten
|
|
3111
|
+
* by the next measurement. */
|
|
3112
|
+
function rowResizeHeightPx(index) {
|
|
3113
|
+
const id = allRows[index]?.id;
|
|
3114
|
+
if (id == null)
|
|
3115
|
+
return undefined;
|
|
3116
|
+
// A collapsed row is 0 whatever else it has, under auto height too.
|
|
3117
|
+
if (collapsedRows.has(id))
|
|
3118
|
+
return 0;
|
|
3119
|
+
if (autoRowHeightOn)
|
|
3120
|
+
return undefined;
|
|
3121
|
+
return rowResizeHeights.get(id);
|
|
3122
|
+
}
|
|
2617
3123
|
/** Report a row's measured height. Called by the view's measuring action.
|
|
2618
3124
|
* Sub-pixel churn is ignored so a fractional layout can't loop. */
|
|
2619
3125
|
function reportRowHeight(index, height) {
|
|
@@ -2638,12 +3144,20 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2638
3144
|
function measureRowHeight(node, index) {
|
|
2639
3145
|
let current = index;
|
|
2640
3146
|
let stop = null;
|
|
2641
|
-
|
|
3147
|
+
// Read the rect only under autoRowHeight. `getBoundingClientRect` forces a
|
|
3148
|
+
// synchronous layout, and the virtualizer's slot keys hand every rendered
|
|
3149
|
+
// <tr> a new index on each one-row scroll, so measuring unconditionally
|
|
3150
|
+
// meant ~30 forced layouts per arrow key on a fixed-height grid whose
|
|
3151
|
+
// measurement `reportRowHeight` then threw away.
|
|
3152
|
+
const measure = () => {
|
|
3153
|
+
if (!autoRowHeightOn)
|
|
3154
|
+
return;
|
|
3155
|
+
reportRowHeight(current, node.getBoundingClientRect().height);
|
|
3156
|
+
};
|
|
2642
3157
|
const attach = () => {
|
|
2643
3158
|
stop?.();
|
|
2644
3159
|
stop = autoRowHeightOn ? observeSizeRaf(node, measure) : null;
|
|
2645
|
-
|
|
2646
|
-
measure();
|
|
3160
|
+
measure();
|
|
2647
3161
|
};
|
|
2648
3162
|
attach();
|
|
2649
3163
|
return {
|
|
@@ -2694,8 +3208,8 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2694
3208
|
: typeof rh === "function"
|
|
2695
3209
|
? rh
|
|
2696
3210
|
: (rh ?? 30);
|
|
2697
|
-
const estimateSize =
|
|
2698
|
-
? (index) =>
|
|
3211
|
+
const estimateSize = rowResizeHeights.size > 0 || collapsedRows.size > 0
|
|
3212
|
+
? (index) => rowResizeHeightPx(index) ??
|
|
2699
3213
|
(typeof base === "function" ? base(index) : base)
|
|
2700
3214
|
: base;
|
|
2701
3215
|
virtualizer.setOptions({
|
|
@@ -2755,6 +3269,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2755
3269
|
// closure each run; the virtualizer sees a new function reference and
|
|
2756
3270
|
// re-derives its layout from the current per-column widths.
|
|
2757
3271
|
columnWidths;
|
|
3272
|
+
collapsedColumns;
|
|
2758
3273
|
columnVirtualizer.setOptions({
|
|
2759
3274
|
count: allColumns.length,
|
|
2760
3275
|
estimateSize: (index) => {
|
|
@@ -2795,9 +3310,17 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2795
3310
|
// Lets the resolver answer from cache without re-invoking the source, so an
|
|
2796
3311
|
// async source fires one request per row instead of one per render.
|
|
2797
3312
|
const asyncEditorColumns = new Set();
|
|
3313
|
+
// Depends on `allRows` and `rowSelectionState` only. It used to read the
|
|
3314
|
+
// catch-all `gridStateVersion` too, which made every arrow key walk all the
|
|
3315
|
+
// rows (10k iterations on the large-dataset demo, 100k on its top tier) to
|
|
3316
|
+
// recount a selection that had not changed.
|
|
2798
3317
|
const headerSelectionState = $derived.by(function headerSelectionState_d() {
|
|
2799
|
-
|
|
2800
|
-
|
|
3318
|
+
// An external model knows about rows the grid has never seen, so it is
|
|
3319
|
+
// the only thing that can answer honestly once the data is a window
|
|
3320
|
+
// onto a server.
|
|
3321
|
+
if (props.rowSelectionModel)
|
|
3322
|
+
return props.rowSelectionModel.headerState();
|
|
3323
|
+
const selectable = allRows.filter((row) => !isGroupRow(row) && !placeholderStateOf(row));
|
|
2801
3324
|
if (!selectable.length)
|
|
2802
3325
|
return "none";
|
|
2803
3326
|
let selected = 0;
|
|
@@ -2815,6 +3338,10 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2815
3338
|
* from a user-focused (0,0). The fill handle keys off this flag so it
|
|
2816
3339
|
* stays hidden until the user actually selects something. */
|
|
2817
3340
|
let userHasActivatedCell = $state(false);
|
|
3341
|
+
/** The column a run of Tabs began in, so the Enter that ends the run goes
|
|
3342
|
+
* down from there (`getEntryStep`). Plain, not state: nothing renders
|
|
3343
|
+
* it. `setActiveCell` clears it; the Tab and Enter paths set it back. */
|
|
3344
|
+
let tabRunOrigin = null;
|
|
2818
3345
|
/**
|
|
2819
3346
|
* Per-column fitted widths when `fitColumns` is on. Computed in one pass
|
|
2820
3347
|
* so the LAST auto-sized column can absorb the rounding residue and make
|
|
@@ -2837,7 +3364,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2837
3364
|
return null;
|
|
2838
3365
|
// Exclude columns hidden by a collapsed group too (#57) - otherwise their
|
|
2839
3366
|
// widths stay in the fit distribution and leave a blank gap at the edge.
|
|
2840
|
-
const cols = grid.getAllColumns().filter((c) => !hiddenColumns[c.id] && !hiddenByGroupCollapse[c.id]);
|
|
3367
|
+
const cols = grid.getAllColumns().filter((c) => !hiddenColumns[c.id] && !hiddenByGroupCollapse[c.id] && !collapsedColumns[c.id]);
|
|
2841
3368
|
if (!cols.length)
|
|
2842
3369
|
return null;
|
|
2843
3370
|
const rowNumberWidth = showRowNumbersEffective ? rowNumberColumnWidth : 0;
|
|
@@ -2946,7 +3473,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2946
3473
|
const meta = isBucketableColumn(column);
|
|
2947
3474
|
if (!meta)
|
|
2948
3475
|
continue;
|
|
2949
|
-
const buckets = buildBuckets(column, meta.isDate, props.data, getColumnAccessorValue);
|
|
3476
|
+
const buckets = buildBuckets(column, meta.isDate, props.data ?? EMPTY_DATA, getColumnAccessorValue);
|
|
2950
3477
|
if (buckets)
|
|
2951
3478
|
map.set(column.id, buckets);
|
|
2952
3479
|
}
|
|
@@ -3170,6 +3697,17 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3170
3697
|
// state mutation inside the callback (e.g. `api.setGroupBy(...)`) created
|
|
3171
3698
|
// an infinite update loop. Now it's a true mount-once notification.
|
|
3172
3699
|
let apiNotified = false;
|
|
3700
|
+
// Memoized: `buildApi()` builds a fresh object each call, and a feature pack
|
|
3701
|
+
// that keys per-grid state on the api (a WeakMap in @svgrid/enterprise) needs
|
|
3702
|
+
// the object the consumer received and the object a command handler sees to
|
|
3703
|
+
// be the same one. Every method reads through `ctx`, so one instance stays
|
|
3704
|
+
// correct for the life of the grid.
|
|
3705
|
+
let apiInstance = null;
|
|
3706
|
+
function api() {
|
|
3707
|
+
if (!apiInstance)
|
|
3708
|
+
apiInstance = buildApi();
|
|
3709
|
+
return apiInstance;
|
|
3710
|
+
}
|
|
3173
3711
|
$effect(() => {
|
|
3174
3712
|
if (apiNotified)
|
|
3175
3713
|
return;
|
|
@@ -3177,7 +3715,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3177
3715
|
if (!cb)
|
|
3178
3716
|
return;
|
|
3179
3717
|
apiNotified = true;
|
|
3180
|
-
cb(
|
|
3718
|
+
cb(api());
|
|
3181
3719
|
});
|
|
3182
3720
|
const ctx = {
|
|
3183
3721
|
get props() { return props; },
|
|
@@ -3239,6 +3777,8 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3239
3777
|
set history(v) { history = v; },
|
|
3240
3778
|
get historyPtr() { return historyPtr; },
|
|
3241
3779
|
set historyPtr(v) { historyPtr = v; },
|
|
3780
|
+
get historyGroupId() { return historyGroupId; },
|
|
3781
|
+
set historyGroupId(v) { historyGroupId = v; },
|
|
3242
3782
|
get historyVersion() { return historyVersion; },
|
|
3243
3783
|
set historyVersion(v) { historyVersion = v; },
|
|
3244
3784
|
get tooltip() { return tooltip; },
|
|
@@ -3261,6 +3801,9 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3261
3801
|
get editorSelectAll() { return editorSelectAll; },
|
|
3262
3802
|
set editorSelectAll(v) { editorSelectAll = v; },
|
|
3263
3803
|
get columnWidths() { return columnWidths; },
|
|
3804
|
+
get collapsedColumns() { return collapsedColumns; },
|
|
3805
|
+
get mergeIndex() { return mergeIndex; },
|
|
3806
|
+
get setColumnCollapsed() { return setColumnCollapsed; },
|
|
3264
3807
|
set columnWidths(v) { columnWidths = v; },
|
|
3265
3808
|
get MIN_COLUMN_WIDTH() { return MIN_COLUMN_WIDTH; },
|
|
3266
3809
|
get columnPinning() { return columnPinning; },
|
|
@@ -3332,6 +3875,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3332
3875
|
get moveCellsEffective() { return moveCellsEffective; },
|
|
3333
3876
|
get flushScheduledScrollSync() { return flushScheduledScrollSync; },
|
|
3334
3877
|
get scheduleScrollSync() { return scheduleScrollSync; },
|
|
3878
|
+
get syncScrollNow() { return syncScrollNow; },
|
|
3335
3879
|
get internalData() { return internalData; },
|
|
3336
3880
|
set internalData(v) { internalData = v; },
|
|
3337
3881
|
get internalColumns() { return internalColumns; },
|
|
@@ -3417,6 +3961,8 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3417
3961
|
get selectionBarMaxVisible() { return selectionBarMaxVisible; },
|
|
3418
3962
|
get selectionBarHideClear() { return selectionBarHideClear; },
|
|
3419
3963
|
get selectionBarTarget() { return selectionBarTarget; },
|
|
3964
|
+
get selectionBarCount() { return selectionBarCount; },
|
|
3965
|
+
get selectionBarVisible() { return selectionBarVisible; },
|
|
3420
3966
|
get lastSelectionSerialized() { return lastSelectionSerialized; },
|
|
3421
3967
|
set lastSelectionSerialized(v) { lastSelectionSerialized = v; },
|
|
3422
3968
|
get lastCellRangeSerialized() { return lastCellRangeSerialized; },
|
|
@@ -3435,6 +3981,8 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3435
3981
|
get moveColumnInPanel() { return moveColumnInPanel; },
|
|
3436
3982
|
// ---- Localized chrome strings ----
|
|
3437
3983
|
get messages() { return gridMessages; },
|
|
3984
|
+
/** The raw `localization.text` map, for the lazy chart panel's own strings. */
|
|
3985
|
+
get localizationText() { return props.localization?.text; },
|
|
3438
3986
|
// ---- In-grid pivot mode ----
|
|
3439
3987
|
get pivotConfig() { return pivotConfig; },
|
|
3440
3988
|
get pivotActive() { return pivotActive; },
|
|
@@ -3467,6 +4015,35 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3467
4015
|
set chartLogScale(v) { activeChart.logScale = v; },
|
|
3468
4016
|
get chartTimeAxis() { return effectiveChartTimeAxis; },
|
|
3469
4017
|
set chartTimeAxis(v) { activeChart.timeAxis = v; },
|
|
4018
|
+
get chartBucket() { return chartBucket; },
|
|
4019
|
+
set chartBucket(v) { activeChart.bucket = v; },
|
|
4020
|
+
get chartBins() { return chartBins; },
|
|
4021
|
+
set chartBins(v) { activeChart.bins = v; },
|
|
4022
|
+
get chartFunnelShape() { return chartFunnelShape; },
|
|
4023
|
+
set chartFunnelShape(v) { activeChart.funnelShape = v; },
|
|
4024
|
+
get chartCandleStyle() { return chartCandleStyle; },
|
|
4025
|
+
set chartCandleStyle(v) { activeChart.candleStyle = v; },
|
|
4026
|
+
get chartFormat() { return activeChart.format; },
|
|
4027
|
+
set chartFormat(v) { activeChart.format = v; },
|
|
4028
|
+
get chartFrozen() { return activeChart.frozen; },
|
|
4029
|
+
get chartIsOhlc() { return chartIsOhlc; },
|
|
4030
|
+
get chartOhlc() { return effectiveChartOhlc; },
|
|
4031
|
+
set chartOhlc(v) { activeChart.ohlc = v; },
|
|
4032
|
+
get chartOhlcDateId() { return effectiveChartOhlcDateId; },
|
|
4033
|
+
get chartIndicators() { return chartIndicators; },
|
|
4034
|
+
set chartIndicators(v) { activeChart.indicators = v; },
|
|
4035
|
+
get chartIndicatorPanes() { return chartIndicatorPanes; },
|
|
4036
|
+
get chartZoom() { return activeChart.zoom; },
|
|
4037
|
+
// The panel keeps one chart component across tabs, so "tabs share a
|
|
4038
|
+
// window" is simply every tab carrying the same window: a zoom on one is
|
|
4039
|
+
// written to all, and switching tabs hands the component the same value.
|
|
4040
|
+
set chartZoom(v) {
|
|
4041
|
+
if (chartCfg?.syncTabs)
|
|
4042
|
+
for (const c of charts)
|
|
4043
|
+
c.zoom = v;
|
|
4044
|
+
else
|
|
4045
|
+
activeChart.zoom = v;
|
|
4046
|
+
},
|
|
3470
4047
|
get chartValueFormat() { return effectiveChartValueFormat ?? "number"; },
|
|
3471
4048
|
set chartValueFormat(v) { activeChart.valueFormat = v; },
|
|
3472
4049
|
get chartDimensionIsDate() { return chartDimensionIsDate; },
|
|
@@ -3487,8 +4064,23 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3487
4064
|
get activeChartIndex() { return Math.min(activeChartIndex, charts.length - 1); },
|
|
3488
4065
|
set activeChartIndex(v) { activeChartIndex = Math.max(0, Math.min(charts.length - 1, v)); },
|
|
3489
4066
|
addChart() {
|
|
3490
|
-
|
|
4067
|
+
const next = makeChart(`Chart ${charts.length + 1}`);
|
|
4068
|
+
if (chartCfg?.syncTabs)
|
|
4069
|
+
next.zoom = activeChart.zoom;
|
|
4070
|
+
charts = [...charts, next];
|
|
3491
4071
|
activeChartIndex = charts.length - 1;
|
|
4072
|
+
chartCfg?.onChartCreated?.({ index: activeChartIndex, title: next.title, type: next.type, spec: null });
|
|
4073
|
+
},
|
|
4074
|
+
/** Unlink the active chart from the grid: it keeps the spec it has now. */
|
|
4075
|
+
freezeChart() {
|
|
4076
|
+
const spec = chartSpec;
|
|
4077
|
+
if (!spec)
|
|
4078
|
+
return;
|
|
4079
|
+
activeChart.frozen = { spec: JSON.parse(JSON.stringify(spec)), at: new Date().toISOString() };
|
|
4080
|
+
},
|
|
4081
|
+
/** Link the active chart back to the grid's rows. */
|
|
4082
|
+
unfreezeChart() {
|
|
4083
|
+
activeChart.frozen = null;
|
|
3492
4084
|
},
|
|
3493
4085
|
removeChart(index) {
|
|
3494
4086
|
if (charts.length <= 1)
|
|
@@ -3504,48 +4096,40 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3504
4096
|
c.title = title;
|
|
3505
4097
|
},
|
|
3506
4098
|
getChartsState() {
|
|
3507
|
-
return charts.map(
|
|
3508
|
-
title: c.title,
|
|
3509
|
-
type: c.type,
|
|
3510
|
-
dimension: c.dimensionId,
|
|
3511
|
-
series: c.seriesId ?? null,
|
|
3512
|
-
measure: c.measureId,
|
|
3513
|
-
measure2: c.measure2Id,
|
|
3514
|
-
orientation: c.orientation,
|
|
3515
|
-
stacked100: c.stacked100,
|
|
3516
|
-
donut: c.donut,
|
|
3517
|
-
palette: c.palette,
|
|
3518
|
-
reduce: c.reduce,
|
|
3519
|
-
stacked: c.stacked,
|
|
3520
|
-
dataLabels: c.dataLabels,
|
|
3521
|
-
logScale: c.logScale,
|
|
3522
|
-
timeAxis: c.timeAxis,
|
|
3523
|
-
valueFormat: c.valueFormat,
|
|
3524
|
-
}));
|
|
4099
|
+
return charts.map(tabToState);
|
|
3525
4100
|
},
|
|
3526
4101
|
applyChartsState(list, active) {
|
|
3527
|
-
const restored = list.map(
|
|
3528
|
-
id: `chart-${chartSeq++}`,
|
|
3529
|
-
title: typeof c.title === "string" ? c.title : `Chart ${i + 1}`,
|
|
3530
|
-
type: c.type ?? "bar",
|
|
3531
|
-
reduce: c.reduce ?? "sum",
|
|
3532
|
-
dimensionId: c.dimension ?? null,
|
|
3533
|
-
measureId: c.measure ?? null,
|
|
3534
|
-
seriesId: c.series,
|
|
3535
|
-
measure2Id: c.measure2 ?? null,
|
|
3536
|
-
orientation: c.orientation ?? null,
|
|
3537
|
-
stacked100: c.stacked100 ?? null,
|
|
3538
|
-
donut: c.donut ?? null,
|
|
3539
|
-
palette: c.palette ?? null,
|
|
3540
|
-
stacked: c.stacked ?? null,
|
|
3541
|
-
dataLabels: c.dataLabels ?? null,
|
|
3542
|
-
logScale: c.logScale ?? null,
|
|
3543
|
-
timeAxis: c.timeAxis ?? null,
|
|
3544
|
-
valueFormat: c.valueFormat ?? null,
|
|
3545
|
-
}));
|
|
4102
|
+
const restored = list.map(tabFromState);
|
|
3546
4103
|
charts = restored.length ? restored : [makeChart("Chart 1")];
|
|
3547
4104
|
activeChartIndex = Math.max(0, Math.min(charts.length - 1, active ?? 0));
|
|
3548
4105
|
},
|
|
4106
|
+
// ---- Saved charts ----
|
|
4107
|
+
get savedCharts() { return savedCharts; },
|
|
4108
|
+
/** Keep the active tab's configuration under a name (replacing a same-named one). */
|
|
4109
|
+
saveChart(name) {
|
|
4110
|
+
const entry = { name, savedAt: new Date().toISOString(), tab: tabToState(activeChart) };
|
|
4111
|
+
savedCharts = [...savedCharts.filter((s) => s.name !== name), entry];
|
|
4112
|
+
},
|
|
4113
|
+
/** Apply a saved chart to the active tab; the tab keeps its id and title. */
|
|
4114
|
+
applySavedChart(name) {
|
|
4115
|
+
const entry = savedCharts.find((s) => s.name === name);
|
|
4116
|
+
if (!entry)
|
|
4117
|
+
return false;
|
|
4118
|
+
const { id: _id, title: _title, ...rest } = tabFromState(entry.tab, activeChartIndex);
|
|
4119
|
+
Object.assign(activeChart, rest);
|
|
4120
|
+
return true;
|
|
4121
|
+
},
|
|
4122
|
+
removeSavedChart(name) {
|
|
4123
|
+
savedCharts = savedCharts.filter((s) => s.name !== name);
|
|
4124
|
+
},
|
|
4125
|
+
getSavedCharts() {
|
|
4126
|
+
return savedCharts.map((s) => ({ ...s, tab: { ...s.tab } }));
|
|
4127
|
+
},
|
|
4128
|
+
applySavedCharts(list) {
|
|
4129
|
+
savedCharts = list
|
|
4130
|
+
.filter((s) => typeof s?.name === "string" && s.tab && typeof s.tab === "object")
|
|
4131
|
+
.map((s) => ({ name: s.name, savedAt: String(s.savedAt ?? ""), tab: s.tab }));
|
|
4132
|
+
},
|
|
3549
4133
|
get chartCrossFilterable() { return chartCrossFilterable; },
|
|
3550
4134
|
get chartOrientation() { return effectiveChartOrientation; },
|
|
3551
4135
|
set chartOrientation(v) { activeChart.orientation = v; },
|
|
@@ -3560,7 +4144,11 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3560
4144
|
get effectiveChartMeasure2Id() { return effectiveChartMeasure2Id; },
|
|
3561
4145
|
get chartAiHandler() { return chartAiHandler; },
|
|
3562
4146
|
set chartAiHandler(v) { chartAiHandler = v; },
|
|
4147
|
+
get chartExplainHandler() { return chartExplainHandler; },
|
|
4148
|
+
set chartExplainHandler(v) { chartExplainHandler = v; },
|
|
3563
4149
|
applyChartConfig(config) {
|
|
4150
|
+
if (config.saved)
|
|
4151
|
+
this.applySavedChart(config.saved);
|
|
3564
4152
|
const cols = [...chartableColumns.dims, ...chartableColumns.measures];
|
|
3565
4153
|
const resolve = (v) => {
|
|
3566
4154
|
if (v == null)
|
|
@@ -3593,6 +4181,33 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3593
4181
|
activeChart.timeAxis = config.timeAxis;
|
|
3594
4182
|
if (config.valueFormat)
|
|
3595
4183
|
activeChart.valueFormat = config.valueFormat;
|
|
4184
|
+
if ("bucket" in config)
|
|
4185
|
+
activeChart.bucket = config.bucket ?? null;
|
|
4186
|
+
if ("bins" in config)
|
|
4187
|
+
activeChart.bins = config.bins ?? null;
|
|
4188
|
+
if ("funnelShape" in config)
|
|
4189
|
+
activeChart.funnelShape = config.funnelShape ?? null;
|
|
4190
|
+
if ("candleStyle" in config)
|
|
4191
|
+
activeChart.candleStyle = config.candleStyle ?? null;
|
|
4192
|
+
if ("measure2" in config)
|
|
4193
|
+
activeChart.measure2Id = resolve(config.measure2);
|
|
4194
|
+
if ("ohlc" in config) {
|
|
4195
|
+
const o = config.ohlc;
|
|
4196
|
+
activeChart.ohlc = o
|
|
4197
|
+
? { open: resolve(o.open), high: resolve(o.high), low: resolve(o.low), close: resolve(o.close), volume: resolve(o.volume) }
|
|
4198
|
+
: null;
|
|
4199
|
+
}
|
|
4200
|
+
if (config.indicators)
|
|
4201
|
+
activeChart.indicators = [...config.indicators];
|
|
4202
|
+
if ("format" in config)
|
|
4203
|
+
activeChart.format = config.format ?? null;
|
|
4204
|
+
if (config.frozen === false)
|
|
4205
|
+
activeChart.frozen = null;
|
|
4206
|
+
else if (config.frozen === true && !activeChart.frozen) {
|
|
4207
|
+
const spec = chartSpec;
|
|
4208
|
+
if (spec)
|
|
4209
|
+
activeChart.frozen = { spec: JSON.parse(JSON.stringify(spec)), at: new Date().toISOString() };
|
|
4210
|
+
}
|
|
3596
4211
|
},
|
|
3597
4212
|
get toggleGroupInPanel() { return toggleGroupInPanel; },
|
|
3598
4213
|
get lastSortingSerialized() { return lastSortingSerialized; },
|
|
@@ -3615,8 +4230,17 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3615
4230
|
},
|
|
3616
4231
|
/** True when the `rowResize` prop is on and nothing overrides it. */
|
|
3617
4232
|
get rowResizeOn() { return rowResizeOn; },
|
|
4233
|
+
get frozenRowCount() { return frozenRowCount; },
|
|
4234
|
+
get frozenRowList() { return frozenRowList; },
|
|
4235
|
+
get frozenBandHeight() { return frozenBandHeight; },
|
|
4236
|
+
get frozenRowTop() { return frozenRowTop; },
|
|
3618
4237
|
/** Record the height the user dragged a row to. */
|
|
3619
4238
|
get setRowResizeHeight() { return setRowResizeHeight; },
|
|
4239
|
+
get isRowCollapsed() { return isRowCollapsed; },
|
|
4240
|
+
get setRowCollapsed() { return setRowCollapsed; },
|
|
4241
|
+
get resizeRowByUser() { return resizeRowByUser; },
|
|
4242
|
+
get resizeColumnByUser() { return resizeColumnByUser; },
|
|
4243
|
+
get autosizeColumnByUser() { return autosizeColumnByUser; },
|
|
3620
4244
|
/** A row's dragged height, or undefined when it has not been resized. */
|
|
3621
4245
|
get rowResizeHeightPx() {
|
|
3622
4246
|
rowResizeVersion;
|
|
@@ -3676,9 +4300,18 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3676
4300
|
get isRowSelected() { return isRowSelected; },
|
|
3677
4301
|
get toggleRowSelectionById() { return toggleRowSelectionById; },
|
|
3678
4302
|
get headerSelectionState() { return headerSelectionState; },
|
|
4303
|
+
get visibleRowRange() { return visibleRowRange; },
|
|
4304
|
+
placeholderStateOf,
|
|
4305
|
+
/** Retry a failed placeholder row. No-op without `onRetryRow`. */
|
|
4306
|
+
retryRow(row) {
|
|
4307
|
+
props.onRetryRow?.(row.original, row.index);
|
|
4308
|
+
},
|
|
3679
4309
|
get toggleSelectAllRows() { return toggleSelectAllRows; },
|
|
4310
|
+
get setSelectAllRows() { return setSelectAllRows; },
|
|
3680
4311
|
get userHasActivatedCell() { return userHasActivatedCell; },
|
|
3681
4312
|
set userHasActivatedCell(v) { userHasActivatedCell = v; },
|
|
4313
|
+
get tabRunOrigin() { return tabRunOrigin; },
|
|
4314
|
+
set tabRunOrigin(v) { tabRunOrigin = v; },
|
|
3682
4315
|
get setActiveCell() { return setActiveCell; },
|
|
3683
4316
|
get scrollActiveCellIntoView() { return scrollActiveCellIntoView; },
|
|
3684
4317
|
get getColumnBaseWidth() { return getColumnBaseWidth; },
|
|
@@ -3688,6 +4321,8 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3688
4321
|
get extendSelection() { return extendSelection; },
|
|
3689
4322
|
get isCellInSelectedRange() { return isCellInSelectedRange; },
|
|
3690
4323
|
get getCellRangeEdges() { return getCellRangeEdges; },
|
|
4324
|
+
get activeRangeRect() { return activeRangeRect; },
|
|
4325
|
+
get cellSelectionState() { return cellSelectionState; },
|
|
3691
4326
|
get getSelectionRects() { return getSelectionRects; },
|
|
3692
4327
|
get fillHandleCell() { return fillHandleCell; },
|
|
3693
4328
|
get isInFillPreview() { return isInFillPreview; },
|
|
@@ -3710,6 +4345,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3710
4345
|
get startFillDrag() { return startFillDrag; },
|
|
3711
4346
|
get onFillPointerMove() { return onFillPointerMove; },
|
|
3712
4347
|
get onFillPointerUp() { return onFillPointerUp; },
|
|
4348
|
+
get fillDownToNeighbour() { return fillDownToNeighbour; },
|
|
3713
4349
|
get isOnMoveGrabStrip() { return isOnMoveGrabStrip; },
|
|
3714
4350
|
get startMoveDrag() { return startMoveDrag; },
|
|
3715
4351
|
get onMovePointerMove() { return onMovePointerMove; },
|
|
@@ -3729,6 +4365,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3729
4365
|
get cutSelectionToClipboard() { return cutSelectionToClipboard; },
|
|
3730
4366
|
get pasteFromClipboard() { return pasteFromClipboard; },
|
|
3731
4367
|
get onGridPaste() { return onGridPaste; },
|
|
4368
|
+
get armPasteFallback() { return armPasteFallback; },
|
|
3732
4369
|
get clearSelectedCells() { return clearSelectedCells; },
|
|
3733
4370
|
get onCellDoubleClick() { return onCellDoubleClick; },
|
|
3734
4371
|
get startEditingWithChar() { return startEditingWithChar; },
|
|
@@ -3805,12 +4442,12 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3805
4442
|
get clearColumnFilter() { return clearColumnFilter; },
|
|
3806
4443
|
get onWindowKeydown() { return onWindowKeydown; },
|
|
3807
4444
|
get columnDefMatchesId() { return columnDefMatchesId; },
|
|
3808
|
-
get buildApi() { return
|
|
4445
|
+
get buildApi() { return api; },
|
|
3809
4446
|
get apiNotified() { return apiNotified; },
|
|
3810
4447
|
set apiNotified(v) { apiNotified = v; },
|
|
3811
4448
|
};
|
|
3812
4449
|
const { resolveEffectiveFeatures } = createFeatures(ctx);
|
|
3813
|
-
const { showTooltipFor, hideTooltip, flushScheduledScrollSync, scheduleScrollSync, onBodyScroll } = createScrollSync(ctx);
|
|
4450
|
+
const { showTooltipFor, hideTooltip, flushScheduledScrollSync, scheduleScrollSync, syncScrollNow, onBodyScroll } = createScrollSync(ctx);
|
|
3814
4451
|
const { onGridKeyDown, onWindowKeydown, onHeaderSortClick } = createKeyboard(ctx);
|
|
3815
4452
|
const { computeSummaries, hasRenderedColumn } = createSummaries(ctx);
|
|
3816
4453
|
// Hand the aggregator to `eagerSummaries` (declared above `ctx`, which this
|
|
@@ -3818,13 +4455,13 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3818
4455
|
summarize = computeSummaries;
|
|
3819
4456
|
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);
|
|
3820
4457
|
const { cellConditionalFormat, computeRowClass, computeCellClass, computeCellTooltip, computeCellValidity, computeCellNote, getColumnEditorOptions, areEditorOptionsLoading, formatListCellValue, formatCellValue, formatPinnedValue, computePinnedCellClass } = createCellRender(ctx);
|
|
3821
|
-
const { isCellEditable, isCellEditableAt, getRowColumnValue, getCellDisplayValue, startEditingWithChar, startEditing, stopEditing, startFullRowEdit, setFullRowDraft, commitFullRowEdit, cancelFullRowEdit, saveEditingCell, applyHistoryStep, updateEditingCellValue, onEditorKeyDown, commitAndMoveByTab, focusOnMount, onCellDoubleClick, pasteFromClipboard, onGridPaste } = createEditing(ctx);
|
|
3822
|
-
const { isRowSelected, toggleRowSelectionById, toggleSelectAllRows, setActiveCell, scrollActiveCellIntoView, setSelection, extendSelection, isCellInSelectedRange, getCellRangeEdges, getSelectionRects, isInFillPreview, dragPreviewRect, fillMarqueeEdges, findColumnById, onCellPointerDown, onCellPointerEnter, endDragSelection, onWindowPointerMove, onCellClick, emitCellDoubleClick } = createSelection(ctx);
|
|
4458
|
+
const { isCellEditable, isCellEditableAt, getRowColumnValue, getCellDisplayValue, startEditingWithChar, startEditing, stopEditing, startFullRowEdit, setFullRowDraft, commitFullRowEdit, cancelFullRowEdit, saveEditingCell, applyHistoryStep, updateEditingCellValue, onEditorKeyDown, commitAndMoveByTab, focusOnMount, onCellDoubleClick, pasteFromClipboard, onGridPaste, armPasteFallback } = createEditing(ctx);
|
|
4459
|
+
const { isRowSelected, toggleRowSelectionById, toggleSelectAllRows, setSelectAllRows, setActiveCell, scrollActiveCellIntoView, setSelection, extendSelection, isCellInSelectedRange, getCellRangeEdges, activeRangeRect, cellSelectionState, getSelectionRects, isInFillPreview, dragPreviewRect, fillMarqueeEdges, findColumnById, onCellPointerDown, onCellPointerEnter, endDragSelection, onWindowPointerMove, onCellClick, emitCellDoubleClick } = createSelection(ctx);
|
|
3823
4460
|
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);
|
|
3824
4461
|
const { onRowDragStart, onRowDragOver, onRowDragLeave, onRowDrop, onRowsContainerDragOver, onRowsContainerDrop, onRowDragEnd, onRowPointerDown, destroyRowDrag } = createRowDrag(ctx);
|
|
3825
4462
|
const { register: registerAlignedGrid, broadcastScroll: broadcastAlignedScroll, broadcastWidths: broadcastAlignedWidths } = createAlignedGrids(ctx);
|
|
3826
4463
|
const { buildApi } = createGridApi(ctx);
|
|
3827
|
-
const { readCellRaw, writeCellRaw, applyFillPattern, clearSelectedCellValues, startFillDrag, onFillPointerMove, onFillPointerUp, isOnMoveGrabStrip, startMoveDrag, onMovePointerMove, onMovePointerUp, applyMoveRange, moveDestRect, trackEdgeScroll, stopEdgeScroll, toggleBooleanCell, copySelectionToClipboard, clearSelectedCells, cutSelectionToClipboard } = createClipboard(ctx);
|
|
4464
|
+
const { readCellRaw, writeCellRaw, applyFillPattern, clearSelectedCellValues, startFillDrag, onFillPointerMove, onFillPointerUp, fillDownToNeighbour, isOnMoveGrabStrip, startMoveDrag, onMovePointerMove, onMovePointerUp, applyMoveRange, moveDestRect, trackEdgeScroll, stopEdgeScroll, toggleBooleanCell, copySelectionToClipboard, clearSelectedCells, cutSelectionToClipboard } = createClipboard(ctx);
|
|
3828
4465
|
// Dev-time configuration checks. Silent misconfiguration was the grid's
|
|
3829
4466
|
// biggest usability gap - a misspelled `field` rendered a column of blank
|
|
3830
4467
|
// cells and printed nothing, and an inert `pageSize` was simply ignored.
|
|
@@ -3858,6 +4495,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3858
4495
|
pageSize: props.pageSize,
|
|
3859
4496
|
groupBy: props.groupBy,
|
|
3860
4497
|
treeData: props.treeData,
|
|
4498
|
+
serverGroup: props.serverGroup,
|
|
3861
4499
|
initialColumnPinning: props.initialColumnPinning,
|
|
3862
4500
|
columnVirtualization: props.columnVirtualization,
|
|
3863
4501
|
externalPagination: props.externalPagination,
|