@svgrid/grid 1.1.0 → 1.1.1

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 (117) hide show
  1. package/dist/GridMenus.svelte +205 -127
  2. package/dist/SvGrid.controller.svelte.d.ts +44 -1
  3. package/dist/SvGrid.controller.svelte.js +223 -38
  4. package/dist/SvGrid.css +2012 -1709
  5. package/dist/SvGrid.helpers.test.d.ts +1 -0
  6. package/dist/SvGrid.helpers.test.js +298 -0
  7. package/dist/SvGrid.svelte +2346 -2043
  8. package/dist/SvGrid.types.d.ts +91 -4
  9. package/dist/aligned-grids.d.ts +6 -0
  10. package/dist/aligned-grids.js +84 -0
  11. package/dist/aligned-grids.test.d.ts +1 -0
  12. package/dist/aligned-grids.test.js +75 -0
  13. package/dist/build-api.coverage.test.d.ts +20 -0
  14. package/dist/build-api.coverage.test.js +505 -0
  15. package/dist/build-api.js +59 -29
  16. package/dist/cell-render.test.d.ts +1 -0
  17. package/dist/cell-render.test.js +338 -0
  18. package/dist/chart-export.test.d.ts +1 -0
  19. package/dist/chart-export.test.js +302 -0
  20. package/dist/chart.coverage.test.d.ts +1 -0
  21. package/dist/chart.coverage.test.js +748 -0
  22. package/dist/clipboard.js +88 -24
  23. package/dist/clipboard.test.d.ts +1 -0
  24. package/dist/clipboard.test.js +700 -0
  25. package/dist/collaboration.coverage.test.d.ts +1 -0
  26. package/dist/collaboration.coverage.test.js +200 -0
  27. package/dist/column-groups.d.ts +19 -0
  28. package/dist/column-groups.js +62 -0
  29. package/dist/column-groups.test.d.ts +1 -0
  30. package/dist/column-groups.test.js +56 -0
  31. package/dist/column-types.d.ts +10 -0
  32. package/dist/column-types.js +63 -0
  33. package/dist/column-types.test.d.ts +1 -0
  34. package/dist/column-types.test.js +62 -0
  35. package/dist/columns.test.d.ts +1 -0
  36. package/dist/columns.test.js +625 -0
  37. package/dist/core.d.ts +85 -0
  38. package/dist/editing.d.ts +7 -0
  39. package/dist/editing.js +191 -5
  40. package/dist/editing.test.d.ts +1 -0
  41. package/dist/editing.test.js +732 -0
  42. package/dist/editors/cell-editors.coverage.test.d.ts +1 -0
  43. package/dist/editors/cell-editors.coverage.test.js +139 -0
  44. package/dist/facet-buckets.test.d.ts +1 -0
  45. package/dist/facet-buckets.test.js +296 -0
  46. package/dist/filter-operators.test.d.ts +1 -0
  47. package/dist/filter-operators.test.js +135 -0
  48. package/dist/hyperformula-adapter.test.d.ts +1 -0
  49. package/dist/hyperformula-adapter.test.js +205 -0
  50. package/dist/index.d.ts +5 -2
  51. package/dist/index.js +4 -1
  52. package/dist/keyboard-handlers.coverage.test.d.ts +1 -0
  53. package/dist/keyboard-handlers.coverage.test.js +495 -0
  54. package/dist/keyboard-handlers.js +7 -2
  55. package/dist/menus.js +1 -0
  56. package/dist/menus.test.d.ts +1 -0
  57. package/dist/menus.test.js +560 -0
  58. package/dist/named-views.coverage.test.d.ts +1 -0
  59. package/dist/named-views.coverage.test.js +180 -0
  60. package/dist/row-drag.d.ts +49 -0
  61. package/dist/row-drag.js +221 -0
  62. package/dist/row-drag.test.d.ts +1 -0
  63. package/dist/row-drag.test.js +142 -0
  64. package/dist/row-resize.test.d.ts +1 -0
  65. package/dist/row-resize.test.js +329 -0
  66. package/dist/scroll-sync.js +3 -0
  67. package/dist/scroll-sync.test.d.ts +1 -0
  68. package/dist/scroll-sync.test.js +290 -0
  69. package/dist/selection.d.ts +7 -1
  70. package/dist/selection.js +76 -36
  71. package/dist/selection.multi-range.test.d.ts +1 -0
  72. package/dist/selection.multi-range.test.js +55 -0
  73. package/dist/selection.test.d.ts +1 -0
  74. package/dist/selection.test.js +647 -0
  75. package/dist/server-data-source.coverage.test.d.ts +1 -0
  76. package/dist/server-data-source.coverage.test.js +154 -0
  77. package/dist/spreadsheet.d.ts +30 -0
  78. package/dist/spreadsheet.js +48 -0
  79. package/dist/spreadsheet.test.d.ts +1 -0
  80. package/dist/spreadsheet.test.js +446 -0
  81. package/dist/sv-grid-scrollbar.js +13 -1
  82. package/dist/svgrid-wrapper.types.d.ts +19 -0
  83. package/dist/svgrid.behavior.test.js +20 -0
  84. package/dist/svgrid.interaction.test.js +31 -0
  85. package/dist/svgrid.new-features.wrapper.test.js +34 -2
  86. package/dist/test-setup.js +9 -3
  87. package/dist/virtualization/scroll-scaling.d.ts +17 -0
  88. package/dist/virtualization/scroll-scaling.js +35 -0
  89. package/dist/virtualization/scroll-scaling.test.js +42 -1
  90. package/package.json +2 -1
  91. package/src/GridMenus.svelte +205 -127
  92. package/src/SvGrid.controller.svelte.ts +2352 -2195
  93. package/src/SvGrid.css +2012 -1747
  94. package/src/SvGrid.svelte +2346 -2047
  95. package/src/SvGrid.types.ts +537 -456
  96. package/src/aligned-grids.test.ts +80 -0
  97. package/src/aligned-grids.ts +87 -0
  98. package/src/build-api.ts +683 -663
  99. package/src/clipboard.test.ts +49 -0
  100. package/src/clipboard.ts +51 -23
  101. package/src/column-groups.test.ts +59 -0
  102. package/src/column-groups.ts +80 -0
  103. package/src/column-types.test.ts +68 -0
  104. package/src/column-types.ts +82 -0
  105. package/src/core.ts +78 -0
  106. package/src/editing.ts +669 -513
  107. package/src/index.ts +12 -0
  108. package/src/menus.ts +1 -0
  109. package/src/row-drag.test.ts +168 -0
  110. package/src/row-drag.ts +255 -0
  111. package/src/scroll-sync.ts +2 -0
  112. package/src/selection.multi-range.test.ts +61 -0
  113. package/src/selection.ts +71 -37
  114. package/src/spreadsheet.test.ts +489 -445
  115. package/src/spreadsheet.ts +304 -246
  116. package/src/svgrid-wrapper.types.ts +19 -0
  117. package/src/svgrid.new-features.wrapper.test.ts +2 -2
