@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
@@ -8,6 +8,7 @@ export declare function createClipboard<TFeatures extends TableFeatures = TableF
8
8
  startFillDrag: (event: PointerEvent, rowIndex: number, colIndex: number) => void;
9
9
  onFillPointerMove: (event: PointerEvent) => void;
10
10
  onFillPointerUp: () => void;
11
+ fillDownToNeighbour: () => void;
11
12
  isOnMoveGrabStrip: (cell: HTMLElement, rowIndex: number, colIndex: number, clientX: number, clientY: number) => boolean;
12
13
  startMoveDrag: (event: PointerEvent, rowIndex: number, colIndex: number) => boolean;
13
14
  onMovePointerMove: (event: PointerEvent) => void;
package/dist/clipboard.js CHANGED
@@ -6,6 +6,7 @@ import "./sv-grid-scrollbar.js";
6
6
  import { buildFillPattern } from "./fill-patterns.js";
7
7
  import { getCellKey, } from "./SvGrid.helpers.js";
8
8
  import { createDataIndexLookup, getColumnBaseValue, isGroupRow, toolPanelHeaderLabel, } from "./cell-values.js";
9
+ import { pushHistory, nextGroupId, runHistoryGroup } from "./history.js";
9
10
  export function createClipboard(ctx) {
10
11
  /**
11
12
  * `rowIndex` here is a DISPLAY index - a position in `allRows`, the space the
@@ -69,7 +70,86 @@ export function createClipboard(ctx) {
69
70
  /** Apply the pattern fill on pointerup. Each NEW row (or column) is
70
71
  * filled from a pattern derived from the matching column (or row) of
71
72
  * the source. Handles all four drag directions. */
73
+ /**
74
+ * Write one filled cell and remember it, so the drag undoes as a unit.
75
+ * `writeCellRaw` keeps no history of its own; the fill used to call it in
76
+ * a loop and push nothing, which left Ctrl+Z blind to a whole drag.
77
+ */
78
+ function writeFilled(steps, rowIndex, columnId, value) {
79
+ const row = ctx.allRows[rowIndex];
80
+ const column = ctx.findColumnById(columnId);
81
+ const field = column?.columnDef.field;
82
+ if (!row || field == null)
83
+ return;
84
+ const before = readCellRaw(rowIndex, columnId);
85
+ writeCellRaw(rowIndex, columnId, value);
86
+ if (before !== value)
87
+ steps.push({ rowId: row.id, columnId, field, before, after: value });
88
+ }
72
89
  function applyFillPattern() {
90
+ const steps = [];
91
+ runHistoryGroup(ctx, () => applyFillCells(steps));
92
+ if (steps.length)
93
+ pushHistory(ctx, steps, nextGroupId());
94
+ }
95
+ /**
96
+ * The value for the i-th filled cell: the consumer's, when its hook has
97
+ * one for the source cell the target cycles back to, else the pattern's.
98
+ * `sourceStart` is the row (or column) of the first source value and
99
+ * `targetStart` that of the first target; `step` is -1 filling upward.
100
+ */
101
+ function filledValue(sources, pattern, i, sourceStart, targetStart, step, axis, rowIndex, columnId) {
102
+ const hook = ctx.props.processCellForFill;
103
+ if (hook && sources.length) {
104
+ const k = i % sources.length;
105
+ const distance = (targetStart + step * i) - (sourceStart + step * k);
106
+ const delta = axis === "rows" ? { rows: distance, cols: 0 } : { rows: 0, cols: distance };
107
+ const decided = hook({ value: sources[k], delta, rowIndex, columnId });
108
+ if (decided !== undefined)
109
+ return decided;
110
+ }
111
+ return pattern[i];
112
+ }
113
+ /**
114
+ * Double-click on the fill handle: fill down as far as the column beside
115
+ * the selection has data, the way Excel's does. The column to the left
116
+ * decides; when it is blank below the selection (or there is none) the
117
+ * one to the right does. Nothing to follow, nothing filled.
118
+ */
119
+ function fillDownToNeighbour() {
120
+ const anchor = ctx.selectionRange.anchor ?? ctx.grid.getState().activeCell;
121
+ if (!anchor)
122
+ return;
123
+ const focus = ctx.selectionRange.focus ?? anchor;
124
+ const minRow = Math.min(anchor.rowIndex, focus.rowIndex);
125
+ const maxRow = Math.max(anchor.rowIndex, focus.rowIndex);
126
+ const minCol = Math.min(anchor.colIndex, focus.colIndex);
127
+ const maxCol = Math.max(anchor.colIndex, focus.colIndex);
128
+ const filled = (r, c) => {
129
+ const col = ctx.allColumns[c];
130
+ if (!col)
131
+ return false;
132
+ const v = readCellRaw(r, col.id);
133
+ return v != null && v !== "";
134
+ };
135
+ const extent = (c) => {
136
+ let r = maxRow;
137
+ while (r + 1 < ctx.allRows.length && filled(r + 1, c))
138
+ r += 1;
139
+ return r;
140
+ };
141
+ let target = minCol > 0 ? extent(minCol - 1) : maxRow;
142
+ if (target === maxRow)
143
+ target = extent(maxCol + 1);
144
+ if (target <= maxRow)
145
+ return;
146
+ ctx.fillDrag = {
147
+ sourceMinRow: minRow, sourceMaxRow: maxRow, sourceMinCol: minCol, sourceMaxCol: maxCol,
148
+ targetRow: target, targetCol: maxCol,
149
+ };
150
+ applyFillPattern();
151
+ }
152
+ function applyFillCells(steps) {
73
153
  const d = ctx.fillDrag;
74
154
  if (!d)
75
155
  return;
@@ -102,7 +182,7 @@ export function createClipboard(ctx) {
102
182
  for (let i = 0; i < targetRows; i += 1) {
103
183
  const targetRow = d.sourceMaxRow + 1 + i;
104
184
  if (ctx.isCellEditableAt(targetRow, c))
105
- writeCellRaw(targetRow, column.id, fills[i]);
185
+ writeFilled(steps, targetRow, column.id, filledValue(sourceColValues, fills, i, d.sourceMinRow, d.sourceMaxRow + 1, 1, "rows", targetRow, column.id));
106
186
  }
107
187
  }
108
188
  if (newMinRow < d.sourceMinRow) {
@@ -113,7 +193,7 @@ export function createClipboard(ctx) {
113
193
  for (let i = 0; i < targetRows; i += 1) {
114
194
  const targetRow = d.sourceMinRow - 1 - i;
115
195
  if (ctx.isCellEditableAt(targetRow, c))
116
- writeCellRaw(targetRow, column.id, fills[i]);
196
+ writeFilled(steps, targetRow, column.id, filledValue(reversed, fills, i, d.sourceMaxRow, d.sourceMinRow - 1, -1, "rows", targetRow, column.id));
117
197
  }
118
198
  }
119
199
  }
@@ -136,7 +216,7 @@ export function createClipboard(ctx) {
136
216
  const targetCol = d.sourceMaxCol + 1 + i;
137
217
  const col = ctx.allColumns[targetCol];
138
218
  if (col && ctx.isCellEditableAt(r, targetCol))
139
- writeCellRaw(r, col.id, fills[i]);
219
+ writeFilled(steps, r, col.id, filledValue(sourceRowValues, fills, i, d.sourceMinCol, d.sourceMaxCol + 1, 1, "cols", r, col.id));
140
220
  }
141
221
  }
142
222
  if (newMinCol < d.sourceMinCol) {
@@ -147,7 +227,7 @@ export function createClipboard(ctx) {
147
227
  const targetCol = d.sourceMinCol - 1 - i;
148
228
  const col = ctx.allColumns[targetCol];
149
229
  if (col && ctx.isCellEditableAt(r, targetCol))
150
- writeCellRaw(r, col.id, fills[i]);
230
+ writeFilled(steps, r, col.id, filledValue(reversed, fills, i, d.sourceMaxCol, d.sourceMinCol - 1, -1, "cols", r, col.id));
151
231
  }
152
232
  }
153
233
  }
