@svgrid/grid 3.0.0 → 3.0.2

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 (304) hide show
  1. package/CHANGELOG.md +116 -88
  2. package/README.md +199 -199
  3. package/dist/FlexRender.svelte +96 -96
  4. package/dist/GridFooter.svelte +190 -181
  5. package/dist/GridFooter.svelte.d.ts +8 -0
  6. package/dist/GridMenus.svelte +18 -10
  7. package/dist/GridMenus.svelte.d.ts +2 -1
  8. package/dist/SvAutoComplete.svelte +169 -169
  9. package/dist/SvAvatar.svelte +75 -75
  10. package/dist/SvCalendar.svelte +503 -503
  11. package/dist/SvCarousel.svelte +141 -141
  12. package/dist/SvCheckBox.svelte +102 -102
  13. package/dist/SvCircularProgress.svelte +109 -109
  14. package/dist/SvColorInput.svelte +181 -181
  15. package/dist/SvComboBox.svelte +279 -279
  16. package/dist/SvContextMenu.svelte +116 -116
  17. package/dist/SvCountryInput.svelte +163 -163
  18. package/dist/SvDrawer.svelte +254 -254
  19. package/dist/SvDropDownList.svelte +378 -378
  20. package/dist/SvDurationInput.svelte +126 -126
  21. package/dist/SvField.svelte +293 -293
  22. package/dist/SvForm.svelte +437 -437
  23. package/dist/SvGrid.controller.svelte.d.ts +17 -0
  24. package/dist/SvGrid.controller.svelte.js +128 -14
  25. package/dist/SvGrid.css +79 -10
  26. package/dist/SvGrid.svelte +191 -186
  27. package/dist/SvGrid.types.d.ts +42 -1
  28. package/dist/SvGridCellEditor.svelte +748 -744
  29. package/dist/SvGridChart.svelte +2246 -1716
  30. package/dist/SvGridChart.svelte.d.ts +63 -1
  31. package/dist/SvGridChartPanel.svelte +633 -485
  32. package/dist/SvGridChartView.svelte +79 -70
  33. package/dist/SvGridChartView.svelte.d.ts +7 -0
  34. package/dist/SvGridDropdown.svelte +728 -728
  35. package/dist/SvGridSelect.svelte +270 -270
  36. package/dist/SvGroupCell.svelte +141 -116
  37. package/dist/SvGroupCell.svelte.d.ts +8 -0
  38. package/dist/SvListBox.svelte +334 -334
  39. package/dist/SvMaskedInput.svelte +122 -122
  40. package/dist/SvMenu.svelte +124 -124
  41. package/dist/SvMenuList.svelte +146 -146
  42. package/dist/SvMultiSelect.svelte +293 -293
  43. package/dist/SvNumberInput.svelte +172 -172
  44. package/dist/SvOtpInput.svelte +158 -158
  45. package/dist/SvPasswordInput.svelte +151 -151
  46. package/dist/SvPhoneInput.svelte +133 -133
  47. package/dist/SvPopover.svelte +197 -197
  48. package/dist/SvProgress.svelte +116 -116
  49. package/dist/SvRadioGroup.svelte +107 -107
  50. package/dist/SvRating.svelte +112 -112
  51. package/dist/SvResult.svelte +73 -73
  52. package/dist/SvRichText.svelte +211 -211
  53. package/dist/SvRowGroupPanel.svelte +186 -170
  54. package/dist/SvRowGroupPanel.svelte.d.ts +8 -0
  55. package/dist/SvScrollArea.svelte +61 -61
  56. package/dist/SvSlider.svelte +203 -203
  57. package/dist/SvSwitchButton.svelte +108 -108
  58. package/dist/SvTagsInput.svelte +115 -115
  59. package/dist/SvTextInput.svelte +147 -147
  60. package/dist/SvTimePicker.svelte +245 -245
  61. package/dist/SvToaster.svelte +159 -159
  62. package/dist/SvToggleButton.svelte +85 -85
  63. package/dist/SvTooltip.svelte +161 -161
  64. package/dist/SvTour.svelte +208 -208
  65. package/dist/SvTree.svelte +444 -444
  66. package/dist/SvTreeSelect.svelte +239 -239
  67. package/dist/ai.d.ts +11 -2
  68. package/dist/ai.js +51 -7
  69. package/dist/cdn/GridMenus-DyFA-QxA.js +602 -0
  70. package/dist/cdn/GridMenus-M2XP9t0_.js +603 -0
  71. package/dist/cdn/{SvDateRangeInput-DflbiP7N.js → SvDateRangeInput-CaOuMs8O.js} +1 -1
  72. package/dist/cdn/{SvDateRangeInput-Bh0A0JkF.js → SvDateRangeInput-DMLKmGEc.js} +1 -1
  73. package/dist/cdn/{SvDateTimePicker-Bivn8dAP.js → SvDateTimePicker-CCbDZNZB.js} +1 -1
  74. package/dist/cdn/{SvDateTimePicker-BWwpfB_o.js → SvDateTimePicker-sonaH0oh.js} +1 -1
  75. package/dist/cdn/{SvGridCellEditor-Ba7rY3eu.js → SvGridCellEditor-BWnTi2N7.js} +2 -2
  76. package/dist/cdn/{SvGridCellEditor-AXL8cHGO.js → SvGridCellEditor-CjEJMJKc.js} +2 -2
  77. package/dist/cdn/SvGridChart-DAb9RbRs.js +1480 -0
  78. package/dist/cdn/SvGridChart-Yl-IXZFO.js +1481 -0
  79. package/dist/cdn/SvGridChartPanel-DayQpDsR.js +834 -0
  80. package/dist/cdn/SvGridChartPanel-aqf8n-vJ.js +810 -0
  81. package/dist/cdn/{SvGridChartView-eSPuJE6g.js → SvGridChartView-CxfakxR0.js} +5 -3
  82. package/dist/cdn/{SvGridChartView-SmPW10dI.js → SvGridChartView-Dm_YEx9L.js} +5 -3
  83. package/dist/cdn/{SvGridDropdown-B1ZcLpgs.js → SvGridDropdown-D0VdjeR8.js} +1 -1
  84. package/dist/cdn/{SvGridDropdown-CtdbHcIS.js → SvGridDropdown-D13MtJ2j.js} +1 -1
  85. package/dist/cdn/cell-formatting-C2Hf5gqd.js +86 -0
  86. package/dist/cdn/chart-CKJRCPOk.js +1652 -0
  87. package/dist/cdn/{date-format-DZT7T1wf.js → date-format-BNii4zeD.js} +2 -2
  88. package/dist/cdn/{date-format-CcP1tafP.js → date-format-BnnHlqGw.js} +2 -2
  89. package/dist/cdn/{export-format-Cv1Dll6k.js → export-format-CUDLy2yn.js} +84 -168
  90. package/dist/cdn/{src-B_YS5AOc.js → src-BJ2opd0j.js} +3642 -3450
  91. package/dist/cdn/{src-DYXXpuSk.js → src-DNbz94Ct.js} +8649 -8457
  92. package/dist/cdn/svgrid.js +16 -15
  93. package/dist/cdn/svgrid.svelte-external.js +16 -15
  94. package/dist/cell-formatting.d.ts +7 -0
  95. package/dist/cell-formatting.js +7 -1
  96. package/dist/chart-export.d.ts +23 -2
  97. package/dist/chart-export.js +88 -14
  98. package/dist/chart.d.ts +410 -18
  99. package/dist/chart.js +824 -24
  100. package/dist/editing.js +22 -13
  101. package/dist/grid-icons.d.ts +49 -0
  102. package/dist/grid-icons.js +86 -0
  103. package/dist/index.d.ts +3 -2
  104. package/dist/index.js +3 -2
  105. package/package.json +1 -1
  106. package/src/FlexRender.svelte +96 -96
  107. package/src/GridFooter.svelte +190 -181
  108. package/src/GridMenus.svelte +18 -10
  109. package/src/SvAutoComplete.svelte +169 -169
  110. package/src/SvAvatar.svelte +75 -75
  111. package/src/SvCalendar.svelte +503 -503
  112. package/src/SvCalendar.test.ts +226 -226
  113. package/src/SvCarousel.svelte +141 -141
  114. package/src/SvCheckBox.svelte +102 -102
  115. package/src/SvCircularProgress.svelte +109 -109
  116. package/src/SvColorInput.svelte +181 -181
  117. package/src/SvComboBox.svelte +279 -279
  118. package/src/SvContextMenu.svelte +116 -116
  119. package/src/SvCountryInput.svelte +163 -163
  120. package/src/SvDrawer.svelte +254 -254
  121. package/src/SvDropDownList.svelte +378 -378
  122. package/src/SvDurationInput.svelte +126 -126
  123. package/src/SvField.svelte +293 -293
  124. package/src/SvForm.svelte +437 -437
  125. package/src/SvForm.test.ts +411 -411
  126. package/src/SvGrid.controller.svelte.ts +146 -13
  127. package/src/SvGrid.css +79 -10
  128. package/src/SvGrid.svelte +191 -186
  129. package/src/SvGrid.types.ts +2133 -2092
  130. package/src/SvGridCellEditor.svelte +748 -744
  131. package/src/SvGridChart.svelte +2246 -1716
  132. package/src/SvGridChart.test.ts +243 -1
  133. package/src/SvGridChartPanel.svelte +633 -485
  134. package/src/SvGridChartView.svelte +79 -70
  135. package/src/SvGridDropdown.svelte +728 -728
  136. package/src/SvGridSelect.svelte +270 -270
  137. package/src/SvGroupCell.svelte +141 -116
  138. package/src/SvListBox.svelte +334 -334
  139. package/src/SvMaskedInput.svelte +122 -122
  140. package/src/SvMenu.svelte +124 -124
  141. package/src/SvMenu.test.ts +97 -97
  142. package/src/SvMenuList.svelte +146 -146
  143. package/src/SvMultiSelect.svelte +293 -293
  144. package/src/SvNumberInput.svelte +172 -172
  145. package/src/SvOtpInput.svelte +158 -158
  146. package/src/SvPasswordInput.svelte +151 -151
  147. package/src/SvPhoneInput.svelte +133 -133
  148. package/src/SvPopover.svelte +197 -197
  149. package/src/SvProgress.svelte +116 -116
  150. package/src/SvRadioGroup.svelte +107 -107
  151. package/src/SvRating.svelte +112 -112
  152. package/src/SvResult.svelte +73 -73
  153. package/src/SvRichText.svelte +211 -211
  154. package/src/SvRowGroupPanel.svelte +186 -170
  155. package/src/SvScrollArea.svelte +61 -61
  156. package/src/SvSlider.svelte +203 -203
  157. package/src/SvSwitchButton.svelte +108 -108
  158. package/src/SvTagsInput.svelte +115 -115
  159. package/src/SvTextInput.svelte +147 -147
  160. package/src/SvTimePicker.svelte +245 -245
  161. package/src/SvToaster.svelte +159 -159
  162. package/src/SvToaster.test.ts +95 -95
  163. package/src/SvToggleButton.svelte +85 -85
  164. package/src/SvTooltip.svelte +161 -161
  165. package/src/SvTour.svelte +208 -208
  166. package/src/SvTree.svelte +444 -444
  167. package/src/SvTreeSelect.svelte +239 -239
  168. package/src/a11y/dismissable.test.ts +119 -119
  169. package/src/a11y/dismissable.ts +114 -114
  170. package/src/a11y.contract.test.ts +49 -49
  171. package/src/a11y.test.ts +59 -59
  172. package/src/a11y.ts +61 -61
  173. package/src/ai.test.ts +561 -502
  174. package/src/ai.ts +1472 -1419
  175. package/src/build-api.coverage.test.ts +633 -633
  176. package/src/build-api.ts +846 -846
  177. package/src/builtin-editors.grid.test.ts +83 -83
  178. package/src/cell-formatting.ts +177 -171
  179. package/src/cell-render.test.ts +513 -513
  180. package/src/cell-render.ts +496 -496
  181. package/src/cell-values.ts +148 -148
  182. package/src/chart-export.test.ts +112 -0
  183. package/src/chart-export.ts +270 -202
  184. package/src/chart-view.svelte.ts +36 -36
  185. package/src/chart.test.ts +830 -1
  186. package/src/chart.ts +3342 -2321
  187. package/src/collaboration.test.ts +104 -104
  188. package/src/collaboration.ts +167 -167
  189. package/src/column-groups.ts +78 -78
  190. package/src/core.performance.test.ts +30 -30
  191. package/src/core.ts +1865 -1865
  192. package/src/createAutocomplete.svelte.ts +132 -132
  193. package/src/createCombobox.svelte.ts +191 -191
  194. package/src/createCountryInput.svelte.ts +157 -157
  195. package/src/createDropdownList.svelte.ts +168 -168
  196. package/src/createForm.svelte.ts +386 -386
  197. package/src/createGrid.svelte.ts +42 -42
  198. package/src/createGrid.test.ts +10 -10
  199. package/src/createGridState.svelte.ts +17 -17
  200. package/src/createListbox.svelte.ts +250 -250
  201. package/src/createMenu.svelte.ts +224 -224
  202. package/src/createPopoverSelect.svelte.ts +213 -213
  203. package/src/createSlider.svelte.ts +191 -191
  204. package/src/createTooltip.svelte.ts +144 -144
  205. package/src/createTree.svelte.ts +322 -322
  206. package/src/datetime/date-core.ts +206 -206
  207. package/src/datetime/date-restrict.ts +61 -61
  208. package/src/datetime/timezone.ts +135 -135
  209. package/src/dock-manager-model.ts +596 -596
  210. package/src/dock-model.ts +374 -374
  211. package/src/editing.test.ts +974 -974
  212. package/src/editing.ts +618 -609
  213. package/src/editor-contract.ts +171 -171
  214. package/src/editor-registry.grid.test.ts +144 -144
  215. package/src/editor-registry.ts +122 -122
  216. package/src/export-data-api.test.ts +126 -126
  217. package/src/export-format.test.ts +107 -107
  218. package/src/export-format.ts +601 -601
  219. package/src/filter-operators.ts +160 -160
  220. package/src/filtering/excel-filters.ts +325 -325
  221. package/src/flex-render.ts +3 -3
  222. package/src/form-field.ts +127 -127
  223. package/src/grid-icons.test.ts +46 -0
  224. package/src/grid-icons.ts +184 -0
  225. package/src/group-display.test.ts +167 -167
  226. package/src/group-display.ts +200 -200
  227. package/src/headless.ts +87 -87
  228. package/src/icon-overrides.grid.test.ts +188 -0
  229. package/src/icon-seam.test.ts +100 -0
  230. package/src/index.ts +19 -0
  231. package/src/js-scroller.svelte.ts +173 -173
  232. package/src/keyboard-handlers.ts +270 -270
  233. package/src/keyboard.test.ts +59 -59
  234. package/src/keyboard.ts +97 -97
  235. package/src/list-nav.test.ts +49 -49
  236. package/src/list-nav.ts +29 -29
  237. package/src/list-option.test.ts +56 -56
  238. package/src/list-option.ts +179 -179
  239. package/src/menus.ts +597 -597
  240. package/src/merge-objects.ts +48 -48
  241. package/src/overlays.test.ts +90 -90
  242. package/src/positioning.ts +268 -268
  243. package/src/render-component.ts +28 -28
  244. package/src/row-drag.test.ts +401 -401
  245. package/src/row-drag.ts +419 -419
  246. package/src/row-resize.test.ts +524 -524
  247. package/src/row-resize.ts +228 -228
  248. package/src/scheduler-ical.ts +181 -181
  249. package/src/scheduler-model.test.ts +562 -562
  250. package/src/scheduler-model.ts +873 -873
  251. package/src/selection.test.ts +885 -885
  252. package/src/server-data-source.test.ts +383 -383
  253. package/src/server-data-source.ts +469 -469
  254. package/src/sparkline.test.ts +68 -68
  255. package/src/sparkline.ts +169 -169
  256. package/src/spreadsheet.test.ts +488 -488
  257. package/src/spreadsheet.ts +312 -312
  258. package/src/static-functions.ts +11 -11
  259. package/src/subscribe.ts +38 -38
  260. package/src/summaries.ts +113 -113
  261. package/src/svgrid-wrapper.types.ts +563 -563
  262. package/src/svgrid.async-editor-options.test.ts +273 -273
  263. package/src/svgrid.auto-row-height.test.ts +204 -204
  264. package/src/svgrid.behavior.test.ts +910 -910
  265. package/src/svgrid.charting.test.ts +654 -534
  266. package/src/svgrid.comments-autocomplete.test.ts +127 -127
  267. package/src/svgrid.context-menu.test.ts +147 -147
  268. package/src/svgrid.features.test.ts +157 -157
  269. package/src/svgrid.filter-depth.test.ts +163 -163
  270. package/src/svgrid.filter-menu-listbox.svelte.test.ts +382 -377
  271. package/src/svgrid.filter-menu-scroll.test.ts +112 -112
  272. package/src/svgrid.grand-total.test.ts +188 -188
  273. package/src/svgrid.group-display-mode.test.ts +171 -171
  274. package/src/svgrid.group-footers.test.ts +121 -121
  275. package/src/svgrid.group-pagination.test.ts +153 -153
  276. package/src/svgrid.new-features.wrapper.test.ts +251 -251
  277. package/src/svgrid.tree-data.test.ts +186 -186
  278. package/src/svgrid.wrapper.test.ts +63 -63
  279. package/src/svgriddropdown.async-panel.svelte.test.ts +195 -195
  280. package/src/test-setup.ts +62 -62
  281. package/src/themes/index.ts +288 -288
  282. package/src/toast-store.svelte.ts +250 -250
  283. package/src/toast-store.test.ts +147 -147
  284. package/src/tree-row-model.test.ts +168 -168
  285. package/src/ui-buttons.test.ts +144 -144
  286. package/src/ui-inputs.test.ts +118 -118
  287. package/src/ui-localization.test.ts +113 -113
  288. package/src/ui-range.test.ts +70 -70
  289. package/src/ui-selection.test.ts +155 -155
  290. package/src/ui-tier1.test.ts +142 -142
  291. package/src/virtual.test.ts +88 -88
  292. package/src/virtualization/column-virtualizer.test.ts +27 -27
  293. package/src/virtualization/column-virtualizer.ts +30 -30
  294. package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
  295. package/src/virtualization/types.ts +30 -30
  296. package/src/virtualization/virtualizer.test.ts +47 -47
  297. package/src/virtualization/virtualizer.ts +322 -322
  298. package/dist/cdn/GridMenus-DMihUtma.js +0 -600
  299. package/dist/cdn/GridMenus-nmNDj1a3.js +0 -601
  300. package/dist/cdn/SvGridChart-Bs2GIR2Q.js +0 -1202
  301. package/dist/cdn/SvGridChart-CwhFz7GV.js +0 -1203
  302. package/dist/cdn/SvGridChartPanel-1WXuStzM.js +0 -561
  303. package/dist/cdn/SvGridChartPanel-e5_nqNP9.js +0 -585
  304. package/dist/cdn/chart-i4XcXyHJ.js +0 -1214
