@svgrid/grid 3.0.3 → 3.0.5
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 +6 -5
- 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 +846 -207
- package/dist/SvGrid.css +178 -2
- package/dist/SvGrid.svelte +717 -720
- package/dist/SvGrid.types.d.ts +566 -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-CID62rlI.js +636 -0
- package/dist/cdn/GridMenus-DLL-qZeu.js +635 -0
- 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-Bklofsad.js} +10615 -10155
- package/dist/cdn/{src-Bugf5XlI.js → src-jcFMkBnN.js} +7111 -6651
- package/dist/cdn/svgrid.js +27 -15
- package/dist/cdn/svgrid.svelte-external.js +27 -15
- package/dist/cdn/validate-C7Rn_Bns.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/gantt-view.svelte.d.ts +24 -0
- package/dist/gantt-view.svelte.js +13 -0
- package/dist/grid-icons.d.ts +2 -2
- package/dist/grid-icons.js +2 -2
- package/dist/grid-messages.d.ts +16 -3
- package/dist/grid-messages.js +10 -0
- package/dist/history.d.ts +83 -0
- package/dist/history.js +133 -0
- package/dist/index.d.ts +29 -10
- package/dist/index.js +23 -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 +4 -0
- package/dist/validate.js +21 -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 +819 -179
- package/src/SvGrid.css +178 -2
- package/src/SvGrid.svelte +717 -720
- package/src/SvGrid.types.ts +564 -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/gantt-stub.test.svelte +38 -0
- package/src/gantt-view.svelte.ts +35 -0
- package/src/grid-icons.ts +2 -2
- package/src/grid-messages.ts +28 -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 +186 -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.gantt-seam.test.ts +199 -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/svgrid.upsell-license.test.ts +7 -4
- package/src/validate.test.ts +29 -0
- package/src/validate.ts +28 -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/GridMenus-DhdPF700.js +0 -603
- 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
|
@@ -25,7 +25,13 @@ import {
|
|
|
25
25
|
type TableFeatures,
|
|
26
26
|
type ChartType,
|
|
27
27
|
type ChartSpec,
|
|
28
|
+
type ChartReducer,
|
|
29
|
+
type ChartTimeBucket,
|
|
28
30
|
type ChartValueFormat,
|
|
31
|
+
type ChartZoomWindow,
|
|
32
|
+
type ChartPanelIndicator,
|
|
33
|
+
type ChartFormatState,
|
|
34
|
+
type OhlcColumns,
|
|
29
35
|
} from "./index";
|
|
30
36
|
import {
|
|
31
37
|
createRowScrollScaling,
|
|
@@ -53,6 +59,8 @@ import {
|
|
|
53
59
|
rawToNumber,
|
|
54
60
|
} from "./SvGrid.helpers";
|
|
55
61
|
import { createFeatures } from "./features";
|
|
62
|
+
import { pushHistory, type HistoryStep as SharedHistoryStep } from "./history";
|
|
63
|
+
import { buildMergeIndex } from "./merges";
|
|
56
64
|
import {
|
|
57
65
|
createScrollSync,
|
|
58
66
|
} from "./scroll-sync";
|
|
@@ -301,6 +309,60 @@ export function createSvGridController<
|
|
|
301
309
|
// preserves reference identity for non-overridden keys, which the columns-identity
|
|
302
310
|
// column cache and the `data`/`columns` sync effects rely on.
|
|
303
311
|
let optionOverrides = $state<Record<string, unknown>>({});
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* A `rowModel` answers for the props it can, so a server-backed grid is
|
|
315
|
+
* one prop rather than a dozen.
|
|
316
|
+
*
|
|
317
|
+
* Bumped by the model itself through `subscribe` (see the effect below),
|
|
318
|
+
* which is what makes `getRows()` and `isLoading()` - plain method calls,
|
|
319
|
+
* invisible to the reactive system - re-read when the model changes.
|
|
320
|
+
*/
|
|
321
|
+
let rowModelVersion = $state(0);
|
|
322
|
+
const rowModelProps = $derived.by(function rowModelProps_d() {
|
|
323
|
+
const model = rawProps.rowModel;
|
|
324
|
+
if (!model) return null;
|
|
325
|
+
rowModelVersion; // read, so a notification re-derives
|
|
326
|
+
const paging = model.pagination;
|
|
327
|
+
return {
|
|
328
|
+
data: model.getRows(),
|
|
329
|
+
loading: model.isLoading(),
|
|
330
|
+
loadingOverlay: true,
|
|
331
|
+
getRowId: model.getRowId,
|
|
332
|
+
// A model that sorts or filters server-side is telling the grid not
|
|
333
|
+
// to do it locally; one that does neither leaves the grid in charge.
|
|
334
|
+
externalSort: model.setSort ? true : undefined,
|
|
335
|
+
externalFilter: model.setFilter ? true : undefined,
|
|
336
|
+
onSortingChange: model.setSort ? (s: any) => model.setSort!(s) : undefined,
|
|
337
|
+
onFiltersChange: model.setFilter ? (f: any) => model.setFilter!(f) : undefined,
|
|
338
|
+
onVisibleRangeChange: model.setViewport
|
|
339
|
+
? (r: { startIndex: number; endIndex: number }) =>
|
|
340
|
+
model.setViewport!(r.startIndex, r.endIndex)
|
|
341
|
+
: undefined,
|
|
342
|
+
rowPlaceholder: model.rowPlaceholder,
|
|
343
|
+
onRetryRow: model.retryRow,
|
|
344
|
+
serverGroup: model.group,
|
|
345
|
+
rowSelectionModel: model.selection,
|
|
346
|
+
serverFilterValues: model.filterValues
|
|
347
|
+
? (columnId: string) => model.filterValues!(columnId)
|
|
348
|
+
: undefined,
|
|
349
|
+
pinnedTopRows: model.pinnedTopRows,
|
|
350
|
+
pinnedBottomRows: model.pinnedBottomRows,
|
|
351
|
+
pivotResultColumns: model.pivotResultColumns ?? undefined,
|
|
352
|
+
externalPagination: paging ? true : undefined,
|
|
353
|
+
rowCount: paging?.rowCount,
|
|
354
|
+
pageIndex: paging?.pageIndex,
|
|
355
|
+
pageSize: paging?.pageSize,
|
|
356
|
+
pageSizeOptions: paging?.pageSizes,
|
|
357
|
+
onPaginationChange: paging
|
|
358
|
+
? (p: { pageIndex: number; pageSize: number }) => {
|
|
359
|
+
if (p.pageSize !== paging.pageSize) paging.setPageSize(p.pageSize);
|
|
360
|
+
else paging.setPage(p.pageIndex);
|
|
361
|
+
}
|
|
362
|
+
: undefined,
|
|
363
|
+
} as Partial<Props<TFeatures, TData>>;
|
|
364
|
+
});
|
|
365
|
+
|
|
304
366
|
const props = new Proxy(rawProps, {
|
|
305
367
|
// Read the override as a PROPERTY (tracked by `$state`), not via `in` (the `has`
|
|
306
368
|
// trap is not tracked) - so a reactive read of `props.X` re-runs when an override
|
|
@@ -310,7 +372,21 @@ export function createSvGridController<
|
|
|
310
372
|
get: (t, k) => {
|
|
311
373
|
const v =
|
|
312
374
|
typeof k === "string" ? optionOverrides[k] : undefined;
|
|
313
|
-
|
|
375
|
+
if (v !== undefined) return v;
|
|
376
|
+
// Pivot mode swaps the column list: `pivotResultColumns` (the prop, or
|
|
377
|
+
// the row model's) stands in for `columns` while it is set, and
|
|
378
|
+
// `columns` itself stays the app's own list for when pivot ends.
|
|
379
|
+
if (k === "columns") {
|
|
380
|
+
const pivot =
|
|
381
|
+
Reflect.get(t, "pivotResultColumns") ??
|
|
382
|
+
(rowModelProps as Record<string, unknown> | null)?.pivotResultColumns;
|
|
383
|
+
if (pivot) return pivot;
|
|
384
|
+
}
|
|
385
|
+
const own = Reflect.get(t, k);
|
|
386
|
+
// A prop written on the grid always beats the row model, so adopting
|
|
387
|
+
// `rowModel` is never all-or-nothing.
|
|
388
|
+
if (own !== undefined || typeof k !== "string") return own;
|
|
389
|
+
return (rowModelProps as Record<string, unknown> | null)?.[k];
|
|
314
390
|
},
|
|
315
391
|
}) as Props<TFeatures, TData>;
|
|
316
392
|
|
|
@@ -373,7 +449,12 @@ export function createSvGridController<
|
|
|
373
449
|
let pendingScrollTop = 0;
|
|
374
450
|
let pendingScrollLeft = 0;
|
|
375
451
|
let scrollSyncRaf: number | null = null;
|
|
376
|
-
|
|
452
|
+
// `$state.raw`: every writer replaces the whole object, and every rendered
|
|
453
|
+
// cell's selection derived reads it. Under a deep proxy each of those ~300
|
|
454
|
+
// deriveds tracked the nested anchor/focus row/col signals separately (six
|
|
455
|
+
// dependencies per cell to mark and re-check on every arrow key) where one
|
|
456
|
+
// will do.
|
|
457
|
+
let selectionRange = $state.raw<SelectionRange>({ anchor: null, focus: null });
|
|
377
458
|
// Extra committed ranges for multi-range (Ctrl+drag) selection. The
|
|
378
459
|
// `selectionRange` above is always the ACTIVE range being manipulated; these
|
|
379
460
|
// are the finished ones. Full selection = these + the active range.
|
|
@@ -418,20 +499,21 @@ export function createSvGridController<
|
|
|
418
499
|
// Full-row editing: the row currently in whole-row edit + its per-column
|
|
419
500
|
// draft (keyed by column id). Null when not in full-row mode.
|
|
420
501
|
let fullRowEdit = $state<{ rowId: string; draft: Record<string, unknown> } | null>(null);
|
|
421
|
-
|
|
502
|
+
// `$state.raw`, not `$state`: every writer replaces the whole object, and
|
|
503
|
+
// the render path asks `key in editedCellValues` once per rendered cell. On
|
|
504
|
+
// a deep proxy that `in` check, run inside an effect, creates and keeps a
|
|
505
|
+
// per-key signal for every cell that ever scrolls into view - a leak that
|
|
506
|
+
// grows with rows x columns and a Map lookup per cell per render.
|
|
507
|
+
let editedCellValues = $state.raw<Record<string, unknown>>({});
|
|
422
508
|
|
|
423
509
|
// ---- Undo / redo (history + pointer model) ---------------------------
|
|
424
510
|
// VSCode-style: one ordered history array, plus a pointer to the index
|
|
425
511
|
// of the NEXT undo step. Avoids the dual-stack edge cases where
|
|
426
512
|
// multiple undo-redo cycles can lose entries.
|
|
427
|
-
// exported for the editing slice (undo/redo)
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
field: string
|
|
432
|
-
before: unknown
|
|
433
|
-
after: unknown
|
|
434
|
-
}
|
|
513
|
+
// exported for the editing slice (undo/redo). The shape lives in history.ts
|
|
514
|
+
// so the controller, editing.ts and clipboard.ts cannot drift apart - they
|
|
515
|
+
// previously declared it twice and open-coded the push in three places.
|
|
516
|
+
type HistoryStep = SharedHistoryStep
|
|
435
517
|
const UNDO_LIMIT = 200
|
|
436
518
|
let history = $state<HistoryStep[]>([])
|
|
437
519
|
/** Index in `history` of the LAST applied step. -1 means "nothing applied".
|
|
@@ -441,6 +523,11 @@ export function createSvGridController<
|
|
|
441
523
|
/** Bumps on every undo / redo / record so $derived consumers can
|
|
442
524
|
* observe via the api without subscribing to history directly. */
|
|
443
525
|
let historyVersion = $state(0)
|
|
526
|
+
/** Set while a `runHistoryGroup` call is on the stack, so every step pushed
|
|
527
|
+
* inside one action shares a group id and undoes together. Deliberately not
|
|
528
|
+
* $state: it is written and read synchronously inside a single call and
|
|
529
|
+
* nothing renders from it. */
|
|
530
|
+
let historyGroupId: string | undefined = undefined
|
|
444
531
|
|
|
445
532
|
// ---- Hover tooltip (custom popover, not native title=) ---------------
|
|
446
533
|
// Triggered by per-column `tooltip` field OR per-cell `notes` prop.
|
|
@@ -479,6 +566,24 @@ export function createSvGridController<
|
|
|
479
566
|
let editorSelectAll = true;
|
|
480
567
|
/** Per-column width overrides set by the resize handles. */
|
|
481
568
|
let columnWidths = $state<Record<string, number>>({});
|
|
569
|
+
/**
|
|
570
|
+
* Columns folded to nothing, the way a sheet hides a column: the column
|
|
571
|
+
* keeps its index, its cells and its width for later, and takes no room.
|
|
572
|
+
* Distinct from `hiddenColumns`, which takes a column out of the model
|
|
573
|
+
* and shifts every index after it; a spreadsheet's formulas and formats
|
|
574
|
+
* are keyed by index and could not follow that.
|
|
575
|
+
*/
|
|
576
|
+
let collapsedColumns = $state<Record<string, boolean>>({});
|
|
577
|
+
function setColumnCollapsed(columnId: string, collapsed: boolean): void {
|
|
578
|
+
if (!!collapsedColumns[columnId] === collapsed) return;
|
|
579
|
+
if (collapsed) {
|
|
580
|
+
collapsedColumns = { ...collapsedColumns, [columnId]: true };
|
|
581
|
+
} else {
|
|
582
|
+
const next = { ...collapsedColumns };
|
|
583
|
+
delete next[columnId];
|
|
584
|
+
collapsedColumns = next;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
482
587
|
const MIN_COLUMN_WIDTH = 40;
|
|
483
588
|
/** Columns pinned to the left or right edge of the grid (sticky positioning).
|
|
484
589
|
* Seeded from `props.initialColumnPinning` so demos / tests can show the
|
|
@@ -570,26 +675,54 @@ export function createSvGridController<
|
|
|
570
675
|
const hb = column.columnDef?.hideBelow;
|
|
571
676
|
return hb != null && viewportWidth > 0 && viewportWidth < hb;
|
|
572
677
|
}
|
|
573
|
-
|
|
678
|
+
/**
|
|
679
|
+
* Live scroll geometry of the scroll container (feeds the custom
|
|
680
|
+
* scrollbars and `hasVerticalOverflow`).
|
|
681
|
+
*
|
|
682
|
+
* Measured from a plain `$effect`, NOT inside a `$derived`, on purpose.
|
|
683
|
+
* `scrollTop` / `scrollHeight` / `scrollWidth` reads force a synchronous
|
|
684
|
+
* layout whenever the tree is dirty. As a derived this was pulled by the
|
|
685
|
+
* shell's `class:sv-grid-has-vscroll` at the very start of every scroll
|
|
686
|
+
* flush - before the recycled rows had been rewritten - so it forced a
|
|
687
|
+
* full table layout that the row writes then invalidated again, and the
|
|
688
|
+
* browser laid the table out a second time before painting. Two table
|
|
689
|
+
* layouts per arrow key on a large grid. An effect runs after the flush
|
|
690
|
+
* has written the DOM, so the reads below land on a tree the browser has
|
|
691
|
+
* to lay out anyway, and the pre-paint pass is incremental.
|
|
692
|
+
*
|
|
693
|
+
* The version reads keep the same triggers the derived had: a scroll
|
|
694
|
+
* tick, a shell resize, and either virtualizer's window/size change (data
|
|
695
|
+
* load, row / column count change) - without those the scrollbar kept a
|
|
696
|
+
* stale `content-size` ≈ 0, its hidden-check tripped, it set
|
|
697
|
+
* `pointer-events: none`, and the user could not drag it.
|
|
698
|
+
*/
|
|
699
|
+
let scrollMetrics = $state.raw({
|
|
700
|
+
scrollTop: 0,
|
|
701
|
+
scrollLeft: 0,
|
|
702
|
+
scrollHeight: 0,
|
|
703
|
+
scrollWidth: 0,
|
|
704
|
+
});
|
|
705
|
+
$effect(function scrollMetrics_e() {
|
|
574
706
|
scrollVersion;
|
|
575
707
|
viewportVersion;
|
|
576
|
-
// Track the virtualizers' versions too so when data loads or row /
|
|
577
|
-
// column counts change, scrollMetrics re-reads the DOM's grown
|
|
578
|
-
// scrollHeight / scrollWidth. Without these deps the scrollbar
|
|
579
|
-
// receives a stale `content-size` ≈ 0, its hidden-check trips, it
|
|
580
|
-
// sets `pointer-events: none`, and the user can't drag it. The
|
|
581
|
-
// identifiers below are declared further down - derived callbacks
|
|
582
|
-
// run lazily, so by the time this fires they're in scope.
|
|
583
708
|
virtualizer.version;
|
|
584
709
|
columnVirtualizerVersion;
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
710
|
+
const el = scrollContainer;
|
|
711
|
+
if (!el) return;
|
|
712
|
+
const scrollTop = el.scrollTop;
|
|
713
|
+
const scrollLeft = el.scrollLeft;
|
|
714
|
+
const scrollHeight = el.scrollHeight;
|
|
715
|
+
const scrollWidth = el.scrollWidth;
|
|
716
|
+
const prev = untrack(() => scrollMetrics);
|
|
717
|
+
if (
|
|
718
|
+
prev.scrollTop === scrollTop &&
|
|
719
|
+
prev.scrollLeft === scrollLeft &&
|
|
720
|
+
prev.scrollHeight === scrollHeight &&
|
|
721
|
+
prev.scrollWidth === scrollWidth
|
|
722
|
+
) {
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
scrollMetrics = { scrollTop, scrollLeft, scrollHeight, scrollWidth };
|
|
593
726
|
});
|
|
594
727
|
/** Vertical overflow from the virtualizer's authoritative total size,
|
|
595
728
|
* NOT from `scrollMetrics.scrollHeight` alone. Reading DOM dimensions
|
|
@@ -674,7 +807,10 @@ export function createSvGridController<
|
|
|
674
807
|
// re-synced whenever the parent passes a new array; the imperative API
|
|
675
808
|
// mutates these so add/remove operations don't need a callback round-trip.
|
|
676
809
|
// svelte-ignore state_referenced_locally
|
|
677
|
-
|
|
810
|
+
// `data` is optional on the type (a `rowModel` may supply the rows
|
|
811
|
+
// instead), so every read falls back to empty rather than to undefined.
|
|
812
|
+
const EMPTY_DATA: ReadonlyArray<TData> = [];
|
|
813
|
+
let internalData = $state.raw<ReadonlyArray<TData>>(props.data ?? EMPTY_DATA);
|
|
678
814
|
// Resolve `cellDataType` / `inferColumnTypes` into concrete editorType +
|
|
679
815
|
// format defaults once, up front, so every downstream reader sees a normal
|
|
680
816
|
// column. Explicit fields on the ColumnDef always win.
|
|
@@ -726,7 +862,7 @@ export function createSvGridController<
|
|
|
726
862
|
// accumulated cell-edit overrides - otherwise `getCellDisplayValue`
|
|
727
863
|
// would keep returning the old edited values from `editedCellValues`
|
|
728
864
|
// even though the underlying data has been replaced.
|
|
729
|
-
internalData = props.data;
|
|
865
|
+
internalData = props.data ?? EMPTY_DATA;
|
|
730
866
|
editedCellValues = {};
|
|
731
867
|
});
|
|
732
868
|
$effect(() => {
|
|
@@ -972,6 +1108,20 @@ export function createSvGridController<
|
|
|
972
1108
|
*
|
|
973
1109
|
* Returns null when the cell should render nothing.
|
|
974
1110
|
*/
|
|
1111
|
+
/**
|
|
1112
|
+
* Why a row has no data yet, or null when it does.
|
|
1113
|
+
*
|
|
1114
|
+
* Group rows are never placeholders, so the check is skipped for them:
|
|
1115
|
+
* their `original` is a synthetic bucket, not a row the consumer handed
|
|
1116
|
+
* us, and passing it to `rowPlaceholder` would ask a question about a row
|
|
1117
|
+
* that does not exist.
|
|
1118
|
+
*/
|
|
1119
|
+
function placeholderStateOf(row: Row<TData>): "loading" | "failed" | null {
|
|
1120
|
+
const ask = props.rowPlaceholder;
|
|
1121
|
+
if (!ask || isGroupRow(row)) return null;
|
|
1122
|
+
return ask(row.original, row.index);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
975
1125
|
function autoGroupCell(
|
|
976
1126
|
row: Row<TData>,
|
|
977
1127
|
columnId: string,
|
|
@@ -1697,8 +1847,14 @@ export function createSvGridController<
|
|
|
1697
1847
|
const paginationPageSize = $derived(
|
|
1698
1848
|
externalPaginationEnabled ? (props.pageSize ?? 10) : paginationState.pageSize,
|
|
1699
1849
|
);
|
|
1850
|
+
// Tracks `dataStateVersion`, which the store subscription bumps when the
|
|
1851
|
+
// rowSelection slice (among others) changes - NOT the catch-all
|
|
1852
|
+
// `gridStateVersion`, which also bumps on every active-cell move. Every
|
|
1853
|
+
// rendered row and cell reads this (`sv-grid-row-selected`, `aria-selected`),
|
|
1854
|
+
// so on the catch-all each arrow key re-marked and re-checked ~400 effects
|
|
1855
|
+
// to find the same object.
|
|
1700
1856
|
const rowSelectionState = $derived.by(function rowSelectionState_d() {
|
|
1701
|
-
|
|
1857
|
+
dataStateVersion;
|
|
1702
1858
|
return grid.getState().rowSelection ?? {};
|
|
1703
1859
|
});
|
|
1704
1860
|
|
|
@@ -1734,15 +1890,42 @@ export function createSvGridController<
|
|
|
1734
1890
|
const rows: TData[] = [];
|
|
1735
1891
|
const ids: string[] = [];
|
|
1736
1892
|
if (!selectionBarOn) return { rows, ids };
|
|
1893
|
+
// Under an external selection model the loaded rows are the ones the
|
|
1894
|
+
// model says are selected; rows that never loaded are not in `allRows`
|
|
1895
|
+
// and are counted by `selectionBarCount` below instead. This walks
|
|
1896
|
+
// every loaded row, so it runs on every block a row model delivers:
|
|
1897
|
+
// a model that can say "nothing is selected" spares the walk, and the
|
|
1898
|
+
// props it needs are read once, not through the proxy per row.
|
|
1899
|
+
const model = props.rowSelectionModel;
|
|
1900
|
+
if (model && model.selectedCount?.() === 0) return { rows, ids };
|
|
1901
|
+
const ask = props.rowPlaceholder;
|
|
1902
|
+
const isPlaceholder = ask
|
|
1903
|
+
? (row: Row<TData>) => !isGroupRow(row) && !!ask(row.original as TData, row.index)
|
|
1904
|
+
: () => false;
|
|
1737
1905
|
for (const row of allRows) {
|
|
1738
|
-
if (isGroupRow(row)) continue;
|
|
1739
|
-
|
|
1906
|
+
if (isGroupRow(row) || isPlaceholder(row)) continue;
|
|
1907
|
+
const selected = model
|
|
1908
|
+
? model.isSelected(row.id, row.original as TData)
|
|
1909
|
+
: !!rowSelectionState[row.id];
|
|
1910
|
+
if (selected) {
|
|
1740
1911
|
rows.push(row.original as TData);
|
|
1741
1912
|
ids.push(row.id);
|
|
1742
1913
|
}
|
|
1743
1914
|
}
|
|
1744
1915
|
return { rows, ids };
|
|
1745
1916
|
});
|
|
1917
|
+
/**
|
|
1918
|
+
* What the selection bar's count chip says. A model that stores the rule
|
|
1919
|
+
* ("everything except these") knows about rows the grid never loaded, so
|
|
1920
|
+
* its number wins over counting ticks on screen; `null` from it means it
|
|
1921
|
+
* cannot say, and the bar falls back to the loaded count.
|
|
1922
|
+
*/
|
|
1923
|
+
const selectionBarCount = $derived.by(function selectionBarCount_d() {
|
|
1924
|
+
const fromModel = props.rowSelectionModel?.selectedCount?.();
|
|
1925
|
+
return fromModel ?? selectionBarTarget.ids.length;
|
|
1926
|
+
});
|
|
1927
|
+
/** Whether the bar has anything to show: the model's count when it has one, else the loaded ticks. */
|
|
1928
|
+
const selectionBarVisible = $derived(selectionBarOn && selectionBarCount > 0);
|
|
1746
1929
|
|
|
1747
1930
|
// Forward selection changes to the consumer. Skips the very first invocation
|
|
1748
1931
|
// (the initial empty state) so consumers don't get a spurious callback on mount.
|
|
@@ -2017,6 +2200,7 @@ export function createSvGridController<
|
|
|
2017
2200
|
let chartAiHandler = $state<
|
|
2018
2201
|
((prompt: string) => Promise<Record<string, unknown> | null>) | null
|
|
2019
2202
|
>(null);
|
|
2203
|
+
let chartExplainHandler = $state<(() => Promise<{ summary: string; insights: string[] } | null>) | null>(null);
|
|
2020
2204
|
|
|
2021
2205
|
// Multiple charts, each an independently-configured working set switched by a
|
|
2022
2206
|
// tab strip. Pickers / spec read the ACTIVE chart.
|
|
@@ -2024,7 +2208,7 @@ export function createSvGridController<
|
|
|
2024
2208
|
id: string;
|
|
2025
2209
|
title: string;
|
|
2026
2210
|
type: ChartType;
|
|
2027
|
-
reduce:
|
|
2211
|
+
reduce: ChartReducer;
|
|
2028
2212
|
dimensionId: string | null;
|
|
2029
2213
|
measureId: string | null;
|
|
2030
2214
|
seriesId: string | null | undefined;
|
|
@@ -2045,8 +2229,28 @@ export function createSvGridController<
|
|
|
2045
2229
|
logScale: boolean | null;
|
|
2046
2230
|
timeAxis: boolean | null;
|
|
2047
2231
|
valueFormat: ChartValueFormat | null;
|
|
2232
|
+
/** Calendar unit a date dimension is grouped by. Null = exact values. */
|
|
2233
|
+
bucket: ChartTimeBucket | null;
|
|
2234
|
+
/** Histogram bin count. Null = Sturges' rule. */
|
|
2235
|
+
bins: number | null;
|
|
2236
|
+
funnelShape: "trapezoid" | "pyramid" | "cone" | null;
|
|
2237
|
+
candleStyle: "classic" | "hollow" | "heikin-ashi" | null;
|
|
2238
|
+
/** The chart's zoom window (category index range), kept per tab. */
|
|
2239
|
+
zoom: ChartZoomWindow | null;
|
|
2240
|
+
/** Candlestick / OHLC: which columns hold the prices (column ids). Null
|
|
2241
|
+
* guesses from the column names. */
|
|
2242
|
+
ohlc: { open: string | null; high: string | null; low: string | null; close: string | null; volume: string | null } | null;
|
|
2243
|
+
/** Indicators: panes under the price (volume, rsi, macd, stochastic,
|
|
2244
|
+
* atr, obv) and overlays on it (sma, ema, bb, vwap). */
|
|
2245
|
+
indicators: ChartPanelIndicator[];
|
|
2246
|
+
/** The builder's Format tab: titles, axes, legend, per-series style. */
|
|
2247
|
+
format: ChartFormatState | null;
|
|
2248
|
+
/** An unlinked chart: the spec it was frozen with and when. */
|
|
2249
|
+
frozen: { spec: ChartSpec; at: string } | null;
|
|
2048
2250
|
};
|
|
2049
2251
|
let chartSeq = 0;
|
|
2252
|
+
const isZoomWindow = (v: unknown): v is ChartZoomWindow =>
|
|
2253
|
+
!!v && typeof v === "object" && Number.isInteger((v as ChartZoomWindow).i0) && Number.isInteger((v as ChartZoomWindow).i1);
|
|
2050
2254
|
const makeChart = (title: string): ChartTabState => ({
|
|
2051
2255
|
id: `chart-${chartSeq++}`,
|
|
2052
2256
|
title,
|
|
@@ -2067,10 +2271,49 @@ export function createSvGridController<
|
|
|
2067
2271
|
logScale: null,
|
|
2068
2272
|
timeAxis: null,
|
|
2069
2273
|
valueFormat: null,
|
|
2274
|
+
bucket: null,
|
|
2275
|
+
bins: null,
|
|
2276
|
+
funnelShape: null,
|
|
2277
|
+
candleStyle: null,
|
|
2278
|
+
zoom: null,
|
|
2279
|
+
ohlc: null,
|
|
2280
|
+
indicators: [],
|
|
2281
|
+
format: null,
|
|
2282
|
+
frozen: null,
|
|
2070
2283
|
});
|
|
2284
|
+
// The saved view (`GridState.charts[i]`) names four fields differently
|
|
2285
|
+
// from the tab; everything else is the tab's own key.
|
|
2286
|
+
const TAB_ALIASES: Record<string, string> = { dimensionId: "dimension", measureId: "measure", seriesId: "series", measure2Id: "measure2" };
|
|
2287
|
+
/** A tab as the saved view carries it, without the runtime id. */
|
|
2288
|
+
const tabToState = (c: ChartTabState): Record<string, unknown> => {
|
|
2289
|
+
const out: Record<string, unknown> = {};
|
|
2290
|
+
for (const k in c) if (k !== "id") out[TAB_ALIASES[k] ?? k] = k === "seriesId" ? (c.seriesId ?? null) : c[k as keyof ChartTabState];
|
|
2291
|
+
return out;
|
|
2292
|
+
};
|
|
2293
|
+
/** A tab from a saved-view snapshot: a fresh tab with every known field
|
|
2294
|
+
* the snapshot carries, and the shape-checked ones dropped when malformed. */
|
|
2295
|
+
const tabFromState = (c: Record<string, unknown>, i: number): ChartTabState => {
|
|
2296
|
+
const t = makeChart(typeof c.title === "string" ? c.title : `Chart ${i + 1}`) as unknown as Record<string, unknown>;
|
|
2297
|
+
for (const k in t) {
|
|
2298
|
+
const v = c[TAB_ALIASES[k] ?? k];
|
|
2299
|
+
if (k !== "id" && k !== "title" && v !== undefined) t[k] = v;
|
|
2300
|
+
}
|
|
2301
|
+
if (!isZoomWindow(t.zoom)) t.zoom = null;
|
|
2302
|
+
if (!Array.isArray(t.indicators)) t.indicators = [];
|
|
2303
|
+
if (typeof t.bins !== "number") t.bins = null;
|
|
2304
|
+
if (!t.frozen || typeof t.frozen !== "object" || !(t.frozen as { spec?: unknown }).spec) t.frozen = null;
|
|
2305
|
+
return t as unknown as ChartTabState;
|
|
2306
|
+
};
|
|
2071
2307
|
// svelte-ignore state_referenced_locally
|
|
2072
2308
|
let charts = $state<ChartTabState[]>([makeChart("Chart 1")]);
|
|
2073
2309
|
let activeChartIndex = $state(0);
|
|
2310
|
+
/**
|
|
2311
|
+
* Named chart configurations the user keeps: a tab's whole state under a
|
|
2312
|
+
* name, applied to any tab later. Part of the saved view (`GridState.savedCharts`)
|
|
2313
|
+
* only when there is one, so existing snapshots do not change.
|
|
2314
|
+
*/
|
|
2315
|
+
type SavedChartEntry = { name: string; savedAt: string; tab: Record<string, unknown> };
|
|
2316
|
+
let savedCharts = $state<SavedChartEntry[]>([]);
|
|
2074
2317
|
const activeChart = $derived(
|
|
2075
2318
|
(charts[Math.min(activeChartIndex, charts.length - 1)] ?? charts[0])!,
|
|
2076
2319
|
);
|
|
@@ -2083,6 +2326,10 @@ export function createSvGridController<
|
|
|
2083
2326
|
const chartDataLabels = $derived(activeChart.dataLabels);
|
|
2084
2327
|
const chartLogScale = $derived(activeChart.logScale);
|
|
2085
2328
|
const chartTimeAxis = $derived(activeChart.timeAxis);
|
|
2329
|
+
const chartBucket = $derived(activeChart.bucket);
|
|
2330
|
+
const chartBins = $derived(activeChart.bins);
|
|
2331
|
+
const chartFunnelShape = $derived(activeChart.funnelShape);
|
|
2332
|
+
const chartCandleStyle = $derived(activeChart.candleStyle);
|
|
2086
2333
|
const chartValueFormat = $derived(activeChart.valueFormat);
|
|
2087
2334
|
|
|
2088
2335
|
const asMeasureList = (m: string | string[] | undefined): string[] =>
|
|
@@ -2198,9 +2445,13 @@ export function createSvGridController<
|
|
|
2198
2445
|
const col = allColumns.find((c) => c.id === effectiveChartMeasureId);
|
|
2199
2446
|
const label = col ? columnLabel(col) : "";
|
|
2200
2447
|
if (!label) return undefined;
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
:
|
|
2448
|
+
const word: Record<string, string> = {
|
|
2449
|
+
sum: "Sum", avg: "Average", min: "Min", max: "Max", median: "Median",
|
|
2450
|
+
first: "First", last: "Last", countDistinct: "Distinct",
|
|
2451
|
+
};
|
|
2452
|
+
if (chartReduce === "count") return "Count";
|
|
2453
|
+
const w = word[chartReduce] ?? (chartReduce.startsWith("p") ? `P${chartReduce.slice(1)}` : "Sum");
|
|
2454
|
+
return `${w} of ${label}`;
|
|
2204
2455
|
});
|
|
2205
2456
|
/**
|
|
2206
2457
|
* Does this column hold dates? Declared type first, then a probe of the
|
|
@@ -2223,6 +2474,41 @@ export function createSvGridController<
|
|
|
2223
2474
|
const chartDimensionIsDate = $derived.by<boolean>(() =>
|
|
2224
2475
|
columnIsDate(allColumns.find((c) => c.id === effectiveChartDimensionId)),
|
|
2225
2476
|
);
|
|
2477
|
+
// The chart derivation engine (rowsToChartSpec) is loaded lazily the first time
|
|
2478
|
+
// charting is enabled, keeping chart.ts (~11 KB gzip) out of the base <SvGrid>
|
|
2479
|
+
// bundle - it rides in the same lazy chunk as the chart panel. `chartSpec` stays
|
|
2480
|
+
// null until it resolves, a tick after the panel itself starts loading (which it
|
|
2481
|
+
// already does). The `getAggregate` server path uses the local bucketsToSpec and
|
|
2482
|
+
// does not need the engine.
|
|
2483
|
+
// The whole module, not just `rowsToChartSpec`: the panel can now reach
|
|
2484
|
+
// every chart type, and the ones that do not read a categories-by-series
|
|
2485
|
+
// grid (scatter, gauge, treemap, calendar, sankey) need their own builders
|
|
2486
|
+
// from the same lazy chunk. Same `import()`, same boundary.
|
|
2487
|
+
let chartEngine = $state<typeof import("./chart") | null>(null);
|
|
2488
|
+
$effect(() => {
|
|
2489
|
+
if (chartingEnabled && !chartEngine)
|
|
2490
|
+
import("./chart").then((m) => (chartEngine = m));
|
|
2491
|
+
});
|
|
2492
|
+
|
|
2493
|
+
const chartIsOhlc = $derived(chartType === "candlestick" || chartType === "ohlc");
|
|
2494
|
+
/** The price columns: what the reader picked, else the engine's guess
|
|
2495
|
+
* from the column names (nothing until the engine chunk has loaded). */
|
|
2496
|
+
const effectiveChartOhlc = $derived<OhlcColumns>(
|
|
2497
|
+
chartEngine
|
|
2498
|
+
? chartEngine.guessOhlcColumns(chartableColumns.measures, activeChart.ohlc)
|
|
2499
|
+
: { open: null, high: null, low: null, close: null, volume: null, ...activeChart.ohlc },
|
|
2500
|
+
);
|
|
2501
|
+
/** The date column a candlestick reads: the picked dimension when it is a
|
|
2502
|
+
* date, else the first date column. */
|
|
2503
|
+
const effectiveChartOhlcDateId = $derived(
|
|
2504
|
+
chartDimensionIsDate ? effectiveChartDimensionId : (chartableColumns.dates[0]?.id ?? null),
|
|
2505
|
+
);
|
|
2506
|
+
const chartIndicators = $derived(activeChart.indicators);
|
|
2507
|
+
/** The panes under a price chart and the overlay on it, split by the engine. */
|
|
2508
|
+
const chartIndicatorSplit = $derived(
|
|
2509
|
+
chartIsOhlc && chartEngine ? chartEngine.splitPanelIndicators(chartIndicators) : { panes: [], overlay: null },
|
|
2510
|
+
);
|
|
2511
|
+
const chartIndicatorPanes = $derived(chartIndicatorSplit.panes);
|
|
2226
2512
|
const chartRows = $derived.by<TData[]>(() => {
|
|
2227
2513
|
const rects = getSelectionRects();
|
|
2228
2514
|
const pushLeaf = (out: TData[], row: Row<TData> | undefined) => {
|
|
@@ -2272,52 +2558,6 @@ export function createSvGridController<
|
|
|
2272
2558
|
if (seq === chartReqSeq) chartServerBuckets = [];
|
|
2273
2559
|
});
|
|
2274
2560
|
});
|
|
2275
|
-
const bucketsToSpec = (
|
|
2276
|
-
buckets: ReadonlyArray<{ category: string; series?: string; value: number }>,
|
|
2277
|
-
type: ChartType,
|
|
2278
|
-
hasSeries: boolean,
|
|
2279
|
-
): ChartSpec => {
|
|
2280
|
-
const cats: string[] = [];
|
|
2281
|
-
const catIdx = new Map<string, number>();
|
|
2282
|
-
const ensureCat = (c: string) => {
|
|
2283
|
-
let i = catIdx.get(c);
|
|
2284
|
-
if (i === undefined) { i = cats.length; catIdx.set(c, i); cats.push(c); }
|
|
2285
|
-
return i;
|
|
2286
|
-
};
|
|
2287
|
-
const seriesMap = new Map<string, number[]>();
|
|
2288
|
-
for (const b of buckets) {
|
|
2289
|
-
const ci = ensureCat(b.category);
|
|
2290
|
-
const key = hasSeries ? (b.series ?? "") : "value";
|
|
2291
|
-
let arr = seriesMap.get(key);
|
|
2292
|
-
if (!arr) { arr = []; seriesMap.set(key, arr); }
|
|
2293
|
-
arr[ci] = (arr[ci] ?? 0) + b.value;
|
|
2294
|
-
}
|
|
2295
|
-
return {
|
|
2296
|
-
type,
|
|
2297
|
-
categories: cats,
|
|
2298
|
-
series: [...seriesMap.entries()].map(([label, values]) => ({
|
|
2299
|
-
label,
|
|
2300
|
-
values: cats.map((_, i) => values[i] ?? 0),
|
|
2301
|
-
})),
|
|
2302
|
-
};
|
|
2303
|
-
};
|
|
2304
|
-
|
|
2305
|
-
// The chart derivation engine (rowsToChartSpec) is loaded lazily the first time
|
|
2306
|
-
// charting is enabled, keeping chart.ts (~11 KB gzip) out of the base <SvGrid>
|
|
2307
|
-
// bundle - it rides in the same lazy chunk as the chart panel. `chartSpec` stays
|
|
2308
|
-
// null until it resolves, a tick after the panel itself starts loading (which it
|
|
2309
|
-
// already does). The `getAggregate` server path uses the local bucketsToSpec and
|
|
2310
|
-
// does not need the engine.
|
|
2311
|
-
// The whole module, not just `rowsToChartSpec`: the panel can now reach
|
|
2312
|
-
// every chart type, and the ones that do not read a categories-by-series
|
|
2313
|
-
// grid (scatter, gauge, treemap, calendar, sankey) need their own builders
|
|
2314
|
-
// from the same lazy chunk. Same `import()`, same boundary.
|
|
2315
|
-
let chartEngine = $state<typeof import("./chart") | null>(null);
|
|
2316
|
-
$effect(() => {
|
|
2317
|
-
if (chartingEnabled && !chartEngine)
|
|
2318
|
-
import("./chart").then((m) => (chartEngine = m));
|
|
2319
|
-
});
|
|
2320
|
-
|
|
2321
2561
|
/** Put the chart's numbers in the same locale as the grid's. A grid that set
|
|
2322
2562
|
* `localization.locale` has already said which locale its data is in, and a
|
|
2323
2563
|
* chart of that data reading in a different one is a bug nobody would think
|
|
@@ -2329,27 +2569,52 @@ export function createSvGridController<
|
|
|
2329
2569
|
if (chartCfg?.currency) spec.currency = chartCfg.currency;
|
|
2330
2570
|
};
|
|
2331
2571
|
|
|
2332
|
-
const
|
|
2572
|
+
const chartSpecLive = $derived.by<ChartSpec | null>(() => {
|
|
2333
2573
|
if (!chartingEnabled || !chartCfg) return null;
|
|
2334
2574
|
if (chartCfg.getAggregate) {
|
|
2335
|
-
if (!chartServerBuckets) return null;
|
|
2336
|
-
const spec =
|
|
2575
|
+
if (!chartServerBuckets || !chartEngine) return null;
|
|
2576
|
+
const spec = chartEngine.bucketsToChartSpec(chartServerBuckets, chartType, !!effectiveChartSeriesId);
|
|
2337
2577
|
if (effectiveChartStacked || chartCfg.stacked100) spec.stacked = true;
|
|
2338
2578
|
if (chartCfg.stacked100) spec.stacked100 = true;
|
|
2339
2579
|
if (chartCfg.palette) spec.palette = chartCfg.palette;
|
|
2340
2580
|
if (effectiveChartTimeAxis) spec.xType = "time";
|
|
2581
|
+
else if (chartBucket && chartDimensionIsDate) spec.xType = "ordinal-time";
|
|
2341
2582
|
if (effectiveChartLogScale) spec.yScale = "log";
|
|
2342
2583
|
if (effectiveChartValueFormat) spec.valueFormat = effectiveChartValueFormat;
|
|
2343
2584
|
applyChartLocale(spec);
|
|
2344
2585
|
return spec;
|
|
2345
2586
|
}
|
|
2346
2587
|
if (chartCfg.buildSpec) return chartCfg.buildSpec(chartRows) ?? null;
|
|
2588
|
+
// Engine not loaded yet (lazy): render nothing until it resolves.
|
|
2589
|
+
if (!chartEngine) return null;
|
|
2590
|
+
// Shared by the direct and aggregated paths so the two cannot drift apart
|
|
2591
|
+
// again - which is how these came to be skipped for scatter, gauge and
|
|
2592
|
+
// boxplot in the first place. Declared here, at the derived's own scope:
|
|
2593
|
+
// a function declaration inside the `else` block below is block-scoped,
|
|
2594
|
+
// and the aggregated path calls it after that block has closed.
|
|
2595
|
+
function applyChartValueSettings<T extends {
|
|
2596
|
+
xType?: string; yScale?: string; valueFormat?: unknown;
|
|
2597
|
+
}>(spec: T): T {
|
|
2598
|
+
if (effectiveChartTimeAxis) spec.xType = "time";
|
|
2599
|
+
if (effectiveChartLogScale) spec.yScale = "log";
|
|
2600
|
+
if (effectiveChartValueFormat) spec.valueFormat = effectiveChartValueFormat;
|
|
2601
|
+
applyChartLocale(spec as never);
|
|
2602
|
+
return spec;
|
|
2603
|
+
}
|
|
2347
2604
|
|
|
2605
|
+
// Pivot mode: the pivot on screen is the chart, rows as categories and
|
|
2606
|
+
// columns as series; the pickers have nothing to choose.
|
|
2607
|
+
let spec: ChartSpec;
|
|
2608
|
+
if (pivotResult) {
|
|
2609
|
+
spec = chartEngine.pivotResultToChartSpec(pivotResult, {
|
|
2610
|
+
type: chartType, stacked: effectiveChartStacked, stacked100: effectiveChartStacked100,
|
|
2611
|
+
measures: pivotConfig!.values,
|
|
2612
|
+
...(effectiveChartPalette ? { palette: effectiveChartPalette } : {}),
|
|
2613
|
+
});
|
|
2614
|
+
} else {
|
|
2348
2615
|
const category = fieldOf(effectiveChartDimensionId);
|
|
2349
2616
|
const values = effectiveChartMeasureIds.map((id) => fieldOf(id)).filter((f): f is string => !!f);
|
|
2350
|
-
if (!category || !values.length) return null;
|
|
2351
|
-
// Engine not loaded yet (lazy): render nothing until it resolves.
|
|
2352
|
-
if (!chartEngine) return null;
|
|
2617
|
+
if (!chartIsOhlc && (!category || !values.length)) return null;
|
|
2353
2618
|
const seriesField = fieldOf(effectiveChartSeriesId);
|
|
2354
2619
|
const rowsForChart = chartRows as Array<Record<string, unknown>>;
|
|
2355
2620
|
|
|
@@ -2360,26 +2625,51 @@ export function createSvGridController<
|
|
|
2360
2625
|
// base bundle, which every grid pays for whether or not it ever charts.
|
|
2361
2626
|
// Everything else falls through to the aggregation below and is reshaped
|
|
2362
2627
|
// afterwards, so reduce / sort / topN / "Other" keep working for all of it.
|
|
2628
|
+
const ohlcOpts = chartIsOhlc
|
|
2629
|
+
? chartEngine.ohlcDirectOptions(fieldOf, effectiveChartOhlcDateId, effectiveChartOhlc, { bucket: chartBucket, candleStyle: chartCandleStyle })
|
|
2630
|
+
: null;
|
|
2363
2631
|
const direct = chartEngine.rowsToDirectSpec(chartType, rowsForChart, {
|
|
2364
|
-
|
|
2632
|
+
...(ohlcOpts ?? {}),
|
|
2633
|
+
category: ohlcOpts?.category ?? (category as string),
|
|
2365
2634
|
...(values[0] ? { value: values[0] } : {}),
|
|
2366
2635
|
...(fieldOf(effectiveChartMeasure2Id) ? { value2: fieldOf(effectiveChartMeasure2Id)! } : {}),
|
|
2367
2636
|
...(seriesField ? { series: seriesField } : {}),
|
|
2368
2637
|
reduce: chartReduce,
|
|
2638
|
+
...(chartBins ? { bins: chartBins } : {}),
|
|
2369
2639
|
...(effectiveChartPalette ? { palette: effectiveChartPalette } : {}),
|
|
2370
2640
|
});
|
|
2371
|
-
|
|
2641
|
+
// Number formatting and the scale/axis-type settings are about how values
|
|
2642
|
+
// READ, so they apply to a scatter or a gauge exactly as they do to a bar
|
|
2643
|
+
// chart. They used to live only on the aggregated path below, so setting
|
|
2644
|
+
// `valueFormat` or `logScale` on a direct type silently did nothing.
|
|
2645
|
+
//
|
|
2646
|
+
// The series-shaped settings (trend, averageLine, seriesTypes) stay on the
|
|
2647
|
+
// aggregated path: a direct spec does not have the series shape they read.
|
|
2648
|
+
if (direct) {
|
|
2649
|
+
if (chartIsOhlc) {
|
|
2650
|
+
// Overlays ride on the price series; the panes are composed by the
|
|
2651
|
+
// panel body from `chartIndicatorPanes`.
|
|
2652
|
+
const overlay = chartIndicatorSplit.overlay;
|
|
2653
|
+
if (overlay && direct.series[0]) direct.series[0].overlay = overlay;
|
|
2654
|
+
}
|
|
2655
|
+
return applyChartValueSettings(direct);
|
|
2656
|
+
}
|
|
2372
2657
|
// A direct type that could not build (scatter with no Y picked yet, for
|
|
2373
2658
|
// instance) must not fall through to the bar-chart path and draw the wrong
|
|
2374
2659
|
// chart - it renders the panel's empty state instead.
|
|
2375
|
-
if (
|
|
2660
|
+
if (
|
|
2661
|
+
chartType === "scatter" || chartType === "gauge" || chartType === "boxplot" ||
|
|
2662
|
+
chartType === "histogram" || chartType === "range-bar" || chartType === "range-area" ||
|
|
2663
|
+
chartType === "dumbbell" || chartType === "bullet" || chartIsOhlc
|
|
2664
|
+
) return null;
|
|
2376
2665
|
|
|
2377
|
-
|
|
2666
|
+
spec = chartEngine.rowsToChartSpec<Record<string, unknown>>(rowsForChart, {
|
|
2378
2667
|
type: chartType,
|
|
2379
2668
|
category: category as string,
|
|
2380
2669
|
value: values.length === 1 ? values[0]! : values,
|
|
2381
2670
|
...(seriesField ? { series: seriesField } : {}),
|
|
2382
2671
|
reduce: chartReduce,
|
|
2672
|
+
...(chartBucket && chartDimensionIsDate ? { bucket: chartBucket } : {}),
|
|
2383
2673
|
stacked: effectiveChartStacked || effectiveChartStacked100,
|
|
2384
2674
|
stacked100: effectiveChartStacked100,
|
|
2385
2675
|
...(effectiveChartPalette ? { palette: effectiveChartPalette } : {}),
|
|
@@ -2388,10 +2678,16 @@ export function createSvGridController<
|
|
|
2388
2678
|
...(chartCfg.sort ? { sort: chartCfg.sort } : {}),
|
|
2389
2679
|
});
|
|
2390
2680
|
|
|
2681
|
+
}
|
|
2391
2682
|
// Reshape the aggregated grid into whatever this type actually reads.
|
|
2392
2683
|
// These run AFTER the aggregation so grouping, reduce, sort, topN and the
|
|
2393
2684
|
// "Other" bucket apply to them exactly as they do to a bar chart.
|
|
2394
|
-
if (chartType === "treemap") spec.treemap = chartEngine.specToTreemap(spec);
|
|
2685
|
+
if (chartType === "treemap" || chartType === "sunburst") spec.treemap = chartEngine.specToTreemap(spec);
|
|
2686
|
+
else if (chartType === "chord") {
|
|
2687
|
+
const flow = chartEngine.specToSankey(spec);
|
|
2688
|
+
spec.sankeyNodes = flow.nodes;
|
|
2689
|
+
spec.sankeyLinks = flow.links;
|
|
2690
|
+
}
|
|
2395
2691
|
else if (chartType === "calendar") spec.calendarValues = chartEngine.specToCalendar(spec);
|
|
2396
2692
|
else if (chartType === "sankey") {
|
|
2397
2693
|
const flow = chartEngine.specToSankey(spec);
|
|
@@ -2400,10 +2696,7 @@ export function createSvGridController<
|
|
|
2400
2696
|
}
|
|
2401
2697
|
|
|
2402
2698
|
if (effectiveChartOrientation === "horizontal") spec.orientation = "horizontal";
|
|
2403
|
-
|
|
2404
|
-
if (effectiveChartLogScale) spec.yScale = "log";
|
|
2405
|
-
if (effectiveChartValueFormat) spec.valueFormat = effectiveChartValueFormat;
|
|
2406
|
-
applyChartLocale(spec);
|
|
2699
|
+
applyChartValueSettings(spec);
|
|
2407
2700
|
if (chartType !== "pie" && spec.orientation !== "horizontal" && !spec.yAxisTitle && chartAutoYAxisTitle) {
|
|
2408
2701
|
spec.yAxisTitle = chartAutoYAxisTitle;
|
|
2409
2702
|
}
|
|
@@ -2413,6 +2706,11 @@ export function createSvGridController<
|
|
|
2413
2706
|
}
|
|
2414
2707
|
if (chartCfg.annotations) spec.annotations = chartCfg.annotations;
|
|
2415
2708
|
if (chartCfg.patternFallback) spec.patternFallback = true;
|
|
2709
|
+
if (chartFunnelShape && chartType === "funnel") spec.funnelShape = chartFunnelShape;
|
|
2710
|
+
if (chartCandleStyle && (chartType === "candlestick" || chartType === "ohlc")) spec.candleStyle = chartCandleStyle;
|
|
2711
|
+
// A stream is a stacked area on a wiggle baseline; the picker's Stacked
|
|
2712
|
+
// box has nothing to add, so the offset is applied here outright.
|
|
2713
|
+
if (chartType === "stream") { spec.stacked = true; spec.stackOffset = "wiggle"; }
|
|
2416
2714
|
const refLines = [...(chartCfg.referenceLines ?? [])];
|
|
2417
2715
|
if (chartCfg.averageLine && spec.series[0]?.values.length) {
|
|
2418
2716
|
const vals = spec.series[0].values;
|
|
@@ -2421,7 +2719,10 @@ export function createSvGridController<
|
|
|
2421
2719
|
}
|
|
2422
2720
|
if (refLines.length) spec.referenceLines = refLines;
|
|
2423
2721
|
for (const s of spec.series) {
|
|
2424
|
-
if (chartCfg.trend)
|
|
2722
|
+
if (chartCfg.trend) {
|
|
2723
|
+
const t = chartCfg.trend;
|
|
2724
|
+
s.overlay = t === "sma" || t === "ema" ? `${t}:7` : t === "poly" ? "poly:2" : t;
|
|
2725
|
+
}
|
|
2425
2726
|
if (chartCfg.smooth) s.smooth = true;
|
|
2426
2727
|
if (chartCfg.seriesTypes?.[s.label]) s.type = chartCfg.seriesTypes[s.label];
|
|
2427
2728
|
if (chartCfg.seriesAxes?.[s.label]) s.axis = chartCfg.seriesAxes[s.label];
|
|
@@ -2429,6 +2730,43 @@ export function createSvGridController<
|
|
|
2429
2730
|
return spec;
|
|
2430
2731
|
});
|
|
2431
2732
|
|
|
2733
|
+
/**
|
|
2734
|
+
* What the panel renders: an unlinked chart's frozen snapshot, else the live
|
|
2735
|
+
* derivation with the builder's format applied. The format runs through
|
|
2736
|
+
* the lazy engine so its code stays out of the base bundle.
|
|
2737
|
+
*/
|
|
2738
|
+
const chartSpec = $derived.by<ChartSpec | null>(() => {
|
|
2739
|
+
if (activeChart.frozen) return activeChart.frozen.spec;
|
|
2740
|
+
const live = chartSpecLive;
|
|
2741
|
+
if (!live || !activeChart.format || !chartEngine) return live;
|
|
2742
|
+
return chartEngine.applyChartFormat(live, activeChart.format);
|
|
2743
|
+
});
|
|
2744
|
+
let announcedFirstChart = false;
|
|
2745
|
+
$effect(() => {
|
|
2746
|
+
if (!chartPanelOpen || announcedFirstChart || !chartCfg?.onChartCreated) return;
|
|
2747
|
+
announcedFirstChart = true;
|
|
2748
|
+
const c = charts[0]!;
|
|
2749
|
+
untrack(() => chartCfg?.onChartCreated?.({ index: 0, title: c.title, type: c.type, spec: null }));
|
|
2750
|
+
});
|
|
2751
|
+
// Lifecycle: one `onChartChanged` per burst of spec changes.
|
|
2752
|
+
let chartChangeTimer: ReturnType<typeof setTimeout> | null = null;
|
|
2753
|
+
$effect(() => {
|
|
2754
|
+
const cb = chartCfg?.onChartChanged;
|
|
2755
|
+
const spec = chartSpec;
|
|
2756
|
+
const index = activeChartIndex;
|
|
2757
|
+
if (!cb) return;
|
|
2758
|
+
untrack(() => {
|
|
2759
|
+
if (chartChangeTimer) clearTimeout(chartChangeTimer);
|
|
2760
|
+
chartChangeTimer = setTimeout(() => {
|
|
2761
|
+
chartChangeTimer = null;
|
|
2762
|
+
const c = charts[index] ?? activeChart;
|
|
2763
|
+
cb({ index, title: c.title, type: c.type, spec });
|
|
2764
|
+
}, 150);
|
|
2765
|
+
});
|
|
2766
|
+
return () => {
|
|
2767
|
+
if (chartChangeTimer) { clearTimeout(chartChangeTimer); chartChangeTimer = null; }
|
|
2768
|
+
};
|
|
2769
|
+
});
|
|
2432
2770
|
/**
|
|
2433
2771
|
* Can a click on this chart be turned back into a grid filter?
|
|
2434
2772
|
*
|
|
@@ -2438,12 +2776,19 @@ export function createSvGridController<
|
|
|
2438
2776
|
* at all), or a scatter (the mark carries a point label, not a group).
|
|
2439
2777
|
*/
|
|
2440
2778
|
const chartCrossFilterable = $derived(
|
|
2441
|
-
chartType !== "sankey" && chartType !== "gauge" && chartType !== "scatter"
|
|
2779
|
+
chartType !== "sankey" && chartType !== "gauge" && chartType !== "scatter" &&
|
|
2780
|
+
// A chord's arcs are both ends of a flow, a sunburst's are a hierarchy
|
|
2781
|
+
// and a histogram's are bins: none maps a click back to one dimension value.
|
|
2782
|
+
chartType !== "chord" && chartType !== "sunburst" && chartType !== "histogram",
|
|
2442
2783
|
);
|
|
2443
2784
|
|
|
2785
|
+
/** The column a chart click filters: the picked dimension, or in pivot mode the innermost row dimension when it is a grid column. */
|
|
2786
|
+
const chartFilterDimId = $derived(
|
|
2787
|
+
pivotResult ? (chartEngine?.pivotFilterColumn(pivotConfig!.rows, chartableColumns.dims, fieldOf) ?? null) : effectiveChartDimensionId,
|
|
2788
|
+
);
|
|
2444
2789
|
function applyChartCrossFilter(category: string) {
|
|
2445
2790
|
if (!chartCrossFilterable) return;
|
|
2446
|
-
const dimId =
|
|
2791
|
+
const dimId = chartFilterDimId;
|
|
2447
2792
|
if (!dimId) return;
|
|
2448
2793
|
const existing = valueFilters[dimId];
|
|
2449
2794
|
const next = new Set(existing && existing.has(category) ? existing : []);
|
|
@@ -2452,7 +2797,7 @@ export function createSvGridController<
|
|
|
2452
2797
|
valueFilters = { ...valueFilters, [dimId]: next };
|
|
2453
2798
|
}
|
|
2454
2799
|
function clearChartCrossFilter() {
|
|
2455
|
-
const dimId =
|
|
2800
|
+
const dimId = chartFilterDimId;
|
|
2456
2801
|
if (!dimId) return;
|
|
2457
2802
|
const next = { ...valueFilters };
|
|
2458
2803
|
delete next[dimId];
|
|
@@ -2600,6 +2945,55 @@ export function createSvGridController<
|
|
|
2600
2945
|
Math.max(virtualRowTotalSize - virtualRowEnd, 0),
|
|
2601
2946
|
);
|
|
2602
2947
|
|
|
2948
|
+
// A row model tells us when to re-read it. Subscribing here (rather than
|
|
2949
|
+
// polling, or asking the consumer to wrap the model in a rune) is what lets
|
|
2950
|
+
// `getRows()` be a plain method call on a plain object.
|
|
2951
|
+
$effect(() => {
|
|
2952
|
+
const model = rawProps.rowModel;
|
|
2953
|
+
if (!model) return;
|
|
2954
|
+
return model.subscribe(() => {
|
|
2955
|
+
rowModelVersion += 1;
|
|
2956
|
+
});
|
|
2957
|
+
});
|
|
2958
|
+
|
|
2959
|
+
// The index range on screen, for data sources that fetch by block.
|
|
2960
|
+
// `virtualRowStart` / `virtualRowEnd` above are PIXEL offsets; a fetcher
|
|
2961
|
+
// needs row numbers, and needs them whether or not virtualization is on.
|
|
2962
|
+
const visibleRowRange = $derived.by(function visibleRowRange_d() {
|
|
2963
|
+
if (!rowVirtualizationEnabled) {
|
|
2964
|
+
return { startIndex: 0, endIndex: Math.max(0, allRows.length - 1) };
|
|
2965
|
+
}
|
|
2966
|
+
const first = virtualRows[0];
|
|
2967
|
+
const last = virtualRows[virtualRows.length - 1];
|
|
2968
|
+
if (!first || !last) return { startIndex: 0, endIndex: 0 };
|
|
2969
|
+
return { startIndex: first.index, endIndex: last.index };
|
|
2970
|
+
});
|
|
2971
|
+
// Emitted from an effect rather than the derived so a consumer that
|
|
2972
|
+
// fetches (and therefore changes `data`) cannot re-enter the derivation.
|
|
2973
|
+
let lastReportedRange = "";
|
|
2974
|
+
$effect(() => {
|
|
2975
|
+
const report = props.onVisibleRangeChange;
|
|
2976
|
+
if (!report) return;
|
|
2977
|
+
const range = visibleRowRange;
|
|
2978
|
+
const key = range.startIndex + ":" + range.endIndex;
|
|
2979
|
+
if (key === lastReportedRange) return;
|
|
2980
|
+
lastReportedRange = key;
|
|
2981
|
+
report(range);
|
|
2982
|
+
});
|
|
2983
|
+
|
|
2984
|
+
// A paging row model that asks for `autoPageSize` gets as many rows as
|
|
2985
|
+
// the body shows without scrolling, re-measured as the viewport changes.
|
|
2986
|
+
$effect(() => {
|
|
2987
|
+
rowModelVersion;
|
|
2988
|
+
const paging = rawProps.rowModel?.pagination;
|
|
2989
|
+
if (!paging?.autoPageSize) return;
|
|
2990
|
+
const height = viewportHeight;
|
|
2991
|
+
if (height <= 0) return;
|
|
2992
|
+
const rowPx = rowSizePxOf(0) || 30;
|
|
2993
|
+
const fit = Math.max(1, Math.floor(height / rowPx));
|
|
2994
|
+
if (fit !== paging.pageSize) paging.setPageSize(fit);
|
|
2995
|
+
});
|
|
2996
|
+
|
|
2603
2997
|
// --- Huge-list scroll scaling -----------------------------------------
|
|
2604
2998
|
// Browsers cap how tall a single element may be, and mobile caps sit well
|
|
2605
2999
|
// below desktop. Past a few hundred thousand rows the true content height
|
|
@@ -2647,9 +3041,46 @@ export function createSvGridController<
|
|
|
2647
3041
|
const logical = virtualizer.getState().scrollOffset;
|
|
2648
3042
|
return logical - rowScrollScaling.logicalToDom(logical);
|
|
2649
3043
|
});
|
|
3044
|
+
/**
|
|
3045
|
+
* The first `frozenRows` rows are rendered ahead of the virtual window
|
|
3046
|
+
* and stick under the header. The virtualizer still counts them, so its
|
|
3047
|
+
* offsets stay in row-index space and nothing downstream has to translate;
|
|
3048
|
+
* the window skips them, and the top spacer gives back the height they
|
|
3049
|
+
* already occupy in the flow.
|
|
3050
|
+
*/
|
|
3051
|
+
const frozenRowCount = $derived(
|
|
3052
|
+
Math.max(0, Math.min(Math.floor(props.frozenRows ?? 0), allRows.length)),
|
|
3053
|
+
);
|
|
3054
|
+
const frozenRowList = $derived(frozenRowCount ? allRows.slice(0, frozenRowCount) : []);
|
|
3055
|
+
/** The merged cells, indexed; null (the usual case) costs nothing. */
|
|
3056
|
+
const mergeIndex = $derived(buildMergeIndex(props.mergedCells));
|
|
3057
|
+
const frozenBandHeight = $derived.by(() => {
|
|
3058
|
+
if (!frozenRowCount) return 0;
|
|
3059
|
+
rowResizeVersion;
|
|
3060
|
+
autoRowHeightVersion;
|
|
3061
|
+
let total = 0;
|
|
3062
|
+
for (let i = 0; i < frozenRowCount; i += 1) total += rowSizePxOf(i);
|
|
3063
|
+
return total;
|
|
3064
|
+
});
|
|
3065
|
+
/** The declared or dragged height of one row; measured under auto height. */
|
|
3066
|
+
function rowSizePxOf(i: number): number {
|
|
3067
|
+
if (isRowCollapsed(i)) return 0;
|
|
3068
|
+
if (autoRowHeightOn) return measuredRowHeights.get(i) ?? autoRowHeightFallback;
|
|
3069
|
+
const own = rowResizeHeightPx(i);
|
|
3070
|
+
if (own != null) return own;
|
|
3071
|
+
const rh = props.rowHeight;
|
|
3072
|
+
return typeof rh === "function" ? rh(i) : (rh ?? 30);
|
|
3073
|
+
}
|
|
3074
|
+
/** Where frozen row i sticks: under the header and the frozen rows above it. */
|
|
3075
|
+
function frozenRowTop(i: number): number {
|
|
3076
|
+
let top = headerHeight;
|
|
3077
|
+
for (let k = 0; k < i; k += 1) top += rowSizePxOf(k);
|
|
3078
|
+
return top;
|
|
3079
|
+
}
|
|
2650
3080
|
// Spacer heights in DOM space. With scaling inert these equal the original
|
|
2651
|
-
// virtualRowStart / virtualRowBottomSpacer.
|
|
2652
|
-
|
|
3081
|
+
// virtualRowStart / virtualRowBottomSpacer. The frozen band is in the flow
|
|
3082
|
+
// before the spacer whatever the window shows, so it comes off the spacer.
|
|
3083
|
+
const rowTopSpacer = $derived(Math.max(virtualRowStart - rowOffsetAdjustment - frozenBandHeight, 0));
|
|
2653
3084
|
const rowBottomSpacer = $derived(
|
|
2654
3085
|
Math.max(rowDomTotalSize - (virtualRowEnd - rowOffsetAdjustment), 0),
|
|
2655
3086
|
);
|
|
@@ -3003,35 +3434,104 @@ export function createSvGridController<
|
|
|
3003
3434
|
);
|
|
3004
3435
|
|
|
3005
3436
|
// ----- Interactive row resize (`rowResize` prop) -----
|
|
3006
|
-
// Heights the user has dragged,
|
|
3007
|
-
//
|
|
3008
|
-
//
|
|
3009
|
-
//
|
|
3010
|
-
//
|
|
3011
|
-
|
|
3437
|
+
// Heights the user has dragged, or set through `api.setRowHeight`, keyed
|
|
3438
|
+
// by ROW ID so they belong to the row rather than to a slot: the height
|
|
3439
|
+
// follows its row through a sort or a filter and survives the data being
|
|
3440
|
+
// replaced. They used to be keyed by index and wiped on every data change,
|
|
3441
|
+
// which in a spreadsheet meant every keystroke, so a row dragged taller
|
|
3442
|
+
// snapped back the moment the next cell was typed. A plain Map plus a
|
|
3443
|
+
// version counter, like `measuredRowHeights` above: the readers touch the
|
|
3444
|
+
// version, and the Map itself stays out of the reactive graph.
|
|
3445
|
+
const rowResizeHeights = new Map<string, number>();
|
|
3012
3446
|
let rowResizeVersion = $state(0);
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3447
|
+
/**
|
|
3448
|
+
* Rows folded to nothing, the row-side twin of `collapsedColumns`: the
|
|
3449
|
+
* row keeps its index and its cells and takes no height. Keyed by row id
|
|
3450
|
+
* like the heights and announced through the same version counter, so
|
|
3451
|
+
* every reader of a row's size sees the change.
|
|
3452
|
+
*/
|
|
3453
|
+
const collapsedRows = new Set<string>();
|
|
3454
|
+
function isRowCollapsed(index: number): boolean {
|
|
3455
|
+
rowResizeVersion;
|
|
3456
|
+
const id = allRows[index]?.id;
|
|
3457
|
+
return id != null && collapsedRows.has(id);
|
|
3458
|
+
}
|
|
3459
|
+
function setRowCollapsed(index: number, collapsed: boolean): void {
|
|
3460
|
+
const id = allRows[index]?.id;
|
|
3461
|
+
if (id == null) return;
|
|
3462
|
+
if (collapsed === collapsedRows.has(id)) return;
|
|
3463
|
+
if (collapsed) collapsedRows.add(id);
|
|
3464
|
+
else collapsedRows.delete(id);
|
|
3017
3465
|
rowResizeVersion += 1;
|
|
3018
3466
|
}
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3467
|
+
|
|
3468
|
+
/**
|
|
3469
|
+
* A size the user changed by hand is one undo step, as it is in a
|
|
3470
|
+
* spreadsheet: Ctrl+Z after a column drag puts the width back. Recorded
|
|
3471
|
+
* only for the user's gestures; the api's own setters record nothing, so
|
|
3472
|
+
* a consumer restoring saved sizes does not fill the history.
|
|
3473
|
+
*/
|
|
3474
|
+
function recordSizeUndo(undo: () => void, redo: () => void): void {
|
|
3475
|
+
pushHistory(ctx, [
|
|
3476
|
+
{ rowId: "", columnId: "", field: "", before: undefined, after: undefined, custom: { undo, redo } },
|
|
3477
|
+
]);
|
|
3478
|
+
}
|
|
3479
|
+
// The undo and redo of a size step report through the same callbacks as
|
|
3480
|
+
// the gesture did: a consumer keeping the sizes (a sheet saving its
|
|
3481
|
+
// document) would otherwise hold the width the user dragged to while the
|
|
3482
|
+
// grid showed the one Ctrl+Z put back.
|
|
3483
|
+
function resizeColumnByUser(columnId: string, width: number, before: number): void {
|
|
3484
|
+
const apply = (w: number) => {
|
|
3485
|
+
columnWidths = { ...columnWidths, [columnId]: w };
|
|
3486
|
+
props.onColumnResize?.({ columnId, width: w });
|
|
3487
|
+
};
|
|
3488
|
+
apply(width);
|
|
3489
|
+
if (before !== width) recordSizeUndo(() => apply(before), () => apply(width));
|
|
3490
|
+
}
|
|
3491
|
+
function autosizeColumnByUser(columnId: string): void {
|
|
3492
|
+
const before = getColumnWidth(columnId);
|
|
3493
|
+
autosizeColumn(columnId);
|
|
3494
|
+
const width = getColumnWidth(columnId);
|
|
3495
|
+
const apply = (w: number) => {
|
|
3496
|
+
columnWidths = { ...columnWidths, [columnId]: w };
|
|
3497
|
+
props.onColumnResize?.({ columnId, width: w });
|
|
3498
|
+
};
|
|
3499
|
+
if (before !== width) recordSizeUndo(() => apply(before), () => apply(width));
|
|
3500
|
+
else props.onColumnResize?.({ columnId, width });
|
|
3501
|
+
}
|
|
3502
|
+
function resizeRowByUser(index: number, height: number | null): void {
|
|
3503
|
+
const before = rowResizeHeightPx(index) ?? null;
|
|
3504
|
+
const apply = (h: number | null) => {
|
|
3505
|
+
setRowResizeHeight(index, h);
|
|
3506
|
+
props.onRowResize?.({ rowIndex: index, height: h });
|
|
3507
|
+
};
|
|
3508
|
+
apply(height);
|
|
3509
|
+
if (before !== height) recordSizeUndo(() => apply(before), () => apply(height));
|
|
3023
3510
|
}
|
|
3024
3511
|
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3512
|
+
function setRowResizeHeight(index: number, height: number | null): void {
|
|
3513
|
+
const id = allRows[index]?.id;
|
|
3514
|
+
if (id == null) return;
|
|
3515
|
+
if (height == null) {
|
|
3516
|
+
if (!rowResizeHeights.delete(id)) return;
|
|
3517
|
+
} else {
|
|
3518
|
+
if (!Number.isFinite(height) || height <= 0) return;
|
|
3519
|
+
rowResizeHeights.set(id, Math.round(height));
|
|
3520
|
+
}
|
|
3033
3521
|
rowResizeVersion += 1;
|
|
3034
|
-
}
|
|
3522
|
+
}
|
|
3523
|
+
/** A row's own height, or undefined when it has none. Read by the view AND
|
|
3524
|
+
* by the virtualizer's `estimateSize`. Auto height wins over it: the
|
|
3525
|
+
* content decides the height there, and a manual one would be overwritten
|
|
3526
|
+
* by the next measurement. */
|
|
3527
|
+
function rowResizeHeightPx(index: number): number | undefined {
|
|
3528
|
+
const id = allRows[index]?.id;
|
|
3529
|
+
if (id == null) return undefined;
|
|
3530
|
+
// A collapsed row is 0 whatever else it has, under auto height too.
|
|
3531
|
+
if (collapsedRows.has(id)) return 0;
|
|
3532
|
+
if (autoRowHeightOn) return undefined;
|
|
3533
|
+
return rowResizeHeights.get(id);
|
|
3534
|
+
}
|
|
3035
3535
|
|
|
3036
3536
|
/** Report a row's measured height. Called by the view's measuring action.
|
|
3037
3537
|
* Sub-pixel churn is ignored so a fractional layout can't loop. */
|
|
@@ -3056,11 +3556,19 @@ export function createSvGridController<
|
|
|
3056
3556
|
function measureRowHeight(node: HTMLElement, index: number) {
|
|
3057
3557
|
let current = index;
|
|
3058
3558
|
let stop: (() => void) | null = null;
|
|
3059
|
-
|
|
3559
|
+
// Read the rect only under autoRowHeight. `getBoundingClientRect` forces a
|
|
3560
|
+
// synchronous layout, and the virtualizer's slot keys hand every rendered
|
|
3561
|
+
// <tr> a new index on each one-row scroll, so measuring unconditionally
|
|
3562
|
+
// meant ~30 forced layouts per arrow key on a fixed-height grid whose
|
|
3563
|
+
// measurement `reportRowHeight` then threw away.
|
|
3564
|
+
const measure = () => {
|
|
3565
|
+
if (!autoRowHeightOn) return;
|
|
3566
|
+
reportRowHeight(current, node.getBoundingClientRect().height);
|
|
3567
|
+
};
|
|
3060
3568
|
const attach = () => {
|
|
3061
3569
|
stop?.();
|
|
3062
3570
|
stop = autoRowHeightOn ? observeSizeRaf(node, measure) : null;
|
|
3063
|
-
|
|
3571
|
+
measure();
|
|
3064
3572
|
};
|
|
3065
3573
|
attach();
|
|
3066
3574
|
return {
|
|
@@ -3112,9 +3620,9 @@ export function createSvGridController<
|
|
|
3112
3620
|
: typeof rh === "function"
|
|
3113
3621
|
? rh
|
|
3114
3622
|
: (rh ?? 30);
|
|
3115
|
-
const estimateSize =
|
|
3623
|
+
const estimateSize = rowResizeHeights.size > 0 || collapsedRows.size > 0
|
|
3116
3624
|
? (index: number) =>
|
|
3117
|
-
|
|
3625
|
+
rowResizeHeightPx(index) ??
|
|
3118
3626
|
(typeof base === "function" ? base(index) : base)
|
|
3119
3627
|
: base;
|
|
3120
3628
|
virtualizer.setOptions({
|
|
@@ -3175,6 +3683,7 @@ export function createSvGridController<
|
|
|
3175
3683
|
// closure each run; the virtualizer sees a new function reference and
|
|
3176
3684
|
// re-derives its layout from the current per-column widths.
|
|
3177
3685
|
columnWidths;
|
|
3686
|
+
collapsedColumns;
|
|
3178
3687
|
columnVirtualizer.setOptions({
|
|
3179
3688
|
count: allColumns.length,
|
|
3180
3689
|
estimateSize: (index: number) => {
|
|
@@ -3243,9 +3752,18 @@ export function createSvGridController<
|
|
|
3243
3752
|
|
|
3244
3753
|
|
|
3245
3754
|
|
|
3755
|
+
// Depends on `allRows` and `rowSelectionState` only. It used to read the
|
|
3756
|
+
// catch-all `gridStateVersion` too, which made every arrow key walk all the
|
|
3757
|
+
// rows (10k iterations on the large-dataset demo, 100k on its top tier) to
|
|
3758
|
+
// recount a selection that had not changed.
|
|
3246
3759
|
const headerSelectionState = $derived.by(function headerSelectionState_d() {
|
|
3247
|
-
|
|
3248
|
-
|
|
3760
|
+
// An external model knows about rows the grid has never seen, so it is
|
|
3761
|
+
// the only thing that can answer honestly once the data is a window
|
|
3762
|
+
// onto a server.
|
|
3763
|
+
if (props.rowSelectionModel) return props.rowSelectionModel.headerState();
|
|
3764
|
+
const selectable = allRows.filter(
|
|
3765
|
+
(row) => !isGroupRow(row) && !placeholderStateOf(row),
|
|
3766
|
+
);
|
|
3249
3767
|
if (!selectable.length) return "none";
|
|
3250
3768
|
let selected = 0;
|
|
3251
3769
|
for (const row of selectable) if (rowSelectionState[row.id]) selected += 1;
|
|
@@ -3261,6 +3779,10 @@ export function createSvGridController<
|
|
|
3261
3779
|
* from a user-focused (0,0). The fill handle keys off this flag so it
|
|
3262
3780
|
* stays hidden until the user actually selects something. */
|
|
3263
3781
|
let userHasActivatedCell = $state(false);
|
|
3782
|
+
/** The column a run of Tabs began in, so the Enter that ends the run goes
|
|
3783
|
+
* down from there (`getEntryStep`). Plain, not state: nothing renders
|
|
3784
|
+
* it. `setActiveCell` clears it; the Tab and Enter paths set it back. */
|
|
3785
|
+
let tabRunOrigin: number | null = null;
|
|
3264
3786
|
|
|
3265
3787
|
|
|
3266
3788
|
|
|
@@ -3286,7 +3808,7 @@ export function createSvGridController<
|
|
|
3286
3808
|
if (!props.fitColumns || isNarrowResponsive) return null;
|
|
3287
3809
|
// Exclude columns hidden by a collapsed group too (#57) - otherwise their
|
|
3288
3810
|
// widths stay in the fit distribution and leave a blank gap at the edge.
|
|
3289
|
-
const cols = grid.getAllColumns().filter((c) => !hiddenColumns[c.id] && !hiddenByGroupCollapse[c.id]);
|
|
3811
|
+
const cols = grid.getAllColumns().filter((c) => !hiddenColumns[c.id] && !hiddenByGroupCollapse[c.id] && !collapsedColumns[c.id]);
|
|
3290
3812
|
if (!cols.length) return null;
|
|
3291
3813
|
const rowNumberWidth = showRowNumbersEffective ? rowNumberColumnWidth : 0;
|
|
3292
3814
|
const selectionWidth = showRowSelectionEffective ? selectionColumnWidth : 0;
|
|
@@ -3465,7 +3987,12 @@ export function createSvGridController<
|
|
|
3465
3987
|
for (const column of allColumns) {
|
|
3466
3988
|
const meta = isBucketableColumn(column);
|
|
3467
3989
|
if (!meta) continue;
|
|
3468
|
-
const buckets = buildBuckets(
|
|
3990
|
+
const buckets = buildBuckets(
|
|
3991
|
+
column,
|
|
3992
|
+
meta.isDate,
|
|
3993
|
+
props.data ?? EMPTY_DATA,
|
|
3994
|
+
getColumnAccessorValue,
|
|
3995
|
+
);
|
|
3469
3996
|
if (buckets) map.set(column.id, buckets);
|
|
3470
3997
|
}
|
|
3471
3998
|
return map;
|
|
@@ -3712,12 +4239,22 @@ export function createSvGridController<
|
|
|
3712
4239
|
// state mutation inside the callback (e.g. `api.setGroupBy(...)`) created
|
|
3713
4240
|
// an infinite update loop. Now it's a true mount-once notification.
|
|
3714
4241
|
let apiNotified = false;
|
|
4242
|
+
// Memoized: `buildApi()` builds a fresh object each call, and a feature pack
|
|
4243
|
+
// that keys per-grid state on the api (a WeakMap in @svgrid/enterprise) needs
|
|
4244
|
+
// the object the consumer received and the object a command handler sees to
|
|
4245
|
+
// be the same one. Every method reads through `ctx`, so one instance stays
|
|
4246
|
+
// correct for the life of the grid.
|
|
4247
|
+
let apiInstance: ReturnType<typeof buildApi> | null = null;
|
|
4248
|
+
function api() {
|
|
4249
|
+
if (!apiInstance) apiInstance = buildApi();
|
|
4250
|
+
return apiInstance;
|
|
4251
|
+
}
|
|
3715
4252
|
$effect(() => {
|
|
3716
4253
|
if (apiNotified) return;
|
|
3717
4254
|
const cb = props.onApiReady;
|
|
3718
4255
|
if (!cb) return;
|
|
3719
4256
|
apiNotified = true;
|
|
3720
|
-
cb(
|
|
4257
|
+
cb(api());
|
|
3721
4258
|
});
|
|
3722
4259
|
|
|
3723
4260
|
const ctx = {
|
|
@@ -3780,6 +4317,8 @@ export function createSvGridController<
|
|
|
3780
4317
|
set history(v) { history = v as never; },
|
|
3781
4318
|
get historyPtr() { return historyPtr; },
|
|
3782
4319
|
set historyPtr(v) { historyPtr = v as never; },
|
|
4320
|
+
get historyGroupId() { return historyGroupId; },
|
|
4321
|
+
set historyGroupId(v) { historyGroupId = v as never; },
|
|
3783
4322
|
get historyVersion() { return historyVersion; },
|
|
3784
4323
|
set historyVersion(v) { historyVersion = v as never; },
|
|
3785
4324
|
get tooltip() { return tooltip; },
|
|
@@ -3802,6 +4341,9 @@ export function createSvGridController<
|
|
|
3802
4341
|
get editorSelectAll() { return editorSelectAll; },
|
|
3803
4342
|
set editorSelectAll(v) { editorSelectAll = v as never; },
|
|
3804
4343
|
get columnWidths() { return columnWidths; },
|
|
4344
|
+
get collapsedColumns() { return collapsedColumns; },
|
|
4345
|
+
get mergeIndex() { return mergeIndex; },
|
|
4346
|
+
get setColumnCollapsed() { return setColumnCollapsed; },
|
|
3805
4347
|
set columnWidths(v) { columnWidths = v as never; },
|
|
3806
4348
|
get MIN_COLUMN_WIDTH() { return MIN_COLUMN_WIDTH; },
|
|
3807
4349
|
get columnPinning() { return columnPinning; },
|
|
@@ -3873,6 +4415,7 @@ export function createSvGridController<
|
|
|
3873
4415
|
get moveCellsEffective() { return moveCellsEffective; },
|
|
3874
4416
|
get flushScheduledScrollSync() { return flushScheduledScrollSync; },
|
|
3875
4417
|
get scheduleScrollSync() { return scheduleScrollSync; },
|
|
4418
|
+
get syncScrollNow() { return syncScrollNow; },
|
|
3876
4419
|
get internalData() { return internalData; },
|
|
3877
4420
|
set internalData(v) { internalData = v as never; },
|
|
3878
4421
|
get internalColumns() { return internalColumns; },
|
|
@@ -3958,6 +4501,8 @@ export function createSvGridController<
|
|
|
3958
4501
|
get selectionBarMaxVisible() { return selectionBarMaxVisible; },
|
|
3959
4502
|
get selectionBarHideClear() { return selectionBarHideClear; },
|
|
3960
4503
|
get selectionBarTarget() { return selectionBarTarget; },
|
|
4504
|
+
get selectionBarCount() { return selectionBarCount; },
|
|
4505
|
+
get selectionBarVisible() { return selectionBarVisible; },
|
|
3961
4506
|
get lastSelectionSerialized() { return lastSelectionSerialized; },
|
|
3962
4507
|
set lastSelectionSerialized(v) { lastSelectionSerialized = v as never; },
|
|
3963
4508
|
get lastCellRangeSerialized() { return lastCellRangeSerialized; },
|
|
@@ -3976,6 +4521,8 @@ export function createSvGridController<
|
|
|
3976
4521
|
get moveColumnInPanel() { return moveColumnInPanel; },
|
|
3977
4522
|
// ---- Localized chrome strings ----
|
|
3978
4523
|
get messages() { return gridMessages; },
|
|
4524
|
+
/** The raw `localization.text` map, for the lazy chart panel's own strings. */
|
|
4525
|
+
get localizationText() { return props.localization?.text; },
|
|
3979
4526
|
// ---- In-grid pivot mode ----
|
|
3980
4527
|
get pivotConfig() { return pivotConfig; },
|
|
3981
4528
|
get pivotActive() { return pivotActive; },
|
|
@@ -4008,6 +4555,32 @@ export function createSvGridController<
|
|
|
4008
4555
|
set chartLogScale(v) { activeChart.logScale = v as never; },
|
|
4009
4556
|
get chartTimeAxis() { return effectiveChartTimeAxis; },
|
|
4010
4557
|
set chartTimeAxis(v) { activeChart.timeAxis = v as never; },
|
|
4558
|
+
get chartBucket() { return chartBucket; },
|
|
4559
|
+
set chartBucket(v) { activeChart.bucket = v as never; },
|
|
4560
|
+
get chartBins() { return chartBins; },
|
|
4561
|
+
set chartBins(v) { activeChart.bins = v as never; },
|
|
4562
|
+
get chartFunnelShape() { return chartFunnelShape; },
|
|
4563
|
+
set chartFunnelShape(v) { activeChart.funnelShape = v as never; },
|
|
4564
|
+
get chartCandleStyle() { return chartCandleStyle; },
|
|
4565
|
+
set chartCandleStyle(v) { activeChart.candleStyle = v as never; },
|
|
4566
|
+
get chartFormat() { return activeChart.format; },
|
|
4567
|
+
set chartFormat(v) { activeChart.format = v as never; },
|
|
4568
|
+
get chartFrozen() { return activeChart.frozen; },
|
|
4569
|
+
get chartIsOhlc() { return chartIsOhlc; },
|
|
4570
|
+
get chartOhlc() { return effectiveChartOhlc; },
|
|
4571
|
+
set chartOhlc(v) { activeChart.ohlc = v as never; },
|
|
4572
|
+
get chartOhlcDateId() { return effectiveChartOhlcDateId; },
|
|
4573
|
+
get chartIndicators() { return chartIndicators; },
|
|
4574
|
+
set chartIndicators(v) { activeChart.indicators = v as never; },
|
|
4575
|
+
get chartIndicatorPanes() { return chartIndicatorPanes; },
|
|
4576
|
+
get chartZoom() { return activeChart.zoom; },
|
|
4577
|
+
// The panel keeps one chart component across tabs, so "tabs share a
|
|
4578
|
+
// window" is simply every tab carrying the same window: a zoom on one is
|
|
4579
|
+
// written to all, and switching tabs hands the component the same value.
|
|
4580
|
+
set chartZoom(v) {
|
|
4581
|
+
if (chartCfg?.syncTabs) for (const c of charts) c.zoom = v as never;
|
|
4582
|
+
else activeChart.zoom = v as never;
|
|
4583
|
+
},
|
|
4011
4584
|
get chartValueFormat() { return effectiveChartValueFormat ?? "number"; },
|
|
4012
4585
|
set chartValueFormat(v) { activeChart.valueFormat = v as never; },
|
|
4013
4586
|
get chartDimensionIsDate() { return chartDimensionIsDate; },
|
|
@@ -4028,8 +4601,21 @@ export function createSvGridController<
|
|
|
4028
4601
|
get activeChartIndex() { return Math.min(activeChartIndex, charts.length - 1); },
|
|
4029
4602
|
set activeChartIndex(v) { activeChartIndex = Math.max(0, Math.min(charts.length - 1, v as number)); },
|
|
4030
4603
|
addChart() {
|
|
4031
|
-
|
|
4604
|
+
const next = makeChart(`Chart ${charts.length + 1}`);
|
|
4605
|
+
if (chartCfg?.syncTabs) next.zoom = activeChart.zoom;
|
|
4606
|
+
charts = [...charts, next];
|
|
4032
4607
|
activeChartIndex = charts.length - 1;
|
|
4608
|
+
chartCfg?.onChartCreated?.({ index: activeChartIndex, title: next.title, type: next.type, spec: null });
|
|
4609
|
+
},
|
|
4610
|
+
/** Unlink the active chart from the grid: it keeps the spec it has now. */
|
|
4611
|
+
freezeChart() {
|
|
4612
|
+
const spec = chartSpec;
|
|
4613
|
+
if (!spec) return;
|
|
4614
|
+
activeChart.frozen = { spec: JSON.parse(JSON.stringify(spec)) as ChartSpec, at: new Date().toISOString() };
|
|
4615
|
+
},
|
|
4616
|
+
/** Link the active chart back to the grid's rows. */
|
|
4617
|
+
unfreezeChart() {
|
|
4618
|
+
activeChart.frozen = null;
|
|
4033
4619
|
},
|
|
4034
4620
|
removeChart(index?: number) {
|
|
4035
4621
|
if (charts.length <= 1) return;
|
|
@@ -4042,48 +4628,39 @@ export function createSvGridController<
|
|
|
4042
4628
|
if (c) c.title = title;
|
|
4043
4629
|
},
|
|
4044
4630
|
getChartsState() {
|
|
4045
|
-
return charts.map(
|
|
4046
|
-
title: c.title,
|
|
4047
|
-
type: c.type,
|
|
4048
|
-
dimension: c.dimensionId,
|
|
4049
|
-
series: c.seriesId ?? null,
|
|
4050
|
-
measure: c.measureId,
|
|
4051
|
-
measure2: c.measure2Id,
|
|
4052
|
-
orientation: c.orientation,
|
|
4053
|
-
stacked100: c.stacked100,
|
|
4054
|
-
donut: c.donut,
|
|
4055
|
-
palette: c.palette,
|
|
4056
|
-
reduce: c.reduce,
|
|
4057
|
-
stacked: c.stacked,
|
|
4058
|
-
dataLabels: c.dataLabels,
|
|
4059
|
-
logScale: c.logScale,
|
|
4060
|
-
timeAxis: c.timeAxis,
|
|
4061
|
-
valueFormat: c.valueFormat,
|
|
4062
|
-
}));
|
|
4631
|
+
return charts.map(tabToState);
|
|
4063
4632
|
},
|
|
4064
4633
|
applyChartsState(list: ReadonlyArray<Record<string, unknown>>, active?: number) {
|
|
4065
|
-
const restored = list.map(
|
|
4066
|
-
id: `chart-${chartSeq++}`,
|
|
4067
|
-
title: typeof c.title === "string" ? c.title : `Chart ${i + 1}`,
|
|
4068
|
-
type: (c.type as ChartType) ?? "bar",
|
|
4069
|
-
reduce: (c.reduce as "sum" | "avg" | "count") ?? "sum",
|
|
4070
|
-
dimensionId: (c.dimension as string | null) ?? null,
|
|
4071
|
-
measureId: (c.measure as string | null) ?? null,
|
|
4072
|
-
seriesId: c.series as string | null | undefined,
|
|
4073
|
-
measure2Id: (c.measure2 as string | null) ?? null,
|
|
4074
|
-
orientation: (c.orientation as "vertical" | "horizontal" | null) ?? null,
|
|
4075
|
-
stacked100: (c.stacked100 as boolean | null) ?? null,
|
|
4076
|
-
donut: (c.donut as boolean | null) ?? null,
|
|
4077
|
-
palette: (c.palette as string[] | null) ?? null,
|
|
4078
|
-
stacked: (c.stacked as boolean | null) ?? null,
|
|
4079
|
-
dataLabels: (c.dataLabels as boolean | null) ?? null,
|
|
4080
|
-
logScale: (c.logScale as boolean | null) ?? null,
|
|
4081
|
-
timeAxis: (c.timeAxis as boolean | null) ?? null,
|
|
4082
|
-
valueFormat: (c.valueFormat as ChartValueFormat | null) ?? null,
|
|
4083
|
-
}));
|
|
4634
|
+
const restored = list.map(tabFromState);
|
|
4084
4635
|
charts = restored.length ? restored : [makeChart("Chart 1")];
|
|
4085
4636
|
activeChartIndex = Math.max(0, Math.min(charts.length - 1, active ?? 0));
|
|
4086
4637
|
},
|
|
4638
|
+
// ---- Saved charts ----
|
|
4639
|
+
get savedCharts() { return savedCharts; },
|
|
4640
|
+
/** Keep the active tab's configuration under a name (replacing a same-named one). */
|
|
4641
|
+
saveChart(name: string) {
|
|
4642
|
+
const entry: SavedChartEntry = { name, savedAt: new Date().toISOString(), tab: tabToState(activeChart) };
|
|
4643
|
+
savedCharts = [...savedCharts.filter((s) => s.name !== name), entry];
|
|
4644
|
+
},
|
|
4645
|
+
/** Apply a saved chart to the active tab; the tab keeps its id and title. */
|
|
4646
|
+
applySavedChart(name: string): boolean {
|
|
4647
|
+
const entry = savedCharts.find((s) => s.name === name);
|
|
4648
|
+
if (!entry) return false;
|
|
4649
|
+
const { id: _id, title: _title, ...rest } = tabFromState(entry.tab, activeChartIndex);
|
|
4650
|
+
Object.assign(activeChart, rest);
|
|
4651
|
+
return true;
|
|
4652
|
+
},
|
|
4653
|
+
removeSavedChart(name: string) {
|
|
4654
|
+
savedCharts = savedCharts.filter((s) => s.name !== name);
|
|
4655
|
+
},
|
|
4656
|
+
getSavedCharts() {
|
|
4657
|
+
return savedCharts.map((s) => ({ ...s, tab: { ...s.tab } }));
|
|
4658
|
+
},
|
|
4659
|
+
applySavedCharts(list: ReadonlyArray<Record<string, unknown>>) {
|
|
4660
|
+
savedCharts = list
|
|
4661
|
+
.filter((s) => typeof s?.name === "string" && s.tab && typeof s.tab === "object")
|
|
4662
|
+
.map((s) => ({ name: s.name as string, savedAt: String(s.savedAt ?? ""), tab: s.tab as Record<string, unknown> }));
|
|
4663
|
+
},
|
|
4087
4664
|
get chartCrossFilterable() { return chartCrossFilterable; },
|
|
4088
4665
|
get chartOrientation() { return effectiveChartOrientation; },
|
|
4089
4666
|
set chartOrientation(v) { activeChart.orientation = v as never; },
|
|
@@ -4098,19 +4675,40 @@ export function createSvGridController<
|
|
|
4098
4675
|
get effectiveChartMeasure2Id() { return effectiveChartMeasure2Id; },
|
|
4099
4676
|
get chartAiHandler() { return chartAiHandler; },
|
|
4100
4677
|
set chartAiHandler(v) { chartAiHandler = v as never; },
|
|
4678
|
+
get chartExplainHandler() { return chartExplainHandler; },
|
|
4679
|
+
set chartExplainHandler(v) { chartExplainHandler = v as never; },
|
|
4101
4680
|
applyChartConfig(config: {
|
|
4102
4681
|
open?: boolean;
|
|
4103
4682
|
type?: ChartType;
|
|
4104
4683
|
dimension?: string | null;
|
|
4105
4684
|
series?: string | null;
|
|
4106
4685
|
measure?: string | null;
|
|
4107
|
-
reduce?:
|
|
4686
|
+
reduce?: ChartReducer;
|
|
4108
4687
|
stacked?: boolean;
|
|
4109
4688
|
dataLabels?: boolean;
|
|
4110
4689
|
logScale?: boolean;
|
|
4111
4690
|
timeAxis?: boolean;
|
|
4112
4691
|
valueFormat?: ChartValueFormat;
|
|
4692
|
+
bucket?: ChartTimeBucket | null;
|
|
4693
|
+
bins?: number | null;
|
|
4694
|
+
funnelShape?: "trapezoid" | "pyramid" | "cone" | null;
|
|
4695
|
+
candleStyle?: "classic" | "hollow" | "heikin-ashi" | null;
|
|
4696
|
+
/** Second measure: scatter Y, range high end, bullet target. */
|
|
4697
|
+
measure2?: string | null;
|
|
4698
|
+
/** Candlestick / OHLC price columns (ids or fields). */
|
|
4699
|
+
ohlc?: { open?: string | null; high?: string | null; low?: string | null; close?: string | null; volume?: string | null } | null;
|
|
4700
|
+
/** Indicators: panes (volume, rsi, macd, stochastic, atr, obv) and
|
|
4701
|
+
* overlays (sma, ema, bb, vwap). */
|
|
4702
|
+
indicators?: ChartPanelIndicator[];
|
|
4703
|
+
/** The builder's format (titles, axes, legend, series style); `null` resets it. */
|
|
4704
|
+
format?: ChartFormatState | null;
|
|
4705
|
+
/** Unlink the chart from the grid (true) or link it back (false). */
|
|
4706
|
+
frozen?: boolean;
|
|
4707
|
+
/** A saved chart's name: applied to the active tab first, then the
|
|
4708
|
+
* other keys on top. */
|
|
4709
|
+
saved?: string;
|
|
4113
4710
|
}) {
|
|
4711
|
+
if (config.saved) this.applySavedChart(config.saved);
|
|
4114
4712
|
const cols = [...chartableColumns.dims, ...chartableColumns.measures];
|
|
4115
4713
|
const resolve = (v: string | null | undefined): string | null => {
|
|
4116
4714
|
if (v == null) return null;
|
|
@@ -4130,6 +4728,24 @@ export function createSvGridController<
|
|
|
4130
4728
|
if (typeof config.logScale === "boolean") activeChart.logScale = config.logScale;
|
|
4131
4729
|
if (typeof config.timeAxis === "boolean") activeChart.timeAxis = config.timeAxis;
|
|
4132
4730
|
if (config.valueFormat) activeChart.valueFormat = config.valueFormat;
|
|
4731
|
+
if ("bucket" in config) activeChart.bucket = config.bucket ?? null;
|
|
4732
|
+
if ("bins" in config) activeChart.bins = config.bins ?? null;
|
|
4733
|
+
if ("funnelShape" in config) activeChart.funnelShape = config.funnelShape ?? null;
|
|
4734
|
+
if ("candleStyle" in config) activeChart.candleStyle = config.candleStyle ?? null;
|
|
4735
|
+
if ("measure2" in config) activeChart.measure2Id = resolve(config.measure2);
|
|
4736
|
+
if ("ohlc" in config) {
|
|
4737
|
+
const o = config.ohlc;
|
|
4738
|
+
activeChart.ohlc = o
|
|
4739
|
+
? { open: resolve(o.open), high: resolve(o.high), low: resolve(o.low), close: resolve(o.close), volume: resolve(o.volume) }
|
|
4740
|
+
: null;
|
|
4741
|
+
}
|
|
4742
|
+
if (config.indicators) activeChart.indicators = [...config.indicators];
|
|
4743
|
+
if ("format" in config) activeChart.format = config.format ?? null;
|
|
4744
|
+
if (config.frozen === false) activeChart.frozen = null;
|
|
4745
|
+
else if (config.frozen === true && !activeChart.frozen) {
|
|
4746
|
+
const spec = chartSpec;
|
|
4747
|
+
if (spec) activeChart.frozen = { spec: JSON.parse(JSON.stringify(spec)) as ChartSpec, at: new Date().toISOString() };
|
|
4748
|
+
}
|
|
4133
4749
|
},
|
|
4134
4750
|
get toggleGroupInPanel() { return toggleGroupInPanel; },
|
|
4135
4751
|
get lastSortingSerialized() { return lastSortingSerialized; },
|
|
@@ -4152,8 +4768,17 @@ export function createSvGridController<
|
|
|
4152
4768
|
},
|
|
4153
4769
|
/** True when the `rowResize` prop is on and nothing overrides it. */
|
|
4154
4770
|
get rowResizeOn() { return rowResizeOn; },
|
|
4771
|
+
get frozenRowCount() { return frozenRowCount; },
|
|
4772
|
+
get frozenRowList() { return frozenRowList; },
|
|
4773
|
+
get frozenBandHeight() { return frozenBandHeight; },
|
|
4774
|
+
get frozenRowTop() { return frozenRowTop; },
|
|
4155
4775
|
/** Record the height the user dragged a row to. */
|
|
4156
4776
|
get setRowResizeHeight() { return setRowResizeHeight; },
|
|
4777
|
+
get isRowCollapsed() { return isRowCollapsed; },
|
|
4778
|
+
get setRowCollapsed() { return setRowCollapsed; },
|
|
4779
|
+
get resizeRowByUser() { return resizeRowByUser; },
|
|
4780
|
+
get resizeColumnByUser() { return resizeColumnByUser; },
|
|
4781
|
+
get autosizeColumnByUser() { return autosizeColumnByUser; },
|
|
4157
4782
|
/** A row's dragged height, or undefined when it has not been resized. */
|
|
4158
4783
|
get rowResizeHeightPx() {
|
|
4159
4784
|
rowResizeVersion;
|
|
@@ -4213,9 +4838,18 @@ export function createSvGridController<
|
|
|
4213
4838
|
get isRowSelected() { return isRowSelected; },
|
|
4214
4839
|
get toggleRowSelectionById() { return toggleRowSelectionById; },
|
|
4215
4840
|
get headerSelectionState() { return headerSelectionState; },
|
|
4841
|
+
get visibleRowRange() { return visibleRowRange; },
|
|
4842
|
+
placeholderStateOf,
|
|
4843
|
+
/** Retry a failed placeholder row. No-op without `onRetryRow`. */
|
|
4844
|
+
retryRow(row: Row<TData>) {
|
|
4845
|
+
props.onRetryRow?.(row.original, row.index);
|
|
4846
|
+
},
|
|
4216
4847
|
get toggleSelectAllRows() { return toggleSelectAllRows; },
|
|
4848
|
+
get setSelectAllRows() { return setSelectAllRows; },
|
|
4217
4849
|
get userHasActivatedCell() { return userHasActivatedCell; },
|
|
4218
4850
|
set userHasActivatedCell(v) { userHasActivatedCell = v as never; },
|
|
4851
|
+
get tabRunOrigin() { return tabRunOrigin; },
|
|
4852
|
+
set tabRunOrigin(v) { tabRunOrigin = v; },
|
|
4219
4853
|
get setActiveCell() { return setActiveCell; },
|
|
4220
4854
|
get scrollActiveCellIntoView() { return scrollActiveCellIntoView; },
|
|
4221
4855
|
get getColumnBaseWidth() { return getColumnBaseWidth; },
|
|
@@ -4225,6 +4859,8 @@ export function createSvGridController<
|
|
|
4225
4859
|
get extendSelection() { return extendSelection; },
|
|
4226
4860
|
get isCellInSelectedRange() { return isCellInSelectedRange; },
|
|
4227
4861
|
get getCellRangeEdges() { return getCellRangeEdges; },
|
|
4862
|
+
get activeRangeRect() { return activeRangeRect; },
|
|
4863
|
+
get cellSelectionState() { return cellSelectionState; },
|
|
4228
4864
|
get getSelectionRects() { return getSelectionRects; },
|
|
4229
4865
|
get fillHandleCell() { return fillHandleCell; },
|
|
4230
4866
|
get isInFillPreview() { return isInFillPreview; },
|
|
@@ -4247,6 +4883,7 @@ export function createSvGridController<
|
|
|
4247
4883
|
get startFillDrag() { return startFillDrag; },
|
|
4248
4884
|
get onFillPointerMove() { return onFillPointerMove; },
|
|
4249
4885
|
get onFillPointerUp() { return onFillPointerUp; },
|
|
4886
|
+
get fillDownToNeighbour() { return fillDownToNeighbour; },
|
|
4250
4887
|
get isOnMoveGrabStrip() { return isOnMoveGrabStrip; },
|
|
4251
4888
|
get startMoveDrag() { return startMoveDrag; },
|
|
4252
4889
|
get onMovePointerMove() { return onMovePointerMove; },
|
|
@@ -4266,6 +4903,7 @@ export function createSvGridController<
|
|
|
4266
4903
|
get cutSelectionToClipboard() { return cutSelectionToClipboard; },
|
|
4267
4904
|
get pasteFromClipboard() { return pasteFromClipboard; },
|
|
4268
4905
|
get onGridPaste() { return onGridPaste; },
|
|
4906
|
+
get armPasteFallback() { return armPasteFallback; },
|
|
4269
4907
|
get clearSelectedCells() { return clearSelectedCells; },
|
|
4270
4908
|
get onCellDoubleClick() { return onCellDoubleClick; },
|
|
4271
4909
|
get startEditingWithChar() { return startEditingWithChar; },
|
|
@@ -4342,12 +4980,12 @@ export function createSvGridController<
|
|
|
4342
4980
|
get clearColumnFilter() { return clearColumnFilter; },
|
|
4343
4981
|
get onWindowKeydown() { return onWindowKeydown; },
|
|
4344
4982
|
get columnDefMatchesId() { return columnDefMatchesId; },
|
|
4345
|
-
get buildApi() { return
|
|
4983
|
+
get buildApi() { return api; },
|
|
4346
4984
|
get apiNotified() { return apiNotified; },
|
|
4347
4985
|
set apiNotified(v) { apiNotified = v as never; },
|
|
4348
4986
|
};
|
|
4349
4987
|
const { resolveEffectiveFeatures } = createFeatures<TFeatures, TData>(ctx);
|
|
4350
|
-
const { showTooltipFor, hideTooltip, flushScheduledScrollSync, scheduleScrollSync, onBodyScroll } = createScrollSync<TFeatures, TData>(ctx);
|
|
4988
|
+
const { showTooltipFor, hideTooltip, flushScheduledScrollSync, scheduleScrollSync, syncScrollNow, onBodyScroll } = createScrollSync<TFeatures, TData>(ctx);
|
|
4351
4989
|
const { onGridKeyDown, onWindowKeydown, onHeaderSortClick } = createKeyboard<TFeatures, TData>(ctx);
|
|
4352
4990
|
const { computeSummaries, hasRenderedColumn } = createSummaries<TFeatures, TData>(ctx);
|
|
4353
4991
|
// Hand the aggregator to `eagerSummaries` (declared above `ctx`, which this
|
|
@@ -4356,13 +4994,13 @@ export function createSvGridController<
|
|
|
4356
4994
|
|
|
4357
4995
|
const { updateFilterRow, updateFilterOperator, updateFilterMenuValue, updateFilterMenuValueTo, addFilterToken, removeFilterToken, toggleFilterToken, toggleCheckboxWithKeyboard, isColumnFiltered, closeMenus, openInSuggest, closeInSuggest, openChooseColumns, openColumnMenu, openFilterMenu, openOperatorMenu, sortColumnFromMenu, clearColumnSort, groupByColumnFromMenu, clearGroupingFromMenu, isFacetChecked, toggleFacetValue, setFacetSelection, setFilterTokens, isAllFacetsChecked, toggleAllFacets, clearColumnFilter, changePage, goToPage, setPageSize, openContextMenu, closeContextMenu, contextMenuItems, saveComment, removeComment, closeCommentEditor } = createMenus<TFeatures, TData>(ctx);
|
|
4358
4996
|
const { cellConditionalFormat, computeRowClass, computeCellClass, computeCellTooltip, computeCellValidity, computeCellNote, getColumnEditorOptions, areEditorOptionsLoading, formatListCellValue, formatCellValue, formatPinnedValue, computePinnedCellClass } = createCellRender<TFeatures, TData>(ctx);
|
|
4359
|
-
const { isCellEditable, isCellEditableAt, getRowColumnValue, getCellDisplayValue, startEditingWithChar, startEditing, stopEditing, startFullRowEdit, setFullRowDraft, commitFullRowEdit, cancelFullRowEdit, saveEditingCell, applyHistoryStep, updateEditingCellValue, onEditorKeyDown, commitAndMoveByTab, focusOnMount, onCellDoubleClick, pasteFromClipboard, onGridPaste } = createEditing<TFeatures, TData>(ctx);
|
|
4360
|
-
const { isRowSelected, toggleRowSelectionById, toggleSelectAllRows, setActiveCell, scrollActiveCellIntoView, setSelection, extendSelection, isCellInSelectedRange, getCellRangeEdges, getSelectionRects, isInFillPreview, dragPreviewRect, fillMarqueeEdges, findColumnById, onCellPointerDown, onCellPointerEnter, endDragSelection, onWindowPointerMove, onCellClick, emitCellDoubleClick } = createSelection<TFeatures, TData>(ctx);
|
|
4997
|
+
const { isCellEditable, isCellEditableAt, getRowColumnValue, getCellDisplayValue, startEditingWithChar, startEditing, stopEditing, startFullRowEdit, setFullRowDraft, commitFullRowEdit, cancelFullRowEdit, saveEditingCell, applyHistoryStep, updateEditingCellValue, onEditorKeyDown, commitAndMoveByTab, focusOnMount, onCellDoubleClick, pasteFromClipboard, onGridPaste, armPasteFallback } = createEditing<TFeatures, TData>(ctx);
|
|
4998
|
+
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<TFeatures, TData>(ctx);
|
|
4361
4999
|
const { cellPinStyle, isColumnPinned, getCurrentColumnOrder, emitColumnOrder, setColumnOrderInternal, applyColumnDrop, onColumnHeaderDragStart, onColumnHeaderDragOver, onColumnHeaderDragLeave, onColumnHeaderDrop, onColumnHeaderDragEnd, pinColumnLeft, pinColumnRight, unpinColumn, toggleColumnVisibleInPanel, moveColumnInPanel, toggleGroupInPanel, getColumnBaseWidth, getColumnWidth, measureText, autosizeColumn, autosizeAllColumns, resetColumns } = createColumns<TFeatures, TData>(ctx);
|
|
4362
5000
|
const { onRowDragStart, onRowDragOver, onRowDragLeave, onRowDrop, onRowsContainerDragOver, onRowsContainerDrop, onRowDragEnd, onRowPointerDown, destroyRowDrag } = createRowDrag<TFeatures, TData>(ctx);
|
|
4363
5001
|
const { register: registerAlignedGrid, broadcastScroll: broadcastAlignedScroll, broadcastWidths: broadcastAlignedWidths } = createAlignedGrids<TFeatures, TData>(ctx);
|
|
4364
5002
|
const { buildApi } = createGridApi<TFeatures, TData>(ctx);
|
|
4365
|
-
const { readCellRaw, writeCellRaw, applyFillPattern, clearSelectedCellValues, startFillDrag, onFillPointerMove, onFillPointerUp, isOnMoveGrabStrip, startMoveDrag, onMovePointerMove, onMovePointerUp, applyMoveRange, moveDestRect, trackEdgeScroll, stopEdgeScroll, toggleBooleanCell, copySelectionToClipboard, clearSelectedCells, cutSelectionToClipboard } = createClipboard(ctx);
|
|
5003
|
+
const { readCellRaw, writeCellRaw, applyFillPattern, clearSelectedCellValues, startFillDrag, onFillPointerMove, onFillPointerUp, fillDownToNeighbour, isOnMoveGrabStrip, startMoveDrag, onMovePointerMove, onMovePointerUp, applyMoveRange, moveDestRect, trackEdgeScroll, stopEdgeScroll, toggleBooleanCell, copySelectionToClipboard, clearSelectedCells, cutSelectionToClipboard } = createClipboard(ctx);
|
|
4366
5004
|
|
|
4367
5005
|
// Dev-time configuration checks. Silent misconfiguration was the grid's
|
|
4368
5006
|
// biggest usability gap - a misspelled `field` rendered a column of blank
|
|
@@ -4397,6 +5035,8 @@ export function createSvGridController<
|
|
|
4397
5035
|
pageSize: props.pageSize,
|
|
4398
5036
|
groupBy: props.groupBy,
|
|
4399
5037
|
treeData: props.treeData,
|
|
5038
|
+
gantt: props.gantt,
|
|
5039
|
+
serverGroup: props.serverGroup,
|
|
4400
5040
|
initialColumnPinning: props.initialColumnPinning,
|
|
4401
5041
|
columnVirtualization: props.columnVirtualization,
|
|
4402
5042
|
externalPagination: props.externalPagination,
|