@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,133 @@
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
+ /** Index of the first step in the group that ends at `end`, or `end` itself
23
+ * when that step is ungrouped. */
24
+ function groupStart(history, end) {
25
+ const id = history[end]?.groupId;
26
+ if (!id)
27
+ return end;
28
+ let i = end;
29
+ while (i > 0 && history[i - 1]?.groupId === id)
30
+ i -= 1;
31
+ return i;
32
+ }
33
+ /** Index of the last step in the group that begins at `start`, or `start`
34
+ * itself when that step is ungrouped. */
35
+ function groupEnd(history, start) {
36
+ const id = history[start]?.groupId;
37
+ if (!id)
38
+ return start;
39
+ let i = start;
40
+ while (i < history.length - 1 && history[i + 1]?.groupId === id)
41
+ i += 1;
42
+ return i;
43
+ }
44
+ /** Undo one action. Returns false when there is nothing left to undo. */
45
+ export function undoHistory(ctx) {
46
+ if (ctx.historyPtr < 0)
47
+ return false;
48
+ const end = ctx.historyPtr;
49
+ if (!ctx.history[end])
50
+ return false;
51
+ const start = groupStart(ctx.history, end);
52
+ // Reverse order: see the note at the top of the file.
53
+ for (let i = end; i >= start; i -= 1) {
54
+ const step = ctx.history[i];
55
+ if (step)
56
+ ctx.applyHistoryStep(step, 'undo');
57
+ }
58
+ ctx.historyPtr = start - 1;
59
+ ctx.historyVersion += 1;
60
+ return true;
61
+ }
62
+ /** Redo one action. Returns false when there is nothing left to redo. */
63
+ export function redoHistory(ctx) {
64
+ if (ctx.historyPtr >= ctx.history.length - 1)
65
+ return false;
66
+ const start = ctx.historyPtr + 1;
67
+ if (!ctx.history[start])
68
+ return false;
69
+ const end = groupEnd(ctx.history, start);
70
+ for (let i = start; i <= end; i += 1) {
71
+ const step = ctx.history[i];
72
+ if (step)
73
+ ctx.applyHistoryStep(step, 'redo');
74
+ }
75
+ ctx.historyPtr = end;
76
+ ctx.historyVersion += 1;
77
+ return true;
78
+ }
79
+ /**
80
+ * Append steps at the pointer, truncating any forward history (the standard
81
+ * "an edit invalidates redo" rule) and capping the buffer at UNDO_LIMIT by
82
+ * dropping the OLDEST entries.
83
+ *
84
+ * Every writer goes through here rather than open-coding the slice / push /
85
+ * cap dance, which editing.ts and clipboard.ts previously did three times
86
+ * with three slightly different shapes.
87
+ */
88
+ export function pushHistory(ctx, steps, groupId) {
89
+ if (!steps.length)
90
+ return;
91
+ // An ambient group wins: inside runHistoryGroup even a single step has to be
92
+ // tagged, because a later push in the same action must join it.
93
+ //
94
+ // Outside one, a group of one is just a step. Tagging it would make undo do
95
+ // the same work through a slower path and would show up in serialized state.
96
+ const id = ctx.historyGroupId ?? (groupId && steps.length > 1 ? groupId : undefined);
97
+ let next = ctx.history.slice(0, ctx.historyPtr + 1);
98
+ for (const step of steps)
99
+ next.push(id ? { ...step, groupId: id } : step);
100
+ if (next.length > ctx.UNDO_LIMIT)
101
+ next = next.slice(next.length - ctx.UNDO_LIMIT);
102
+ ctx.history = next;
103
+ ctx.historyPtr = ctx.history.length - 1;
104
+ ctx.historyVersion += 1;
105
+ }
106
+ let groupCounter = 0;
107
+ /** A fresh group id. Monotonic, not random: ids never leave the session and a
108
+ * counter is comparable in a test snapshot. */
109
+ export function nextGroupId() {
110
+ groupCounter += 1;
111
+ return `g${groupCounter}`;
112
+ }
113
+ /**
114
+ * Run `fn` with every history step it writes tagged as one undoable action.
115
+ *
116
+ * This is ambient rather than a wrapper that re-tags afterwards, because
117
+ * `pushHistory` can drop the oldest entries when the buffer hits UNDO_LIMIT.
118
+ * A re-tagging implementation would compute its slice bounds from a length
119
+ * that had shifted underneath it and stamp the wrong steps.
120
+ *
121
+ * Nested calls join the outer group: one user action is one undo, however many
122
+ * layers of helper it goes through.
123
+ */
124
+ export function runHistoryGroup(ctx, fn) {
125
+ const outer = ctx.historyGroupId;
126
+ ctx.historyGroupId = outer ?? nextGroupId();
127
+ try {
128
+ return fn();
129
+ }
130
+ finally {
131
+ ctx.historyGroupId = outer;
132
+ }
133
+ }
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@ export { subscribeGrid, subscribeSvGrid } from './subscribe.js';
7
7
  export { default as SvGrid } from './SvGrid.svelte';
8
8
  export type { BoardConfig, BoardLane, BoardCardMoveEvent, BoardCardCommitEvent, BoardLayout, BoardSubtask, BoardComment, BoardMenuContext, BoardLaneMenuContext, BoardDrawerConfig } from './SvGrid.types.js';
9
9
  export { registerBoardView, getBoardView, hasBoardView } from './board-view.svelte.js';
10
- export type { SelectionBarAction, SelectionBarTarget, ContextMenuItem, ContextMenuTarget, } from './SvGrid.types.js';
10
+ export type { SelectionBarAction, SelectionBarTarget, ContextMenuIcon, ContextMenuItem, ContextMenuTarget, } from './SvGrid.types.js';
11
11
  export * from './ai.js';