@@ -2028,6 +2028,18 @@ export function createSvGridController<
2028
2028
  dimensionId: string | null;
2029
2029
  measureId: string | null;
2030
2030
  seriesId: string | null | undefined;
2031
+ /** Scatter only: the Y measure. The X measure reuses `measureId`. */
2032
+ measure2Id: string | null;
2033
+ // These four were config-only until the panel grew controls for them, so
2034
+ // an author could set them and a user could not. Null inherits the config.
2035
+ /** Bar orientation. */
2036
+ orientation: "vertical" | "horizontal" | null;
2037
+ /** Normalise each category to 100 percent. Implies stacked. */
2038
+ stacked100: boolean | null;
2039
+ /** Pie only: draw as a donut. */
2040
+ donut: boolean | null;
2041
+ /** Series colours chosen in the panel. */
2042
+ palette: string[] | null;
2031
2043
  stacked: boolean | null;
2032
2044
  dataLabels: boolean | null;
2033
2045
  logScale: boolean | null;
@@ -2045,6 +2057,11 @@ export function createSvGridController<
2045
2057
  dimensionId: null,
2046
2058
  measureId: null,
2047
2059
  seriesId: undefined,
2060
+ measure2Id: null,
2061
+ orientation: null,
2062
+ stacked100: null,
2063
+ donut: null,
2064
+ palette: null,
2048
2065
  stacked: null,
