@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
package/dist/ai.d.ts
CHANGED
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
* so they add nothing to the base bundle unless you actually use them.
|
|
26
26
|
*/
|
|
27
27
|
import type { RowData, SvGridApi, TableFeatures } from './index.js';
|
|
28
|
+
import { type ChartDiagnostic } from './chart-validate.js';
|
|
29
|
+
import type { ChartSpec } from './chart-types.js';
|
|
28
30
|
type ExportFormat = 'xlsx' | 'xls' | 'pdf' | 'csv' | 'tsv' | 'html' | 'json' | 'xml' | 'md';
|
|
29
31
|
/**
|
|
30
32
|
* Shape every consumer-provided model adapter must implement. Keeping this
|
|
@@ -53,7 +55,7 @@ export type AIRequest = {
|
|
|
53
55
|
maxOutputTokens?: number;
|
|
54
56
|
};
|
|
55
57
|
/** Which helper produced a request - carried on {@link AIRequest} for routing and telemetry. */
|
|
56
|
-
export type AITask = 'filter' | 'smart-fill' | 'summarize' | 'classify' | 'export' | 'anomaly' | 'chart';
|
|
58
|
+
export type AITask = 'filter' | 'smart-fill' | 'summarize' | 'classify' | 'export' | 'anomaly' | 'chart' | 'explain-chart';
|
|
57
59
|
/**
|
|
58
60
|
* Register the model adapter every AI call will route through. Call once
|
|
59
61
|
* at app boot. Passing `null` clears the provider and AI calls revert to
|
|
@@ -270,13 +272,14 @@ export declare function aiFindAnomalies<TFeatures extends TableFeatures, TData e
|
|
|
270
272
|
* Chart shapes the model may choose from when planning a visualisation.
|
|
271
273
|
*
|
|
272
274
|
* These are exactly the types the built-in panel can build from a dimension, a
|
|
273
|
-
* measure and an optional split - which is all a chart plan carries.
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
* panel renders as an empty frame, which is worse than not offering it.
|
|
275
|
+
* measure and an optional split - which is all a chart plan carries. Scatter
|
|
276
|
+
* is deliberately absent, because a plan cannot express its SECOND measure.
|
|
277
|
+
* A candlestick can be asked for: the panel finds the open / high / low /
|
|
278
|
+
* close / volume columns by name, so the plan only needs a date dimension.
|
|
278
279
|
*/
|
|
279
|
-
export type AIChartType = 'bar' | 'line' | 'area' | 'pie' | 'treemap' | 'funnel' | 'waterfall' | 'radar' | 'heatmap' | 'boxplot' | 'gauge' | 'calendar' | 'sankey';
|
|
280
|
+
export type AIChartType = 'bar' | 'line' | 'area' | 'pie' | 'treemap' | 'funnel' | 'waterfall' | 'radar' | 'heatmap' | 'boxplot' | 'gauge' | 'calendar' | 'sankey' | 'histogram' | 'lollipop' | 'pareto' | 'sunburst' | 'radial-bar' | 'radial-column' | 'nightingale' | 'bullet' | 'chord' | 'stream' | 'candlestick';
|
|
281
|
+
/** The indicators a chart plan may ask for on a candlestick. */
|
|
282
|
+
export type AIChartIndicator = 'volume' | 'rsi' | 'macd' | 'stochastic' | 'atr' | 'obv' | 'sma' | 'ema' | 'bb' | 'vwap';
|
|
280
283
|
/** A chart the model proposed: its type, and the fields to plot. */
|
|
281
284
|
export type AIChartPlan = {
|
|
282
285
|
type: AIChartType;
|
|
@@ -286,13 +289,22 @@ export type AIChartPlan = {
|
|
|
286
289
|
series: string | null;
|
|
287
290
|
/** Measure (value-axis) column field, or null. */
|
|
288
291
|
measure: string | null;
|
|
289
|
-
reduce:
|
|
292
|
+
reduce: AIChartReduce;
|
|
290
293
|
stacked: boolean;
|
|
291
294
|
logScale: boolean;
|
|
292
295
|
timeAxis: boolean;
|
|
293
296
|
valueFormat: 'number' | 'currency' | 'percent';
|
|
297
|
+
/** Calendar unit to group a date dimension by, or null for exact values. */
|
|
298
|
+
bucket: 'day' | 'week' | 'month' | 'quarter' | 'year' | null;
|
|
299
|
+
/** Candlestick only: indicator panes and overlays to add. */
|
|
300
|
+
indicators?: AIChartIndicator[];
|
|
294
301
|
rationale: string;
|
|
302
|
+
/** With `apply`: what `validateChartSpec` flagged as an error on the spec
|
|
303
|
+
* the panel built from the plan, empty when it drew cleanly. */
|
|
304
|
+
diagnostics?: ChartDiagnostic[];
|
|
295
305
|
};
|
|
306
|
+
/** The reducers a chart plan may name: the panel's full list. */
|
|
307
|
+
export type AIChartReduce = 'sum' | 'avg' | 'count' | 'min' | 'max' | 'median' | 'p90' | 'first' | 'last' | 'countDistinct';
|
|
296
308
|
/** Options for `aiChart` - preview the plan, or render it into the grid. */
|
|
297
309
|
export type AIChartOptions = {
|
|
298
310
|
/** Apply the plan to the grid's chart panel (open + configure). Default false. */
|
|
@@ -305,6 +317,26 @@ export type AIChartOptions = {
|
|
|
305
317
|
* `apply: true`) pushes it into the panel through `api.configureChart`.
|
|
306
318
|
*/
|
|
307
319
|
export declare function aiChart<TFeatures extends TableFeatures, TData extends RowData>(api: SvGridApi<TFeatures, TData>, query: string, opts?: AIChartOptions): Promise<AIChartPlan>;
|
|
320
|
+
/** What `aiExplainChart` returns: the grounded summary and the model's insights. */
|
|
321
|
+
export type AIChartExplanation = {
|
|
322
|
+
/** The plain-language reading of the chart (`chartSummary`), as the model saw it. */
|
|
323
|
+
summary: string;
|
|
324
|
+
/** Two to three observations the model added: comparisons, outliers, what to look at next. */
|
|
325
|
+
insights: string[];
|
|
326
|
+
};
|
|
327
|
+
/** Options for `aiExplainChart`. */
|
|
328
|
+
export type AIExplainChartOptions = {
|
|
329
|
+
/** The spec to explain. Default: the grid panel's active chart. */
|
|
330
|
+
spec?: ChartSpec;
|
|
331
|
+
signal?: AbortSignal;
|
|
332
|
+
};
|
|
333
|
+
/**
|
|
334
|
+
* Explain a chart in plain words. The prompt is grounded on `chartSummary`
|
|
335
|
+
* (the same sentence a screen reader hears) plus a compact table of the
|
|
336
|
+
* first 50 categories, so the model comments on numbers it was given rather
|
|
337
|
+
* than on an image. Returns the summary and up to three insights.
|
|
338
|
+
*/
|
|
339
|
+
export declare function aiExplainChart<TFeatures extends TableFeatures, TData extends RowData>(api: SvGridApi<TFeatures, TData>, opts?: AIExplainChartOptions): Promise<AIChartExplanation>;
|
|
308
340
|
/**
|
|
309
341
|
* Wire the grid's chart-panel AI button to the model: registers a handler (via
|
|
310
342
|
* `api.setChartAiHandler`) that runs `aiChart` and returns the plan for the
|
package/dist/ai.js
CHANGED
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
* so they add nothing to the base bundle unless you actually use them.
|
|
26
26
|
*/
|
|
27
27
|
import { getExportProvider } from './export-provider.js';
|
|
28
|
+
import { validateChartSpec } from './chart-validate.js';
|
|
29
|
+
import { chartSummary } from './chart-summary.js';
|
|
28
30
|
let provider = null;
|
|
29
31
|
/**
|
|
30
32
|
* Register the model adapter every AI call will route through. Call once
|
|
@@ -500,6 +502,8 @@ export async function aiFindAnomalies(api, opts = {}) {
|
|
|
500
502
|
summary: result.summary ?? '',
|
|
501
503
|
};
|
|
502
504
|
}
|
|
505
|
+
const AI_INDICATORS = ['volume', 'rsi', 'macd', 'stochastic', 'atr', 'obv', 'sma', 'ema', 'bb', 'vwap'];
|
|
506
|
+
const AI_REDUCERS = ['sum', 'avg', 'count', 'min', 'max', 'median', 'p90', 'first', 'last', 'countDistinct'];
|
|
503
507
|
/**
|
|
504
508
|
* Translate a natural-language request into a chart plan for the grid's built-in
|
|
505
509
|
* `charting` panel. Validates every field against the real schema, then (with
|
|
@@ -511,15 +515,17 @@ export async function aiChart(api, query, opts = {}) {
|
|
|
511
515
|
`into a strict-JSON chart plan the grid can render.\n\n` +
|
|
512
516
|
`Columns:\n${schemaToPromptBlock(schema)}\n\n` +
|
|
513
517
|
`Output JSON schema:\n` +
|
|
514
|
-
`{ "type": "bar"|"line"|"area"|"pie"|"treemap"|"funnel"|"waterfall"|"radar"|"heatmap"|"boxplot"|"gauge"|"calendar"|"sankey", ` +
|
|
518
|
+
`{ "type": "bar"|"line"|"area"|"pie"|"treemap"|"funnel"|"waterfall"|"radar"|"heatmap"|"boxplot"|"gauge"|"calendar"|"sankey"|"histogram"|"lollipop"|"pareto"|"sunburst"|"radial-bar"|"radial-column"|"nightingale"|"bullet"|"chord"|"stream"|"candlestick", ` +
|
|
515
519
|
`"dimension": "<a categorical column to group by>", ` +
|
|
516
520
|
`"series": "<a second categorical column to split into series, or null>", ` +
|
|
517
521
|
`"measure": "<a numeric column to aggregate>", ` +
|
|
518
|
-
`"reduce": "sum"|"avg"|"count", ` +
|
|
522
|
+
`"reduce": "sum"|"avg"|"count"|"min"|"max"|"median"|"p90"|"first"|"last"|"countDistinct", ` +
|
|
523
|
+
`"bucket": "day"|"week"|"month"|"quarter"|"year"|null, ` +
|
|
519
524
|
`"stacked": true|false, ` +
|
|
520
525
|
`"logScale": true|false, ` +
|
|
521
526
|
`"timeAxis": true|false, ` +
|
|
522
527
|
`"valueFormat": "number"|"currency"|"percent", ` +
|
|
528
|
+
`"indicators": ["volume"|"rsi"|"macd"|"stochastic"|"atr"|"obv"|"sma"|"ema"|"bb"|"vwap", ...] (candlestick only, else omit), ` +
|
|
523
529
|
`"rationale": "<one-sentence explanation>" }\n\n` +
|
|
524
530
|
`Rules: pick "dimension" and "series" from text/date columns and "measure" ` +
|
|
525
531
|
`from a numeric column; use only column names from the list above; ` +
|
|
@@ -532,9 +538,17 @@ export async function aiChart(api, query, opts = {}) {
|
|
|
532
538
|
`number vs a target -> gauge (leave "dimension" null); a year of daily ` +
|
|
533
539
|
`activity -> calendar (dimension must be a date column); flow between two ` +
|
|
534
540
|
`things -> sankey (needs "series": dimension is the source, series the ` +
|
|
535
|
-
`target)
|
|
541
|
+
`target); flows between groups round a circle -> chord (needs "series"); ` +
|
|
542
|
+
`frequency/how many fall in each range -> histogram (leave "dimension" null); ` +
|
|
543
|
+
`vital few/80-20/biggest contributors -> pareto; drilldown/rings/nested share -> sunburst; ` +
|
|
544
|
+
`progress rings/circular -> radial-bar; polar/rose -> nightingale or radial-column; ` +
|
|
545
|
+
`actual vs target per item -> bullet; layered over time/stream -> stream (needs "series"); ` +
|
|
546
|
+
`price/candles/OHLC/stock/ticker -> candlestick (dimension is the date column; the open/high/low/close/volume columns are found by name), ` +
|
|
547
|
+
`with "volume"/"RSI"/"MACD"/"Bollinger"/"moving average" -> indicators. ` +
|
|
536
548
|
`"stacked"/"stack" -> stacked:true; "log"/"logarithmic" -> logScale:true; ` +
|
|
537
549
|
`"over time"/"by date"/a date dimension -> timeAxis:true; ` +
|
|
550
|
+
`"monthly"/"per month"/"by month" -> bucket:"month" (likewise daily/weekly/quarterly/yearly), else bucket:null; ` +
|
|
551
|
+
`"median"/"typical" -> reduce:"median"; "highest"/"peak" -> "max"; "lowest" -> "min"; "unique"/"distinct" -> "countDistinct"; ` +
|
|
538
552
|
`money/revenue/price/$ -> valueFormat:"currency"; rate/%/share -> valueFormat:"percent"; ` +
|
|
539
553
|
`else valueFormat:"number". default reduce is "sum". ` +
|
|
540
554
|
`Return JSON only, no prose.\n\n` +
|
|
@@ -551,6 +565,8 @@ export async function aiChart(api, query, opts = {}) {
|
|
|
551
565
|
'bar', 'line', 'area', 'pie',
|
|
552
566
|
'treemap', 'funnel', 'waterfall', 'radar',
|
|
553
567
|
'heatmap', 'boxplot', 'gauge', 'calendar', 'sankey',
|
|
568
|
+
'histogram', 'lollipop', 'pareto', 'sunburst', 'radial-bar', 'radial-column', 'nightingale', 'bullet', 'chord', 'stream',
|
|
569
|
+
'candlestick',
|
|
554
570
|
];
|
|
555
571
|
plan.type = CHART_TYPES.includes(plan.type) ? plan.type : 'bar';
|
|
556
572
|
plan.dimension = plan.dimension && valid.has(plan.dimension) ? plan.dimension : null;
|
|
@@ -564,17 +580,33 @@ export async function aiChart(api, query, opts = {}) {
|
|
|
564
580
|
// a chart the data cannot make. Fall back to a bar, which any dimension and
|
|
565
581
|
// measure can draw, rather than showing nothing and blaming the request.
|
|
566
582
|
const dateFields = new Set(schema.filter((c) => c.type === 'date').map((c) => c.field));
|
|
567
|
-
const needsSplit = plan.type === 'heatmap' || plan.type === 'sankey';
|
|
583
|
+
const needsSplit = plan.type === 'heatmap' || plan.type === 'sankey' || plan.type === 'chord' || plan.type === 'stream';
|
|
568
584
|
if (needsSplit && !plan.series)
|
|
569
585
|
plan.type = 'bar';
|
|
570
586
|
if (plan.type === 'calendar' && !(plan.dimension && dateFields.has(plan.dimension)))
|
|
571
587
|
plan.type = 'bar';
|
|
588
|
+
// A candlestick needs a date to run along and four prices to draw; the
|
|
589
|
+
// panel finds the price columns by name, so the shape check is the count.
|
|
590
|
+
if (plan.type === 'candlestick' && !(plan.dimension && dateFields.has(plan.dimension) && numericFields.size >= 4))
|
|
591
|
+
plan.type = 'bar';
|
|
592
|
+
plan.indicators = plan.type === 'candlestick' && Array.isArray(plan.indicators)
|
|
593
|
+
? plan.indicators.filter((k) => AI_INDICATORS.includes(k))
|
|
594
|
+
: undefined;
|
|
595
|
+
if (plan.indicators === undefined)
|
|
596
|
+
delete plan.indicators;
|
|
572
597
|
// A gauge is one number: it has no category axis, so a dimension is noise.
|
|
573
|
-
|
|
598
|
+
// A histogram bins the measure itself, so a dimension is noise there too.
|
|
599
|
+
if (plan.type === 'gauge' || plan.type === 'histogram')
|
|
574
600
|
plan.dimension = null;
|
|
575
|
-
if (plan.type !== 'gauge' && !plan.dimension)
|
|
601
|
+
if (plan.type !== 'gauge' && plan.type !== 'histogram' && !plan.dimension)
|
|
576
602
|
plan.type = 'bar';
|
|
577
|
-
plan.reduce =
|
|
603
|
+
plan.reduce = AI_REDUCERS.includes(plan.reduce) ? plan.reduce : 'sum';
|
|
604
|
+
// A bucket only means something on a date dimension.
|
|
605
|
+
const BUCKETS = ['day', 'week', 'month', 'quarter', 'year'];
|
|
606
|
+
plan.bucket =
|
|
607
|
+
plan.bucket && BUCKETS.includes(plan.bucket) && plan.dimension && dateFields.has(plan.dimension)
|
|
608
|
+
? plan.bucket
|
|
609
|
+
: null;
|
|
578
610
|
plan.stacked = plan.stacked === true;
|
|
579
611
|
plan.logScale = plan.logScale === true;
|
|
580
612
|
plan.timeAxis = plan.timeAxis === true;
|
|
@@ -593,10 +625,41 @@ export async function aiChart(api, query, opts = {}) {
|
|
|
593
625
|
logScale: plan.logScale,
|
|
594
626
|
timeAxis: plan.timeAxis,
|
|
595
627
|
valueFormat: plan.valueFormat,
|
|
628
|
+
...(plan.indicators ? { indicators: plan.indicators } : {}),
|
|
629
|
+
bucket: plan.bucket,
|
|
596
630
|
});
|
|
631
|
+
// The panel built a spec from the plan; if it cannot draw as intended,
|
|
632
|
+
// say so on the plan rather than showing an empty frame in silence.
|
|
633
|
+
const spec = api.getChartSpec?.();
|
|
634
|
+
if (spec)
|
|
635
|
+
plan.diagnostics = validateChartSpec(spec).filter((d) => d.severity === 'error');
|
|
597
636
|
}
|
|
598
637
|
return plan;
|
|
599
638
|
}
|
|
639
|
+
/**
|
|
640
|
+
* Explain a chart in plain words. The prompt is grounded on `chartSummary`
|
|
641
|
+
* (the same sentence a screen reader hears) plus a compact table of the
|
|
642
|
+
* first 50 categories, so the model comments on numbers it was given rather
|
|
643
|
+
* than on an image. Returns the summary and up to three insights.
|
|
644
|
+
*/
|
|
645
|
+
export async function aiExplainChart(api, opts = {}) {
|
|
646
|
+
const spec = opts.spec ?? api.getChartSpec?.() ?? null;
|
|
647
|
+
if (!spec)
|
|
648
|
+
return { summary: 'No chart to explain.', insights: [] };
|
|
649
|
+
const summary = chartSummary(spec);
|
|
650
|
+
const cats = spec.categories.slice(0, 50);
|
|
651
|
+
const table = cats.length
|
|
652
|
+
? `${['category', ...spec.series.map((s) => s.label)].join('\t')}\n` +
|
|
653
|
+
cats.map((c, i) => [c, ...spec.series.map((s) => String(s.values[i] ?? ''))].join('\t')).join('\n')
|
|
654
|
+
: '';
|
|
655
|
+
const prompt = `You are explaining a ${spec.type} chart to a busy reader.\n\n` +
|
|
656
|
+
`Reading of the chart: ${summary}\n\n` +
|
|
657
|
+
(table ? `Data (first ${cats.length} categories, tab separated):\n${table}\n\n` : '') +
|
|
658
|
+
`Return strict JSON only: { "insights": ["<2-3 short observations a reader would miss at a glance: comparisons, outliers, what to look at next>"] }`;
|
|
659
|
+
const out = await callJSON({ prompt, task: 'explain-chart', signal: opts.signal, maxOutputTokens: 300 });
|
|
660
|
+
const insights = Array.isArray(out.insights) ? out.insights.filter((x) => typeof x === 'string').slice(0, 3) : [];
|
|
661
|
+
return { summary, insights };
|
|
662
|
+
}
|
|
600
663
|
/**
|
|
601
664
|
* Wire the grid's chart-panel AI button to the model: registers a handler (via
|
|
602
665
|
* `api.setChartAiHandler`) that runs `aiChart` and returns the plan for the
|
|
@@ -617,9 +680,13 @@ export function enableAiCharting(api) {
|
|
|
617
680
|
logScale: plan.logScale,
|
|
618
681
|
timeAxis: plan.timeAxis,
|
|
619
682
|
valueFormat: plan.valueFormat,
|
|
683
|
+
bucket: plan.bucket,
|
|
620
684
|
rationale: plan.rationale,
|
|
621
685
|
};
|
|
622
686
|
});
|
|
687
|
+
// And the Explain button beside it.
|
|
688
|
+
const explain = api;
|
|
689
|
+
explain.setChartExplainHandler?.(() => aiExplainChart(api));
|
|
623
690
|
}
|
|
624
691
|
/**
|
|
625
692
|
* Remove the natural-language chart handler, hiding the AI button in the chart
|
|
@@ -628,6 +695,7 @@ export function enableAiCharting(api) {
|
|
|
628
695
|
export function disableAiCharting(api) {
|
|
629
696
|
const hook = api;
|
|
630
697
|
hook.setChartAiHandler?.(null);
|
|
698
|
+
hook.setChartExplainHandler?.(null);
|
|
631
699
|
}
|
|
632
700
|
// ---------------------------------------------------------------------------
|
|
633
701
|
// 8. Mock provider for examples + tests
|
|
@@ -662,6 +730,17 @@ export const mockAIProvider = async (req) => {
|
|
|
662
730
|
if (req.task === 'chart') {
|
|
663
731
|
return JSON.stringify(buildMockChart(req.prompt));
|
|
664
732
|
}
|
|
733
|
+
if (req.task === 'explain-chart') {
|
|
734
|
+
// Two canned observations round the reading the prompt was grounded on.
|
|
735
|
+
const reading = /Reading of the chart: ([^\n]+)/.exec(req.prompt)?.[1] ?? '';
|
|
736
|
+
const rises = /rises/.test(reading);
|
|
737
|
+
return JSON.stringify({
|
|
738
|
+
insights: [
|
|
739
|
+
rises ? 'The growth is front-loaded: the early categories carry most of the change.' : 'The movement is gradual with no single category driving it.',
|
|
740
|
+
'Compare the peak against the same category a period earlier before reading it as a trend.',
|
|
741
|
+
],
|
|
742
|
+
});
|
|
743
|
+
}
|
|
665
744
|
return '{}';
|
|
666
745
|
};
|
|
667
746
|
function buildMockChart(prompt) {
|
|
@@ -672,19 +751,30 @@ function buildMockChart(prompt) {
|
|
|
672
751
|
// Mirrors the type hints in the real prompt, so the bundled offline provider
|
|
673
752
|
// can reach the same charts a model can. Ordered most-specific first: "share
|
|
674
753
|
// of the pipeline" is a funnel request, not a pie one.
|
|
675
|
-
const type = /\
|
|
676
|
-
: /\
|
|
677
|
-
: /\
|
|
678
|
-
: /\
|
|
679
|
-
: /\
|
|
680
|
-
: /\
|
|
681
|
-
: /\
|
|
682
|
-
: /\
|
|
683
|
-
:
|
|
684
|
-
:
|
|
685
|
-
: /\
|
|
686
|
-
: /\
|
|
687
|
-
: '
|
|
754
|
+
const type = /\bcandle|\bohlc\b|stock price|price chart|\bticker\b/.test(q) ? 'candlestick'
|
|
755
|
+
: /\bhistogram\b|frequenc|how many fall|bucket(?:ed)? by value/.test(q) ? 'histogram'
|
|
756
|
+
: /\bpareto\b|80.?20|vital few|biggest contributors/.test(q) ? 'pareto'
|
|
757
|
+
: /\bsunburst\b|\brings?\b|drill.?down/.test(q) ? 'sunburst'
|
|
758
|
+
: /\bchord\b/.test(q) ? 'chord'
|
|
759
|
+
: /\bstream\b|layered over time/.test(q) ? 'stream'
|
|
760
|
+
: /\bbullet\b|actual vs target|against (?:a |the )?target per/.test(q) ? 'bullet'
|
|
761
|
+
: /\bnightingale\b|\brose\b|\bpolar\b/.test(q) ? 'nightingale'
|
|
762
|
+
: /radial (?:bar|progress)|progress rings?|circular progress/.test(q) ? 'radial-bar'
|
|
763
|
+
: /radial column/.test(q) ? 'radial-column'
|
|
764
|
+
: /\blollipop\b/.test(q) ? 'lollipop'
|
|
765
|
+
: /\bfunnel\b|drop.?off|conversion|\bstages?\b|pipeline/.test(q) ? 'funnel'
|
|
766
|
+
: /\bwaterfall\b|running total|contribution|\bbridge\b/.test(q) ? 'waterfall'
|
|
767
|
+
: /\btree ?map\b|nested|hierarch/.test(q) ? 'treemap'
|
|
768
|
+
: /\bradar\b|spider|multi.?measure/.test(q) ? 'radar'
|
|
769
|
+
: /\bheat ?map\b/.test(q) ? 'heatmap'
|
|
770
|
+
: /\bbox ?plot\b|spread|distribution|variance|outlier|percentile|quartile/.test(q) ? 'boxplot'
|
|
771
|
+
: /\bgauge\b|\bdial\b|vs target|against target/.test(q) ? 'gauge'
|
|
772
|
+
: /\bcalendar\b|daily activity|year of/.test(q) ? 'calendar'
|
|
773
|
+
: /\bsankey\b|\bflow\b/.test(q) ? 'sankey'
|
|
774
|
+
: /\bline\b|\btrend\b|over time/.test(q) ? 'line'
|
|
775
|
+
: /\barea\b/.test(q) ? 'area'
|
|
776
|
+
: /\bpie\b|share|proportion|breakdown/.test(q) ? 'pie'
|
|
777
|
+
: 'bar';
|
|
688
778
|
const byMatch = q.match(/\bby\s+([a-z0-9_ ]+?)(?:\s+(?:split|stacked|grouped|by|as|,|$))/);
|
|
689
779
|
const pickText = (hint) => {
|
|
690
780
|
if (hint) {
|
|
@@ -698,11 +788,22 @@ function buildMockChart(prompt) {
|
|
|
698
788
|
// Heatmap and sankey are two-dimension charts: without a split the shape
|
|
699
789
|
// check downgrades them to a bar, so the mock would answer "heatmap" with a
|
|
700
790
|
// bar chart and look broken rather than offline.
|
|
701
|
-
const series = /\b(split|stack(?:ed)?|by product|by category|per)\b/.test(q) || type === 'heatmap' || type === 'sankey'
|
|
791
|
+
const series = /\b(split|stack(?:ed)?|by product|by category|per)\b/.test(q) || type === 'heatmap' || type === 'sankey' || type === 'chord' || type === 'stream'
|
|
702
792
|
? (textFields.find((t) => t !== dimension) ?? null)
|
|
703
793
|
: null;
|
|
704
794
|
const measure = numberFields.find((n) => q.includes(n.toLowerCase())) ?? numberFields[0] ?? null;
|
|
705
|
-
const reduce = /\b(average|avg|mean)\b/.test(q) ? 'avg'
|
|
795
|
+
const reduce = /\b(average|avg|mean)\b/.test(q) ? 'avg'
|
|
796
|
+
: /\b(median|typical)\b/.test(q) ? 'median'
|
|
797
|
+
: /\b(highest|peak|max(?:imum)?)\b/.test(q) ? 'max'
|
|
798
|
+
: /\b(lowest|min(?:imum)?)\b/.test(q) ? 'min'
|
|
799
|
+
: /\b(unique|distinct)\b/.test(q) ? 'countDistinct'
|
|
800
|
+
: /\b(count|number of|how many)\b/.test(q) ? 'count' : 'sum';
|
|
801
|
+
const bucket = /\b(monthly|per month|by month|each month)\b/.test(q) ? 'month'
|
|
802
|
+
: /\b(weekly|per week|by week|each week)\b/.test(q) ? 'week'
|
|
803
|
+
: /\b(daily|per day|by day|each day)\b/.test(q) ? 'day'
|
|
804
|
+
: /\b(quarterly|per quarter|by quarter)\b/.test(q) ? 'quarter'
|
|
805
|
+
: /\b(yearly|annually|per year|by year)\b/.test(q) ? 'year'
|
|
806
|
+
: null;
|
|
706
807
|
const stacked = /\bstack(ed)?\b/.test(q);
|
|
707
808
|
const logScale = /\b(log|logarithmic)\b/.test(q);
|
|
708
809
|
const timeAxis = /\b(over time|by date|by day|by month|by week|time series|timeline|trend)\b/.test(q);
|
|
@@ -711,11 +812,21 @@ function buildMockChart(prompt) {
|
|
|
711
812
|
: /\b(percent|percentage|%|rate|share)\b/.test(q)
|
|
712
813
|
? 'percent'
|
|
713
814
|
: 'number';
|
|
815
|
+
// A candlestick runs along a DATE, and the indicators are named in the request.
|
|
816
|
+
const dateField = typed.find((f) => f.type === 'date')?.name ?? null;
|
|
817
|
+
const indicators = type === 'candlestick'
|
|
818
|
+
? [
|
|
819
|
+
/\bvolume\b/.test(q) && 'volume', /\brsi\b|relative strength/.test(q) && 'rsi', /\bmacd\b/.test(q) && 'macd',
|
|
820
|
+
/\bstochastic\b/.test(q) && 'stochastic', /\batr\b|true range/.test(q) && 'atr', /\bobv\b|on.balance/.test(q) && 'obv',
|
|
821
|
+
/bollinger|\bbands?\b/.test(q) && 'bb', /\bvwap\b/.test(q) && 'vwap', /\bema\b|exponential/.test(q) && 'ema', /\bsma\b|moving average/.test(q) && 'sma',
|
|
822
|
+
].filter(Boolean)
|
|
823
|
+
: undefined;
|
|
714
824
|
return {
|
|
715
|
-
type, dimension, series, measure, reduce, stacked, logScale, timeAxis, valueFormat,
|
|
825
|
+
type, dimension: type === 'candlestick' ? (dateField ?? dimension) : dimension, series, measure, reduce, stacked, logScale, timeAxis, valueFormat, bucket,
|
|
826
|
+
...(indicators?.length ? { indicators } : {}),
|
|
716
827
|
rationale: `${type} chart of ${reduce}(${measure ?? 'value'}) by ${dimension ?? 'category'}` +
|
|
717
828
|
`${series ? `, split by ${series}` : ''}${stacked ? ', stacked' : ''}` +
|
|
718
|
-
`${logScale ? ', log scale' : ''}${timeAxis ? ', date axis' : ''} (mock - wire a real model for genuine parsing).`,
|
|
829
|
+
`${logScale ? ', log scale' : ''}${timeAxis ? ', date axis' : ''}${bucket ? `, by ${bucket}` : ''} (mock - wire a real model for genuine parsing).`,
|
|
719
830
|
};
|
|
720
831
|
}
|
|
721
832
|
function extractUserRequest(prompt) {
|
package/dist/aria.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// `aria-description` (ARIA 1.3) is what the chart uses to hand a screen
|
|
2
|
+
// reader its plain-language summary. Svelte's element typings predate it,
|
|
3
|
+
// so this adds the attribute to every element's ARIA set rather than casting
|
|
4
|
+
// at the one use site.
|
|
5
|
+
import 'svelte/elements'
|
|
6
|
+
|
|
7
|
+
declare module 'svelte/elements' {
|
|
8
|
+
interface AriaAttributes {
|
|
9
|
+
'aria-description'?: string | null | undefined
|
|
10
|
+
}
|
|
11
|
+
}
|
package/dist/build-api.js
CHANGED
|
@@ -2,6 +2,40 @@ import "./sv-grid-scrollbar.js";
|
|
|
2
2
|
import { untrack } from "svelte";
|
|
3
3
|
import { isGroupRow, getColumnAlign, columnDefMatchesId, } from "./cell-values.js";
|
|
4
4
|
import { hasAdvancedFilterEngine } from "./advanced-filter.svelte.js";
|
|
5
|
+
import { undoHistory, redoHistory } from "./history.js";
|
|
6
|
+
import { buildCommandContext } from "./command-context.js";
|
|
7
|
+
/**
|
|
8
|
+
* The selection as the user made it: committed ranges first, the active one
|
|
9
|
+
* last, each keeping its anchor as `from` and its focus as `to`. The active
|
|
10
|
+
* cell is the highlight of the range it sits in and of no other, which is
|
|
11
|
+
* how Handsontable reports it and what a range-aware paste needs to know.
|
|
12
|
+
*/
|
|
13
|
+
function selectedRanges(ctx) {
|
|
14
|
+
const committed = ctx.selectionRanges ?? [];
|
|
15
|
+
const active = ctx.selectionRange ?? { anchor: null, focus: null };
|
|
16
|
+
const cursor = ctx.grid.getState().activeCell;
|
|
17
|
+
const toRange = (r) => {
|
|
18
|
+
if (!r.anchor || !r.focus)
|
|
19
|
+
return null;
|
|
20
|
+
const from = { row: r.anchor.rowIndex, col: r.anchor.colIndex };
|
|
21
|
+
const to = { row: r.focus.rowIndex, col: r.focus.colIndex };
|
|
22
|
+
const inside = cursor &&
|
|
23
|
+
cursor.rowIndex >= Math.min(from.row, to.row) && cursor.rowIndex <= Math.max(from.row, to.row) &&
|
|
24
|
+
cursor.colIndex >= Math.min(from.col, to.col) && cursor.colIndex <= Math.max(from.col, to.col);
|
|
25
|
+
const highlight = inside ? { row: cursor.rowIndex, col: cursor.colIndex } : from;
|
|
26
|
+
return { from, to, highlight };
|
|
27
|
+
};
|
|
28
|
+
const out = [];
|
|
29
|
+
for (const r of committed) {
|
|
30
|
+
const o = toRange(r);
|
|
31
|
+
if (o)
|
|
32
|
+
out.push(o);
|
|
33
|
+
}
|
|
34
|
+
const last = toRange(active);
|
|
35
|
+
if (last)
|
|
36
|
+
out.push(last);
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
5
39
|
export function createGridApi(ctx) {
|
|
6
40
|
function buildApi() {
|
|
7
41
|
const findColumn = (columnId) => ctx.grid.getAllColumns().find((column) => column.id === columnId);
|
|
@@ -94,10 +128,26 @@ export function createGridApi(ctx) {
|
|
|
94
128
|
ctx.setActiveCell(active.anchor.rowIndex, active.anchor.colIndex);
|
|
95
129
|
},
|
|
96
130
|
getSelected() {
|
|
131
|
+
// Rectangles only. With cell selection off none is ever recorded and
|
|
132
|
+
// this stays [], which is how `selectable={false}` is observable from
|
|
133
|
+
// the API; the focused cell is `getActiveCell()`, not a selection.
|
|
97
134
|
return ctx
|
|
98
135
|
.getSelectionRects()
|
|
99
136
|
.map((r) => [r.minRow, r.minCol, r.maxRow, r.maxCol]);
|
|
100
137
|
},
|
|
138
|
+
getSelectedLast() {
|
|
139
|
+
const all = selectedRanges(ctx);
|
|
140
|
+
const last = all[all.length - 1];
|
|
141
|
+
return last ? [last.from.row, last.from.col, last.to.row, last.to.col] : undefined;
|
|
142
|
+
},
|
|
143
|
+
getSelectedRange() {
|
|
144
|
+
const all = selectedRanges(ctx);
|
|
145
|
+
return all.length ? all : undefined;
|
|
146
|
+
},
|
|
147
|
+
getSelectedRangeLast() {
|
|
148
|
+
const all = selectedRanges(ctx);
|
|
149
|
+
return all[all.length - 1];
|
|
150
|
+
},
|
|
101
151
|
openChart() {
|
|
102
152
|
ctx.chartPanelOpen = true;
|
|
103
153
|
},
|
|
@@ -116,6 +166,23 @@ export function createGridApi(ctx) {
|
|
|
116
166
|
if (ctx.chartingEnabled)
|
|
117
167
|
ctx.applyChartConfig(config);
|
|
118
168
|
},
|
|
169
|
+
saveChart(name) {
|
|
170
|
+
if (ctx.chartingEnabled)
|
|
171
|
+
ctx.saveChart(name);
|
|
172
|
+
},
|
|
173
|
+
applySavedChart(name) {
|
|
174
|
+
return ctx.chartingEnabled ? ctx.applySavedChart(name) : false;
|
|
175
|
+
},
|
|
176
|
+
removeSavedChart(name) {
|
|
177
|
+
if (ctx.chartingEnabled)
|
|
178
|
+
ctx.removeSavedChart(name);
|
|
179
|
+
},
|
|
180
|
+
getSavedCharts() {
|
|
181
|
+
return ctx.chartingEnabled ? ctx.getSavedCharts() : [];
|
|
182
|
+
},
|
|
183
|
+
setChartExplainHandler(handler) {
|
|
184
|
+
ctx.chartExplainHandler = handler ?? null;
|
|
185
|
+
},
|
|
119
186
|
setChartAiHandler(handler) {
|
|
120
187
|
ctx.chartAiHandler = handler ?? null;
|
|
121
188
|
},
|
|
@@ -316,6 +383,12 @@ export function createGridApi(ctx) {
|
|
|
316
383
|
clearFilter(columnId) {
|
|
317
384
|
ctx.clearColumnFilter(columnId);
|
|
318
385
|
},
|
|
386
|
+
openContextMenu(event, rowIndex, colIndex) {
|
|
387
|
+
const columnId = ctx.allColumns[colIndex]?.id;
|
|
388
|
+
if (columnId == null)
|
|
389
|
+
return;
|
|
390
|
+
ctx.openContextMenu(event, rowIndex, colIndex, columnId);
|
|
391
|
+
},
|
|
319
392
|
refreshEditorOptions(columnId) {
|
|
320
393
|
// Async `editorOptions` are cached per column (or per column+row for a
|
|
321
394
|
// per-row source) so opening an editor twice does not refetch. This
|
|
@@ -506,6 +579,31 @@ export function createGridApi(ctx) {
|
|
|
506
579
|
autosizeColumn(columnId) {
|
|
507
580
|
ctx.autosizeColumn(columnId);
|
|
508
581
|
},
|
|
582
|
+
getRowHeight(rowIndex) {
|
|
583
|
+
const own = ctx.rowResizeHeightPx(rowIndex);
|
|
584
|
+
if (own != null)
|
|
585
|
+
return own;
|
|
586
|
+
const declared = ctx.props.rowHeight;
|
|
587
|
+
return typeof declared === "function" ? declared(rowIndex) : (declared ?? 30);
|
|
588
|
+
},
|
|
589
|
+
setRowHeight(rowIndex, height) {
|
|
590
|
+
ctx.setRowResizeHeight(rowIndex, height);
|
|
591
|
+
},
|
|
592
|
+
setColumnCollapsed(columnId, collapsed) {
|
|
593
|
+
ctx.setColumnCollapsed(columnId, collapsed);
|
|
594
|
+
},
|
|
595
|
+
isColumnCollapsed(columnId) {
|
|
596
|
+
return !!ctx.collapsedColumns[columnId];
|
|
597
|
+
},
|
|
598
|
+
setRowCollapsed(rowIndex, collapsed) {
|
|
599
|
+
ctx.setRowCollapsed(rowIndex, collapsed);
|
|
600
|
+
},
|
|
601
|
+
getMergedCells() {
|
|
602
|
+
return (ctx.props.mergedCells ?? []).map((m) => ({ ...m }));
|
|
603
|
+
},
|
|
604
|
+
isRowCollapsed(rowIndex) {
|
|
605
|
+
return ctx.isRowCollapsed(rowIndex);
|
|
606
|
+
},
|
|
509
607
|
autosizeAllColumns() {
|
|
510
608
|
ctx.autosizeAllColumns();
|
|
511
609
|
},
|
|
@@ -550,28 +648,12 @@ export function createGridApi(ctx) {
|
|
|
550
648
|
ctx.grid.setExpanded(() => ({}));
|
|
551
649
|
},
|
|
552
650
|
// ---- Undo / redo (history + pointer)
|
|
553
|
-
undo() {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
ctx.applyHistoryStep(step, 'undo');
|
|
560
|
-
ctx.historyPtr -= 1;
|
|
561
|
-
ctx.historyVersion += 1;
|
|
562
|
-
return true;
|
|
563
|
-
},
|
|
564
|
-
redo() {
|
|
565
|
-
if (ctx.historyPtr >= ctx.history.length - 1)
|
|
566
|
-
return false;
|
|
567
|
-
const step = ctx.history[ctx.historyPtr + 1];
|
|
568
|
-
if (!step)
|
|
569
|
-
return false;
|
|
570
|
-
ctx.applyHistoryStep(step, 'redo');
|
|
571
|
-
ctx.historyPtr += 1;
|
|
572
|
-
ctx.historyVersion += 1;
|
|
573
|
-
return true;
|
|
574
|
-
},
|
|
651
|
+
undo() { return undoHistory(ctx); },
|
|
652
|
+
redo() { return redoHistory(ctx); },
|
|
653
|
+
// Built fresh per call rather than memoized: it is a handful of getters
|
|
654
|
+
// over `ctx`, and a caller that holds one keeps reading live state
|
|
655
|
+
// anyway, so caching would only add a slot to keep in sync.
|
|
656
|
+
getCommandContext() { return buildCommandContext(ctx, false); },
|
|
575
657
|
canUndo() { void ctx.historyVersion; return ctx.historyPtr >= 0; },
|
|
576
658
|
canRedo() { void ctx.historyVersion; return ctx.historyPtr < ctx.history.length - 1; },
|
|
577
659
|
clearHistory() { ctx.history = []; ctx.historyPtr = -1; ctx.historyVersion += 1; },
|
|
@@ -715,6 +797,8 @@ export function createGridApi(ctx) {
|
|
|
715
797
|
},
|
|
716
798
|
charts: ctx.getChartsState(),
|
|
717
799
|
chartActive: ctx.activeChartIndex,
|
|
800
|
+
// Only when there is one, for the same snapshot-churn reason.
|
|
801
|
+
...(ctx.savedCharts.length ? { savedCharts: ctx.getSavedCharts() } : {}),
|
|
718
802
|
}
|
|
719
803
|
: {}),
|
|
720
804
|
};
|
|
@@ -768,6 +852,8 @@ export function createGridApi(ctx) {
|
|
|
768
852
|
if ("advancedFilter" in state) {
|
|
769
853
|
ctx.advancedFilter = state.advancedFilter ?? null;
|
|
770
854
|
}
|
|
855
|
+
if (Array.isArray(state.savedCharts) && ctx.chartingEnabled)
|
|
856
|
+
ctx.applySavedCharts(state.savedCharts);
|
|
771
857
|
if ((state.chart || state.charts) && ctx.chartingEnabled) {
|
|
772
858
|
if (Array.isArray(state.charts) && state.charts.length) {
|
|
773
859
|
ctx.applyChartsState(state.charts, state.chartActive);
|