12
12
  export { registerExportProvider, getExportProvider, type GridExportProvider, type GridExportRunOptions, } from './export-provider.js';
13
13
  export type { SchedulerConfig, SchedulerDrawerConfig, SchedulerEventMoveEvent, SchedulerEventResizeEvent, SchedulerEventCommitEvent, SchedulerRangeSelection, SchedulerView, SchedulerResource, SchedulerCollisionMode, SchedulerException, SchedulerEditScope, SchedulerOccurrenceChangeEvent, } from './SvGrid.types.js';
@@ -190,11 +190,18 @@ export { renderComponent, renderSnippet } from './render-component.js';
190
190
  export { default as SvGridChart } from './SvGridChart.svelte';
191
191
  export { default as SvGridChartPanel } from './SvGridChartPanel.svelte';
192
192
  export { default as SvChart } from './SvGridChart.svelte';
193
+ export type { SvChartProps, ChartRenderContext, ChartTooltipContext, ChartTooltipRow, ChartLegendItemContext, ChartLegendPosition, ChartZoomConfig, ChartAnimateConfig, ChartContextTarget, ChartHoverPoint, } from './SvGridChart.types.js';
194
+ export { wheelWindow, panWindow, pinchWindow, presetWindow, nearestIndexByTime, type ChartRangePreset, } from './chart-zoom.js';
195
+ export { interpolateGeometry } from './chart-motion.js';
196
+ export { drillTree, pathTo } from './chart-hierarchy.js';
197
+ export { syncGroupState, publishSyncHover, publishSyncZoom, type ChartSyncState } from './chart-sync.svelte.js';
193
198
  export { default as SvChartPanel } from './SvGridChartPanel.svelte';
194
- export type { ChartingConfig, ChartAggregateRequest, ChartAggregateBucket } from './SvGrid.types.js';
199
+ export { default as SvChartPanes } from './SvChartPanes.svelte';
200
+ export type { ChartingConfig, ChartAggregateRequest, ChartAggregateBucket, ChartPanelInfo } from './SvGrid.types.js';
195
201
  export type { GridLocalization } from './SvGrid.types.js';
196
- export { buildChart, formatChartValue, rowsToChartSpec, rowsToBoxSpec, rowsToDirectSpec, boxStats, sliceChartWindow, chartScales, ordinalDateTicks, niceScale, niceLogScale, linearTrend, simpleMovingAverage, exponentialMovingAverage, computeOverlay, buildLinePath, sampleGradient, pickContrastText, DEFAULT_PALETTE, type ChartType, type ChartSpec, type ChartSeries, type ChartGeometry, type ChartScales, type ChartBar, type ChartCandle, type ChartBox, type ChartErrorBar, type BoxStats, type OhlcBar, type ChartLine, type ChartLinePoint, type ChartPieSlice, type ChartSelection, type ChartReferenceLine, type ChartRefLineGeo, type ChartRefLineGeoV, type ChartScatterDot, type ScatterPoint, type NiceScale, type SeriesOverlay, type SeriesPattern, type ChartAnnotation, type ChartHeatmapCell, type ChartFunnelSegment, type ChartRadarSeries, type ChartRadarAxis, type ChartTreemapCell, type ChartCalendarCell, type ChartGaugeLayout, type ChartSankeyNode, type ChartSankeyLink, type ChartValueFormat, type TreeNode, } from './chart.js';
202
+ export { buildChart, formatChartValue, rowsToChartSpec, rowsToBoxSpec, rowsToDirectSpec, rowsToScatterSpec, rowsToGaugeSpec, rowsToHistogramSpec, rowsToRangeSpec, paretoSpec, specToTreemap, specToCalendar, specToSankey, binValues, heikinAshi, resampleOhlc, rowsToOhlcSpec, guessOhlcColumns, splitPanelIndicators, applyChartFormat, CHART_RESPONSIVE_PRESETS, type ChartFormatState, type OhlcColumns, type ChartPanelIndicator, bollingerBands, rsi, macd, vwap, atr, stochastic, wma, obv, indicatorPane, type ChartIndicatorSpec, arcPath, streamBaseline, boxStats, sliceChartWindow, matchResponsiveRules, resolveResponsive, chartStyleVars, chartScales, ordinalDateTicks, niceScale, niceLogScale, linearTrend, linearFit, polynomialFit, exponentialFit, logarithmicFit, powerFit, rSquared, pearson, type RegressionFit, simpleMovingAverage, exponentialMovingAverage, computeOverlay, computeOverlayFit, regressionFit, overlayName, buildLinePath, layoutDataLabels, markerPath, decimateSpec, pickCategories, lttb, minMaxIndices, reduceValues, percentile, bucketStart, PER_CATEGORY_SERIES_KEYS, sampleGradient, pickContrastText, DEFAULT_PALETTE, type ChartType, type ChartSpec, type ChartSeries, type ChartGeometry, type ChartScales, type ChartBar, type ChartCandle, type ChartBox, type ChartErrorBar, type BoxStats, type OhlcBar, type ChartLine, type ChartLinePoint, type ChartPieSlice, type ChartResponsiveRule, type ChartStyle, type ChartSeriesLabel, type ChartSelection, type ChartReferenceLine, type ChartRefLineGeo, type ChartRefLineGeoV, type ChartScatterDot, type ScatterPoint, type NiceScale, type SeriesOverlay, type SeriesPattern, type ChartAnnotation, type ChartHeatmapCell, type ChartFunnelSegment, type ChartRadarSeries, type ChartRadarAxis, type ChartTreemapCell, type ChartCalendarCell, type ChartGaugeLayout, type ChartSankeyNode, type ChartSankeyLink, type ChartValueFormat, type TreeNode, type ChartAxisConfig, type ChartReferenceBand, type ChartRefBandGeo, type ChartMarker, type ChartMarkerShape, type ChartDataLabelConfig, type ChartDataLabel, type ChartDecimateConfig, type ChartReducer, type ChartTimeBucket, type ChartFrame, type ChartLineStyle, type ChartBins, type ChartStem, type ChartArc, type ChartChordRibbon, type ChartBullet, type ChartZoomWindow, type ChartPointRef, type ChartDrawing, type ChartDrawingKind, type ChartDrawingGeo, } from './chart.js';
197
203
  export { chartToSvgString, downloadChartSvg, chartToPngBlob, downloadChartPng, chartCsvExportable, chartSpecToCsv, downloadChartCsv, type ChartExportOptions, } from './chart-export.js';
