@svgrid/grid 3.0.3 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (318) hide show
  1. package/README.md +5 -4
  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 +122 -41
  8. package/dist/SvGrid.controller.svelte.js +845 -207
  9. package/dist/SvGrid.css +178 -2
  10. package/dist/SvGrid.svelte +467 -516
  11. package/dist/SvGrid.types.d.ts +315 -12
  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 +119 -28
  29. package/dist/cdn/GridMenus-CLI93jke.js +635 -0
  30. package/dist/cdn/{GridMenus-DhdPF700.js → GridMenus-CwooL3QA.js} +240 -207
  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/server-block-cache-CGoWz-87.js +237 -0
  67. package/dist/cdn/{src-D6XvA1Ij.js → src-B6HXdfHY.js} +10448 -10049
  68. package/dist/cdn/{src-Bugf5XlI.js → src-C6Iowdvs.js} +6908 -6509
  69. package/dist/cdn/svgrid.js +27 -15
  70. package/dist/cdn/svgrid.svelte-external.js +27 -15
  71. package/dist/cdn/validate-CchXzyrX.js +76 -0
  72. package/dist/chart-axes.d.ts +53 -0
  73. package/dist/chart-axes.js +351 -0
  74. package/dist/chart-cartesian.d.ts +88 -0
  75. package/dist/chart-cartesian.js +1862 -0
  76. package/dist/chart-decimate.d.ts +51 -0
  77. package/dist/chart-decimate.js +199 -0
  78. package/dist/chart-export-pdf.d.ts +47 -0
  79. package/dist/chart-export-pdf.js +187 -0
  80. package/dist/chart-export.d.ts +2 -1
  81. package/dist/chart-export.js +18 -3
  82. package/dist/chart-financial.d.ts +91 -0
  83. package/dist/chart-financial.js +175 -0
  84. package/dist/chart-flow.d.ts +10 -0
  85. package/dist/chart-flow.js +298 -0
  86. package/dist/chart-format.d.ts +18 -0
  87. package/dist/chart-format.js +65 -0
  88. package/dist/chart-grid.d.ts +8 -0
  89. package/dist/chart-grid.js +190 -0
  90. package/dist/chart-hierarchy.d.ts +17 -0
  91. package/dist/chart-hierarchy.js +146 -0
  92. package/dist/chart-indicators.d.ts +115 -0
  93. package/dist/chart-indicators.js +389 -0
  94. package/dist/chart-messages.d.ts +95 -0
  95. package/dist/chart-messages.js +92 -0
  96. package/dist/chart-motion.d.ts +16 -0
  97. package/dist/chart-motion.js +141 -0
  98. package/dist/chart-panel-messages.d.ts +265 -0
  99. package/dist/chart-panel-messages.js +285 -0
  100. package/dist/chart-pivot.d.ts +87 -0
  101. package/dist/chart-pivot.js +156 -0
  102. package/dist/chart-polar.d.ts +25 -0
  103. package/dist/chart-polar.js +700 -0
  104. package/dist/chart-samples.d.ts +16 -0
  105. package/dist/chart-samples.js +153 -0
  106. package/dist/chart-scale.d.ts +113 -0
  107. package/dist/chart-scale.js +405 -0
  108. package/dist/chart-stats.d.ts +111 -0
  109. package/dist/chart-stats.js +419 -0
  110. package/dist/chart-stream.d.ts +45 -0
  111. package/dist/chart-stream.js +91 -0
  112. package/dist/chart-summary.d.ts +27 -0
  113. package/dist/chart-summary.js +205 -0
  114. package/dist/chart-sync.svelte.d.ts +32 -0
  115. package/dist/chart-sync.svelte.js +23 -0
  116. package/dist/chart-table.d.ts +35 -0
  117. package/dist/chart-table.js +165 -0
  118. package/dist/chart-trend.d.ts +19 -0
  119. package/dist/chart-trend.js +61 -0
  120. package/dist/chart-types.d.ts +1456 -0
  121. package/dist/chart-types.js +6 -0
  122. package/dist/chart-validate.d.ts +43 -0
  123. package/dist/chart-validate.js +272 -0
  124. package/dist/chart-zoom.d.ts +40 -0
  125. package/dist/chart-zoom.js +145 -0
  126. package/dist/chart.d.ts +186 -865
  127. package/dist/chart.js +460 -2204
  128. package/dist/clipboard.d.ts +1 -0
  129. package/dist/clipboard.js +137 -18
  130. package/dist/column-resize.d.ts +3 -0
  131. package/dist/column-resize.js +4 -1
  132. package/dist/columns.js +3 -0
  133. package/dist/command-context.d.ts +19 -0
  134. package/dist/command-context.js +116 -0
  135. package/dist/core.d.ts +7 -0
  136. package/dist/core.js +36 -13
  137. package/dist/createPopoverSelect.svelte.d.ts +2 -2
  138. package/dist/editing.d.ts +2 -8
  139. package/dist/editing.js +272 -33
  140. package/dist/fill-patterns.d.ts +0 -5
  141. package/dist/fill-patterns.js +56 -2
  142. package/dist/grid-icons.d.ts +2 -2
  143. package/dist/grid-icons.js +2 -2
  144. package/dist/grid-messages.d.ts +12 -3
  145. package/dist/grid-messages.js +7 -0
  146. package/dist/history.d.ts +83 -0
  147. package/dist/history.js +133 -0
  148. package/dist/index.d.ts +27 -10
  149. package/dist/index.js +22 -5
  150. package/dist/keyboard-handlers.js +88 -31
  151. package/dist/keyboard.d.ts +55 -0
  152. package/dist/keyboard.js +132 -0
  153. package/dist/menus.d.ts +3 -1
  154. package/dist/menus.js +7 -2
  155. package/dist/merges.d.ts +97 -0
  156. package/dist/merges.js +147 -0
  157. package/dist/row-model.d.ts +133 -0
  158. package/dist/row-model.js +16 -0
  159. package/dist/row-resize.d.ts +3 -0
  160. package/dist/row-resize.js +14 -0
  161. package/dist/scroll-sync.d.ts +1 -0
  162. package/dist/scroll-sync.js +24 -0
  163. package/dist/selection.d.ts +18 -1
  164. package/dist/selection.js +138 -18
  165. package/dist/server-block-cache.d.ts +214 -0
  166. package/dist/server-block-cache.js +531 -0
  167. package/dist/server-data-source.d.ts +240 -6
  168. package/dist/server-data-source.js +220 -20
  169. package/dist/server.d.ts +13 -0
  170. package/dist/server.js +13 -0
  171. package/dist/shortcut-registry.d.ts +145 -0
  172. package/dist/shortcut-registry.js +44 -0
  173. package/dist/svgrid-wrapper.types.d.ts +213 -22
  174. package/dist/validate.d.ts +2 -0
  175. package/dist/validate.js +8 -3
  176. package/dist/virtualization/virtualizer.js +38 -17
  177. package/package.json +11 -1
  178. package/src/GridMenus.svelte +44 -2
  179. package/src/SvChartMenu.svelte +69 -0
  180. package/src/SvChartPanes.svelte +113 -0
  181. package/src/SvChartPanes.test.ts +66 -0
  182. package/src/SvGrid.controller.svelte.ts +818 -179
  183. package/src/SvGrid.css +178 -2
  184. package/src/SvGrid.svelte +467 -516
  185. package/src/SvGrid.types.ts +293 -11
  186. package/src/SvGridCellEditor.svelte +17 -0
  187. package/src/SvGridChart.svelte +2257 -329
  188. package/src/SvGridChart.test.ts +1419 -1
  189. package/src/SvGridChart.types.ts +336 -0
  190. package/src/SvGridChartBuilder.svelte +323 -0
  191. package/src/SvGridChartPanel.svelte +221 -275
  192. package/src/SvGridChartPickers.svelte +417 -0
  193. package/src/SvGridChartView.svelte +13 -14
  194. package/src/SvModal.svelte +7 -1
  195. package/src/SvModal.test.ts +9 -0
  196. package/src/ai.test.ts +57 -2
  197. package/src/ai.ts +166 -22
  198. package/src/aria.d.ts +11 -0
  199. package/src/build-api.coverage.test.ts +184 -0
  200. package/src/build-api.ts +118 -22
  201. package/src/chart-axes.test.ts +573 -0
  202. package/src/chart-axes.ts +342 -0
  203. package/src/chart-cartesian.ts +1863 -0
  204. package/src/chart-decimate.test.ts +249 -0
  205. package/src/chart-decimate.ts +186 -0
  206. package/src/chart-export-pdf.test.ts +91 -0
  207. package/src/chart-export-pdf.ts +207 -0
  208. package/src/chart-export.test.ts +14 -0
  209. package/src/chart-export.ts +13 -3
  210. package/src/chart-financial.ts +208 -0
  211. package/src/chart-flow.ts +286 -0
  212. package/src/chart-format.ts +67 -0
  213. package/src/chart-grid.ts +179 -0
  214. package/src/chart-hierarchy.ts +135 -0
  215. package/src/chart-indicators.test.ts +443 -0
  216. package/src/chart-indicators.ts +384 -0
  217. package/src/chart-messages.test.ts +21 -0
  218. package/src/chart-messages.ts +184 -0
  219. package/src/chart-motion.test.ts +71 -0
  220. package/src/chart-motion.ts +148 -0
  221. package/src/chart-panel-messages.test.ts +29 -0
  222. package/src/chart-panel-messages.ts +550 -0
  223. package/src/chart-pivot.test.ts +54 -0
  224. package/src/chart-pivot.ts +225 -0
  225. package/src/chart-polar.ts +674 -0
  226. package/src/chart-samples.ts +106 -0
  227. package/src/chart-scale.ts +394 -0
  228. package/src/chart-series-types.test.ts +714 -0
  229. package/src/chart-stats.ts +409 -0
  230. package/src/chart-stream.test.ts +75 -0
  231. package/src/chart-stream.ts +114 -0
  232. package/src/chart-summary.test.ts +90 -0
  233. package/src/chart-summary.ts +197 -0
  234. package/src/chart-sync.svelte.ts +41 -0
  235. package/src/chart-table.test.ts +76 -0
  236. package/src/chart-table.ts +178 -0
  237. package/src/chart-trend.ts +47 -0
  238. package/src/chart-types.ts +1309 -0
  239. package/src/chart-validate.test.ts +100 -0
  240. package/src/chart-validate.ts +247 -0
  241. package/src/chart-zoom.test.ts +137 -0
  242. package/src/chart-zoom.ts +124 -0
  243. package/src/chart.coverage.test.ts +81 -0
  244. package/src/chart.test.ts +56 -3
  245. package/src/chart.ts +557 -2784
  246. package/src/clipboard.test.ts +151 -0
  247. package/src/clipboard.ts +145 -16
  248. package/src/column-resize.ts +7 -1
  249. package/src/columns.ts +2 -0
  250. package/src/command-context.test.ts +200 -0
  251. package/src/command-context.ts +137 -0
  252. package/src/core.rowmodel-cache.test.ts +51 -0
  253. package/src/core.ts +40 -12
  254. package/src/editing.test.ts +281 -3
  255. package/src/editing.ts +264 -38
  256. package/src/fill-patterns.test.ts +35 -0
  257. package/src/fill-patterns.ts +56 -2
  258. package/src/grid-icons.ts +2 -2
  259. package/src/grid-messages.ts +21 -3
  260. package/src/history.test.ts +196 -0
  261. package/src/history.ts +162 -0
  262. package/src/icon-seam.test.ts +5 -6
  263. package/src/index.ts +169 -12
  264. package/src/keyboard-handlers.coverage.test.ts +126 -6
  265. package/src/keyboard-handlers.ts +91 -30
  266. package/src/keyboard-shortcuts.seam.test.ts +200 -0
  267. package/src/keyboard.test.ts +124 -1
  268. package/src/keyboard.ts +142 -0
  269. package/src/menus.test.ts +26 -0
  270. package/src/menus.ts +10 -3
  271. package/src/merges.test.ts +112 -0
  272. package/src/merges.ts +168 -0
  273. package/src/pivot.grid.test.ts +40 -5
  274. package/src/row-model.ts +146 -0
  275. package/src/row-resize.test.ts +44 -0
  276. package/src/row-resize.ts +17 -0
  277. package/src/scroll-sync.test.ts +42 -1
  278. package/src/scroll-sync.ts +25 -0
  279. package/src/selection.multi-range.test.ts +34 -0
  280. package/src/selection.test.ts +33 -2
  281. package/src/selection.ts +143 -21
  282. package/src/server-block-cache.test.ts +645 -0
  283. package/src/server-block-cache.ts +677 -0
  284. package/src/server-data-source.infinite.test.ts +343 -0
  285. package/src/server-data-source.ts +450 -27
  286. package/src/server.ts +47 -0
  287. package/src/shortcut-registry.test.ts +97 -0
  288. package/src/shortcut-registry.ts +181 -0
  289. package/src/svgrid-wrapper.types.ts +184 -13
  290. package/src/svgrid.behavior.test.ts +135 -0
  291. package/src/svgrid.charting.test.ts +513 -3
  292. package/src/svgrid.context-menu.test.ts +31 -0
  293. package/src/svgrid.new-features.wrapper.test.ts +10 -3
  294. package/src/svgrid.row-model-prop.svelte.test.ts +352 -0
  295. package/src/svgrid.row-model-seam.svelte.test.ts +289 -0
  296. package/src/validate.test.ts +10 -0
  297. package/src/validate.ts +10 -3
  298. package/src/virtualization/virtualizer.ts +39 -17
  299. package/dist/SvGroupCell.svelte +0 -141
  300. package/dist/SvGroupCell.svelte.d.ts +0 -49
  301. package/dist/SvRowGroupPanel.svelte +0 -186
  302. package/dist/SvRowGroupPanel.svelte.d.ts +0 -25
  303. package/dist/cdn/GridMenus-CHOMnW-c.js +0 -602
  304. package/dist/cdn/SvDateTimePicker-CCbDZNZB.js +0 -816
  305. package/dist/cdn/SvGridChart-1PMSp2LZ.js +0 -1481
  306. package/dist/cdn/SvGridChart-D02avhyr.js +0 -1480
  307. package/dist/cdn/SvGridChartPanel-DJnViOv4.js +0 -834
  308. package/dist/cdn/SvGridChartPanel-vPTNerFZ.js +0 -810
  309. package/dist/cdn/SvGridChartView-C--HhdUo.js +0 -56
  310. package/dist/cdn/SvGridChartView-KyyFG-wY.js +0 -55
  311. package/dist/cdn/chart-DLg3_zTQ.js +0 -1652
  312. package/dist/cdn/validate-_CDJzgIo.js +0 -75
  313. package/dist/server-group-model.d.ts +0 -98
  314. package/dist/server-group-model.js +0 -263
  315. package/src/SvGroupCell.svelte +0 -141
  316. package/src/SvRowGroupPanel.svelte +0 -186
  317. package/src/server-group-model.test.ts +0 -294
  318. package/src/server-group-model.ts +0 -370
