@svgrid/grid 3.0.2 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. package/README.md +2 -1
  2. package/dist/GridMenus.svelte +44 -2
  3. package/dist/SvChartMenu.svelte +69 -0
  4. package/dist/SvChartMenu.svelte.d.ts +11 -0
  5. package/dist/SvChartPanes.svelte +113 -0
  6. package/dist/SvChartPanes.svelte.d.ts +20 -0
  7. package/dist/SvGrid.controller.svelte.d.ts +110 -39
  8. package/dist/SvGrid.controller.svelte.js +655 -200
  9. package/dist/SvGrid.css +105 -2
  10. package/dist/SvGrid.svelte +386 -514
  11. package/dist/SvGrid.types.d.ts +210 -9
  12. package/dist/SvGridCellEditor.svelte +17 -0
  13. package/dist/SvGridChart.svelte +2257 -329
  14. package/dist/SvGridChart.svelte.d.ts +6 -106
  15. package/dist/SvGridChart.types.d.ts +338 -0
  16. package/dist/SvGridChart.types.js +1 -0
  17. package/dist/SvGridChartBuilder.svelte +323 -0
  18. package/dist/SvGridChartBuilder.svelte.d.ts +34 -0
  19. package/dist/SvGridChartPanel.svelte +221 -275
  20. package/dist/SvGridChartPickers.svelte +417 -0
  21. package/dist/SvGridChartPickers.svelte.d.ts +70 -0
  22. package/dist/SvGridChartView.svelte +13 -14
  23. package/dist/SvModal.svelte +7 -1
  24. package/dist/SvModal.svelte.d.ts +5 -0
  25. package/dist/ai.d.ts +40 -8
  26. package/dist/ai.js +135 -24
  27. package/dist/aria.d.ts +11 -0
  28. package/dist/build-api.js +108 -22
  29. package/dist/cdn/GridMenus-7kbpnnBW.js +635 -0
  30. package/dist/cdn/GridMenus-BL7ZgQvU.js +636 -0
  31. package/dist/cdn/SvChartMenu-Bl6PBbkT.js +58 -0
  32. package/dist/cdn/SvChartMenu-FBSMINA6.js +59 -0
  33. package/dist/cdn/{SvDateRangeInput-DMLKmGEc.js → SvDateRangeInput-BeU_TkbQ.js} +15 -14
  34. package/dist/cdn/{SvDateRangeInput-CaOuMs8O.js → SvDateRangeInput-yX8vzleW.js} +38 -37
  35. package/dist/cdn/{SvDateTimePicker-sonaH0oh.js → SvDateTimePicker-DQwt4UAs.js} +139 -180
  36. package/dist/cdn/SvDateTimePicker-vNU6bZ-q.js +775 -0
  37. package/dist/cdn/{SvGridCellEditor-CjEJMJKc.js → SvGridCellEditor-B1p-vCK5.js} +188 -178
  38. package/dist/cdn/{SvGridCellEditor-BWnTi2N7.js → SvGridCellEditor-D_0q4xAu.js} +98 -88
  39. package/dist/cdn/SvGridChart-C3EWAZaM.js +3574 -0
  40. package/dist/cdn/SvGridChart-DzLkSwCH.js +3572 -0
  41. package/dist/cdn/SvGridChartBuilder-BE2T1ykB.js +783 -0
  42. package/dist/cdn/SvGridChartBuilder-CfII62sZ.js +784 -0
  43. package/dist/cdn/SvGridChartPanel-CCX5_Wgd.js +675 -0
  44. package/dist/cdn/SvGridChartPanel-xf4sWVTo.js +699 -0
  45. package/dist/cdn/SvGridChartView-CfuXmY5I.js +48 -0
  46. package/dist/cdn/SvGridChartView-DYwabQWj.js +47 -0
  47. package/dist/cdn/SvMenuList-CLN8OuIK.js +452 -0
  48. package/dist/cdn/SvMenuList-DkGhNKjf.js +451 -0
  49. package/dist/cdn/SvModal-BAE-pjZX.js +396 -0
  50. package/dist/cdn/SvModal-CIZWCcad.js +397 -0
  51. package/dist/cdn/chart-Dz7SqMXH.js +4341 -0
  52. package/dist/cdn/chart-export-BXqueeWJ.js +135 -0
  53. package/dist/cdn/chart-export-pdf-DONFUnNY.js +110 -0
  54. package/dist/cdn/chart-panel-messages-CUbf2R4i.js +1052 -0
  55. package/dist/cdn/chart-panel-messages-CmNrMdsr.js +1053 -0
  56. package/dist/cdn/chart-summary-BJW_tg_X.js +138 -0
  57. package/dist/cdn/chart-trend-CaN9mDEV.js +50 -0
  58. package/dist/cdn/chart-validate-HcOVFAfJ.js +188 -0
  59. package/dist/cdn/{column-resize-DsfNXMom.js → column-resize-DpmOLfRp.js} +3 -3
  60. package/dist/cdn/{date-format-BnnHlqGw.js → date-format-CtquV-p3.js} +583 -603
  61. package/dist/cdn/{date-format-BNii4zeD.js → date-format-D6KFzU_W.js} +347 -367
  62. package/dist/cdn/dismissable-DAHetSNk.js +44 -0
  63. package/dist/cdn/editor-contract-LXgQJAgd.js +22 -0
  64. package/dist/cdn/focus-trap-BBOQYBma.js +65 -0
  65. package/dist/cdn/{row-resize-BRcimkUT.js → row-resize-niQCp040.js} +44 -36
  66. package/dist/cdn/{src-DNbz94Ct.js → src-BKhZ6eXd.js} +10227 -9787
  67. package/dist/cdn/{src-BJ2opd0j.js → src-V1uu8iE9.js} +6568 -6128
  68. package/dist/cdn/svgrid.js +26 -15
  69. package/dist/cdn/svgrid.svelte-external.js +26 -15
  70. package/dist/chart-axes.d.ts +53 -0
  71. package/dist/chart-axes.js +351 -0
  72. package/dist/chart-cartesian.d.ts +88 -0
  73. package/dist/chart-cartesian.js +1862 -0
  74. package/dist/chart-decimate.d.ts +51 -0
  75. package/dist/chart-decimate.js +199 -0
  76. package/dist/chart-export-pdf.d.ts +47 -0
  77. package/dist/chart-export-pdf.js +187 -0
  78. package/dist/chart-export.d.ts +2 -1
  79. package/dist/chart-export.js +18 -3
  80. package/dist/chart-financial.d.ts +91 -0
  81. package/dist/chart-financial.js +175 -0
  82. package/dist/chart-flow.d.ts +10 -0
  83. package/dist/chart-flow.js +298 -0
  84. package/dist/chart-format.d.ts +18 -0
  85. package/dist/chart-format.js +65 -0
  86. package/dist/chart-grid.d.ts +8 -0
  87. package/dist/chart-grid.js +190 -0
  88. package/dist/chart-hierarchy.d.ts +17 -0
  89. package/dist/chart-hierarchy.js +146 -0
  90. package/dist/chart-indicators.d.ts +115 -0
  91. package/dist/chart-indicators.js +389 -0
  92. package/dist/chart-messages.d.ts +95 -0
  93. package/dist/chart-messages.js +92 -0
  94. package/dist/chart-motion.d.ts +16 -0
  95. package/dist/chart-motion.js +141 -0
  96. package/dist/chart-panel-messages.d.ts +265 -0
  97. package/dist/chart-panel-messages.js +285 -0
  98. package/dist/chart-pivot.d.ts +87 -0
  99. package/dist/chart-pivot.js +156 -0
  100. package/dist/chart-polar.d.ts +25 -0
  101. package/dist/chart-polar.js +700 -0
  102. package/dist/chart-samples.d.ts +16 -0
  103. package/dist/chart-samples.js +153 -0
  104. package/dist/chart-scale.d.ts +113 -0
  105. package/dist/chart-scale.js +405 -0
  106. package/dist/chart-stats.d.ts +111 -0
  107. package/dist/chart-stats.js +419 -0
  108. package/dist/chart-stream.d.ts +45 -0
  109. package/dist/chart-stream.js +91 -0
  110. package/dist/chart-summary.d.ts +27 -0
  111. package/dist/chart-summary.js +205 -0
  112. package/dist/chart-sync.svelte.d.ts +32 -0
  113. package/dist/chart-sync.svelte.js +23 -0
  114. package/dist/chart-table.d.ts +35 -0
  115. package/dist/chart-table.js +165 -0
  116. package/dist/chart-trend.d.ts +19 -0
  117. package/dist/chart-trend.js +61 -0
  118. package/dist/chart-types.d.ts +1456 -0
  119. package/dist/chart-types.js +6 -0
  120. package/dist/chart-validate.d.ts +43 -0
  121. package/dist/chart-validate.js +272 -0
  122. package/dist/chart-zoom.d.ts +40 -0
  123. package/dist/chart-zoom.js +145 -0
  124. package/dist/chart.d.ts +186 -865
  125. package/dist/chart.js +460 -2204
  126. package/dist/clipboard.d.ts +1 -0
  127. package/dist/clipboard.js +137 -18
  128. package/dist/column-resize.d.ts +3 -0
  129. package/dist/column-resize.js +4 -1
  130. package/dist/columns.js +3 -0
  131. package/dist/command-context.d.ts +19 -0
  132. package/dist/command-context.js +116 -0
  133. package/dist/core.d.ts +7 -0
  134. package/dist/createPopoverSelect.svelte.d.ts +2 -2
  135. package/dist/editing.d.ts +2 -8
  136. package/dist/editing.js +262 -33
  137. package/dist/fill-patterns.d.ts +0 -5
  138. package/dist/fill-patterns.js +56 -2
  139. package/dist/grid-messages.d.ts +10 -3
  140. package/dist/grid-messages.js +5 -0
  141. package/dist/history.d.ts +83 -0
  142. package/dist/history.js +133 -0
  143. package/dist/index.d.ts +24 -6
  144. package/dist/index.js +20 -2
  145. package/dist/keyboard-handlers.js +88 -31
  146. package/dist/keyboard.d.ts +55 -0
  147. package/dist/keyboard.js +132 -0
  148. package/dist/menus.d.ts +3 -1
  149. package/dist/menus.js +7 -2
  150. package/dist/merges.d.ts +97 -0
  151. package/dist/merges.js +147 -0
  152. package/dist/row-resize.d.ts +3 -0
  153. package/dist/row-resize.js +14 -0
  154. package/dist/scroll-sync.d.ts +1 -0
  155. package/dist/scroll-sync.js +24 -0
  156. package/dist/selection.d.ts +16 -0
  157. package/dist/selection.js +108 -16
  158. package/dist/shortcut-registry.d.ts +145 -0
  159. package/dist/shortcut-registry.js +44 -0
  160. package/dist/svgrid-wrapper.types.d.ts +213 -22
  161. package/dist/virtualization/virtualizer.js +38 -17
  162. package/package.json +6 -1
  163. package/src/GridMenus.svelte +44 -2
  164. package/src/SvChartMenu.svelte +69 -0
  165. package/src/SvChartPanes.svelte +113 -0
  166. package/src/SvChartPanes.test.ts +66 -0
  167. package/src/SvGrid.controller.svelte.ts +629 -172
  168. package/src/SvGrid.css +105 -2
  169. package/src/SvGrid.svelte +386 -514
  170. package/src/SvGrid.types.ts +188 -8
  171. package/src/SvGridCellEditor.svelte +17 -0
  172. package/src/SvGridChart.svelte +2257 -329
  173. package/src/SvGridChart.test.ts +1419 -1
  174. package/src/SvGridChart.types.ts +336 -0
  175. package/src/SvGridChartBuilder.svelte +323 -0
  176. package/src/SvGridChartPanel.svelte +221 -275
  177. package/src/SvGridChartPickers.svelte +417 -0
  178. package/src/SvGridChartView.svelte +13 -14
  179. package/src/SvModal.svelte +7 -1
  180. package/src/SvModal.test.ts +9 -0
  181. package/src/ai.test.ts +57 -2
  182. package/src/ai.ts +166 -22
  183. package/src/aria.d.ts +11 -0
  184. package/src/build-api.coverage.test.ts +184 -0
  185. package/src/build-api.ts +107 -18
  186. package/src/chart-axes.test.ts +573 -0
  187. package/src/chart-axes.ts +342 -0
  188. package/src/chart-cartesian.ts +1863 -0
  189. package/src/chart-decimate.test.ts +249 -0
  190. package/src/chart-decimate.ts +186 -0
  191. package/src/chart-export-pdf.test.ts +91 -0
  192. package/src/chart-export-pdf.ts +207 -0
  193. package/src/chart-export.test.ts +14 -0
  194. package/src/chart-export.ts +13 -3
  195. package/src/chart-financial.ts +208 -0
  196. package/src/chart-flow.ts +286 -0
  197. package/src/chart-format.ts +67 -0
  198. package/src/chart-grid.ts +179 -0
  199. package/src/chart-hierarchy.ts +135 -0
  200. package/src/chart-indicators.test.ts +443 -0
  201. package/src/chart-indicators.ts +384 -0
  202. package/src/chart-messages.test.ts +21 -0
  203. package/src/chart-messages.ts +184 -0
  204. package/src/chart-motion.test.ts +71 -0
  205. package/src/chart-motion.ts +148 -0
  206. package/src/chart-panel-messages.test.ts +29 -0
  207. package/src/chart-panel-messages.ts +550 -0
  208. package/src/chart-pivot.test.ts +54 -0
  209. package/src/chart-pivot.ts +225 -0
  210. package/src/chart-polar.ts +674 -0
  211. package/src/chart-samples.ts +106 -0
  212. package/src/chart-scale.ts +394 -0
  213. package/src/chart-series-types.test.ts +714 -0
  214. package/src/chart-stats.ts +409 -0
  215. package/src/chart-stream.test.ts +75 -0
  216. package/src/chart-stream.ts +114 -0
  217. package/src/chart-summary.test.ts +90 -0
  218. package/src/chart-summary.ts +197 -0
  219. package/src/chart-sync.svelte.ts +41 -0
  220. package/src/chart-table.test.ts +76 -0
  221. package/src/chart-table.ts +178 -0
  222. package/src/chart-trend.ts +47 -0
  223. package/src/chart-types.ts +1309 -0
  224. package/src/chart-validate.test.ts +100 -0
  225. package/src/chart-validate.ts +247 -0
  226. package/src/chart-zoom.test.ts +137 -0
  227. package/src/chart-zoom.ts +124 -0
  228. package/src/chart.coverage.test.ts +81 -0
  229. package/src/chart.test.ts +56 -3
  230. package/src/chart.ts +557 -2784
  231. package/src/clipboard.test.ts +151 -0
  232. package/src/clipboard.ts +145 -16
  233. package/src/column-resize.ts +7 -1
  234. package/src/columns.ts +2 -0
  235. package/src/command-context.test.ts +200 -0
  236. package/src/command-context.ts +137 -0
  237. package/src/core.ts +7 -0
  238. package/src/editing.test.ts +281 -3
  239. package/src/editing.ts +256 -38
  240. package/src/fill-patterns.test.ts +35 -0
  241. package/src/fill-patterns.ts +56 -2
  242. package/src/grid-messages.ts +16 -3
  243. package/src/history.test.ts +196 -0
  244. package/src/history.ts +162 -0
  245. package/src/icon-seam.test.ts +3 -2
  246. package/src/index.ts +150 -2
  247. package/src/keyboard-handlers.coverage.test.ts +126 -6
  248. package/src/keyboard-handlers.ts +91 -30
  249. package/src/keyboard-shortcuts.seam.test.ts +200 -0
  250. package/src/keyboard.test.ts +124 -1
  251. package/src/keyboard.ts +142 -0
  252. package/src/menus.test.ts +26 -0
  253. package/src/menus.ts +10 -3
  254. package/src/merges.test.ts +112 -0
  255. package/src/merges.ts +168 -0
  256. package/src/pivot.grid.test.ts +40 -5
  257. package/src/row-resize.test.ts +44 -0
  258. package/src/row-resize.ts +17 -0
  259. package/src/scroll-sync.test.ts +42 -1
  260. package/src/scroll-sync.ts +25 -0
  261. package/src/selection.multi-range.test.ts +34 -0
  262. package/src/selection.test.ts +30 -2
  263. package/src/selection.ts +111 -19
  264. package/src/shortcut-registry.test.ts +97 -0
  265. package/src/shortcut-registry.ts +181 -0
  266. package/src/svgrid-wrapper.types.ts +184 -13
  267. package/src/svgrid.behavior.test.ts +135 -0
  268. package/src/svgrid.charting.test.ts +513 -3
  269. package/src/svgrid.context-menu.test.ts +31 -0
  270. package/src/svgrid.new-features.wrapper.test.ts +10 -3
  271. package/src/virtualization/virtualizer.ts +39 -17
  272. package/dist/cdn/GridMenus-DyFA-QxA.js +0 -602
  273. package/dist/cdn/GridMenus-M2XP9t0_.js +0 -603
  274. package/dist/cdn/SvDateTimePicker-CCbDZNZB.js +0 -816
  275. package/dist/cdn/SvGridChart-DAb9RbRs.js +0 -1480
  276. package/dist/cdn/SvGridChart-Yl-IXZFO.js +0 -1481
  277. package/dist/cdn/SvGridChartPanel-DayQpDsR.js +0 -834
  278. package/dist/cdn/SvGridChartPanel-aqf8n-vJ.js +0 -810
  279. package/dist/cdn/SvGridChartView-CxfakxR0.js +0 -56
  280. package/dist/cdn/SvGridChartView-Dm_YEx9L.js +0 -55
  281. package/dist/cdn/chart-CKJRCPOk.js +0 -1652
