@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
|
@@ -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";
|
|
@@ -373,7 +381,12 @@ export function createSvGridController<
|
|
|
373
381
|
let pendingScrollTop = 0;
|
|
374
382
|
let pendingScrollLeft = 0;
|
|
375
383
|
let scrollSyncRaf: number | null = null;
|
|
376
|
-
|
|
384
|
+
// `$state.raw`: every writer replaces the whole object, and every rendered
|
|
385
|
+
// cell's selection derived reads it. Under a deep proxy each of those ~300
|
|
386
|
+
// deriveds tracked the nested anchor/focus row/col signals separately (six
|
|
387
|
+
// dependencies per cell to mark and re-check on every arrow key) where one
|
|
388
|
+
// will do.
|
|
389
|
+
let selectionRange = $state.raw<SelectionRange>({ anchor: null, focus: null });
|
|
377
390
|
// Extra committed ranges for multi-range (Ctrl+drag) selection. The
|
|
378
391
|
// `selectionRange` above is always the ACTIVE range being manipulated; these
|
|
379
392
|
// are the finished ones. Full selection = these + the active range.
|
|
@@ -418,20 +431,21 @@ export function createSvGridController<
|
|
|
418
431
|
// Full-row editing: the row currently in whole-row edit + its per-column
|
|
419
432
|
// draft (keyed by column id). Null when not in full-row mode.
|
|
420
433
|
let fullRowEdit = $state<{ rowId: string; draft: Record<string, unknown> } | null>(null);
|
|
421
|
-
|
|
434
|
+
// `$state.raw`, not `$state`: every writer replaces the whole object, and
|
|
435
|
+
// the render path asks `key in editedCellValues` once per rendered cell. On
|
|
436
|
+
// a deep proxy that `in` check, run inside an effect, creates and keeps a
|
|
437
|
+
// per-key signal for every cell that ever scrolls into view - a leak that
|
|
438
|
+
// grows with rows x columns and a Map lookup per cell per render.
|
|
439
|
+
let editedCellValues = $state.raw<Record<string, unknown>>({});
|
|
422
440
|
|
|
423
441
|
// ---- Undo / redo (history + pointer model) ---------------------------
|
|
424
442
|
// VSCode-style: one ordered history array, plus a pointer to the index
|
|
425
443
|
// of the NEXT undo step. Avoids the dual-stack edge cases where
|
|
426
444
|
// 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
|
-
}
|
|
445
|
+
// exported for the editing slice (undo/redo). The shape lives in history.ts
|
|
446
|
+
// so the controller, editing.ts and clipboard.ts cannot drift apart - they
|
|
447
|
+
// previously declared it twice and open-coded the push in three places.
|
|
448
|
+
type HistoryStep = SharedHistoryStep
|
|
435
449
|
const UNDO_LIMIT = 200
|
|
436
450
|
let history = $state<HistoryStep[]>([])
|
|
437
451
|
/** Index in `history` of the LAST applied step. -1 means "nothing applied".
|
|
@@ -441,6 +455,11 @@ export function createSvGridController<
|
|
|
441
455
|
/** Bumps on every undo / redo / record so $derived consumers can
|
|
442
456
|
* observe via the api without subscribing to history directly. */
|
|
443
457
|
let historyVersion = $state(0)
|
|
458
|
+
/** Set while a `runHistoryGroup` call is on the stack, so every step pushed
|
|
459
|
+
* inside one action shares a group id and undoes together. Deliberately not
|
|
460
|
+
* $state: it is written and read synchronously inside a single call and
|
|
461
|
+
* nothing renders from it. */
|
|
462
|
+
let historyGroupId: string | undefined = undefined
|
|
444
463
|
|
|
445
464
|
// ---- Hover tooltip (custom popover, not native title=) ---------------
|
|
446
465
|
// Triggered by per-column `tooltip` field OR per-cell `notes` prop.
|
|
@@ -479,6 +498,24 @@ export function createSvGridController<
|
|
|
479
498
|
let editorSelectAll = true;
|
|
480
499
|
/** Per-column width overrides set by the resize handles. */
|
|
481
500
|
let columnWidths = $state<Record<string, number>>({});
|
|
501
|
+
/**
|
|
502
|
+
* Columns folded to nothing, the way a sheet hides a column: the column
|
|
503
|
+
* keeps its index, its cells and its width for later, and takes no room.
|
|
504
|
+
* Distinct from `hiddenColumns`, which takes a column out of the model
|
|
505
|
+
* and shifts every index after it; a spreadsheet's formulas and formats
|
|
506
|
+
* are keyed by index and could not follow that.
|
|
507
|
+
*/
|
|
508
|
+
let collapsedColumns = $state<Record<string, boolean>>({});
|
|
509
|
+
function setColumnCollapsed(columnId: string, collapsed: boolean): void {
|
|
510
|
+
if (!!collapsedColumns[columnId] === collapsed) return;
|
|
511
|
+
if (collapsed) {
|
|
512
|
+
collapsedColumns = { ...collapsedColumns, [columnId]: true };
|
|
513
|
+
} else {
|
|
514
|
+
const next = { ...collapsedColumns };
|
|
515
|
+
delete next[columnId];
|
|
516
|
+
collapsedColumns = next;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
482
519
|
const MIN_COLUMN_WIDTH = 40;
|
|
483
520
|
/** Columns pinned to the left or right edge of the grid (sticky positioning).
|
|
484
521
|
* Seeded from `props.initialColumnPinning` so demos / tests can show the
|
|
@@ -570,26 +607,54 @@ export function createSvGridController<
|
|
|
570
607
|
const hb = column.columnDef?.hideBelow;
|
|
571
608
|
return hb != null && viewportWidth > 0 && viewportWidth < hb;
|
|
572
609
|
}
|
|
573
|
-
|
|
610
|
+
/**
|
|
611
|
+
* Live scroll geometry of the scroll container (feeds the custom
|
|
612
|
+
* scrollbars and `hasVerticalOverflow`).
|
|
613
|
+
*
|
|
614
|
+
* Measured from a plain `$effect`, NOT inside a `$derived`, on purpose.
|
|
615
|
+
* `scrollTop` / `scrollHeight` / `scrollWidth` reads force a synchronous
|
|
616
|
+
* layout whenever the tree is dirty. As a derived this was pulled by the
|
|
617
|
+
* shell's `class:sv-grid-has-vscroll` at the very start of every scroll
|
|
618
|
+
* flush - before the recycled rows had been rewritten - so it forced a
|
|
619
|
+
* full table layout that the row writes then invalidated again, and the
|
|
620
|
+
* browser laid the table out a second time before painting. Two table
|
|
621
|
+
* layouts per arrow key on a large grid. An effect runs after the flush
|
|
622
|
+
* has written the DOM, so the reads below land on a tree the browser has
|
|
623
|
+
* to lay out anyway, and the pre-paint pass is incremental.
|
|
624
|
+
*
|
|
625
|
+
* The version reads keep the same triggers the derived had: a scroll
|
|
626
|
+
* tick, a shell resize, and either virtualizer's window/size change (data
|
|
627
|
+
* load, row / column count change) - without those the scrollbar kept a
|
|
628
|
+
* stale `content-size` ≈ 0, its hidden-check tripped, it set
|
|
629
|
+
* `pointer-events: none`, and the user could not drag it.
|
|
630
|
+
*/
|
|
631
|
+
let scrollMetrics = $state.raw({
|
|
632
|
+
scrollTop: 0,
|
|
633
|
+
scrollLeft: 0,
|
|
634
|
+
scrollHeight: 0,
|
|
635
|
+
scrollWidth: 0,
|
|
636
|
+
});
|
|
637
|
+
$effect(function scrollMetrics_e() {
|
|
574
638
|
scrollVersion;
|
|
575
639
|
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
640
|
virtualizer.version;
|
|
584
641
|
columnVirtualizerVersion;
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
642
|
+
const el = scrollContainer;
|
|
643
|
+
if (!el) return;
|
|
644
|
+
const scrollTop = el.scrollTop;
|
|
645
|
+
const scrollLeft = el.scrollLeft;
|
|
646
|
+
const scrollHeight = el.scrollHeight;
|
|
647
|
+
const scrollWidth = el.scrollWidth;
|
|
648
|
+
const prev = untrack(() => scrollMetrics);
|
|
649
|
+
if (
|
|
650
|
+
prev.scrollTop === scrollTop &&
|
|
651
|
+
prev.scrollLeft === scrollLeft &&
|
|
652
|
+
prev.scrollHeight === scrollHeight &&
|
|
653
|
+
prev.scrollWidth === scrollWidth
|
|
654
|
+
) {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
scrollMetrics = { scrollTop, scrollLeft, scrollHeight, scrollWidth };
|
|
593
658
|
});
|
|
594
659
|
/** Vertical overflow from the virtualizer's authoritative total size,
|
|
595
660
|
* NOT from `scrollMetrics.scrollHeight` alone. Reading DOM dimensions
|
|
@@ -1697,8 +1762,14 @@ export function createSvGridController<
|
|
|
1697
1762
|
const paginationPageSize = $derived(
|
|
1698
1763
|
externalPaginationEnabled ? (props.pageSize ?? 10) : paginationState.pageSize,
|
|
1699
1764
|
);
|
|
1765
|
+
// Tracks `dataStateVersion`, which the store subscription bumps when the
|
|
1766
|
+
// rowSelection slice (among others) changes - NOT the catch-all
|
|
1767
|
+
// `gridStateVersion`, which also bumps on every active-cell move. Every
|
|
1768
|
+
// rendered row and cell reads this (`sv-grid-row-selected`, `aria-selected`),
|
|
1769
|
+
// so on the catch-all each arrow key re-marked and re-checked ~400 effects
|
|
1770
|
+
// to find the same object.
|
|
1700
1771
|
const rowSelectionState = $derived.by(function rowSelectionState_d() {
|
|
1701
|
-
|
|
1772
|
+
dataStateVersion;
|
|
1702
1773
|
return grid.getState().rowSelection ?? {};
|
|
1703
1774
|
});
|
|
1704
1775
|
|
|
@@ -2017,6 +2088,7 @@ export function createSvGridController<
|
|
|
2017
2088
|
let chartAiHandler = $state<
|
|
2018
2089
|
((prompt: string) => Promise<Record<string, unknown> | null>) | null
|
|
2019
2090
|
>(null);
|
|
2091
|
+
let chartExplainHandler = $state<(() => Promise<{ summary: string; insights: string[] } | null>) | null>(null);
|
|
2020
2092
|
|
|
2021
2093
|
// Multiple charts, each an independently-configured working set switched by a
|
|
2022
2094
|
// tab strip. Pickers / spec read the ACTIVE chart.
|
|
@@ -2024,7 +2096,7 @@ export function createSvGridController<
|
|
|
2024
2096
|
id: string;
|
|
2025
2097
|
title: string;
|
|
2026
2098
|
type: ChartType;
|
|
2027
|
-
reduce:
|
|
2099
|
+
reduce: ChartReducer;
|
|
2028
2100
|
dimensionId: string | null;
|
|
2029
2101
|
measureId: string | null;
|
|
2030
2102
|
seriesId: string | null | undefined;
|
|
@@ -2045,8 +2117,28 @@ export function createSvGridController<
|
|
|
2045
2117
|
logScale: boolean | null;
|
|
2046
2118
|
timeAxis: boolean | null;
|
|
2047
2119
|
valueFormat: ChartValueFormat | null;
|
|
2120
|
+
/** Calendar unit a date dimension is grouped by. Null = exact values. */
|
|
2121
|
+
bucket: ChartTimeBucket | null;
|
|
2122
|
+
/** Histogram bin count. Null = Sturges' rule. */
|
|
2123
|
+
bins: number | null;
|
|
2124
|
+
funnelShape: "trapezoid" | "pyramid" | "cone" | null;
|
|
2125
|
+
candleStyle: "classic" | "hollow" | "heikin-ashi" | null;
|
|
2126
|
+
/** The chart's zoom window (category index range), kept per tab. */
|
|
2127
|
+
zoom: ChartZoomWindow | null;
|
|
2128
|
+
/** Candlestick / OHLC: which columns hold the prices (column ids). Null
|
|
2129
|
+
* guesses from the column names. */
|
|
2130
|
+
ohlc: { open: string | null; high: string | null; low: string | null; close: string | null; volume: string | null } | null;
|
|
2131
|
+
/** Indicators: panes under the price (volume, rsi, macd, stochastic,
|
|
2132
|
+
* atr, obv) and overlays on it (sma, ema, bb, vwap). */
|
|
2133
|
+
indicators: ChartPanelIndicator[];
|
|
2134
|
+
/** The builder's Format tab: titles, axes, legend, per-series style. */
|
|
2135
|
+
format: ChartFormatState | null;
|
|
2136
|
+
/** An unlinked chart: the spec it was frozen with and when. */
|
|
2137
|
+
frozen: { spec: ChartSpec; at: string } | null;
|
|
2048
2138
|
};
|
|
2049
2139
|
let chartSeq = 0;
|
|
2140
|
+
const isZoomWindow = (v: unknown): v is ChartZoomWindow =>
|
|
2141
|
+
!!v && typeof v === "object" && Number.isInteger((v as ChartZoomWindow).i0) && Number.isInteger((v as ChartZoomWindow).i1);
|
|
2050
2142
|
const makeChart = (title: string): ChartTabState => ({
|
|
2051
2143
|
id: `chart-${chartSeq++}`,
|
|
2052
2144
|
title,
|
|
@@ -2067,10 +2159,49 @@ export function createSvGridController<
|
|
|
2067
2159
|
logScale: null,
|
|
2068
2160
|
timeAxis: null,
|
|
2069
2161
|
valueFormat: null,
|
|
2162
|
+
bucket: null,
|
|
2163
|
+
bins: null,
|
|
2164
|
+
funnelShape: null,
|
|
2165
|
+
candleStyle: null,
|
|
2166
|
+
zoom: null,
|
|
2167
|
+
ohlc: null,
|
|
2168
|
+
indicators: [],
|
|
2169
|
+
format: null,
|
|
2170
|
+
frozen: null,
|
|
2070
2171
|
});
|
|
2172
|
+
// The saved view (`GridState.charts[i]`) names four fields differently
|
|
2173
|
+
// from the tab; everything else is the tab's own key.
|
|
2174
|
+
const TAB_ALIASES: Record<string, string> = { dimensionId: "dimension", measureId: "measure", seriesId: "series", measure2Id: "measure2" };
|
|
2175
|
+
/** A tab as the saved view carries it, without the runtime id. */
|
|
2176
|
+
const tabToState = (c: ChartTabState): Record<string, unknown> => {
|
|
2177
|
+
const out: Record<string, unknown> = {};
|
|
2178
|
+
for (const k in c) if (k !== "id") out[TAB_ALIASES[k] ?? k] = k === "seriesId" ? (c.seriesId ?? null) : c[k as keyof ChartTabState];
|
|
2179
|
+
return out;
|
|
2180
|
+
};
|
|
2181
|
+
/** A tab from a saved-view snapshot: a fresh tab with every known field
|
|
2182
|
+
* the snapshot carries, and the shape-checked ones dropped when malformed. */
|
|
2183
|
+
const tabFromState = (c: Record<string, unknown>, i: number): ChartTabState => {
|
|
2184
|
+
const t = makeChart(typeof c.title === "string" ? c.title : `Chart ${i + 1}`) as unknown as Record<string, unknown>;
|
|
2185
|
+
for (const k in t) {
|
|
2186
|
+
const v = c[TAB_ALIASES[k] ?? k];
|
|
2187
|
+
if (k !== "id" && k !== "title" && v !== undefined) t[k] = v;
|
|
2188
|
+
}
|
|
2189
|
+
if (!isZoomWindow(t.zoom)) t.zoom = null;
|
|
2190
|
+
if (!Array.isArray(t.indicators)) t.indicators = [];
|
|
2191
|
+
if (typeof t.bins !== "number") t.bins = null;
|
|
2192
|
+
if (!t.frozen || typeof t.frozen !== "object" || !(t.frozen as { spec?: unknown }).spec) t.frozen = null;
|
|
2193
|
+
return t as unknown as ChartTabState;
|
|
2194
|
+
};
|
|
2071
2195
|
// svelte-ignore state_referenced_locally
|
|
2072
2196
|
let charts = $state<ChartTabState[]>([makeChart("Chart 1")]);
|
|
2073
2197
|
let activeChartIndex = $state(0);
|
|
2198
|
+
/**
|
|
2199
|
+
* Named chart configurations the user keeps: a tab's whole state under a
|
|
2200
|
+
* name, applied to any tab later. Part of the saved view (`GridState.savedCharts`)
|
|
2201
|
+
* only when there is one, so existing snapshots do not change.
|
|
2202
|
+
*/
|
|
2203
|
+
type SavedChartEntry = { name: string; savedAt: string; tab: Record<string, unknown> };
|
|
2204
|
+
let savedCharts = $state<SavedChartEntry[]>([]);
|
|
2074
2205
|
const activeChart = $derived(
|
|
2075
2206
|
(charts[Math.min(activeChartIndex, charts.length - 1)] ?? charts[0])!,
|
|
2076
2207
|
);
|
|
@@ -2083,6 +2214,10 @@ export function createSvGridController<
|
|
|
2083
2214
|
const chartDataLabels = $derived(activeChart.dataLabels);
|
|
2084
2215
|
const chartLogScale = $derived(activeChart.logScale);
|
|
2085
2216
|
const chartTimeAxis = $derived(activeChart.timeAxis);
|
|
2217
|
+
const chartBucket = $derived(activeChart.bucket);
|
|
2218
|
+
const chartBins = $derived(activeChart.bins);
|
|
2219
|
+
const chartFunnelShape = $derived(activeChart.funnelShape);
|
|
2220
|
+
const chartCandleStyle = $derived(activeChart.candleStyle);
|
|
2086
2221
|
const chartValueFormat = $derived(activeChart.valueFormat);
|
|
2087
2222
|
|
|
2088
2223
|
const asMeasureList = (m: string | string[] | undefined): string[] =>
|
|
@@ -2198,9 +2333,13 @@ export function createSvGridController<
|
|
|
2198
2333
|
const col = allColumns.find((c) => c.id === effectiveChartMeasureId);
|
|
2199
2334
|
const label = col ? columnLabel(col) : "";
|
|
2200
2335
|
if (!label) return undefined;
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
:
|
|
2336
|
+
const word: Record<string, string> = {
|
|
2337
|
+
sum: "Sum", avg: "Average", min: "Min", max: "Max", median: "Median",
|
|
2338
|
+
first: "First", last: "Last", countDistinct: "Distinct",
|
|
2339
|
+
};
|
|
2340
|
+
if (chartReduce === "count") return "Count";
|
|
2341
|
+
const w = word[chartReduce] ?? (chartReduce.startsWith("p") ? `P${chartReduce.slice(1)}` : "Sum");
|
|
2342
|
+
return `${w} of ${label}`;
|
|
2204
2343
|
});
|
|
2205
2344
|
/**
|
|
2206
2345
|
* Does this column hold dates? Declared type first, then a probe of the
|
|
@@ -2223,6 +2362,41 @@ export function createSvGridController<
|
|
|
2223
2362
|
const chartDimensionIsDate = $derived.by<boolean>(() =>
|
|
2224
2363
|
columnIsDate(allColumns.find((c) => c.id === effectiveChartDimensionId)),
|
|
2225
2364
|
);
|
|
2365
|
+
// The chart derivation engine (rowsToChartSpec) is loaded lazily the first time
|
|
2366
|
+
// charting is enabled, keeping chart.ts (~11 KB gzip) out of the base <SvGrid>
|
|
2367
|
+
// bundle - it rides in the same lazy chunk as the chart panel. `chartSpec` stays
|
|
2368
|
+
// null until it resolves, a tick after the panel itself starts loading (which it
|
|
2369
|
+
// already does). The `getAggregate` server path uses the local bucketsToSpec and
|
|
2370
|
+
// does not need the engine.
|
|
2371
|
+
// The whole module, not just `rowsToChartSpec`: the panel can now reach
|
|
2372
|
+
// every chart type, and the ones that do not read a categories-by-series
|
|
2373
|
+
// grid (scatter, gauge, treemap, calendar, sankey) need their own builders
|
|
2374
|
+
// from the same lazy chunk. Same `import()`, same boundary.
|
|
2375
|
+
let chartEngine = $state<typeof import("./chart") | null>(null);
|
|
2376
|
+
$effect(() => {
|
|
2377
|
+
if (chartingEnabled && !chartEngine)
|
|
2378
|
+
import("./chart").then((m) => (chartEngine = m));
|
|
2379
|
+
});
|
|
2380
|
+
|
|
2381
|
+
const chartIsOhlc = $derived(chartType === "candlestick" || chartType === "ohlc");
|
|
2382
|
+
/** The price columns: what the reader picked, else the engine's guess
|
|
2383
|
+
* from the column names (nothing until the engine chunk has loaded). */
|
|
2384
|
+
const effectiveChartOhlc = $derived<OhlcColumns>(
|
|
2385
|
+
chartEngine
|
|
2386
|
+
? chartEngine.guessOhlcColumns(chartableColumns.measures, activeChart.ohlc)
|
|
2387
|
+
: { open: null, high: null, low: null, close: null, volume: null, ...activeChart.ohlc },
|
|
2388
|
+
);
|
|
2389
|
+
/** The date column a candlestick reads: the picked dimension when it is a
|
|
2390
|
+
* date, else the first date column. */
|
|
2391
|
+
const effectiveChartOhlcDateId = $derived(
|
|
2392
|
+
chartDimensionIsDate ? effectiveChartDimensionId : (chartableColumns.dates[0]?.id ?? null),
|
|
2393
|
+
);
|
|
2394
|
+
const chartIndicators = $derived(activeChart.indicators);
|
|
2395
|
+
/** The panes under a price chart and the overlay on it, split by the engine. */
|
|
2396
|
+
const chartIndicatorSplit = $derived(
|
|
2397
|
+
chartIsOhlc && chartEngine ? chartEngine.splitPanelIndicators(chartIndicators) : { panes: [], overlay: null },
|
|
2398
|
+
);
|
|
2399
|
+
const chartIndicatorPanes = $derived(chartIndicatorSplit.panes);
|
|
2226
2400
|
const chartRows = $derived.by<TData[]>(() => {
|
|
2227
2401
|
const rects = getSelectionRects();
|
|
2228
2402
|
const pushLeaf = (out: TData[], row: Row<TData> | undefined) => {
|
|
@@ -2272,52 +2446,6 @@ export function createSvGridController<
|
|
|
2272
2446
|
if (seq === chartReqSeq) chartServerBuckets = [];
|
|
2273
2447
|
});
|
|
2274
2448
|
});
|
|
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
2449
|
/** Put the chart's numbers in the same locale as the grid's. A grid that set
|
|
2322
2450
|
* `localization.locale` has already said which locale its data is in, and a
|
|
2323
2451
|
* chart of that data reading in a different one is a bug nobody would think
|
|
@@ -2329,27 +2457,52 @@ export function createSvGridController<
|
|
|
2329
2457
|
if (chartCfg?.currency) spec.currency = chartCfg.currency;
|
|
2330
2458
|
};
|
|
2331
2459
|
|
|
2332
|
-
const
|
|
2460
|
+
const chartSpecLive = $derived.by<ChartSpec | null>(() => {
|
|
2333
2461
|
if (!chartingEnabled || !chartCfg) return null;
|
|
2334
2462
|
if (chartCfg.getAggregate) {
|
|
2335
|
-
if (!chartServerBuckets) return null;
|
|
2336
|
-
const spec =
|
|
2463
|
+
if (!chartServerBuckets || !chartEngine) return null;
|
|
2464
|
+
const spec = chartEngine.bucketsToChartSpec(chartServerBuckets, chartType, !!effectiveChartSeriesId);
|
|
2337
2465
|
if (effectiveChartStacked || chartCfg.stacked100) spec.stacked = true;
|
|
2338
2466
|
if (chartCfg.stacked100) spec.stacked100 = true;
|
|
2339
2467
|
if (chartCfg.palette) spec.palette = chartCfg.palette;
|
|
2340
2468
|
if (effectiveChartTimeAxis) spec.xType = "time";
|
|
2469
|
+
else if (chartBucket && chartDimensionIsDate) spec.xType = "ordinal-time";
|
|
2341
2470
|
if (effectiveChartLogScale) spec.yScale = "log";
|
|
2342
2471
|
if (effectiveChartValueFormat) spec.valueFormat = effectiveChartValueFormat;
|
|
2343
2472
|
applyChartLocale(spec);
|
|
2344
2473
|
return spec;
|
|
2345
2474
|
}
|
|
2346
2475
|
if (chartCfg.buildSpec) return chartCfg.buildSpec(chartRows) ?? null;
|
|
2476
|
+
// Engine not loaded yet (lazy): render nothing until it resolves.
|
|
2477
|
+
if (!chartEngine) return null;
|
|
2478
|
+
// Shared by the direct and aggregated paths so the two cannot drift apart
|
|
2479
|
+
// again - which is how these came to be skipped for scatter, gauge and
|
|
2480
|
+
// boxplot in the first place. Declared here, at the derived's own scope:
|
|
2481
|
+
// a function declaration inside the `else` block below is block-scoped,
|
|
2482
|
+
// and the aggregated path calls it after that block has closed.
|
|
2483
|
+
function applyChartValueSettings<T extends {
|
|
2484
|
+
xType?: string; yScale?: string; valueFormat?: unknown;
|
|
2485
|
+
}>(spec: T): T {
|
|
2486
|
+
if (effectiveChartTimeAxis) spec.xType = "time";
|
|
2487
|
+
if (effectiveChartLogScale) spec.yScale = "log";
|
|
2488
|
+
if (effectiveChartValueFormat) spec.valueFormat = effectiveChartValueFormat;
|
|
2489
|
+
applyChartLocale(spec as never);
|
|
2490
|
+
return spec;
|
|
2491
|
+
}
|
|
2347
2492
|
|
|
2493
|
+
// Pivot mode: the pivot on screen is the chart, rows as categories and
|
|
2494
|
+
// columns as series; the pickers have nothing to choose.
|
|
2495
|
+
let spec: ChartSpec;
|
|
2496
|
+
if (pivotResult) {
|
|
2497
|
+
spec = chartEngine.pivotResultToChartSpec(pivotResult, {
|
|
2498
|
+
type: chartType, stacked: effectiveChartStacked, stacked100: effectiveChartStacked100,
|
|
2499
|
+
measures: pivotConfig!.values,
|
|
2500
|
+
...(effectiveChartPalette ? { palette: effectiveChartPalette } : {}),
|
|
2501
|
+
});
|
|
2502
|
+
} else {
|
|
2348
2503
|
const category = fieldOf(effectiveChartDimensionId);
|
|
2349
2504
|
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;
|
|
2505
|
+
if (!chartIsOhlc && (!category || !values.length)) return null;
|
|
2353
2506
|
const seriesField = fieldOf(effectiveChartSeriesId);
|
|
2354
2507
|
const rowsForChart = chartRows as Array<Record<string, unknown>>;
|
|
2355
2508
|
|
|
@@ -2360,26 +2513,51 @@ export function createSvGridController<
|
|
|
2360
2513
|
// base bundle, which every grid pays for whether or not it ever charts.
|
|
2361
2514
|
// Everything else falls through to the aggregation below and is reshaped
|
|
2362
2515
|
// afterwards, so reduce / sort / topN / "Other" keep working for all of it.
|
|
2516
|
+
const ohlcOpts = chartIsOhlc
|
|
2517
|
+
? chartEngine.ohlcDirectOptions(fieldOf, effectiveChartOhlcDateId, effectiveChartOhlc, { bucket: chartBucket, candleStyle: chartCandleStyle })
|
|
2518
|
+
: null;
|
|
2363
2519
|
const direct = chartEngine.rowsToDirectSpec(chartType, rowsForChart, {
|
|
2364
|
-
|
|
2520
|
+
...(ohlcOpts ?? {}),
|
|
2521
|
+
category: ohlcOpts?.category ?? (category as string),
|
|
2365
2522
|
...(values[0] ? { value: values[0] } : {}),
|
|
2366
2523
|
...(fieldOf(effectiveChartMeasure2Id) ? { value2: fieldOf(effectiveChartMeasure2Id)! } : {}),
|
|
2367
2524
|
...(seriesField ? { series: seriesField } : {}),
|
|
2368
2525
|
reduce: chartReduce,
|
|
2526
|
+
...(chartBins ? { bins: chartBins } : {}),
|
|
2369
2527
|
...(effectiveChartPalette ? { palette: effectiveChartPalette } : {}),
|
|
2370
2528
|
});
|
|
2371
|
-
|
|
2529
|
+
// Number formatting and the scale/axis-type settings are about how values
|
|
2530
|
+
// READ, so they apply to a scatter or a gauge exactly as they do to a bar
|
|
2531
|
+
// chart. They used to live only on the aggregated path below, so setting
|
|
2532
|
+
// `valueFormat` or `logScale` on a direct type silently did nothing.
|
|
2533
|
+
//
|
|
2534
|
+
// The series-shaped settings (trend, averageLine, seriesTypes) stay on the
|
|
2535
|
+
// aggregated path: a direct spec does not have the series shape they read.
|
|
2536
|
+
if (direct) {
|
|
2537
|
+
if (chartIsOhlc) {
|
|
2538
|
+
// Overlays ride on the price series; the panes are composed by the
|
|
2539
|
+
// panel body from `chartIndicatorPanes`.
|
|
2540
|
+
const overlay = chartIndicatorSplit.overlay;
|
|
2541
|
+
if (overlay && direct.series[0]) direct.series[0].overlay = overlay;
|
|
2542
|
+
}
|
|
2543
|
+
return applyChartValueSettings(direct);
|
|
2544
|
+
}
|
|
2372
2545
|
// A direct type that could not build (scatter with no Y picked yet, for
|
|
2373
2546
|
// instance) must not fall through to the bar-chart path and draw the wrong
|
|
2374
2547
|
// chart - it renders the panel's empty state instead.
|
|
2375
|
-
if (
|
|
2548
|
+
if (
|
|
2549
|
+
chartType === "scatter" || chartType === "gauge" || chartType === "boxplot" ||
|
|
2550
|
+
chartType === "histogram" || chartType === "range-bar" || chartType === "range-area" ||
|
|
2551
|
+
chartType === "dumbbell" || chartType === "bullet" || chartIsOhlc
|
|
2552
|
+
) return null;
|
|
2376
2553
|
|
|
2377
|
-
|
|
2554
|
+
spec = chartEngine.rowsToChartSpec<Record<string, unknown>>(rowsForChart, {
|
|
2378
2555
|
type: chartType,
|
|
2379
2556
|
category: category as string,
|
|
2380
2557
|
value: values.length === 1 ? values[0]! : values,
|
|
2381
2558
|
...(seriesField ? { series: seriesField } : {}),
|
|
2382
2559
|
reduce: chartReduce,
|
|
2560
|
+
...(chartBucket && chartDimensionIsDate ? { bucket: chartBucket } : {}),
|
|
2383
2561
|
stacked: effectiveChartStacked || effectiveChartStacked100,
|
|
2384
2562
|
stacked100: effectiveChartStacked100,
|
|
2385
2563
|
...(effectiveChartPalette ? { palette: effectiveChartPalette } : {}),
|
|
@@ -2388,10 +2566,16 @@ export function createSvGridController<
|
|
|
2388
2566
|
...(chartCfg.sort ? { sort: chartCfg.sort } : {}),
|
|
2389
2567
|
});
|
|
2390
2568
|
|
|
2569
|
+
}
|
|
2391
2570
|
// Reshape the aggregated grid into whatever this type actually reads.
|
|
2392
2571
|
// These run AFTER the aggregation so grouping, reduce, sort, topN and the
|
|
2393
2572
|
// "Other" bucket apply to them exactly as they do to a bar chart.
|
|
2394
|
-
if (chartType === "treemap") spec.treemap = chartEngine.specToTreemap(spec);
|
|
2573
|
+
if (chartType === "treemap" || chartType === "sunburst") spec.treemap = chartEngine.specToTreemap(spec);
|
|
2574
|
+
else if (chartType === "chord") {
|
|
2575
|
+
const flow = chartEngine.specToSankey(spec);
|
|
2576
|
+
spec.sankeyNodes = flow.nodes;
|
|
2577
|
+
spec.sankeyLinks = flow.links;
|
|
2578
|
+
}
|
|
2395
2579
|
else if (chartType === "calendar") spec.calendarValues = chartEngine.specToCalendar(spec);
|
|
2396
2580
|
else if (chartType === "sankey") {
|
|
2397
2581
|
const flow = chartEngine.specToSankey(spec);
|
|
@@ -2400,10 +2584,7 @@ export function createSvGridController<
|
|
|
2400
2584
|
}
|
|
2401
2585
|
|
|
2402
2586
|
if (effectiveChartOrientation === "horizontal") spec.orientation = "horizontal";
|
|
2403
|
-
|
|
2404
|
-
if (effectiveChartLogScale) spec.yScale = "log";
|
|
2405
|
-
if (effectiveChartValueFormat) spec.valueFormat = effectiveChartValueFormat;
|
|
2406
|
-
applyChartLocale(spec);
|
|
2587
|
+
applyChartValueSettings(spec);
|
|
2407
2588
|
if (chartType !== "pie" && spec.orientation !== "horizontal" && !spec.yAxisTitle && chartAutoYAxisTitle) {
|
|
2408
2589
|
spec.yAxisTitle = chartAutoYAxisTitle;
|
|
2409
2590
|
}
|
|
@@ -2413,6 +2594,11 @@ export function createSvGridController<
|
|
|
2413
2594
|
}
|
|
2414
2595
|
if (chartCfg.annotations) spec.annotations = chartCfg.annotations;
|
|
2415
2596
|
if (chartCfg.patternFallback) spec.patternFallback = true;
|
|
2597
|
+
if (chartFunnelShape && chartType === "funnel") spec.funnelShape = chartFunnelShape;
|
|
2598
|
+
if (chartCandleStyle && (chartType === "candlestick" || chartType === "ohlc")) spec.candleStyle = chartCandleStyle;
|
|
2599
|
+
// A stream is a stacked area on a wiggle baseline; the picker's Stacked
|
|
2600
|
+
// box has nothing to add, so the offset is applied here outright.
|
|
2601
|
+
if (chartType === "stream") { spec.stacked = true; spec.stackOffset = "wiggle"; }
|
|
2416
2602
|
const refLines = [...(chartCfg.referenceLines ?? [])];
|
|
2417
2603
|
if (chartCfg.averageLine && spec.series[0]?.values.length) {
|
|
2418
2604
|
const vals = spec.series[0].values;
|
|
@@ -2421,7 +2607,10 @@ export function createSvGridController<
|
|
|
2421
2607
|
}
|
|
2422
2608
|
if (refLines.length) spec.referenceLines = refLines;
|
|
2423
2609
|
for (const s of spec.series) {
|
|
2424
|
-
if (chartCfg.trend)
|
|
2610
|
+
if (chartCfg.trend) {
|
|
2611
|
+
const t = chartCfg.trend;
|
|
2612
|
+
s.overlay = t === "sma" || t === "ema" ? `${t}:7` : t === "poly" ? "poly:2" : t;
|
|
2613
|
+
}
|
|
2425
2614
|
if (chartCfg.smooth) s.smooth = true;
|
|
2426
2615
|
if (chartCfg.seriesTypes?.[s.label]) s.type = chartCfg.seriesTypes[s.label];
|
|
2427
2616
|
if (chartCfg.seriesAxes?.[s.label]) s.axis = chartCfg.seriesAxes[s.label];
|
|
@@ -2429,6 +2618,43 @@ export function createSvGridController<
|
|
|
2429
2618
|
return spec;
|
|
2430
2619
|
});
|
|
2431
2620
|
|
|
2621
|
+
/**
|
|
2622
|
+
* What the panel renders: an unlinked chart's frozen snapshot, else the live
|
|
2623
|
+
* derivation with the builder's format applied. The format runs through
|
|
2624
|
+
* the lazy engine so its code stays out of the base bundle.
|
|
2625
|
+
*/
|
|
2626
|
+
const chartSpec = $derived.by<ChartSpec | null>(() => {
|
|
2627
|
+
if (activeChart.frozen) return activeChart.frozen.spec;
|
|
2628
|
+
const live = chartSpecLive;
|
|
2629
|
+
if (!live || !activeChart.format || !chartEngine) return live;
|
|
2630
|
+
return chartEngine.applyChartFormat(live, activeChart.format);
|
|
2631
|
+
});
|
|
2632
|
+
let announcedFirstChart = false;
|
|
2633
|
+
$effect(() => {
|
|
2634
|
+
if (!chartPanelOpen || announcedFirstChart || !chartCfg?.onChartCreated) return;
|
|
2635
|
+
announcedFirstChart = true;
|
|
2636
|
+
const c = charts[0]!;
|
|
2637
|
+
untrack(() => chartCfg?.onChartCreated?.({ index: 0, title: c.title, type: c.type, spec: null }));
|
|
2638
|
+
});
|
|
2639
|
+
// Lifecycle: one `onChartChanged` per burst of spec changes.
|
|
2640
|
+
let chartChangeTimer: ReturnType<typeof setTimeout> | null = null;
|
|
2641
|
+
$effect(() => {
|
|
2642
|
+
const cb = chartCfg?.onChartChanged;
|
|
2643
|
+
const spec = chartSpec;
|
|
2644
|
+
const index = activeChartIndex;
|
|
2645
|
+
if (!cb) return;
|
|
2646
|
+
untrack(() => {
|
|
2647
|
+
if (chartChangeTimer) clearTimeout(chartChangeTimer);
|
|
2648
|
+
chartChangeTimer = setTimeout(() => {
|
|
2649
|
+
chartChangeTimer = null;
|
|
2650
|
+
const c = charts[index] ?? activeChart;
|
|
2651
|
+
cb({ index, title: c.title, type: c.type, spec });
|
|
2652
|
+
}, 150);
|
|
2653
|
+
});
|
|
2654
|
+
return () => {
|
|
2655
|
+
if (chartChangeTimer) { clearTimeout(chartChangeTimer); chartChangeTimer = null; }
|
|
2656
|
+
};
|
|
2657
|
+
});
|
|
2432
2658
|
/**
|
|
2433
2659
|
* Can a click on this chart be turned back into a grid filter?
|
|
2434
2660
|
*
|
|
@@ -2438,12 +2664,19 @@ export function createSvGridController<
|
|
|
2438
2664
|
* at all), or a scatter (the mark carries a point label, not a group).
|
|
2439
2665
|
*/
|
|
2440
2666
|
const chartCrossFilterable = $derived(
|
|
2441
|
-
chartType !== "sankey" && chartType !== "gauge" && chartType !== "scatter"
|
|
2667
|
+
chartType !== "sankey" && chartType !== "gauge" && chartType !== "scatter" &&
|
|
2668
|
+
// A chord's arcs are both ends of a flow, a sunburst's are a hierarchy
|
|
2669
|
+
// and a histogram's are bins: none maps a click back to one dimension value.
|
|
2670
|
+
chartType !== "chord" && chartType !== "sunburst" && chartType !== "histogram",
|
|
2442
2671
|
);
|
|
2443
2672
|
|
|
2673
|
+
/** The column a chart click filters: the picked dimension, or in pivot mode the innermost row dimension when it is a grid column. */
|
|
2674
|
+
const chartFilterDimId = $derived(
|
|
2675
|
+
pivotResult ? (chartEngine?.pivotFilterColumn(pivotConfig!.rows, chartableColumns.dims, fieldOf) ?? null) : effectiveChartDimensionId,
|
|
2676
|
+
);
|
|
2444
2677
|
function applyChartCrossFilter(category: string) {
|
|
2445
2678
|
if (!chartCrossFilterable) return;
|
|
2446
|
-
const dimId =
|
|
2679
|
+
const dimId = chartFilterDimId;
|
|
2447
2680
|
if (!dimId) return;
|
|
2448
2681
|
const existing = valueFilters[dimId];
|
|
2449
2682
|
const next = new Set(existing && existing.has(category) ? existing : []);
|
|
@@ -2452,7 +2685,7 @@ export function createSvGridController<
|
|
|
2452
2685
|
valueFilters = { ...valueFilters, [dimId]: next };
|
|
2453
2686
|
}
|
|
2454
2687
|
function clearChartCrossFilter() {
|
|
2455
|
-
const dimId =
|
|
2688
|
+
const dimId = chartFilterDimId;
|
|
2456
2689
|
if (!dimId) return;
|
|
2457
2690
|
const next = { ...valueFilters };
|
|
2458
2691
|
delete next[dimId];
|
|
@@ -2647,9 +2880,46 @@ export function createSvGridController<
|
|
|
2647
2880
|
const logical = virtualizer.getState().scrollOffset;
|
|
2648
2881
|
return logical - rowScrollScaling.logicalToDom(logical);
|
|
2649
2882
|
});
|
|
2883
|
+
/**
|
|
2884
|
+
* The first `frozenRows` rows are rendered ahead of the virtual window
|
|
2885
|
+
* and stick under the header. The virtualizer still counts them, so its
|
|
2886
|
+
* offsets stay in row-index space and nothing downstream has to translate;
|
|
2887
|
+
* the window skips them, and the top spacer gives back the height they
|
|
2888
|
+
* already occupy in the flow.
|
|
2889
|
+
*/
|
|
2890
|
+
const frozenRowCount = $derived(
|
|
2891
|
+
Math.max(0, Math.min(Math.floor(props.frozenRows ?? 0), allRows.length)),
|
|
2892
|
+
);
|
|
2893
|
+
const frozenRowList = $derived(frozenRowCount ? allRows.slice(0, frozenRowCount) : []);
|
|
2894
|
+
/** The merged cells, indexed; null (the usual case) costs nothing. */
|
|
2895
|
+
const mergeIndex = $derived(buildMergeIndex(props.mergedCells));
|
|
2896
|
+
const frozenBandHeight = $derived.by(() => {
|
|
2897
|
+
if (!frozenRowCount) return 0;
|
|
2898
|
+
rowResizeVersion;
|
|
2899
|
+
autoRowHeightVersion;
|
|
2900
|
+
let total = 0;
|
|
2901
|
+
for (let i = 0; i < frozenRowCount; i += 1) total += rowSizePxOf(i);
|
|
2902
|
+
return total;
|
|
2903
|
+
});
|
|
2904
|
+
/** The declared or dragged height of one row; measured under auto height. */
|
|
2905
|
+
function rowSizePxOf(i: number): number {
|
|
2906
|
+
if (isRowCollapsed(i)) return 0;
|
|
2907
|
+
if (autoRowHeightOn) return measuredRowHeights.get(i) ?? autoRowHeightFallback;
|
|
2908
|
+
const own = rowResizeHeightPx(i);
|
|
2909
|
+
if (own != null) return own;
|
|
2910
|
+
const rh = props.rowHeight;
|
|
2911
|
+
return typeof rh === "function" ? rh(i) : (rh ?? 30);
|
|
2912
|
+
}
|
|
2913
|
+
/** Where frozen row i sticks: under the header and the frozen rows above it. */
|
|
2914
|
+
function frozenRowTop(i: number): number {
|
|
2915
|
+
let top = headerHeight;
|
|
2916
|
+
for (let k = 0; k < i; k += 1) top += rowSizePxOf(k);
|
|
2917
|
+
return top;
|
|
2918
|
+
}
|
|
2650
2919
|
// Spacer heights in DOM space. With scaling inert these equal the original
|
|
2651
|
-
// virtualRowStart / virtualRowBottomSpacer.
|
|
2652
|
-
|
|
2920
|
+
// virtualRowStart / virtualRowBottomSpacer. The frozen band is in the flow
|
|
2921
|
+
// before the spacer whatever the window shows, so it comes off the spacer.
|
|
2922
|
+
const rowTopSpacer = $derived(Math.max(virtualRowStart - rowOffsetAdjustment - frozenBandHeight, 0));
|
|
2653
2923
|
const rowBottomSpacer = $derived(
|
|
2654
2924
|
Math.max(rowDomTotalSize - (virtualRowEnd - rowOffsetAdjustment), 0),
|
|
2655
2925
|
);
|
|
@@ -3003,35 +3273,104 @@ export function createSvGridController<
|
|
|
3003
3273
|
);
|
|
3004
3274
|
|
|
3005
3275
|
// ----- Interactive row resize (`rowResize` prop) -----
|
|
3006
|
-
// Heights the user has dragged,
|
|
3007
|
-
//
|
|
3008
|
-
//
|
|
3009
|
-
//
|
|
3010
|
-
//
|
|
3011
|
-
|
|
3276
|
+
// Heights the user has dragged, or set through `api.setRowHeight`, keyed
|
|
3277
|
+
// by ROW ID so they belong to the row rather than to a slot: the height
|
|
3278
|
+
// follows its row through a sort or a filter and survives the data being
|
|
3279
|
+
// replaced. They used to be keyed by index and wiped on every data change,
|
|
3280
|
+
// which in a spreadsheet meant every keystroke, so a row dragged taller
|
|
3281
|
+
// snapped back the moment the next cell was typed. A plain Map plus a
|
|
3282
|
+
// version counter, like `measuredRowHeights` above: the readers touch the
|
|
3283
|
+
// version, and the Map itself stays out of the reactive graph.
|
|
3284
|
+
const rowResizeHeights = new Map<string, number>();
|
|
3012
3285
|
let rowResizeVersion = $state(0);
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3286
|
+
/**
|
|
3287
|
+
* Rows folded to nothing, the row-side twin of `collapsedColumns`: the
|
|
3288
|
+
* row keeps its index and its cells and takes no height. Keyed by row id
|
|
3289
|
+
* like the heights and announced through the same version counter, so
|
|
3290
|
+
* every reader of a row's size sees the change.
|
|
3291
|
+
*/
|
|
3292
|
+
const collapsedRows = new Set<string>();
|
|
3293
|
+
function isRowCollapsed(index: number): boolean {
|
|
3294
|
+
rowResizeVersion;
|
|
3295
|
+
const id = allRows[index]?.id;
|
|
3296
|
+
return id != null && collapsedRows.has(id);
|
|
3297
|
+
}
|
|
3298
|
+
function setRowCollapsed(index: number, collapsed: boolean): void {
|
|
3299
|
+
const id = allRows[index]?.id;
|
|
3300
|
+
if (id == null) return;
|
|
3301
|
+
if (collapsed === collapsedRows.has(id)) return;
|
|
3302
|
+
if (collapsed) collapsedRows.add(id);
|
|
3303
|
+
else collapsedRows.delete(id);
|
|
3017
3304
|
rowResizeVersion += 1;
|
|
3018
3305
|
}
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3306
|
+
|
|
3307
|
+
/**
|
|
3308
|
+
* A size the user changed by hand is one undo step, as it is in a
|
|
3309
|
+
* spreadsheet: Ctrl+Z after a column drag puts the width back. Recorded
|
|
3310
|
+
* only for the user's gestures; the api's own setters record nothing, so
|
|
3311
|
+
* a consumer restoring saved sizes does not fill the history.
|
|
3312
|
+
*/
|
|
3313
|
+
function recordSizeUndo(undo: () => void, redo: () => void): void {
|
|
3314
|
+
pushHistory(ctx, [
|
|
3315
|
+
{ rowId: "", columnId: "", field: "", before: undefined, after: undefined, custom: { undo, redo } },
|
|
3316
|
+
]);
|
|
3317
|
+
}
|
|
3318
|
+
// The undo and redo of a size step report through the same callbacks as
|
|
3319
|
+
// the gesture did: a consumer keeping the sizes (a sheet saving its
|
|
3320
|
+
// document) would otherwise hold the width the user dragged to while the
|
|
3321
|
+
// grid showed the one Ctrl+Z put back.
|
|
3322
|
+
function resizeColumnByUser(columnId: string, width: number, before: number): void {
|
|
3323
|
+
const apply = (w: number) => {
|
|
3324
|
+
columnWidths = { ...columnWidths, [columnId]: w };
|
|
3325
|
+
props.onColumnResize?.({ columnId, width: w });
|
|
3326
|
+
};
|
|
3327
|
+
apply(width);
|
|
3328
|
+
if (before !== width) recordSizeUndo(() => apply(before), () => apply(width));
|
|
3329
|
+
}
|
|
3330
|
+
function autosizeColumnByUser(columnId: string): void {
|
|
3331
|
+
const before = getColumnWidth(columnId);
|
|
3332
|
+
autosizeColumn(columnId);
|
|
3333
|
+
const width = getColumnWidth(columnId);
|
|
3334
|
+
const apply = (w: number) => {
|
|
3335
|
+
columnWidths = { ...columnWidths, [columnId]: w };
|
|
3336
|
+
props.onColumnResize?.({ columnId, width: w });
|
|
3337
|
+
};
|
|
3338
|
+
if (before !== width) recordSizeUndo(() => apply(before), () => apply(width));
|
|
3339
|
+
else props.onColumnResize?.({ columnId, width });
|
|
3340
|
+
}
|
|
3341
|
+
function resizeRowByUser(index: number, height: number | null): void {
|
|
3342
|
+
const before = rowResizeHeightPx(index) ?? null;
|
|
3343
|
+
const apply = (h: number | null) => {
|
|
3344
|
+
setRowResizeHeight(index, h);
|
|
3345
|
+
props.onRowResize?.({ rowIndex: index, height: h });
|
|
3346
|
+
};
|
|
3347
|
+
apply(height);
|
|
3348
|
+
if (before !== height) recordSizeUndo(() => apply(before), () => apply(height));
|
|
3023
3349
|
}
|
|
3024
3350
|
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3351
|
+
function setRowResizeHeight(index: number, height: number | null): void {
|
|
3352
|
+
const id = allRows[index]?.id;
|
|
3353
|
+
if (id == null) return;
|
|
3354
|
+
if (height == null) {
|
|
3355
|
+
if (!rowResizeHeights.delete(id)) return;
|
|
3356
|
+
} else {
|
|
3357
|
+
if (!Number.isFinite(height) || height <= 0) return;
|
|
3358
|
+
rowResizeHeights.set(id, Math.round(height));
|
|
3359
|
+
}
|
|
3033
3360
|
rowResizeVersion += 1;
|
|
3034
|
-
}
|
|
3361
|
+
}
|
|
3362
|
+
/** A row's own height, or undefined when it has none. Read by the view AND
|
|
3363
|
+
* by the virtualizer's `estimateSize`. Auto height wins over it: the
|
|
3364
|
+
* content decides the height there, and a manual one would be overwritten
|
|
3365
|
+
* by the next measurement. */
|
|
3366
|
+
function rowResizeHeightPx(index: number): number | undefined {
|
|
3367
|
+
const id = allRows[index]?.id;
|
|
3368
|
+
if (id == null) return undefined;
|
|
3369
|
+
// A collapsed row is 0 whatever else it has, under auto height too.
|
|
3370
|
+
if (collapsedRows.has(id)) return 0;
|
|
3371
|
+
if (autoRowHeightOn) return undefined;
|
|
3372
|
+
return rowResizeHeights.get(id);
|
|
3373
|
+
}
|
|
3035
3374
|
|
|
3036
3375
|
/** Report a row's measured height. Called by the view's measuring action.
|
|
3037
3376
|
* Sub-pixel churn is ignored so a fractional layout can't loop. */
|
|
@@ -3056,11 +3395,19 @@ export function createSvGridController<
|
|
|
3056
3395
|
function measureRowHeight(node: HTMLElement, index: number) {
|
|
3057
3396
|
let current = index;
|
|
3058
3397
|
let stop: (() => void) | null = null;
|
|
3059
|
-
|
|
3398
|
+
// Read the rect only under autoRowHeight. `getBoundingClientRect` forces a
|
|
3399
|
+
// synchronous layout, and the virtualizer's slot keys hand every rendered
|
|
3400
|
+
// <tr> a new index on each one-row scroll, so measuring unconditionally
|
|
3401
|
+
// meant ~30 forced layouts per arrow key on a fixed-height grid whose
|
|
3402
|
+
// measurement `reportRowHeight` then threw away.
|
|
3403
|
+
const measure = () => {
|
|
3404
|
+
if (!autoRowHeightOn) return;
|
|
3405
|
+
reportRowHeight(current, node.getBoundingClientRect().height);
|
|
3406
|
+
};
|
|
3060
3407
|
const attach = () => {
|
|
3061
3408
|
stop?.();
|
|
3062
3409
|
stop = autoRowHeightOn ? observeSizeRaf(node, measure) : null;
|
|
3063
|
-
|
|
3410
|
+
measure();
|
|
3064
3411
|
};
|
|
3065
3412
|
attach();
|
|
3066
3413
|
return {
|
|
@@ -3112,9 +3459,9 @@ export function createSvGridController<
|
|
|
3112
3459
|
: typeof rh === "function"
|
|
3113
3460
|
? rh
|
|
3114
3461
|
: (rh ?? 30);
|
|
3115
|
-
const estimateSize =
|
|
3462
|
+
const estimateSize = rowResizeHeights.size > 0 || collapsedRows.size > 0
|
|
3116
3463
|
? (index: number) =>
|
|
3117
|
-
|
|
3464
|
+
rowResizeHeightPx(index) ??
|
|
3118
3465
|
(typeof base === "function" ? base(index) : base)
|
|
3119
3466
|
: base;
|
|
3120
3467
|
virtualizer.setOptions({
|
|
@@ -3175,6 +3522,7 @@ export function createSvGridController<
|
|
|
3175
3522
|
// closure each run; the virtualizer sees a new function reference and
|
|
3176
3523
|
// re-derives its layout from the current per-column widths.
|
|
3177
3524
|
columnWidths;
|
|
3525
|
+
collapsedColumns;
|
|
3178
3526
|
columnVirtualizer.setOptions({
|
|
3179
3527
|
count: allColumns.length,
|
|
3180
3528
|
estimateSize: (index: number) => {
|
|
@@ -3243,8 +3591,11 @@ export function createSvGridController<
|
|
|
3243
3591
|
|
|
3244
3592
|
|
|
3245
3593
|
|
|
3594
|
+
// Depends on `allRows` and `rowSelectionState` only. It used to read the
|
|
3595
|
+
// catch-all `gridStateVersion` too, which made every arrow key walk all the
|
|
3596
|
+
// rows (10k iterations on the large-dataset demo, 100k on its top tier) to
|
|
3597
|
+
// recount a selection that had not changed.
|
|
3246
3598
|
const headerSelectionState = $derived.by(function headerSelectionState_d() {
|
|
3247
|
-
gridStateVersion;
|
|
3248
3599
|
const selectable = allRows.filter((row) => !isGroupRow(row));
|
|
3249
3600
|
if (!selectable.length) return "none";
|
|
3250
3601
|
let selected = 0;
|
|
@@ -3261,6 +3612,10 @@ export function createSvGridController<
|
|
|
3261
3612
|
* from a user-focused (0,0). The fill handle keys off this flag so it
|
|
3262
3613
|
* stays hidden until the user actually selects something. */
|
|
3263
3614
|
let userHasActivatedCell = $state(false);
|
|
3615
|
+
/** The column a run of Tabs began in, so the Enter that ends the run goes
|
|
3616
|
+
* down from there (`getEntryStep`). Plain, not state: nothing renders
|
|
3617
|
+
* it. `setActiveCell` clears it; the Tab and Enter paths set it back. */
|
|
3618
|
+
let tabRunOrigin: number | null = null;
|
|
3264
3619
|
|
|
3265
3620
|
|
|
3266
3621
|
|
|
@@ -3286,7 +3641,7 @@ export function createSvGridController<
|
|
|
3286
3641
|
if (!props.fitColumns || isNarrowResponsive) return null;
|
|
3287
3642
|
// Exclude columns hidden by a collapsed group too (#57) - otherwise their
|
|
3288
3643
|
// 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]);
|
|
3644
|
+
const cols = grid.getAllColumns().filter((c) => !hiddenColumns[c.id] && !hiddenByGroupCollapse[c.id] && !collapsedColumns[c.id]);
|
|
3290
3645
|
if (!cols.length) return null;
|
|
3291
3646
|
const rowNumberWidth = showRowNumbersEffective ? rowNumberColumnWidth : 0;
|
|
3292
3647
|
const selectionWidth = showRowSelectionEffective ? selectionColumnWidth : 0;
|
|
@@ -3712,12 +4067,22 @@ export function createSvGridController<
|
|
|
3712
4067
|
// state mutation inside the callback (e.g. `api.setGroupBy(...)`) created
|
|
3713
4068
|
// an infinite update loop. Now it's a true mount-once notification.
|
|
3714
4069
|
let apiNotified = false;
|
|
4070
|
+
// Memoized: `buildApi()` builds a fresh object each call, and a feature pack
|
|
4071
|
+
// that keys per-grid state on the api (a WeakMap in @svgrid/enterprise) needs
|
|
4072
|
+
// the object the consumer received and the object a command handler sees to
|
|
4073
|
+
// be the same one. Every method reads through `ctx`, so one instance stays
|
|
4074
|
+
// correct for the life of the grid.
|
|
4075
|
+
let apiInstance: ReturnType<typeof buildApi> | null = null;
|
|
4076
|
+
function api() {
|
|
4077
|
+
if (!apiInstance) apiInstance = buildApi();
|
|
4078
|
+
return apiInstance;
|
|
4079
|
+
}
|
|
3715
4080
|
$effect(() => {
|
|
3716
4081
|
if (apiNotified) return;
|
|
3717
4082
|
const cb = props.onApiReady;
|
|
3718
4083
|
if (!cb) return;
|
|
3719
4084
|
apiNotified = true;
|
|
3720
|
-
cb(
|
|
4085
|
+
cb(api());
|
|
3721
4086
|
});
|
|
3722
4087
|
|
|
3723
4088
|
const ctx = {
|
|
@@ -3780,6 +4145,8 @@ export function createSvGridController<
|
|
|
3780
4145
|
set history(v) { history = v as never; },
|
|
3781
4146
|
get historyPtr() { return historyPtr; },
|
|
3782
4147
|
set historyPtr(v) { historyPtr = v as never; },
|
|
4148
|
+
get historyGroupId() { return historyGroupId; },
|
|
4149
|
+
set historyGroupId(v) { historyGroupId = v as never; },
|
|
3783
4150
|
get historyVersion() { return historyVersion; },
|
|
3784
4151
|
set historyVersion(v) { historyVersion = v as never; },
|
|
3785
4152
|
get tooltip() { return tooltip; },
|
|
@@ -3802,6 +4169,9 @@ export function createSvGridController<
|
|
|
3802
4169
|
get editorSelectAll() { return editorSelectAll; },
|
|
3803
4170
|
set editorSelectAll(v) { editorSelectAll = v as never; },
|
|
3804
4171
|
get columnWidths() { return columnWidths; },
|
|
4172
|
+
get collapsedColumns() { return collapsedColumns; },
|
|
4173
|
+
get mergeIndex() { return mergeIndex; },
|
|
4174
|
+
get setColumnCollapsed() { return setColumnCollapsed; },
|
|
3805
4175
|
set columnWidths(v) { columnWidths = v as never; },
|
|
3806
4176
|
get MIN_COLUMN_WIDTH() { return MIN_COLUMN_WIDTH; },
|
|
3807
4177
|
get columnPinning() { return columnPinning; },
|
|
@@ -3873,6 +4243,7 @@ export function createSvGridController<
|
|
|
3873
4243
|
get moveCellsEffective() { return moveCellsEffective; },
|
|
3874
4244
|
get flushScheduledScrollSync() { return flushScheduledScrollSync; },
|
|
3875
4245
|
get scheduleScrollSync() { return scheduleScrollSync; },
|
|
4246
|
+
get syncScrollNow() { return syncScrollNow; },
|
|
3876
4247
|
get internalData() { return internalData; },
|
|
3877
4248
|
set internalData(v) { internalData = v as never; },
|
|
3878
4249
|
get internalColumns() { return internalColumns; },
|
|
@@ -3976,6 +4347,8 @@ export function createSvGridController<
|
|
|
3976
4347
|
get moveColumnInPanel() { return moveColumnInPanel; },
|
|
3977
4348
|
// ---- Localized chrome strings ----
|
|
3978
4349
|
get messages() { return gridMessages; },
|
|
4350
|
+
/** The raw `localization.text` map, for the lazy chart panel's own strings. */
|
|
4351
|
+
get localizationText() { return props.localization?.text; },
|
|
3979
4352
|
// ---- In-grid pivot mode ----
|
|
3980
4353
|
get pivotConfig() { return pivotConfig; },
|
|
3981
4354
|
get pivotActive() { return pivotActive; },
|
|
@@ -4008,6 +4381,32 @@ export function createSvGridController<
|
|
|
4008
4381
|
set chartLogScale(v) { activeChart.logScale = v as never; },
|
|
4009
4382
|
get chartTimeAxis() { return effectiveChartTimeAxis; },
|
|
4010
4383
|
set chartTimeAxis(v) { activeChart.timeAxis = v as never; },
|
|
4384
|
+
get chartBucket() { return chartBucket; },
|
|
4385
|
+
set chartBucket(v) { activeChart.bucket = v as never; },
|
|
4386
|
+
get chartBins() { return chartBins; },
|
|
4387
|
+
set chartBins(v) { activeChart.bins = v as never; },
|
|
4388
|
+
get chartFunnelShape() { return chartFunnelShape; },
|
|
4389
|
+
set chartFunnelShape(v) { activeChart.funnelShape = v as never; },
|
|
4390
|
+
get chartCandleStyle() { return chartCandleStyle; },
|
|
4391
|
+
set chartCandleStyle(v) { activeChart.candleStyle = v as never; },
|
|
4392
|
+
get chartFormat() { return activeChart.format; },
|
|
4393
|
+
set chartFormat(v) { activeChart.format = v as never; },
|
|
4394
|
+
get chartFrozen() { return activeChart.frozen; },
|
|
4395
|
+
get chartIsOhlc() { return chartIsOhlc; },
|
|
4396
|
+
get chartOhlc() { return effectiveChartOhlc; },
|
|
4397
|
+
set chartOhlc(v) { activeChart.ohlc = v as never; },
|
|
4398
|
+
get chartOhlcDateId() { return effectiveChartOhlcDateId; },
|
|
4399
|
+
get chartIndicators() { return chartIndicators; },
|
|
4400
|
+
set chartIndicators(v) { activeChart.indicators = v as never; },
|
|
4401
|
+
get chartIndicatorPanes() { return chartIndicatorPanes; },
|
|
4402
|
+
get chartZoom() { return activeChart.zoom; },
|
|
4403
|
+
// The panel keeps one chart component across tabs, so "tabs share a
|
|
4404
|
+
// window" is simply every tab carrying the same window: a zoom on one is
|
|
4405
|
+
// written to all, and switching tabs hands the component the same value.
|
|
4406
|
+
set chartZoom(v) {
|
|
4407
|
+
if (chartCfg?.syncTabs) for (const c of charts) c.zoom = v as never;
|
|
4408
|
+
else activeChart.zoom = v as never;
|
|
4409
|
+
},
|
|
4011
4410
|
get chartValueFormat() { return effectiveChartValueFormat ?? "number"; },
|
|
4012
4411
|
set chartValueFormat(v) { activeChart.valueFormat = v as never; },
|
|
4013
4412
|
get chartDimensionIsDate() { return chartDimensionIsDate; },
|
|
@@ -4028,8 +4427,21 @@ export function createSvGridController<
|
|
|
4028
4427
|
get activeChartIndex() { return Math.min(activeChartIndex, charts.length - 1); },
|
|
4029
4428
|
set activeChartIndex(v) { activeChartIndex = Math.max(0, Math.min(charts.length - 1, v as number)); },
|
|
4030
4429
|
addChart() {
|
|
4031
|
-
|
|
4430
|
+
const next = makeChart(`Chart ${charts.length + 1}`);
|
|
4431
|
+
if (chartCfg?.syncTabs) next.zoom = activeChart.zoom;
|
|
4432
|
+
charts = [...charts, next];
|
|
4032
4433
|
activeChartIndex = charts.length - 1;
|
|
4434
|
+
chartCfg?.onChartCreated?.({ index: activeChartIndex, title: next.title, type: next.type, spec: null });
|
|
4435
|
+
},
|
|
4436
|
+
/** Unlink the active chart from the grid: it keeps the spec it has now. */
|
|
4437
|
+
freezeChart() {
|
|
4438
|
+
const spec = chartSpec;
|
|
4439
|
+
if (!spec) return;
|
|
4440
|
+
activeChart.frozen = { spec: JSON.parse(JSON.stringify(spec)) as ChartSpec, at: new Date().toISOString() };
|
|
4441
|
+
},
|
|
4442
|
+
/** Link the active chart back to the grid's rows. */
|
|
4443
|
+
unfreezeChart() {
|
|
4444
|
+
activeChart.frozen = null;
|
|
4033
4445
|
},
|
|
4034
4446
|
removeChart(index?: number) {
|
|
4035
4447
|
if (charts.length <= 1) return;
|
|
@@ -4042,48 +4454,39 @@ export function createSvGridController<
|
|
|
4042
4454
|
if (c) c.title = title;
|
|
4043
4455
|
},
|
|
4044
4456
|
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
|
-
}));
|
|
4457
|
+
return charts.map(tabToState);
|
|
4063
4458
|
},
|
|
4064
4459
|
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
|
-
}));
|
|
4460
|
+
const restored = list.map(tabFromState);
|
|
4084
4461
|
charts = restored.length ? restored : [makeChart("Chart 1")];
|
|
4085
4462
|
activeChartIndex = Math.max(0, Math.min(charts.length - 1, active ?? 0));
|
|
4086
4463
|
},
|
|
4464
|
+
// ---- Saved charts ----
|
|
4465
|
+
get savedCharts() { return savedCharts; },
|
|
4466
|
+
/** Keep the active tab's configuration under a name (replacing a same-named one). */
|
|
4467
|
+
saveChart(name: string) {
|
|
4468
|
+
const entry: SavedChartEntry = { name, savedAt: new Date().toISOString(), tab: tabToState(activeChart) };
|
|
4469
|
+
savedCharts = [...savedCharts.filter((s) => s.name !== name), entry];
|
|
4470
|
+
},
|
|
4471
|
+
/** Apply a saved chart to the active tab; the tab keeps its id and title. */
|
|
4472
|
+
applySavedChart(name: string): boolean {
|
|
4473
|
+
const entry = savedCharts.find((s) => s.name === name);
|
|
4474
|
+
if (!entry) return false;
|
|
4475
|
+
const { id: _id, title: _title, ...rest } = tabFromState(entry.tab, activeChartIndex);
|
|
4476
|
+
Object.assign(activeChart, rest);
|
|
4477
|
+
return true;
|
|
4478
|
+
},
|
|
4479
|
+
removeSavedChart(name: string) {
|
|
4480
|
+
savedCharts = savedCharts.filter((s) => s.name !== name);
|
|
4481
|
+
},
|
|
4482
|
+
getSavedCharts() {
|
|
4483
|
+
return savedCharts.map((s) => ({ ...s, tab: { ...s.tab } }));
|
|
4484
|
+
},
|
|
4485
|
+
applySavedCharts(list: ReadonlyArray<Record<string, unknown>>) {
|
|
4486
|
+
savedCharts = list
|
|
4487
|
+
.filter((s) => typeof s?.name === "string" && s.tab && typeof s.tab === "object")
|
|
4488
|
+
.map((s) => ({ name: s.name as string, savedAt: String(s.savedAt ?? ""), tab: s.tab as Record<string, unknown> }));
|
|
4489
|
+
},
|
|
4087
4490
|
get chartCrossFilterable() { return chartCrossFilterable; },
|
|
4088
4491
|
get chartOrientation() { return effectiveChartOrientation; },
|
|
4089
4492
|
set chartOrientation(v) { activeChart.orientation = v as never; },
|
|
@@ -4098,19 +4501,40 @@ export function createSvGridController<
|
|
|
4098
4501
|
get effectiveChartMeasure2Id() { return effectiveChartMeasure2Id; },
|
|
4099
4502
|
get chartAiHandler() { return chartAiHandler; },
|
|
4100
4503
|
set chartAiHandler(v) { chartAiHandler = v as never; },
|
|
4504
|
+
get chartExplainHandler() { return chartExplainHandler; },
|
|
4505
|
+
set chartExplainHandler(v) { chartExplainHandler = v as never; },
|
|
4101
4506
|
applyChartConfig(config: {
|
|
4102
4507
|
open?: boolean;
|
|
4103
4508
|
type?: ChartType;
|
|
4104
4509
|
dimension?: string | null;
|
|
4105
4510
|
series?: string | null;
|
|
4106
4511
|
measure?: string | null;
|
|
4107
|
-
reduce?:
|
|
4512
|
+
reduce?: ChartReducer;
|
|
4108
4513
|
stacked?: boolean;
|
|
4109
4514
|
dataLabels?: boolean;
|
|
4110
4515
|
logScale?: boolean;
|
|
4111
4516
|
timeAxis?: boolean;
|
|
4112
4517
|
valueFormat?: ChartValueFormat;
|
|
4518
|
+
bucket?: ChartTimeBucket | null;
|
|
4519
|
+
bins?: number | null;
|
|
4520
|
+
funnelShape?: "trapezoid" | "pyramid" | "cone" | null;
|
|
4521
|
+
candleStyle?: "classic" | "hollow" | "heikin-ashi" | null;
|
|
4522
|
+
/** Second measure: scatter Y, range high end, bullet target. */
|
|
4523
|
+
measure2?: string | null;
|
|
4524
|
+
/** Candlestick / OHLC price columns (ids or fields). */
|
|
4525
|
+
ohlc?: { open?: string | null; high?: string | null; low?: string | null; close?: string | null; volume?: string | null } | null;
|
|
4526
|
+
/** Indicators: panes (volume, rsi, macd, stochastic, atr, obv) and
|
|
4527
|
+
* overlays (sma, ema, bb, vwap). */
|
|
4528
|
+
indicators?: ChartPanelIndicator[];
|
|
4529
|
+
/** The builder's format (titles, axes, legend, series style); `null` resets it. */
|
|
4530
|
+
format?: ChartFormatState | null;
|
|
4531
|
+
/** Unlink the chart from the grid (true) or link it back (false). */
|
|
4532
|
+
frozen?: boolean;
|
|
4533
|
+
/** A saved chart's name: applied to the active tab first, then the
|
|
4534
|
+
* other keys on top. */
|
|
4535
|
+
saved?: string;
|
|
4113
4536
|
}) {
|
|
4537
|
+
if (config.saved) this.applySavedChart(config.saved);
|
|
4114
4538
|
const cols = [...chartableColumns.dims, ...chartableColumns.measures];
|
|
4115
4539
|
const resolve = (v: string | null | undefined): string | null => {
|
|
4116
4540
|
if (v == null) return null;
|
|
@@ -4130,6 +4554,24 @@ export function createSvGridController<
|
|
|
4130
4554
|
if (typeof config.logScale === "boolean") activeChart.logScale = config.logScale;
|
|
4131
4555
|
if (typeof config.timeAxis === "boolean") activeChart.timeAxis = config.timeAxis;
|
|
4132
4556
|
if (config.valueFormat) activeChart.valueFormat = config.valueFormat;
|
|
4557
|
+
if ("bucket" in config) activeChart.bucket = config.bucket ?? null;
|
|
4558
|
+
if ("bins" in config) activeChart.bins = config.bins ?? null;
|
|
4559
|
+
if ("funnelShape" in config) activeChart.funnelShape = config.funnelShape ?? null;
|
|
4560
|
+
if ("candleStyle" in config) activeChart.candleStyle = config.candleStyle ?? null;
|
|
4561
|
+
if ("measure2" in config) activeChart.measure2Id = resolve(config.measure2);
|
|
4562
|
+
if ("ohlc" in config) {
|
|
4563
|
+
const o = config.ohlc;
|
|
4564
|
+
activeChart.ohlc = o
|
|
4565
|
+
? { open: resolve(o.open), high: resolve(o.high), low: resolve(o.low), close: resolve(o.close), volume: resolve(o.volume) }
|
|
4566
|
+
: null;
|
|
4567
|
+
}
|
|
4568
|
+
if (config.indicators) activeChart.indicators = [...config.indicators];
|
|
4569
|
+
if ("format" in config) activeChart.format = config.format ?? null;
|
|
4570
|
+
if (config.frozen === false) activeChart.frozen = null;
|
|
4571
|
+
else if (config.frozen === true && !activeChart.frozen) {
|
|
4572
|
+
const spec = chartSpec;
|
|
4573
|
+
if (spec) activeChart.frozen = { spec: JSON.parse(JSON.stringify(spec)) as ChartSpec, at: new Date().toISOString() };
|
|
4574
|
+
}
|
|
4133
4575
|
},
|
|
4134
4576
|
get toggleGroupInPanel() { return toggleGroupInPanel; },
|
|
4135
4577
|
get lastSortingSerialized() { return lastSortingSerialized; },
|
|
@@ -4152,8 +4594,17 @@ export function createSvGridController<
|
|
|
4152
4594
|
},
|
|
4153
4595
|
/** True when the `rowResize` prop is on and nothing overrides it. */
|
|
4154
4596
|
get rowResizeOn() { return rowResizeOn; },
|
|
4597
|
+
get frozenRowCount() { return frozenRowCount; },
|
|
4598
|
+
get frozenRowList() { return frozenRowList; },
|
|
4599
|
+
get frozenBandHeight() { return frozenBandHeight; },
|
|
4600
|
+
get frozenRowTop() { return frozenRowTop; },
|
|
4155
4601
|
/** Record the height the user dragged a row to. */
|
|
4156
4602
|
get setRowResizeHeight() { return setRowResizeHeight; },
|
|
4603
|
+
get isRowCollapsed() { return isRowCollapsed; },
|
|
4604
|
+
get setRowCollapsed() { return setRowCollapsed; },
|
|
4605
|
+
get resizeRowByUser() { return resizeRowByUser; },
|
|
4606
|
+
get resizeColumnByUser() { return resizeColumnByUser; },
|
|
4607
|
+
get autosizeColumnByUser() { return autosizeColumnByUser; },
|
|
4157
4608
|
/** A row's dragged height, or undefined when it has not been resized. */
|
|
4158
4609
|
get rowResizeHeightPx() {
|
|
4159
4610
|
rowResizeVersion;
|
|
@@ -4216,6 +4667,8 @@ export function createSvGridController<
|
|
|
4216
4667
|
get toggleSelectAllRows() { return toggleSelectAllRows; },
|
|
4217
4668
|
get userHasActivatedCell() { return userHasActivatedCell; },
|
|
4218
4669
|
set userHasActivatedCell(v) { userHasActivatedCell = v as never; },
|
|
4670
|
+
get tabRunOrigin() { return tabRunOrigin; },
|
|
4671
|
+
set tabRunOrigin(v) { tabRunOrigin = v; },
|
|
4219
4672
|
get setActiveCell() { return setActiveCell; },
|
|
4220
4673
|
get scrollActiveCellIntoView() { return scrollActiveCellIntoView; },
|
|
4221
4674
|
get getColumnBaseWidth() { return getColumnBaseWidth; },
|
|
@@ -4225,6 +4678,8 @@ export function createSvGridController<
|
|
|
4225
4678
|
get extendSelection() { return extendSelection; },
|
|
4226
4679
|
get isCellInSelectedRange() { return isCellInSelectedRange; },
|
|
4227
4680
|
get getCellRangeEdges() { return getCellRangeEdges; },
|
|
4681
|
+
get activeRangeRect() { return activeRangeRect; },
|
|
4682
|
+
get cellSelectionState() { return cellSelectionState; },
|
|
4228
4683
|
get getSelectionRects() { return getSelectionRects; },
|
|
4229
4684
|
get fillHandleCell() { return fillHandleCell; },
|
|
4230
4685
|
get isInFillPreview() { return isInFillPreview; },
|
|
@@ -4247,6 +4702,7 @@ export function createSvGridController<
|
|
|
4247
4702
|
get startFillDrag() { return startFillDrag; },
|
|
4248
4703
|
get onFillPointerMove() { return onFillPointerMove; },
|
|
4249
4704
|
get onFillPointerUp() { return onFillPointerUp; },
|
|
4705
|
+
get fillDownToNeighbour() { return fillDownToNeighbour; },
|
|
4250
4706
|
get isOnMoveGrabStrip() { return isOnMoveGrabStrip; },
|
|
4251
4707
|
get startMoveDrag() { return startMoveDrag; },
|
|
4252
4708
|
get onMovePointerMove() { return onMovePointerMove; },
|
|
@@ -4266,6 +4722,7 @@ export function createSvGridController<
|
|
|
4266
4722
|
get cutSelectionToClipboard() { return cutSelectionToClipboard; },
|
|
4267
4723
|
get pasteFromClipboard() { return pasteFromClipboard; },
|
|
4268
4724
|
get onGridPaste() { return onGridPaste; },
|
|
4725
|
+
get armPasteFallback() { return armPasteFallback; },
|
|
4269
4726
|
get clearSelectedCells() { return clearSelectedCells; },
|
|
4270
4727
|
get onCellDoubleClick() { return onCellDoubleClick; },
|
|
4271
4728
|
get startEditingWithChar() { return startEditingWithChar; },
|
|
@@ -4342,12 +4799,12 @@ export function createSvGridController<
|
|
|
4342
4799
|
get clearColumnFilter() { return clearColumnFilter; },
|
|
4343
4800
|
get onWindowKeydown() { return onWindowKeydown; },
|
|
4344
4801
|
get columnDefMatchesId() { return columnDefMatchesId; },
|
|
4345
|
-
get buildApi() { return
|
|
4802
|
+
get buildApi() { return api; },
|
|
4346
4803
|
get apiNotified() { return apiNotified; },
|
|
4347
4804
|
set apiNotified(v) { apiNotified = v as never; },
|
|
4348
4805
|
};
|
|
4349
4806
|
const { resolveEffectiveFeatures } = createFeatures<TFeatures, TData>(ctx);
|
|
4350
|
-
const { showTooltipFor, hideTooltip, flushScheduledScrollSync, scheduleScrollSync, onBodyScroll } = createScrollSync<TFeatures, TData>(ctx);
|
|
4807
|
+
const { showTooltipFor, hideTooltip, flushScheduledScrollSync, scheduleScrollSync, syncScrollNow, onBodyScroll } = createScrollSync<TFeatures, TData>(ctx);
|
|
4351
4808
|
const { onGridKeyDown, onWindowKeydown, onHeaderSortClick } = createKeyboard<TFeatures, TData>(ctx);
|
|
4352
4809
|
const { computeSummaries, hasRenderedColumn } = createSummaries<TFeatures, TData>(ctx);
|
|
4353
4810
|
// Hand the aggregator to `eagerSummaries` (declared above `ctx`, which this
|
|
@@ -4356,13 +4813,13 @@ export function createSvGridController<
|
|
|
4356
4813
|
|
|
4357
4814
|
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
4815
|
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);
|
|
4816
|
+
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);
|
|
4817
|
+
const { isRowSelected, toggleRowSelectionById, toggleSelectAllRows, setActiveCell, scrollActiveCellIntoView, setSelection, extendSelection, isCellInSelectedRange, getCellRangeEdges, activeRangeRect, cellSelectionState, getSelectionRects, isInFillPreview, dragPreviewRect, fillMarqueeEdges, findColumnById, onCellPointerDown, onCellPointerEnter, endDragSelection, onWindowPointerMove, onCellClick, emitCellDoubleClick } = createSelection<TFeatures, TData>(ctx);
|
|
4361
4818
|
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
4819
|
const { onRowDragStart, onRowDragOver, onRowDragLeave, onRowDrop, onRowsContainerDragOver, onRowsContainerDrop, onRowDragEnd, onRowPointerDown, destroyRowDrag } = createRowDrag<TFeatures, TData>(ctx);
|
|
4363
4820
|
const { register: registerAlignedGrid, broadcastScroll: broadcastAlignedScroll, broadcastWidths: broadcastAlignedWidths } = createAlignedGrids<TFeatures, TData>(ctx);
|
|
4364
4821
|
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);
|
|
4822
|
+
const { readCellRaw, writeCellRaw, applyFillPattern, clearSelectedCellValues, startFillDrag, onFillPointerMove, onFillPointerUp, fillDownToNeighbour, isOnMoveGrabStrip, startMoveDrag, onMovePointerMove, onMovePointerUp, applyMoveRange, moveDestRect, trackEdgeScroll, stopEdgeScroll, toggleBooleanCell, copySelectionToClipboard, clearSelectedCells, cutSelectionToClipboard } = createClipboard(ctx);
|
|
4366
4823
|
|
|
4367
4824
|
// Dev-time configuration checks. Silent misconfiguration was the grid's
|
|
4368
4825
|
// biggest usability gap - a misspelled `field` rendered a column of blank
|