204
+ export { chartToPdfBlob, downloadChartPdf, printChart, buildChartPdf, type ChartPdfOptions } from './chart-export-pdf.js';
198
205
  export { buildSparkline, toSparklineValues, type SparklineConfig, type SparklineType, type SparklineGeometry, } from './sparkline.js';
199
206
  export { spreadsheetLayout, spansToMerges, type SpanColumn, type SpreadsheetActionOptions, type MergeSpec, type CellBorderSpec, type BorderSpec, } from './spreadsheet.js';
200
207
  export { rowResize, type RowResizeOptions } from './row-resize.js';
@@ -210,15 +217,26 @@ export { default as SvGroupCell } from './SvGroupCell.svelte';
210
217
  export { default as SvRowGroupPanel } from './SvRowGroupPanel.svelte';
211
218
  export { createNamedViews, memoryViews, localStorageViews, attachAutoSavedView, type SavedView, type ViewStorage, type NamedViews, type AutoSavedViewOptions, } from './named-views.js';
212
219
  export { resolveCellFormat, computeColumnStat, lerpColor, rgba, contrastText, formatNeedsStats, formatsNeedingStats, type ConditionalFormat, type ConditionalFormatSpec, type ColorScaleFormat, type ColorScaleStop, type ScaleBounds, type DataBarFormat, type IconSetFormat, type RuleFormat, type ResolvedCellFormat, } from './conditional-formatting.js';
213
- export { defaultGridMessages, resolveGridMessages, type GridMessages, } from './grid-messages.js';
220
+ export { defaultGridMessages, resolveGridMessages, type GridMessages, type GridChromeMessages, } from './grid-messages.js';
221
+ export { defaultChartMessages, resolveChartMessages, chartMessage, type ChartMessages, } from './chart-messages.js';
222
+ export { defaultChartPanelMessages, resolveChartPanelMessages, chartPanelMessage, type GridChartPanelMessages, } from './chart-panel-messages.js';
223
+ export { validateChartSpec, warnChartSpec, nearestKey as nearestChartKey, CHART_TYPES, KNOWN_SPEC_KEYS, KNOWN_SERIES_KEYS, type ChartDiagnostic, } from './chart-validate.js';
224
+ export { chartSummary, type ChartSummaryOptions } from './chart-summary.js';
225
+ export { appendPoints, type ChartPoint, type AppendPointsOptions } from './chart-stream.js';
226
+ export { pivotResultToChartSpec, pivotChartType, pivotFilterColumn, type PivotResultChartOptions, type PivotResultLike } from './chart-pivot.js';
227
+ export { chartSpecToTable, type ChartTable, type ChartTableColumn } from './chart-table.js';
214
228
  export { GRID_ICON_GLYPHS, GRID_ICON_NAMES, type GridIconName, type GridIcons, } from './grid-icons.js';
215
229
  export { insertGroupFooters, buildAutoGroupColumns, paginateGroupedRows, type GroupDisplayType, type GroupFooterOptions, type AutoGroupColumnSpec, type AutoGroupResult, type GroupPaginationOptions, type GroupPaginationResult, } from './group-display.js';
216
- export { getKeyboardIntent, getNextActiveCell, type GridKeyboardIntent } from './keyboard.js';
230
+ export { getKeyboardIntent, getNextActiveCell, getEntryStep, pastCollapsed, type GridKeyboardIntent, type EntryIntent, type EntryRect, type EntryStep, type Collapsed } from './keyboard.js';
231
+ export { buildMergeIndex, mergeAt, isCovered, originOf, endOf, expandRectToMerges, stepPastMerge, mergeDrawAt, type MergedCell, type MergeIndex, type MergeRect, type MergeDraw } from './merges.js';
232
+ export { buildFillPattern } from './fill-patterns.js';
233
+ export { registerGridShortcuts, hasGridShortcuts, clearGridShortcuts, type GridShortcutHandler, type GridCommandContext, } from './shortcut-registry.js';
234
+ export type { HistoryStep } from './history.js';
217
235
  export { createVirtualizer } from './virtualization/virtualizer.js';
218
236
  export { createSvelteVirtualizer } from './virtualization/svelte-virtualizer.svelte.js';
219
237
  export { createColumnVirtualizer } from './virtualization/column-virtualizer.js';
220
238
  export type { VirtualItem, VirtualizerOptions, VirtualizerState } from './virtualization/types.js';
221
- export type { SvGridApi, SvGridFilterOperator, SvGridViewState, SvGridWrapperProps, } from './svgrid-wrapper.types.js';
239
+ export type { SvGridApi, SvGridFilterOperator, SvGridViewState, SvGridWrapperProps, SvGridSelectedRange, SvGridCellCoords, ChartTabSnapshot, SavedChart, } from './svgrid-wrapper.types.js';
222
240
  export { parseEditorValue, normalizeEditorOptions, type CellEditorType, type CellEditorOption, } from './editors/cell-editors.js';