2049
2066
  dataLabels: null,
2050
2067
  logScale: null,
@@ -2088,14 +2105,22 @@ export function createSvGridController<
2088
2105
  const chartableColumns = $derived.by(function chartableColumns_d() {
2089
2106
  const dims: Array<{ id: string; field: string; label: string }> = [];
2090
2107
  const measures: Array<{ id: string; field: string; label: string }> = [];
2108
+ // Dates are a subset of dims, tracked separately so the panel can tell
2109
+ // whether a calendar chart is possible at all before the user picks
2110
+ // anything. Offering a type that can only render blank is worse than not
2111
+ // offering it.
2112
+ const dates: Array<{ id: string; field: string; label: string }> = [];
2091
2113
  for (const col of allColumns) {
2092
2114
  const field = col.columnDef.field;
2093
2115
  if (!field) continue;
2094
2116
  const entry = { id: col.id, field, label: columnLabel(col) };
2095
2117
  if (isNumericColumn(col)) measures.push(entry);
2096
- else dims.push(entry);
2118
+ else {
2119
+ dims.push(entry);
2120
+ if (columnIsDate(col)) dates.push(entry);
2121
+ }
2097
2122
  }
2098
- return { dims, measures };
2123
+ return { dims, measures, dates };
2099
2124
  });
