@svgrid/grid 2.2.35 → 2.5.0

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 (380) hide show
  1. package/README.md +199 -199
  2. package/dist/FlexRender.svelte +96 -96
  3. package/dist/GridFooter.svelte +178 -178
  4. package/dist/GridMenus.svelte +705 -705
  5. package/dist/SvAutoComplete.svelte +165 -165
  6. package/dist/SvAvatar.svelte +75 -75
  7. package/dist/SvCalendar.svelte +503 -503
  8. package/dist/SvCarousel.svelte +141 -141
  9. package/dist/SvCheckBox.svelte +102 -102
  10. package/dist/SvCircularProgress.svelte +109 -109
  11. package/dist/SvColorInput.svelte +181 -181
  12. package/dist/SvComboBox.svelte +275 -275
  13. package/dist/SvContextMenu.svelte +116 -116
  14. package/dist/SvCountryInput.svelte +160 -160
  15. package/dist/SvDrawer.svelte +254 -254
  16. package/dist/SvDropDownList.svelte +374 -374
  17. package/dist/SvDurationInput.svelte +126 -126
  18. package/dist/SvField.svelte +293 -293
  19. package/dist/SvForm.svelte +437 -437
  20. package/dist/SvGrid.css +2814 -2814
  21. package/dist/SvGrid.svelte +3490 -3490
  22. package/dist/SvGridChart.svelte +1716 -1716
  23. package/dist/SvGridChartPanel.svelte +485 -485
  24. package/dist/SvGridChartView.svelte +70 -70
  25. package/dist/SvGridDropdown.svelte +728 -728
  26. package/dist/SvGridSelect.svelte +266 -266
  27. package/dist/SvGroupCell.svelte +116 -116
  28. package/dist/SvListBox.svelte +326 -326
  29. package/dist/SvMaskedInput.svelte +122 -122
  30. package/dist/SvMenu.svelte +124 -124
  31. package/dist/SvMenuList.svelte +146 -146
  32. package/dist/SvMultiSelect.svelte +289 -289
  33. package/dist/SvNumberInput.svelte +168 -168
  34. package/dist/SvOtpInput.svelte +158 -158
  35. package/dist/SvPasswordInput.svelte +151 -151
  36. package/dist/SvPhoneInput.svelte +133 -133
  37. package/dist/SvPopover.svelte +197 -197
  38. package/dist/SvProgress.svelte +116 -116
  39. package/dist/SvRadioGroup.svelte +107 -107
  40. package/dist/SvRating.svelte +112 -112
  41. package/dist/SvResult.svelte +73 -73
  42. package/dist/SvRichText.svelte +211 -211
  43. package/dist/SvRowGroupPanel.svelte +170 -170
  44. package/dist/SvScrollArea.svelte +61 -61
  45. package/dist/SvSlider.svelte +200 -200
  46. package/dist/SvSwitchButton.svelte +108 -108
  47. package/dist/SvTagsInput.svelte +112 -112
  48. package/dist/SvTextInput.svelte +147 -147
  49. package/dist/SvTimePicker.svelte +245 -245
  50. package/dist/SvToaster.svelte +159 -159
  51. package/dist/SvToggleButton.svelte +85 -85
  52. package/dist/SvTooltip.svelte +161 -161
  53. package/dist/SvTour.svelte +208 -208
  54. package/dist/SvTree.svelte +444 -444
  55. package/dist/SvTreeSelect.svelte +235 -235
  56. package/dist/cdn/{GridMenus-oQYElQll.js → GridMenus-BpVcKyUr.js} +2 -2
  57. package/dist/cdn/{GridMenus-T-bZNprc.js → GridMenus-CeXL5IW4.js} +2 -2
  58. package/dist/cdn/{SvDateTimePicker-CHnUkWcH.js → SvDateTimePicker-DSUhIta8.js} +2 -2
  59. package/dist/cdn/{SvDateTimePicker-CRQH_U7E.js → SvDateTimePicker-h9kPyszT.js} +2 -2
  60. package/dist/cdn/{SvGridChart-BwVos976.js → SvGridChart-Bs2GIR2Q.js} +1 -1
  61. package/dist/cdn/{SvGridChart-BEJmNNx9.js → SvGridChart-CwhFz7GV.js} +1 -1
  62. package/dist/cdn/{SvGridChartPanel-BOknOkrP.js → SvGridChartPanel-1WXuStzM.js} +1 -1
  63. package/dist/cdn/{SvGridChartPanel-D2PjII4O.js → SvGridChartPanel-e5_nqNP9.js} +1 -1
  64. package/dist/cdn/{SvGridChartView-00LPUHL1.js → SvGridChartView-SmPW10dI.js} +1 -1
  65. package/dist/cdn/{SvGridChartView-BhUEJ5ki.js → SvGridChartView-eSPuJE6g.js} +1 -1
  66. package/dist/cdn/{SvGridDropdown-D0VdjeR8.js → SvGridDropdown-B1ZcLpgs.js} +1 -1
  67. package/dist/cdn/{SvGridDropdown-D13MtJ2j.js → SvGridDropdown-CtdbHcIS.js} +1 -1
  68. package/dist/cdn/{src-Cm7C_WOZ.js → src-DbeN1ReZ.js} +24 -24
  69. package/dist/cdn/{src-72QTH8lh.js → src-DmEQFW5_.js} +23 -23
  70. package/dist/cdn/svgrid.js +5 -5
  71. package/dist/cdn/svgrid.svelte-external.js +5 -5
  72. package/dist/chart-export.js +8 -8
  73. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/_svelte_metadata.json +1 -0
  74. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/_metadata.json +178 -0
  75. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/attachments-qzRy9DX4.js +3843 -0
  76. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/attachments-qzRy9DX4.js.map +1 -0
  77. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/constants-RLNOXCpz.js +23 -0
  78. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/constants-RLNOXCpz.js.map +1 -0
  79. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/dev-DPd12uEU.js +1925 -0
  80. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/dev-DPd12uEU.js.map +1 -0
  81. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/events-CDC4DKuQ.js +206 -0
  82. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/events-CDC4DKuQ.js.map +1 -0
  83. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/flags-BD-lYX7s.js +20 -0
  84. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/flags-BD-lYX7s.js.map +1 -0
  85. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/hydration-D3UBU2t8.js +264 -0
  86. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/hydration-D3UBU2t8.js.map +1 -0
  87. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/legacy-client-B0ZUlbSQ.js +698 -0
  88. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/legacy-client-B0ZUlbSQ.js.map +1 -0
  89. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/package.json +3 -0
  90. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte.js +135 -0
  91. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte.js.map +1 -0
  92. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_animate.js +57 -0
  93. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_animate.js.map +1 -0
  94. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_attachments.js +2 -0
  95. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_compiler.js +46691 -0
  96. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_compiler.js.map +1 -0
  97. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_easing.js +239 -0
  98. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_easing.js.map +1 -0
  99. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_events.js +2 -0
  100. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal.js +6 -0
  101. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal.js.map +1 -0
  102. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_client.js +7 -0
  103. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_disclose-version.js +6 -0
  104. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_disclose-version.js.map +1 -0
  105. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_async.js +7 -0
  106. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_async.js.map +1 -0
  107. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_legacy.js +7 -0
  108. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_legacy.js.map +1 -0
  109. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_tracing.js +7 -0
  110. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_tracing.js.map +1 -0
  111. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_server.js +6 -0
  112. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_legacy.js +85 -0
  113. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_legacy.js.map +1 -0
  114. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_motion.js +561 -0
  115. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_motion.js.map +1 -0
  116. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_reactivity.js +26 -0
  117. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_reactivity.js.map +1 -0
  118. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_reactivity_window.js +124 -0
  119. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_reactivity_window.js.map +1 -0
  120. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_server.js +2 -0
  121. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_store.js +91 -0
  122. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_store.js.map +1 -0
  123. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_transition.js +242 -0
  124. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_transition.js.map +1 -0
  125. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/utils-lObbumRX.js +497 -0
  126. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/utils-lObbumRX.js.map +1 -0
  127. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/utils-mR9mAWyK.js +4929 -0
  128. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/utils-mR9mAWyK.js.map +1 -0
  129. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/validate-CezNmRkM.js +192 -0
  130. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/validate-CezNmRkM.js.map +1 -0
  131. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/version-BHHo0WE-.js +10 -0
  132. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/version-BHHo0WE-.js.map +1 -0
  133. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/warnings-yiExAB06.js +140 -0
  134. package/dist/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/warnings-yiExAB06.js.map +1 -0
  135. package/dist/themes/index.js +28 -9
  136. package/package.json +11 -11
  137. package/src/FlexRender.svelte +96 -96
  138. package/src/GridFooter.svelte +178 -178
  139. package/src/GridMenus.svelte +705 -705
  140. package/src/SvAutoComplete.svelte +165 -165
  141. package/src/SvAvatar.svelte +75 -75
  142. package/src/SvCalendar.svelte +503 -503
  143. package/src/SvCalendar.test.ts +226 -226
  144. package/src/SvCarousel.svelte +141 -141
  145. package/src/SvCheckBox.svelte +102 -102
  146. package/src/SvCircularProgress.svelte +109 -109
  147. package/src/SvColorInput.svelte +181 -181
  148. package/src/SvComboBox.svelte +275 -275
  149. package/src/SvContextMenu.svelte +116 -116
  150. package/src/SvCountryInput.svelte +160 -160
  151. package/src/SvDrawer.svelte +254 -254
  152. package/src/SvDropDownList.svelte +374 -374
  153. package/src/SvDurationInput.svelte +126 -126
  154. package/src/SvField.svelte +293 -293
  155. package/src/SvForm.svelte +437 -437
  156. package/src/SvForm.test.ts +411 -411
  157. package/src/SvGrid.controller.svelte.ts +3680 -3680
  158. package/src/SvGrid.css +2814 -2814
  159. package/src/SvGrid.svelte +3490 -3490
  160. package/src/SvGrid.types.ts +1708 -1708
  161. package/src/SvGridChart.svelte +1716 -1716
  162. package/src/SvGridChartPanel.svelte +485 -485
  163. package/src/SvGridChartView.svelte +70 -70
  164. package/src/SvGridDropdown.svelte +728 -728
  165. package/src/SvGridSelect.svelte +266 -266
  166. package/src/SvGroupCell.svelte +116 -116
  167. package/src/SvListBox.svelte +326 -326
  168. package/src/SvMaskedInput.svelte +122 -122
  169. package/src/SvMenu.svelte +124 -124
  170. package/src/SvMenu.test.ts +97 -97
  171. package/src/SvMenuList.svelte +146 -146
  172. package/src/SvMultiSelect.svelte +289 -289
  173. package/src/SvNumberInput.svelte +168 -168
  174. package/src/SvOtpInput.svelte +158 -158
  175. package/src/SvPasswordInput.svelte +151 -151
  176. package/src/SvPhoneInput.svelte +133 -133
  177. package/src/SvPopover.svelte +197 -197
  178. package/src/SvProgress.svelte +116 -116
  179. package/src/SvRadioGroup.svelte +107 -107
  180. package/src/SvRating.svelte +112 -112
  181. package/src/SvResult.svelte +73 -73
  182. package/src/SvRichText.svelte +211 -211
  183. package/src/SvRowGroupPanel.svelte +170 -170
  184. package/src/SvScrollArea.svelte +61 -61
  185. package/src/SvSlider.svelte +200 -200
  186. package/src/SvSwitchButton.svelte +108 -108
  187. package/src/SvTagsInput.svelte +112 -112
  188. package/src/SvTextInput.svelte +147 -147
  189. package/src/SvTimePicker.svelte +245 -245
  190. package/src/SvToaster.svelte +159 -159
  191. package/src/SvToaster.test.ts +95 -95
  192. package/src/SvToggleButton.svelte +85 -85
  193. package/src/SvTooltip.svelte +161 -161
  194. package/src/SvTour.svelte +208 -208
  195. package/src/SvTree.svelte +444 -444
  196. package/src/SvTreeSelect.svelte +235 -235
  197. package/src/a11y/dismissable.test.ts +119 -119
  198. package/src/a11y/dismissable.ts +114 -114
  199. package/src/a11y.contract.test.ts +49 -49
  200. package/src/a11y.test.ts +59 -59
  201. package/src/a11y.ts +61 -61
  202. package/src/ai.test.ts +502 -502
  203. package/src/ai.ts +1391 -1391
  204. package/src/build-api.coverage.test.ts +633 -633
  205. package/src/build-api.ts +805 -805
  206. package/src/builtin-editors.grid.test.ts +85 -85
  207. package/src/cell-formatting.ts +169 -169
  208. package/src/cell-render.test.ts +513 -513
  209. package/src/cell-render.ts +496 -496
  210. package/src/cell-values.ts +118 -118
  211. package/src/chart-export.ts +201 -201
  212. package/src/chart-view.svelte.ts +36 -36
  213. package/src/chart.ts +2295 -2295
  214. package/src/collaboration.test.ts +104 -104
  215. package/src/collaboration.ts +167 -167
  216. package/src/core.performance.test.ts +30 -30
  217. package/src/core.ts +1261 -1261
  218. package/src/createAutocomplete.svelte.ts +132 -132
  219. package/src/createCombobox.svelte.ts +191 -191
  220. package/src/createCountryInput.svelte.ts +157 -157
  221. package/src/createDropdownList.svelte.ts +168 -168
  222. package/src/createForm.svelte.ts +386 -386
  223. package/src/createGrid.svelte.ts +42 -42
  224. package/src/createGrid.test.ts +10 -10
  225. package/src/createGridState.svelte.ts +17 -17
  226. package/src/createListbox.svelte.ts +250 -250
  227. package/src/createMenu.svelte.ts +224 -224
  228. package/src/createPopoverSelect.svelte.ts +213 -213
  229. package/src/createSlider.svelte.ts +191 -191
  230. package/src/createTooltip.svelte.ts +144 -144
  231. package/src/createTree.svelte.ts +319 -319
  232. package/src/editing.test.ts +949 -949
  233. package/src/editing.ts +606 -606
  234. package/src/editor-contract.ts +164 -164
  235. package/src/editor-registry.grid.test.ts +144 -144
  236. package/src/editor-registry.ts +122 -122
  237. package/src/export-data-api.test.ts +126 -126
  238. package/src/export-format.test.ts +107 -107
  239. package/src/export-format.ts +601 -601
  240. package/src/filter-operators.ts +157 -157
  241. package/src/flex-render.ts +3 -3
  242. package/src/form-field.ts +127 -127
  243. package/src/group-display.test.ts +167 -167
  244. package/src/group-display.ts +200 -200
  245. package/src/index.ts +724 -724
  246. package/src/js-scroller.svelte.ts +173 -173
  247. package/src/keyboard-handlers.ts +270 -270
  248. package/src/keyboard.test.ts +59 -59
  249. package/src/keyboard.ts +97 -97
  250. package/src/list-nav.test.ts +49 -49
  251. package/src/list-nav.ts +29 -29
  252. package/src/list-option.test.ts +56 -56
  253. package/src/list-option.ts +172 -172
  254. package/src/menus.ts +567 -567
  255. package/src/merge-objects.ts +48 -48
  256. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/_svelte_metadata.json +1 -0
  257. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/_metadata.json +178 -0
  258. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/attachments-qzRy9DX4.js +3843 -0
  259. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/attachments-qzRy9DX4.js.map +1 -0
  260. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/constants-RLNOXCpz.js +23 -0
  261. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/constants-RLNOXCpz.js.map +1 -0
  262. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/dev-DPd12uEU.js +1925 -0
  263. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/dev-DPd12uEU.js.map +1 -0
  264. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/events-CDC4DKuQ.js +206 -0
  265. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/events-CDC4DKuQ.js.map +1 -0
  266. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/flags-BD-lYX7s.js +20 -0
  267. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/flags-BD-lYX7s.js.map +1 -0
  268. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/hydration-D3UBU2t8.js +264 -0
  269. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/hydration-D3UBU2t8.js.map +1 -0
  270. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/legacy-client-B0ZUlbSQ.js +698 -0
  271. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/legacy-client-B0ZUlbSQ.js.map +1 -0
  272. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/package.json +3 -0
  273. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte.js +135 -0
  274. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte.js.map +1 -0
  275. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_animate.js +57 -0
  276. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_animate.js.map +1 -0
  277. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_attachments.js +2 -0
  278. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_compiler.js +46691 -0
  279. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_compiler.js.map +1 -0
  280. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_easing.js +239 -0
  281. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_easing.js.map +1 -0
  282. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_events.js +2 -0
  283. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal.js +6 -0
  284. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal.js.map +1 -0
  285. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_client.js +7 -0
  286. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_disclose-version.js +6 -0
  287. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_disclose-version.js.map +1 -0
  288. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_async.js +7 -0
  289. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_async.js.map +1 -0
  290. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_legacy.js +7 -0
  291. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_legacy.js.map +1 -0
  292. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_tracing.js +7 -0
  293. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_flags_tracing.js.map +1 -0
  294. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_internal_server.js +6 -0
  295. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_legacy.js +85 -0
  296. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_legacy.js.map +1 -0
  297. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_motion.js +561 -0
  298. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_motion.js.map +1 -0
  299. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_reactivity.js +26 -0
  300. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_reactivity.js.map +1 -0
  301. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_reactivity_window.js +124 -0
  302. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_reactivity_window.js.map +1 -0
  303. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_server.js +2 -0
  304. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_store.js +91 -0
  305. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_store.js.map +1 -0
  306. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_transition.js +242 -0
  307. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/svelte_transition.js.map +1 -0
  308. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/utils-lObbumRX.js +497 -0
  309. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/utils-lObbumRX.js.map +1 -0
  310. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/utils-mR9mAWyK.js +4929 -0
  311. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/utils-mR9mAWyK.js.map +1 -0
  312. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/validate-CezNmRkM.js +192 -0
  313. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/validate-CezNmRkM.js.map +1 -0
  314. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/version-BHHo0WE-.js +10 -0
  315. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/version-BHHo0WE-.js.map +1 -0
  316. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/warnings-yiExAB06.js +140 -0
  317. package/src/node_modules/.vite/vitest/67d8ed22ed986c5e2e948f64d892f30be166cc36/deps___vitest__/warnings-yiExAB06.js.map +1 -0
  318. package/src/overlays.test.ts +90 -90
  319. package/src/render-component.ts +28 -28
  320. package/src/row-resize.ts +210 -210
  321. package/src/scheduler-model.test.ts +562 -562
  322. package/src/scheduler-model.ts +872 -872
  323. package/src/selection.test.ts +754 -754
  324. package/src/selection.ts +491 -491
  325. package/src/server-data-source.test.ts +289 -289
  326. package/src/server-data-source.ts +413 -413
  327. package/src/sparkline.test.ts +68 -68
  328. package/src/sparkline.ts +169 -169
  329. package/src/spreadsheet.test.ts +488 -488
  330. package/src/spreadsheet.ts +312 -312
  331. package/src/static-functions.ts +11 -11
  332. package/src/subscribe.ts +38 -38
  333. package/src/svgrid-wrapper.types.ts +526 -526
  334. package/src/svgrid.async-editor-options.test.ts +271 -271
  335. package/src/svgrid.auto-row-height.test.ts +204 -204
  336. package/src/svgrid.behavior.test.ts +705 -705
  337. package/src/svgrid.charting.test.ts +534 -534
  338. package/src/svgrid.comments-autocomplete.test.ts +123 -123
  339. package/src/svgrid.context-menu.test.ts +147 -147
  340. package/src/svgrid.features.test.ts +157 -157
  341. package/src/svgrid.filter-depth.test.ts +163 -163
  342. package/src/svgrid.filter-menu-listbox.svelte.test.ts +331 -322
  343. package/src/svgrid.filter-menu-scroll.test.ts +112 -112
  344. package/src/svgrid.grand-total.test.ts +188 -188
  345. package/src/svgrid.group-display-mode.test.ts +171 -171
  346. package/src/svgrid.group-footers.test.ts +121 -121
  347. package/src/svgrid.group-pagination.test.ts +153 -153
  348. package/src/svgrid.new-features.wrapper.test.ts +251 -251
  349. package/src/svgrid.tree-data.test.ts +186 -186
  350. package/src/svgrid.wrapper.test.ts +40 -40
  351. package/src/svgriddropdown.async-panel.svelte.test.ts +195 -195
  352. package/src/test-setup.ts +62 -62
  353. package/src/themes/index.ts +278 -257
  354. package/src/toast-store.svelte.ts +246 -246
  355. package/src/toast-store.test.ts +147 -147
  356. package/src/tree-row-model.test.ts +168 -168
  357. package/src/ui-buttons.test.ts +144 -144
  358. package/src/ui-inputs.test.ts +118 -118
  359. package/src/ui-localization.test.ts +113 -113
  360. package/src/ui-range.test.ts +70 -70
  361. package/src/ui-selection.test.ts +155 -155
  362. package/src/ui-tier1.test.ts +142 -142
  363. package/src/virtual.test.ts +88 -88
  364. package/src/virtualization/column-virtualizer.test.ts +27 -27
  365. package/src/virtualization/column-virtualizer.ts +30 -30
  366. package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
  367. package/src/virtualization/types.ts +30 -30
  368. package/src/virtualization/virtualizer.test.ts +47 -47
  369. package/src/virtualization/virtualizer.ts +296 -296
  370. package/themes/ag-alpine.css +2 -2
  371. package/themes/antd.css +4 -4
  372. package/themes/atlassian.css +1 -1
  373. package/themes/ember.css +2 -2
  374. package/themes/fluent.css +1 -1
  375. package/themes/github.css +1 -1
  376. package/themes/nord.css +2 -2
  377. package/themes/notion.css +2 -2
  378. package/themes/salesforce.css +1 -1
  379. package/themes/tailwind.css +1 -1
  380. package/themes/vercel.css +1 -1