223
241
  export { applyExcelFilter, compileExcelFilter, normalizeForFilter, splitInTokens, joinInTokens, trailingInToken, type CompiledExcelFilter, type ExcelFilter, type ExcelFilterOperator, type ExcelFilterOptions, } from './filtering/excel-filters.js';
224
242
  export { ALL_FILTER_OPERATORS, SET_OPERATOR_IDS, VALUELESS_OPERATOR_IDS, RANGE_OPERATOR_IDS, isSetOperator, isValuelessOperator, isRangeOperator, type FilterValueType, } from './filtering/filter-operator-catalogue.js';
package/dist/index.js CHANGED
@@ -229,9 +229,15 @@ export { default as SvGridChartPanel } from './SvGridChartPanel.svelte';
229
229
  // unprefixed siblings SvGauge / SvSparkline. The `SvGrid*` names remain exported
230
230
  // as aliases so existing imports keep working.
231
231
  export { default as SvChart } from './SvGridChart.svelte';
232
+ export { wheelWindow, panWindow, pinchWindow, presetWindow, nearestIndexByTime, } from './chart-zoom.js';
233
+ export { interpolateGeometry } from './chart-motion.js';
234
+ export { drillTree, pathTo } from './chart-hierarchy.js';
235
+ export { syncGroupState, publishSyncHover, publishSyncZoom } from './chart-sync.svelte.js';
232
236
  export { default as SvChartPanel } from './SvGridChartPanel.svelte';
233
- export { buildChart, formatChartValue, rowsToChartSpec, rowsToBoxSpec, rowsToDirectSpec, boxStats, sliceChartWindow, chartScales, ordinalDateTicks, niceScale, niceLogScale, linearTrend, simpleMovingAverage, exponentialMovingAverage, computeOverlay, buildLinePath, sampleGradient, pickContrastText, DEFAULT_PALETTE, } from './chart.js';
237
+ export { default as SvChartPanes } from './SvChartPanes.svelte';
238
+ export { buildChart, formatChartValue, rowsToChartSpec, rowsToBoxSpec, rowsToDirectSpec, rowsToScatterSpec, rowsToGaugeSpec, rowsToHistogramSpec, rowsToRangeSpec, paretoSpec, specToTreemap, specToCalendar, specToSankey, binValues, heikinAshi, resampleOhlc, rowsToOhlcSpec, guessOhlcColumns, splitPanelIndicators, applyChartFormat, CHART_RESPONSIVE_PRESETS, bollingerBands, rsi, macd, vwap, atr, stochastic, wma, obv, indicatorPane, arcPath, streamBaseline, boxStats, sliceChartWindow, matchResponsiveRules, resolveResponsive, chartStyleVars, chartScales, ordinalDateTicks, niceScale, niceLogScale, linearTrend, linearFit, polynomialFit, exponentialFit, logarithmicFit, powerFit, rSquared, pearson, simpleMovingAverage, exponentialMovingAverage, computeOverlay, computeOverlayFit, regressionFit, overlayName, buildLinePath, layoutDataLabels, markerPath, decimateSpec, pickCategories, lttb, minMaxIndices, reduceValues, percentile, bucketStart, PER_CATEGORY_SERIES_KEYS, sampleGradient, pickContrastText, DEFAULT_PALETTE, } from './chart.js';
234
239
  export { chartToSvgString, downloadChartSvg, chartToPngBlob, downloadChartPng, chartCsvExportable, chartSpecToCsv, downloadChartCsv, } from './chart-export.js';
240
+ export { chartToPdfBlob, downloadChartPdf, printChart, buildChartPdf } from './chart-export-pdf.js';
235
241
  export { buildSparkline, toSparklineValues, } from './sparkline.js';
236
242
  export { spreadsheetLayout, spansToMerges, } from './spreadsheet.js';
237
243
  // The two resize actions, exported as a matched pair. `<SvGrid>` drives both
@@ -253,9 +259,21 @@ export { default as SvRowGroupPanel } from './SvRowGroupPanel.svelte';
253
259
  export { createNamedViews, memoryViews, localStorageViews, attachAutoSavedView, } from './named-views.js';
254
260
  export { resolveCellFormat, computeColumnStat, lerpColor, rgba, contrastText, formatNeedsStats, formatsNeedingStats, } from './conditional-formatting.js';
255
261
  export { defaultGridMessages, resolveGridMessages, } from './grid-messages.js';
262
+ export { defaultChartMessages, resolveChartMessages, chartMessage, } from './chart-messages.js';
263
+ export { defaultChartPanelMessages, resolveChartPanelMessages, chartPanelMessage, } from './chart-panel-messages.js';
264
+ export { validateChartSpec, warnChartSpec, nearestKey as nearestChartKey, CHART_TYPES, KNOWN_SPEC_KEYS, KNOWN_SERIES_KEYS, } from './chart-validate.js';
265
+ export { chartSummary } from './chart-summary.js';
266
+ export { appendPoints } from './chart-stream.js';
267
+ export { pivotResultToChartSpec, pivotChartType, pivotFilterColumn } from './chart-pivot.js';
268
+ export { chartSpecToTable } from './chart-table.js';
256
269
  export { GRID_ICON_GLYPHS, GRID_ICON_NAMES, } from './grid-icons.js';
257
270
  export { insertGroupFooters, buildAutoGroupColumns, paginateGroupedRows, } from './group-display.js';
