@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/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.
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Merged cells: a rectangle of cells drawn as one, the way a spreadsheet's
3
+ * Merge & Center does. The grid takes them as `mergedCells`, a list of
4
+ * origins with their spans in display indices, and this module is the
5
+ * pure part: an index over them, the questions the renderer, the
6
+ * selection and the keyboard ask, and nothing that touches the DOM.
7
+ *
8
+ * A merge is drawn by its origin td, which takes `rowspan` and `colspan`;
9
+ * the cells it covers are not emitted. Selection rectangles grow to whole
10
+ * merges (a merge is selected as one), the active cell inside a merge is
11
+ * its origin, and an arrow key moving into a merge lands on the origin
12
+ * and moving out of one steps past its far edge.
13
+ */
14
+ export type MergedCell = {
15
+ /** Top-left cell of the merge, display indices. */
16
+ rowIndex: number;
17
+ colIndex: number;
18
+ /** How many rows and columns it covers, 1 or more. */
19
+ rowSpan: number;
20
+ colSpan: number;
21
+ };
22
+ export type MergeIndex = {
23
+ readonly list: ReadonlyArray<MergedCell>;
24
+ /** Every cell a merge covers (origin included) -> its merge. */
25
+ readonly byCell: ReadonlyMap<string, MergedCell>;
26
+ };
27
+ export type MergeRect = {
28
+ minRow: number;
29
+ maxRow: number;
30
+ minCol: number;
31
+ maxCol: number;
32
+ };
33
+ /** An index over the merges, or null when there are none (the common case). */
34
+ export declare function buildMergeIndex(merges: ReadonlyArray<MergedCell> | undefined | null): MergeIndex | null;
35
+ /** The merge covering (r, c), origin included, or undefined. */
36
+ export declare function mergeAt(index: MergeIndex | null, r: number, c: number): MergedCell | undefined;
37
+ /** Whether (r, c) is inside a merge without being its origin. */
38
+ export declare function isCovered(index: MergeIndex | null, r: number, c: number): boolean;
39
+ /** The origin of the merge (r, c) sits in, or (r, c) itself. */
40
+ export declare function originOf(index: MergeIndex | null, r: number, c: number): {
41
+ rowIndex: number;
42
+ colIndex: number;
43
+ };
44
+ /** The far corner of the merge (r, c) sits in, or (r, c) itself. */
45
+ export declare function endOf(index: MergeIndex | null, r: number, c: number): {
46
+ rowIndex: number;
47
+ colIndex: number;
48
+ };
49
+ /**
50
+ * The rectangle grown to hold every merge it touches, to a fixed point: a
51
+ * merge that pokes out of the rectangle pulls the edge out, which can
52
+ * reach another merge, and so on, as a spreadsheet's selection does.
53
+ */
54
+ export declare function expandRectToMerges(index: MergeIndex | null, rect: MergeRect): MergeRect;
55
+ /**
56
+ * Where a move from `from` to `next` really lands. A step into a merge
57
+ * lands on its origin; a step that stays inside the merge the move started
58
+ * in carries on past its far edge (right from a three-wide merge reaches
59
+ * the column after it), clamped to the bounds; a step that then lands in
60
+ * another merge takes that one's origin.
61
+ */
62
+ export declare function stepPastMerge(index: MergeIndex | null, from: {
63
+ rowIndex: number;
64
+ colIndex: number;
65
+ }, next: {
66
+ rowIndex: number;
67
+ colIndex: number;
68
+ }, bounds: {
69
+ maxRow: number;
70
+ maxCol: number;
71
+ }): {
72
+ rowIndex: number;
73
+ colIndex: number;
74
+ };
75
+ /**
76
+ * How a td at (r, c) draws inside a rendered window of rows and columns.
77
+ * A merge is drawn once per window, by the top-left cell of the part of it
78
+ * the window holds: the origin when the origin is in the window (`origin`),
79
+ * else the first covered cell the window shows (`continuation`, which
80
+ * draws the origin's content itself), with the spans clamped to the window
81
+ * so a table never spans into rows it has not drawn. Every other cell of
82
+ * the merge is `skip`: no td, the drawn one covers its slot.
83
+ */
84
+ export type MergeDraw = {
85
+ kind: 'skip';
86
+ } | {
87
+ kind: 'origin' | 'continuation';
88
+ merge: MergedCell;
89
+ rowSpan: number;
90
+ colSpan: number;
91
+ };
92
+ export declare function mergeDrawAt(index: MergeIndex | null, r: number, c: number, window: {
93
+ firstRow: number;
94
+ lastRow: number;
95
+ firstCol: number;
96
+ lastCol: number;
97
+ }): MergeDraw | null;
package/dist/merges.js ADDED
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Merged cells: a rectangle of cells drawn as one, the way a spreadsheet's
3
+ * Merge & Center does. The grid takes them as `mergedCells`, a list of
4
+ * origins with their spans in display indices, and this module is the
5
+ * pure part: an index over them, the questions the renderer, the
6
+ * selection and the keyboard ask, and nothing that touches the DOM.
7
+ *
8
+ * A merge is drawn by its origin td, which takes `rowspan` and `colspan`;
9
+ * the cells it covers are not emitted. Selection rectangles grow to whole
10
+ * merges (a merge is selected as one), the active cell inside a merge is
11
+ * its origin, and an arrow key moving into a merge lands on the origin
12
+ * and moving out of one steps past its far edge.
13
+ */
14
+ const key = (r, c) => `${r}:${c}`;
15
+ /** An index over the merges, or null when there are none (the common case). */
16
+ export function buildMergeIndex(merges) {
17
+ if (!merges || merges.length === 0)
18
+ return null;
19
+ const byCell = new Map();
20
+ const list = [];
21
+ for (const raw of merges) {
22
+ const rowSpan = Math.max(1, Math.floor(raw.rowSpan));
23
+ const colSpan = Math.max(1, Math.floor(raw.colSpan));
24
+ if (rowSpan === 1 && colSpan === 1)
25
+ continue;
26
+ const merge = { rowIndex: raw.rowIndex, colIndex: raw.colIndex, rowSpan, colSpan };
27
+ list.push(merge);
28
+ for (let r = merge.rowIndex; r < merge.rowIndex + rowSpan; r += 1) {
29
+ for (let c = merge.colIndex; c < merge.colIndex + colSpan; c += 1)
30
+ byCell.set(key(r, c), merge);
31
+ }
32
+ }
33
+ return list.length ? { list, byCell } : null;
34
+ }
35
+ /** The merge covering (r, c), origin included, or undefined. */
36
+ export function mergeAt(index, r, c) {
37
+ return index?.byCell.get(key(r, c));
38
+ }
39
+ /** Whether (r, c) is inside a merge without being its origin. */
40
+ export function isCovered(index, r, c) {
41
+ const m = mergeAt(index, r, c);
42
+ return !!m && (m.rowIndex !== r || m.colIndex !== c);
43
+ }
44
+ /** The origin of the merge (r, c) sits in, or (r, c) itself. */
45
+ export function originOf(index, r, c) {
46
+ const m = mergeAt(index, r, c);
47
+ return m ? { rowIndex: m.rowIndex, colIndex: m.colIndex } : { rowIndex: r, colIndex: c };
48
+ }
49
+ /** The far corner of the merge (r, c) sits in, or (r, c) itself. */
50
+ export function endOf(index, r, c) {
51
+ const m = mergeAt(index, r, c);
52
+ return m
53
+ ? { rowIndex: m.rowIndex + m.rowSpan - 1, colIndex: m.colIndex + m.colSpan - 1 }
54
+ : { rowIndex: r, colIndex: c };
55
+ }
56
+ /**
57
+ * The rectangle grown to hold every merge it touches, to a fixed point: a
58
+ * merge that pokes out of the rectangle pulls the edge out, which can
59
+ * reach another merge, and so on, as a spreadsheet's selection does.
60
+ */
61
+ export function expandRectToMerges(index, rect) {
62
+ if (!index)
63
+ return rect;
64
+ let { minRow, maxRow, minCol, maxCol } = rect;
65
+ for (let guard = 0; guard < 64; guard += 1) {
66
+ let grew = false;
67
+ for (const m of index.list) {
68
+ const r2 = m.rowIndex + m.rowSpan - 1;
69
+ const c2 = m.colIndex + m.colSpan - 1;
70
+ const touches = m.rowIndex <= maxRow && r2 >= minRow && m.colIndex <= maxCol && c2 >= minCol;
71
+ if (!touches)
72
+ continue;
73
+ if (m.rowIndex < minRow) {
74
+ minRow = m.rowIndex;
75
+ grew = true;
76
+ }
77
+ if (r2 > maxRow) {
78
+ maxRow = r2;
79
+ grew = true;
80
+ }
81
+ if (m.colIndex < minCol) {
82
+ minCol = m.colIndex;
83
+ grew = true;
84
+ }
85
+ if (c2 > maxCol) {
86
+ maxCol = c2;
87
+ grew = true;
88
+ }
89
+ }
90
+ if (!grew)
91
+ break;
92
+ }
93
+ return minRow === rect.minRow && maxRow === rect.maxRow && minCol === rect.minCol && maxCol === rect.maxCol
94
+ ? rect
95
+ : { minRow, maxRow, minCol, maxCol };
96
+ }
97
+ /**
98
+ * Where a move from `from` to `next` really lands. A step into a merge
99
+ * lands on its origin; a step that stays inside the merge the move started
100
+ * in carries on past its far edge (right from a three-wide merge reaches
101
+ * the column after it), clamped to the bounds; a step that then lands in
102
+ * another merge takes that one's origin.
103
+ */
104
+ export function stepPastMerge(index, from, next, bounds) {
105
+ if (!index)
106
+ return next;
107
+ const target = mergeAt(index, next.rowIndex, next.colIndex);
108
+ if (!target)
109
+ return next;
110
+ const source = mergeAt(index, from.rowIndex, from.colIndex);
111
+ if (source !== target)
112
+ return { rowIndex: target.rowIndex, colIndex: target.colIndex };
113
+ // Still inside the merge the move began in: keep going in that direction.
114
+ const dr = Math.sign(next.rowIndex - from.rowIndex);
115
+ const dc = Math.sign(next.colIndex - from.colIndex);
116
+ if (dr === 0 && dc === 0)
117
+ return { rowIndex: target.rowIndex, colIndex: target.colIndex };
118
+ let rowIndex = next.rowIndex;
119
+ let colIndex = next.colIndex;
120
+ if (dr > 0)
121
+ rowIndex = target.rowIndex + target.rowSpan;
122
+ else if (dr < 0)
123
+ rowIndex = target.rowIndex - 1;
124
+ if (dc > 0)
125
+ colIndex = target.colIndex + target.colSpan;
126
+ else if (dc < 0)
127
+ colIndex = target.colIndex - 1;
128
+ if (rowIndex < 0 || rowIndex > bounds.maxRow || colIndex < 0 || colIndex > bounds.maxCol) {
129
+ // Nowhere further to go: the merge itself stays current.
130
+ return { rowIndex: target.rowIndex, colIndex: target.colIndex };
131
+ }
132
+ const landed = mergeAt(index, rowIndex, colIndex);
133
+ return landed ? { rowIndex: landed.rowIndex, colIndex: landed.colIndex } : { rowIndex, colIndex };
134
+ }
135
+ export function mergeDrawAt(index, r, c, window) {
136
+ const m = mergeAt(index, r, c);
137
+ if (!m)
138
+ return null;
139
+ const anchorRow = Math.max(m.rowIndex, window.firstRow);
140
+ const anchorCol = Math.max(m.colIndex, window.firstCol);
141
+ if (r !== anchorRow || c !== anchorCol)
142
+ return { kind: 'skip' };
143
+ const isOrigin = anchorRow === m.rowIndex && anchorCol === m.colIndex;
144
+ const rowSpan = Math.max(1, Math.min(m.rowIndex + m.rowSpan - r, window.lastRow - r + 1));
145
+ const colSpan = Math.max(1, Math.min(m.colIndex + m.colSpan - c, window.lastCol - c + 1));
146
+ return { kind: isOrigin ? 'origin' : 'continuation', merge: m, rowSpan, colSpan };
147
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * `GridRowModel` - one prop instead of eleven.
3
+ *
4
+ * A grid backed by a server needs the same wiring every time: hand it the
5
+ * rows, tell it not to sort or filter locally, forward the sort and the filter
6
+ * to the backend, report which rows are on screen, say which rows have not
7
+ * loaded, and delegate selection. Written out by hand that is a dozen props
8
+ * and a filter-shape conversion that every app gets slightly differently:
9
+ *
10
+ * ```svelte
11
+ * <SvGrid data={s.rows} externalSort externalFilter loading={s.loading}
12
+ * onSortingChange={(x) => ctl.setSort(x)}
13
+ * onFiltersChange={(f) => ctl.setFilter({ global: f.global,
14
+ * columns: Object.fromEntries(f.columns.map((c) => [c.id, { ... }])) })}
15
+ * onVisibleRangeChange={(r) => ctl.setViewport(r.startIndex, r.endIndex)}
16
+ * ... />
17
+ * ```
18
+ *
19
+ * An object that implements this interface carries all of it, so the same grid
20
+ * becomes:
21
+ *
22
+ * ```svelte
23
+ * <SvGrid rowModel={ctl} {columns} />
24
+ * ```
25
+ *
26
+ * `createServerDataSource` returns one, and so does the Enterprise server row
27
+ * model - which is the point of the interface rather than a concrete type: the
28
+ * grid depends on the SHAPE, never on either implementation, and an app with
29
+ * its own fetching can satisfy it too.
30
+ *
31
+ * Every member except `subscribe` / `getRows` / `isLoading` is optional, and a
32
+ * prop written explicitly on `<SvGrid>` always wins over the model's answer -
33
+ * so adopting this is never all-or-nothing.
34
+ */
35
+ import type { FilterOperator } from './SvGrid.types.js';
36
+ /** The filter state as the grid reports it, straight from `onFiltersChange`. */
37
+ export type GridFilterState = {
38
+ global: string;
39
+ columns: Array<{
40
+ id: string;
41
+ operator: FilterOperator;
42
+ value: string;
43
+ valueTo?: string;
44
+ selectedValues?: Array<string>;
45
+ }>;
46
+ };
47
+ /** The sort the grid hands a row model: column id and direction, in priority order. */
48
+ export type GridRowModelSort = Array<{
49
+ id: string;
50
+ desc: boolean;
51
+ }>;
52
+ /**
53
+ * What `<SvGrid rowModel>` consumes: rows, a loading flag, a change
54
+ * subscription, and whichever optional parts the model implements - sort,
55
+ * filter, viewport, placeholders, group accessors, selection, filter
56
+ * values, paging, pinned rows and pivot columns. `createServerDataSource`
57
+ * and the Enterprise `createServerRowModel` both satisfy it.
58
+ */
59
+ export type GridRowModel<TData> = {
60
+ /**
61
+ * Register a callback for "something changed, re-read me". Returns an
62
+ * unsubscribe function, which the grid calls on unmount.
63
+ */
64
+ subscribe(onChange: () => void): () => void;
65
+ /** The rows to render, including any placeholders for unloaded ones. */
66
+ getRows(): ReadonlyArray<TData>;
67
+ /** True while a fetch is in flight, for the loading overlay. */
68
+ isLoading(): boolean;
69
+ /** Stable row id. Required once selection or editing is involved. */
70
+ getRowId?(row: TData, index: number): string;
71
+ /** Sort on the server. Present means "do not sort locally". */
72
+ setSort?(sort: GridRowModelSort): void;
73
+ /** Filter on the server. Present means "do not filter locally". */
74
+ setFilter?(filters: GridFilterState): void;
75
+ /** Which rows are on screen, so a block-loading model knows what to fetch. */
76
+ setViewport?(startIndex: number, endIndex: number): void;
77
+ /** Why a row has no data yet, or null when it does. */
78
+ rowPlaceholder?(row: TData, rowIndex: number): 'loading' | 'failed' | null;
79
+ /** Retry the failed block a placeholder row belongs to. */
80
+ retryRow?(row: TData, rowIndex: number): void;
81
+ /** Group / tree accessors, for the grid's treegrid keyboard and ARIA. */
82
+ group?: {
83
+ isGroup: (row: TData) => boolean;
84
+ level: (row: TData) => number;
85
+ expanded?: (row: TData) => boolean;
86
+ onToggle: (row: TData) => void;
87
+ };
88
+ /** Selection the grid does not own - see the `rowSelectionModel` prop. */
89
+ selection?: {
90
+ isSelected: (rowId: string, row: TData) => boolean;
91
+ headerState: () => 'none' | 'some' | 'all';
92
+ toggle: (rowId: string, row: TData, next: boolean) => void;
93
+ toggleAll: (next: boolean) => void;
94
+ selectedCount?: () => number | null;
95
+ bulkUpdate?: (patch: Record<string, unknown>) => Promise<number>;
96
+ };
97
+ /** Distinct values for a column's set filter, fetched from the server. */
98
+ filterValues?(columnId: string): Promise<Array<string>>;
99
+ /** Server-side paging. Omit it for infinite scrolling. */
100
+ pagination?: {
101
+ pageIndex: number;
102
+ pageSize: number;
103
+ rowCount: number;
104
+ setPage(pageIndex: number): void;
105
+ setPageSize(pageSize: number): void;
106
+ /** Choices for the footer's page-size selector. */
107
+ pageSizes?: number[];
108
+ /**
109
+ * Ask the grid for as many rows as its body fits: it measures and calls
110
+ * `setPageSize` whenever the fit changes.
111
+ */
112
+ autoPageSize?: boolean;
113
+ };
114
+ /** Rows to pin above / below the scrolling body - a grand total, say. */
115
+ readonly pinnedTopRows?: ReadonlyArray<TData>;
116
+ readonly pinnedBottomRows?: ReadonlyArray<TData>;
117
+ /**
118
+ * Columns that replace the grid's `columns` while the model is in pivot
119
+ * mode, or null. Read after every change notification.
120
+ */
121
+ readonly pivotResultColumns?: ReadonlyArray<unknown> | null;
122
+ };
123
+ /**
124
+ * Turn the grid's filter payload into the column-keyed map a `ServerRequest`
125
+ * carries. Exported because a hand-written `GridRowModel` needs exactly this
126
+ * conversion, and copying it is how the two shapes drift apart.
127
+ */
128
+ export declare function toServerFilterColumns(filters: GridFilterState): Record<string, {
129
+ operator: string;
130
+ value: string;
131
+ valueTo?: string;
132
+ selectedValues?: Array<string>;
133
+ }>;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Turn the grid's filter payload into the column-keyed map a `ServerRequest`
3
+ * carries. Exported because a hand-written `GridRowModel` needs exactly this
4
+ * conversion, and copying it is how the two shapes drift apart.
5
+ */
6
+ export function toServerFilterColumns(filters) {
7
+ return Object.fromEntries(filters.columns.map((c) => [
8
+ c.id,
9
+ {
10
+ operator: c.operator,
11
+ value: c.value,
12
+ ...(c.valueTo !== undefined ? { valueTo: c.valueTo } : {}),
13
+ ...(c.selectedValues !== undefined ? { selectedValues: c.selectedValues } : {}),
14
+ },
15
+ ]));
16
+ }
@@ -30,6 +30,9 @@ export type RowResizeOptions = {
30
30
  onResize: (rowIndex: number, height: number) => void;
31
31
  /** Optional callback fired during the drag (every pointermove). */
32
32
  onResizeMove?: (rowIndex: number, height: number) => void;
33
+ /** Double-click on the strip: size the row to its content, the
34
+ * spreadsheet gesture. Without it a double-click does nothing. */
35
+ onAutosize?: (rowIndex: number) => void;
33
36
  /** Min row height in px. Default 20. */
34
37
  min?: number;
35
38
  /** Max row height in px. Default 320. */
@@ -29,6 +29,8 @@ const STRIP_CLASS = 'sv-grid-row-resize-handle';
29
29
  export function rowResize(node, opts) {
30
30
  let current = opts;
31
31
  let drag = null;
32
+ /** The last press on a strip, for telling a double-click from two drags. */
33
+ let lastPress = null;
32
34
  function rowIndexOf(tr) {
33
35
  const dataRow = tr.querySelector('[data-svgrid-row]');
34
36
  const val = dataRow?.dataset.svgridRow;
@@ -109,6 +111,18 @@ export function rowResize(node, opts) {
109
111
  return;
110
112
  e.preventDefault();
111
113
  e.stopPropagation();
114
+ // The second press of a double-click: size the row to its content
115
+ // instead of starting another drag. Timed here rather than read from
116
+ // a dblclick event, whose target is the row once the first press's
117
+ // resize has re-rendered the strip; pointerdown carries no count.
118
+ const now = e.timeStamp;
119
+ const again = lastPress !== null && lastPress.rowIndex === rowIndex && now - lastPress.at < 400;
120
+ lastPress = { rowIndex, at: now };
121
+ if (again && current.onAutosize) {
122
+ lastPress = null;
123
+ current.onAutosize(rowIndex);
124
+ return;
125
+ }
112
126
  t.classList.add('is-resizing');
113
127
  drag = {
114
128
  rowIndex,
@@ -5,5 +5,6 @@ export declare function createScrollSync<TFeatures extends TableFeatures = Table
5
5
  hideTooltip: () => void;
6
6
  flushScheduledScrollSync: () => void;
7
7
  scheduleScrollSync: (scrollTop: number, scrollLeft: number) => void;
8
+ syncScrollNow: (scrollTop: number, scrollLeft: number) => void;
8
9
  onBodyScroll: (event: Event) => void;
9
10
  };
@@ -57,6 +57,29 @@ export function createScrollSync(ctx) {
57
57
  return;
58
58
  ctx.scrollSyncRaf = requestAnimationFrame(flushScheduledScrollSync);
59
59
  }
60
+ /**
61
+ * Sync the virtualizers to a scroll position NOW instead of on the next
62
+ * animation frame.
63
+ *
64
+ * For a scroll the grid made itself - keyboard navigation bringing the
65
+ * active cell into view - the position is already known, and going through
66
+ * the browser's `scroll` event and then the rAF above put the row-window
67
+ * update one frame behind the key press: the keydown's own flush painted
68
+ * the active cell on the OLD rows, and the rows only caught up on the next
69
+ * frame. Two painted frames per arrow key. Syncing inline folds the window
70
+ * update into the same flush as the active-cell change, so the key press
71
+ * paints once. The `scroll` event still fires afterwards and re-syncs to
72
+ * the same offset, which the virtualizers treat as a no-op.
73
+ */
74
+ function syncScrollNow(scrollTop, scrollLeft) {
75
+ if (ctx.scrollSyncRaf !== null) {
76
+ cancelAnimationFrame(ctx.scrollSyncRaf);
77
+ ctx.scrollSyncRaf = null;
78
+ }
79
+ ctx.pendingScrollTop = scrollTop;
80
+ ctx.pendingScrollLeft = scrollLeft;
81
+ flushScheduledScrollSync();
82
+ }
60
83
  function onBodyScroll(event) {
61
84
  const container = event.currentTarget;
62
85
  if (!container)
@@ -84,6 +107,7 @@ export function createScrollSync(ctx) {
84
107
  hideTooltip,
85
108
  flushScheduledScrollSync,
86
109
  scheduleScrollSync,
110
+ syncScrollNow,
87
111
  onBodyScroll,
88
112
  };
89
113
  }