@@ -0,0 +1,196 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+ import {
3
+ undoHistory,
4
+ redoHistory,
5
+ pushHistory,
6
+ runHistoryGroup,
7
+ nextGroupId,
8
+ type HistoryStep,
9
+ } from './history'
10
+
11
+ function step(n: number, over: Partial<HistoryStep> = {}): HistoryStep {
12
+ return { rowId: `r${n}`, columnId: 'c', field: 'c', before: n, after: n * 10, ...over }
13
+ }
14
+
15
+ function makeCtx(history: HistoryStep[] = [], limit = 200) {
16
+ const applied: Array<{ rowId: string; direction: string }> = []
17
+ const ctx = {
18
+ history,
19
+ historyPtr: history.length - 1,
20
+ historyVersion: 0,
21
+ historyGroupId: undefined as string | undefined,
22
+ UNDO_LIMIT: limit,
23
+ applyHistoryStep: vi.fn((s: HistoryStep, direction: 'undo' | 'redo') => {
24
+ applied.push({ rowId: s.rowId, direction })
25
+ }),
26
+ }
27
+ return { ctx, applied }
28
+ }
29
+
30
+ describe('undo / redo without groups', () => {
31
+ it('walks one step per call', () => {
32
+ const { ctx, applied } = makeCtx([step(1), step(2)])
33
+ expect(undoHistory(ctx)).toBe(true)
34
+ expect(applied).toEqual([{ rowId: 'r2', direction: 'undo' }])
35
+ expect(ctx.historyPtr).toBe(0)
36
+ })
37
+
38
+ it('returns false at the bottom and the top', () => {
39
+ const { ctx } = makeCtx([])
40
+ expect(undoHistory(ctx)).toBe(false)
41
+ expect(redoHistory(ctx)).toBe(false)
42
+ })
43
+
44
+ it('round-trips undo then redo', () => {
45
+ const { ctx, applied } = makeCtx([step(1)])
46
+ undoHistory(ctx)
47
+ expect(ctx.historyPtr).toBe(-1)
48
+ redoHistory(ctx)
49
+ expect(ctx.historyPtr).toBe(0)
50
+ expect(applied.map((a) => a.direction)).toEqual(['undo', 'redo'])
51
+ })
52
+ })
53
+
54
+ describe('undo / redo with groups', () => {
55
+ it('undoes a whole group in one call', () => {
56
+ const g = 'g1'
57
+ const { ctx, applied } = makeCtx([
58
+ step(1),
59
+ step(2, { groupId: g }), step(3, { groupId: g }), step(4, { groupId: g }),
60
+ ])
61
+ expect(undoHistory(ctx)).toBe(true)
62
+ expect(applied.map((a) => a.rowId)).toEqual(['r4', 'r3', 'r2'])
63
+ // The pointer lands before the group, so the next undo takes the lone step.
64
+ expect(ctx.historyPtr).toBe(0)
65
+ })
66
+
67
+ it('undoes a group newest-first so repeated writes to one cell unwind right', () => {
68
+ // Both steps touch r1. Applying oldest-first would leave the cell holding
69
+ // the intermediate value instead of the original.
70
+ const g = 'g1'
71
+ const { ctx, applied } = makeCtx([
72
+ { rowId: 'r1', columnId: 'c', field: 'c', before: 'orig', after: 'mid', groupId: g },
73
+ { rowId: 'r1', columnId: 'c', field: 'c', before: 'mid', after: 'final', groupId: g },
74
+ ])
75
+ undoHistory(ctx)
76
+ expect(applied).toEqual([
77
+ { rowId: 'r1', direction: 'undo' },
78
+ { rowId: 'r1', direction: 'undo' },
79
+ ])
80
+ expect(ctx.applyHistoryStep.mock.calls[0]![0].before).toBe('mid')
81
+ expect(ctx.applyHistoryStep.mock.calls[1]![0].before).toBe('orig')
82
+ })
83
+
84
+ it('redoes a whole group in order', () => {
85
+ const g = 'g1'
86
+ const { ctx, applied } = makeCtx([
87
+ step(1, { groupId: g }), step(2, { groupId: g }), step(3, { groupId: g }),
88
+ ])
89
+ ctx.historyPtr = -1
90
+ expect(redoHistory(ctx)).toBe(true)
91
+ expect(applied.map((a) => a.rowId)).toEqual(['r1', 'r2', 'r3'])
92
+ expect(ctx.historyPtr).toBe(2)
93
+ })
94
+
95
+ it('keeps two adjacent groups separate', () => {
96
+ const { ctx, applied } = makeCtx([
97
+ step(1, { groupId: 'g1' }), step(2, { groupId: 'g1' }),
98
+ step(3, { groupId: 'g2' }), step(4, { groupId: 'g2' }),
99
+ ])
100
+ undoHistory(ctx)
101
+ expect(applied.map((a) => a.rowId)).toEqual(['r4', 'r3'])
102
+ expect(ctx.historyPtr).toBe(1)
103
+ undoHistory(ctx)
104
+ expect(applied.map((a) => a.rowId)).toEqual(['r4', 'r3', 'r2', 'r1'])
105
+ expect(ctx.historyPtr).toBe(-1)
106
+ })
107
+ })
108
+
109
+ describe('pushHistory', () => {
110
+ it('truncates forward history so an edit invalidates redo', () => {
111
+ const { ctx } = makeCtx([step(1), step(2), step(3)])
112
+ ctx.historyPtr = 0
113
+ pushHistory(ctx, [step(9)])
114
+ expect(ctx.history.map((s) => s.rowId)).toEqual(['r1', 'r9'])
115
+ expect(ctx.historyPtr).toBe(1)
116
+ })
117
+
118
+ it('drops the OLDEST entries when the buffer is full', () => {
119
+ const { ctx } = makeCtx([step(1), step(2), step(3)], 3)
120
+ pushHistory(ctx, [step(4)])
121
+ expect(ctx.history.map((s) => s.rowId)).toEqual(['r2', 'r3', 'r4'])
122
+ expect(ctx.historyPtr).toBe(2)
123
+ })
124
+
125
+ it('does not tag a group of one', () => {
126
+ const { ctx } = makeCtx()
127
+ pushHistory(ctx, [step(1)], nextGroupId())
128
+ expect(ctx.history[0]!.groupId).toBeUndefined()
129
+ })
130
+
131
+ it('tags every step when several are pushed together', () => {
132
+ const { ctx } = makeCtx()
133
+ pushHistory(ctx, [step(1), step(2)], nextGroupId())
134
+ const ids = ctx.history.map((s) => s.groupId)
135
+ expect(ids[0]).toBeDefined()
136
+ expect(new Set(ids).size).toBe(1)
137
+ })
138
+
139
+ it('ignores an empty push', () => {
140
+ const { ctx } = makeCtx()
141
+ pushHistory(ctx, [])
142
+ expect(ctx.history).toHaveLength(0)
143
+ expect(ctx.historyVersion).toBe(0)
144
+ })
145
+ })
146
+
147
+ describe('runHistoryGroup', () => {
148
+ it('joins separate pushes into one undoable action', () => {
149
+ const { ctx, applied } = makeCtx()
150
+ runHistoryGroup(ctx, () => {
151
+ pushHistory(ctx, [step(1)])
152
+ pushHistory(ctx, [step(2)])
153
+ pushHistory(ctx, [step(3)])
154
+ })
155
+ expect(new Set(ctx.history.map((s) => s.groupId)).size).toBe(1)
156
+ undoHistory(ctx)
157
+ expect(applied.map((a) => a.rowId)).toEqual(['r3', 'r2', 'r1'])
158
+ expect(ctx.historyPtr).toBe(-1)
159
+ })
160
+
161
+ it('survives the buffer overflowing mid-group', () => {
162
+ // This is why the group id is ambient rather than stamped afterwards: the
163
+ // cap drops entries from the front, so any index captured before the call
164
+ // would point at the wrong steps by the end of it.
165
+ const { ctx } = makeCtx([step(0)], 3)
166
+ runHistoryGroup(ctx, () => {
167
+ pushHistory(ctx, [step(1)])
168
+ pushHistory(ctx, [step(2)])
169
+ pushHistory(ctx, [step(3)])
170
+ })
171
+ expect(ctx.history.map((s) => s.rowId)).toEqual(['r1', 'r2', 'r3'])
172
+ expect(new Set(ctx.history.map((s) => s.groupId)).size).toBe(1)
173
+ })
174
+
175
+ it('nests into the outer group so one action is one undo', () => {
176
+ const { ctx } = makeCtx()
177
+ runHistoryGroup(ctx, () => {
178
+ pushHistory(ctx, [step(1)])
179
+ runHistoryGroup(ctx, () => pushHistory(ctx, [step(2)]))
180
+ })
181
+ expect(new Set(ctx.history.map((s) => s.groupId)).size).toBe(1)
182
+ })
183
+
184
+ it('clears the ambient id when the body throws', () => {
185
+ const { ctx } = makeCtx()
186
+ expect(() => runHistoryGroup(ctx, () => { throw new Error('boom') })).toThrow('boom')
187
+ expect(ctx.historyGroupId).toBeUndefined()
188
+ pushHistory(ctx, [step(1)])
189
+ expect(ctx.history[0]!.groupId).toBeUndefined()
190
+ })
191
+
192
+ it('returns the body result', () => {
193
+ const { ctx } = makeCtx()
194
+ expect(runHistoryGroup(ctx, () => 42)).toBe(42)
195
+ })
196
+ })
package/src/history.ts ADDED
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Undo / redo over the grid's history array, group-aware.
3
+ *
4
+ * The history is one ordered array of per-cell steps plus a pointer to the
5
+ * last applied step (the model lives in SvGrid.controller.svelte.ts). A step
6
+ * describes ONE cell, which is right for a single edit and wrong for anything
7
+ * that writes many cells at once: `applyMoveRange` in clipboard.ts pushes one
8
+ * step per moved cell, so undoing a 15x20 range move takes 300 presses of
9
+ * Ctrl+Z, and with UNDO_LIMIT at 200 the same move evicts the entire history
10
+ * behind it.
11
+ *
12
+ * `groupId` fixes that without changing the storage model. Steps written
13
+ * inside one logical action share an id; undo and redo walk the whole run of
14
+ * steps carrying that id and apply them together, so a paste, a fill-down or
15
+ * a range move is one press, the way it is in a spreadsheet. Steps with no
16
+ * groupId behave exactly as before, one step per press.
17
+ *
18
+ * Undo applies a group in REVERSE order. Two steps that touched the same cell
19
+ * inside one action have to be unwound newest-first or the older `before`
20
+ * value is overwritten by the newer one and the cell lands on the wrong value.
21
+ */
22
+
23
+ export type HistoryStep = {
24
+ rowId: string
25
+ columnId: string
26
+ field: string
27
+ before: unknown
28
+ after: unknown
29
+ /** Set when this step is part of a multi-cell action. Steps sharing an id
30
+ * are contiguous in the array and undo / redo as one. */
31
+ groupId?: string
32
+ /**
33
+ * A step that is not a cell write: something outside the grid's data that
34
+ * still belongs in the same Ctrl+Z stream, such as a cell format kept in a
35
+ * consumer's store. The cell fields are empty on such a step and the two
36
+ * functions do the work. Never serialised, since a closure cannot be.
37
+ */
38
+ custom?: { undo: () => void; redo: () => void }
39
+ }
40
+
41
+ /** Ctx surface these helpers touch. The controller satisfies it structurally. */
42
+ type HistoryCtx = {
43
+ history: HistoryStep[]
44
+ historyPtr: number
45
+ historyVersion: number
46
+ UNDO_LIMIT: number
47
+ /** Set while a `runHistoryGroup` call is on the stack. Every step pushed
48
+ * inside it joins that group, so a command does not have to thread a group
49
+ * id down through the writers it calls. */
50
+ historyGroupId: string | undefined
51
+ applyHistoryStep(step: HistoryStep, direction: 'undo' | 'redo'): void
52
+ }
53
+
54
+ /** Index of the first step in the group that ends at `end`, or `end` itself
55
+ * when that step is ungrouped. */
56
+ function groupStart(history: ReadonlyArray<HistoryStep>, end: number): number {
57
+ const id = history[end]?.groupId
58
+ if (!id) return end
59
+ let i = end
60
+ while (i > 0 && history[i - 1]?.groupId === id) i -= 1
61
+ return i
62
+ }
63
+
64
+ /** Index of the last step in the group that begins at `start`, or `start`
65
+ * itself when that step is ungrouped. */
66
+ function groupEnd(history: ReadonlyArray<HistoryStep>, start: number): number {
67
+ const id = history[start]?.groupId
68
+ if (!id) return start
69
+ let i = start
70
+ while (i < history.length - 1 && history[i + 1]?.groupId === id) i += 1
71
+ return i
72
+ }
73
+
74
+ /** Undo one action. Returns false when there is nothing left to undo. */
75
+ export function undoHistory(ctx: HistoryCtx): boolean {
76
+ if (ctx.historyPtr < 0) return false
77
+ const end = ctx.historyPtr
78
+ if (!ctx.history[end]) return false
79
+ const start = groupStart(ctx.history, end)
80
+ // Reverse order: see the note at the top of the file.
81
+ for (let i = end; i >= start; i -= 1) {
82
+ const step = ctx.history[i]
83
+ if (step) ctx.applyHistoryStep(step, 'undo')
84
+ }
85
+ ctx.historyPtr = start - 1
86
+ ctx.historyVersion += 1
87
+ return true
88
+ }
89
+
90
+ /** Redo one action. Returns false when there is nothing left to redo. */
91
+ export function redoHistory(ctx: HistoryCtx): boolean {
92
+ if (ctx.historyPtr >= ctx.history.length - 1) return false
93
+ const start = ctx.historyPtr + 1
94
+ if (!ctx.history[start]) return false
95
+ const end = groupEnd(ctx.history, start)
96
+ for (let i = start; i <= end; i += 1) {
97
+ const step = ctx.history[i]
98
+ if (step) ctx.applyHistoryStep(step, 'redo')
99
+ }
100
+ ctx.historyPtr = end
101
+ ctx.historyVersion += 1
102
+ return true
103
+ }
104
+
105
+ /**
106
+ * Append steps at the pointer, truncating any forward history (the standard
107
+ * "an edit invalidates redo" rule) and capping the buffer at UNDO_LIMIT by
108
+ * dropping the OLDEST entries.
109
+ *
110
+ * Every writer goes through here rather than open-coding the slice / push /
111
+ * cap dance, which editing.ts and clipboard.ts previously did three times
112
+ * with three slightly different shapes.
113
+ */
114
+ export function pushHistory(
115
+ ctx: HistoryCtx,
116
+ steps: ReadonlyArray<HistoryStep>,
117
+ groupId?: string,
118
+ ): void {
119
+ if (!steps.length) return
120
+ // An ambient group wins: inside runHistoryGroup even a single step has to be
121
+ // tagged, because a later push in the same action must join it.
122
+ //
123
+ // Outside one, a group of one is just a step. Tagging it would make undo do
124
+ // the same work through a slower path and would show up in serialized state.
125
+ const id = ctx.historyGroupId ?? (groupId && steps.length > 1 ? groupId : undefined)
126
+ let next = ctx.history.slice(0, ctx.historyPtr + 1)
127
+ for (const step of steps) next.push(id ? { ...step, groupId: id } : step)
128
+ if (next.length > ctx.UNDO_LIMIT) next = next.slice(next.length - ctx.UNDO_LIMIT)
129
+ ctx.history = next
130
+ ctx.historyPtr = ctx.history.length - 1
131
+ ctx.historyVersion += 1
132
+ }
133
+
134
+ let groupCounter = 0
135
+
136
+ /** A fresh group id. Monotonic, not random: ids never leave the session and a
137
+ * counter is comparable in a test snapshot. */
138
+ export function nextGroupId(): string {
139
+ groupCounter += 1
140
+ return `g${groupCounter}`
141
+ }
142
+
143
+ /**
144
+ * Run `fn` with every history step it writes tagged as one undoable action.
145
+ *
146
+ * This is ambient rather than a wrapper that re-tags afterwards, because
147
+ * `pushHistory` can drop the oldest entries when the buffer hits UNDO_LIMIT.
148
+ * A re-tagging implementation would compute its slice bounds from a length
149
+ * that had shifted underneath it and stamp the wrong steps.
150
+ *
151
+ * Nested calls join the outer group: one user action is one undo, however many
152
+ * layers of helper it goes through.
153
+ */
154
+ export function runHistoryGroup<T>(ctx: HistoryCtx, fn: () => T): T {
155
+ const outer = ctx.historyGroupId
156
+ ctx.historyGroupId = outer ?? nextGroupId()
157
+ try {
158
+ return fn()
159
+ } finally {
160
+ ctx.historyGroupId = outer
161
+ }
162
+ }
@@ -38,8 +38,9 @@ function stripAllowedRegions(src: string): string {
38
38
  describe('icon seam', () => {
39
39
  it('leaves no inline <svg> outside the built-in catalogue', () => {
40
40
  // Data visualisation is not chrome: these draw values, not affordances, and
41
- // there is nothing for a consumer's icon set to say about them.
42
- const ALLOWED = ['sv-grid-sparkline']
41
+ // there is nothing for a consumer's icon set to say about them. Nor is a
42
+ // context-menu item's own path icon: that IS the consumer's drawing.
43
+ const ALLOWED = ['sv-grid-sparkline', 'sv-grid-menu-icon-own']
43
44
 
44
45
  const offenders: string[] = []
45
46
  for (const file of CHROME) {
package/src/index.ts CHANGED
@@ -86,6 +86,7 @@ export { registerBoardView, getBoardView, hasBoardView } from './board-view.svel
86
86
  export type {
87
87
  SelectionBarAction,
88
88
  SelectionBarTarget,
89
+ ContextMenuIcon,
89
90
  ContextMenuItem,
90
91
  ContextMenuTarget,
91
92
  } from './SvGrid.types'
@@ -529,8 +530,32 @@ export { default as SvGridChartPanel } from './SvGridChartPanel.svelte'
529
530
  // unprefixed siblings SvGauge / SvSparkline. The `SvGrid*` names remain exported
530
531
  // as aliases so existing imports keep working.
531
532
  export { default as SvChart } from './SvGridChart.svelte'
533
+ export type {
534
+ SvChartProps,
535
+ ChartRenderContext,
536
+ ChartTooltipContext,
537
+ ChartTooltipRow,
538
+ ChartLegendItemContext,
539
+ ChartLegendPosition,
540
+ ChartZoomConfig,
541
+ ChartAnimateConfig,
542
+ ChartContextTarget,
543
+ ChartHoverPoint,
544
+ } from './SvGridChart.types'
545
+ export {
546
+ wheelWindow,
547
+ panWindow,
548
+ pinchWindow,
549
+ presetWindow,
550
+ nearestIndexByTime,
551
+ type ChartRangePreset,
552
+ } from './chart-zoom'
553
+ export { interpolateGeometry } from './chart-motion'
554
+ export { drillTree, pathTo } from './chart-hierarchy'
555
+ export { syncGroupState, publishSyncHover, publishSyncZoom, type ChartSyncState } from './chart-sync.svelte'
532
556
  export { default as SvChartPanel } from './SvGridChartPanel.svelte'
533
- export type { ChartingConfig, ChartAggregateRequest, ChartAggregateBucket } from './SvGrid.types'
557
+ export { default as SvChartPanes } from './SvChartPanes.svelte'
558
+ export type { ChartingConfig, ChartAggregateRequest, ChartAggregateBucket, ChartPanelInfo } from './SvGrid.types'
534
559
  export type { GridLocalization } from './SvGrid.types'
535
560
  export {
536
561
  buildChart,
@@ -538,17 +563,72 @@ export {
538
563
  rowsToChartSpec,
539
564
  rowsToBoxSpec,
540
565
  rowsToDirectSpec,
566
+ rowsToScatterSpec,
567
+ rowsToGaugeSpec,
568
+ rowsToHistogramSpec,
569
+ rowsToRangeSpec,
570
+ paretoSpec,
571
+ specToTreemap,
572
+ specToCalendar,
573
+ specToSankey,
574
+ binValues,
575
+ heikinAshi,
576
+ resampleOhlc,
577
+ rowsToOhlcSpec,
578
+ guessOhlcColumns,
579
+ splitPanelIndicators,
580
+ applyChartFormat,
581
+ CHART_RESPONSIVE_PRESETS,
582
+ type ChartFormatState,
583
+ type OhlcColumns,
584
+ type ChartPanelIndicator,
585
+ bollingerBands,
586
+ rsi,
587
+ macd,
588
+ vwap,
589
+ atr,
590
+ stochastic,
591
+ wma,
592
+ obv,
593
+ indicatorPane,
594
+ type ChartIndicatorSpec,
595
+ arcPath,
596
+ streamBaseline,
541
597
  boxStats,
542
598
  sliceChartWindow,
599
+ matchResponsiveRules,
600
+ resolveResponsive,
601
+ chartStyleVars,
543
602
  chartScales,
544
603
  ordinalDateTicks,
545
604
  niceScale,
546
605
  niceLogScale,
547
606
  linearTrend,
607
+ linearFit,
608
+ polynomialFit,
609
+ exponentialFit,
610
+ logarithmicFit,
611
+ powerFit,
612
+ rSquared,
613
+ pearson,
614
+ type RegressionFit,
548
615
  simpleMovingAverage,
549
616
  exponentialMovingAverage,
550
617
  computeOverlay,
618
+ computeOverlayFit,
619
+ regressionFit,
620
+ overlayName,
551
621
  buildLinePath,
622
+ layoutDataLabels,
623
+ markerPath,
624
+ decimateSpec,
625
+ pickCategories,
626
+ lttb,
627
+ minMaxIndices,
628
+ reduceValues,
629
+ percentile,
630
+ bucketStart,
631
+ PER_CATEGORY_SERIES_KEYS,
552
632
  sampleGradient,
553
633
  pickContrastText,
554
634
  DEFAULT_PALETTE,
@@ -566,6 +646,9 @@ export {
566
646
  type ChartLine,
567
647
  type ChartLinePoint,
568
648
  type ChartPieSlice,
649
+ type ChartResponsiveRule,
650
+ type ChartStyle,
651
+ type ChartSeriesLabel,
569
652
  type ChartSelection,
570
653
  type ChartReferenceLine,
571
654
  type ChartRefLineGeo,
@@ -587,6 +670,28 @@ export {
587
670
  type ChartSankeyLink,
588
671
  type ChartValueFormat,
589
672
  type TreeNode,
673
+ type ChartAxisConfig,
674
+ type ChartReferenceBand,
675
+ type ChartRefBandGeo,
676
+ type ChartMarker,
677
+ type ChartMarkerShape,
678
+ type ChartDataLabelConfig,
679
+ type ChartDataLabel,
680
+ type ChartDecimateConfig,
681
+ type ChartReducer,
682
+ type ChartTimeBucket,
683
+ type ChartFrame,
684
+ type ChartLineStyle,
685
+ type ChartBins,
686
+ type ChartStem,
687
+ type ChartArc,
688
+ type ChartChordRibbon,
689
+ type ChartBullet,
690
+ type ChartZoomWindow,
691
+ type ChartPointRef,
692
+ type ChartDrawing,
693
+ type ChartDrawingKind,
694
+ type ChartDrawingGeo,
590
695
  } from './chart'
591
696
  export {
592
697
  chartToSvgString,
@@ -598,6 +703,7 @@ export {
598
703
  downloadChartCsv,
599
704
  type ChartExportOptions,
600
705
  } from './chart-export'
706
+ export { chartToPdfBlob, downloadChartPdf, printChart, buildChartPdf, type ChartPdfOptions } from './chart-export-pdf'
601
707
  export {
602
708
  buildSparkline,
603
709
  toSparklineValues,
@@ -705,7 +811,33 @@ export {
705
811
  defaultGridMessages,
706
812
  resolveGridMessages,
707
813
  type GridMessages,
814
+ type GridChromeMessages,
708
815
  } from './grid-messages'
816
+ export {
817
+ defaultChartMessages,
818
+ resolveChartMessages,
819
+ chartMessage,
820
+ type ChartMessages,
821
+ } from './chart-messages'
822
+ export {
823
+ defaultChartPanelMessages,
824
+ resolveChartPanelMessages,
825
+ chartPanelMessage,
826
+ type GridChartPanelMessages,
827
+ } from './chart-panel-messages'
828
+ export {
829
+ validateChartSpec,
830
+ warnChartSpec,
831
+ nearestKey as nearestChartKey,
832
+ CHART_TYPES,
833
+ KNOWN_SPEC_KEYS,
834
+ KNOWN_SERIES_KEYS,
835
+ type ChartDiagnostic,
836
+ } from './chart-validate'
837
+ export { chartSummary, type ChartSummaryOptions } from './chart-summary'
838
+ export { appendPoints, type ChartPoint, type AppendPointsOptions } from './chart-stream'
839
+ export { pivotResultToChartSpec, pivotChartType, pivotFilterColumn, type PivotResultChartOptions, type PivotResultLike } from './chart-pivot'
840
+ export { chartSpecToTable, type ChartTable, type ChartTableColumn } from './chart-table'
709
841
  export {
710
842
  GRID_ICON_GLYPHS,
711
843
  GRID_ICON_NAMES,
@@ -723,7 +855,19 @@ export {
723
855
  type GroupPaginationOptions,
724
856
  type GroupPaginationResult,
725
857
  } from './group-display'
726
- export { getKeyboardIntent, getNextActiveCell, type GridKeyboardIntent } from './keyboard'
858
+ export { getKeyboardIntent, getNextActiveCell, getEntryStep, pastCollapsed, type GridKeyboardIntent, type EntryIntent, type EntryRect, type EntryStep, type Collapsed } from './keyboard'
859
+ export { buildMergeIndex, mergeAt, isCovered, originOf, endOf, expandRectToMerges, stepPastMerge, mergeDrawAt, type MergedCell, type MergeIndex, type MergeRect, type MergeDraw } from './merges'
860
+ // The fill handle's series (2, 4, 6; Jan, Feb; dates by the day), for a host
861
+ // that wants to show what a fill will write before it lands.
862
+ export { buildFillPattern } from './fill-patterns'
863
+ export {
864
+ registerGridShortcuts,
865
+ hasGridShortcuts,
866
+ clearGridShortcuts,
867
+ type GridShortcutHandler,
868
+ type GridCommandContext,
869
+ } from './shortcut-registry'
870
+ export type { HistoryStep } from './history'
727
871
  export { createVirtualizer } from './virtualization/virtualizer'
728
872
  export { createSvelteVirtualizer } from './virtualization/svelte-virtualizer.svelte'
729
873
  export { createColumnVirtualizer } from './virtualization/column-virtualizer'
@@ -733,6 +877,10 @@ export type {
733
877
  SvGridFilterOperator,
734
878
  SvGridViewState,
735
879
  SvGridWrapperProps,
880
+ SvGridSelectedRange,
881
+ SvGridCellCoords,
882
+ ChartTabSnapshot,
883
+ SavedChart,
736
884
  } from './svgrid-wrapper.types'
737
885
  export {
738
886
  parseEditorValue,