@@ -572,19 +652,10 @@ export function createClipboard(ctx) {
572
652
  write(dest.minRow + r, dest.minCol + c, values[r][c]);
573
653
  }
574
654
  }
575
- // One undo entry per changed cell, appended together so a single Ctrl+Z
576
- // sequence walks the whole move back. Same shape and truncation rule as
577
- // a whole-row commit.
578
- if (steps.length) {
579
- let hist = ctx.history.slice(0, ctx.historyPtr + 1);
580
- for (const step of steps)
581
- hist.push(step);
582
- if (hist.length > ctx.UNDO_LIMIT)
583
- hist = hist.slice(hist.length - ctx.UNDO_LIMIT);
584
- ctx.history = hist;
585
- ctx.historyPtr = ctx.history.length - 1;
586
- ctx.historyVersion += 1;
587
- }
655
+ // The whole move is ONE undo entry. It used to be one step per changed
656
+ // cell, so walking a 15x20 move back took 300 presses of Ctrl+Z and blew
657
+ // past UNDO_LIMIT, evicting every step behind it.
658
+ pushHistory(ctx, steps, nextGroupId());
588
659
  // Follow the values: the range stays selected where it landed, so a
589
660
  // second drag can chain off the first the way it does in a spreadsheet.
590
661
  ctx.selectionRanges = [];
