@svgrid/grid 3.0.3 → 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.
Files changed (281) hide show
  1. package/README.md +2 -1
  2. package/dist/GridMenus.svelte +44 -2
  3. package/dist/SvChartMenu.svelte +69 -0
  4. package/dist/SvChartMenu.svelte.d.ts +11 -0
  5. package/dist/SvChartPanes.svelte +113 -0
  6. package/dist/SvChartPanes.svelte.d.ts +20 -0
  7. package/dist/SvGrid.controller.svelte.d.ts +110 -39
  8. package/dist/SvGrid.controller.svelte.js +655 -200
  9. package/dist/SvGrid.css +105 -2
  10. package/dist/SvGrid.svelte +386 -514
  11. package/dist/SvGrid.types.d.ts +210 -9
  12. package/dist/SvGridCellEditor.svelte +17 -0
  13. package/dist/SvGridChart.svelte +2257 -329
  14. package/dist/SvGridChart.svelte.d.ts +6 -106
  15. package/dist/SvGridChart.types.d.ts +338 -0
  16. package/dist/SvGridChart.types.js +1 -0
  17. package/dist/SvGridChartBuilder.svelte +323 -0
  18. package/dist/SvGridChartBuilder.svelte.d.ts +34 -0
  19. package/dist/SvGridChartPanel.svelte +221 -275
  20. package/dist/SvGridChartPickers.svelte +417 -0
  21. package/dist/SvGridChartPickers.svelte.d.ts +70 -0
  22. package/dist/SvGridChartView.svelte +13 -14
  23. package/dist/SvModal.svelte +7 -1
  24. package/dist/SvModal.svelte.d.ts +5 -0
  25. package/dist/ai.d.ts +40 -8
  26. package/dist/ai.js +135 -24
  27. package/dist/aria.d.ts +11 -0
  28. package/dist/build-api.js +108 -22
  29. package/dist/cdn/GridMenus-7kbpnnBW.js +635 -0
  30. package/dist/cdn/GridMenus-BL7ZgQvU.js +636 -0
  31. package/dist/cdn/SvChartMenu-Bl6PBbkT.js +58 -0
  32. package/dist/cdn/SvChartMenu-FBSMINA6.js +59 -0
  33. package/dist/cdn/{SvDateRangeInput-DMLKmGEc.js → SvDateRangeInput-BeU_TkbQ.js} +15 -14
  34. package/dist/cdn/{SvDateRangeInput-CaOuMs8O.js → SvDateRangeInput-yX8vzleW.js} +38 -37
  35. package/dist/cdn/{SvDateTimePicker-sonaH0oh.js → SvDateTimePicker-DQwt4UAs.js} +139 -180
  36. package/dist/cdn/SvDateTimePicker-vNU6bZ-q.js +775 -0
  37. package/dist/cdn/{SvGridCellEditor-CjEJMJKc.js → SvGridCellEditor-B1p-vCK5.js} +188 -178
  38. package/dist/cdn/{SvGridCellEditor-BWnTi2N7.js → SvGridCellEditor-D_0q4xAu.js} +98 -88
  39. package/dist/cdn/SvGridChart-C3EWAZaM.js +3574 -0
  40. package/dist/cdn/SvGridChart-DzLkSwCH.js +3572 -0
  41. package/dist/cdn/SvGridChartBuilder-BE2T1ykB.js +783 -0
  42. package/dist/cdn/SvGridChartBuilder-CfII62sZ.js +784 -0
  43. package/dist/cdn/SvGridChartPanel-CCX5_Wgd.js +675 -0
  44. package/dist/cdn/SvGridChartPanel-xf4sWVTo.js +699 -0
  45. package/dist/cdn/SvGridChartView-CfuXmY5I.js +48 -0
  46. package/dist/cdn/SvGridChartView-DYwabQWj.js +47 -0
  47. package/dist/cdn/SvMenuList-CLN8OuIK.js +452 -0
  48. package/dist/cdn/SvMenuList-DkGhNKjf.js +451 -0
  49. package/dist/cdn/SvModal-BAE-pjZX.js +396 -0
  50. package/dist/cdn/SvModal-CIZWCcad.js +397 -0
  51. package/dist/cdn/chart-Dz7SqMXH.js +4341 -0
  52. package/dist/cdn/chart-export-BXqueeWJ.js +135 -0
  53. package/dist/cdn/chart-export-pdf-DONFUnNY.js +110 -0
  54. package/dist/cdn/chart-panel-messages-CUbf2R4i.js +1052 -0
  55. package/dist/cdn/chart-panel-messages-CmNrMdsr.js +1053 -0
  56. package/dist/cdn/chart-summary-BJW_tg_X.js +138 -0
  57. package/dist/cdn/chart-trend-CaN9mDEV.js +50 -0
  58. package/dist/cdn/chart-validate-HcOVFAfJ.js +188 -0
  59. package/dist/cdn/{column-resize-DsfNXMom.js → column-resize-DpmOLfRp.js} +3 -3
  60. package/dist/cdn/{date-format-BnnHlqGw.js → date-format-CtquV-p3.js} +583 -603
  61. package/dist/cdn/{date-format-BNii4zeD.js → date-format-D6KFzU_W.js} +347 -367
  62. package/dist/cdn/dismissable-DAHetSNk.js +44 -0
  63. package/dist/cdn/editor-contract-LXgQJAgd.js +22 -0
  64. package/dist/cdn/focus-trap-BBOQYBma.js +65 -0
  65. package/dist/cdn/{row-resize-BRcimkUT.js → row-resize-niQCp040.js} +44 -36
  66. package/dist/cdn/{src-D6XvA1Ij.js → src-BKhZ6eXd.js} +10227 -9787
  67. package/dist/cdn/{src-Bugf5XlI.js → src-V1uu8iE9.js} +6568 -6128
  68. package/dist/cdn/svgrid.js +26 -15
  69. package/dist/cdn/svgrid.svelte-external.js +26 -15
  70. package/dist/chart-axes.d.ts +53 -0
  71. package/dist/chart-axes.js +351 -0
  72. package/dist/chart-cartesian.d.ts +88 -0
  73. package/dist/chart-cartesian.js +1862 -0
  74. package/dist/chart-decimate.d.ts +51 -0
  75. package/dist/chart-decimate.js +199 -0
  76. package/dist/chart-export-pdf.d.ts +47 -0
  77. package/dist/chart-export-pdf.js +187 -0
  78. package/dist/chart-export.d.ts +2 -1
  79. package/dist/chart-export.js +18 -3
  80. package/dist/chart-financial.d.ts +91 -0
  81. package/dist/chart-financial.js +175 -0
  82. package/dist/chart-flow.d.ts +10 -0
  83. package/dist/chart-flow.js +298 -0
  84. package/dist/chart-format.d.ts +18 -0
  85. package/dist/chart-format.js +65 -0
  86. package/dist/chart-grid.d.ts +8 -0
  87. package/dist/chart-grid.js +190 -0
  88. package/dist/chart-hierarchy.d.ts +17 -0
  89. package/dist/chart-hierarchy.js +146 -0
  90. package/dist/chart-indicators.d.ts +115 -0
  91. package/dist/chart-indicators.js +389 -0
  92. package/dist/chart-messages.d.ts +95 -0
  93. package/dist/chart-messages.js +92 -0
  94. package/dist/chart-motion.d.ts +16 -0
  95. package/dist/chart-motion.js +141 -0
  96. package/dist/chart-panel-messages.d.ts +265 -0
  97. package/dist/chart-panel-messages.js +285 -0
  98. package/dist/chart-pivot.d.ts +87 -0
  99. package/dist/chart-pivot.js +156 -0
  100. package/dist/chart-polar.d.ts +25 -0
  101. package/dist/chart-polar.js +700 -0
  102. package/dist/chart-samples.d.ts +16 -0
  103. package/dist/chart-samples.js +153 -0
  104. package/dist/chart-scale.d.ts +113 -0
  105. package/dist/chart-scale.js +405 -0
  106. package/dist/chart-stats.d.ts +111 -0
  107. package/dist/chart-stats.js +419 -0
  108. package/dist/chart-stream.d.ts +45 -0
  109. package/dist/chart-stream.js +91 -0
  110. package/dist/chart-summary.d.ts +27 -0
  111. package/dist/chart-summary.js +205 -0
  112. package/dist/chart-sync.svelte.d.ts +32 -0
  113. package/dist/chart-sync.svelte.js +23 -0
  114. package/dist/chart-table.d.ts +35 -0
  115. package/dist/chart-table.js +165 -0
  116. package/dist/chart-trend.d.ts +19 -0
  117. package/dist/chart-trend.js +61 -0
  118. package/dist/chart-types.d.ts +1456 -0
  119. package/dist/chart-types.js +6 -0
  120. package/dist/chart-validate.d.ts +43 -0
  121. package/dist/chart-validate.js +272 -0
  122. package/dist/chart-zoom.d.ts +40 -0
  123. package/dist/chart-zoom.js +145 -0
  124. package/dist/chart.d.ts +186 -865
  125. package/dist/chart.js +460 -2204
  126. package/dist/clipboard.d.ts +1 -0
  127. package/dist/clipboard.js +137 -18
  128. package/dist/column-resize.d.ts +3 -0
  129. package/dist/column-resize.js +4 -1
  130. package/dist/columns.js +3 -0
  131. package/dist/command-context.d.ts +19 -0
  132. package/dist/command-context.js +116 -0
  133. package/dist/core.d.ts +7 -0
  134. package/dist/createPopoverSelect.svelte.d.ts +2 -2
  135. package/dist/editing.d.ts +2 -8
  136. package/dist/editing.js +262 -33
  137. package/dist/fill-patterns.d.ts +0 -5
  138. package/dist/fill-patterns.js +56 -2
  139. package/dist/grid-messages.d.ts +10 -3
  140. package/dist/grid-messages.js +5 -0
  141. package/dist/history.d.ts +83 -0
  142. package/dist/history.js +133 -0
  143. package/dist/index.d.ts +24 -6
  144. package/dist/index.js +20 -2
  145. package/dist/keyboard-handlers.js +88 -31
  146. package/dist/keyboard.d.ts +55 -0
  147. package/dist/keyboard.js +132 -0
  148. package/dist/menus.d.ts +3 -1
  149. package/dist/menus.js +7 -2
  150. package/dist/merges.d.ts +97 -0
  151. package/dist/merges.js +147 -0
  152. package/dist/row-resize.d.ts +3 -0
  153. package/dist/row-resize.js +14 -0
  154. package/dist/scroll-sync.d.ts +1 -0
  155. package/dist/scroll-sync.js +24 -0
  156. package/dist/selection.d.ts +16 -0
  157. package/dist/selection.js +108 -16
  158. package/dist/shortcut-registry.d.ts +145 -0
  159. package/dist/shortcut-registry.js +44 -0
  160. package/dist/svgrid-wrapper.types.d.ts +213 -22
  161. package/dist/virtualization/virtualizer.js +38 -17
  162. package/package.json +6 -1
  163. package/src/GridMenus.svelte +44 -2
  164. package/src/SvChartMenu.svelte +69 -0
  165. package/src/SvChartPanes.svelte +113 -0
  166. package/src/SvChartPanes.test.ts +66 -0
  167. package/src/SvGrid.controller.svelte.ts +629 -172
  168. package/src/SvGrid.css +105 -2
  169. package/src/SvGrid.svelte +386 -514
  170. package/src/SvGrid.types.ts +188 -8
  171. package/src/SvGridCellEditor.svelte +17 -0
  172. package/src/SvGridChart.svelte +2257 -329
  173. package/src/SvGridChart.test.ts +1419 -1
  174. package/src/SvGridChart.types.ts +336 -0
  175. package/src/SvGridChartBuilder.svelte +323 -0
  176. package/src/SvGridChartPanel.svelte +221 -275
  177. package/src/SvGridChartPickers.svelte +417 -0
  178. package/src/SvGridChartView.svelte +13 -14
  179. package/src/SvModal.svelte +7 -1
  180. package/src/SvModal.test.ts +9 -0
  181. package/src/ai.test.ts +57 -2
  182. package/src/ai.ts +166 -22
  183. package/src/aria.d.ts +11 -0
  184. package/src/build-api.coverage.test.ts +184 -0
  185. package/src/build-api.ts +107 -18
  186. package/src/chart-axes.test.ts +573 -0
  187. package/src/chart-axes.ts +342 -0
  188. package/src/chart-cartesian.ts +1863 -0
  189. package/src/chart-decimate.test.ts +249 -0
  190. package/src/chart-decimate.ts +186 -0
  191. package/src/chart-export-pdf.test.ts +91 -0
  192. package/src/chart-export-pdf.ts +207 -0
  193. package/src/chart-export.test.ts +14 -0
  194. package/src/chart-export.ts +13 -3
  195. package/src/chart-financial.ts +208 -0
  196. package/src/chart-flow.ts +286 -0
  197. package/src/chart-format.ts +67 -0
  198. package/src/chart-grid.ts +179 -0
  199. package/src/chart-hierarchy.ts +135 -0
  200. package/src/chart-indicators.test.ts +443 -0
  201. package/src/chart-indicators.ts +384 -0
  202. package/src/chart-messages.test.ts +21 -0
  203. package/src/chart-messages.ts +184 -0
  204. package/src/chart-motion.test.ts +71 -0
  205. package/src/chart-motion.ts +148 -0
  206. package/src/chart-panel-messages.test.ts +29 -0
  207. package/src/chart-panel-messages.ts +550 -0
  208. package/src/chart-pivot.test.ts +54 -0
  209. package/src/chart-pivot.ts +225 -0
  210. package/src/chart-polar.ts +674 -0
  211. package/src/chart-samples.ts +106 -0
  212. package/src/chart-scale.ts +394 -0
  213. package/src/chart-series-types.test.ts +714 -0
  214. package/src/chart-stats.ts +409 -0
  215. package/src/chart-stream.test.ts +75 -0
  216. package/src/chart-stream.ts +114 -0
  217. package/src/chart-summary.test.ts +90 -0
  218. package/src/chart-summary.ts +197 -0
  219. package/src/chart-sync.svelte.ts +41 -0
  220. package/src/chart-table.test.ts +76 -0
  221. package/src/chart-table.ts +178 -0
  222. package/src/chart-trend.ts +47 -0
  223. package/src/chart-types.ts +1309 -0
  224. package/src/chart-validate.test.ts +100 -0
  225. package/src/chart-validate.ts +247 -0
  226. package/src/chart-zoom.test.ts +137 -0
  227. package/src/chart-zoom.ts +124 -0
  228. package/src/chart.coverage.test.ts +81 -0
  229. package/src/chart.test.ts +56 -3
  230. package/src/chart.ts +557 -2784
  231. package/src/clipboard.test.ts +151 -0
  232. package/src/clipboard.ts +145 -16
  233. package/src/column-resize.ts +7 -1
  234. package/src/columns.ts +2 -0
  235. package/src/command-context.test.ts +200 -0
  236. package/src/command-context.ts +137 -0
  237. package/src/core.ts +7 -0
  238. package/src/editing.test.ts +281 -3
  239. package/src/editing.ts +256 -38
  240. package/src/fill-patterns.test.ts +35 -0
  241. package/src/fill-patterns.ts +56 -2
  242. package/src/grid-messages.ts +16 -3
  243. package/src/history.test.ts +196 -0
  244. package/src/history.ts +162 -0
  245. package/src/icon-seam.test.ts +3 -2
  246. package/src/index.ts +150 -2
  247. package/src/keyboard-handlers.coverage.test.ts +126 -6
  248. package/src/keyboard-handlers.ts +91 -30
  249. package/src/keyboard-shortcuts.seam.test.ts +200 -0
  250. package/src/keyboard.test.ts +124 -1
  251. package/src/keyboard.ts +142 -0
  252. package/src/menus.test.ts +26 -0
  253. package/src/menus.ts +10 -3
  254. package/src/merges.test.ts +112 -0
  255. package/src/merges.ts +168 -0
  256. package/src/pivot.grid.test.ts +40 -5
  257. package/src/row-resize.test.ts +44 -0
  258. package/src/row-resize.ts +17 -0
  259. package/src/scroll-sync.test.ts +42 -1
  260. package/src/scroll-sync.ts +25 -0
  261. package/src/selection.multi-range.test.ts +34 -0
  262. package/src/selection.test.ts +30 -2
  263. package/src/selection.ts +111 -19
  264. package/src/shortcut-registry.test.ts +97 -0
  265. package/src/shortcut-registry.ts +181 -0
  266. package/src/svgrid-wrapper.types.ts +184 -13
  267. package/src/svgrid.behavior.test.ts +135 -0
  268. package/src/svgrid.charting.test.ts +513 -3
  269. package/src/svgrid.context-menu.test.ts +31 -0
  270. package/src/svgrid.new-features.wrapper.test.ts +10 -3
  271. package/src/virtualization/virtualizer.ts +39 -17
  272. package/dist/cdn/GridMenus-CHOMnW-c.js +0 -602
  273. package/dist/cdn/GridMenus-DhdPF700.js +0 -603
  274. package/dist/cdn/SvDateTimePicker-CCbDZNZB.js +0 -816
  275. package/dist/cdn/SvGridChart-1PMSp2LZ.js +0 -1481
  276. package/dist/cdn/SvGridChart-D02avhyr.js +0 -1480
  277. package/dist/cdn/SvGridChartPanel-DJnViOv4.js +0 -834
  278. package/dist/cdn/SvGridChartPanel-vPTNerFZ.js +0 -810
  279. package/dist/cdn/SvGridChartView-C--HhdUo.js +0 -56
  280. package/dist/cdn/SvGridChartView-KyyFG-wY.js +0 -55
  281. package/dist/cdn/chart-DLg3_zTQ.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. Two