@@ -1,485 +1,485 @@
1
- <script lang="ts" generics="TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData">
2
- /**
3
- * The built-in integrated-chart panel. Opens as a right-side drawer (default)
4
- * or docks to the bottom edge, and stays live with the grid: it reads the
5
- * controller's derived
6
- * `chartSpec` - which itself derives from the displayed rows + the active cell
7
- * selection - so filtering, sorting, paging, or selecting a range re-draws the
8
- * chart with no manual sync. Clicking a chart category cross-filters the grid.
9
- *
10
- * In `buildSpec` (custom) mode the axis pickers are hidden - the author owns
11
- * the spec - and the panel is a thin live frame around it.
12
- *
13
- * Rendered by SvGrid.svelte and handed `{ctrl}`; not placed by consumers.
14
- */
15
- import { fly } from "svelte/transition";
16
- import { cubicOut } from "svelte/easing";
17
- import SvGridChart from "./SvGridChart.svelte";
18
- import { downloadChartPng, downloadChartSvg, downloadChartCsv, chartToPngBlob } from "./chart-export";
19
- import type { ChartType, ChartSelection, RowData, TableFeatures } from "./index";
20
- import type { SvGridController } from "./SvGrid.controller.svelte";
21
-
22
- let { ctrl }: { ctrl: SvGridController<TFeatures, TData> } = $props();
23
-
24
- const config = $derived(ctrl.chartingConfig);
25
- const cfg = $derived(ctrl.chartCfg);
26
- const isCustom = $derived(ctrl.chartIsCustom);
27
- const position = $derived(config?.position ?? "bottom");
28
- const crossFilter = $derived(config?.crossFilter !== false);
29
- const spec = $derived(ctrl.chartSpec);
30
- const columns = $derived(ctrl.chartableColumns);
31
- const size = $derived(
32
- ctrl.chartSize ?? (position === "right" ? (config?.width ?? 340) : (config?.height ?? 260)),
33
- );
34
-
35
- // ---- Pop-out (floating window) state -----------------------------------
36
- const floating = $derived(ctrl.chartFloating);
37
- const maximized = $derived(ctrl.chartMaximized);
38
- const rect = $derived(ctrl.chartFloatRect);
39
- let sectionEl = $state<HTMLElement | null>(null);
40
-
41
- function parentBox(): { w: number; h: number } {
42
- const p = sectionEl?.offsetParent as HTMLElement | null;
43
- return { w: p?.clientWidth ?? 800, h: p?.clientHeight ?? 520 };
44
- }
45
- function popOut() {
46
- if (!ctrl.chartFloatRect) {
47
- const { w: pw, h: ph } = parentBox();
48
- const w = Math.min(600, Math.max(360, pw - 80));
49
- const h = Math.min(430, Math.max(260, ph - 90));
50
- ctrl.chartFloatRect = { x: Math.round((pw - w) / 2), y: Math.round((ph - h) / 2), w, h };
51
- }
52
- ctrl.chartFloating = true;
53
- }
54
- function dockBack() {
55
- ctrl.chartFloating = false;
56
- ctrl.chartMaximized = false;
57
- }
58
- const toggleMax = () => (ctrl.chartMaximized = !ctrl.chartMaximized);
59
-
60
- function startDrag(e: PointerEvent) {
61
- if (!floating || maximized) return;
62
- if ((e.target as HTMLElement)?.closest("button")) return; // don't drag from the buttons
63
- const r = ctrl.chartFloatRect;
64
- if (!r) return;
65
- e.preventDefault();
66
- const sx = e.clientX;
67
- const sy = e.clientY;
68
- const { w: pw, h: ph } = parentBox();
69
- const move = (ev: PointerEvent) => {
70
- const nx = Math.max(0, Math.min(pw - r.w, r.x + ev.clientX - sx));
71
- const ny = Math.max(0, Math.min(ph - r.h, r.y + ev.clientY - sy));
72
- ctrl.chartFloatRect = { x: nx, y: ny, w: r.w, h: r.h };
73
- };
74
- const up = () => {
75
- window.removeEventListener("pointermove", move);
76
- window.removeEventListener("pointerup", up);
77
- };
78
- window.addEventListener("pointermove", move);
79
- window.addEventListener("pointerup", up);
80
- }
81
-
82
- function startFloatResize(e: PointerEvent) {
83
- const r = ctrl.chartFloatRect;
84
- if (!r) return;
85
- e.preventDefault();
86
- e.stopPropagation();
87
- const sx = e.clientX;
88
- const sy = e.clientY;
89
- (e.target as HTMLElement).setPointerCapture?.(e.pointerId);
90
- const move = (ev: PointerEvent) => {
91
- ctrl.chartFloatRect = {
92
- x: r.x,
93
- y: r.y,
94
- w: Math.max(300, r.w + ev.clientX - sx),
95
- h: Math.max(220, r.h + ev.clientY - sy),
96
- };
97
- };
98
- const up = () => {
99
- window.removeEventListener("pointermove", move);
100
- window.removeEventListener("pointerup", up);
101
- };
102
- window.addEventListener("pointermove", move);
103
- window.addEventListener("pointerup", up);
104
- }
105
-
106
- const floatStyle = $derived(
107
- floating
108
- ? maximized
109
- ? "left:12px; top:12px; right:12px; bottom:12px; width:auto; height:auto;"
110
- : rect
111
- ? `left:${rect.x}px; top:${rect.y}px; width:${rect.w}px; height:${rect.h}px;`
112
- : ""
113
- : position === "right"
114
- ? `width:${size}px`
115
- : `height:${size}px`,
116
- );
117
-
118
- // Measure the chart area so the chart lays out to fit it (no overflow /
119
- // clipped axis labels) - the docked panel is a fixed-size container. Reserve
120
- // room for the legend (rendered below the SVG for >1 series) and the export
121
- // toolbar, so neither gets clipped.
122
- let bodyW = $state(0);
123
- let bodyH = $state(0);
124
- const seriesCount = $derived(spec ? (spec.type === "pie" ? spec.categories.length : spec.series.length) : 0);
125
- const reserve = $derived((seriesCount > 1 ? 36 : 0) + (cfg?.export === true ? 30 : 0));
126
- const chartW = $derived(bodyW > 24 ? bodyW - 8 : undefined);
127
- const chartH = $derived(bodyH > 24 ? Math.max(120, bodyH - 8 - reserve) : undefined);
128
-
129
- // Aggregation-friendly types offered in the picker; exotic types come via
130
- // `buildSpec`, where the picker is hidden.
131
- const TYPES: Array<{ value: ChartType; label: string }> = [
132
- { value: "bar", label: "Bar" },
133
- { value: "line", label: "Line" },
134
- { value: "area", label: "Area" },
135
- { value: "pie", label: "Pie" },
136
- ];
137
- const REDUCERS = [
138
- { value: "sum", label: "Sum" },
139
- { value: "avg", label: "Average" },
140
- { value: "count", label: "Count" },
141
- ] as const;
142
- const FORMATS = [
143
- { value: "number", label: "Number" },
144
- { value: "currency", label: "Currency" },
145
- { value: "percent", label: "Percent" },
146
- ] as const;
147
-
148
- function onChartSelect(sel: ChartSelection) {
149
- if (crossFilter && sel.category != null) ctrl.applyChartCrossFilter(String(sel.category));
150
- }
151
-
152
- // ---- Export (PNG / SVG / CSV / copy) -----------------------------------
153
- let bodyEl = $state<HTMLElement | null>(null);
154
- let exportOpen = $state(false);
155
- let copied = $state(false);
156
- const svgEl = () => bodyEl?.querySelector("svg") ?? null;
157
- const csvExportable = $derived(!!(spec && spec.categories?.length && spec.series?.length));
158
- function exportPng() {
159
- const el = svgEl();
160
- if (el) void downloadChartPng(el, "chart.png");
161
- exportOpen = false;
162
- }
163
- function exportSvg() {
164
- const el = svgEl();
165
- if (el) downloadChartSvg(el, "chart.svg");
166
- exportOpen = false;
167
- }
168
- function exportCsv() {
169
- if (spec) downloadChartCsv(spec, "chart.csv");
170
- exportOpen = false;
171
- }
172
- async function copyImage() {
173
- const el = svgEl();
174
- exportOpen = false;
175
- if (!el || typeof ClipboardItem === "undefined" || !navigator.clipboard?.write) return;
176
- try {
177
- const blob = await chartToPngBlob(el);
178
- await navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]);
179
- copied = true;
180
- setTimeout(() => (copied = false), 1400);
181
- } catch {
182
- /* clipboard blocked - no-op */
183
- }
184
- }
185
- $effect(() => {
186
- if (!exportOpen) return;
187
- const close = (e: PointerEvent) => {
188
- if (!(e.target as HTMLElement)?.closest(".sv-grid-chart-export")) exportOpen = false;
189
- };
190
- window.addEventListener("pointerdown", close);
191
- return () => window.removeEventListener("pointerdown", close);
192
- });
193
-
194
- // ---- AI "chart this" (enterprise-registered handler) -------------------
195
- const aiEnabled = $derived(!!ctrl.chartAiHandler);
196
- let aiOpen = $state(false);
197
- let aiPrompt = $state("");
198
- let aiBusy = $state(false);
199
- let aiError = $state("");
200
- let aiRationale = $state("");
201
- async function runAi() {
202
- const handler = ctrl.chartAiHandler;
203
- const prompt = aiPrompt.trim();
204
- if (!handler || !prompt || aiBusy) return;
205
- aiBusy = true;
206
- aiError = "";
207
- aiRationale = "";
208
- try {
209
- const cfg = await handler(prompt);
210
- if (cfg) {
211
- ctrl.applyChartConfig(cfg as Parameters<typeof ctrl.applyChartConfig>[0]);
212
- aiRationale = typeof cfg.rationale === "string" ? cfg.rationale : "";
213
- } else {
214
- aiError = "Couldn't build a chart from that request.";
215
- }
216
- } catch (e) {
217
- aiError = e instanceof Error ? e.message : "Something went wrong.";
218
- } finally {
219
- aiBusy = false;
220
- }
221
- }
222
-
223
- // ---- Resize by dragging the panel's inner edge -------------------------
224
- let dragging = $state(false);
225
- function startResize(e: PointerEvent) {
226
- e.preventDefault();
227
- dragging = true;
228
- const startPos = position === "right" ? e.clientX : e.clientY;
229
- const startSize = size;
230
- (e.target as HTMLElement).setPointerCapture?.(e.pointerId);
231
- const move = (ev: PointerEvent) => {
232
- const delta = position === "right" ? startPos - ev.clientX : startPos - ev.clientY;
233
- ctrl.chartSize = Math.max(160, Math.min(720, startSize + delta));
234
- };
235
- const up = () => {
236
- dragging = false;
237
- window.removeEventListener("pointermove", move);
238
- window.removeEventListener("pointerup", up);
239
- };
240
- window.addEventListener("pointermove", move);
241
- window.addEventListener("pointerup", up);
242
- }
243
- </script>
244
-
245
- <section
246
- bind:this={sectionEl}
247
- class="sv-grid-chart-panel"
248
- class:is-right={position === "right" && !floating}
249
- class:is-bottom={position === "bottom" && !floating}
250
- class:is-floating={floating}
251
- class:is-maximized={floating && maximized}
252
- style={floatStyle}
253
- aria-label="Chart"
254
- transition:fly={{
255
- x: !floating && position === "right" ? size : 0,
256
- y: !floating && position === "bottom" ? size : 0,
257
- duration: 180,
258
- easing: cubicOut,
259
- }}
260
- >
261
- {#if !floating}
262
- <!-- svelte-ignore a11y_no_static_element_interactions -->
263
- <div
264
- class="sv-grid-chart-resizer"
265
- class:is-dragging={dragging}
266
- onpointerdown={startResize}
267
- role="separator"
268
- aria-orientation={position === "right" ? "vertical" : "horizontal"}
269
- aria-label="Resize chart panel"
270
- ></div>
271
- {/if}
272
-
273
- <header class="sv-grid-chart-panel-head">
274
- <!-- svelte-ignore a11y_no_static_element_interactions -->
275
- <div
276
- class="sv-grid-chart-drawer-title"
277
- class:is-drag-handle={floating && !maximized}
278
- onpointerdown={startDrag}
279
- >
280
- <svg class="sv-grid-chart-title-icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
281
- <line x1="12" y1="20" x2="12" y2="10" /><line x1="18" y1="20" x2="18" y2="4" /><line x1="6" y1="20" x2="6" y2="16" />
282
- </svg>
283
- <span class="sv-grid-chart-title">Chart</span>
284
- <div class="sv-grid-chart-actions">
285
- {#if crossFilter && !isCustom}
286
- <button type="button" class="sv-grid-chart-btn" onclick={() => ctrl.clearChartCrossFilter()}>Clear filter</button>
287
- {/if}
288
- <div class="sv-grid-chart-export">
289
- <button type="button" class="sv-grid-chart-icon-btn" aria-label="Export chart" title="Export" aria-expanded={exportOpen} onclick={() => (exportOpen = !exportOpen)}>
290
- {#if copied}
291
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12" /></svg>
292
- {:else}
293
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" y1="15" x2="12" y2="3" /></svg>
294
- {/if}
295
- </button>
296
- {#if exportOpen}
297
- <div class="sv-grid-chart-export-menu" role="menu">
298
- <button type="button" role="menuitem" onclick={exportPng}>PNG image</button>
299
- <button type="button" role="menuitem" onclick={exportSvg}>SVG vector</button>
300
- <button type="button" role="menuitem" disabled={!csvExportable} onclick={exportCsv}>CSV data</button>
301
- <button type="button" role="menuitem" onclick={copyImage}>Copy to clipboard</button>
302
- </div>
303
- {/if}
304
- </div>
305
- {#if aiEnabled && !isCustom}
306
- <button type="button" class="sv-grid-chart-btn sv-grid-chart-ai-btn" class:is-active={aiOpen} aria-label="Chart with AI" title="Chart with AI" aria-expanded={aiOpen} onclick={() => (aiOpen = !aiOpen)}>
307
- <svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2l1.9 4.6L18.5 8.5 13.9 10.4 12 15l-1.9-4.6L5.5 8.5l4.6-1.9L12 2zm6 11l1 2.4 2.4 1-2.4 1L18 21l-1-2.6-2.4-1 2.4-1L18 13zM6 14l.8 2 2 .8-2 .8L6 20l-.8-2.4-2-.8 2-.8L6 14z" /></svg>
308
- AI
309
- </button>
310
- {/if}
311
- {#if !isCustom}
312
- <button type="button" class="sv-grid-chart-icon-btn" aria-label="Add chart" title="Add chart" onclick={() => ctrl.addChart()}>
313
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" /></svg>
314
- </button>
315
- {/if}
316
- {#if floating}
317
- <button type="button" class="sv-grid-chart-icon-btn" aria-label={maximized ? "Restore" : "Maximize"} title={maximized ? "Restore" : "Maximize"} onclick={toggleMax}>
318
- {#if maximized}
319
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 3v3a2 2 0 0 1-2 2H3" /><path d="M21 8h-3a2 2 0 0 1-2-2V3" /><path d="M3 16h3a2 2 0 0 1 2 2v3" /><path d="M16 21v-3a2 2 0 0 1 2-2h3" /></svg>
320
- {:else}
321
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 3H5a2 2 0 0 0-2 2v3" /><path d="M21 8V5a2 2 0 0 0-2-2h-3" /><path d="M3 16v3a2 2 0 0 0 2 2h3" /><path d="M16 21h3a2 2 0 0 0 2-2v-3" /></svg>
322
- {/if}
323
- </button>
324
- <button type="button" class="sv-grid-chart-icon-btn" aria-label="Dock chart" title="Dock" onclick={dockBack}>
325
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2" /><line x1="15" y1="3" x2="15" y2="21" /></svg>
326
- </button>
327
- {:else}
328
- <button type="button" class="sv-grid-chart-icon-btn" aria-label="Pop out chart" title="Pop out" onclick={popOut}>
329
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 3h6v6" /><path d="M10 14 21 3" /><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></svg>
330
- </button>
331
- {/if}
332
- <button type="button" class="sv-grid-chart-close" aria-label="Close chart" onclick={() => (ctrl.chartPanelOpen = false)}>&times;</button>
333
- </div>
334
- </div>
335
- {#if !isCustom && ctrl.charts.length > 1}
336
- <div class="sv-grid-chart-tabs" role="tablist" aria-label="Charts">
337
- {#each ctrl.charts as c, i (c.id)}
338
- <div class="sv-grid-chart-tab" class:is-active={i === ctrl.activeChartIndex}>
339
- <button
340
- type="button"
341
- class="sv-grid-chart-tab-label"
342
- role="tab"
343
- aria-selected={i === ctrl.activeChartIndex}
344
- onclick={() => (ctrl.activeChartIndex = i)}
345
- >{c.title}</button>
346
- <button
347
- type="button"
348
- class="sv-grid-chart-tab-x"
349
- aria-label={`Remove ${c.title}`}
350
- title="Remove"
351
- onclick={() => ctrl.removeChart(i)}
352
- >&times;</button>
353
- </div>
354
- {/each}
355
- </div>
356
- {/if}
357
- {#if aiEnabled && aiOpen && !isCustom}
358
- <div class="sv-grid-chart-ai">
359
- <form
360
- class="sv-grid-chart-ai-row"
361
- onsubmit={(e) => {
362
- e.preventDefault();
363
- runAi();
364
- }}
365
- >
366
- <input
367
- class="sv-grid-chart-ai-input"
368
- type="text"
369
- placeholder="Describe a chart, e.g. revenue by region stacked by product"
370
- bind:value={aiPrompt}
371
- disabled={aiBusy}
372
- />
373
- <button type="submit" class="sv-grid-chart-btn" disabled={aiBusy || !aiPrompt.trim()}>
374
- {aiBusy ? "Thinking..." : "Chart it"}
375
- </button>
376
- </form>
377
- {#if aiError}
378
- <p class="sv-grid-chart-ai-msg is-error">{aiError}</p>
379
- {:else if aiRationale}
380
- <p class="sv-grid-chart-ai-msg">{aiRationale}</p>
381
- {/if}
382
- </div>
383
- {/if}
384
- <div class="sv-grid-chart-controls">
385
- {#if !isCustom}
386
- <label class="sv-grid-chart-ctl">
387
- <span class="sv-grid-chart-ctl-lbl">Type</span>
388
- <select value={ctrl.chartType} onchange={(e) => (ctrl.chartType = e.currentTarget.value as ChartType)}>
389
- {#each TYPES as t (t.value)}<option value={t.value}>{t.label}</option>{/each}
390
- </select>
391
- </label>
392
- <label class="sv-grid-chart-ctl">
393
- <span class="sv-grid-chart-ctl-lbl">Group by</span>
394
- <select value={ctrl.chartDimensionId ?? ""} onchange={(e) => (ctrl.chartDimensionId = e.currentTarget.value || null)}>
395
- {#each columns.dims as d (d.id)}<option value={d.id}>{d.label}</option>{/each}
396
- </select>
397
- </label>
398
- {#if columns.dims.length > 1}
399
- <label class="sv-grid-chart-ctl">
400
- <span class="sv-grid-chart-ctl-lbl">Split by</span>
401
- <select value={ctrl.chartSeriesId ?? ""} onchange={(e) => (ctrl.chartSeriesId = e.currentTarget.value || null)}>
402
- <option value="">(none)</option>
403
- {#each columns.dims as d (d.id)}<option value={d.id}>{d.label}</option>{/each}
404
- </select>
405
- </label>
406
- {/if}
407
- <label class="sv-grid-chart-ctl">
408
- <span class="sv-grid-chart-ctl-lbl">Value</span>
409
- <select value={ctrl.chartMeasureId ?? ""} onchange={(e) => (ctrl.chartMeasureId = e.currentTarget.value || null)}>
410
- {#each columns.measures as m (m.id)}<option value={m.id}>{m.label}</option>{/each}
411
- </select>
412
- </label>
413
- <label class="sv-grid-chart-ctl">
414
- <span class="sv-grid-chart-ctl-lbl">Aggregate</span>
415
- <select value={ctrl.chartReduce} onchange={(e) => (ctrl.chartReduce = e.currentTarget.value as "sum" | "avg" | "count")}>
416
- {#each REDUCERS as r (r.value)}<option value={r.value}>{r.label}</option>{/each}
417
- </select>
418
- </label>
419
- <label class="sv-grid-chart-ctl">
420
- <span class="sv-grid-chart-ctl-lbl">Format</span>
421
- <select value={ctrl.chartValueFormat} onchange={(e) => (ctrl.chartValueFormat = e.currentTarget.value as "number" | "currency" | "percent")}>
422
- {#each FORMATS as f (f.value)}<option value={f.value}>{f.label}</option>{/each}
423
- </select>
424
- </label>
425
- {#if ctrl.chartType !== "pie"}
426
- <label class="sv-grid-chart-toggle">
427
- <input type="checkbox" checked={ctrl.chartStacked} onchange={(e) => (ctrl.chartStacked = e.currentTarget.checked)} />
428
- Stacked
429
- </label>
430
- {/if}
431
- <label class="sv-grid-chart-toggle">
432
- <input type="checkbox" checked={ctrl.chartDataLabels} onchange={(e) => (ctrl.chartDataLabels = e.currentTarget.checked)} />
433
- Labels
434
- </label>
435
- {#if ctrl.chartType !== "pie"}
436
- <label class="sv-grid-chart-toggle" title="Logarithmic value axis - flattens wide-range data">
437
- <input type="checkbox" checked={ctrl.chartLogScale} onchange={(e) => (ctrl.chartLogScale = e.currentTarget.checked)} />
438
- Log scale
439
- </label>
440
- {/if}
441
- {#if ctrl.chartDimensionIsDate && ctrl.chartType !== "pie"}
442
- <label class="sv-grid-chart-toggle" title="Space points by actual date - real time ticks and proportional gaps">
443
- <input type="checkbox" checked={ctrl.chartTimeAxis} onchange={(e) => (ctrl.chartTimeAxis = e.currentTarget.checked)} />
444
- Date axis
445
- </label>
446
- {/if}
447
- {/if}
448
- </div>
449
- </header>
450
-
451
- <div class="sv-grid-chart-panel-body" bind:this={bodyEl} bind:clientWidth={bodyW} bind:clientHeight={bodyH}>
452
- {#if spec && (spec.series.length || isCustom)}
453
- <SvGridChart
454
- {spec}
455
- width={chartW}
456
- height={chartH}
457
- onSelect={onChartSelect}
458
- interactive
459
- legend
460
- dataLabels={ctrl.chartDataLabels}
461
- zoomable={cfg?.zoom === true}
462
- brush={cfg?.brush === true}
463
- toolbar={cfg?.export === true ? true : undefined}
464
- />
465
- {:else}
466
- <p class="sv-grid-chart-empty">
467
- {#if !columns.dims.length || !columns.measures.length}
468
- Charting needs at least one text column (to group by) and one numeric column (to measure).
469
- {:else}
470
- No data to chart.
471
- {/if}
472
- </p>
473
- {/if}
474
- </div>
475
-
476
- {#if floating && !maximized}
477
- <!-- svelte-ignore a11y_no_static_element_interactions -->
478
- <div
479
- class="sv-grid-chart-float-resizer"
480
- onpointerdown={startFloatResize}
481
- role="separator"
482
- aria-label="Resize chart window"
483
- ></div>
484
- {/if}
485
- </section>
1
+ <script lang="ts" generics="TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData">
2
+ /**
3
+ * The built-in integrated-chart panel. Opens as a right-side drawer (default)
4
+ * or docks to the bottom edge, and stays live with the grid: it reads the
5
+ * controller's derived
6
+ * `chartSpec` - which itself derives from the displayed rows + the active cell
7
+ * selection - so filtering, sorting, paging, or selecting a range re-draws the
8
+ * chart with no manual sync. Clicking a chart category cross-filters the grid.
9
+ *
10
+ * In `buildSpec` (custom) mode the axis pickers are hidden - the author owns
11
+ * the spec - and the panel is a thin live frame around it.
12
+ *
13
+ * Rendered by SvGrid.svelte and handed `{ctrl}`; not placed by consumers.
14
+ */
15
+ import { fly } from "svelte/transition";
16
+ import { cubicOut } from "svelte/easing";
17
+ import SvGridChart from "./SvGridChart.svelte";
18
+ import { downloadChartPng, downloadChartSvg, downloadChartCsv, chartToPngBlob } from "./chart-export";
19
+ import type { ChartType, ChartSelection, RowData, TableFeatures } from "./index";
20
+ import type { SvGridController } from "./SvGrid.controller.svelte";
21
+
22
+ let { ctrl }: { ctrl: SvGridController<TFeatures, TData> } = $props();
23
+
24
+ const config = $derived(ctrl.chartingConfig);
25
+ const cfg = $derived(ctrl.chartCfg);
26
+ const isCustom = $derived(ctrl.chartIsCustom);
27
+ const position = $derived(config?.position ?? "bottom");
28
+ const crossFilter = $derived(config?.crossFilter !== false);
29
+ const spec = $derived(ctrl.chartSpec);
30
+ const columns = $derived(ctrl.chartableColumns);
31
+ const size = $derived(
32
+ ctrl.chartSize ?? (position === "right" ? (config?.width ?? 340) : (config?.height ?? 260)),
33
+ );
34
+
35
+ // ---- Pop-out (floating window) state -----------------------------------
36
+ const floating = $derived(ctrl.chartFloating);
37
+ const maximized = $derived(ctrl.chartMaximized);
38
+ const rect = $derived(ctrl.chartFloatRect);
39
+ let sectionEl = $state<HTMLElement | null>(null);
40
+
41
+ function parentBox(): { w: number; h: number } {
42
+ const p = sectionEl?.offsetParent as HTMLElement | null;
43
+ return { w: p?.clientWidth ?? 800, h: p?.clientHeight ?? 520 };
44
+ }
45
+ function popOut() {
46
+ if (!ctrl.chartFloatRect) {
47
+ const { w: pw, h: ph } = parentBox();
48
+ const w = Math.min(600, Math.max(360, pw - 80));
49
+ const h = Math.min(430, Math.max(260, ph - 90));
50
+ ctrl.chartFloatRect = { x: Math.round((pw - w) / 2), y: Math.round((ph - h) / 2), w, h };
51
+ }
52
+ ctrl.chartFloating = true;
53
+ }
54
+ function dockBack() {
55
+ ctrl.chartFloating = false;
56
+ ctrl.chartMaximized = false;
57
+ }
58
+ const toggleMax = () => (ctrl.chartMaximized = !ctrl.chartMaximized);
59
+
60
+ function startDrag(e: PointerEvent) {
61
+ if (!floating || maximized) return;
62
+ if ((e.target as HTMLElement)?.closest("button")) return; // don't drag from the buttons
63
+ const r = ctrl.chartFloatRect;
64
+ if (!r) return;
65
+ e.preventDefault();
66
+ const sx = e.clientX;
67
+ const sy = e.clientY;
68
+ const { w: pw, h: ph } = parentBox();
69
+ const move = (ev: PointerEvent) => {
70
+ const nx = Math.max(0, Math.min(pw - r.w, r.x + ev.clientX - sx));
71
+ const ny = Math.max(0, Math.min(ph - r.h, r.y + ev.clientY - sy));
72
+ ctrl.chartFloatRect = { x: nx, y: ny, w: r.w, h: r.h };
73
+ };
74
+ const up = () => {
75
+ window.removeEventListener("pointermove", move);
76
+ window.removeEventListener("pointerup", up);
77
+ };
78
+ window.addEventListener("pointermove", move);
79
+ window.addEventListener("pointerup", up);
80
+ }
81
+
82
+ function startFloatResize(e: PointerEvent) {
83
+ const r = ctrl.chartFloatRect;
84
+ if (!r) return;
85
+ e.preventDefault();
86
+ e.stopPropagation();
87
+ const sx = e.clientX;
88
+ const sy = e.clientY;
89
+ (e.target as HTMLElement).setPointerCapture?.(e.pointerId);
90
+ const move = (ev: PointerEvent) => {
91
+ ctrl.chartFloatRect = {
92
+ x: r.x,
93
+ y: r.y,
94
+ w: Math.max(300, r.w + ev.clientX - sx),
95
+ h: Math.max(220, r.h + ev.clientY - sy),
96
+ };
97
+ };
98
+ const up = () => {
99
+ window.removeEventListener("pointermove", move);
100
+ window.removeEventListener("pointerup", up);
101
+ };
102
+ window.addEventListener("pointermove", move);
103
+ window.addEventListener("pointerup", up);
104
+ }
105
+
106
+ const floatStyle = $derived(
107
+ floating
108
+ ? maximized
109
+ ? "left:12px; top:12px; right:12px; bottom:12px; width:auto; height:auto;"
110
+ : rect
111
+ ? `left:${rect.x}px; top:${rect.y}px; width:${rect.w}px; height:${rect.h}px;`
112
+ : ""
113
+ : position === "right"
114
+ ? `width:${size}px`
115
+ : `height:${size}px`,
116
+ );
117
+
118
+ // Measure the chart area so the chart lays out to fit it (no overflow /
119
+ // clipped axis labels) - the docked panel is a fixed-size container. Reserve
120
+ // room for the legend (rendered below the SVG for >1 series) and the export
121
+ // toolbar, so neither gets clipped.
122
+ let bodyW = $state(0);
123
+ let bodyH = $state(0);
124
+ const seriesCount = $derived(spec ? (spec.type === "pie" ? spec.categories.length : spec.series.length) : 0);
125
+ const reserve = $derived((seriesCount > 1 ? 36 : 0) + (cfg?.export === true ? 30 : 0));
126
+ const chartW = $derived(bodyW > 24 ? bodyW - 8 : undefined);
127
+ const chartH = $derived(bodyH > 24 ? Math.max(120, bodyH - 8 - reserve) : undefined);
128
+
129
+ // Aggregation-friendly types offered in the picker; exotic types come via
130
+ // `buildSpec`, where the picker is hidden.
131
+ const TYPES: Array<{ value: ChartType; label: string }> = [
132
+ { value: "bar", label: "Bar" },
133
+ { value: "line", label: "Line" },
134
+ { value: "area", label: "Area" },
135
+ { value: "pie", label: "Pie" },
136
+ ];
137
+ const REDUCERS = [
138
+ { value: "sum", label: "Sum" },
139
+ { value: "avg", label: "Average" },
140
+ { value: "count", label: "Count" },
141
+ ] as const;
142
+ const FORMATS = [
143
+ { value: "number", label: "Number" },
144
+ { value: "currency", label: "Currency" },
145
+ { value: "percent", label: "Percent" },
146
+ ] as const;
147
+
148
+ function onChartSelect(sel: ChartSelection) {
149
+ if (crossFilter && sel.category != null) ctrl.applyChartCrossFilter(String(sel.category));
150
+ }
151
+
152
+ // ---- Export (PNG / SVG / CSV / copy) -----------------------------------
153
+ let bodyEl = $state<HTMLElement | null>(null);
154
+ let exportOpen = $state(false);
155
+ let copied = $state(false);
156
+ const svgEl = () => bodyEl?.querySelector("svg") ?? null;
157
+ const csvExportable = $derived(!!(spec && spec.categories?.length && spec.series?.length));
158
+ function exportPng() {
159
+ const el = svgEl();
160
+ if (el) void downloadChartPng(el, "chart.png");
161
+ exportOpen = false;
162
+ }
163
+ function exportSvg() {
164
+ const el = svgEl();
165
+ if (el) downloadChartSvg(el, "chart.svg");
166
+ exportOpen = false;
167
+ }
168
+ function exportCsv() {
169
+ if (spec) downloadChartCsv(spec, "chart.csv");
170
+ exportOpen = false;
171
+ }
172
+ async function copyImage() {
173
+ const el = svgEl();
174
+ exportOpen = false;
175
+ if (!el || typeof ClipboardItem === "undefined" || !navigator.clipboard?.write) return;
176
+ try {
177
+ const blob = await chartToPngBlob(el);
178
+ await navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]);
179
+ copied = true;
180
+ setTimeout(() => (copied = false), 1400);
181
+ } catch {
182
+ /* clipboard blocked - no-op */
183
+ }
184
+ }
185
+ $effect(() => {
186
+ if (!exportOpen) return;
187
+ const close = (e: PointerEvent) => {
188
+ if (!(e.target as HTMLElement)?.closest(".sv-grid-chart-export")) exportOpen = false;
189
+ };
190
+ window.addEventListener("pointerdown", close);
191
+ return () => window.removeEventListener("pointerdown", close);
192
+ });
193
+
194
+ // ---- AI "chart this" (enterprise-registered handler) -------------------
195
+ const aiEnabled = $derived(!!ctrl.chartAiHandler);
196
+ let aiOpen = $state(false);
197
+ let aiPrompt = $state("");
198
+ let aiBusy = $state(false);
199
+ let aiError = $state("");
200
+ let aiRationale = $state("");
201
+ async function runAi() {
202
+ const handler = ctrl.chartAiHandler;
203
+ const prompt = aiPrompt.trim();
204
+ if (!handler || !prompt || aiBusy) return;
205
+ aiBusy = true;
206
+ aiError = "";
207
+ aiRationale = "";
208
+ try {
209
+ const cfg = await handler(prompt);
210
+ if (cfg) {
211
+ ctrl.applyChartConfig(cfg as Parameters<typeof ctrl.applyChartConfig>[0]);
212
+ aiRationale = typeof cfg.rationale === "string" ? cfg.rationale : "";
213
+ } else {
214
+ aiError = "Couldn't build a chart from that request.";
215
+ }
216
+ } catch (e) {
217
+ aiError = e instanceof Error ? e.message : "Something went wrong.";
218
+ } finally {
219
+ aiBusy = false;
220
+ }
221
+ }
222
+
223
+ // ---- Resize by dragging the panel's inner edge -------------------------
224
+ let dragging = $state(false);
225
+ function startResize(e: PointerEvent) {
226
+ e.preventDefault();
227
+ dragging = true;
228
+ const startPos = position === "right" ? e.clientX : e.clientY;
229
+ const startSize = size;
230
+ (e.target as HTMLElement).setPointerCapture?.(e.pointerId);
231
+ const move = (ev: PointerEvent) => {
232
+ const delta = position === "right" ? startPos - ev.clientX : startPos - ev.clientY;
233
+ ctrl.chartSize = Math.max(160, Math.min(720, startSize + delta));
234
+ };
235
+ const up = () => {
236
+ dragging = false;
237
+ window.removeEventListener("pointermove", move);
238
+ window.removeEventListener("pointerup", up);
239
+ };
240
+ window.addEventListener("pointermove", move);
241
+ window.addEventListener("pointerup", up);
242
+ }
243
+ </script>
244
+
245
+ <section
246
+ bind:this={sectionEl}
247
+ class="sv-grid-chart-panel"
248
+ class:is-right={position === "right" && !floating}
249
+ class:is-bottom={position === "bottom" && !floating}
250
+ class:is-floating={floating}
251
+ class:is-maximized={floating && maximized}
252
+ style={floatStyle}
253
+ aria-label="Chart"
254
+ transition:fly={{
255
+ x: !floating && position === "right" ? size : 0,
256
+ y: !floating && position === "bottom" ? size : 0,
257
+ duration: 180,
258
+ easing: cubicOut,
259
+ }}
260
+ >
261
+ {#if !floating}
262
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
263
+ <div
264
+ class="sv-grid-chart-resizer"
265
+ class:is-dragging={dragging}
266
+ onpointerdown={startResize}
267
+ role="separator"
268
+ aria-orientation={position === "right" ? "vertical" : "horizontal"}
269
+ aria-label="Resize chart panel"
270
+ ></div>
271
+ {/if}
272
+
273
+ <header class="sv-grid-chart-panel-head">
274
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
275
+ <div
276
+ class="sv-grid-chart-drawer-title"
277
+ class:is-drag-handle={floating && !maximized}
278
+ onpointerdown={startDrag}
279
+ >
280
+ <svg class="sv-grid-chart-title-icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
281
+ <line x1="12" y1="20" x2="12" y2="10" /><line x1="18" y1="20" x2="18" y2="4" /><line x1="6" y1="20" x2="6" y2="16" />
282
+ </svg>
283
+ <span class="sv-grid-chart-title">Chart</span>
284
+ <div class="sv-grid-chart-actions">
285
+ {#if crossFilter && !isCustom}
286
+ <button type="button" class="sv-grid-chart-btn" onclick={() => ctrl.clearChartCrossFilter()}>Clear filter</button>
287
+ {/if}
288
+ <div class="sv-grid-chart-export">
289
+ <button type="button" class="sv-grid-chart-icon-btn" aria-label="Export chart" title="Export" aria-expanded={exportOpen} onclick={() => (exportOpen = !exportOpen)}>
290
+ {#if copied}
291
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12" /></svg>
292
+ {:else}
293
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" y1="15" x2="12" y2="3" /></svg>
294
+ {/if}
295
+ </button>
296
+ {#if exportOpen}
297
+ <div class="sv-grid-chart-export-menu" role="menu">
298
+ <button type="button" role="menuitem" onclick={exportPng}>PNG image</button>
299
+ <button type="button" role="menuitem" onclick={exportSvg}>SVG vector</button>
300
+ <button type="button" role="menuitem" disabled={!csvExportable} onclick={exportCsv}>CSV data</button>
301
+ <button type="button" role="menuitem" onclick={copyImage}>Copy to clipboard</button>
302
+ </div>
303
+ {/if}
304
+ </div>
305
+ {#if aiEnabled && !isCustom}
306
+ <button type="button" class="sv-grid-chart-btn sv-grid-chart-ai-btn" class:is-active={aiOpen} aria-label="Chart with AI" title="Chart with AI" aria-expanded={aiOpen} onclick={() => (aiOpen = !aiOpen)}>
307
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2l1.9 4.6L18.5 8.5 13.9 10.4 12 15l-1.9-4.6L5.5 8.5l4.6-1.9L12 2zm6 11l1 2.4 2.4 1-2.4 1L18 21l-1-2.6-2.4-1 2.4-1L18 13zM6 14l.8 2 2 .8-2 .8L6 20l-.8-2.4-2-.8 2-.8L6 14z" /></svg>
308
+ AI
309
+ </button>
310
+ {/if}
311
+ {#if !isCustom}
312
+ <button type="button" class="sv-grid-chart-icon-btn" aria-label="Add chart" title="Add chart" onclick={() => ctrl.addChart()}>
313
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" /></svg>
314
+ </button>
315
+ {/if}
316
+ {#if floating}
317
+ <button type="button" class="sv-grid-chart-icon-btn" aria-label={maximized ? "Restore" : "Maximize"} title={maximized ? "Restore" : "Maximize"} onclick={toggleMax}>
318
+ {#if maximized}
319
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 3v3a2 2 0 0 1-2 2H3" /><path d="M21 8h-3a2 2 0 0 1-2-2V3" /><path d="M3 16h3a2 2 0 0 1 2 2v3" /><path d="M16 21v-3a2 2 0 0 1 2-2h3" /></svg>
320
+ {:else}
321
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 3H5a2 2 0 0 0-2 2v3" /><path d="M21 8V5a2 2 0 0 0-2-2h-3" /><path d="M3 16v3a2 2 0 0 0 2 2h3" /><path d="M16 21h3a2 2 0 0 0 2-2v-3" /></svg>
322
+ {/if}
323
+ </button>
324
+ <button type="button" class="sv-grid-chart-icon-btn" aria-label="Dock chart" title="Dock" onclick={dockBack}>
325
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2" /><line x1="15" y1="3" x2="15" y2="21" /></svg>
326
+ </button>
327
+ {:else}
328
+ <button type="button" class="sv-grid-chart-icon-btn" aria-label="Pop out chart" title="Pop out" onclick={popOut}>
329
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 3h6v6" /><path d="M10 14 21 3" /><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></svg>
330
+ </button>
331
+ {/if}
332
+ <button type="button" class="sv-grid-chart-close" aria-label="Close chart" onclick={() => (ctrl.chartPanelOpen = false)}>&times;</button>
333
+ </div>
334
+ </div>
335
+ {#if !isCustom && ctrl.charts.length > 1}
336
+ <div class="sv-grid-chart-tabs" role="tablist" aria-label="Charts">
337
+ {#each ctrl.charts as c, i (c.id)}
338
+ <div class="sv-grid-chart-tab" class:is-active={i === ctrl.activeChartIndex}>
339
+ <button
340
+ type="button"
341
+ class="sv-grid-chart-tab-label"
342
+ role="tab"
343
+ aria-selected={i === ctrl.activeChartIndex}
344
+ onclick={() => (ctrl.activeChartIndex = i)}
345
+ >{c.title}</button>
346
+ <button
347
+ type="button"
348
+ class="sv-grid-chart-tab-x"
349
+ aria-label={`Remove ${c.title}`}
350
+ title="Remove"
351
+ onclick={() => ctrl.removeChart(i)}
352
+ >&times;</button>
353
+ </div>
354
+ {/each}
355
+ </div>
356
+ {/if}
357
+ {#if aiEnabled && aiOpen && !isCustom}
358
+ <div class="sv-grid-chart-ai">
359
+ <form
360
+ class="sv-grid-chart-ai-row"
361
+ onsubmit={(e) => {
362
+ e.preventDefault();
363
+ runAi();
364
+ }}
365
+ >
366
+ <input
367
+ class="sv-grid-chart-ai-input"
368
+ type="text"
369
+ placeholder="Describe a chart, e.g. revenue by region stacked by product"
370
+ bind:value={aiPrompt}
371
+ disabled={aiBusy}
372
+ />
373
+ <button type="submit" class="sv-grid-chart-btn" disabled={aiBusy || !aiPrompt.trim()}>
374
+ {aiBusy ? "Thinking..." : "Chart it"}
375
+ </button>
376
+ </form>
377
+ {#if aiError}
378
+ <p class="sv-grid-chart-ai-msg is-error">{aiError}</p>
379
+ {:else if aiRationale}
380
+ <p class="sv-grid-chart-ai-msg">{aiRationale}</p>
381
+ {/if}
382
+ </div>
383
+ {/if}
384
+ <div class="sv-grid-chart-controls">
385
+ {#if !isCustom}
386
+ <label class="sv-grid-chart-ctl">
387
+ <span class="sv-grid-chart-ctl-lbl">Type</span>
388
+ <select value={ctrl.chartType} onchange={(e) => (ctrl.chartType = e.currentTarget.value as ChartType)}>
389
+ {#each TYPES as t (t.value)}<option value={t.value}>{t.label}</option>{/each}
390
+ </select>
391
+ </label>
392
+ <label class="sv-grid-chart-ctl">
393
+ <span class="sv-grid-chart-ctl-lbl">Group by</span>
394
+ <select value={ctrl.chartDimensionId ?? ""} onchange={(e) => (ctrl.chartDimensionId = e.currentTarget.value || null)}>
395
+ {#each columns.dims as d (d.id)}<option value={d.id}>{d.label}</option>{/each}
396
+ </select>
397
+ </label>
398
+ {#if columns.dims.length > 1}
399
+ <label class="sv-grid-chart-ctl">
400
+ <span class="sv-grid-chart-ctl-lbl">Split by</span>
401
+ <select value={ctrl.chartSeriesId ?? ""} onchange={(e) => (ctrl.chartSeriesId = e.currentTarget.value || null)}>
402
+ <option value="">(none)</option>
403
+ {#each columns.dims as d (d.id)}<option value={d.id}>{d.label}</option>{/each}
404
+ </select>
405
+ </label>
406
+ {/if}
407
+ <label class="sv-grid-chart-ctl">
408
+ <span class="sv-grid-chart-ctl-lbl">Value</span>
409
+ <select value={ctrl.chartMeasureId ?? ""} onchange={(e) => (ctrl.chartMeasureId = e.currentTarget.value || null)}>
410
+ {#each columns.measures as m (m.id)}<option value={m.id}>{m.label}</option>{/each}
411
+ </select>
412
+ </label>
413
+ <label class="sv-grid-chart-ctl">
414
+ <span class="sv-grid-chart-ctl-lbl">Aggregate</span>
415
+ <select value={ctrl.chartReduce} onchange={(e) => (ctrl.chartReduce = e.currentTarget.value as "sum" | "avg" | "count")}>
416
+ {#each REDUCERS as r (r.value)}<option value={r.value}>{r.label}</option>{/each}
417
+ </select>
418
+ </label>
419
+ <label class="sv-grid-chart-ctl">
420
+ <span class="sv-grid-chart-ctl-lbl">Format</span>
421
+ <select value={ctrl.chartValueFormat} onchange={(e) => (ctrl.chartValueFormat = e.currentTarget.value as "number" | "currency" | "percent")}>
422
+ {#each FORMATS as f (f.value)}<option value={f.value}>{f.label}</option>{/each}
423
+ </select>
424
+ </label>
425
+ {#if ctrl.chartType !== "pie"}
426
+ <label class="sv-grid-chart-toggle">
427
+ <input type="checkbox" checked={ctrl.chartStacked} onchange={(e) => (ctrl.chartStacked = e.currentTarget.checked)} />
428
+ Stacked
429
+ </label>
430
+ {/if}
431
+ <label class="sv-grid-chart-toggle">
432
+ <input type="checkbox" checked={ctrl.chartDataLabels} onchange={(e) => (ctrl.chartDataLabels = e.currentTarget.checked)} />
433
+ Labels
434
+ </label>
435
+ {#if ctrl.chartType !== "pie"}
436
+ <label class="sv-grid-chart-toggle" title="Logarithmic value axis - flattens wide-range data">
437
+ <input type="checkbox" checked={ctrl.chartLogScale} onchange={(e) => (ctrl.chartLogScale = e.currentTarget.checked)} />
438
+ Log scale
439
+ </label>
440
+ {/if}
441
+ {#if ctrl.chartDimensionIsDate && ctrl.chartType !== "pie"}
442
+ <label class="sv-grid-chart-toggle" title="Space points by actual date - real time ticks and proportional gaps">
443
+ <input type="checkbox" checked={ctrl.chartTimeAxis} onchange={(e) => (ctrl.chartTimeAxis = e.currentTarget.checked)} />
444
+ Date axis
445
+ </label>
446
+ {/if}
447
+ {/if}
448
+ </div>
449
+ </header>
450
+
451
+ <div class="sv-grid-chart-panel-body" bind:this={bodyEl} bind:clientWidth={bodyW} bind:clientHeight={bodyH}>
452
+ {#if spec && (spec.series.length || isCustom)}
453
+ <SvGridChart
454
+ {spec}
455
+ width={chartW}
456
+ height={chartH}
457
+ onSelect={onChartSelect}
458
+ interactive
459
+ legend
460
+ dataLabels={ctrl.chartDataLabels}
461
+ zoomable={cfg?.zoom === true}
462
+ brush={cfg?.brush === true}
463
+ toolbar={cfg?.export === true ? true : undefined}
464
+ />
465
+ {:else}
466
+ <p class="sv-grid-chart-empty">
467
+ {#if !columns.dims.length || !columns.measures.length}
468
+ Charting needs at least one text column (to group by) and one numeric column (to measure).
469
+ {:else}
470
+ No data to chart.
471
+ {/if}
472
+ </p>
473
+ {/if}
474
+ </div>
475
+
476
+ {#if floating && !maximized}
477
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
478
+ <div
479
+ class="sv-grid-chart-float-resizer"
480
+ onpointerdown={startFloatResize}
481
+ role="separator"
482
+ aria-label="Resize chart window"
483
+ ></div>
484
+ {/if}
485
+ </section>