@@ -629,6 +700,41 @@ export function createClipboard(ctx) {
629
700
  }
630
701
  legacyCopyText(text);
631
702
  }
703
+ /**
704
+ * Text and HTML together. The `copy` event is the first path: a listener
705
+ * armed for this one call puts both types on the event's clipboardData
706
+ * while the legacy textarea copy runs, which works in a secure and an
707
+ * insecure context alike and keeps every attribute of the HTML. Only when
708
+ * the browser refuses that does the async `ClipboardItem` write run, and
709
+ * text alone after that.
710
+ */
711
+ function writeClipboard(text, html) {
712
+ if (typeof document !== "undefined") {
713
+ let armed = true;
714
+ const onCopy = (event) => {
715
+ if (!armed || !event.clipboardData)
716
+ return;
717
+ armed = false;
718
+ event.clipboardData.setData("text/plain", text);
719
+ event.clipboardData.setData("text/html", html);
720
+ event.preventDefault();
721
+ };
722
+ document.addEventListener("copy", onCopy, { once: true, capture: true });
723
+ const ok = legacyCopyText(text);
724
+ document.removeEventListener("copy", onCopy, { capture: true });
725
+ if (ok && !armed)
726
+ return;
727
+ }
728
+ if (navigator.clipboard?.write && typeof ClipboardItem !== "undefined") {
729
+ const item = new ClipboardItem({
730
+ "text/plain": new Blob([text], { type: "text/plain" }),
731
+ "text/html": new Blob([html], { type: "text/html" }),
732
+ });
733
+ void navigator.clipboard.write([item]).catch(() => writeClipboardText(text));
734
+ return;
735
+ }
736
+ writeClipboardText(text);
737
+ }
632
738
  function legacyCopyText(text) {
633
739
  if (typeof document === "undefined")
634
740
  return false;
@@ -705,7 +811,13 @@ export function createClipboard(ctx) {
705
811
  }
706
812
  blocks.push(lines.join("\n"));
707
813
  }
708
- writeClipboardText(blocks.join("\n\n"));
814
+ const text = blocks.join("\n\n");
815
+ const toHtml = ctx.props.clipboardHtml;
816
+ const html = toHtml?.({ rects, text });
817
+ if (html)
818
+ writeClipboard(text, html);
819
+ else
820
+ writeClipboardText(text);
709
821
  }
710
822
  /**
711
823
  * Clear every editable cell in the current selection range. Used by
@@ -725,6 +837,10 @@ export function createClipboard(ctx) {
725
837
  const next = ctx.internalData.slice();
726
838
  let mutated = false;
727
839
  const changes = [];
840
+ // One history step per cleared cell, grouped, so Delete over a block is
841
+ // one Ctrl+Z. Clearing used to record nothing at all: the values were
842
+ // gone and undo skipped straight past them to whatever came before.
843
+ const steps = [];
728
844
  const dataIndexOf = createDataIndexLookup(next);
729
845
  for (let r = startRow; r <= endRow; r += 1) {
730
846
  const row = ctx.allRows[r];
@@ -757,6 +873,7 @@ export function createClipboard(ctx) {
757
873
  const cleared = parseEditorValue(editorType, "");
758
874
  updated[field] = cleared;
759
875
  rowChanged = true;
876
+ steps.push({ rowId: row.id, columnId: column.id, field, before: oldValue, after: cleared });
760
877
  changes.push({
761
878
  rowIndex: dataIndex,
762
879
  columnId: column.id,
@@ -773,6 +890,7 @@ export function createClipboard(ctx) {
773
890
  if (mutated) {
774
891
  ctx.internalData = next;
775
892
  ctx.grid.store.setState((prev) => ({ ...prev }));
893
+ pushHistory(ctx, steps, nextGroupId());
776
894
  // Fire onCellValueChange per cleared cell - same as paste's writeCellRaw -
777
895
  // so consumers (formula engines, autosave) recompute. Clear IS a value
778
896
  // change; without this, a HyperFormula-backed grid wouldn't re-evaluate.
@@ -800,6 +918,7 @@ export function createClipboard(ctx) {
800
918
  startFillDrag,
801
919
  onFillPointerMove,
802
920
  onFillPointerUp,
921
+ fillDownToNeighbour,
803
922
  isOnMoveGrabStrip,
804
923
  startMoveDrag,
805
924
  onMovePointerMove,
@@ -24,6 +24,9 @@ export type ColumnResizeOptions = {
24
24
  getWidth: (columnId: string) => number;
25
25
  /** Called on every animation frame of the drag, and once at the end. */
