@svgrid/grid 3.0.2 → 3.0.4
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 +2 -1
- 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 +110 -39
- package/dist/SvGrid.controller.svelte.js +655 -200
- package/dist/SvGrid.css +105 -2
- package/dist/SvGrid.svelte +386 -514
- package/dist/SvGrid.types.d.ts +210 -9
- 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 +108 -22
- package/dist/cdn/GridMenus-7kbpnnBW.js +635 -0
- package/dist/cdn/GridMenus-BL7ZgQvU.js +636 -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/{src-DNbz94Ct.js → src-BKhZ6eXd.js} +10227 -9787
- package/dist/cdn/{src-BJ2opd0j.js → src-V1uu8iE9.js} +6568 -6128
- package/dist/cdn/svgrid.js +26 -15
- package/dist/cdn/svgrid.svelte-external.js +26 -15
- 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/createPopoverSelect.svelte.d.ts +2 -2
- package/dist/editing.d.ts +2 -8
- package/dist/editing.js +262 -33
- package/dist/fill-patterns.d.ts +0 -5
- package/dist/fill-patterns.js +56 -2
- package/dist/grid-messages.d.ts +10 -3
- package/dist/grid-messages.js +5 -0
- package/dist/history.d.ts +83 -0
- package/dist/history.js +133 -0
- package/dist/index.d.ts +24 -6
- package/dist/index.js +20 -2
- 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-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 +16 -0
- package/dist/selection.js +108 -16
- 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/virtualization/virtualizer.js +38 -17
- package/package.json +6 -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 +629 -172
- package/src/SvGrid.css +105 -2
- package/src/SvGrid.svelte +386 -514
- package/src/SvGrid.types.ts +188 -8
- 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 +107 -18
- 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.ts +7 -0
- package/src/editing.test.ts +281 -3
- package/src/editing.ts +256 -38
- package/src/fill-patterns.test.ts +35 -0
- package/src/fill-patterns.ts +56 -2
- package/src/grid-messages.ts +16 -3
- package/src/history.test.ts +196 -0
- package/src/history.ts +162 -0
- package/src/icon-seam.test.ts +3 -2
- package/src/index.ts +150 -2
- 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-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 +30 -2
- package/src/selection.ts +111 -19
- package/src/shortcut-registry.test.ts +97 -0
- package/src/shortcut-registry.ts +181 -0
- package/src/svgrid-wrapper.types.ts +184 -13
- package/src/svgrid.behavior.test.ts +135 -0
- package/src/svgrid.charting.test.ts +513 -3
- package/src/svgrid.context-menu.test.ts +31 -0
- package/src/svgrid.new-features.wrapper.test.ts +10 -3
- package/src/virtualization/virtualizer.ts +39 -17
- package/dist/cdn/GridMenus-DyFA-QxA.js +0 -602
- package/dist/cdn/GridMenus-M2XP9t0_.js +0 -603
- package/dist/cdn/SvDateTimePicker-CCbDZNZB.js +0 -816
- package/dist/cdn/SvGridChart-DAb9RbRs.js +0 -1480
- package/dist/cdn/SvGridChart-Yl-IXZFO.js +0 -1481
- package/dist/cdn/SvGridChartPanel-DayQpDsR.js +0 -834
- package/dist/cdn/SvGridChartPanel-aqf8n-vJ.js +0 -810
- package/dist/cdn/SvGridChartView-CxfakxR0.js +0 -56
- package/dist/cdn/SvGridChartView-Dm_YEx9L.js +0 -55
- package/dist/cdn/chart-CKJRCPOk.js +0 -1652
|
@@ -6,6 +6,8 @@ import "./sv-grid-scrollbar.js";
|
|
|
6
6
|
import { computeColumnStat, formatsNeedingStats, formatNeedsStats, } from "./conditional-formatting.js";
|
|
7
7
|
import { rawToNumber, } from "./SvGrid.helpers.js";
|
|
8
8
|
import { createFeatures } from "./features.js";
|
|
9
|
+
import { pushHistory } from "./history.js";
|
|
10
|
+
import { buildMergeIndex } from "./merges.js";
|
|
9
11
|
import { createScrollSync, } from "./scroll-sync.js";
|
|
10
12
|
import { createKeyboard, } from "./keyboard-handlers.js";
|
|
11
13
|
import { createSummaries, } from "./summaries.js";
|
|
@@ -215,7 +217,12 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
215
217
|
let pendingScrollTop = 0;
|
|
216
218
|
let pendingScrollLeft = 0;
|
|
217
219
|
let scrollSyncRaf = null;
|
|
218
|
-
|
|
220
|
+
// `$state.raw`: every writer replaces the whole object, and every rendered
|
|
221
|
+
// cell's selection derived reads it. Under a deep proxy each of those ~300
|
|
222
|
+
// deriveds tracked the nested anchor/focus row/col signals separately (six
|
|
223
|
+
// dependencies per cell to mark and re-check on every arrow key) where one
|
|
224
|
+
// will do.
|
|
225
|
+
let selectionRange = $state.raw({ anchor: null, focus: null });
|
|
219
226
|
// Extra committed ranges for multi-range (Ctrl+drag) selection. The
|
|
220
227
|
// `selectionRange` above is always the ACTIVE range being manipulated; these
|
|
221
228
|
// are the finished ones. Full selection = these + the active range.
|
|
@@ -243,7 +250,12 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
243
250
|
// Full-row editing: the row currently in whole-row edit + its per-column
|
|
244
251
|
// draft (keyed by column id). Null when not in full-row mode.
|
|
245
252
|
let fullRowEdit = $state(null);
|
|
246
|
-
|
|
253
|
+
// `$state.raw`, not `$state`: every writer replaces the whole object, and
|
|
254
|
+
// the render path asks `key in editedCellValues` once per rendered cell. On
|
|
255
|
+
// a deep proxy that `in` check, run inside an effect, creates and keeps a
|
|
256
|
+
// per-key signal for every cell that ever scrolls into view - a leak that
|
|
257
|
+
// grows with rows x columns and a Map lookup per cell per render.
|
|
258
|
+
let editedCellValues = $state.raw({});
|
|
247
259
|
const UNDO_LIMIT = 200;
|
|
248
260
|
let history = $state([]);
|
|
249
261
|
/** Index in `history` of the LAST applied step. -1 means "nothing applied".
|
|
@@ -253,6 +265,11 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
253
265
|
/** Bumps on every undo / redo / record so $derived consumers can
|
|
254
266
|
* observe via the api without subscribing to history directly. */
|
|
255
267
|
let historyVersion = $state(0);
|
|
268
|
+
/** Set while a `runHistoryGroup` call is on the stack, so every step pushed
|
|
269
|
+
* inside one action shares a group id and undoes together. Deliberately not
|
|
270
|
+
* $state: it is written and read synchronously inside a single call and
|
|
271
|
+
* nothing renders from it. */
|
|
272
|
+
let historyGroupId = undefined;
|
|
256
273
|
let tooltip = $state(null);
|
|
257
274
|
let tooltipTimer = null;
|
|
258
275
|
// ---- Find-in-grid ----------------------------------------------------
|
|
@@ -288,6 +305,26 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
288
305
|
let editorSelectAll = true;
|
|
289
306
|
/** Per-column width overrides set by the resize handles. */
|
|
290
307
|
let columnWidths = $state({});
|
|
308
|
+
/**
|
|
309
|
+
* Columns folded to nothing, the way a sheet hides a column: the column
|
|
310
|
+
* keeps its index, its cells and its width for later, and takes no room.
|
|
311
|
+
* Distinct from `hiddenColumns`, which takes a column out of the model
|
|
312
|
+
* and shifts every index after it; a spreadsheet's formulas and formats
|
|
313
|
+
* are keyed by index and could not follow that.
|
|
314
|
+
*/
|
|
315
|
+
let collapsedColumns = $state({});
|
|
316
|
+
function setColumnCollapsed(columnId, collapsed) {
|
|
317
|
+
if (!!collapsedColumns[columnId] === collapsed)
|
|
318
|
+
return;
|
|
319
|
+
if (collapsed) {
|
|
320
|
+
collapsedColumns = { ...collapsedColumns, [columnId]: true };
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
const next = { ...collapsedColumns };
|
|
324
|
+
delete next[columnId];
|
|
325
|
+
collapsedColumns = next;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
291
328
|
const MIN_COLUMN_WIDTH = 40;
|
|
292
329
|
/** Columns pinned to the left or right edge of the grid (sticky positioning).
|
|
293
330
|
* Seeded from `props.initialColumnPinning` so demos / tests can show the
|
|
@@ -373,26 +410,53 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
373
410
|
const hb = column.columnDef?.hideBelow;
|
|
374
411
|
return hb != null && viewportWidth > 0 && viewportWidth < hb;
|
|
375
412
|
}
|
|
376
|
-
|
|
413
|
+
/**
|
|
414
|
+
* Live scroll geometry of the scroll container (feeds the custom
|
|
415
|
+
* scrollbars and `hasVerticalOverflow`).
|
|
416
|
+
*
|
|
417
|
+
* Measured from a plain `$effect`, NOT inside a `$derived`, on purpose.
|
|
418
|
+
* `scrollTop` / `scrollHeight` / `scrollWidth` reads force a synchronous
|
|
419
|
+
* layout whenever the tree is dirty. As a derived this was pulled by the
|
|
420
|
+
* shell's `class:sv-grid-has-vscroll` at the very start of every scroll
|
|
421
|
+
* flush - before the recycled rows had been rewritten - so it forced a
|
|
422
|
+
* full table layout that the row writes then invalidated again, and the
|
|
423
|
+
* browser laid the table out a second time before painting. Two table
|
|
424
|
+
* layouts per arrow key on a large grid. An effect runs after the flush
|
|
425
|
+
* has written the DOM, so the reads below land on a tree the browser has
|
|
426
|
+
* to lay out anyway, and the pre-paint pass is incremental.
|
|
427
|
+
*
|
|
428
|
+
* The version reads keep the same triggers the derived had: a scroll
|
|
429
|
+
* tick, a shell resize, and either virtualizer's window/size change (data
|
|
430
|
+
* load, row / column count change) - without those the scrollbar kept a
|
|
431
|
+
* stale `content-size` ≈ 0, its hidden-check tripped, it set
|
|
432
|
+
* `pointer-events: none`, and the user could not drag it.
|
|
433
|
+
*/
|
|
434
|
+
let scrollMetrics = $state.raw({
|
|
435
|
+
scrollTop: 0,
|
|
436
|
+
scrollLeft: 0,
|
|
437
|
+
scrollHeight: 0,
|
|
438
|
+
scrollWidth: 0,
|
|
439
|
+
});
|
|
440
|
+
$effect(function scrollMetrics_e() {
|
|
377
441
|
scrollVersion;
|
|
378
442
|
viewportVersion;
|
|
379
|
-
// Track the virtualizers' versions too so when data loads or row /
|
|
380
|
-
// column counts change, scrollMetrics re-reads the DOM's grown
|
|
381
|
-
// scrollHeight / scrollWidth. Without these deps the scrollbar
|
|
382
|
-
// receives a stale `content-size` ≈ 0, its hidden-check trips, it
|
|
383
|
-
// sets `pointer-events: none`, and the user can't drag it. The
|
|
384
|
-
// identifiers below are declared further down - derived callbacks
|
|
385
|
-
// run lazily, so by the time this fires they're in scope.
|
|
386
443
|
virtualizer.version;
|
|
387
444
|
columnVirtualizerVersion;
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
445
|
+
const el = scrollContainer;
|
|
446
|
+
if (!el)
|
|
447
|
+
return;
|
|
448
|
+
const scrollTop = el.scrollTop;
|
|
449
|
+
const scrollLeft = el.scrollLeft;
|
|
450
|
+
const scrollHeight = el.scrollHeight;
|
|
451
|
+
const scrollWidth = el.scrollWidth;
|
|
452
|
+
const prev = untrack(() => scrollMetrics);
|
|
453
|
+
if (prev.scrollTop === scrollTop &&
|
|
454
|
+
prev.scrollLeft === scrollLeft &&
|
|
455
|
+
prev.scrollHeight === scrollHeight &&
|
|
456
|
+
prev.scrollWidth === scrollWidth) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
scrollMetrics = { scrollTop, scrollLeft, scrollHeight, scrollWidth };
|
|
396
460
|
});
|
|
397
461
|
/** Vertical overflow from the virtualizer's authoritative total size,
|
|
398
462
|
* NOT from `scrollMetrics.scrollHeight` alone. Reading DOM dimensions
|
|
@@ -1355,8 +1419,14 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1355
1419
|
const paginationTotalRows = $derived(externalPaginationEnabled ? (props.rowCount ?? 0) : paginatedRowTotal);
|
|
1356
1420
|
const paginationPageIndex = $derived(externalPaginationEnabled ? (props.pageIndex ?? 0) : paginationState.pageIndex);
|
|
1357
1421
|
const paginationPageSize = $derived(externalPaginationEnabled ? (props.pageSize ?? 10) : paginationState.pageSize);
|
|
1422
|
+
// Tracks `dataStateVersion`, which the store subscription bumps when the
|
|
1423
|
+
// rowSelection slice (among others) changes - NOT the catch-all
|
|
1424
|
+
// `gridStateVersion`, which also bumps on every active-cell move. Every
|
|
1425
|
+
// rendered row and cell reads this (`sv-grid-row-selected`, `aria-selected`),
|
|
1426
|
+
// so on the catch-all each arrow key re-marked and re-checked ~400 effects
|
|
1427
|
+
// to find the same object.
|
|
1358
1428
|
const rowSelectionState = $derived.by(function rowSelectionState_d() {
|
|
1359
|
-
|
|
1429
|
+
dataStateVersion;
|
|
1360
1430
|
return grid.getState().rowSelection ?? {};
|
|
1361
1431
|
});
|
|
1362
1432
|
// ---- Selection bar ----------------------------------------------------
|
|
@@ -1652,7 +1722,9 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1652
1722
|
let chartMaximized = $state(false);
|
|
1653
1723
|
let chartFloatRect = $state.raw(null);
|
|
1654
1724
|
let chartAiHandler = $state(null);
|
|
1725
|
+
let chartExplainHandler = $state(null);
|
|
1655
1726
|
let chartSeq = 0;
|
|
1727
|
+
const isZoomWindow = (v) => !!v && typeof v === "object" && Number.isInteger(v.i0) && Number.isInteger(v.i1);
|
|
1656
1728
|
const makeChart = (title) => ({
|
|
1657
1729
|
id: `chart-${chartSeq++}`,
|
|
1658
1730
|
title,
|
|
@@ -1671,10 +1743,50 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1671
1743
|
logScale: null,
|
|
1672
1744
|
timeAxis: null,
|
|
1673
1745
|
valueFormat: null,
|
|
1746
|
+
bucket: null,
|
|
1747
|
+
bins: null,
|
|
1748
|
+
funnelShape: null,
|
|
1749
|
+
candleStyle: null,
|
|
1750
|
+
zoom: null,
|
|
1751
|
+
ohlc: null,
|
|
1752
|
+
indicators: [],
|
|
1753
|
+
format: null,
|
|
1754
|
+
frozen: null,
|
|
1674
1755
|
});
|
|
1756
|
+
// The saved view (`GridState.charts[i]`) names four fields differently
|
|
1757
|
+
// from the tab; everything else is the tab's own key.
|
|
1758
|
+
const TAB_ALIASES = { dimensionId: "dimension", measureId: "measure", seriesId: "series", measure2Id: "measure2" };
|
|
1759
|
+
/** A tab as the saved view carries it, without the runtime id. */
|
|
1760
|
+
const tabToState = (c) => {
|
|
1761
|
+
const out = {};
|
|
1762
|
+
for (const k in c)
|
|
1763
|
+
if (k !== "id")
|
|
1764
|
+
out[TAB_ALIASES[k] ?? k] = k === "seriesId" ? (c.seriesId ?? null) : c[k];
|
|
1765
|
+
return out;
|
|
1766
|
+
};
|
|
1767
|
+
/** A tab from a saved-view snapshot: a fresh tab with every known field
|
|
1768
|
+
* the snapshot carries, and the shape-checked ones dropped when malformed. */
|
|
1769
|
+
const tabFromState = (c, i) => {
|
|
1770
|
+
const t = makeChart(typeof c.title === "string" ? c.title : `Chart ${i + 1}`);
|
|
1771
|
+
for (const k in t) {
|
|
1772
|
+
const v = c[TAB_ALIASES[k] ?? k];
|
|
1773
|
+
if (k !== "id" && k !== "title" && v !== undefined)
|
|
1774
|
+
t[k] = v;
|
|
1775
|
+
}
|
|
1776
|
+
if (!isZoomWindow(t.zoom))
|
|
1777
|
+
t.zoom = null;
|
|
1778
|
+
if (!Array.isArray(t.indicators))
|
|
1779
|
+
t.indicators = [];
|
|
1780
|
+
if (typeof t.bins !== "number")
|
|
1781
|
+
t.bins = null;
|
|
1782
|
+
if (!t.frozen || typeof t.frozen !== "object" || !t.frozen.spec)
|
|
1783
|
+
t.frozen = null;
|
|
1784
|
+
return t;
|
|
1785
|
+
};
|
|
1675
1786
|
// svelte-ignore state_referenced_locally
|
|
1676
1787
|
let charts = $state([makeChart("Chart 1")]);
|
|
1677
1788
|
let activeChartIndex = $state(0);
|
|
1789
|
+
let savedCharts = $state([]);
|
|
1678
1790
|
const activeChart = $derived((charts[Math.min(activeChartIndex, charts.length - 1)] ?? charts[0]));
|
|
1679
1791
|
const chartType = $derived(activeChart.type);
|
|
1680
1792
|
const chartReduce = $derived(activeChart.reduce);
|
|
@@ -1685,6 +1797,10 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1685
1797
|
const chartDataLabels = $derived(activeChart.dataLabels);
|
|
1686
1798
|
const chartLogScale = $derived(activeChart.logScale);
|
|
1687
1799
|
const chartTimeAxis = $derived(activeChart.timeAxis);
|
|
1800
|
+
const chartBucket = $derived(activeChart.bucket);
|
|
1801
|
+
const chartBins = $derived(activeChart.bins);
|
|
1802
|
+
const chartFunnelShape = $derived(activeChart.funnelShape);
|
|
1803
|
+
const chartCandleStyle = $derived(activeChart.candleStyle);
|
|
1688
1804
|
const chartValueFormat = $derived(activeChart.valueFormat);
|
|
1689
1805
|
const asMeasureList = (m) => m == null ? [] : Array.isArray(m) ? m : [m];
|
|
1690
1806
|
const isNumericColumn = (col) => {
|
|
@@ -1802,9 +1918,14 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1802
1918
|
const label = col ? columnLabel(col) : "";
|
|
1803
1919
|
if (!label)
|
|
1804
1920
|
return undefined;
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
:
|
|
1921
|
+
const word = {
|
|
1922
|
+
sum: "Sum", avg: "Average", min: "Min", max: "Max", median: "Median",
|
|
1923
|
+
first: "First", last: "Last", countDistinct: "Distinct",
|
|
1924
|
+
};
|
|
1925
|
+
if (chartReduce === "count")
|
|
1926
|
+
return "Count";
|
|
1927
|
+
const w = word[chartReduce] ?? (chartReduce.startsWith("p") ? `P${chartReduce.slice(1)}` : "Sum");
|
|
1928
|
+
return `${w} of ${label}`;
|
|
1808
1929
|
});
|
|
1809
1930
|
/**
|
|
1810
1931
|
* Does this column hold dates? Declared type first, then a probe of the
|
|
@@ -1830,6 +1951,34 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1830
1951
|
return false;
|
|
1831
1952
|
}
|
|
1832
1953
|
const chartDimensionIsDate = $derived.by(() => columnIsDate(allColumns.find((c) => c.id === effectiveChartDimensionId)));
|
|
1954
|
+
// The chart derivation engine (rowsToChartSpec) is loaded lazily the first time
|
|
1955
|
+
// charting is enabled, keeping chart.ts (~11 KB gzip) out of the base <SvGrid>
|
|
1956
|
+
// bundle - it rides in the same lazy chunk as the chart panel. `chartSpec` stays
|
|
1957
|
+
// null until it resolves, a tick after the panel itself starts loading (which it
|
|
1958
|
+
// already does). The `getAggregate` server path uses the local bucketsToSpec and
|
|
1959
|
+
// does not need the engine.
|
|
1960
|
+
// The whole module, not just `rowsToChartSpec`: the panel can now reach
|
|
1961
|
+
// every chart type, and the ones that do not read a categories-by-series
|
|
1962
|
+
// grid (scatter, gauge, treemap, calendar, sankey) need their own builders
|
|
1963
|
+
// from the same lazy chunk. Same `import()`, same boundary.
|
|
1964
|
+
let chartEngine = $state(null);
|
|
1965
|
+
$effect(() => {
|
|
1966
|
+
if (chartingEnabled && !chartEngine)
|
|
1967
|
+
import("./chart.js").then((m) => (chartEngine = m));
|
|
1968
|
+
});
|
|
1969
|
+
const chartIsOhlc = $derived(chartType === "candlestick" || chartType === "ohlc");
|
|
1970
|
+
/** The price columns: what the reader picked, else the engine's guess
|
|
1971
|
+
* from the column names (nothing until the engine chunk has loaded). */
|
|
1972
|
+
const effectiveChartOhlc = $derived(chartEngine
|
|
1973
|
+
? chartEngine.guessOhlcColumns(chartableColumns.measures, activeChart.ohlc)
|
|
1974
|
+
: { open: null, high: null, low: null, close: null, volume: null, ...activeChart.ohlc });
|
|
1975
|
+
/** The date column a candlestick reads: the picked dimension when it is a
|
|
1976
|
+
* date, else the first date column. */
|
|
1977
|
+
const effectiveChartOhlcDateId = $derived(chartDimensionIsDate ? effectiveChartDimensionId : (chartableColumns.dates[0]?.id ?? null));
|
|
1978
|
+
const chartIndicators = $derived(activeChart.indicators);
|
|
1979
|
+
/** The panes under a price chart and the overlay on it, split by the engine. */
|
|
1980
|
+
const chartIndicatorSplit = $derived(chartIsOhlc && chartEngine ? chartEngine.splitPanelIndicators(chartIndicators) : { panes: [], overlay: null });
|
|
1981
|
+
const chartIndicatorPanes = $derived(chartIndicatorSplit.panes);
|
|
1833
1982
|
const chartRows = $derived.by(() => {
|
|
1834
1983
|
const rects = getSelectionRects();
|
|
1835
1984
|
const pushLeaf = (out, row) => {
|
|
@@ -1883,53 +2032,6 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1883
2032
|
chartServerBuckets = [];
|
|
1884
2033
|
});
|
|
1885
2034
|
});
|
|
1886
|
-
const bucketsToSpec = (buckets, type, hasSeries) => {
|
|
1887
|
-
const cats = [];
|
|
1888
|
-
const catIdx = new Map();
|
|
1889
|
-
const ensureCat = (c) => {
|
|
1890
|
-
let i = catIdx.get(c);
|
|
1891
|
-
if (i === undefined) {
|
|
1892
|
-
i = cats.length;
|
|
1893
|
-
catIdx.set(c, i);
|
|
1894
|
-
cats.push(c);
|
|
1895
|
-
}
|
|
1896
|
-
return i;
|
|
1897
|
-
};
|
|
1898
|
-
const seriesMap = new Map();
|
|
1899
|
-
for (const b of buckets) {
|
|
1900
|
-
const ci = ensureCat(b.category);
|
|
1901
|
-
const key = hasSeries ? (b.series ?? "") : "value";
|
|
1902
|
-
let arr = seriesMap.get(key);
|
|
1903
|
-
if (!arr) {
|
|
1904
|
-
arr = [];
|
|
1905
|
-
seriesMap.set(key, arr);
|
|
1906
|
-
}
|
|
1907
|
-
arr[ci] = (arr[ci] ?? 0) + b.value;
|
|
1908
|
-
}
|
|
1909
|
-
return {
|
|
1910
|
-
type,
|
|
1911
|
-
categories: cats,
|
|
1912
|
-
series: [...seriesMap.entries()].map(([label, values]) => ({
|
|
1913
|
-
label,
|
|
1914
|
-
values: cats.map((_, i) => values[i] ?? 0),
|
|
1915
|
-
})),
|
|
1916
|
-
};
|
|
1917
|
-
};
|
|
1918
|
-
// The chart derivation engine (rowsToChartSpec) is loaded lazily the first time
|
|
1919
|
-
// charting is enabled, keeping chart.ts (~11 KB gzip) out of the base <SvGrid>
|
|
1920
|
-
// bundle - it rides in the same lazy chunk as the chart panel. `chartSpec` stays
|
|
1921
|
-
// null until it resolves, a tick after the panel itself starts loading (which it
|
|
1922
|
-
// already does). The `getAggregate` server path uses the local bucketsToSpec and
|
|
1923
|
-
// does not need the engine.
|
|
1924
|
-
// The whole module, not just `rowsToChartSpec`: the panel can now reach
|
|
1925
|
-
// every chart type, and the ones that do not read a categories-by-series
|
|
1926
|
-
// grid (scatter, gauge, treemap, calendar, sankey) need their own builders
|
|
1927
|
-
// from the same lazy chunk. Same `import()`, same boundary.
|
|
1928
|
-
let chartEngine = $state(null);
|
|
1929
|
-
$effect(() => {
|
|
1930
|
-
if (chartingEnabled && !chartEngine)
|
|
1931
|
-
import("./chart.js").then((m) => (chartEngine = m));
|
|
1932
|
-
});
|
|
1933
2035
|
/** Put the chart's numbers in the same locale as the grid's. A grid that set
|
|
1934
2036
|
* `localization.locale` has already said which locale its data is in, and a
|
|
1935
2037
|
* chart of that data reading in a different one is a bug nobody would think
|
|
@@ -1942,13 +2044,13 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1942
2044
|
if (chartCfg?.currency)
|
|
1943
2045
|
spec.currency = chartCfg.currency;
|
|
1944
2046
|
};
|
|
1945
|
-
const
|
|
2047
|
+
const chartSpecLive = $derived.by(() => {
|
|
1946
2048
|
if (!chartingEnabled || !chartCfg)
|
|
1947
2049
|
return null;
|
|
1948
2050
|
if (chartCfg.getAggregate) {
|
|
1949
|
-
if (!chartServerBuckets)
|
|
2051
|
+
if (!chartServerBuckets || !chartEngine)
|
|
1950
2052
|
return null;
|
|
1951
|
-
const spec =
|
|
2053
|
+
const spec = chartEngine.bucketsToChartSpec(chartServerBuckets, chartType, !!effectiveChartSeriesId);
|
|
1952
2054
|
if (effectiveChartStacked || chartCfg.stacked100)
|
|
1953
2055
|
spec.stacked = true;
|
|
1954
2056
|
if (chartCfg.stacked100)
|
|
@@ -1957,6 +2059,8 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1957
2059
|
spec.palette = chartCfg.palette;
|
|
1958
2060
|
if (effectiveChartTimeAxis)
|
|
1959
2061
|
spec.xType = "time";
|
|
2062
|
+
else if (chartBucket && chartDimensionIsDate)
|
|
2063
|
+
spec.xType = "ordinal-time";
|
|
1960
2064
|
if (effectiveChartLogScale)
|
|
1961
2065
|
spec.yScale = "log";
|
|
1962
2066
|
if (effectiveChartValueFormat)
|
|
@@ -1966,55 +2070,110 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1966
2070
|
}
|
|
1967
2071
|
if (chartCfg.buildSpec)
|
|
1968
2072
|
return chartCfg.buildSpec(chartRows) ?? null;
|
|
1969
|
-
const category = fieldOf(effectiveChartDimensionId);
|
|
1970
|
-
const values = effectiveChartMeasureIds.map((id) => fieldOf(id)).filter((f) => !!f);
|
|
1971
|
-
if (!category || !values.length)
|
|
1972
|
-
return null;
|
|
1973
2073
|
// Engine not loaded yet (lazy): render nothing until it resolves.
|
|
1974
2074
|
if (!chartEngine)
|
|
1975
2075
|
return null;
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
//
|
|
1979
|
-
//
|
|
1980
|
-
//
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
category
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2076
|
+
// Shared by the direct and aggregated paths so the two cannot drift apart
|
|
2077
|
+
// again - which is how these came to be skipped for scatter, gauge and
|
|
2078
|
+
// boxplot in the first place. Declared here, at the derived's own scope:
|
|
2079
|
+
// a function declaration inside the `else` block below is block-scoped,
|
|
2080
|
+
// and the aggregated path calls it after that block has closed.
|
|
2081
|
+
function applyChartValueSettings(spec) {
|
|
2082
|
+
if (effectiveChartTimeAxis)
|
|
2083
|
+
spec.xType = "time";
|
|
2084
|
+
if (effectiveChartLogScale)
|
|
2085
|
+
spec.yScale = "log";
|
|
2086
|
+
if (effectiveChartValueFormat)
|
|
2087
|
+
spec.valueFormat = effectiveChartValueFormat;
|
|
2088
|
+
applyChartLocale(spec);
|
|
2089
|
+
return spec;
|
|
2090
|
+
}
|
|
2091
|
+
// Pivot mode: the pivot on screen is the chart, rows as categories and
|
|
2092
|
+
// columns as series; the pickers have nothing to choose.
|
|
2093
|
+
let spec;
|
|
2094
|
+
if (pivotResult) {
|
|
2095
|
+
spec = chartEngine.pivotResultToChartSpec(pivotResult, {
|
|
2096
|
+
type: chartType, stacked: effectiveChartStacked, stacked100: effectiveChartStacked100,
|
|
2097
|
+
measures: pivotConfig.values,
|
|
2098
|
+
...(effectiveChartPalette ? { palette: effectiveChartPalette } : {}),
|
|
2099
|
+
});
|
|
2100
|
+
}
|
|
2101
|
+
else {
|
|
2102
|
+
const category = fieldOf(effectiveChartDimensionId);
|
|
2103
|
+
const values = effectiveChartMeasureIds.map((id) => fieldOf(id)).filter((f) => !!f);
|
|
2104
|
+
if (!chartIsOhlc && (!category || !values.length))
|
|
2105
|
+
return null;
|
|
2106
|
+
const seriesField = fieldOf(effectiveChartSeriesId);
|
|
2107
|
+
const rowsForChart = chartRows;
|
|
2108
|
+
// Some types read the rows directly rather than a grouped grid: a scatter
|
|
2109
|
+
// point is one row, a gauge has no category axis at all, and a box plot
|
|
2110
|
+
// needs the whole sample per group rather than one reduced number. The
|
|
2111
|
+
// engine owns that dispatch - naming each type here would put them in the
|
|
2112
|
+
// base bundle, which every grid pays for whether or not it ever charts.
|
|
2113
|
+
// Everything else falls through to the aggregation below and is reshaped
|
|
2114
|
+
// afterwards, so reduce / sort / topN / "Other" keep working for all of it.
|
|
2115
|
+
const ohlcOpts = chartIsOhlc
|
|
2116
|
+
? chartEngine.ohlcDirectOptions(fieldOf, effectiveChartOhlcDateId, effectiveChartOhlc, { bucket: chartBucket, candleStyle: chartCandleStyle })
|
|
2117
|
+
: null;
|
|
2118
|
+
const direct = chartEngine.rowsToDirectSpec(chartType, rowsForChart, {
|
|
2119
|
+
...(ohlcOpts ?? {}),
|
|
2120
|
+
category: ohlcOpts?.category ?? category,
|
|
2121
|
+
...(values[0] ? { value: values[0] } : {}),
|
|
2122
|
+
...(fieldOf(effectiveChartMeasure2Id) ? { value2: fieldOf(effectiveChartMeasure2Id) } : {}),
|
|
2123
|
+
...(seriesField ? { series: seriesField } : {}),
|
|
2124
|
+
reduce: chartReduce,
|
|
2125
|
+
...(chartBins ? { bins: chartBins } : {}),
|
|
2126
|
+
...(effectiveChartPalette ? { palette: effectiveChartPalette } : {}),
|
|
2127
|
+
});
|
|
2128
|
+
// Number formatting and the scale/axis-type settings are about how values
|
|
2129
|
+
// READ, so they apply to a scatter or a gauge exactly as they do to a bar
|
|
2130
|
+
// chart. They used to live only on the aggregated path below, so setting
|
|
2131
|
+
// `valueFormat` or `logScale` on a direct type silently did nothing.
|
|
2132
|
+
//
|
|
2133
|
+
// The series-shaped settings (trend, averageLine, seriesTypes) stay on the
|
|
2134
|
+
// aggregated path: a direct spec does not have the series shape they read.
|
|
2135
|
+
if (direct) {
|
|
2136
|
+
if (chartIsOhlc) {
|
|
2137
|
+
// Overlays ride on the price series; the panes are composed by the
|
|
2138
|
+
// panel body from `chartIndicatorPanes`.
|
|
2139
|
+
const overlay = chartIndicatorSplit.overlay;
|
|
2140
|
+
if (overlay && direct.series[0])
|
|
2141
|
+
direct.series[0].overlay = overlay;
|
|
2142
|
+
}
|
|
2143
|
+
return applyChartValueSettings(direct);
|
|
2144
|
+
}
|
|
2145
|
+
// A direct type that could not build (scatter with no Y picked yet, for
|
|
2146
|
+
// instance) must not fall through to the bar-chart path and draw the wrong
|
|
2147
|
+
// chart - it renders the panel's empty state instead.
|
|
2148
|
+
if (chartType === "scatter" || chartType === "gauge" || chartType === "boxplot" ||
|
|
2149
|
+
chartType === "histogram" || chartType === "range-bar" || chartType === "range-area" ||
|
|
2150
|
+
chartType === "dumbbell" || chartType === "bullet" || chartIsOhlc)
|
|
2151
|
+
return null;
|
|
2152
|
+
spec = chartEngine.rowsToChartSpec(rowsForChart, {
|
|
2153
|
+
type: chartType,
|
|
2154
|
+
category: category,
|
|
2155
|
+
value: values.length === 1 ? values[0] : values,
|
|
2156
|
+
...(seriesField ? { series: seriesField } : {}),
|
|
2157
|
+
reduce: chartReduce,
|
|
2158
|
+
...(chartBucket && chartDimensionIsDate ? { bucket: chartBucket } : {}),
|
|
2159
|
+
stacked: effectiveChartStacked || effectiveChartStacked100,
|
|
2160
|
+
stacked100: effectiveChartStacked100,
|
|
2161
|
+
...(effectiveChartPalette ? { palette: effectiveChartPalette } : {}),
|
|
2162
|
+
...(chartCfg.topN ? { topN: chartCfg.topN } : {}),
|
|
2163
|
+
...(chartCfg.otherLabel ? { otherLabel: chartCfg.otherLabel } : {}),
|
|
2164
|
+
...(chartCfg.sort ? { sort: chartCfg.sort } : {}),
|
|
2165
|
+
});
|
|
2166
|
+
}
|
|
2013
2167
|
// Reshape the aggregated grid into whatever this type actually reads.
|
|
2014
2168
|
// These run AFTER the aggregation so grouping, reduce, sort, topN and the
|
|
2015
2169
|
// "Other" bucket apply to them exactly as they do to a bar chart.
|
|
2016
|
-
if (chartType === "treemap")
|
|
2170
|
+
if (chartType === "treemap" || chartType === "sunburst")
|
|
2017
2171
|
spec.treemap = chartEngine.specToTreemap(spec);
|
|
2172
|
+
else if (chartType === "chord") {
|
|
2173
|
+
const flow = chartEngine.specToSankey(spec);
|
|
2174
|
+
spec.sankeyNodes = flow.nodes;
|
|
2175
|
+
spec.sankeyLinks = flow.links;
|
|
2176
|
+
}
|
|
2018
2177
|
else if (chartType === "calendar")
|
|
2019
2178
|
spec.calendarValues = chartEngine.specToCalendar(spec);
|
|
2020
2179
|
else if (chartType === "sankey") {
|
|
@@ -2024,13 +2183,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2024
2183
|
}
|
|
2025
2184
|
if (effectiveChartOrientation === "horizontal")
|
|
2026
2185
|
spec.orientation = "horizontal";
|
|
2027
|
-
|
|
2028
|
-
spec.xType = "time";
|
|
2029
|
-
if (effectiveChartLogScale)
|
|
2030
|
-
spec.yScale = "log";
|
|
2031
|
-
if (effectiveChartValueFormat)
|
|
2032
|
-
spec.valueFormat = effectiveChartValueFormat;
|
|
2033
|
-
applyChartLocale(spec);
|
|
2186
|
+
applyChartValueSettings(spec);
|
|
2034
2187
|
if (chartType !== "pie" && spec.orientation !== "horizontal" && !spec.yAxisTitle && chartAutoYAxisTitle) {
|
|
2035
2188
|
spec.yAxisTitle = chartAutoYAxisTitle;
|
|
2036
2189
|
}
|
|
@@ -2043,6 +2196,16 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2043
2196
|
spec.annotations = chartCfg.annotations;
|
|
2044
2197
|
if (chartCfg.patternFallback)
|
|
2045
2198
|
spec.patternFallback = true;
|
|
2199
|
+
if (chartFunnelShape && chartType === "funnel")
|
|
2200
|
+
spec.funnelShape = chartFunnelShape;
|
|
2201
|
+
if (chartCandleStyle && (chartType === "candlestick" || chartType === "ohlc"))
|
|
2202
|
+
spec.candleStyle = chartCandleStyle;
|
|
2203
|
+
// A stream is a stacked area on a wiggle baseline; the picker's Stacked
|
|
2204
|
+
// box has nothing to add, so the offset is applied here outright.
|
|
2205
|
+
if (chartType === "stream") {
|
|
2206
|
+
spec.stacked = true;
|
|
2207
|
+
spec.stackOffset = "wiggle";
|
|
2208
|
+
}
|
|
2046
2209
|
const refLines = [...(chartCfg.referenceLines ?? [])];
|
|
2047
2210
|
if (chartCfg.averageLine && spec.series[0]?.values.length) {
|
|
2048
2211
|
const vals = spec.series[0].values;
|
|
@@ -2052,8 +2215,10 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2052
2215
|
if (refLines.length)
|
|
2053
2216
|
spec.referenceLines = refLines;
|
|
2054
2217
|
for (const s of spec.series) {
|
|
2055
|
-
if (chartCfg.trend)
|
|
2056
|
-
|
|
2218
|
+
if (chartCfg.trend) {
|
|
2219
|
+
const t = chartCfg.trend;
|
|
2220
|
+
s.overlay = t === "sma" || t === "ema" ? `${t}:7` : t === "poly" ? "poly:2" : t;
|
|
2221
|
+
}
|
|
2057
2222
|
if (chartCfg.smooth)
|
|
2058
2223
|
s.smooth = true;
|
|
2059
2224
|
if (chartCfg.seriesTypes?.[s.label])
|
|
@@ -2063,6 +2228,51 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2063
2228
|
}
|
|
2064
2229
|
return spec;
|
|
2065
2230
|
});
|
|
2231
|
+
/**
|
|
2232
|
+
* What the panel renders: an unlinked chart's frozen snapshot, else the live
|
|
2233
|
+
* derivation with the builder's format applied. The format runs through
|
|
2234
|
+
* the lazy engine so its code stays out of the base bundle.
|
|
2235
|
+
*/
|
|
2236
|
+
const chartSpec = $derived.by(() => {
|
|
2237
|
+
if (activeChart.frozen)
|
|
2238
|
+
return activeChart.frozen.spec;
|
|
2239
|
+
const live = chartSpecLive;
|
|
2240
|
+
if (!live || !activeChart.format || !chartEngine)
|
|
2241
|
+
return live;
|
|
2242
|
+
return chartEngine.applyChartFormat(live, activeChart.format);
|
|
2243
|
+
});
|
|
2244
|
+
let announcedFirstChart = false;
|
|
2245
|
+
$effect(() => {
|
|
2246
|
+
if (!chartPanelOpen || announcedFirstChart || !chartCfg?.onChartCreated)
|
|
2247
|
+
return;
|
|
2248
|
+
announcedFirstChart = true;
|
|
2249
|
+
const c = charts[0];
|
|
2250
|
+
untrack(() => chartCfg?.onChartCreated?.({ index: 0, title: c.title, type: c.type, spec: null }));
|
|
2251
|
+
});
|
|
2252
|
+
// Lifecycle: one `onChartChanged` per burst of spec changes.
|
|
2253
|
+
let chartChangeTimer = null;
|
|
2254
|
+
$effect(() => {
|
|
2255
|
+
const cb = chartCfg?.onChartChanged;
|
|
2256
|
+
const spec = chartSpec;
|
|
2257
|
+
const index = activeChartIndex;
|
|
2258
|
+
if (!cb)
|
|
2259
|
+
return;
|
|
2260
|
+
untrack(() => {
|
|
2261
|
+
if (chartChangeTimer)
|
|
2262
|
+
clearTimeout(chartChangeTimer);
|
|
2263
|
+
chartChangeTimer = setTimeout(() => {
|
|
2264
|
+
chartChangeTimer = null;
|
|
2265
|
+
const c = charts[index] ?? activeChart;
|
|
2266
|
+
cb({ index, title: c.title, type: c.type, spec });
|
|
2267
|
+
}, 150);
|
|
2268
|
+
});
|
|
2269
|
+
return () => {
|
|
2270
|
+
if (chartChangeTimer) {
|
|
2271
|
+
clearTimeout(chartChangeTimer);
|
|
2272
|
+
chartChangeTimer = null;
|
|
2273
|
+
}
|
|
2274
|
+
};
|
|
2275
|
+
});
|
|
2066
2276
|
/**
|
|
2067
2277
|
* Can a click on this chart be turned back into a grid filter?
|
|
2068
2278
|
*
|
|
@@ -2071,11 +2281,16 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2071
2281
|
* SOURCE column by a target value and empty the grid), a gauge (no category
|
|
2072
2282
|
* at all), or a scatter (the mark carries a point label, not a group).
|
|
2073
2283
|
*/
|
|
2074
|
-
const chartCrossFilterable = $derived(chartType !== "sankey" && chartType !== "gauge" && chartType !== "scatter"
|
|
2284
|
+
const chartCrossFilterable = $derived(chartType !== "sankey" && chartType !== "gauge" && chartType !== "scatter" &&
|
|
2285
|
+
// A chord's arcs are both ends of a flow, a sunburst's are a hierarchy
|
|
2286
|
+
// and a histogram's are bins: none maps a click back to one dimension value.
|
|
2287
|
+
chartType !== "chord" && chartType !== "sunburst" && chartType !== "histogram");
|
|
2288
|
+
/** The column a chart click filters: the picked dimension, or in pivot mode the innermost row dimension when it is a grid column. */
|
|
2289
|
+
const chartFilterDimId = $derived(pivotResult ? (chartEngine?.pivotFilterColumn(pivotConfig.rows, chartableColumns.dims, fieldOf) ?? null) : effectiveChartDimensionId);
|
|
2075
2290
|
function applyChartCrossFilter(category) {
|
|
2076
2291
|
if (!chartCrossFilterable)
|
|
2077
2292
|
return;
|
|
2078
|
-
const dimId =
|
|
2293
|
+
const dimId = chartFilterDimId;
|
|
2079
2294
|
if (!dimId)
|
|
2080
2295
|
return;
|
|
2081
2296
|
const existing = valueFilters[dimId];
|
|
@@ -2087,7 +2302,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2087
2302
|
valueFilters = { ...valueFilters, [dimId]: next };
|
|
2088
2303
|
}
|
|
2089
2304
|
function clearChartCrossFilter() {
|
|
2090
|
-
const dimId =
|
|
2305
|
+
const dimId = chartFilterDimId;
|
|
2091
2306
|
if (!dimId)
|
|
2092
2307
|
return;
|
|
2093
2308
|
const next = { ...valueFilters };
|
|
@@ -2246,9 +2461,50 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2246
2461
|
const logical = virtualizer.getState().scrollOffset;
|
|
2247
2462
|
return logical - rowScrollScaling.logicalToDom(logical);
|
|
2248
2463
|
});
|
|
2464
|
+
/**
|
|
2465
|
+
* The first `frozenRows` rows are rendered ahead of the virtual window
|
|
2466
|
+
* and stick under the header. The virtualizer still counts them, so its
|
|
2467
|
+
* offsets stay in row-index space and nothing downstream has to translate;
|
|
2468
|
+
* the window skips them, and the top spacer gives back the height they
|
|
2469
|
+
* already occupy in the flow.
|
|
2470
|
+
*/
|
|
2471
|
+
const frozenRowCount = $derived(Math.max(0, Math.min(Math.floor(props.frozenRows ?? 0), allRows.length)));
|
|
2472
|
+
const frozenRowList = $derived(frozenRowCount ? allRows.slice(0, frozenRowCount) : []);
|
|
2473
|
+
/** The merged cells, indexed; null (the usual case) costs nothing. */
|
|
2474
|
+
const mergeIndex = $derived(buildMergeIndex(props.mergedCells));
|
|
2475
|
+
const frozenBandHeight = $derived.by(() => {
|
|
2476
|
+
if (!frozenRowCount)
|
|
2477
|
+
return 0;
|
|
2478
|
+
rowResizeVersion;
|
|
2479
|
+
autoRowHeightVersion;
|
|
2480
|
+
let total = 0;
|
|
2481
|
+
for (let i = 0; i < frozenRowCount; i += 1)
|
|
2482
|
+
total += rowSizePxOf(i);
|
|
2483
|
+
return total;
|
|
2484
|
+
});
|
|
2485
|
+
/** The declared or dragged height of one row; measured under auto height. */
|
|
2486
|
+
function rowSizePxOf(i) {
|
|
2487
|
+
if (isRowCollapsed(i))
|
|
2488
|
+
return 0;
|
|
2489
|
+
if (autoRowHeightOn)
|
|
2490
|
+
return measuredRowHeights.get(i) ?? autoRowHeightFallback;
|
|
2491
|
+
const own = rowResizeHeightPx(i);
|
|
2492
|
+
if (own != null)
|
|
2493
|
+
return own;
|
|
2494
|
+
const rh = props.rowHeight;
|
|
2495
|
+
return typeof rh === "function" ? rh(i) : (rh ?? 30);
|
|
2496
|
+
}
|
|
2497
|
+
/** Where frozen row i sticks: under the header and the frozen rows above it. */
|
|
2498
|
+
function frozenRowTop(i) {
|
|
2499
|
+
let top = headerHeight;
|
|
2500
|
+
for (let k = 0; k < i; k += 1)
|
|
2501
|
+
top += rowSizePxOf(k);
|
|
2502
|
+
return top;
|
|
2503
|
+
}
|
|
2249
2504
|
// Spacer heights in DOM space. With scaling inert these equal the original
|
|
2250
|
-
// virtualRowStart / virtualRowBottomSpacer.
|
|
2251
|
-
|
|
2505
|
+
// virtualRowStart / virtualRowBottomSpacer. The frozen band is in the flow
|
|
2506
|
+
// before the spacer whatever the window shows, so it comes off the spacer.
|
|
2507
|
+
const rowTopSpacer = $derived(Math.max(virtualRowStart - rowOffsetAdjustment - frozenBandHeight, 0));
|
|
2252
2508
|
const rowBottomSpacer = $derived(Math.max(rowDomTotalSize - (virtualRowEnd - rowOffsetAdjustment), 0));
|
|
2253
2509
|
const virtualColumns = $derived.by(function virtualColumns_d() {
|
|
2254
2510
|
columnVirtualizerVersion;
|
|
@@ -2585,35 +2841,117 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2585
2841
|
let autoRowHeightVersion = $state(0);
|
|
2586
2842
|
const autoRowHeightFallback = $derived(typeof props.rowHeight === "number" ? props.rowHeight : 30);
|
|
2587
2843
|
// ----- Interactive row resize (`rowResize` prop) -----
|
|
2588
|
-
// Heights the user has dragged,
|
|
2589
|
-
//
|
|
2590
|
-
//
|
|
2591
|
-
//
|
|
2592
|
-
//
|
|
2844
|
+
// Heights the user has dragged, or set through `api.setRowHeight`, keyed
|
|
2845
|
+
// by ROW ID so they belong to the row rather than to a slot: the height
|
|
2846
|
+
// follows its row through a sort or a filter and survives the data being
|
|
2847
|
+
// replaced. They used to be keyed by index and wiped on every data change,
|
|
2848
|
+
// which in a spreadsheet meant every keystroke, so a row dragged taller
|
|
2849
|
+
// snapped back the moment the next cell was typed. A plain Map plus a
|
|
2850
|
+
// version counter, like `measuredRowHeights` above: the readers touch the
|
|
2851
|
+
// version, and the Map itself stays out of the reactive graph.
|
|
2593
2852
|
const rowResizeHeights = new Map();
|
|
2594
2853
|
let rowResizeVersion = $state(0);
|
|
2595
|
-
|
|
2596
|
-
|
|
2854
|
+
/**
|
|
2855
|
+
* Rows folded to nothing, the row-side twin of `collapsedColumns`: the
|
|
2856
|
+
* row keeps its index and its cells and takes no height. Keyed by row id
|
|
2857
|
+
* like the heights and announced through the same version counter, so
|
|
2858
|
+
* every reader of a row's size sees the change.
|
|
2859
|
+
*/
|
|
2860
|
+
const collapsedRows = new Set();
|
|
2861
|
+
function isRowCollapsed(index) {
|
|
2862
|
+
rowResizeVersion;
|
|
2863
|
+
const id = allRows[index]?.id;
|
|
2864
|
+
return id != null && collapsedRows.has(id);
|
|
2865
|
+
}
|
|
2866
|
+
function setRowCollapsed(index, collapsed) {
|
|
2867
|
+
const id = allRows[index]?.id;
|
|
2868
|
+
if (id == null)
|
|
2597
2869
|
return;
|
|
2598
|
-
|
|
2870
|
+
if (collapsed === collapsedRows.has(id))
|
|
2871
|
+
return;
|
|
2872
|
+
if (collapsed)
|
|
2873
|
+
collapsedRows.add(id);
|
|
2874
|
+
else
|
|
2875
|
+
collapsedRows.delete(id);
|
|
2599
2876
|
rowResizeVersion += 1;
|
|
2600
2877
|
}
|
|
2601
|
-
/**
|
|
2602
|
-
*
|
|
2603
|
-
|
|
2604
|
-
|
|
2878
|
+
/**
|
|
2879
|
+
* A size the user changed by hand is one undo step, as it is in a
|
|
2880
|
+
* spreadsheet: Ctrl+Z after a column drag puts the width back. Recorded
|
|
2881
|
+
* only for the user's gestures; the api's own setters record nothing, so
|
|
2882
|
+
* a consumer restoring saved sizes does not fill the history.
|
|
2883
|
+
*/
|
|
2884
|
+
function recordSizeUndo(undo, redo) {
|
|
2885
|
+
pushHistory(ctx, [
|
|
2886
|
+
{ rowId: "", columnId: "", field: "", before: undefined, after: undefined, custom: { undo, redo } },
|
|
2887
|
+
]);
|
|
2605
2888
|
}
|
|
2606
|
-
//
|
|
2607
|
-
//
|
|
2608
|
-
//
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2889
|
+
// The undo and redo of a size step report through the same callbacks as
|
|
2890
|
+
// the gesture did: a consumer keeping the sizes (a sheet saving its
|
|
2891
|
+
// document) would otherwise hold the width the user dragged to while the
|
|
2892
|
+
// grid showed the one Ctrl+Z put back.
|
|
2893
|
+
function resizeColumnByUser(columnId, width, before) {
|
|
2894
|
+
const apply = (w) => {
|
|
2895
|
+
columnWidths = { ...columnWidths, [columnId]: w };
|
|
2896
|
+
props.onColumnResize?.({ columnId, width: w });
|
|
2897
|
+
};
|
|
2898
|
+
apply(width);
|
|
2899
|
+
if (before !== width)
|
|
2900
|
+
recordSizeUndo(() => apply(before), () => apply(width));
|
|
2901
|
+
}
|
|
2902
|
+
function autosizeColumnByUser(columnId) {
|
|
2903
|
+
const before = getColumnWidth(columnId);
|
|
2904
|
+
autosizeColumn(columnId);
|
|
2905
|
+
const width = getColumnWidth(columnId);
|
|
2906
|
+
const apply = (w) => {
|
|
2907
|
+
columnWidths = { ...columnWidths, [columnId]: w };
|
|
2908
|
+
props.onColumnResize?.({ columnId, width: w });
|
|
2909
|
+
};
|
|
2910
|
+
if (before !== width)
|
|
2911
|
+
recordSizeUndo(() => apply(before), () => apply(width));
|
|
2912
|
+
else
|
|
2913
|
+
props.onColumnResize?.({ columnId, width });
|
|
2914
|
+
}
|
|
2915
|
+
function resizeRowByUser(index, height) {
|
|
2916
|
+
const before = rowResizeHeightPx(index) ?? null;
|
|
2917
|
+
const apply = (h) => {
|
|
2918
|
+
setRowResizeHeight(index, h);
|
|
2919
|
+
props.onRowResize?.({ rowIndex: index, height: h });
|
|
2920
|
+
};
|
|
2921
|
+
apply(height);
|
|
2922
|
+
if (before !== height)
|
|
2923
|
+
recordSizeUndo(() => apply(before), () => apply(height));
|
|
2924
|
+
}
|
|
2925
|
+
function setRowResizeHeight(index, height) {
|
|
2926
|
+
const id = allRows[index]?.id;
|
|
2927
|
+
if (id == null)
|
|
2613
2928
|
return;
|
|
2614
|
-
|
|
2929
|
+
if (height == null) {
|
|
2930
|
+
if (!rowResizeHeights.delete(id))
|
|
2931
|
+
return;
|
|
2932
|
+
}
|
|
2933
|
+
else {
|
|
2934
|
+
if (!Number.isFinite(height) || height <= 0)
|
|
2935
|
+
return;
|
|
2936
|
+
rowResizeHeights.set(id, Math.round(height));
|
|
2937
|
+
}
|
|
2615
2938
|
rowResizeVersion += 1;
|
|
2616
|
-
}
|
|
2939
|
+
}
|
|
2940
|
+
/** A row's own height, or undefined when it has none. Read by the view AND
|
|
2941
|
+
* by the virtualizer's `estimateSize`. Auto height wins over it: the
|
|
2942
|
+
* content decides the height there, and a manual one would be overwritten
|
|
2943
|
+
* by the next measurement. */
|
|
2944
|
+
function rowResizeHeightPx(index) {
|
|
2945
|
+
const id = allRows[index]?.id;
|
|
2946
|
+
if (id == null)
|
|
2947
|
+
return undefined;
|
|
2948
|
+
// A collapsed row is 0 whatever else it has, under auto height too.
|
|
2949
|
+
if (collapsedRows.has(id))
|
|
2950
|
+
return 0;
|
|
2951
|
+
if (autoRowHeightOn)
|
|
2952
|
+
return undefined;
|
|
2953
|
+
return rowResizeHeights.get(id);
|
|
2954
|
+
}
|
|
2617
2955
|
/** Report a row's measured height. Called by the view's measuring action.
|
|
2618
2956
|
* Sub-pixel churn is ignored so a fractional layout can't loop. */
|
|
2619
2957
|
function reportRowHeight(index, height) {
|
|
@@ -2638,12 +2976,20 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2638
2976
|
function measureRowHeight(node, index) {
|
|
2639
2977
|
let current = index;
|
|
2640
2978
|
let stop = null;
|
|
2641
|
-
|
|
2979
|
+
// Read the rect only under autoRowHeight. `getBoundingClientRect` forces a
|
|
2980
|
+
// synchronous layout, and the virtualizer's slot keys hand every rendered
|
|
2981
|
+
// <tr> a new index on each one-row scroll, so measuring unconditionally
|
|
2982
|
+
// meant ~30 forced layouts per arrow key on a fixed-height grid whose
|
|
2983
|
+
// measurement `reportRowHeight` then threw away.
|
|
2984
|
+
const measure = () => {
|
|
2985
|
+
if (!autoRowHeightOn)
|
|
2986
|
+
return;
|
|
2987
|
+
reportRowHeight(current, node.getBoundingClientRect().height);
|
|
2988
|
+
};
|
|
2642
2989
|
const attach = () => {
|
|
2643
2990
|
stop?.();
|
|
2644
2991
|
stop = autoRowHeightOn ? observeSizeRaf(node, measure) : null;
|
|
2645
|
-
|
|
2646
|
-
measure();
|
|
2992
|
+
measure();
|
|
2647
2993
|
};
|
|
2648
2994
|
attach();
|
|
2649
2995
|
return {
|
|
@@ -2694,8 +3040,8 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2694
3040
|
: typeof rh === "function"
|
|
2695
3041
|
? rh
|
|
2696
3042
|
: (rh ?? 30);
|
|
2697
|
-
const estimateSize =
|
|
2698
|
-
? (index) =>
|
|
3043
|
+
const estimateSize = rowResizeHeights.size > 0 || collapsedRows.size > 0
|
|
3044
|
+
? (index) => rowResizeHeightPx(index) ??
|
|
2699
3045
|
(typeof base === "function" ? base(index) : base)
|
|
2700
3046
|
: base;
|
|
2701
3047
|
virtualizer.setOptions({
|
|
@@ -2755,6 +3101,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2755
3101
|
// closure each run; the virtualizer sees a new function reference and
|
|
2756
3102
|
// re-derives its layout from the current per-column widths.
|
|
2757
3103
|
columnWidths;
|
|
3104
|
+
collapsedColumns;
|
|
2758
3105
|
columnVirtualizer.setOptions({
|
|
2759
3106
|
count: allColumns.length,
|
|
2760
3107
|
estimateSize: (index) => {
|
|
@@ -2795,8 +3142,11 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2795
3142
|
// Lets the resolver answer from cache without re-invoking the source, so an
|
|
2796
3143
|
// async source fires one request per row instead of one per render.
|
|
2797
3144
|
const asyncEditorColumns = new Set();
|
|
3145
|
+
// Depends on `allRows` and `rowSelectionState` only. It used to read the
|
|
3146
|
+
// catch-all `gridStateVersion` too, which made every arrow key walk all the
|
|
3147
|
+
// rows (10k iterations on the large-dataset demo, 100k on its top tier) to
|
|
3148
|
+
// recount a selection that had not changed.
|
|
2798
3149
|
const headerSelectionState = $derived.by(function headerSelectionState_d() {
|
|
2799
|
-
gridStateVersion;
|
|
2800
3150
|
const selectable = allRows.filter((row) => !isGroupRow(row));
|
|
2801
3151
|
if (!selectable.length)
|
|
2802
3152
|
return "none";
|
|
@@ -2815,6 +3165,10 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2815
3165
|
* from a user-focused (0,0). The fill handle keys off this flag so it
|
|
2816
3166
|
* stays hidden until the user actually selects something. */
|
|
2817
3167
|
let userHasActivatedCell = $state(false);
|
|
3168
|
+
/** The column a run of Tabs began in, so the Enter that ends the run goes
|
|
3169
|
+
* down from there (`getEntryStep`). Plain, not state: nothing renders
|
|
3170
|
+
* it. `setActiveCell` clears it; the Tab and Enter paths set it back. */
|
|
3171
|
+
let tabRunOrigin = null;
|
|
2818
3172
|
/**
|
|
2819
3173
|
* Per-column fitted widths when `fitColumns` is on. Computed in one pass
|
|
2820
3174
|
* so the LAST auto-sized column can absorb the rounding residue and make
|
|
@@ -2837,7 +3191,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2837
3191
|
return null;
|
|
2838
3192
|
// Exclude columns hidden by a collapsed group too (#57) - otherwise their
|
|
2839
3193
|
// widths stay in the fit distribution and leave a blank gap at the edge.
|
|
2840
|
-
const cols = grid.getAllColumns().filter((c) => !hiddenColumns[c.id] && !hiddenByGroupCollapse[c.id]);
|
|
3194
|
+
const cols = grid.getAllColumns().filter((c) => !hiddenColumns[c.id] && !hiddenByGroupCollapse[c.id] && !collapsedColumns[c.id]);
|
|
2841
3195
|
if (!cols.length)
|
|
2842
3196
|
return null;
|
|
2843
3197
|
const rowNumberWidth = showRowNumbersEffective ? rowNumberColumnWidth : 0;
|
|
@@ -3170,6 +3524,17 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3170
3524
|
// state mutation inside the callback (e.g. `api.setGroupBy(...)`) created
|
|
3171
3525
|
// an infinite update loop. Now it's a true mount-once notification.
|
|
3172
3526
|
let apiNotified = false;
|
|
3527
|
+
// Memoized: `buildApi()` builds a fresh object each call, and a feature pack
|
|
3528
|
+
// that keys per-grid state on the api (a WeakMap in @svgrid/enterprise) needs
|
|
3529
|
+
// the object the consumer received and the object a command handler sees to
|
|
3530
|
+
// be the same one. Every method reads through `ctx`, so one instance stays
|
|
3531
|
+
// correct for the life of the grid.
|
|
3532
|
+
let apiInstance = null;
|
|
3533
|
+
function api() {
|
|
3534
|
+
if (!apiInstance)
|
|
3535
|
+
apiInstance = buildApi();
|
|
3536
|
+
return apiInstance;
|
|
3537
|
+
}
|
|
3173
3538
|
$effect(() => {
|
|
3174
3539
|
if (apiNotified)
|
|
3175
3540
|
return;
|
|
@@ -3177,7 +3542,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3177
3542
|
if (!cb)
|
|
3178
3543
|
return;
|
|
3179
3544
|
apiNotified = true;
|
|
3180
|
-
cb(
|
|
3545
|
+
cb(api());
|
|
3181
3546
|
});
|
|
3182
3547
|
const ctx = {
|
|
3183
3548
|
get props() { return props; },
|
|
@@ -3239,6 +3604,8 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3239
3604
|
set history(v) { history = v; },
|
|
3240
3605
|
get historyPtr() { return historyPtr; },
|
|
3241
3606
|
set historyPtr(v) { historyPtr = v; },
|
|
3607
|
+
get historyGroupId() { return historyGroupId; },
|
|
3608
|
+
set historyGroupId(v) { historyGroupId = v; },
|
|
3242
3609
|
get historyVersion() { return historyVersion; },
|
|
3243
3610
|
set historyVersion(v) { historyVersion = v; },
|
|
3244
3611
|
get tooltip() { return tooltip; },
|
|
@@ -3261,6 +3628,9 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3261
3628
|
get editorSelectAll() { return editorSelectAll; },
|
|
3262
3629
|
set editorSelectAll(v) { editorSelectAll = v; },
|
|
3263
3630
|
get columnWidths() { return columnWidths; },
|
|
3631
|
+
get collapsedColumns() { return collapsedColumns; },
|
|
3632
|
+
get mergeIndex() { return mergeIndex; },
|
|
3633
|
+
get setColumnCollapsed() { return setColumnCollapsed; },
|
|
3264
3634
|
set columnWidths(v) { columnWidths = v; },
|
|
3265
3635
|
get MIN_COLUMN_WIDTH() { return MIN_COLUMN_WIDTH; },
|
|
3266
3636
|
get columnPinning() { return columnPinning; },
|
|
@@ -3332,6 +3702,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3332
3702
|
get moveCellsEffective() { return moveCellsEffective; },
|
|
3333
3703
|
get flushScheduledScrollSync() { return flushScheduledScrollSync; },
|
|
3334
3704
|
get scheduleScrollSync() { return scheduleScrollSync; },
|
|
3705
|
+
get syncScrollNow() { return syncScrollNow; },
|
|
3335
3706
|
get internalData() { return internalData; },
|
|
3336
3707
|
set internalData(v) { internalData = v; },
|
|
3337
3708
|
get internalColumns() { return internalColumns; },
|
|
@@ -3435,6 +3806,8 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3435
3806
|
get moveColumnInPanel() { return moveColumnInPanel; },
|
|
3436
3807
|
// ---- Localized chrome strings ----
|
|
3437
3808
|
get messages() { return gridMessages; },
|
|
3809
|
+
/** The raw `localization.text` map, for the lazy chart panel's own strings. */
|
|
3810
|
+
get localizationText() { return props.localization?.text; },
|
|
3438
3811
|
// ---- In-grid pivot mode ----
|
|
3439
3812
|
get pivotConfig() { return pivotConfig; },
|
|
3440
3813
|
get pivotActive() { return pivotActive; },
|
|
@@ -3467,6 +3840,35 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3467
3840
|
set chartLogScale(v) { activeChart.logScale = v; },
|
|
3468
3841
|
get chartTimeAxis() { return effectiveChartTimeAxis; },
|
|
3469
3842
|
set chartTimeAxis(v) { activeChart.timeAxis = v; },
|
|
3843
|
+
get chartBucket() { return chartBucket; },
|
|
3844
|
+
set chartBucket(v) { activeChart.bucket = v; },
|
|
3845
|
+
get chartBins() { return chartBins; },
|
|
3846
|
+
set chartBins(v) { activeChart.bins = v; },
|
|
3847
|
+
get chartFunnelShape() { return chartFunnelShape; },
|
|
3848
|
+
set chartFunnelShape(v) { activeChart.funnelShape = v; },
|
|
3849
|
+
get chartCandleStyle() { return chartCandleStyle; },
|
|
3850
|
+
set chartCandleStyle(v) { activeChart.candleStyle = v; },
|
|
3851
|
+
get chartFormat() { return activeChart.format; },
|
|
3852
|
+
set chartFormat(v) { activeChart.format = v; },
|
|
3853
|
+
get chartFrozen() { return activeChart.frozen; },
|
|
3854
|
+
get chartIsOhlc() { return chartIsOhlc; },
|
|
3855
|
+
get chartOhlc() { return effectiveChartOhlc; },
|
|
3856
|
+
set chartOhlc(v) { activeChart.ohlc = v; },
|
|
3857
|
+
get chartOhlcDateId() { return effectiveChartOhlcDateId; },
|
|
3858
|
+
get chartIndicators() { return chartIndicators; },
|
|
3859
|
+
set chartIndicators(v) { activeChart.indicators = v; },
|
|
3860
|
+
get chartIndicatorPanes() { return chartIndicatorPanes; },
|
|
3861
|
+
get chartZoom() { return activeChart.zoom; },
|
|
3862
|
+
// The panel keeps one chart component across tabs, so "tabs share a
|
|
3863
|
+
// window" is simply every tab carrying the same window: a zoom on one is
|
|
3864
|
+
// written to all, and switching tabs hands the component the same value.
|
|
3865
|
+
set chartZoom(v) {
|
|
3866
|
+
if (chartCfg?.syncTabs)
|
|
3867
|
+
for (const c of charts)
|
|
3868
|
+
c.zoom = v;
|
|
3869
|
+
else
|
|
3870
|
+
activeChart.zoom = v;
|
|
3871
|
+
},
|
|
3470
3872
|
get chartValueFormat() { return effectiveChartValueFormat ?? "number"; },
|
|
3471
3873
|
set chartValueFormat(v) { activeChart.valueFormat = v; },
|
|
3472
3874
|
get chartDimensionIsDate() { return chartDimensionIsDate; },
|
|
@@ -3487,8 +3889,23 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3487
3889
|
get activeChartIndex() { return Math.min(activeChartIndex, charts.length - 1); },
|
|
3488
3890
|
set activeChartIndex(v) { activeChartIndex = Math.max(0, Math.min(charts.length - 1, v)); },
|
|
3489
3891
|
addChart() {
|
|
3490
|
-
|
|
3892
|
+
const next = makeChart(`Chart ${charts.length + 1}`);
|
|
3893
|
+
if (chartCfg?.syncTabs)
|
|
3894
|
+
next.zoom = activeChart.zoom;
|
|
3895
|
+
charts = [...charts, next];
|
|
3491
3896
|
activeChartIndex = charts.length - 1;
|
|
3897
|
+
chartCfg?.onChartCreated?.({ index: activeChartIndex, title: next.title, type: next.type, spec: null });
|
|
3898
|
+
},
|
|
3899
|
+
/** Unlink the active chart from the grid: it keeps the spec it has now. */
|
|
3900
|
+
freezeChart() {
|
|
3901
|
+
const spec = chartSpec;
|
|
3902
|
+
if (!spec)
|
|
3903
|
+
return;
|
|
3904
|
+
activeChart.frozen = { spec: JSON.parse(JSON.stringify(spec)), at: new Date().toISOString() };
|
|
3905
|
+
},
|
|
3906
|
+
/** Link the active chart back to the grid's rows. */
|
|
3907
|
+
unfreezeChart() {
|
|
3908
|
+
activeChart.frozen = null;
|
|
3492
3909
|
},
|
|
3493
3910
|
removeChart(index) {
|
|
3494
3911
|
if (charts.length <= 1)
|
|
@@ -3504,48 +3921,40 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3504
3921
|
c.title = title;
|
|
3505
3922
|
},
|
|
3506
3923
|
getChartsState() {
|
|
3507
|
-
return charts.map(
|
|
3508
|
-
title: c.title,
|
|
3509
|
-
type: c.type,
|
|
3510
|
-
dimension: c.dimensionId,
|
|
3511
|
-
series: c.seriesId ?? null,
|
|
3512
|
-
measure: c.measureId,
|
|
3513
|
-
measure2: c.measure2Id,
|
|
3514
|
-
orientation: c.orientation,
|
|
3515
|
-
stacked100: c.stacked100,
|
|
3516
|
-
donut: c.donut,
|
|
3517
|
-
palette: c.palette,
|
|
3518
|
-
reduce: c.reduce,
|
|
3519
|
-
stacked: c.stacked,
|
|
3520
|
-
dataLabels: c.dataLabels,
|
|
3521
|
-
logScale: c.logScale,
|
|
3522
|
-
timeAxis: c.timeAxis,
|
|
3523
|
-
valueFormat: c.valueFormat,
|
|
3524
|
-
}));
|
|
3924
|
+
return charts.map(tabToState);
|
|
3525
3925
|
},
|
|
3526
3926
|
applyChartsState(list, active) {
|
|
3527
|
-
const restored = list.map(
|
|
3528
|
-
id: `chart-${chartSeq++}`,
|
|
3529
|
-
title: typeof c.title === "string" ? c.title : `Chart ${i + 1}`,
|
|
3530
|
-
type: c.type ?? "bar",
|
|
3531
|
-
reduce: c.reduce ?? "sum",
|
|
3532
|
-
dimensionId: c.dimension ?? null,
|
|
3533
|
-
measureId: c.measure ?? null,
|
|
3534
|
-
seriesId: c.series,
|
|
3535
|
-
measure2Id: c.measure2 ?? null,
|
|
3536
|
-
orientation: c.orientation ?? null,
|
|
3537
|
-
stacked100: c.stacked100 ?? null,
|
|
3538
|
-
donut: c.donut ?? null,
|
|
3539
|
-
palette: c.palette ?? null,
|
|
3540
|
-
stacked: c.stacked ?? null,
|
|
3541
|
-
dataLabels: c.dataLabels ?? null,
|
|
3542
|
-
logScale: c.logScale ?? null,
|
|
3543
|
-
timeAxis: c.timeAxis ?? null,
|
|
3544
|
-
valueFormat: c.valueFormat ?? null,
|
|
3545
|
-
}));
|
|
3927
|
+
const restored = list.map(tabFromState);
|
|
3546
3928
|
charts = restored.length ? restored : [makeChart("Chart 1")];
|
|
3547
3929
|
activeChartIndex = Math.max(0, Math.min(charts.length - 1, active ?? 0));
|
|
3548
3930
|
},
|
|
3931
|
+
// ---- Saved charts ----
|
|
3932
|
+
get savedCharts() { return savedCharts; },
|
|
3933
|
+
/** Keep the active tab's configuration under a name (replacing a same-named one). */
|
|
3934
|
+
saveChart(name) {
|
|
3935
|
+
const entry = { name, savedAt: new Date().toISOString(), tab: tabToState(activeChart) };
|
|
3936
|
+
savedCharts = [...savedCharts.filter((s) => s.name !== name), entry];
|
|
3937
|
+
},
|
|
3938
|
+
/** Apply a saved chart to the active tab; the tab keeps its id and title. */
|
|
3939
|
+
applySavedChart(name) {
|
|
3940
|
+
const entry = savedCharts.find((s) => s.name === name);
|
|
3941
|
+
if (!entry)
|
|
3942
|
+
return false;
|
|
3943
|
+
const { id: _id, title: _title, ...rest } = tabFromState(entry.tab, activeChartIndex);
|
|
3944
|
+
Object.assign(activeChart, rest);
|
|
3945
|
+
return true;
|
|
3946
|
+
},
|
|
3947
|
+
removeSavedChart(name) {
|
|
3948
|
+
savedCharts = savedCharts.filter((s) => s.name !== name);
|
|
3949
|
+
},
|
|
3950
|
+
getSavedCharts() {
|
|
3951
|
+
return savedCharts.map((s) => ({ ...s, tab: { ...s.tab } }));
|
|
3952
|
+
},
|
|
3953
|
+
applySavedCharts(list) {
|
|
3954
|
+
savedCharts = list
|
|
3955
|
+
.filter((s) => typeof s?.name === "string" && s.tab && typeof s.tab === "object")
|
|
3956
|
+
.map((s) => ({ name: s.name, savedAt: String(s.savedAt ?? ""), tab: s.tab }));
|
|
3957
|
+
},
|
|
3549
3958
|
get chartCrossFilterable() { return chartCrossFilterable; },
|
|
3550
3959
|
get chartOrientation() { return effectiveChartOrientation; },
|
|
3551
3960
|
set chartOrientation(v) { activeChart.orientation = v; },
|
|
@@ -3560,7 +3969,11 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3560
3969
|
get effectiveChartMeasure2Id() { return effectiveChartMeasure2Id; },
|
|
3561
3970
|
get chartAiHandler() { return chartAiHandler; },
|
|
3562
3971
|
set chartAiHandler(v) { chartAiHandler = v; },
|
|
3972
|
+
get chartExplainHandler() { return chartExplainHandler; },
|
|
3973
|
+
set chartExplainHandler(v) { chartExplainHandler = v; },
|
|
3563
3974
|
applyChartConfig(config) {
|
|
3975
|
+
if (config.saved)
|
|
3976
|
+
this.applySavedChart(config.saved);
|
|
3564
3977
|
const cols = [...chartableColumns.dims, ...chartableColumns.measures];
|
|
3565
3978
|
const resolve = (v) => {
|
|
3566
3979
|
if (v == null)
|
|
@@ -3593,6 +4006,33 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3593
4006
|
activeChart.timeAxis = config.timeAxis;
|
|
3594
4007
|
if (config.valueFormat)
|
|
3595
4008
|
activeChart.valueFormat = config.valueFormat;
|
|
4009
|
+
if ("bucket" in config)
|
|
4010
|
+
activeChart.bucket = config.bucket ?? null;
|
|
4011
|
+
if ("bins" in config)
|
|
4012
|
+
activeChart.bins = config.bins ?? null;
|
|
4013
|
+
if ("funnelShape" in config)
|
|
4014
|
+
activeChart.funnelShape = config.funnelShape ?? null;
|
|
4015
|
+
if ("candleStyle" in config)
|
|
4016
|
+
activeChart.candleStyle = config.candleStyle ?? null;
|
|
4017
|
+
if ("measure2" in config)
|
|
4018
|
+
activeChart.measure2Id = resolve(config.measure2);
|
|
4019
|
+
if ("ohlc" in config) {
|
|
4020
|
+
const o = config.ohlc;
|
|
4021
|
+
activeChart.ohlc = o
|
|
4022
|
+
? { open: resolve(o.open), high: resolve(o.high), low: resolve(o.low), close: resolve(o.close), volume: resolve(o.volume) }
|
|
4023
|
+
: null;
|
|
4024
|
+
}
|
|
4025
|
+
if (config.indicators)
|
|
4026
|
+
activeChart.indicators = [...config.indicators];
|
|
4027
|
+
if ("format" in config)
|
|
4028
|
+
activeChart.format = config.format ?? null;
|
|
4029
|
+
if (config.frozen === false)
|
|
4030
|
+
activeChart.frozen = null;
|
|
4031
|
+
else if (config.frozen === true && !activeChart.frozen) {
|
|
4032
|
+
const spec = chartSpec;
|
|
4033
|
+
if (spec)
|
|
4034
|
+
activeChart.frozen = { spec: JSON.parse(JSON.stringify(spec)), at: new Date().toISOString() };
|
|
4035
|
+
}
|
|
3596
4036
|
},
|
|
3597
4037
|
get toggleGroupInPanel() { return toggleGroupInPanel; },
|
|
3598
4038
|
get lastSortingSerialized() { return lastSortingSerialized; },
|
|
@@ -3615,8 +4055,17 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3615
4055
|
},
|
|
3616
4056
|
/** True when the `rowResize` prop is on and nothing overrides it. */
|
|
3617
4057
|
get rowResizeOn() { return rowResizeOn; },
|
|
4058
|
+
get frozenRowCount() { return frozenRowCount; },
|
|
4059
|
+
get frozenRowList() { return frozenRowList; },
|
|
4060
|
+
get frozenBandHeight() { return frozenBandHeight; },
|
|
4061
|
+
get frozenRowTop() { return frozenRowTop; },
|
|
3618
4062
|
/** Record the height the user dragged a row to. */
|
|
3619
4063
|
get setRowResizeHeight() { return setRowResizeHeight; },
|
|
4064
|
+
get isRowCollapsed() { return isRowCollapsed; },
|
|
4065
|
+
get setRowCollapsed() { return setRowCollapsed; },
|
|
4066
|
+
get resizeRowByUser() { return resizeRowByUser; },
|
|
4067
|
+
get resizeColumnByUser() { return resizeColumnByUser; },
|
|
4068
|
+
get autosizeColumnByUser() { return autosizeColumnByUser; },
|
|
3620
4069
|
/** A row's dragged height, or undefined when it has not been resized. */
|
|
3621
4070
|
get rowResizeHeightPx() {
|
|
3622
4071
|
rowResizeVersion;
|
|
@@ -3679,6 +4128,8 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3679
4128
|
get toggleSelectAllRows() { return toggleSelectAllRows; },
|
|
3680
4129
|
get userHasActivatedCell() { return userHasActivatedCell; },
|
|
3681
4130
|
set userHasActivatedCell(v) { userHasActivatedCell = v; },
|
|
4131
|
+
get tabRunOrigin() { return tabRunOrigin; },
|
|
4132
|
+
set tabRunOrigin(v) { tabRunOrigin = v; },
|
|
3682
4133
|
get setActiveCell() { return setActiveCell; },
|
|
3683
4134
|
get scrollActiveCellIntoView() { return scrollActiveCellIntoView; },
|
|
3684
4135
|
get getColumnBaseWidth() { return getColumnBaseWidth; },
|
|
@@ -3688,6 +4139,8 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3688
4139
|
get extendSelection() { return extendSelection; },
|
|
3689
4140
|
get isCellInSelectedRange() { return isCellInSelectedRange; },
|
|
3690
4141
|
get getCellRangeEdges() { return getCellRangeEdges; },
|
|
4142
|
+
get activeRangeRect() { return activeRangeRect; },
|
|
4143
|
+
get cellSelectionState() { return cellSelectionState; },
|
|
3691
4144
|
get getSelectionRects() { return getSelectionRects; },
|
|
3692
4145
|
get fillHandleCell() { return fillHandleCell; },
|
|
3693
4146
|
get isInFillPreview() { return isInFillPreview; },
|
|
@@ -3710,6 +4163,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3710
4163
|
get startFillDrag() { return startFillDrag; },
|
|
3711
4164
|
get onFillPointerMove() { return onFillPointerMove; },
|
|
3712
4165
|
get onFillPointerUp() { return onFillPointerUp; },
|
|
4166
|
+
get fillDownToNeighbour() { return fillDownToNeighbour; },
|
|
3713
4167
|
get isOnMoveGrabStrip() { return isOnMoveGrabStrip; },
|
|
3714
4168
|
get startMoveDrag() { return startMoveDrag; },
|
|
3715
4169
|
get onMovePointerMove() { return onMovePointerMove; },
|
|
@@ -3729,6 +4183,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3729
4183
|
get cutSelectionToClipboard() { return cutSelectionToClipboard; },
|
|
3730
4184
|
get pasteFromClipboard() { return pasteFromClipboard; },
|
|
3731
4185
|
get onGridPaste() { return onGridPaste; },
|
|
4186
|
+
get armPasteFallback() { return armPasteFallback; },
|
|
3732
4187
|
get clearSelectedCells() { return clearSelectedCells; },
|
|
3733
4188
|
get onCellDoubleClick() { return onCellDoubleClick; },
|
|
3734
4189
|
get startEditingWithChar() { return startEditingWithChar; },
|
|
@@ -3805,12 +4260,12 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3805
4260
|
get clearColumnFilter() { return clearColumnFilter; },
|
|
3806
4261
|
get onWindowKeydown() { return onWindowKeydown; },
|
|
3807
4262
|
get columnDefMatchesId() { return columnDefMatchesId; },
|
|
3808
|
-
get buildApi() { return
|
|
4263
|
+
get buildApi() { return api; },
|
|
3809
4264
|
get apiNotified() { return apiNotified; },
|
|
3810
4265
|
set apiNotified(v) { apiNotified = v; },
|
|
3811
4266
|
};
|
|
3812
4267
|
const { resolveEffectiveFeatures } = createFeatures(ctx);
|
|
3813
|
-
const { showTooltipFor, hideTooltip, flushScheduledScrollSync, scheduleScrollSync, onBodyScroll } = createScrollSync(ctx);
|
|
4268
|
+
const { showTooltipFor, hideTooltip, flushScheduledScrollSync, scheduleScrollSync, syncScrollNow, onBodyScroll } = createScrollSync(ctx);
|
|
3814
4269
|
const { onGridKeyDown, onWindowKeydown, onHeaderSortClick } = createKeyboard(ctx);
|
|
3815
4270
|
const { computeSummaries, hasRenderedColumn } = createSummaries(ctx);
|
|
3816
4271
|
// Hand the aggregator to `eagerSummaries` (declared above `ctx`, which this
|
|
@@ -3818,13 +4273,13 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3818
4273
|
summarize = computeSummaries;
|
|
3819
4274
|
const { updateFilterRow, updateFilterOperator, updateFilterMenuValue, updateFilterMenuValueTo, addFilterToken, removeFilterToken, toggleFilterToken, toggleCheckboxWithKeyboard, isColumnFiltered, closeMenus, openInSuggest, closeInSuggest, openChooseColumns, openColumnMenu, openFilterMenu, openOperatorMenu, sortColumnFromMenu, clearColumnSort, groupByColumnFromMenu, clearGroupingFromMenu, isFacetChecked, toggleFacetValue, setFacetSelection, setFilterTokens, isAllFacetsChecked, toggleAllFacets, clearColumnFilter, changePage, goToPage, setPageSize, openContextMenu, closeContextMenu, contextMenuItems, saveComment, removeComment, closeCommentEditor } = createMenus(ctx);
|
|
3820
4275
|
const { cellConditionalFormat, computeRowClass, computeCellClass, computeCellTooltip, computeCellValidity, computeCellNote, getColumnEditorOptions, areEditorOptionsLoading, formatListCellValue, formatCellValue, formatPinnedValue, computePinnedCellClass } = createCellRender(ctx);
|
|
3821
|
-
const { isCellEditable, isCellEditableAt, getRowColumnValue, getCellDisplayValue, startEditingWithChar, startEditing, stopEditing, startFullRowEdit, setFullRowDraft, commitFullRowEdit, cancelFullRowEdit, saveEditingCell, applyHistoryStep, updateEditingCellValue, onEditorKeyDown, commitAndMoveByTab, focusOnMount, onCellDoubleClick, pasteFromClipboard, onGridPaste } = createEditing(ctx);
|
|
3822
|
-
const { isRowSelected, toggleRowSelectionById, toggleSelectAllRows, setActiveCell, scrollActiveCellIntoView, setSelection, extendSelection, isCellInSelectedRange, getCellRangeEdges, getSelectionRects, isInFillPreview, dragPreviewRect, fillMarqueeEdges, findColumnById, onCellPointerDown, onCellPointerEnter, endDragSelection, onWindowPointerMove, onCellClick, emitCellDoubleClick } = createSelection(ctx);
|
|
4276
|
+
const { isCellEditable, isCellEditableAt, getRowColumnValue, getCellDisplayValue, startEditingWithChar, startEditing, stopEditing, startFullRowEdit, setFullRowDraft, commitFullRowEdit, cancelFullRowEdit, saveEditingCell, applyHistoryStep, updateEditingCellValue, onEditorKeyDown, commitAndMoveByTab, focusOnMount, onCellDoubleClick, pasteFromClipboard, onGridPaste, armPasteFallback } = createEditing(ctx);
|
|
4277
|
+
const { isRowSelected, toggleRowSelectionById, toggleSelectAllRows, setActiveCell, scrollActiveCellIntoView, setSelection, extendSelection, isCellInSelectedRange, getCellRangeEdges, activeRangeRect, cellSelectionState, getSelectionRects, isInFillPreview, dragPreviewRect, fillMarqueeEdges, findColumnById, onCellPointerDown, onCellPointerEnter, endDragSelection, onWindowPointerMove, onCellClick, emitCellDoubleClick } = createSelection(ctx);
|
|
3823
4278
|
const { cellPinStyle, isColumnPinned, getCurrentColumnOrder, emitColumnOrder, setColumnOrderInternal, applyColumnDrop, onColumnHeaderDragStart, onColumnHeaderDragOver, onColumnHeaderDragLeave, onColumnHeaderDrop, onColumnHeaderDragEnd, pinColumnLeft, pinColumnRight, unpinColumn, toggleColumnVisibleInPanel, moveColumnInPanel, toggleGroupInPanel, getColumnBaseWidth, getColumnWidth, measureText, autosizeColumn, autosizeAllColumns, resetColumns } = createColumns(ctx);
|
|
3824
4279
|
const { onRowDragStart, onRowDragOver, onRowDragLeave, onRowDrop, onRowsContainerDragOver, onRowsContainerDrop, onRowDragEnd, onRowPointerDown, destroyRowDrag } = createRowDrag(ctx);
|
|
3825
4280
|
const { register: registerAlignedGrid, broadcastScroll: broadcastAlignedScroll, broadcastWidths: broadcastAlignedWidths } = createAlignedGrids(ctx);
|
|
3826
4281
|
const { buildApi } = createGridApi(ctx);
|
|
3827
|
-
const { readCellRaw, writeCellRaw, applyFillPattern, clearSelectedCellValues, startFillDrag, onFillPointerMove, onFillPointerUp, isOnMoveGrabStrip, startMoveDrag, onMovePointerMove, onMovePointerUp, applyMoveRange, moveDestRect, trackEdgeScroll, stopEdgeScroll, toggleBooleanCell, copySelectionToClipboard, clearSelectedCells, cutSelectionToClipboard } = createClipboard(ctx);
|
|
4282
|
+
const { readCellRaw, writeCellRaw, applyFillPattern, clearSelectedCellValues, startFillDrag, onFillPointerMove, onFillPointerUp, fillDownToNeighbour, isOnMoveGrabStrip, startMoveDrag, onMovePointerMove, onMovePointerUp, applyMoveRange, moveDestRect, trackEdgeScroll, stopEdgeScroll, toggleBooleanCell, copySelectionToClipboard, clearSelectedCells, cutSelectionToClipboard } = createClipboard(ctx);
|
|
3828
4283
|
// Dev-time configuration checks. Silent misconfiguration was the grid's
|
|
3829
4284
|
// biggest usability gap - a misspelled `field` rendered a column of blank
|
|
3830
4285
|
// cells and printed nothing, and an inert `pageSize` was simply ignored.
|