2100
2125
  const chartColumnDefaults = $derived.by(function chartColumnDefaults_d() {
2101
2126
  const { dims, measures } = chartableColumns;
@@ -2139,6 +2164,21 @@ export function createSvGridController<
2139
2164
  if (configured.length) return configured;
2140
2165
  return chartColumnDefaults.measureIds;
2141
2166
  });
2167
+ /** Scatter's Y measure. Defaults to the second numeric column, so picking
2168
+ * scatter plots something immediately rather than nothing. */
2169
+ const effectiveChartMeasure2Id = $derived(
2170
+ activeChart.measure2Id ??
2171
+ chartableColumns.measures.find((m) => m.id !== effectiveChartMeasureId)?.id ??
2172
+ null,
2173
+ );
2174
+ const effectiveChartOrientation = $derived(
2175
+ activeChart.orientation ?? chartCfg?.orientation ?? "vertical",
2176
+ );
2177
+ const effectiveChartStacked100 = $derived(
2178
+ activeChart.stacked100 ?? chartCfg?.stacked100 === true,
2179
+ );
2180
+ const effectiveChartDonut = $derived(activeChart.donut ?? !!chartCfg?.donut);
2181
+ const effectiveChartPalette = $derived(activeChart.palette ?? chartCfg?.palette ?? null);
2142
2182
  const effectiveChartStacked = $derived(chartStacked ?? chartCfg?.stacked ?? false);