258
- export { getKeyboardIntent, getNextActiveCell } from './keyboard.js';
271
+ export { getKeyboardIntent, getNextActiveCell, getEntryStep, pastCollapsed } from './keyboard.js';
272
+ export { buildMergeIndex, mergeAt, isCovered, originOf, endOf, expandRectToMerges, stepPastMerge, mergeDrawAt } from './merges.js';
273
+ // The fill handle's series (2, 4, 6; Jan, Feb; dates by the day), for a host
274
+ // that wants to show what a fill will write before it lands.
275
+ export { buildFillPattern } from './fill-patterns.js';
276
+ export { registerGridShortcuts, hasGridShortcuts, clearGridShortcuts, } from './shortcut-registry.js';
259
277
  export { createVirtualizer } from './virtualization/virtualizer.js';
260
278
  export { createSvelteVirtualizer } from './virtualization/svelte-virtualizer.svelte.js';
261
279
  export { createColumnVirtualizer } from './virtualization/column-virtualizer.js';
@@ -1,11 +1,27 @@
1
1
  import "./sv-grid-scrollbar.js";
2
- import { getKeyboardIntent, getNextActiveCell } from "./keyboard.js";
2
+ import { getKeyboardIntent, getNextActiveCell, getEntryStep, pastCollapsed } from "./keyboard.js";
3
+ import { stepPastMerge } from "./merges.js";
4
+ import { hasGridShortcuts, runGridShortcuts } from "./shortcut-registry.js";
5
+ import { undoHistory, redoHistory } from "./history.js";
6
+ import { buildCommandContext } from "./command-context.js";
3
7
  export function createKeyboard(ctx) {
4
8
  function onGridKeyDown(event) {
5
9
  // Only the grid root drives navigation - keys on header buttons, menus,
6
10
  // or the cell editor are handled by those controls themselves.
11
+ //
12
+ // The command chain runs AFTER this guard, not before. Running it first
13
+ // meant a key typed into a filter-row input reached the sheet commands,
14
+ // so Ctrl+A or Ctrl+D while filtering mutated grid cells. The editor has
15
+ // its own chain call in editing.ts, which is what Alt+Enter needs.
7
16
  if (event.target !== event.currentTarget)
8
17
  return;
18
+ // Registered commands get the key BEFORE the grid interprets it. That is
19
+ // how @svgrid/enterprise binds Ctrl+Arrow, Ctrl+D and the rest without
20
+ // widening the closed GridKeyboardIntent union, which is public API. A
21
+ // grid with nothing registered pays one array-length read.
22
+ if (hasGridShortcuts() && runGridShortcuts(event, buildCommandContext(ctx, false))) {
23
+ return;
24
+ }
9
25
  if (ctx.editingCell)
10
26
  return;
11
27
  if ((event.ctrlKey || event.metaKey) && !event.altKey) {
@@ -16,6 +32,14 @@ export function createKeyboard(ctx) {
16
32
  return;
17
33
  }
18
34
  if (lower === "v") {
35
+ // A consumer taking the paste over wants the browser's own `paste`
36
+ // event, which carries the clipboard's HTML unsanitised; the key is
37
+ // left alone and the async API is armed as the fallback for a
38
+ // browser that never delivers the event to a non-editable element.
39
+ if (ctx.props.onPasteClipboard) {
40
+ ctx.armPasteFallback();
41
+ return;
42
+ }
19
43
  // Secure context: read via the async Clipboard API and swallow the
20
44
  // key. Insecure context (no navigator.clipboard): DON'T preventDefault
21
45
  // so the browser delivers a native `paste` event to `onGridPaste`.
@@ -34,26 +58,12 @@ export function createKeyboard(ctx) {
34
58
  // and Ctrl+Y both redo. Mirrors VSCode / Sheets / Excel.
35
59
  if (lower === "z" && !event.shiftKey) {
36
60
  event.preventDefault();
37
- if (ctx.historyPtr >= 0) {
38
- const step = ctx.history[ctx.historyPtr];
39
- if (step) {
40
- ctx.applyHistoryStep(step, 'undo');
41
- ctx.historyPtr -= 1;
42
- ctx.historyVersion += 1;
43
- }
44
- }
61
+ undoHistory(ctx);
45
62
  return;
46
63
  }
47
64
  if ((lower === "z" && event.shiftKey) || lower === "y") {
48
65
  event.preventDefault();
49
- if (ctx.historyPtr < ctx.history.length - 1) {
50
- const step = ctx.history[ctx.historyPtr + 1];
51
- if (step) {
52
- ctx.applyHistoryStep(step, 'redo');
53
- ctx.historyPtr += 1;
54
- ctx.historyVersion += 1;
55
- }
56
- }
66
+ redoHistory(ctx);
57
67
  return;
58
68
  }
59
69
  // Ctrl+F opens the find overlay.
@@ -178,26 +188,73 @@ export function createKeyboard(ctx) {
178
188
  const usable = Math.max(0, clientHeight - headerHeight);
179
189
  return Math.max(1, Math.floor(usable / Math.max(rowHeight, 1)) - 1);
180
190
  }
181
- const next = getNextActiveCell(current, intent, {
191
+ const bounds = {
182
192
  maxRow: Math.max(ctx.allRows.length - 1, 0),
183
193
  maxCol: Math.max(ctx.allColumns.length - 1, 0),
184
- pageSize: pageStep(),
185
- });
186
- ctx.setActiveCell(next.rowIndex, next.colIndex);
187
- ctx.scrollActiveCellIntoView(next.rowIndex, next.colIndex);
188
- // Shift extends the selection ONLY for arrow keys (Excel-style).
189
- // Shift+Enter / Shift+Tab just change direction; they don't grow the
190
- // range, otherwise hammering Shift+Tab to backspace through a row
191
- // would paint a creeping rectangle behind the cursor.
192
- const shouldExtend = event.shiftKey &&
194
+ };
195
+ // Collapsed rows and columns are stepped over, as a sheet's hidden ones.
196
+ const collapsed = {
197
+ isRowCollapsed: (i) => ctx.isRowCollapsed(i),
198
+ isColumnCollapsed: (i) => !!ctx.collapsedColumns[ctx.allColumns[i]?.id],
199
+ };
200
+ // Enter and Tab are entry keys: they remember a Tab run and stay inside
201
+ // a selected block (getEntryStep). ArrowDown shares the moveDown intent
202
+ // and is a plain move, hence the key check.
203
+ if ((event.key === "Enter" || event.key === "Tab") &&
204
+ (intent === "moveDown" || intent === "moveUp" || intent === "tabNext" || intent === "tabPrev")) {
205
+ const step = getEntryStep(current, intent, {
206
+ ...bounds,
207
+ tabOrigin: ctx.tabRunOrigin,
208
+ range: ctx.activeRangeRect(),
209
+ collapsed,
210
+ });
211
+ const landed = stepPastMerge(ctx.mergeIndex, current, step.cell, bounds);
212
+ ctx.setActiveCell(landed.rowIndex, landed.colIndex);
213
+ ctx.tabRunOrigin = step.tabOrigin;
214
+ ctx.scrollActiveCellIntoView(landed.rowIndex, landed.colIndex);
215
+ if (!step.withinRange)
216
+ ctx.setSelection(landed.rowIndex, landed.colIndex);
217
+ return;
218
+ }
219
+ // Shift+Arrow grows the range from its far corner, and the active cell
220
+ // stays where it is, as in Excel: it is the cell the formula bar shows
221
+ // and the one the next keystroke edits. Shift+Home, Shift+End,
222
+ // Shift+PageUp / PageDown and Ctrl+Shift+Home / End grow it the same
223
+ // way, to where the plain key would have moved. Shift+Enter and
224
+ // Shift+Tab never come this way (they are entry keys, above), so
225
+ // hammering Shift+Tab back through a row cannot paint a creeping
226
+ // rectangle. A grid without cell selection has no range to grow and
227
+ // moves the active cell instead.
228
+ const extending = event.shiftKey &&
229
+ ctx.enableCellSelectionEffective !== false &&
193
230
  (intent === "moveLeft" ||
194
231
  intent === "moveRight" ||
195
232
  intent === "moveUp" ||
196
- intent === "moveDown");
197
- if (shouldExtend)
233
+ intent === "moveDown" ||
234
+ intent === "rowStart" ||
235
+ intent === "rowEnd" ||
236
+ intent === "pageUp" ||
237
+ intent === "pageDown" ||
238
+ intent === "gridStart" ||
239
+ intent === "gridEnd");
240
+ if (extending) {
241
+ const anchored = !!ctx.selectionRange?.anchor;
242
+ if (!anchored)
243
+ ctx.setSelection(current.rowIndex, current.colIndex);
244
+ const from = anchored ? ctx.selectionRange.focus ?? current : current;
245
+ const next = stepPastMerge(ctx.mergeIndex, from, pastCollapsed(from, getNextActiveCell(from, intent, { ...bounds, pageSize: pageStep() }), bounds, collapsed), bounds);
198
246
  ctx.extendSelection(next.rowIndex, next.colIndex);
199
- else
200
- ctx.setSelection(next.rowIndex, next.colIndex);
247
+ // Scroll along the axis of the key only, as Excel does: Shift+Right
248
+ // brings the new column into view without moving the rows, so a
249
+ // whole-column selection (its far corner on the last row) stays put.
250
+ const sideways = intent === "moveLeft" || intent === "moveRight" || intent === "rowStart" || intent === "rowEnd";
251
+ ctx.scrollActiveCellIntoView(sideways ? current.rowIndex : next.rowIndex, sideways ? next.colIndex : current.colIndex);
252
+ return;
253
+ }
254
+ const next = stepPastMerge(ctx.mergeIndex, current, pastCollapsed(current, getNextActiveCell(current, intent, { ...bounds, pageSize: pageStep() }), bounds, collapsed), bounds);
255
+ ctx.setActiveCell(next.rowIndex, next.colIndex);
256
+ ctx.scrollActiveCellIntoView(next.rowIndex, next.colIndex);
257
+ ctx.setSelection(next.rowIndex, next.colIndex);
201
258
  }
202
259
  function onWindowKeydown(event) {
203
260
  if (event.key === "Escape" && (ctx.columnMenuFor || ctx.operatorMenuFor)) {
@@ -6,3 +6,58 @@ export declare function getNextActiveCell(current: ActiveCellState, intent: Grid
6
6
  maxCol: number;
7
7
  pageSize?: number;
8
8
  }): ActiveCellState;
9
+ export type Collapsed = {
10
+ isRowCollapsed: (rowIndex: number) => boolean;
11
+ isColumnCollapsed: (colIndex: number) => boolean;
12
+ };
13
+ /**
14
+ * Where a move lands once collapsed rows and columns are stepped over.
15
+ *
16
+ * A sheet's arrow keys never rest on a hidden row or column: the cursor
17
+ * goes on in the direction of travel to the next one that shows, and when
18
+ * nothing shows in that direction it comes back to the nearest one before
19
+ * the landing, which for End on a sheet whose last columns are hidden is the
20
+ * last visible column. An axis the move did not touch is left alone, so a
21
+ * cursor sitting on a row the user has just hidden still moves sideways.
22
+ * With every cell in the direction of travel hidden the cursor stays put.
23
+ */
24
+ export declare function pastCollapsed(from: ActiveCellState, next: ActiveCellState, bounds: {
25
+ maxRow: number;
26
+ maxCol: number;
27
+ }, collapsed: Collapsed): ActiveCellState;
28
+ export type EntryIntent = 'moveDown' | 'moveUp' | 'tabNext' | 'tabPrev';
29
+ export type EntryRect = {
30
+ minRow: number;
31
+ maxRow: number;
32
+ minCol: number;
33
+ maxCol: number;
34
+ };
35
+ export type EntryStep = {
36
+ cell: ActiveCellState;
37
+ /** The move stayed inside a multi-cell selection, which is kept. */
38
+ withinRange: boolean;
39
+ /** The column a run of Tabs began in, for the Enter that ends it. */
40
+ tabOrigin: number | null;
41
+ };
42
+ /**
43
+ * Where Enter and Tab put the cursor, the way a sheet's data entry expects.
44
+ *
45
+ * Two rules on top of `getNextActiveCell`. A run of Tabs remembers the
46
+ * column it started in, and the Enter that ends the run goes down from
47
+ * THAT column, not from the last cell: type across A1, B1, C1 with Tab and
48
+ * Enter lands on A2, ready for the next record. Any other move clears the
49
+ * memory, which the callers do by resetting `tabOrigin` on every plain
50
+ * `setActiveCell`. And inside a selection of more than one cell the cursor
51
+ * stays inside it: Enter walks down a column and wraps to the top of the
52
+ * next, Tab walks along a row and wraps to the start of the next, both
53
+ * back to the first cell after the last, and the selection is left
54
+ * standing, so a block can be filled without ever reaching for the mouse.
55
+ * Arrow keys are not entry keys and never come here.
56
+ */
57
+ export declare function getEntryStep(current: ActiveCellState, intent: EntryIntent, opts: {
58
+ maxRow: number;
59
+ maxCol: number;
60
+ tabOrigin: number | null;
61
+ range: EntryRect | null;
62
+ collapsed?: Collapsed;
63
+ }): EntryStep;
package/dist/keyboard.js CHANGED
@@ -4,6 +4,18 @@ export function getKeyboardIntent(event) {
4
4
  return 'gridStart';
5
5
  if (event.ctrlKey && event.key === 'End')
6
6
  return 'gridEnd';
7
+ // Ctrl/Cmd + arrow is Excel's "jump to the edge of the data region". The
8
+ // grid does not implement that itself - it is a paid command registered
9
+ // through `registerGridShortcuts`, which runs before this function is ever
10
+ // called. If we get here the key was NOT claimed, and the right answer is to
11
+ // do nothing: moving one cell is a silently wrong response to a shortcut
12
+ // every spreadsheet user presses. Alt is excluded so a browser or OS
13
+ // combination still falls through untouched.
14
+ if ((event.ctrlKey || event.metaKey) && !event.altKey) {
15
+ if (event.key === 'ArrowLeft' || event.key === 'ArrowRight' ||
16
+ event.key === 'ArrowUp' || event.key === 'ArrowDown')
17
+ return 'noop';
18
+ }
7
19
  if (event.key === 'ArrowLeft')
8
20
  return 'moveLeft';
9
21
  if (event.key === 'ArrowRight')
@@ -89,3 +101,123 @@ export function getNextActiveCell(current, intent, bounds) {
89
101
  cellId: current.cellId,
90
102
  };
91
103
  }
104
+ /**
105
+ * Where a move lands once collapsed rows and columns are stepped over.
106
+ *
107
+ * A sheet's arrow keys never rest on a hidden row or column: the cursor
108
+ * goes on in the direction of travel to the next one that shows, and when
109
+ * nothing shows in that direction it comes back to the nearest one before
110
+ * the landing, which for End on a sheet whose last columns are hidden is the
111
+ * last visible column. An axis the move did not touch is left alone, so a
112
+ * cursor sitting on a row the user has just hidden still moves sideways.
113
+ * With every cell in the direction of travel hidden the cursor stays put.
114
+ */
115
+ export function pastCollapsed(from, next, bounds, collapsed) {
116
+ const axis = (fromIdx, nextIdx, max, isCollapsed) => {
117
+ if (nextIdx === fromIdx)
118
+ return nextIdx;
119
+ const dir = nextIdx > fromIdx ? 1 : -1;
120
+ let i = nextIdx;
121
+ while (i >= 0 && i <= max && isCollapsed(i))
122
+ i += dir;
123
+ if (i >= 0 && i <= max)
124
+ return i;
125
+ i = nextIdx - dir;
126
+ while (i !== fromIdx && isCollapsed(i))
127
+ i -= dir;
128
+ return i;
129
+ };
130
+ const rowIndex = axis(from.rowIndex, next.rowIndex, bounds.maxRow, collapsed.isRowCollapsed);
131
+ const colIndex = axis(from.colIndex, next.colIndex, bounds.maxCol, collapsed.isColumnCollapsed);
132
+ return rowIndex === next.rowIndex && colIndex === next.colIndex ? next : { rowIndex, colIndex, cellId: next.cellId };
133
+ }
134
+ /**
135
+ * Where Enter and Tab put the cursor, the way a sheet's data entry expects.
136
+ *
137
+ * Two rules on top of `getNextActiveCell`. A run of Tabs remembers the
138
+ * column it started in, and the Enter that ends the run goes down from
139
+ * THAT column, not from the last cell: type across A1, B1, C1 with Tab and
140
+ * Enter lands on A2, ready for the next record. Any other move clears the
141
+ * memory, which the callers do by resetting `tabOrigin` on every plain
142
+ * `setActiveCell`. And inside a selection of more than one cell the cursor
143
+ * stays inside it: Enter walks down a column and wraps to the top of the
144
+ * next, Tab walks along a row and wraps to the start of the next, both
145
+ * back to the first cell after the last, and the selection is left
146
+ * standing, so a block can be filled without ever reaching for the mouse.
147
+ * Arrow keys are not entry keys and never come here.
148
+ */
149
+ export function getEntryStep(current, intent, opts) {
150
+ const step = entryStep(current, intent, opts);
151
+ const collapsed = opts.collapsed;
152
+ if (!collapsed)
153
+ return step;
154
+ const hidden = (c) => collapsed.isRowCollapsed(c.rowIndex) || collapsed.isColumnCollapsed(c.colIndex);
155
+ if (!hidden(step.cell))
156
+ return step;
157
+ if (!step.withinRange)
158
+ return { ...step, cell: pastCollapsed(current, step.cell, opts, collapsed) };
159
+ // Walking a block: the same step again from the hidden cell, until one
160
+ // shows. A block with nothing showing leaves the cursor where it is.
161
+ let cell = step.cell;
162
+ for (let guard = (opts.range.maxRow - opts.range.minRow + 1) * (opts.range.maxCol - opts.range.minCol + 1); guard > 0 && hidden(cell); guard -= 1) {
163
+ cell = entryStep(cell, intent, opts).cell;
164
+ }
165
+ return { ...step, cell: hidden(cell) ? current : cell };
166
+ }
167
+ function entryStep(current, intent, opts) {
168
+ const range = opts.range;
169
+ const inRange = range !== null &&
170
+ (range.maxRow > range.minRow || range.maxCol > range.minCol) &&
171
+ current.rowIndex >= range.minRow && current.rowIndex <= range.maxRow &&
172
+ current.colIndex >= range.minCol && current.colIndex <= range.maxCol;
173
+ if (inRange) {
174
+ let { rowIndex, colIndex } = current;
175
+ if (intent === 'moveDown') {
176
+ rowIndex += 1;
177
+ if (rowIndex > range.maxRow) {
178
+ rowIndex = range.minRow;
179
+ colIndex += 1;
180
+ }
181
+ if (colIndex > range.maxCol)
182
+ colIndex = range.minCol;
183
+ }
184
+ else if (intent === 'moveUp') {
185
+ rowIndex -= 1;
186
+ if (rowIndex < range.minRow) {
187
+ rowIndex = range.maxRow;
188
+ colIndex -= 1;
189
+ }
190
+ if (colIndex < range.minCol)
191
+ colIndex = range.maxCol;
192
+ }
193
+ else if (intent === 'tabNext') {
194
+ colIndex += 1;
195
+ if (colIndex > range.maxCol) {
196
+ colIndex = range.minCol;
197
+ rowIndex += 1;
198
+ }
199
+ if (rowIndex > range.maxRow)
200
+ rowIndex = range.minRow;
201
+ }
202
+ else {
203
+ colIndex -= 1;
204
+ if (colIndex < range.minCol) {
205
+ colIndex = range.maxCol;
206
+ rowIndex -= 1;
207
+ }
208
+ if (rowIndex < range.minRow)
209
+ rowIndex = range.maxRow;
210
+ }
211
+ return { cell: { rowIndex, colIndex, cellId: current.cellId }, withinRange: true, tabOrigin: null };
212
+ }
213
+ const bounds = { maxRow: opts.maxRow, maxCol: opts.maxCol };
214
+ if (intent === 'tabNext' || intent === 'tabPrev') {
215
+ return {
216
+ cell: getNextActiveCell(current, intent, bounds),
217
+ withinRange: false,
218
+ tabOrigin: opts.tabOrigin ?? current.colIndex,
219
+ };
220
+ }
221
+ const from = opts.tabOrigin === null ? current : { ...current, colIndex: opts.tabOrigin };
222
+ return { cell: getNextActiveCell(from, intent, bounds), withinRange: false, tabOrigin: null };
223
+ }
package/dist/menus.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { type RowData, type TableFeatures } from "./index.js";
2
2
  import "./sv-grid-scrollbar.js";
3
- import type { FilterOperator } from "./SvGrid.types.js";
3
+ import type { ContextMenuIcon, FilterOperator } from "./SvGrid.types.js";
4
+ import type { GridIconName } from "./grid-icons.js";
4
5
  export declare function createMenus<TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData>(ctx: any): {
5
6
  updateFilterRow: (columnId: string, value: string) => void;
6
7
  updateFilterOperator: (columnId: string, operator: FilterOperator) => void;
@@ -37,6 +38,7 @@ export declare function createMenus<TFeatures extends TableFeatures = TableFeatu
37
38
  contextMenuItems: () => {
38
39
  key: string;
39
40
  label: string;
41
+ icon?: GridIconName | ContextMenuIcon;
40
42
  separator?: boolean;
41
43
  disabled?: boolean;
42
44
  run?: () => void;
package/dist/menus.js CHANGED
@@ -487,11 +487,16 @@ export function createMenus(ctx) {
487
487
  }
488
488
  if (entry.hidden?.(target))
489
489
  continue;
490
+ // An object on a built-in's key with no action of its own is that
491
+ // built-in with an icon (or a label) of the consumer's choosing.
492
+ const b = builtins[entry.key];
493
+ const builtinLabel = b ? ((b.msg && messages?.[b.msg]) || b.label) : entry.key;
490
494
  out.push({
491
495
  key: entry.key,
492
- label: entry.label,
496
+ label: entry.label ?? builtinLabel,
497
+ icon: entry.icon,
493
498
  disabled: entry.disabled?.(target) ?? false,
494
- run: () => entry.action(target),
499
+ run: entry.action ? () => entry.action(target) : b ? () => b.run(target) : undefined,
495
500
  });
496
501
  }
497
502
  // Drop leading/trailing/double separators.