26
26
  onResize: (columnId: string, width: number) => void;
27
+ /** Called once when a drag ends or an arrow key has resized, with the
28
+ * width the gesture started from: what an undo puts back. */
29
+ onResizeEnd?: (columnId: string, width: number, startWidth: number) => void;
27
30
  /** Accessible name for the handle. Defaults to the column id. */
28
31
  label?: (columnId: string) => string;
29
32
  /**
@@ -58,6 +58,7 @@ export function columnResize(node, opts) {
58
58
  // Commit the final width even if the last frame was cancelled mid-flight.
59
59
  pendingWidth = Math.max(minWidth(), drag.startWidth + (e.clientX - drag.startX));
60
60
  commit();
61
+ current.onResizeEnd?.(drag.columnId, pendingWidth, drag.startWidth);
61
62
  drag.handle.classList.remove('is-resizing');
62
63
  syncAria(drag.handle, drag.columnId);
63
64
  try {
@@ -121,8 +122,10 @@ export function columnResize(node, opts) {
121
122
  return;
122
123
  e.preventDefault();
123
124
  e.stopPropagation();
124
- const next = Math.max(minWidth(), Math.round(current.getWidth(columnId) + delta));
125
+ const start = current.getWidth(columnId);
126
+ const next = Math.max(minWidth(), Math.round(start + delta));
125
127
  current.onResize(columnId, next);
128
+ current.onResizeEnd?.(columnId, next, start);
126
129
  syncAria(t, columnId);
127
130
  }
128
131
  /** A focusable separator is a widget, so ARIA wants a current value. */
package/dist/columns.js CHANGED
@@ -151,6 +151,9 @@ export function createColumns(ctx) {
151
151
  return ctx.props.columnWidth ?? 140;
152
152
  }