274
- * families are deliberately absent, because a plan cannot express them:
275
- * scatter needs a SECOND measure for its y axis, and candlestick / OHLC need
276
- * four under first/max/min/last. Asking for either would produce a plan the
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: 'sum' | 'avg' | 'count';
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
- if (plan.type === 'gauge')
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 = ['sum', 'avg', 'count'].includes(plan.reduce) ? plan.reduce : 'sum';
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 = /\bfunnel\b|drop.?off|conversion|\bstages?\b|pipeline/.test(q) ? 'funnel'
676
- : /\bwaterfall\b|running total|contribution|\bbridge\b/.test(q) ? 'waterfall'
677
- : /\btree ?map\b|nested|hierarch/.test(q) ? 'treemap'
678
- : /\bradar\b|spider|multi.?measure/.test(q) ? 'radar'
679
- : /\bheat ?map\b/.test(q) ? 'heatmap'
680
- : /\bbox ?plot\b|spread|distribution|variance|outlier|percentile|quartile/.test(q) ? 'boxplot'
681
- : /\bgauge\b|\bdial\b|vs target|against target/.test(q) ? 'gauge'
682
- : /\bcalendar\b|daily activity|year of/.test(q) ? 'calendar'
683
- : /\bsankey\b|\bflow\b/.test(q) ? 'sankey'
684
- : /\bline\b|\btrend\b|over time/.test(q) ? 'line'
685
- : /\barea\b/.test(q) ? 'area'
686
- : /\bpie\b|share|proportion|breakdown/.test(q) ? 'pie'
687
- : 'bar';
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' : /\b(count|number of|how many)\b/.test(q) ? 'count' : 'sum';
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
- if (ctx.historyPtr < 0)
555
- return false;
556
- const step = ctx.history[ctx.historyPtr];
557
- if (!step)
558
- return false;
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);