@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.
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-DNbz94Ct.js → src-BKhZ6eXd.js} +10227 -9787
  67. package/dist/cdn/{src-BJ2opd0j.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-DyFA-QxA.js +0 -602
  273. package/dist/cdn/GridMenus-M2XP9t0_.js +0 -603
  274. package/dist/cdn/SvDateTimePicker-CCbDZNZB.js +0 -816
  275. package/dist/cdn/SvGridChart-DAb9RbRs.js +0 -1480
  276. package/dist/cdn/SvGridChart-Yl-IXZFO.js +0 -1481
  277. package/dist/cdn/SvGridChartPanel-DayQpDsR.js +0 -834
  278. package/dist/cdn/SvGridChartPanel-aqf8n-vJ.js +0 -810
  279. package/dist/cdn/SvGridChartView-CxfakxR0.js +0 -56
  280. package/dist/cdn/SvGridChartView-Dm_YEx9L.js +0 -55
  281. package/dist/cdn/chart-CKJRCPOk.js +0 -1652
@@ -0,0 +1,336 @@
1
+ /**
2
+ * The props of `SvChart` / `SvGridChart`, in a plain `.ts` file so tooling
3
+ * that cannot parse a `.svelte` component (the web-component surface
4
+ * generator, the API reference) can read them.
5
+ */
6
+ import type { Snippet } from 'svelte'
7
+ import type { ChartDataLabelConfig, ChartDrawing, ChartDrawingKind, ChartGeometry, ChartPointRef, ChartScales, ChartSelection, ChartSpec, ChartZoomWindow } from './chart-types'
8
+ import type { ChartRangePreset } from './chart-zoom'
9
+ import type { ChartMessages } from './chart-messages'
10
+ import type { MenuItem } from './menu-item'
11
+
12
+ /** The point `onHover` reports: the category, its index in the drawn
13
+ * window, the series nearest the pointer (or null) and that series' value. */
14
+ export type ChartHoverPoint = { category: string; index: number; series: string | null; value: number | null }
15
+
16
+ /** What a custom mark is handed. `scales` is null on non-cartesian types. */
17
+ export type ChartRenderContext = {
18
+ geo: ChartGeometry
19
+ /** Pixel x at the centre of category `i`. Null when there is no x axis. */
20
+ xOf: ChartScales['xOf'] | null
21
+ /** Pixel y for a value. Null when there is no value axis. */
22
+ yOf: ChartScales['yOf'] | null
23
+ scales: ChartScales | null
24
+ }
25
+
26
+ /** One row of the default tooltip. */
27
+ export type ChartTooltipRow = { label?: string; color?: string; value: string }
28
+
29
+ /**
30
+ * What a custom `tooltip` snippet or `tooltipFormat` receives: the hovered
31
+ * category, its index, and the rows the default tooltip would have shown
32
+ * (already formatted), plus the single series when `tooltipMode` is
33
+ * `'single'` or the hover was on one mark.
34
+ */
35
+ export type ChartTooltipContext = {
36
+ /** The category (x label) under the pointer, or the mark's own label. */
37
+ category: string
38
+ /** Its index in the visible categories, or -1 for a non-category mark. */
39
+ index: number
40
+ /** The default rows, one per visible series (or per number of a candle). */
41
+ rows: ChartTooltipRow[]
42
+ /** The series under the pointer when the tooltip is for one series. */
43
+ series?: string
44
+ /** That series' value at the category, when known. */
45
+ value?: number
46
+ }
47
+
48
+ /** What a custom `legendItem` snippet receives. */
49
+ export type ChartLegendItemContext = {
50
+ label: string
51
+ color: string
52
+ /** The series (or slice) is toggled off. */
53
+ off: boolean
54
+ index: number
55
+ }
56
+
57
+ /**
58
+ * Which zoom gestures the plot answers to. `zoomable: true` is drag only,
59
+ * the behaviour the chart always had.
60
+ */
61
+ export type ChartZoomConfig = {
62
+ /** Rubber-band a rectangle to zoom into it. Default true. */
63
+ drag?: boolean
64
+ /** Mouse wheel zooms around the pointer. `'modifier'` needs Ctrl / Cmd held,
65
+ * so the page still scrolls over the chart. Default false. */
66
+ wheel?: boolean | 'modifier'
67
+ /** Two-finger pinch on touch screens. Default true when `wheel` is set. */
68
+ pinch?: boolean
69
+ /** Drag to pan a zoomed chart: `'shift'` with Shift held, `'mode'` through a
70
+ * toolbar toggle, `true` both. Default false. */
71
+ pan?: boolean | 'shift' | 'mode'
72
+ /** Which axes the rubber band zooms. Default `'x'`. */
73
+ axis?: 'x' | 'y' | 'xy'
74
+ }
75
+
76
+ /** How the chart animates. `animate: true` turns on the data-update tween. */
77
+ export type ChartAnimateConfig = {
78
+ /** Tween length in ms. Default 400. */
79
+ duration?: number
80
+ /** How marks first appear: fade (default), grow from the baseline, or wipe
81
+ * left to right. `'none'` draws them in place. */
82
+ enter?: 'fade' | 'grow' | 'wipe' | 'none'
83
+ /** Slide marks to their new positions when the data changes. Default true
84
+ * when `animate` is set at all. */
85
+ update?: boolean
86
+ }
87
+
88
+ /** What the context menu's custom items are built against. */
89
+ export type ChartContextTarget = {
90
+ category?: string
91
+ index?: number
92
+ series?: string
93
+ value?: number
94
+ }
95
+
96
+ /** Where the legend sits. `true` is `'bottom'`. */
97
+ export type ChartLegendPosition = 'top' | 'bottom' | 'left' | 'right'
98
+
99
+ /**
100
+ * The props of `SvChart` (alias `SvGridChart`). Everything is optional but
101
+ * `spec`; the defaults are noted per field.
102
+ */
103
+ export type SvChartProps = {
104
+ spec: ChartSpec
105
+ /** Show the (clickable) legend, and where. `true` = below the chart.
106
+ * Default true. */
107
+ legend?: boolean | ChartLegendPosition
108
+ /** Render each legend entry yourself. Receives the label, colour, whether
109
+ * it is toggled off, and its index. The click / double-click / hover
110
+ * behaviour stays on the button around it. */
111
+ legendItem?: Snippet<[ChartLegendItemContext]>
112
+ /** Enable tooltips + crosshair + legend toggling. Default true. */
113
+ interactive?: boolean
114
+ /** Draw the value on each bar / point / slice. `true` uses the defaults;
115
+ * an object picks placement, a formatter and overlap hiding. On a pie,
116
+ * `{ placement: 'outside' }` draws callout labels with leader lines.
117
+ * Unset, the spec's own `dataLabels` applies; default off. */
118
+ dataLabels?: boolean | ChartDataLabelConfig
119
+ /** Format a value for tooltips, data labels, AND Y-axis ticks. */
120
+ formatValue?: (value: number) => string
121
+ /**
122
+ * Render the tooltip body yourself. Receives the hovered category, the
123
+ * default rows (already formatted) and, for a single-series hover, that
124
+ * series. The positioning, flipping and theming stay with the chart.
125
+ */
126
+ tooltip?: Snippet<[ChartTooltipContext]>
127
+ /** Rewrite the default tooltip's title and rows without taking over the
128
+ * markup. Return only what you want to change. */
129
+ tooltipFormat?: (ctx: ChartTooltipContext) => { title?: string; rows?: ChartTooltipRow[] }
130
+ /** `'shared'` (default) lists every series at the hovered category;
131
+ * `'single'` shows only the series nearest the pointer. */
132
+ tooltipMode?: 'shared' | 'single'
133
+ /**
134
+ * Where the tooltip sits: `'follow'` (default) beside the pointer, or
135
+ * pinned in a corner of the plot so it never covers the mark under the
136
+ * pointer on a dense chart and never flips.
137
+ */
138
+ tooltipPosition?: 'follow' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
139
+ /**
140
+ * A click pins the tooltip: it stays while the pointer leaves, its text
141
+ * can be selected and copied, and a second click on the same category,
142
+ * Escape or a click outside the chart releases it. A tap on a touch screen
143
+ * shows and pins in one. Default false.
144
+ */
145
+ tooltipSticky?: boolean
146
+ /** Fired when a category / slice is clicked (drill into the grid). */
147
+ onSelect?: (selection: ChartSelection) => void
148
+ /** Like `onSelect`, but only fires when the spec carries `rowIds` and
149
+ * includes them in the payload. Use this to filter / highlight the
150
+ * source grid when the user clicks a chart element. */
151
+ onDrill?: (selection: ChartSelection) => void
152
+ /** Allow zooming the plot: `true` for drag-to-zoom (double-click resets),
153
+ * or an object choosing wheel, pinch, pan and the axes. Cartesian charts
154
+ * only. Default false. */
155
+ zoomable?: boolean | ChartZoomConfig
156
+ /**
157
+ * The visible window, bindable: read it to persist a zoom, set it to zoom
158
+ * from outside (`null` is the whole chart). The component also exposes
159
+ * `zoomTo(window)` and `resetZoom()` through `bind:this`.
160
+ */
161
+ zoom?: ChartZoomWindow | null
162
+ /** Fired whenever the window changes, by gesture, preset, API or sync. */
163
+ onZoom?: (window: ChartZoomWindow | null) => void
164
+ /**
165
+ * Range buttons in the toolbar for a time axis: `true` for 1W / 1M / 3M /
166
+ * 6M / YTD / 1Y / All, or your own list (`'1M'`, or
167
+ * `{ label, days | from, to }`). Also in the context menu.
168
+ */
169
+ rangePresets?: boolean | ChartRangePreset[]
170
+ /**
171
+ * Charts sharing a group name share their crosshair and their zoom window.
172
+ * Categories are matched by label, then by date when both parse, so a daily
173
+ * and a weekly chart still line up.
174
+ */
175
+ syncGroup?: string
176
+ /**
177
+ * A right-click menu: `true` for the built-in items (export, copy, reset
178
+ * zoom, series toggles, presets), an array of your own `MenuItem`s appended
179
+ * after them, or a function of the clicked point returning that array.
180
+ */
181
+ contextMenu?: boolean | MenuItem[] | ((target: ChartContextTarget) => MenuItem[])
182
+ /**
183
+ * Motion: `true` for a 400ms data-update tween with the default fade-in, or
184
+ * an object choosing the enter effect and duration. Respects
185
+ * `prefers-reduced-motion`. Dense charts (thousands of points) never tween.
186
+ * A feed faster than the tween wants `live` instead.
187
+ */
188
+ animate?: boolean | ChartAnimateConfig
189
+ /**
190
+ * The spec is a live feed (see `appendPoints`): skip the data-update tween
191
+ * and the enter effect, because a tween restarted every few hundred
192
+ * milliseconds never settles and an enter effect replays on every tick.
193
+ * Default false.
194
+ */
195
+ live?: boolean
196
+ /**
197
+ * Drill into a tree map, sunburst or pie (built from `tree`) by clicking a
198
+ * node with children; the toolbar grows a breadcrumb back up. Default false.
199
+ */
200
+ drillable?: boolean
201
+ /** The current drill path (node names from the root), bindable. */
202
+ drillPath?: string[]
203
+ /**
204
+ * Let readers select points: `'single'` (or `true`) keeps one, Ctrl / Shift
205
+ * adds; `'multi'` toggles on every click. Unselected marks dim while there
206
+ * is a selection. Default false.
207
+ */
208
+ selectable?: boolean | 'single' | 'multi'
209
+ /** The selected points, bindable. */
210
+ selected?: ChartPointRef[]
211
+ onSelectionChange?: (selected: ChartPointRef[]) => void
212
+ /**
213
+ * The point under the pointer or the keyboard focus, or `null` when it
214
+ * leaves. Fires once per change, not per pixel: mirror it into a KPI, a
215
+ * linked table row or another chart. `series` is null when the whole
216
+ * category is hovered and no series is near.
217
+ */
218
+ onHover?: (point: ChartHoverPoint | null) => void
219
+ /**
220
+ * Hovering a mark dims the other series so the one under the pointer
221
+ * stands out, the way the legend does on hover. On a category chart the
222
+ * series within 18px of the pointer is the one; farther away, nothing
223
+ * dims. Default true; `false` keeps every series at full opacity.
224
+ */
225
+ hoverHighlight?: boolean
226
+ /** Announce the focused point to screen readers through a live region.
227
+ * Default true. */
228
+ announce?: boolean
229
+ /**
230
+ * Describe the chart in plain words (`chartSummary`: trend and extremes
231
+ * per series, the biggest slices, a scatter's correlation, the last close)
232
+ * as the SVG's `aria-description` and in the data table's caption, and
233
+ * offer "Describe chart" in the context menu, which copies the sentence.
234
+ * Default true.
235
+ */
236
+ describe?: boolean
237
+ /**
238
+ * Translations for the chart's own strings: toolbar buttons and titles,
239
+ * the context menu, the legend's hints, the empty state and what it says
240
+ * to assistive technology. A partial map; unset keys stay English. See
241
+ * {@link ChartMessages} for every key.
242
+ */
243
+ localeText?: Partial<ChartMessages>
244
+ /**
245
+ * Read the crosshair off the axes: the hovered category in a pill on the x
246
+ * axis and the value under the pointer in a pill on the value axis, the way
247
+ * a trading chart reads. Default true on cartesian charts; `false` keeps
248
+ * the crosshair line alone.
249
+ */
250
+ crosshairLabels?: boolean
251
+ /** Show a compact brush / mini-map under the main chart. A draggable
252
+ * window selects what the main chart shows. Pairs naturally with
253
+ * `zoomable`. Default false. */
254
+ brush?: boolean
255
+ /** Height of the brush strip in pixels. Default 88. */
256
+ brushHeight?: number
257
+ /** Show a small toolbar above the chart: reset-zoom (when zoomed) and
258
+ * export (PNG / SVG / copy). Default true when zoomable or onDrill is
259
+ * set, otherwise false. Set to `false` to hide explicitly. */
260
+ toolbar?: boolean
261
+ /** Explicit chart pixel size (viewBox). When set, the chart lays out to
262
+ * fit exactly - used by the docked panel to size the chart to its body. */
263
+ width?: number
264
+ height?: number
265
+ /**
266
+ * Size the chart to its container. The SVG is laid out at the container's
267
+ * pixel size (minus the toolbar, legend and brush) and re-laid-out when it
268
+ * resizes, so axis labels never squash and the plot uses every pixel. Give
269
+ * the chart its height through a flex or grid parent that stretches it; in a
270
+ * plain block it follows the width and keeps its own `height` (default 300).
271
+ * Explicit `width` / `height` win. Default false.
272
+ */
273
+ autosize?: boolean
274
+ /**
275
+ * Draw your own marks in the chart's own coordinates - the custom-series
276
+ * seam. `underlay` paints beneath the built-in marks (bands, shaded
277
+ * regions, a background you want the bars to sit on); `overlay` paints
278
+ * above them but below the crosshair and the hit layer, so tooltips and
279
+ * clicks keep working over whatever you draw.
280
+ *
281
+ * Both receive the laid-out geometry and its scales, so a custom mark lands
282
+ * on exactly the axis the built-in ones did:
283
+ *
284
+ * ```svelte
285
+ * <SvChart {spec}>
286
+ * {#snippet overlay({ geo, xOf, yOf })}
287
+ * <circle cx={xOf(3)} cy={yOf(120)} r="5" fill="tomato" />
288
+ * {/snippet}
289
+ * </SvChart>
290
+ * ```
291
+ *
292
+ * `xOf` / `yOf` are null for the types with no cartesian axes (pie, gauge,
293
+ * treemap, sankey, calendar, radar, funnel); `geo` is always there.
294
+ */
295
+ underlay?: Snippet<[ChartRenderContext]>
296
+ overlay?: Snippet<[ChartRenderContext]>
297
+ /**
298
+ * Let a reader pin annotations on the plot. Adds an "Annotate" toggle to
299
+ * the toolbar; while it is on, clicking the plot fires `onAnnotate` with
300
+ * the point that was clicked, and clicking an existing marker fires
301
+ * `onAnnotationRemove` with its index.
302
+ *
303
+ * The chart does NOT store them. It owns the gesture - which you cannot
304
+ * build from outside, because drag is already zoom and click is already
305
+ * drill - and hands back the data point; where the annotations live, and
306
+ * whether they survive a reload, is the host's business. Put what comes
307
+ * back into `spec.annotations` and they render like any other.
308
+ */
309
+ annotatable?: boolean
310
+ /**
311
+ * A point the reader picked while annotating. It is a DATA point, not a
312
+ * pixel: annotate mode takes over the category gesture, so a note anchors
313
+ * to the category it was pinned on and stays there when the chart is
314
+ * re-laid-out, re-sorted or zoomed. That also means it works from the
315
+ * keyboard, since the category hit zones already navigate with arrows.
316
+ */
317
+ onAnnotate?: (at: {
318
+ /** The category picked, and its index. */
319
+ category: string
320
+ index: number
321
+ /** The value at that category on the first series. */
322
+ value: number
323
+ }) => void
324
+ /** An existing `spec.annotations` entry was clicked, by index. */
325
+ onAnnotationRemove?: (index: number) => void
326
+ /**
327
+ * Drawing tools in the toolbar: `true` for all of them (trend line,
328
+ * horizontal ray, Fibonacci retracement, rectangle, arrow, text) or a list
329
+ * of kinds. A tool takes the plot click over; the drawings are DATA points
330
+ * the host stores in `spec.drawings`. Cartesian charts only.
331
+ */
332
+ drawable?: boolean | ChartDrawingKind[]
333
+ /** The drawings after a reader added, moved or removed one. Store them in
334
+ * `spec.drawings` and they render like any other. */
335
+ onDrawingsChange?: (drawings: ChartDrawing[]) => void
336
+ }
@@ -0,0 +1,323 @@
1
+ <script lang="ts" generics="TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData">
2
+ /**
3
+ * The chart builder behind the grid panel's Build button: a modal with a
4
+ * type gallery of live thumbnails, the same pickers the panel shows (one
5
+ * component, SvGridChartPickers, laid out as a form), and a Format tab for
6
+ * the things the derived spec does not carry (titles, axis bounds, legend
7
+ * placement, per-series colour / type / axis / stack, label placement,
8
+ * series and crosshair labels, a compact rule for narrow widths, palette,
9
+ * font and colours).
10
+ *
11
+ * Format edits are plain data ({@link ChartFormatState}) on the chart tab,
12
+ * applied by the engine to every spec the panel derives, so they survive a
13
+ * data change and round-trip through the saved view. Loaded lazily by the
14
+ * panel on the first open. Every label comes from the panel's messages, so
15
+ * `localization.text` translates the builder too.
16
+ */
17
+ import SvModal from "./SvModal.svelte";
18
+ import SvTabs from "./SvTabs.svelte";
19
+ import SvGridChart from "./SvGridChart.svelte";
20
+ import SvGridChartPickers, { type ChartPanelTypeDef } from "./SvGridChartPickers.svelte";
21
+ import { sampleChartThumb } from "./chart-samples";
22
+ import { chartPanelMessage, type GridChartPanelMessages } from "./chart-panel-messages";
23
+ import type { ChartFormatState, ChartMarkerShape, ChartValueFormat, RowData, TableFeatures } from "./index";
24
+ import type { SvGridController } from "./SvGrid.controller.svelte";
25
+
26
+ let {
27
+ ctrl,
28
+ open = $bindable(false),
29
+ types,
30
+ messages: m,
31
+ }: {
32
+ ctrl: SvGridController<TFeatures, TData>;
33
+ open?: boolean;
34
+ /** The types the panel offers for the current columns, in its groups. */
35
+ types: ChartPanelTypeDef[];
36
+ messages: GridChartPanelMessages;
37
+ } = $props();
38
+
39
+ let tab = $state("type");
40
+ const groups = $derived([...new Set(types.map((t) => t.group))]);
41
+ const spec = $derived(ctrl.chartSpec);
42
+ const palette = $derived(ctrl.chartPalette ?? undefined);
43
+ const seriesLabels = $derived(spec?.series.map((s) => s.label) ?? []);
44
+
45
+ const fmt = $derived<ChartFormatState>(ctrl.chartFormat ?? {});
46
+ function patch(p: Partial<ChartFormatState>) {
47
+ ctrl.chartFormat = { ...fmt, ...p };
48
+ }
49
+ function patchAxis(key: "xAxis" | "yAxis" | "y2Axis", p: Record<string, unknown>) {
50
+ patch({ [key]: { ...(fmt[key] ?? {}), ...p } } as Partial<ChartFormatState>);
51
+ }
52
+ function patchSeries(label: string, p: NonNullable<ChartFormatState["series"]>[string]) {
53
+ patch({ series: { ...(fmt.series ?? {}), [label]: { ...(fmt.series?.[label] ?? {}), ...p } } });
54
+ }
55
+ function patchStyle(p: NonNullable<ChartFormatState["style"]>) {
56
+ patch({ style: { ...(fmt.style ?? {}), ...p } });
57
+ }
58
+ const num = (v: string): number | null => (v.trim() === "" ? null : Number(v));
59
+ const hasRightAxis = $derived(!!spec?.series.some((s) => s.axis === "right") || !!fmt.series && Object.values(fmt.series).some((s) => s?.axis === "right"));
60
+ /** Stacks only mean something on bars and areas; the column shows for a chart that has any. */
61
+ const hasStackable = $derived(!!spec && ["bar", "area"].includes(spec.type));
62
+ const MARKERS: ChartMarkerShape[] = ["circle", "square", "diamond", "triangle", "cross", "none"];
63
+ const FORMATS: ChartValueFormat[] = ["number", "currency", "percent", "compact"];
64
+ </script>
65
+
66
+ <SvModal bind:open title={m.chartBuilderTitle} size="lg">
67
+ <div class="sv-grid-chart-builder">
68
+ <SvTabs
69
+ tabs={[{ id: "type", label: m.chartBuilderTabType }, { id: "data", label: m.chartBuilderTabData }, { id: "format", label: m.chartBuilderTabFormat }]}
70
+ bind:value={tab}
71
+ ariaLabel={m.chartBuilderTitle}
72
+ >
73
+ {#snippet panel(id)}
74
+ {#if id === "type"}
75
+ <div class="sv-grid-chart-builder-gallery">
76
+ {#each groups as g (g)}
77
+ <div class="sv-grid-chart-builder-group">{g}</div>
78
+ <div class="sv-grid-chart-builder-cards">
79
+ {#each types.filter((t) => t.group === g) as t (t.value)}
80
+ <button
81
+ type="button"
82
+ class="sv-grid-chart-builder-card"
83
+ class:is-selected={ctrl.chartType === t.value}
84
+ aria-pressed={ctrl.chartType === t.value}
85
+ title={t.label}
86
+ onclick={() => (ctrl.chartType = t.value)}
87
+ >
88
+ <div class="sv-grid-chart-builder-thumb">
89
+ <SvGridChart spec={sampleChartThumb(t.value, palette)} interactive={false} legend={false} toolbar={false} announce={false} />
90
+ </div>
91
+ <span class="sv-grid-chart-builder-card-label">{t.label}</span>
92
+ </button>
93
+ {/each}
94
+ </div>
95
+ {/each}
96
+ </div>
97
+ {:else if id === "data"}
98
+ <div class="sv-grid-chart-builder-form">
99
+ <SvGridChartPickers {ctrl} messages={m} scope="builder" />
100
+ <label class="sv-grid-chart-builder-row">
101
+ <span>{m.chartBuilderLinked}</span>
102
+ <input type="checkbox" checked={!ctrl.chartFrozen} onchange={(e) => (e.currentTarget.checked ? ctrl.unfreezeChart() : ctrl.freezeChart())} />
103
+ <small>{ctrl.chartFrozen ? chartPanelMessage(m.chartBuilderFrozenAt, { time: new Date(ctrl.chartFrozen.at).toLocaleTimeString() }) : m.chartBuilderFollows}</small>
104
+ </label>
105
+ </div>
106
+ {:else}
107
+ <div class="sv-grid-chart-builder-form">
108
+ <label class="sv-grid-chart-builder-row"><span>{m.chartBuilderTitleField}</span><input type="text" value={fmt.title ?? ""} oninput={(e) => patch({ title: e.currentTarget.value })} /></label>
109
+ <label class="sv-grid-chart-builder-row"><span>{m.chartBuilderSubtitle}</span><input type="text" value={fmt.subtitle ?? ""} oninput={(e) => patch({ subtitle: e.currentTarget.value })} /></label>
110
+ <label class="sv-grid-chart-builder-row"><span>{m.chartBuilderCaption}</span><input type="text" value={fmt.caption ?? ""} oninput={(e) => patch({ caption: e.currentTarget.value })} /></label>
111
+ <label class="sv-grid-chart-builder-row">
112
+ <span>{m.chartBuilderLegend}</span>
113
+ <select value={fmt.legend === false ? "none" : (fmt.legend ?? "bottom")} onchange={(e) => patch({ legend: e.currentTarget.value === "none" ? false : (e.currentTarget.value as "top" | "bottom" | "left" | "right") })}>
114
+ <option value="bottom">{m.chartLegendBottom}</option><option value="top">{m.chartLegendTop}</option><option value="left">{m.chartLegendLeft}</option><option value="right">{m.chartLegendRight}</option><option value="none">{m.chartLegendHidden}</option>
115
+ </select>
116
+ </label>
117
+ <label class="sv-grid-chart-builder-row">
118
+ <span>{m.chartBuilderDataLabels}</span>
119
+ <select value={fmt.dataLabels === null ? "none" : (fmt.dataLabels ?? "auto")} onchange={(e) => patch({ dataLabels: e.currentTarget.value === "auto" ? undefined : e.currentTarget.value === "none" ? null : (e.currentTarget.value as "inside" | "outside" | "top" | "center") })}>
120
+ <option value="auto">{m.chartBuilderPanelSetting}</option><option value="top">{m.chartLabelsTop}</option><option value="outside">{m.chartLabelsOutside}</option><option value="inside">{m.chartLabelsInside}</option><option value="center">{m.chartLabelsCenter}</option><option value="none">{m.chartLabelsHidden}</option>
121
+ </select>
122
+ </label>
123
+ <label class="sv-grid-chart-builder-row"><span>{m.chartBuilderSeriesLabels}</span><input type="checkbox" checked={fmt.seriesLabels === true} onchange={(e) => patch({ seriesLabels: e.currentTarget.checked })} /></label>
124
+ <label class="sv-grid-chart-builder-row"><span>{m.chartBuilderCrosshairLabels}</span><input type="checkbox" checked={fmt.crosshairLabels !== false} onchange={(e) => patch({ crosshairLabels: e.currentTarget.checked })} /></label>
125
+ <label class="sv-grid-chart-builder-row" title={m.chartBuilderCompactBelowTitle}>
126
+ <span>{m.chartBuilderCompactBelow}</span>
127
+ <input type="number" min="0" step="10" value={fmt.compactBelow ?? ""} placeholder={m.chartBuilderOff} onchange={(e) => patch({ compactBelow: num(e.currentTarget.value) })} />
128
+ </label>
129
+ <fieldset class="sv-grid-chart-builder-fs">
130
+ <legend>{m.chartBuilderValueAxis}</legend>
131
+ <label class="sv-grid-chart-builder-row"><span>{m.chartBuilderTitleField}</span><input type="text" value={fmt.yAxis?.title ?? ""} oninput={(e) => patchAxis("yAxis", { title: e.currentTarget.value })} /></label>
132
+ <label class="sv-grid-chart-builder-row"><span>{m.chartBuilderMin}</span><input type="number" value={fmt.yAxis?.min ?? ""} placeholder={m.chartBuilderAuto} onchange={(e) => patchAxis("yAxis", { min: num(e.currentTarget.value) })} /></label>
133
+ <label class="sv-grid-chart-builder-row"><span>{m.chartBuilderMax}</span><input type="number" value={fmt.yAxis?.max ?? ""} placeholder={m.chartBuilderAuto} onchange={(e) => patchAxis("yAxis", { max: num(e.currentTarget.value) })} /></label>
134
+ <label class="sv-grid-chart-builder-row">
135
+ <span>{m.chartFormat}</span>
136
+ <select value={fmt.yAxis?.format ?? ""} onchange={(e) => patchAxis("yAxis", { format: (e.currentTarget.value || undefined) as ChartValueFormat | undefined })}>
137
+ <option value="">{m.chartBuilderPanelSetting}</option>{#each FORMATS as f (f)}<option value={f}>{f}</option>{/each}
138
+ </select>
139
+ </label>
140
+ <label class="sv-grid-chart-builder-row"><span>{m.chartBuilderGridLines}</span><input type="checkbox" checked={fmt.yAxis?.gridLines !== false} onchange={(e) => patchAxis("yAxis", { gridLines: e.currentTarget.checked })} /></label>
141
+ </fieldset>
142
+ {#if hasRightAxis}
143
+ <fieldset class="sv-grid-chart-builder-fs">
144
+ <legend>{m.chartBuilderRightAxis}</legend>
145
+ <label class="sv-grid-chart-builder-row"><span>{m.chartBuilderTitleField}</span><input type="text" value={fmt.y2Axis?.title ?? ""} oninput={(e) => patchAxis("y2Axis", { title: e.currentTarget.value })} /></label>
146
+ <label class="sv-grid-chart-builder-row"><span>{m.chartBuilderMin}</span><input type="number" value={fmt.y2Axis?.min ?? ""} placeholder={m.chartBuilderAuto} onchange={(e) => patchAxis("y2Axis", { min: num(e.currentTarget.value) })} /></label>
147
+ <label class="sv-grid-chart-builder-row"><span>{m.chartBuilderMax}</span><input type="number" value={fmt.y2Axis?.max ?? ""} placeholder={m.chartBuilderAuto} onchange={(e) => patchAxis("y2Axis", { max: num(e.currentTarget.value) })} /></label>
148
+ </fieldset>
149
+ {/if}
150
+ <fieldset class="sv-grid-chart-builder-fs">
151
+ <legend>{m.chartBuilderCategoryAxis}</legend>
152
+ <label class="sv-grid-chart-builder-row"><span>{m.chartBuilderTitleField}</span><input type="text" value={fmt.xAxis?.title ?? ""} oninput={(e) => patchAxis("xAxis", { title: e.currentTarget.value })} /></label>
153
+ <label class="sv-grid-chart-builder-row"><span>{m.chartBuilderGridLines}</span><input type="checkbox" checked={fmt.xAxis?.gridLines === true} onchange={(e) => patchAxis("xAxis", { gridLines: e.currentTarget.checked })} /></label>
154
+ <label class="sv-grid-chart-builder-row">
155
+ <span>{m.chartBuilderAxisLabels}</span>
156
+ <select value={fmt.xAxis?.labelRotation === undefined ? "auto" : String(fmt.xAxis.labelRotation)} onchange={(e) => patchAxis("xAxis", { labelRotation: e.currentTarget.value === "auto" ? "auto" : Number(e.currentTarget.value) })}>
157
+ <option value="auto">{m.chartBuilderLabelsAuto}</option><option value="0">{m.chartBuilderLabelsHorizontal}</option><option value="-45">{m.chartBuilderLabelsTilted}</option><option value="-90">{m.chartBuilderLabelsVertical}</option>
158
+ </select>
159
+ </label>
160
+ </fieldset>
161
+ {#if seriesLabels.length}
162
+ <fieldset class="sv-grid-chart-builder-fs">
163
+ <legend>{m.chartBuilderSeries}</legend>
164
+ <div class="sv-grid-chart-builder-series-head" class:has-stack={hasStackable}><span>{m.chartBuilderSeries}</span><span>{m.chartBuilderColor}</span><span>{m.chartBuilderSeriesType}</span><span>{m.chartBuilderAxis}</span><span>{m.chartBuilderMarker}</span><span>{m.chartBuilderWidth}</span>{#if hasStackable}<span>{m.chartBuilderStack}</span>{/if}</div>
165
+ {#each seriesLabels as label (label)}
166
+ {@const f = fmt.series?.[label] ?? {}}
167
+ {@const s = spec?.series.find((x) => x.label === label)}
168
+ <div class="sv-grid-chart-builder-series" class:has-stack={hasStackable} data-series={label}>
169
+ <span class="sv-grid-chart-builder-series-name" title={label}>{label}</span>
170
+ <input type="color" value={f.color ?? s?.color ?? "#2563eb"} aria-label={chartPanelMessage(m.chartBuilderSeriesColorLabel, { series: label })} oninput={(e) => patchSeries(label, { color: e.currentTarget.value })} />
171
+ <select value={f.type ?? s?.type ?? ""} aria-label={chartPanelMessage(m.chartBuilderSeriesTypeLabel, { series: label })} onchange={(e) => patchSeries(label, { type: (e.currentTarget.value || undefined) as "bar" | "line" | "area" | undefined })}>
172
+ <option value="">{m.chartBuilderChartType}</option><option value="bar">{m.chartBuilderBar}</option><option value="line">{m.chartBuilderLine}</option><option value="area">{m.chartBuilderArea}</option>
173
+ </select>
174
+ <select value={f.axis ?? s?.axis ?? "left"} aria-label={chartPanelMessage(m.chartBuilderSeriesAxisLabel, { series: label })} onchange={(e) => patchSeries(label, { axis: e.currentTarget.value as "left" | "right" })}>
175
+ <option value="left">{m.chartBuilderLeft}</option><option value="right">{m.chartBuilderRight}</option>
176
+ </select>
177
+ <select value={f.marker ?? "circle"} aria-label={chartPanelMessage(m.chartBuilderSeriesMarkerLabel, { series: label })} onchange={(e) => patchSeries(label, { marker: e.currentTarget.value as ChartMarkerShape })}>
178
+ {#each MARKERS as mk (mk)}<option value={mk}>{mk}</option>{/each}
179
+ </select>
180
+ <input type="number" min="0.5" max="8" step="0.5" value={f.strokeWidth ?? 2} aria-label={chartPanelMessage(m.chartBuilderSeriesWidthLabel, { series: label })} onchange={(e) => patchSeries(label, { strokeWidth: Number(e.currentTarget.value) || 2 })} />
181
+ {#if hasStackable}
182
+ <input type="text" value={f.stack ?? s?.stack ?? ""} placeholder={m.chartBuilderStackPlaceholder} aria-label={chartPanelMessage(m.chartBuilderSeriesStackLabel, { series: label })} onchange={(e) => patchSeries(label, { stack: e.currentTarget.value.trim() })} />
183
+ {/if}
184
+ </div>
185
+ {/each}
186
+ </fieldset>
187
+ {/if}
188
+ <fieldset class="sv-grid-chart-builder-fs">
189
+ <legend>{m.chartBuilderStyle}</legend>
190
+ <label class="sv-grid-chart-builder-row"><span>{m.chartBuilderFontSize}</span><input type="number" min="8" max="32" value={fmt.style?.fontSize ?? ""} placeholder="12" onchange={(e) => patchStyle({ fontSize: num(e.currentTarget.value) ?? undefined })} /></label>
191
+ <label class="sv-grid-chart-builder-row"><span>{m.chartBuilderFontFamily}</span><input type="text" value={fmt.style?.fontFamily ?? ""} oninput={(e) => patchStyle({ fontFamily: e.currentTarget.value || undefined })} /></label>
192
+ <label class="sv-grid-chart-builder-row"><span>{m.chartBuilderBackground}</span><input type="text" value={fmt.style?.background ?? ""} placeholder="transparent" oninput={(e) => patchStyle({ background: e.currentTarget.value || undefined })} /></label>
193
+ <label class="sv-grid-chart-builder-row"><span>{m.chartBuilderTextColor}</span><input type="text" value={fmt.style?.textColor ?? ""} placeholder="inherit" oninput={(e) => patchStyle({ textColor: e.currentTarget.value || undefined })} /></label>
194
+ </fieldset>
195
+ <div class="sv-grid-chart-builder-actions">
196
+ <button type="button" class="sv-grid-chart-btn" onclick={() => (ctrl.chartFormat = null)}>{m.chartBuilderResetFormat}</button>
197
+ </div>
198
+ </div>
199
+ {/if}
200
+ {/snippet}
201
+ </SvTabs>
202
+ </div>
203
+ </SvModal>
204
+
205
+ <style>
206
+ .sv-grid-chart-builder {
207
+ min-height: 420px;
208
+ max-height: 70vh;
209
+ overflow: auto;
210
+ font-size: 12px;
211
+ }
212
+ .sv-grid-chart-builder-group {
213
+ font-size: 11px;
214
+ font-weight: 600;
215
+ color: var(--sg-muted, #64748b);
216
+ text-transform: uppercase;
217
+ letter-spacing: 0.04em;
218
+ margin: 10px 0 6px;
219
+ }
220
+ .sv-grid-chart-builder-cards {
221
+ display: grid;
222
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
223
+ gap: 8px;
224
+ }
225
+ .sv-grid-chart-builder-card {
226
+ display: flex;
227
+ flex-direction: column;
228
+ gap: 4px;
229
+ padding: 6px;
230
+ border: 1px solid var(--sg-border, #e2e8f0);
231
+ border-radius: 8px;
232
+ background: var(--sg-bg, #fff);
233
+ color: var(--sg-fg, #0f172a);
234
+ cursor: pointer;
235
+ text-align: left;
236
+ font: inherit;
237
+ }
238
+ .sv-grid-chart-builder-card:hover { border-color: var(--sg-accent, #2563eb); }
239
+ .sv-grid-chart-builder-card.is-selected {
240
+ border-color: var(--sg-accent, #2563eb);
241
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-accent, #2563eb) 30%, transparent 70%);
242
+ }
243
+ .sv-grid-chart-builder-thumb {
244
+ height: 92px;
245
+ overflow: hidden;
246
+ pointer-events: none;
247
+ }
248
+ .sv-grid-chart-builder-card-label {
249
+ font-size: 11px;
250
+ font-weight: 600;
251
+ }
252
+ .sv-grid-chart-builder-form {
253
+ display: flex;
254
+ flex-direction: column;
255
+ gap: 8px;
256
+ padding: 8px 2px;
257
+ }
258
+ .sv-grid-chart-builder-row {
259
+ display: grid;
260
+ grid-template-columns: 110px 1fr;
261
+ align-items: center;
262
+ gap: 8px;
263
+ }
264
+ .sv-grid-chart-builder-row > span {
265
+ color: var(--sg-muted, #64748b);
266
+ }
267
+ .sv-grid-chart-builder-row > small {
268
+ grid-column: 2;
269
+ color: var(--sg-muted, #64748b);
270
+ }
271
+ .sv-grid-chart-builder-row > input[type="checkbox"] {
272
+ justify-self: start;
273
+ }
274
+ .sv-grid-chart-builder-row input[type="text"],
275
+ .sv-grid-chart-builder-row input[type="number"],
276
+ .sv-grid-chart-builder-row select,
277
+ .sv-grid-chart-builder-series input,
278
+ .sv-grid-chart-builder-series select {
279
+ font: inherit;
280
+ border: 1px solid var(--sg-border, #e2e8f0);
281
+ border-radius: 6px;
282
+ background: var(--sg-bg, #fff);
283
+ color: var(--sg-fg, #0f172a);
284
+ padding: 3px 6px;
285
+ min-width: 0;
286
+ }
287
+ .sv-grid-chart-builder-fs {
288
+ border: 1px solid var(--sg-border, #e2e8f0);
289
+ border-radius: 8px;
290
+ padding: 6px 10px 10px;
291
+ display: flex;
292
+ flex-direction: column;
293
+ gap: 6px;
294
+ }
295
+ .sv-grid-chart-builder-fs > legend {
296
+ font-weight: 600;
297
+ padding: 0 4px;
298
+ }
299
+ .sv-grid-chart-builder-series,
300
+ .sv-grid-chart-builder-series-head {
301
+ display: grid;
302
+ grid-template-columns: minmax(80px, 1.4fr) 44px 1fr 1fr 1fr 64px;
303
+ gap: 6px;
304
+ align-items: center;
305
+ }
306
+ .sv-grid-chart-builder-series.has-stack,
307
+ .sv-grid-chart-builder-series-head.has-stack {
308
+ grid-template-columns: minmax(80px, 1.4fr) 44px 1fr 1fr 1fr 64px 72px;
309
+ }
310
+ .sv-grid-chart-builder-series-head {
311
+ color: var(--sg-muted, #64748b);
312
+ font-size: 11px;
313
+ }
314
+ .sv-grid-chart-builder-series-name {
315
+ overflow: hidden;
316
+ text-overflow: ellipsis;
317
+ white-space: nowrap;
318
+ }
319
+ .sv-grid-chart-builder-actions {
320
+ display: flex;
321
+ justify-content: flex-end;
322
+ }
323
+ </style>