153
153
  function getColumnWidth(columnId) {
154
+ // Collapsed: no room at all, whatever width it keeps for later.
155
+ if (ctx.collapsedColumns?.[columnId])
156
+ return 0;
154
157
  const fitted = ctx.fittedColumnWidths?.[columnId];
155
158
  if (fitted !== undefined && ctx.columnWidths[columnId] === undefined)
156
159
  return fitted;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Builds the `GridCommandContext` handed to registered keyboard commands.
3
+ *
4
+ * Deliberately a getter object over the controller `ctx` rather than a
5
+ * snapshot: it is constructed on every keystroke that reaches a registered
6
+ * handler, so copying row and column arrays into it would put an allocation
7
+ * proportional to the grid in the hot path. Getters make construction O(1) and
8
+ * charge only for what a handler actually reads.
9
+ *
10
+ * Everything here is display-indexed, matching `SelectionPoint` and the rest of
11
+ * the selection model, so indices follow sort, filter and pagination. That is
12
+ * why `getCellValue` / `setCellValue` exist here rather than being left to
13
+ * `api.getCellValue`: the api pair indexes the DATA array, so handing a handler
14
+ * a selection index and an api that reads a different index space would give
15
+ * the wrong cell the moment the grid is sorted or filtered. `api.refresh()` has
16
+ * no index in it, so that one is left to the api.
17
+ */
18
+ import type { GridCommandContext } from "./shortcut-registry.js";
19
+ export declare function buildCommandContext(ctx: any, editing: boolean): GridCommandContext;
@@ -0,0 +1,116 @@
1
+ import { pushHistory, runHistoryGroup } from "./history.js";
2
+ export function buildCommandContext(ctx, editing) {
3
+ function columnIdAt(colIndex) {
4
+ return ctx.allColumns[colIndex]?.id ?? null;
5
+ }
6
+ return {
7
+ get api() {
8
+ return ctx.buildApi();
9
+ },
10
+ editing,
11
+ get editor() {
12
+ const root = ctx.gridRootEl ?? null;
13
+ const el = root?.querySelector(".sv-grid-cell-editor") ?? null;
14
+ return el;
15
+ },
16
+ get activeCell() {
17
+ const active = ctx.grid.getState().activeCell;
18
+ if (!active)
19
+ return null;
20
+ return {
21
+ rowIndex: active.rowIndex,
22
+ colIndex: active.colIndex,
23
+ columnId: columnIdAt(active.colIndex),
24
+ };
25
+ },
26
+ get selectionFocus() {
27
+ const focus = ctx.selectionRange?.focus;
28
+ return focus ? { rowIndex: focus.rowIndex, colIndex: focus.colIndex } : null;
29
+ },
30
+ get rowCount() {
31
+ return ctx.allRows.length;
32
+ },
33
+ get colCount() {
34
+ return ctx.allColumns.length;
35
+ },
36
+ get ranges() {
37
+ // getSelectionRects() already normalises anchor/focus into rectangles
38
+ // and puts the active range last, which is the order a handler wants
39
+ // when it has to pick one.
40
+ return ctx.getSelectionRects().map((r) => [r.minRow, r.minCol, r.maxRow, r.maxCol]);
41
+ },
42
+ columnIdAt,
43
+ getCellValue(rowIndex, colIndex) {
44
+ const columnId = columnIdAt(colIndex);
45
+ return columnId == null ? undefined : ctx.readCellRaw(rowIndex, columnId);
46
+ },
47
+ setCellValue(rowIndex, colIndex, value) {
48
+ const columnId = columnIdAt(colIndex);
49
+ if (columnId == null)
50
+ return;
51
+ // Record the write so Ctrl+Z walks it back. `writeCellRaw` is the raw
52
+ // writer and deliberately keeps no history of its own - the fill handle
53
+ // and paste call it in loops and push their own entries (or, today, none
54
+ // at all). A command has to push here or every multi-cell command it
55
+ // runs would be silently un-undoable.
56
+ //
57
+ // Both reads happen BEFORE the write: writeCellRaw swaps a fresh row
58
+ // object into `internalData`, so afterwards the id lookup would miss.
59
+ const field = ctx.allColumns[colIndex]?.columnDef?.field;
60
+ const rowId = ctx.allRows[rowIndex]?.id;
61
+ const before = ctx.readCellRaw(rowIndex, columnId);
62
+ ctx.writeCellRaw(rowIndex, columnId, value);
63
+ // Only when something actually changed: writeCellRaw no-ops on an equal
64
+ // value, and a step whose before and after match would make Ctrl+Z look
65
+ // like it had swallowed a press.
66
+ if (field != null && rowId != null && before !== value) {
67
+ pushHistory(ctx, [{ rowId, columnId, field, before, after: value }]);
68
+ }
69
+ },
70
+ setActiveCell(rowIndex, colIndex) {
71
+ ctx.setActiveCell(rowIndex, colIndex);
72
+ },
73
+ setSelection(rowIndex, colIndex) {
74
+ ctx.setSelection(rowIndex, colIndex);
75
+ },
76
+ extendSelection(rowIndex, colIndex) {
77
+ ctx.extendSelection(rowIndex, colIndex);
78
+ },
79
+ scrollIntoView(rowIndex, colIndex) {
80
+ ctx.scrollActiveCellIntoView(rowIndex, colIndex);
81
+ },
82
+ startEditing(rowIndex, colIndex, seed) {
83
+ if (seed !== undefined) {
84
+ return ctx.startEditingWithChar(rowIndex, colIndex, seed) === true;
85
+ }
86
+ ctx.onCellDoubleClick(rowIndex, colIndex);
87
+ return true;
88
+ },
89
+ batch(fn) {
90
+ return runHistoryGroup(ctx, fn);
91
+ },
92
+ recordUndo(undo, redo) {
93
+ pushHistory(ctx, [
94
+ { rowId: "", columnId: "", field: "", before: undefined, after: undefined, custom: { undo, redo } },
95
+ ]);
96
+ },
97
+ focus() {
98
+ // preventScroll: the grid keeps its own scroll position, and a focus
99
+ // that yanked the viewport to the root's top edge would undo whatever
100
+ // scrollIntoView the command just asked for.
101
+ ctx.gridRootEl?.focus({ preventScroll: true });
102
+ },
103
+ async paste() {
104
+ await ctx.pasteFromClipboard?.();
105
+ },
106
+ copy() {
107
+ ctx.copySelectionToClipboard?.();
108
+ },
109
+ async cut() {
110
+ await ctx.cutSelectionToClipboard?.();
111
+ },
112
+ canEdit(rowIndex, colIndex) {
113
+ return ctx.isCellEditableAt?.(rowIndex, colIndex) ?? true;
114
+ },
115
+ };
116
+ }
package/dist/core.d.ts CHANGED
@@ -348,6 +348,13 @@ export type ColumnDef<TFeatures extends TableFeatures, TData extends RowData> =
348
348
  editorOptions?: EditorOptionSource | Promise<EditorOptionSource> | ((row: TData) => EditorOptionSource | Promise<EditorOptionSource>);
349
349
  /** When true, list/chips allow multiple selections. Cell value becomes an array. */
350
350
  editorMultiple?: boolean;
351
+ /**
352
+ * The text editor accepts line breaks: Alt+Enter inserts one and the
353
+ * editor grows a line per break, while Enter still commits. What a
354
+ * spreadsheet cell with wrapped text wants; `editorType: 'textarea'` is
355
+ * the other shape, where Enter itself is the line break.
356
+ */
357
+ editorMultiline?: boolean;
351
358
  /** Separator used when joining array values for the readonly cell display. Defaults to ', '. */
352
359
  editorSeparator?: string;
353
360
  format?: CellFormatConfig;
@@ -68,7 +68,7 @@ export declare function createPopoverSelect(config: PopoverSelectConfig): {
68
68
  * exposes (mirrors `focusOwnerProps(role)`); defaults to 'listbox' for
69
69
  * the classic select panels. */
70
70
  triggerProps: (haspopup?: "listbox" | "tree" | "grid") => {
71
- 'aria-haspopup': "grid" | "listbox" | "tree";
71
+ 'aria-haspopup': "grid" | "tree" | "listbox";
72
72
  'aria-expanded': boolean;
73
73
  'aria-controls': string | undefined;
74
74
  'aria-readonly': true | undefined;
@@ -77,7 +77,7 @@ export declare function createPopoverSelect(config: PopoverSelectConfig): {
77
77
  /** Attributes for the element that OWNS focus + activedescendant (the panel,
78
78
  * or a search input). Pass the ARIA role the panel should expose. */
79
79
  focusOwnerProps: (role?: "listbox" | "tree" | "grid") => {
80
- role: "grid" | "listbox" | "tree";
80
+ role: "grid" | "tree" | "listbox";
81
81
  'aria-activedescendant': string | undefined;
82
82
  onkeydown: (e: KeyboardEvent) => void;
83
83
  };
package/dist/editing.d.ts CHANGED
@@ -1,12 +1,6 @@
1
1
  import { type Column, type Row, type RowData, type TableFeatures } from "./index.js";
2
2
  import "./sv-grid-scrollbar.js";
3
- type HistoryStep = {
4
- rowId: string;
5
- columnId: string;
6
- field: string;
7
- before: unknown;
8
- after: unknown;
9
- };
3
+ import { type HistoryStep } from "./history.js";
10
4
  export declare function createEditing<TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData>(ctx: any): {
11
5
  isCellEditable: (column: Column<TData>, row?: Row<TData>) => boolean;
12
6
  isCellEditableAt: (rowIndex: number, colIndex: number) => boolean;
@@ -28,5 +22,5 @@ export declare function createEditing<TFeatures extends TableFeatures = TableFea
28
22
  onCellDoubleClick: (rowIndex: number, colIndex: number) => void;
29
23
  pasteFromClipboard: () => Promise<void>;
30
24
  onGridPaste: (event: ClipboardEvent) => void;
25
+ armPasteFallback: () => void;
31
26
  };
32
- export {};