2143
2183
  const effectiveChartDataLabels = $derived(chartDataLabels ?? chartCfg?.dataLabels ?? false);
2144
2184
  const effectiveChartLogScale = $derived(chartLogScale ?? chartCfg?.yScale === "log");
@@ -2162,8 +2202,13 @@ export function createSvGridController<
2162
2202
  ? "Count"
2163
2203
  : `${chartReduce === "avg" ? "Average" : "Sum"} of ${label}`;
2164
2204
  });
2165
- const chartDimensionIsDate = $derived.by<boolean>(() => {
2166
- const col = allColumns.find((c) => c.id === effectiveChartDimensionId);
2205
+ /**
2206
+ * Does this column hold dates? Declared type first, then a probe of the
2207
+ * first non-empty value. Lifted out of `chartDimensionIsDate` so the chart
2208
+ * panel can also ask it of columns the user has NOT picked yet, which is
2209
+ * what decides whether a calendar chart is offerable at all.
2210
+ */
2211
+ function columnIsDate(col: Column<TData> | undefined): boolean {
2167
2212
  if (!col) return false;
2168
2213
  if (col.columnDef.cellDataType === "date") return true;
2169
2214
  if (col.columnDef.cellDataType) return false;
@@ -2174,7 +2219,10 @@ export function createSvGridController<
2174
2219
  return typeof v === "string" && /^\d{4}-\d{2}(-\d{2})?/.test(v);
2175
2220
  }
2176
2221
  return false;
2177
- });
2222
+ }
2223
+ const chartDimensionIsDate = $derived.by<boolean>(() =>
2224
+ columnIsDate(allColumns.find((c) => c.id === effectiveChartDimensionId)),
2225
+ );
2178
2226
  const chartRows = $derived.by<TData[]>(() => {
2179
2227
  const rects = getSelectionRects();
2180
2228
  const pushLeaf = (out: TData[], row: Row<TData> | undefined) => {
@@ -2260,12 +2308,27 @@ export function createSvGridController<
2260
2308
  // null until it resolves, a tick after the panel itself starts loading (which it
2261
2309
  // already does). The `getAggregate` server path uses the local bucketsToSpec and
2262
2310
  // does not need the engine.
2263
- let chartEngine = $state<typeof import("./chart").rowsToChartSpec | null>(null);
2311
+ // The whole module, not just `rowsToChartSpec`: the panel can now reach
2312
+ // every chart type, and the ones that do not read a categories-by-series
2313
+ // grid (scatter, gauge, treemap, calendar, sankey) need their own builders
2314
+ // from the same lazy chunk. Same `import()`, same boundary.
2315
+ let chartEngine = $state<typeof import("./chart") | null>(null);
2264
2316
  $effect(() => {
2265
2317
  if (chartingEnabled && !chartEngine)
2266
- import("./chart").then((m) => (chartEngine = m.rowsToChartSpec));
2318
+ import("./chart").then((m) => (chartEngine = m));
2267
2319
  });
2268
2320
 
2321
+ /** Put the chart's numbers in the same locale as the grid's. A grid that set
2322
+ * `localization.locale` has already said which locale its data is in, and a
2323
+ * chart of that data reading in a different one is a bug nobody would think
2324
+ * to look for; `charting.locale` overrides when they really should differ.
2325
+ * Currency has no such default - only the app knows what the numbers are. */
2326
+ const applyChartLocale = (spec: ChartSpec) => {
2327
+ const locale = chartCfg?.locale ?? props.localization?.locale;
2328
+ if (locale) spec.locale = locale;
2329
+ if (chartCfg?.currency) spec.currency = chartCfg.currency;
2330
+ };
2331
+
2269
2332
  const chartSpec = $derived.by<ChartSpec | null>(() => {
2270
2333
  if (!chartingEnabled || !chartCfg) return null;
2271
2334
  if (chartCfg.getAggregate) {
@@ -2277,6 +2340,7 @@ export function createSvGridController<
2277
2340
  if (effectiveChartTimeAxis) spec.xType = "time";
2278
2341
  if (effectiveChartLogScale) spec.yScale = "log";
2279
2342
  if (effectiveChartValueFormat) spec.valueFormat = effectiveChartValueFormat;
2343
+ applyChartLocale(spec);
2280
2344
  return spec;
2281
2345
  }
2282
2346
  if (chartCfg.buildSpec) return chartCfg.buildSpec(chartRows) ?? null;
@@ -2287,31 +2351,65 @@ export function createSvGridController<
2287
2351
  // Engine not loaded yet (lazy): render nothing until it resolves.
2288
2352
  if (!chartEngine) return null;
2289
2353
  const seriesField = fieldOf(effectiveChartSeriesId);
2354
+ const rowsForChart = chartRows as Array<Record<string, unknown>>;
2355
+
2356
+ // Some types read the rows directly rather than a grouped grid: a scatter
2357
+ // point is one row, a gauge has no category axis at all, and a box plot
2358
+ // needs the whole sample per group rather than one reduced number. The
2359
+ // engine owns that dispatch - naming each type here would put them in the
2360
+ // base bundle, which every grid pays for whether or not it ever charts.
2361
+ // Everything else falls through to the aggregation below and is reshaped
2362
+ // afterwards, so reduce / sort / topN / "Other" keep working for all of it.
2363
+ const direct = chartEngine.rowsToDirectSpec(chartType, rowsForChart, {
2364
+ category: category as string,
2365
+ ...(values[0] ? { value: values[0] } : {}),
2366
+ ...(fieldOf(effectiveChartMeasure2Id) ? { value2: fieldOf(effectiveChartMeasure2Id)! } : {}),
2367
+ ...(seriesField ? { series: seriesField } : {}),
2368
+ reduce: chartReduce,
2369
+ ...(effectiveChartPalette ? { palette: effectiveChartPalette } : {}),
2370
+ });
2371
+ if (direct) return direct;
2372
+ // A direct type that could not build (scatter with no Y picked yet, for
2373
+ // instance) must not fall through to the bar-chart path and draw the wrong
2374
+ // chart - it renders the panel's empty state instead.
2375
+ if (chartType === "scatter" || chartType === "gauge" || chartType === "boxplot") return null;
2290
2376
 
2291
- const spec = chartEngine<Record<string, unknown>>(chartRows as Array<Record<string, unknown>>, {
2377
+ const spec = chartEngine.rowsToChartSpec<Record<string, unknown>>(rowsForChart, {
2292
2378
  type: chartType,
2293
2379
  category: category as string,
2294
2380
  value: values.length === 1 ? values[0]! : values,
2295
2381
  ...(seriesField ? { series: seriesField } : {}),
2296
2382
  reduce: chartReduce,
2297
- stacked: effectiveChartStacked || chartCfg.stacked100 === true,
2298
- stacked100: chartCfg.stacked100 === true,
2299
- ...(chartCfg.palette ? { palette: chartCfg.palette } : {}),
2383
+ stacked: effectiveChartStacked || effectiveChartStacked100,
2384
+ stacked100: effectiveChartStacked100,
2385
+ ...(effectiveChartPalette ? { palette: effectiveChartPalette } : {}),
2300
2386
  ...(chartCfg.topN ? { topN: chartCfg.topN } : {}),
2301
2387
  ...(chartCfg.otherLabel ? { otherLabel: chartCfg.otherLabel } : {}),
2302
2388
  ...(chartCfg.sort ? { sort: chartCfg.sort } : {}),
2303
2389
  });
2304
2390
 
2305
- if (chartCfg.orientation) spec.orientation = chartCfg.orientation;
2391
+ // Reshape the aggregated grid into whatever this type actually reads.
2392
+ // These run AFTER the aggregation so grouping, reduce, sort, topN and the
2393
+ // "Other" bucket apply to them exactly as they do to a bar chart.
2394
+ if (chartType === "treemap") spec.treemap = chartEngine.specToTreemap(spec);
2395
+ else if (chartType === "calendar") spec.calendarValues = chartEngine.specToCalendar(spec);
2396
+ else if (chartType === "sankey") {
2397
+ const flow = chartEngine.specToSankey(spec);
2398
+ spec.sankeyNodes = flow.nodes;
2399
+ spec.sankeyLinks = flow.links;
2400
+ }
2401
+
2402
+ if (effectiveChartOrientation === "horizontal") spec.orientation = "horizontal";
2306
2403
  if (effectiveChartTimeAxis) spec.xType = "time";
2307
2404
  if (effectiveChartLogScale) spec.yScale = "log";
2308
2405
  if (effectiveChartValueFormat) spec.valueFormat = effectiveChartValueFormat;
2406
+ applyChartLocale(spec);
2309
2407
  if (chartType !== "pie" && spec.orientation !== "horizontal" && !spec.yAxisTitle && chartAutoYAxisTitle) {
2310
2408
  spec.yAxisTitle = chartAutoYAxisTitle;
2311
2409
  }
2312
2410
  if (effectiveChartValueFormat && activeChart.valueFormat === null) { /* inherited default */ }
2313
2411
  if (chartType === "pie") {
2314
- if (chartCfg.donut) spec.innerRadius = typeof chartCfg.donut === "number" ? chartCfg.donut : 0.6;
2412
+ if (effectiveChartDonut) spec.innerRadius = typeof chartCfg.donut === "number" ? chartCfg.donut : 0.6;
2315
2413
  }
2316
2414
  if (chartCfg.annotations) spec.annotations = chartCfg.annotations;
2317
2415
  if (chartCfg.patternFallback) spec.patternFallback = true;
@@ -2331,7 +2429,20 @@ export function createSvGridController<
2331
2429
  return spec;
2332
2430
  });
2333
2431
 
2432
+ /**
2433
+ * Can a click on this chart be turned back into a grid filter?
2434
+ *
2435
+ * Only where a clicked mark maps to exactly one value of the charted
2436
+ * dimension. It does not for a sankey (clicking a TARGET would filter the
2437
+ * SOURCE column by a target value and empty the grid), a gauge (no category
2438
+ * at all), or a scatter (the mark carries a point label, not a group).
2439
+ */
2440
+ const chartCrossFilterable = $derived(
2441
+ chartType !== "sankey" && chartType !== "gauge" && chartType !== "scatter",
2442
+ );
2443
+
2334
2444
  function applyChartCrossFilter(category: string) {
2445
+ if (!chartCrossFilterable) return;
2335
2446
  const dimId = effectiveChartDimensionId;
2336
2447
  if (!dimId) return;
2337
2448
  const existing = valueFilters[dimId];
@@ -3937,6 +4048,11 @@ export function createSvGridController<
3937
4048
  dimension: c.dimensionId,
3938
4049
  series: c.seriesId ?? null,
3939
4050
  measure: c.measureId,
4051
+ measure2: c.measure2Id,
4052
+ orientation: c.orientation,
4053
+ stacked100: c.stacked100,
4054
+ donut: c.donut,
4055
+ palette: c.palette,
3940
4056
  reduce: c.reduce,
3941
4057
  stacked: c.stacked,
3942
4058
  dataLabels: c.dataLabels,
@@ -3954,6 +4070,11 @@ export function createSvGridController<
3954
4070
  dimensionId: (c.dimension as string | null) ?? null,
3955
4071
  measureId: (c.measure as string | null) ?? null,
3956
4072
  seriesId: c.series as string | null | undefined,
4073
+ measure2Id: (c.measure2 as string | null) ?? null,
4074
+ orientation: (c.orientation as "vertical" | "horizontal" | null) ?? null,
4075
+ stacked100: (c.stacked100 as boolean | null) ?? null,
4076
+ donut: (c.donut as boolean | null) ?? null,
4077
+ palette: (c.palette as string[] | null) ?? null,
3957
4078
  stacked: (c.stacked as boolean | null) ?? null,
3958
4079
  dataLabels: (c.dataLabels as boolean | null) ?? null,
3959
4080
  logScale: (c.logScale as boolean | null) ?? null,
@@ -3963,6 +4084,18 @@ export function createSvGridController<
3963
4084
  charts = restored.length ? restored : [makeChart("Chart 1")];
3964
4085
  activeChartIndex = Math.max(0, Math.min(charts.length - 1, active ?? 0));
3965
4086
  },
4087
+ get chartCrossFilterable() { return chartCrossFilterable; },
4088
+ get chartOrientation() { return effectiveChartOrientation; },
4089
+ set chartOrientation(v) { activeChart.orientation = v as never; },
4090
+ get chartStacked100() { return effectiveChartStacked100; },
4091
+ set chartStacked100(v) { activeChart.stacked100 = v as never; },
4092
+ get chartDonut() { return effectiveChartDonut; },
4093
+ set chartDonut(v) { activeChart.donut = v as never; },
4094
+ get chartPalette() { return effectiveChartPalette; },
4095
+ set chartPalette(v) { activeChart.palette = v as never; },
4096
+ get chartMeasure2Id() { return activeChart.measure2Id; },
4097
+ set chartMeasure2Id(v) { activeChart.measure2Id = v as never; },
4098
+ get effectiveChartMeasure2Id() { return effectiveChartMeasure2Id; },
3966
4099
  get chartAiHandler() { return chartAiHandler; },
3967
4100
  set chartAiHandler(v) { chartAiHandler = v as never; },
3968
4101
  applyChartConfig(config: {
package/src/SvGrid.css CHANGED
@@ -153,6 +153,10 @@
153
153
  /* Advanced-filter indicator. Reads as state rather than as another action, so
154
154
  it uses the accent fill the toolbar buttons reserve for `is-active` instead
155
155
  of the neutral button shell. */
156
+ .sv-grid-advf-chip > .sv-grid-icon {
157
+ width: 13px;
158
+ height: 13px;
159
+ }
156
160
  .sv-grid-advf-chip {
157
161
  display: inline-flex;
158
162
  align-items: center;
@@ -261,6 +265,12 @@
261
265
  flex-shrink: 0;
262
266
  }
263
267
  .sv-grid-tool-panel-btn {
268
+ /* Centred as flex because these draw an icon now, not a bare character: a
269
+ glyph default still centres, and an SVG override no longer sits on the
270
+ text baseline. */
271
+ display: inline-flex;
272
+ align-items: center;
273
+ justify-content: center;
264
274
  width: 22px;
265
275
  height: 22px;
266
276
  border: 0;
@@ -807,9 +817,18 @@
807
817
  color: var(--sg-accent, #2563eb);
808
818
  }
809
819
  .sv-grid-group-caret {
820
+ display: inline-flex;
821
+ align-items: center;
822
+ justify-content: center;
823
+ width: 10px;
824
+ height: 10px;
810
825
  flex-shrink: 0;
811
826
  transition: transform 140ms ease;
812
827
  }
828
+ .sv-grid-group-caret > .sv-grid-icon {
829
+ width: 100%;
830
+ height: 100%;
831
+ }
813
832
  /* Collapsed: point the caret right (like a closed disclosure). */
814
833
  .sv-grid-group-toggle.is-collapsed .sv-grid-group-caret {
815
834
  transform: rotate(-90deg);
@@ -1855,6 +1874,23 @@ select.sv-grid-fr-editor {
1855
1874
  display: block;
1856
1875
  flex: none;
1857
1876
  }
1877
+ /* A consumer icon from the `icons` prop. SvGrid wraps the override in this box
1878
+ rather than asking the consumer to add `.sv-grid-icon` themselves, so it
1879
+ inherits every per-site size rule below and the expander's rotate-on-open
1880
+ without needing to know those rules exist. Must stay AFTER .sv-grid-icon:
1881
+ equal specificity, and this display has to win. */
1882
+ .sv-grid-icon-custom {
1883
+ display: inline-flex;
1884
+ align-items: center;
1885
+ justify-content: center;
1886
+ line-height: 0;
1887
+ }
1888
+ .sv-grid-icon-custom > svg,
1889
+ .sv-grid-icon-custom > img {
1890
+ width: 100%;
1891
+ height: 100%;
1892
+ display: block;
1893
+ }
1858
1894
 
1859
1895
  .sv-grid-header-cell {
1860
1896
  display: flex;
@@ -2453,14 +2489,19 @@ select.sv-grid-fr-editor {
2453
2489
  group banners and auto-group columns, SvGroupCell and SvTree all draw the
2454
2490
  same `chevron-right` and rotate it open, rather than swapping between two
2455
2491
  different glyphs. */
2456
- .sv-grid-tree-toggle .sv-grid-icon,
2457
- .sv-grid-group-toggle .sv-grid-icon {
2492
+ /* Direct child, not descendant. The column-group header button also carries
2493
+ `sv-grid-group-toggle` + `aria-expanded`, and its caret is wrapped one level
2494
+ deeper - a descendant selector would rotate that caret 90deg too, so an
2495
+ expanded down-caret would point left. The three row/tree expanders render
2496
+ their icon as an immediate child, so `>` changes nothing for them. */
2497
+ .sv-grid-tree-toggle > .sv-grid-icon,
2498
+ .sv-grid-group-toggle > .sv-grid-icon {
2458
2499
  width: 12px;
2459
2500
  height: 12px;
2460
2501
  transition: transform 0.15s ease;
2461
2502
  }
2462
- .sv-grid-tree-toggle[aria-expanded="true"] .sv-grid-icon,
2463
- .sv-grid-group-toggle[aria-expanded="true"] .sv-grid-icon {
2503
+ .sv-grid-tree-toggle[aria-expanded="true"] > .sv-grid-icon,
2504
+ .sv-grid-group-toggle[aria-expanded="true"] > .sv-grid-icon {
2464
2505
  transform: rotate(90deg);
2465
2506
  }
2466
2507
  .sv-grid-tree-toggle:focus-visible {
@@ -2624,7 +2665,7 @@ select.sv-grid-fr-editor {
2624
2665
  box-shadow: 0 10px 25px rgba(15, 23, 42, 0.18);
2625
2666
  min-width: 280px;
2626
2667
  }
2627
- .sv-grid-find-icon {
2668
+ .sv-grid-find > .sv-grid-icon {
2628
2669
  width: 14px; height: 14px;
2629
2670
  color: var(--sg-muted, #94a3b8);
2630
2671
  flex: none;
@@ -2868,13 +2909,26 @@ select.sv-grid-fr-editor {
2868
2909
  opacity: 1;
2869
2910
  }
2870
2911
 
2871
- /* Magnifier prefix on the menu's search input. */
2912
+ /* Magnifier prefix on the menu's search input. Real markup rather than a
2913
+ background-image, so it follows the theme and the `icons` prop can replace
2914
+ it; the input keeps its own class because focusFilterPanel() and a test both
2915
+ resolve `.sv-grid-menu-search`. */
2872
2916
  .sv-grid-menu-search {
2873
2917
  padding-left: 28px;
2874
- background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='6'/%3E%3Cpath d='M20 20l-4.5-4.5'/%3E%3C/svg%3E");
2875
- background-position: 8px center;
2876
- background-repeat: no-repeat;
2877
- background-size: 14px 14px;
2918
+ }
2919
+ .sv-grid-menu-search-wrap {
2920
+ position: relative;
2921
+ display: block;
2922
+ }
2923
+ .sv-grid-menu-search-wrap > .sv-grid-icon {
2924
+ position: absolute;
2925
+ left: 8px;
2926
+ top: 50%;
2927
+ transform: translateY(-50%);
2928
+ width: 14px;
2929
+ height: 14px;
2930
+ color: var(--sg-muted, #94a3b8);
2931
+ pointer-events: none;
2878
2932
  }
2879
2933
 
2880
2934
  /* Filter menu vertical rhythm. */
@@ -2910,6 +2964,11 @@ select.sv-grid-fr-editor {
2910
2964
  opacity: 1;
2911
2965
  }
2912
2966
 
2967
+ .sv-grid-board-search > .sv-grid-icon {
2968
+ width: 14px;
2969
+ height: 14px;
2970
+ }
2971
+
2913
2972
  /* Kanban board mode search box (board.searchable) */
2914
2973
  .sv-grid-board-search {
2915
2974
  display: flex;
@@ -3048,6 +3107,16 @@ select.sv-grid-fr-editor {
3048
3107
  .sv-grid-chart-actions { display: inline-flex; align-items: center; gap: 6px; }
3049
3108
  .sv-grid-chart-controls { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; }
3050
3109
  .sv-grid-chart-controls:empty { display: none; }
3110
+ /* Says why the chosen chart type cannot draw yet - a missing Split by, too few
3111
+ groups. The picker deliberately does NOT hide such a type: options that come
3112
+ and go as the user filters are worse than one sentence of explanation. */
3113
+ .sv-grid-chart-hint {
3114
+ flex-basis: 100%;
3115
+ margin: 2px 0 0;
3116
+ font-size: 11.5px;
3117
+ font-weight: 400;
3118
+ color: var(--sg-muted, #64748b);
3119
+ }
3051
3120
  .sv-grid-chart-ctl { display: flex; flex-direction: column; gap: 2px; font-size: 11px; font-weight: 600; color: var(--sg-muted, #64748b); }
3052
3121
  .sv-grid-chart-ctl select {
3053
3122
  font: inherit; font-size: 12.5px; font-weight: 400; color: var(--sg-fg, #0f172a);