package/dist/editing.js CHANGED
@@ -3,9 +3,12 @@
3
3
  // ($state/$derived/$effect) stays in the controller.
4
4
  import { parseEditorValue, } from "./index.js";
5
5
  import "./sv-grid-scrollbar.js";
6
- import { getNextActiveCell } from "./keyboard.js";
6
+ import { getEntryStep } from "./keyboard.js";
7
7
  import { getCellKey, toValueArray, } from "./SvGrid.helpers.js";
8
8
  import { createDataIndexLookup, getColumnBaseValue, isGroupRow, } from "./cell-values.js";
9
+ import { pushHistory, nextGroupId, runHistoryGroup } from "./history.js";
10
+ import { hasGridShortcuts, runGridShortcuts } from "./shortcut-registry.js";
11
+ import { buildCommandContext } from "./command-context.js";
9
12
  export function createEditing(ctx) {
10
13
  /**
11
14
  * Resolve `columnDef.editable` for one row × column pair.
@@ -25,6 +28,16 @@ export function createEditing(ctx) {
25
28
  const editable = column.columnDef.editable;
26
29
  if (editable === false)
27
30
  return false;
31
+ if (row) {
32
+ // A server-side group row is a key with aggregates under the leaf
33
+ // columns, and a placeholder row has no data yet: neither takes an
34
+ // edit, whatever the column says.
35
+ const serverGroup = ctx.props.serverGroup;
36
+ if (serverGroup && row.original !== undefined && serverGroup.isGroup(row.original))
37
+ return false;
38
+ if (ctx.placeholderStateOf?.(row))
39
+ return false;
40
+ }
28
41
  if (typeof editable !== "function")
29
42
  return true;
30
43
  if (!row)
@@ -100,10 +113,27 @@ export function createEditing(ctx) {
100
113
  rowRef: row.original,
101
114
  };
102
115
  ctx.setActiveCell(rowIndex, colIndex);
103
- ctx.setSelection(rowIndex, colIndex);
116
+ selectForEdit(rowIndex, colIndex);
104
117
  return true;
105
118
  }
119
+ /**
120
+ * The selection an edit starts with. A cell already inside the selection
121
+ * leaves it standing: typing into the active cell of a selected block
122
+ * used to collapse the block to that one cell, so Enter could never walk
123
+ * on inside it. A cell outside the selection becomes the selection.
124
+ */
125
+ function selectForEdit(rowIndex, colIndex) {
126
+ if (ctx.isCellInSelectedRange(rowIndex, colIndex))
127
+ return;
128
+ ctx.setSelection(rowIndex, colIndex);
129
+ }
106
130
  function saveEditingCell() {
131
+ // One group: a consumer's onCellValueChange may record a step of its own
132
+ // (a spreadsheet gives "12%" a percent format as it lands), and that
133
+ // belongs to the same Ctrl+Z as the value.
134
+ runHistoryGroup(ctx, () => commitEditingCell());
135
+ }
136
+ function commitEditingCell() {
107
137
  if (!ctx.editingCell)
108
138
  return;
109
139
  const editing = ctx.editingCell;
@@ -154,20 +184,7 @@ export function createEditing(ctx) {
154
184
  before: oldValue,
155
185
  after: finalValue,
156
186
  };
157
- const truncated = ctx.history.slice(0, ctx.historyPtr + 1);
158
- truncated.push(step);
159
- // Cap the buffer at UNDO_LIMIT; drop the OLDEST entries so the
160
- // pointer stays valid relative to the newest steps.
161
- if (truncated.length > ctx.UNDO_LIMIT) {
162
- const drop = truncated.length - ctx.UNDO_LIMIT;
163
- ctx.history = truncated.slice(drop);
164
- ctx.historyPtr = ctx.history.length - 1;
165
- }
166
- else {
167
- ctx.history = truncated;
168
- ctx.historyPtr = ctx.history.length - 1;
169
- }
170
- ctx.historyVersion += 1;
187
+ pushHistory(ctx, [step]);
171
188
  }
172
189
  // Notify the consumer AFTER the row has been updated so any callback-
173
190
  // driven recompute (cascade totals, server save, undo stack) sees the
@@ -187,6 +204,13 @@ export function createEditing(ctx) {
187
204
  /** Apply an undo / redo step directly to the underlying row, bypassing
188
205
  * the editor pipeline so we don't accidentally re-push to the stack. */
189
206
  function applyHistoryStep(step, direction) {
207
+ if (step.custom) {
208
+ if (direction === 'undo')
209
+ step.custom.undo();
210
+ else
211
+ step.custom.redo();
212
+ return;
213
+ }
190
214
  const row = ctx.allRows.find((r) => r.id === step.rowId);
191
215
  const col = ctx.allColumns.find((c) => c.id === step.columnId);
192
216
  if (!row?.original || !col)
@@ -217,26 +241,115 @@ export function createEditing(ctx) {
217
241
  * move, Tab committed the edit and then let the browser walk focus out of the
218
242
  * grid entirely (#48). Shared by every editor type, including the textarea.
219
243
  */
220
- function commitAndMoveByTab(shiftKey) {
244
+ /**
245
+ * Commit the edit and step the cursor the way data entry expects: Tab a
246
+ * column to the right (wrapping at the row's end), Enter a row down, Shift
247
+ * reversing either. Enter used to commit and stay, so a column of numbers
248
+ * took a click or an arrow between every value; Excel and every other
249
+ * sheet move down, and the accessibility page has always said this grid
250
+ * does too. At the last row or column the cursor stays put.
251
+ */
252
+ function commitAndMove(intent) {
221
253
  const active = ctx.activeCell;
222
254
  saveEditingCell();
223
255
  ctx.gridRootEl?.focus({ preventScroll: true });
224
256
  if (!active)
225
257
  return;
226
- const next = getNextActiveCell(active, shiftKey ? "tabPrev" : "tabNext", {
258
+ // The same stepping as Enter and Tab on the grid root: a Tab run's Enter
259
+ // goes back to the run's first column, and a selected block keeps the
260
+ // cursor inside it.
261
+ const step = getEntryStep(active, intent, {
227
262
  maxRow: ctx.allRows.length - 1,
228
263
  maxCol: ctx.allColumns.length - 1,
264
+ tabOrigin: ctx.tabRunOrigin,
265
+ range: ctx.activeRangeRect(),
266
+ collapsed: {
267
+ isRowCollapsed: (i) => ctx.isRowCollapsed(i),
268
+ isColumnCollapsed: (i) => !!ctx.collapsedColumns[ctx.allColumns[i]?.id],
269
+ },
229
270
  });
271
+ const next = step.cell;
230
272
  ctx.setActiveCell(next.rowIndex, next.colIndex);
231
- ctx.setSelection(next.rowIndex, next.colIndex);
273
+ ctx.tabRunOrigin = step.tabOrigin;
274
+ if (!step.withinRange)
275
+ ctx.setSelection(next.rowIndex, next.colIndex);
232
276
  ctx.scrollActiveCellIntoView(next.rowIndex, next.colIndex);
233
277
  }
278
+ function commitAndMoveByTab(shiftKey) {
279
+ commitAndMove(shiftKey ? "tabPrev" : "tabNext");
280
+ }
281
+ /**
282
+ * Ctrl+Enter: the entry goes into every cell of the selected block and the
283
+ * cursor stays where it is, as in Excel, where it is how a block is filled
284
+ * with one value in one go. Each cell takes the same raw entry through the
285
+ * ordinary commit, so its column's parser, the history and
286
+ * `onCellValueChange` all see it, and the whole block is one undo. A cell
287
+ * that cannot be edited is left alone. With nothing but the active cell
288
+ * selected it is a commit that does not move.
289
+ */
290
+ function commitToSelection() {
291
+ const editing = ctx.editingCell;
292
+ if (!editing)
293
+ return;
294
+ const raw = editing.value;
295
+ const active = ctx.activeCell;
296
+ const range = ctx.activeRangeRect();
297
+ runHistoryGroup(ctx, () => {
298
+ commitEditingCell();
299
+ if (!active || !range)
300
+ return;
301
+ for (let r = range.minRow; r <= range.maxRow; r += 1) {
302
+ for (let c = range.minCol; c <= range.maxCol; c += 1) {
303
+ if (r === active.rowIndex && c === active.colIndex)
304
+ continue;
305
+ const row = ctx.allRows[r];
306
+ const column = ctx.allColumns[c];
307
+ if (!row || !column || isGroupRow(row) || !isCellEditable(column, row))
308
+ continue;
309
+ ctx.editingCell = {
310
+ rowId: row.id,
311
+ columnId: column.id,
312
+ editorType: (column.columnDef.editorType ?? "text"),
313
+ value: raw,
314
+ rowRef: row.original,
315
+ };
316
+ commitEditingCell();
317
+ }
318
+ }
319
+ });
320
+ ctx.gridRootEl?.focus({ preventScroll: true });
321
+ }
234
322
  function onEditorKeyDown(event) {
235
323
  event.stopPropagation();
236
- if (event.key === "Enter") {
324
+ // Registered commands get the key first here too, with `editing: true`, so
325
+ // a handler can claim a combination that only means something mid-edit
326
+ // (Alt+Enter for a newline, F4 to cycle a reference between relative and
327
+ // absolute) before the editor's own Enter / Tab / Escape handling runs.
328
+ if (hasGridShortcuts() && runGridShortcuts(event, buildCommandContext(ctx, true))) {
329
+ return;
330
+ }
331
+ if (event.key === "Enter" && event.altKey) {
332
+ // A line break, in an editor that can hold one (`editorMultiline`).
333
+ // In a single-line editor Alt+Enter is just Enter.
334
+ const el = event.currentTarget;
335
+ if (el instanceof HTMLTextAreaElement) {
336
+ event.preventDefault();
337
+ const start = el.selectionStart ?? el.value.length;
338
+ const end = el.selectionEnd ?? start;
339
+ el.value = el.value.slice(0, start) + "\n" + el.value.slice(end);
340
+ el.selectionStart = el.selectionEnd = start + 1;
341
+ updateEditingCellValue(el.value);
342
+ el.dispatchEvent(new Event("input", { bubbles: true }));
343
+ return;
344
+ }
345
+ }
346
+ if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) {
237
347
  event.preventDefault();
238
- saveEditingCell();
239
- ctx.gridRootEl?.focus({ preventScroll: true });
348
+ commitToSelection();
349
+ }
350
+ else if (event.key === "Enter") {
351
+ event.preventDefault();
352
+ commitAndMove(event.shiftKey ? "moveUp" : "moveDown");
240
353
  }
241
354
  else if (event.key === "Tab") {
242
355
  event.preventDefault();
@@ -316,7 +429,7 @@ export function createEditing(ctx) {
316
429
  rowRef: row.original,
317
430
  };
318
431
  ctx.setActiveCell(rowIndex, colIndex);
319
- ctx.setSelection(rowIndex, colIndex);
432
+ selectForEdit(rowIndex, colIndex);
320
433
  }
321
434
  async function pasteFromClipboard() {
322
435
  // Preferred path: the async Clipboard API. Requires a secure context
@@ -325,6 +438,34 @@ export function createEditing(ctx) {
325
438
  // On plain HTTP (a XAMPP/Apache LAN host) `navigator.clipboard` is
326
439
  // undefined - there the Ctrl+V keydown handler skips preventDefault and
327
440
  // lets the browser deliver a native `paste` event to `onGridPaste`.
441
+ if (ctx.props.onPasteClipboard && navigator.clipboard?.read) {
442
+ // Both types, for a handler that reads the HTML.
443
+ try {
444
+ const items = await navigator.clipboard.read();
445
+ let text = "";
446
+ let html = null;
447
+ for (const item of items) {
448
+ if (item.types.includes("text/html"))
449
+ html = await (await item.getType("text/html")).text();
450
+ if (item.types.includes("text/plain"))
451
+ text = await (await item.getType("text/plain")).text();
452
+ }
453
+ if (text || html)
454
+ applyPastedPayload({ text, html, source: "async" });
455
+ }
456
+ catch {
457
+ // No permission or nothing readable: the text-only read below may still work.
458
+ if (!navigator.clipboard.readText)
459
+ return;
460
+ try {
461
+ applyPastedPayload({ text: await navigator.clipboard.readText(), html: null, source: "async" });
462
+ }
463
+ catch {
464
+ return;
465
+ }
466
+ }
467
+ return;
468
+ }
328
469
  if (!navigator.clipboard?.readText)
329
470
  return;
330
471
  let text;
@@ -336,6 +477,27 @@ export function createEditing(ctx) {
336
477
  }
337
478
  applyPastedText(text);
338
479
  }
480
+ /**
481
+ * With `onPasteClipboard` set, Ctrl+V leaves the key to the browser so the
482
+ * `paste` event arrives with the HTML. A browser that does not deliver
483
+ * that event to the grid root (Firefox, a non-editable element) would
484
+ * paste nothing, so the async read is armed to run shortly after unless
485
+ * the event lands first and disarms it.
486
+ */
487
+ let pasteFallback = null;
488
+ function armPasteFallback() {
489
+ if (pasteFallback)
490
+ clearTimeout(pasteFallback);
491
+ pasteFallback = setTimeout(() => {
492
+ pasteFallback = null;
493
+ void pasteFromClipboard();
494
+ }, 80);
495
+ }
496
+ function disarmPasteFallback() {
497
+ if (pasteFallback)
498
+ clearTimeout(pasteFallback);
499
+ pasteFallback = null;
500
+ }
339
501
  /**
340
502
  * Native `paste` ClipboardEvent fallback (insecure-context path). A real
341
503
  * Ctrl/Cmd+V on the focused grid root fires this with `clipboardData`
@@ -351,6 +513,18 @@ export function createEditing(ctx) {
351
513
  // so the edit "wouldn't update".
352
514
  if (ctx.editingCell)
353
515
  return;
516
+ if (ctx.props.onPasteClipboard) {
517
+ // The event is the preferred path for a handler: it carries the HTML
518
+ // as the source wrote it, needs no permission, and works over HTTP.
519
+ disarmPasteFallback();
520
+ const text = event.clipboardData?.getData("text/plain") ?? "";
521
+ const html = event.clipboardData?.getData("text/html") || null;
522
+ if (!text && !html)
523
+ return;
524
+ event.preventDefault();
525
+ applyPastedPayload({ text, html, source: "event" });
526
+ return;
527
+ }
354
528
  // When the async API is available the keydown handler already pasted via
355
529
  // pasteFromClipboard(); ignore the (suppressed) native event so we don't
356
530
  // paste twice.
@@ -363,6 +537,22 @@ export function createEditing(ctx) {
363
537
  applyPastedText(text);
364
538
  }
365
539
  function applyPastedText(text) {
540
+ // One history group for the whole paste, so whatever a consumer's
541
+ // processCellFromClipboard records alongside the values (a format, via
542
+ // recordUndo) comes back with them on a single Ctrl+Z.
543
+ runHistoryGroup(ctx, () => applyPastedCells(text));
544
+ }
545
+ /** A paste with both types: the handler first, the plain text otherwise. */
546
+ function applyPastedPayload(payload) {
547
+ runHistoryGroup(ctx, () => {
548
+ const handled = ctx.props.onPasteClipboard?.(payload);
549
+ if (handled === true)
550
+ return;
551
+ if (payload.text)
552
+ applyPastedCells(payload.text);
553
+ });
554
+ }
555
+ function applyPastedCells(text) {
366
556
  const anchor = ctx.selectionRange.anchor ?? ctx.grid.getState().activeCell;
367
557
  if (!anchor)
368
558
  return;
@@ -390,6 +580,12 @@ export function createEditing(ctx) {
390
580
  : Math.max(...lines.map((l) => l.split("\t").length));
391
581
  const next = ctx.internalData.slice();
392
582
  const dataIndexOf = createDataIndexLookup(next);
583
+ // Every cell the paste changes: one grouped history entry for the lot,
584
+ // so Ctrl+Z takes the whole paste back, and one onCellValueChange each,
585
+ // so a consumer keeping its own model behind the grid (a formula engine)
586
+ // learns the values. The loop used to swap the rows in and stop, which
587
+ // left the undo stack blind to a paste and the consumer never told.
588
+ const changed = [];
393
589
  for (let i = 0; i < rowSpan; i += 1) {
394
590
  const targetRowIndex = startRow + i;
395
591
  const row = ctx.allRows[targetRowIndex];
@@ -409,23 +605,69 @@ export function createEditing(ctx) {
409
605
  const updated = {
410
606
  ...originalRow,
411
607
  };
608
+ const rowChanges = [];
609
+ const record = (column, before, after) => {
610
+ if (before === after)
611
+ return;
612
+ rowChanges.push({
613
+ row: updated, rowId: row.id, rowIndex: dataIndex, columnId: column.id,
614
+ field: column.columnDef.field, before, after,
615
+ });
616
+ };
412
617
  for (let j = 0; j < colSpan; j += 1) {
413
618
  const column = ctx.allColumns[startCol + j];
414
619
  if (!column?.columnDef.field)
415
620
  continue;
416
621
  if (!isCellEditableAt(targetRowIndex, startCol + j))
417
622
  continue;
623
+ const before = updated[column.columnDef.field];
418
624
  const editorType = (column.columnDef.editorType ??
419
625
  "text");
420
626
  const raw = fillRange ? lines[0] : sourceCells?.[j] ?? "";
421
- updated[column.columnDef.field] = parseEditorValue(editorType, raw, {
627
+ const parsedValue = parseEditorValue(editorType, raw, {
422
628
  dateOnly: column.columnDef.cellDataType === "dateString",
423
629
  });
630
+ // The inbound half of the clipboard pair. A consumer (or a feature
631
+ // pack doing paste-special) gets the raw text AND what the grid would
632
+ // have written, and can return either. `undefined` leaves the cell
633
+ // alone, which is how "paste values only" skips a formula column.
634
+ const hook = ctx.props.processCellFromClipboard;
635
+ if (hook) {
636
+ const decided = hook({
637
+ text: raw,
638
+ parsedValue,
639
+ row: originalRow,
640
+ rowIndex: targetRowIndex,
641
+ columnId: column.id,
642
+ });
643
+ if (decided === undefined)
644
+ continue;
645
+ updated[column.columnDef.field] = decided;
646
+ record(column, before, decided);
647
+ continue;
648
+ }
649
+ updated[column.columnDef.field] = parsedValue;
650
+ record(column, before, parsedValue);
424
651
  }
652
+ if (!rowChanges.length)
653
+ continue;
425
654
  next[dataIndex] = updated;
655
+ changed.push(...rowChanges);
426
656
  }
657
+ if (!changed.length)
658
+ return;
427
659
  ctx.internalData = next;
428
660
  ctx.grid.store.setState((prev) => ({ ...prev }));
661
+ pushHistory(ctx, changed.map((c) => ({
662
+ rowId: c.rowId, columnId: c.columnId, field: c.field, before: c.before, after: c.after,
663
+ })), nextGroupId());
664
+ if (ctx.props.onCellValueChange) {
665
+ for (const c of changed) {
666
+ ctx.props.onCellValueChange({
667
+ rowIndex: c.rowIndex, columnId: c.columnId, oldValue: c.before, newValue: c.after, row: c.row,
668
+ });
669
+ }
670
+ }
429
671
  }
430
672
  /** Programmatically begin editing a cell (mirrors a double-click). */
431
673
  function startEditing(rowIndex, columnId) {
@@ -531,15 +773,11 @@ export function createEditing(ctx) {
531
773
  }
532
774
  // Record the whole-row change as consecutive history steps.
533
775
  if (changed.length) {
534
- let hist = ctx.history.slice(0, ctx.historyPtr + 1);
535
- for (const c of changed) {
536
- hist.push({ rowId: fr.rowId, columnId: c.columnId, field: c.field, before: c.before, after: c.after });
537
- }
538
- if (hist.length > ctx.UNDO_LIMIT)
539
- hist = hist.slice(hist.length - ctx.UNDO_LIMIT);
540
- ctx.history = hist;
541
- ctx.historyPtr = ctx.history.length - 1;
542
- ctx.historyVersion += 1;
776
+ // One full-row commit is ONE undo, not one per changed column.
777
+ pushHistory(ctx, changed.map((c) => ({
778
+ rowId: fr.rowId, columnId: c.columnId, field: c.field,
779
+ before: c.before, after: c.after,
780
+ })), nextGroupId());
543
781
  }
544
782
  ctx.grid.store.setState((prev) => ({ ...prev }));
545
783
  if (ctx.props.onCellValueChange && changed.length) {
@@ -581,5 +819,6 @@ export function createEditing(ctx) {
581
819
  onCellDoubleClick,
582
820
  pasteFromClipboard,
583
821
  onGridPaste,
822
+ armPasteFallback,
584
823
  };
585
824
  }
@@ -22,9 +22,4 @@
22
22
  * A single source value falls through to the cycle case (repeats), which
23
23
  * matches Excel's default fill behavior.
24
24
  */
25
- /**
26
- * Compute the values to write into `targetCount` new cells given the
27
- * `sourceValues` already in the selection. See module header for the
28
- * pattern-detection rules.
29
- */
30
25
  export declare function buildFillPattern(sourceValues: ReadonlyArray<unknown>, targetCount: number): unknown[];
@@ -149,10 +149,48 @@ function arithmeticStep(nums) {
149
149
  * `sourceValues` already in the selection. See module header for the
150
150
  * pattern-detection rules.
151
151
  */
152
+ const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
153
+ /**
154
+ * The sources as day stamps when every one is a date: `Date` objects, or
155
+ * `YYYY-MM-DD` text (read as UTC so a fill never crosses a day boundary
156
+ * with the time zone). Anything else means "not a date series".
157
+ */
158
+ function datesOf(values) {
159
+ if (values.every((v) => v instanceof Date && Number.isFinite(v.getTime()))) {
160
+ return { stamps: values.map((v) => v.getTime()), iso: false };
161
+ }
162
+ if (values.every((v) => typeof v === 'string' && ISO_DATE.test(v))) {
163
+ const stamps = values.map((v) => Date.parse(`${v}T00:00:00Z`));
164
+ return stamps.every((t) => Number.isFinite(t)) ? { stamps, iso: true } : null;
165
+ }
166
+ return null;
167
+ }
152
168
  export function buildFillPattern(sourceValues, targetCount) {
153
169
  if (sourceValues.length === 0 || targetCount <= 0)
154
170
  return [];
155
- // -------- 1) Numeric arithmetic progression (2+ values) -----------
171
+ // -------- 0) Dates step by the day ---------------------------------
172
+ // Before the numeric rule, since a Date coerces to a finite number of
173
+ // milliseconds, and before the prefix rule, which read 2024-01-15 as the
174
+ // number 2024 with a suffix and filled 2025-01-15. Two dates set the step
175
+ // (a week apart fills weekly); one date steps by a day, as Excel's does.
176
+ const dates = datesOf(sourceValues);
177
+ if (dates) {
178
+ const DAY = 86400000;
179
+ const days = dates.stamps.map((t) => Math.round(t / DAY));
180
+ const step = days.length >= 2 ? arithmeticStep(days) : 1;
181
+ if (step !== null) {
182
+ const last = days[days.length - 1];
183
+ return Array.from({ length: targetCount }, (_, i) => {
184
+ const stamp = (last + step * (i + 1)) * DAY;
185
+ return dates.iso ? new Date(stamp).toISOString().slice(0, 10) : new Date(stamp);
186
+ });
187
+ }
188
+ }
189
+ // -------- 1) Numbers: the linear trend (2+ values) -----------------
190
+ // Excel's AutoFill of two or more numbers continues their least-squares
191
+ // line: 1, 2, 3 goes on 4, 5, 6, and 1, 2, 4 goes on 5.33, 6.83, ...
192
+ // rather than repeating. An even step is the same line, so the common
193
+ // case is unchanged.
156
194
  const nums = sourceValues.map((v) => Number(v));
157
195
  const allNumericLike = sourceValues.every((v) => v !== null && v !== '' && v !== undefined) &&
158
196
  nums.every((n) => Number.isFinite(n));
@@ -162,6 +200,20 @@ export function buildFillPattern(sourceValues, targetCount) {
162
200
  const last = nums[nums.length - 1];
163
201
  return Array.from({ length: targetCount }, (_, i) => last + step * (i + 1));
164
202
  }
203
+ const n = nums.length;
204
+ const meanX = (n - 1) / 2;
205
+ const meanY = nums.reduce((a, b) => a + b, 0) / n;
206
+ let sxy = 0;
207
+ let sxx = 0;
208
+ for (let i = 0; i < n; i += 1) {
209
+ sxy += (i - meanX) * (nums[i] - meanY);
210
+ sxx += (i - meanX) ** 2;
211
+ }
212
+ const slope = sxx === 0 ? 0 : sxy / sxx;
213
+ const intercept = meanY - slope * meanX;
214
+ // Rounded to the precision floating point gives 5.333333333333333, so
215
+ // 10, 20, 40 fills 50, 60 and not 50.00000000000001.
216
+ return Array.from({ length: targetCount }, (_, i) => Number((intercept + slope * (n + i)).toPrecision(15)));
165
217
  }
166
218
  // -------- 2) Known string sequence (days / months / quarters) -----
167
219
  const allStrings = sourceValues.every((v) => typeof v === 'string');
@@ -179,7 +231,9 @@ export function buildFillPattern(sourceValues, targetCount) {
179
231
  if (allStrings && sourceValues.length >= 1) {
180
232
  const stringSources = sourceValues;
181
233
  const split = findPrefixNumberSuffix(stringSources);
182
- if (split) {
234
+ // A bare number as text ('5') is a number, not 'Item 5': one of them
235
+ // copies, as a numeric 5 does, and two or more were a progression above.
236
+ if (split && (split.prefix !== '' || split.suffix !== '')) {
183
237
  const step = split.numbers.length >= 2
184
238
  ? arithmeticStep(split.numbers) ?? 1
185
239
  : 1;
@@ -35,8 +35,8 @@ export type GridIconName = 'sort' | 'sort-asc' | 'sort-desc' | 'filter' | 'menu'
35
35
  export type GridIcons = Partial<Record<GridIconName, Snippet>>;
36
36
  /**
37
37
  * Icons whose built-in form is a character rather than an SVG path. Kept as
38
- * data so `<SvGrid>`, its footer and the standalone `SvRowGroupPanel` render
39
- * the same defaults instead of three copies drifting apart.
38
+ * data so `<SvGrid>`, its footer and the row-group panel in @svgrid/enterprise
39
+ * render the same defaults instead of three copies drifting apart.
40
40
  *
41
41
  * Each is the exact character that shipped inline before, so a grid that sets
42
42
  * no `icons` renders byte-for-byte what it always did.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Icons whose built-in form is a character rather than an SVG path. Kept as
3
- * data so `<SvGrid>`, its footer and the standalone `SvRowGroupPanel` render
4
- * the same defaults instead of three copies drifting apart.
3
+ * data so `<SvGrid>`, its footer and the row-group panel in @svgrid/enterprise
4
+ * render the same defaults instead of three copies drifting apart.
5
5
  *
6
6
  * Each is the exact character that shipped inline before, so a grid that sets
7
7
  * no `icons` renders byte-for-byte what it always did.
@@ -1,5 +1,12 @@
1
+ import type { GridChartPanelMessages } from './chart-panel-messages.js';
2
+ /**
3
+ * Every string `localization.text` accepts: the grid chrome's own
4
+ * ({@link GridChromeMessages}) and the chart panel's
5
+ * ({@link GridChartPanelMessages}).
6
+ */
7
+ export type GridMessages = GridChromeMessages & GridChartPanelMessages;
1
8
  /** Every localizable chrome string, grouped by area in the comments. */
2
- export type GridMessages = {
9
+ export type GridChromeMessages = {
3
10
  noRows: string;
4
11
  loading: string;
5
12
  columns: string;
@@ -67,6 +74,8 @@ export type GridMessages = {
67
74
  bulkEditCancel: string;
68
75
  bulkEditNoFields: string;
69
76
  chartRange: string;
77
+ rowLoadFailed: string;
78
+ rowLoadRetry: string;
70
79
  pivotUpsellTitle: string;
71
80
  pivotUpsellBody: string;
72
81
  /** Shared licensing line under every Enterprise upsell note. */
@@ -74,10 +83,10 @@ export type GridMessages = {
74
83
  upsellLicenseLink: string;
75
84
  };
76
85
  /** English defaults - the literal strings the grid shipped before localization. */
77
- export declare const defaultGridMessages: GridMessages;
86
+ export declare const defaultGridMessages: GridChromeMessages;
78
87
  /**
79
88
  * Merge a consumer's `localeText` over the English defaults. Undefined/empty
80
89
  * values fall back to the default (via `resolveMessages`), so a partial map only
81
90
  * replaces the keys it sets.
82
91
  */
83
- export declare function resolveGridMessages(overrides?: Partial<GridMessages> | null): GridMessages;
92
+ export declare function resolveGridMessages(overrides?: Partial<GridMessages> | null): GridChromeMessages;
@@ -7,6 +7,11 @@
7
7
  *
8
8
  * This mirrors the editor kit's `resolveMessages` pattern (editor-contract.ts) so
9
9
  * chrome and editors share one localization idiom.
10
+ *
11
+ * The chart panel's strings are the other half of `GridMessages`
12
+ * (`GridChartPanelMessages` in chart-panel-messages.ts): same map, same
13
+ * `localization.text`, but their English defaults load with the panel, not
14
+ * with every grid.
10
15
  */
11
16
  import { resolveMessages } from './editor-contract.js';
12
17
  /** English defaults - the literal strings the grid shipped before localization. */
@@ -79,6 +84,8 @@ export const defaultGridMessages = {
79
84
  opBefore: 'Before',
80
85
  opAfter: 'After',
81
86
  chartRange: 'Chart selected range',
87
+ rowLoadFailed: 'Could not load these rows.',
88
+ rowLoadRetry: 'Retry',
82
89
  pivotUpsellTitle: 'Pivot mode',
83
90
  pivotUpsellBody: 'Pivot mode is an Enterprise feature. Install @svgrid/enterprise and call enablePivot() to use it.',
84
91
  upsellLicense: 'A license key is required for production use. Without one the feature still works, but the grid renders an unlicensed watermark.',