package/dist/selection.js CHANGED
@@ -158,9 +158,47 @@ export function createSelection(ctx) {
158
158
  // No inline scrollVersion bump - the `scroll` event triggers
159
159
  // onBodyScroll which flushes via rAF, doing one batched update.
160
160
  }
161
- function setSelection(rowIndex, colIndex) {
161
+ // Normalize a range to a rectangle, or null when incomplete.
162
+ function rangeRect(range) {
163
+ const a = range?.anchor;
164
+ const f = range?.focus;
165
+ if (!a || !f)
166
+ return null;
167
+ return {
168
+ minRow: Math.min(a.rowIndex, f.rowIndex),
169
+ maxRow: Math.max(a.rowIndex, f.rowIndex),
170
+ minCol: Math.min(a.colIndex, f.colIndex),
171
+ maxCol: Math.max(a.colIndex, f.colIndex),
172
+ };
173
+ }
174
+ // Every selected rectangle: the committed extra ranges plus the active one.
175
+ // Order matters for copy (added-order); the active range comes last.
176
+ function getSelectionRects() {
177
+ const rects = [];
178
+ for (const r of ctx.selectionRanges ?? []) {
179
+ const rect = rangeRect(r);
180
+ if (rect)
181
+ rects.push(rect);
182
+ }
183
+ const active = rangeRect(ctx.selectionRange);
184
+ if (active)
185
+ rects.push(active);
186
+ return rects;
187
+ }
188
+ function setSelection(rowIndex, colIndex, additive = false) {
162
189
  if (!ctx.enableCellSelectionEffective)
163
190
  return;
191
+ if (additive) {
192
+ // Commit the current active range (if any) and start a fresh one, so the
193
+ // previous rectangle stays highlighted alongside the new Ctrl+drag.
194
+ const active = ctx.selectionRange;
195
+ if (active.anchor && active.focus) {
196
+ ctx.selectionRanges = [...ctx.selectionRanges, active];
197
+ }
198
+ }
199
+ else {
200
+ ctx.selectionRanges = [];
201
+ }
164
202
  const point = { rowIndex, colIndex };
165
203
  ctx.selectionRange = { anchor: point, focus: point };
166
204
  }
@@ -171,44 +209,38 @@ export function createSelection(ctx) {
171
209
  ctx.selectionRange = { anchor, focus: { rowIndex, colIndex } };
172
210
  }
173
211
  function isCellInSelectedRange(rowIndex, colIndex) {
174
- const anchor = ctx.selectionRange.anchor;
175
- const focus = ctx.selectionRange.focus;
176
- if (!anchor || !focus)
177
- return false;
178
- const minRow = Math.min(anchor.rowIndex, focus.rowIndex);
179
- const maxRow = Math.max(anchor.rowIndex, focus.rowIndex);
180
- const minCol = Math.min(anchor.colIndex, focus.colIndex);
181
- const maxCol = Math.max(anchor.colIndex, focus.colIndex);
182
- return (rowIndex >= minRow &&
183
- rowIndex <= maxRow &&
184
- colIndex >= minCol &&
185
- colIndex <= maxCol);
212
+ for (const rect of getSelectionRects()) {
213
+ if (rowIndex >= rect.minRow &&
214
+ rowIndex <= rect.maxRow &&
215
+ colIndex >= rect.minCol &&
216
+ colIndex <= rect.maxCol) {
217
+ return true;
218
+ }
219
+ }
220
+ return false;
186
221
  }
187
222
  /**
188
- * Returns which sides of the selection rectangle a cell sits on, for the
189
- * Excel-style outline. Returns null when the cell is outside the range.
223
+ * Returns which sides of a selection rectangle a cell sits on, for the
224
+ * Excel-style outline. With multiple ranges, the edges of the FIRST range
225
+ * that contains the cell are returned (active range checked last so its
226
+ * outline wins on overlap). Returns null when the cell is in no range.
190
227
  */
191
228
  function getCellRangeEdges(rowIndex, colIndex) {
192
- const anchor = ctx.selectionRange.anchor;
193
- const focus = ctx.selectionRange.focus;
194
- if (!anchor || !focus)
195
- return null;
196
- const minRow = Math.min(anchor.rowIndex, focus.rowIndex);
197
- const maxRow = Math.max(anchor.rowIndex, focus.rowIndex);
198
- const minCol = Math.min(anchor.colIndex, focus.colIndex);
199
- const maxCol = Math.max(anchor.colIndex, focus.colIndex);
200
- if (rowIndex < minRow ||
201
- rowIndex > maxRow ||
202
- colIndex < minCol ||
203
- colIndex > maxCol) {
204
- return null;
229
+ for (const rect of getSelectionRects()) {
230
+ if (rowIndex < rect.minRow ||
231
+ rowIndex > rect.maxRow ||
232
+ colIndex < rect.minCol ||
233
+ colIndex > rect.maxCol) {
234
+ continue;
235
+ }
236
+ return {
237
+ top: rowIndex === rect.minRow,
238
+ bottom: rowIndex === rect.maxRow,
239
+ left: colIndex === rect.minCol,
240
+ right: colIndex === rect.maxCol,
241
+ };
205
242
  }
206
- return {
207
- top: rowIndex === minRow,
208
- bottom: rowIndex === maxRow,
209
- left: colIndex === minCol,
210
- right: colIndex === maxCol,
211
- };
243
+ return null;
212
244
  }
213
245
  /** Returns true when the given cell is inside the fill-drag preview
214
246
  * range BUT outside the original source range. Used to paint a
@@ -259,8 +291,15 @@ export function createSelection(ctx) {
259
291
  }
260
292
  else {
261
293
  setActiveCell(rowIndex, colIndex);
262
- setSelection(rowIndex, colIndex);
263
- ctx.isDraggingSelection = true;
294
+ // Ctrl/Cmd starts an ADDITIONAL range, keeping prior ranges highlighted.
295
+ setSelection(rowIndex, colIndex, event.ctrlKey || event.metaKey);
296
+ // Range drag-select is a mouse/pen affordance. On touch, starting a drag
297
+ // here would rubber-band a selection AND fight the browser's native
298
+ // scroll (we never preventDefault), so a finger-drag to scroll the grid
299
+ // instead selected cells. Tap still selects the single cell above; we
300
+ // just don't enter drag-select mode for touch. (issue #23)
301
+ if (event.pointerType !== "touch")
302
+ ctx.isDraggingSelection = true;
264
303
  }
265
304
  }
266
305
  function onCellPointerEnter(rowIndex, colIndex) {
@@ -375,6 +414,7 @@ export function createSelection(ctx) {
375
414
  extendSelection,
376
415
  isCellInSelectedRange,
377
416
  getCellRangeEdges,
417
+ getSelectionRects,
378
418
  isInFillPreview,
379
419
  findColumnById,
380
420
  onCellPointerDown,
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Unit tests for multiple-range (Ctrl+drag) cell selection, driven through the
3
+ * pure range helpers of createSelection(ctx) with a minimal fake ctx.
4
+ */
5
+ import { describe, expect, it } from 'vitest';
6
+ import { createSelection } from './selection';
7
+ function makeCtx() {
8
+ return {
9
+ enableCellSelectionEffective: true,
10
+ selectionRange: { anchor: null, focus: null },
11
+ selectionRanges: [],
12
+ };
13
+ }
14
+ describe('multiple range selection', () => {
15
+ it('a plain selection replaces any committed ranges', () => {
16
+ const ctx = makeCtx();
17
+ const s = createSelection(ctx);
18
+ s.setSelection(0, 0);
19
+ s.extendSelection(1, 1); // active range 0,0..1,1
20
+ s.setSelection(5, 5, true); // additive: commit prior, start new
21
+ expect(ctx.selectionRanges.length).toBe(1);
22
+ s.setSelection(9, 9, false); // plain: clears committed
23
+ expect(ctx.selectionRanges.length).toBe(0);
24
+ });
25
+ it('Ctrl+drag commits the prior range and keeps both highlighted', () => {
26
+ const ctx = makeCtx();
27
+ const s = createSelection(ctx);
28
+ s.setSelection(0, 0);
29
+ s.extendSelection(1, 1); // range A: rows 0-1, cols 0-1
30
+ s.setSelection(3, 3, true); // start range B additively
31
+ s.extendSelection(4, 4); // range B: rows 3-4, cols 3-4
32
+ const rects = s.getSelectionRects();
33
+ expect(rects.length).toBe(2);
34
+ // both ranges report cells as selected
35
+ expect(s.isCellInSelectedRange(0, 0)).toBe(true);
36
+ expect(s.isCellInSelectedRange(1, 1)).toBe(true);
37
+ expect(s.isCellInSelectedRange(4, 4)).toBe(true);
38
+ // a cell in neither is not selected
39
+ expect(s.isCellInSelectedRange(2, 2)).toBe(false);
40
+ });
41
+ it('getCellRangeEdges outlines each range independently', () => {
42
+ const ctx = makeCtx();
43
+ const s = createSelection(ctx);
44
+ s.setSelection(0, 0);
45
+ s.extendSelection(1, 1);
46
+ s.setSelection(3, 3, true);
47
+ s.extendSelection(4, 4);
48
+ // top-left corner of range A
49
+ expect(s.getCellRangeEdges(0, 0)).toEqual({ top: true, bottom: false, left: true, right: false });
50
+ // bottom-right corner of range B
51
+ expect(s.getCellRangeEdges(4, 4)).toEqual({ top: false, bottom: true, left: false, right: true });
52
+ // outside every range
53
+ expect(s.getCellRangeEdges(2, 2)).toBeNull();
54
+ });
55
+ });
@@ -0,0 +1 @@
1
+ export {};