@svgrid/grid 1.1.2 → 1.2.3

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 (143) hide show
  1. package/dist/SvGrid.css +294 -16
  2. package/dist/SvGrid.svelte +4 -1
  3. package/dist/SvGridDropdown.svelte +7 -3
  4. package/dist/cdn/svgrid.js +13506 -0
  5. package/dist/cdn/svgrid.svelte-external.js +11190 -0
  6. package/package.json +12 -2
  7. package/src/SvGrid.css +294 -16
  8. package/src/SvGrid.svelte +4 -1
  9. package/src/SvGridDropdown.svelte +7 -3
  10. package/dist/SvGrid.helpers.test.d.ts +0 -1
  11. package/dist/SvGrid.helpers.test.js +0 -298
  12. package/dist/SvGridChart.test.d.ts +0 -1
  13. package/dist/SvGridChart.test.js +0 -62
  14. package/dist/a11y.contract.test.d.ts +0 -1
  15. package/dist/a11y.contract.test.js +0 -39
  16. package/dist/a11y.test.d.ts +0 -1
  17. package/dist/a11y.test.js +0 -44
  18. package/dist/aligned-grids.test.d.ts +0 -1
  19. package/dist/aligned-grids.test.js +0 -75
  20. package/dist/build-api.coverage.test.d.ts +0 -20
  21. package/dist/build-api.coverage.test.js +0 -505
  22. package/dist/cell-formatting.test.d.ts +0 -1
  23. package/dist/cell-formatting.test.js +0 -234
  24. package/dist/cell-render.test.d.ts +0 -1
  25. package/dist/cell-render.test.js +0 -338
  26. package/dist/chart-export.test.d.ts +0 -1
  27. package/dist/chart-export.test.js +0 -302
  28. package/dist/chart.coverage.test.d.ts +0 -1
  29. package/dist/chart.coverage.test.js +0 -748
  30. package/dist/chart.test.d.ts +0 -1
  31. package/dist/chart.test.js +0 -289
  32. package/dist/clipboard.test.d.ts +0 -1
  33. package/dist/clipboard.test.js +0 -700
  34. package/dist/collaboration.coverage.test.d.ts +0 -1
  35. package/dist/collaboration.coverage.test.js +0 -200
  36. package/dist/collaboration.test.d.ts +0 -1
  37. package/dist/collaboration.test.js +0 -65
  38. package/dist/column-groups.test.d.ts +0 -1
  39. package/dist/column-groups.test.js +0 -56
  40. package/dist/column-types.test.d.ts +0 -1
  41. package/dist/column-types.test.js +0 -62
  42. package/dist/columns.test.d.ts +0 -1
  43. package/dist/columns.test.js +0 -625
  44. package/dist/conditional-formatting.test.d.ts +0 -1
  45. package/dist/conditional-formatting.test.js +0 -87
  46. package/dist/core.coverage.test.d.ts +0 -1
  47. package/dist/core.coverage.test.js +0 -186
  48. package/dist/core.performance.test.d.ts +0 -1
  49. package/dist/core.performance.test.js +0 -29
  50. package/dist/createGrid.test.d.ts +0 -1
  51. package/dist/createGrid.test.js +0 -9
  52. package/dist/editing.test.d.ts +0 -1
  53. package/dist/editing.test.js +0 -732
  54. package/dist/editors/cell-editors.coverage.test.d.ts +0 -1
  55. package/dist/editors/cell-editors.coverage.test.js +0 -139
  56. package/dist/editors/cell-editors.test.d.ts +0 -1
  57. package/dist/editors/cell-editors.test.js +0 -75
  58. package/dist/facet-buckets.test.d.ts +0 -1
  59. package/dist/facet-buckets.test.js +0 -296
  60. package/dist/fill-patterns.test.d.ts +0 -1
  61. package/dist/fill-patterns.test.js +0 -81
  62. package/dist/filter-operators.test.d.ts +0 -1
  63. package/dist/filter-operators.test.js +0 -135
  64. package/dist/filtering/excel-filters.test.d.ts +0 -1
  65. package/dist/filtering/excel-filters.test.js +0 -116
  66. package/dist/filtering/locale-filter.test.d.ts +0 -13
  67. package/dist/filtering/locale-filter.test.js +0 -189
  68. package/dist/flex-render.test.d.ts +0 -1
  69. package/dist/flex-render.test.js +0 -142
  70. package/dist/group-aggregate.test.d.ts +0 -1
  71. package/dist/group-aggregate.test.js +0 -32
  72. package/dist/hyperformula-adapter.test.d.ts +0 -1
  73. package/dist/hyperformula-adapter.test.js +0 -205
  74. package/dist/keyboard-handlers.coverage.test.d.ts +0 -1
  75. package/dist/keyboard-handlers.coverage.test.js +0 -495
  76. package/dist/keyboard.test.d.ts +0 -1
  77. package/dist/keyboard.test.js +0 -27
  78. package/dist/menus.test.d.ts +0 -1
  79. package/dist/menus.test.js +0 -560
  80. package/dist/named-views.coverage.test.d.ts +0 -1
  81. package/dist/named-views.coverage.test.js +0 -180
  82. package/dist/named-views.test.d.ts +0 -1
  83. package/dist/named-views.test.js +0 -57
  84. package/dist/new-features.test.d.ts +0 -1
  85. package/dist/new-features.test.js +0 -183
  86. package/dist/render-component.test.d.ts +0 -1
  87. package/dist/render-component.test.js +0 -41
  88. package/dist/row-drag.test.d.ts +0 -1
  89. package/dist/row-drag.test.js +0 -142
  90. package/dist/row-resize.test.d.ts +0 -1
  91. package/dist/row-resize.test.js +0 -329
  92. package/dist/scroll-sync.test.d.ts +0 -1
  93. package/dist/scroll-sync.test.js +0 -290
  94. package/dist/selection.multi-range.test.d.ts +0 -1
  95. package/dist/selection.multi-range.test.js +0 -55
  96. package/dist/selection.test.d.ts +0 -1
  97. package/dist/selection.test.js +0 -647
  98. package/dist/server-data-source.coverage.test.d.ts +0 -1
  99. package/dist/server-data-source.coverage.test.js +0 -154
  100. package/dist/server-data-source.test.d.ts +0 -1
  101. package/dist/server-data-source.test.js +0 -81
  102. package/dist/sparkline.test.d.ts +0 -1
  103. package/dist/sparkline.test.js +0 -52
  104. package/dist/spreadsheet.test.d.ts +0 -1
  105. package/dist/spreadsheet.test.js +0 -446
  106. package/dist/subscribe.test.d.ts +0 -1
  107. package/dist/subscribe.test.js +0 -93
  108. package/dist/svgrid.api-extensions.test.d.ts +0 -14
  109. package/dist/svgrid.api-extensions.test.js +0 -334
  110. package/dist/svgrid.api.test.d.ts +0 -15
  111. package/dist/svgrid.api.test.js +0 -169
  112. package/dist/svgrid.behavior.test.d.ts +0 -15
  113. package/dist/svgrid.behavior.test.js +0 -625
  114. package/dist/svgrid.column-reorder.test.d.ts +0 -15
  115. package/dist/svgrid.column-reorder.test.js +0 -224
  116. package/dist/svgrid.comments-autocomplete.test.d.ts +0 -1
  117. package/dist/svgrid.comments-autocomplete.test.js +0 -96
  118. package/dist/svgrid.context-menu.test.d.ts +0 -1
  119. package/dist/svgrid.context-menu.test.js +0 -102
  120. package/dist/svgrid.features.test.d.ts +0 -1
  121. package/dist/svgrid.features.test.js +0 -129
  122. package/dist/svgrid.interaction.test.d.ts +0 -9
  123. package/dist/svgrid.interaction.test.js +0 -349
  124. package/dist/svgrid.locale-filtering.test.d.ts +0 -15
  125. package/dist/svgrid.locale-filtering.test.js +0 -261
  126. package/dist/svgrid.new-features.wrapper.test.d.ts +0 -1
  127. package/dist/svgrid.new-features.wrapper.test.js +0 -194
  128. package/dist/svgrid.row-pinning.test.d.ts +0 -14
  129. package/dist/svgrid.row-pinning.test.js +0 -250
  130. package/dist/svgrid.set-filter.test.d.ts +0 -17
  131. package/dist/svgrid.set-filter.test.js +0 -268
  132. package/dist/svgrid.wrapper.test.d.ts +0 -1
  133. package/dist/svgrid.wrapper.test.js +0 -59
  134. package/dist/test-setup.d.ts +0 -1
  135. package/dist/test-setup.js +0 -35
  136. package/dist/transaction.test.d.ts +0 -1
  137. package/dist/transaction.test.js +0 -89
  138. package/dist/virtualization/column-virtualizer.test.d.ts +0 -1
  139. package/dist/virtualization/column-virtualizer.test.js +0 -25
  140. package/dist/virtualization/scroll-scaling.test.d.ts +0 -1
  141. package/dist/virtualization/scroll-scaling.test.js +0 -127
  142. package/dist/virtualization/virtualizer.test.d.ts +0 -1
  143. package/dist/virtualization/virtualizer.test.js +0 -41
@@ -1,495 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
- import { createKeyboard } from './keyboard-handlers';
3
- function makeCtx(overrides = {}) {
4
- const state = {
5
- activeCell: { rowIndex: 1, colIndex: 1, cellId: null },
6
- sorting: [],
7
- ...overrides.state,
8
- };
9
- const setState = vi.fn((updater) => {
10
- Object.assign(state, typeof updater === 'function' ? updater(state) : updater);
11
- });
12
- const ctx = {
13
- editingCell: null,
14
- findOpen: false,
15
- columnMenuFor: null,
16
- operatorMenuFor: null,
17
- history: [],
18
- historyPtr: -1,
19
- historyVersion: 0,
20
- headerHeight: 30,
21
- scrollContainer: { clientHeight: 330 },
22
- props: { rowHeight: 30 },
23
- allRows: [],
24
- allColumns: [],
25
- grid: {
26
- getState: () => state,
27
- store: { setState },
28
- },
29
- // recorded side effects
30
- copySelectionToClipboard: vi.fn(),
31
- pasteFromClipboard: vi.fn(() => Promise.resolve()),
32
- cutSelectionToClipboard: vi.fn(() => Promise.resolve()),
33
- applyHistoryStep: vi.fn(),
34
- clearSelectedCells: vi.fn(() => false),
35
- clearSelectedCellValues: vi.fn(),
36
- onCellDoubleClick: vi.fn(),
37
- startEditingWithChar: vi.fn(() => true),
38
- toggleBooleanCell: vi.fn(),
39
- setActiveCell: vi.fn(),
40
- scrollActiveCellIntoView: vi.fn(),
41
- extendSelection: vi.fn(),
42
- setSelection: vi.fn(),
43
- closeMenus: vi.fn(),
44
- ...overrides,
45
- };
46
- ctx._state = state;
47
- ctx._setState = setState;
48
- return ctx;
49
- }
50
- // A keydown event whose target === currentTarget (so the grid root owns it).
51
- function rootKeyEvent(init) {
52
- const el = document.createElement('div');
53
- const event = new KeyboardEvent('keydown', { cancelable: true, ...init });
54
- Object.defineProperty(event, 'target', { value: el });
55
- Object.defineProperty(event, 'currentTarget', { value: el });
56
- vi.spyOn(event, 'preventDefault');
57
- return event;
58
- }
59
- describe('onGridKeyDown / guards', () => {
60
- it('ignores events that did not originate on the grid root', () => {
61
- const ctx = makeCtx();
62
- const { onGridKeyDown } = createKeyboard(ctx);
63
- const el = document.createElement('div');
64
- const other = document.createElement('div');
65
- const event = new KeyboardEvent('keydown', { key: 'ArrowDown' });
66
- Object.defineProperty(event, 'target', { value: other });
67
- Object.defineProperty(event, 'currentTarget', { value: el });
68
- onGridKeyDown(event);
69
- expect(ctx.setActiveCell).not.toHaveBeenCalled();
70
- });
71
- it('ignores all keys while a cell is being edited', () => {
72
- const ctx = makeCtx({ editingCell: { rowIndex: 0, colIndex: 0 } });
73
- const { onGridKeyDown } = createKeyboard(ctx);
74
- onGridKeyDown(rootKeyEvent({ key: 'ArrowDown' }));
75
- expect(ctx.setActiveCell).not.toHaveBeenCalled();
76
- });
77
- });
78
- describe('onGridKeyDown / clipboard shortcuts', () => {
79
- it('Ctrl+C copies the selection', () => {
80
- const ctx = makeCtx();
81
- const { onGridKeyDown } = createKeyboard(ctx);
82
- const e = rootKeyEvent({ key: 'c', ctrlKey: true });
83
- onGridKeyDown(e);
84
- expect(ctx.copySelectionToClipboard).toHaveBeenCalledTimes(1);
85
- expect(e.preventDefault).toHaveBeenCalled();
86
- });
87
- it('Cmd+C (metaKey) also copies', () => {
88
- const ctx = makeCtx();
89
- const { onGridKeyDown } = createKeyboard(ctx);
90
- onGridKeyDown(rootKeyEvent({ key: 'C', metaKey: true }));
91
- expect(ctx.copySelectionToClipboard).toHaveBeenCalledTimes(1);
92
- });
93
- it('Ctrl+V in a secure context reads via the Clipboard API', () => {
94
- const original = navigator.clipboard;
95
- Object.defineProperty(navigator, 'clipboard', {
96
- value: { readText: () => Promise.resolve('') },
97
- configurable: true,
98
- });
99
- const ctx = makeCtx();
100
- const { onGridKeyDown } = createKeyboard(ctx);
101
- const e = rootKeyEvent({ key: 'v', ctrlKey: true });
102
- onGridKeyDown(e);
103
- expect(ctx.pasteFromClipboard).toHaveBeenCalledTimes(1);
104
- expect(e.preventDefault).toHaveBeenCalled();
105
- Object.defineProperty(navigator, 'clipboard', { value: original, configurable: true });
106
- });
107
- it('Ctrl+V in an insecure context does NOT preventDefault (lets native paste through)', () => {
108
- const original = navigator.clipboard;
109
- Object.defineProperty(navigator, 'clipboard', { value: undefined, configurable: true });
110
- const ctx = makeCtx();
111
- const { onGridKeyDown } = createKeyboard(ctx);
112
- const e = rootKeyEvent({ key: 'v', ctrlKey: true });
113
- onGridKeyDown(e);
114
- expect(ctx.pasteFromClipboard).not.toHaveBeenCalled();
115
- expect(e.preventDefault).not.toHaveBeenCalled();
116
- Object.defineProperty(navigator, 'clipboard', { value: original, configurable: true });
117
- });
118
- it('Ctrl+X cuts the selection', () => {
119
- const ctx = makeCtx();
120
- const { onGridKeyDown } = createKeyboard(ctx);
121
- const e = rootKeyEvent({ key: 'x', ctrlKey: true });
122
- onGridKeyDown(e);
123
- expect(ctx.cutSelectionToClipboard).toHaveBeenCalledTimes(1);
124
- expect(e.preventDefault).toHaveBeenCalled();
125
- });
126
- it('does not treat Ctrl+Alt+C as a copy shortcut', () => {
127
- const ctx = makeCtx();
128
- const { onGridKeyDown } = createKeyboard(ctx);
129
- onGridKeyDown(rootKeyEvent({ key: 'c', ctrlKey: true, altKey: true }));
130
- expect(ctx.copySelectionToClipboard).not.toHaveBeenCalled();
131
- });
132
- });
133
- describe('onGridKeyDown / undo & redo', () => {
134
- it('Ctrl+Z undoes when there is history to undo', () => {
135
- const step = { kind: 'edit' };
136
- const ctx = makeCtx({ history: [step], historyPtr: 0 });
137
- const { onGridKeyDown } = createKeyboard(ctx);
138
- onGridKeyDown(rootKeyEvent({ key: 'z', ctrlKey: true }));
139
- expect(ctx.applyHistoryStep).toHaveBeenCalledWith(step, 'undo');
140
- expect(ctx.historyPtr).toBe(-1);
141
- expect(ctx.historyVersion).toBe(1);
142
- });
143
- it('Ctrl+Z is a no-op when nothing to undo', () => {
144
- const ctx = makeCtx({ history: [], historyPtr: -1 });
145
- const { onGridKeyDown } = createKeyboard(ctx);
146
- onGridKeyDown(rootKeyEvent({ key: 'z', ctrlKey: true }));
147
- expect(ctx.applyHistoryStep).not.toHaveBeenCalled();
148
- });
149
- it('Ctrl+Shift+Z redoes when there is a future step', () => {
150
- const step = { kind: 'edit' };
151
- const ctx = makeCtx({ history: [{}, step], historyPtr: 0 });
152
- const { onGridKeyDown } = createKeyboard(ctx);
153
- onGridKeyDown(rootKeyEvent({ key: 'z', ctrlKey: true, shiftKey: true }));
154
- expect(ctx.applyHistoryStep).toHaveBeenCalledWith(step, 'redo');
155
- expect(ctx.historyPtr).toBe(1);
156
- expect(ctx.historyVersion).toBe(1);
157
- });
158
- it('Ctrl+Y also redoes', () => {
159
- const step = { kind: 'edit' };
160
- const ctx = makeCtx({ history: [{}, step], historyPtr: 0 });
161
- const { onGridKeyDown } = createKeyboard(ctx);
162
- onGridKeyDown(rootKeyEvent({ key: 'y', ctrlKey: true }));
163
- expect(ctx.applyHistoryStep).toHaveBeenCalledWith(step, 'redo');
164
- });
165
- it('redo is a no-op when already at the end of history', () => {
166
- const ctx = makeCtx({ history: [{}], historyPtr: 0 });
167
- const { onGridKeyDown } = createKeyboard(ctx);
168
- onGridKeyDown(rootKeyEvent({ key: 'y', ctrlKey: true }));
169
- expect(ctx.applyHistoryStep).not.toHaveBeenCalled();
170
- });
171
- });
172
- describe('onGridKeyDown / find overlay', () => {
173
- it('Ctrl+F opens the find overlay', () => {
174
- const ctx = makeCtx();
175
- const { onGridKeyDown } = createKeyboard(ctx);
176
- onGridKeyDown(rootKeyEvent({ key: 'f', ctrlKey: true }));
177
- expect(ctx.findOpen).toBe(true);
178
- });
179
- it('Escape closes the find overlay when it is open', () => {
180
- const ctx = makeCtx({ findOpen: true });
181
- const { onGridKeyDown } = createKeyboard(ctx);
182
- const e = rootKeyEvent({ key: 'Escape' });
183
- onGridKeyDown(e);
184
- expect(ctx.findOpen).toBe(false);
185
- expect(e.preventDefault).toHaveBeenCalled();
186
- });
187
- });
188
- describe('onGridKeyDown / delete & backspace clearing', () => {
189
- it('Delete clears the selection when cells were cleared', () => {
190
- const ctx = makeCtx({ clearSelectedCells: vi.fn(() => true), allRows: [{}], allColumns: [{}] });
191
- const { onGridKeyDown } = createKeyboard(ctx);
192
- const e = rootKeyEvent({ key: 'Delete' });
193
- onGridKeyDown(e);
194
- expect(ctx.clearSelectedCells).toHaveBeenCalled();
195
- expect(e.preventDefault).toHaveBeenCalled();
196
- });
197
- it('Backspace clears the selection too', () => {
198
- const ctx = makeCtx({ clearSelectedCells: vi.fn(() => true), allRows: [{}], allColumns: [{}] });
199
- const { onGridKeyDown } = createKeyboard(ctx);
200
- onGridKeyDown(rootKeyEvent({ key: 'Backspace' }));
201
- expect(ctx.clearSelectedCells).toHaveBeenCalled();
202
- });
203
- it('Delete falls through to clearCells intent when nothing was cleared', () => {
204
- const ctx = makeCtx({
205
- clearSelectedCells: vi.fn(() => false),
206
- allRows: [{}, {}],
207
- allColumns: [{}, {}],
208
- });
209
- const { onGridKeyDown } = createKeyboard(ctx);
210
- onGridKeyDown(rootKeyEvent({ key: 'Delete' }));
211
- expect(ctx.clearSelectedCellValues).toHaveBeenCalledTimes(1);
212
- });
213
- });
214
- describe('onGridKeyDown / F2 + activation', () => {
215
- it('F2 starts editing the active cell', () => {
216
- const ctx = makeCtx({ allRows: [{}, {}], allColumns: [{}, {}] });
217
- const { onGridKeyDown } = createKeyboard(ctx);
218
- onGridKeyDown(rootKeyEvent({ key: 'F2' }));
219
- expect(ctx.onCellDoubleClick).toHaveBeenCalledWith(1, 1);
220
- });
221
- it('Space toggles row selection', () => {
222
- const toggleSelected = vi.fn();
223
- const ctx = makeCtx({
224
- allRows: [{}, { toggleSelected }],
225
- allColumns: [{}, {}],
226
- });
227
- const { onGridKeyDown } = createKeyboard(ctx);
228
- onGridKeyDown(rootKeyEvent({ key: ' ' }));
229
- expect(toggleSelected).toHaveBeenCalled();
230
- });
231
- it('Space on an expandable row toggles selection (space branch wins over expand)', () => {
232
- // getKeyboardIntent(" ") === "activate". The handler checks `event.key === " "`
233
- // FIRST and toggles row selection, returning before the ctrl/expand branch.
234
- const toggleSelected = vi.fn();
235
- const toggleExpanded = vi.fn();
236
- const row = { toggleSelected, getCanExpand: () => true, toggleExpanded };
237
- const ctx = makeCtx({ allRows: [{}, row], allColumns: [{}, {}] });
238
- const { onGridKeyDown } = createKeyboard(ctx);
239
- onGridKeyDown(rootKeyEvent({ key: ' ', ctrlKey: true }));
240
- expect(toggleSelected).toHaveBeenCalled();
241
- expect(toggleExpanded).not.toHaveBeenCalled();
242
- });
243
- it('F2 on a checkbox column opens editing via onCellDoubleClick', () => {
244
- // F2 is intercepted before intent resolution and always calls
245
- // onCellDoubleClick regardless of column editorType.
246
- const ctx = makeCtx({
247
- allRows: [{}, {}],
248
- allColumns: [{}, { columnDef: { editorType: 'checkbox' } }],
249
- });
250
- const { onGridKeyDown } = createKeyboard(ctx);
251
- onGridKeyDown(rootKeyEvent({ key: 'F2' }));
252
- expect(ctx.onCellDoubleClick).toHaveBeenCalledWith(1, 1);
253
- expect(ctx.toggleBooleanCell).not.toHaveBeenCalled();
254
- });
255
- });
256
- describe('onGridKeyDown / typing to edit', () => {
257
- it('a printable character starts editing seeded with that char', () => {
258
- const ctx = makeCtx({
259
- allRows: [{}, {}],
260
- allColumns: [{}, {}],
261
- startEditingWithChar: vi.fn(() => true),
262
- });
263
- const { onGridKeyDown } = createKeyboard(ctx);
264
- const e = rootKeyEvent({ key: 'a' });
265
- onGridKeyDown(e);
266
- expect(ctx.startEditingWithChar).toHaveBeenCalledWith(1, 1, 'a');
267
- expect(e.preventDefault).toHaveBeenCalled();
268
- });
269
- it('does not preventDefault when editing cannot start', () => {
270
- const ctx = makeCtx({
271
- allRows: [{}, {}],
272
- allColumns: [{}, {}],
273
- startEditingWithChar: vi.fn(() => false),
274
- });
275
- const { onGridKeyDown } = createKeyboard(ctx);
276
- const e = rootKeyEvent({ key: 'a' });
277
- onGridKeyDown(e);
278
- expect(e.preventDefault).not.toHaveBeenCalled();
279
- });
280
- it('ignores a multi-character / modified key as a noop', () => {
281
- const ctx = makeCtx({ allRows: [{}, {}], allColumns: [{}, {}] });
282
- const { onGridKeyDown } = createKeyboard(ctx);
283
- onGridKeyDown(rootKeyEvent({ key: 'Shift' }));
284
- expect(ctx.startEditingWithChar).not.toHaveBeenCalled();
285
- });
286
- });
287
- describe('onGridKeyDown / navigation', () => {
288
- function navCtx(extra = {}) {
289
- return makeCtx({
290
- state: { activeCell: { rowIndex: 1, colIndex: 1, cellId: null } },
291
- allRows: [{}, {}, {}, {}, {}],
292
- allColumns: [{}, {}, {}, {}],
293
- ...extra,
294
- });
295
- }
296
- it('ArrowDown moves the active cell down and updates selection', () => {
297
- const ctx = navCtx();
298
- const { onGridKeyDown } = createKeyboard(ctx);
299
- onGridKeyDown(rootKeyEvent({ key: 'ArrowDown' }));
300
- expect(ctx.setActiveCell).toHaveBeenCalledWith(2, 1);
301
- expect(ctx.scrollActiveCellIntoView).toHaveBeenCalledWith(2, 1);
302
- expect(ctx.setSelection).toHaveBeenCalledWith(2, 1);
303
- expect(ctx.extendSelection).not.toHaveBeenCalled();
304
- });
305
- it('Shift+ArrowRight extends the selection rectangle', () => {
306
- const ctx = navCtx();
307
- const { onGridKeyDown } = createKeyboard(ctx);
308
- onGridKeyDown(rootKeyEvent({ key: 'ArrowRight', shiftKey: true }));
309
- expect(ctx.setActiveCell).toHaveBeenCalledWith(1, 2);
310
- expect(ctx.extendSelection).toHaveBeenCalledWith(1, 2);
311
- expect(ctx.setSelection).not.toHaveBeenCalled();
312
- });
313
- it('Shift+Tab changes direction without extending the range', () => {
314
- // tabPrev is NOT in the arrow-family extend set, so even with shiftKey the
315
- // selection is replaced rather than grown.
316
- const ctx = navCtx({ state: { activeCell: { rowIndex: 1, colIndex: 1, cellId: null } } });
317
- const { onGridKeyDown } = createKeyboard(ctx);
318
- onGridKeyDown(rootKeyEvent({ key: 'Tab', shiftKey: true }));
319
- expect(ctx.setActiveCell).toHaveBeenCalledWith(1, 0);
320
- expect(ctx.extendSelection).not.toHaveBeenCalled();
321
- expect(ctx.setSelection).toHaveBeenCalledWith(1, 0);
322
- });
323
- it('Shift+Enter (moveUp) DOES extend the selection (arrow-family intent)', () => {
324
- const ctx = navCtx();
325
- const { onGridKeyDown } = createKeyboard(ctx);
326
- onGridKeyDown(rootKeyEvent({ key: 'Enter', shiftKey: true }));
327
- // Enter+shift -> moveUp, which is in the extend set.
328
- expect(ctx.setActiveCell).toHaveBeenCalledWith(0, 1);
329
- expect(ctx.extendSelection).toHaveBeenCalledWith(0, 1);
330
- });
331
- it('Tab moves right and wraps using getNextActiveCell', () => {
332
- const ctx = navCtx({ state: { activeCell: { rowIndex: 1, colIndex: 3, cellId: null } } });
333
- const { onGridKeyDown } = createKeyboard(ctx);
334
- onGridKeyDown(rootKeyEvent({ key: 'Tab' }));
335
- // colIndex 3 is maxCol -> wraps to col 0 row 2
336
- expect(ctx.setActiveCell).toHaveBeenCalledWith(2, 0);
337
- });
338
- it('defaults the active cell to (0,0) when state has none', () => {
339
- const ctx = navCtx({ state: { activeCell: undefined } });
340
- const { onGridKeyDown } = createKeyboard(ctx);
341
- onGridKeyDown(rootKeyEvent({ key: 'ArrowRight' }));
342
- expect(ctx.setActiveCell).toHaveBeenCalledWith(0, 1);
343
- });
344
- it('PageDown uses the pagination pageSize when present', () => {
345
- const ctx = navCtx({
346
- state: {
347
- activeCell: { rowIndex: 0, colIndex: 0, cellId: null },
348
- pagination: { pageSize: 2 },
349
- },
350
- });
351
- const { onGridKeyDown } = createKeyboard(ctx);
352
- onGridKeyDown(rootKeyEvent({ key: 'PageDown' }));
353
- expect(ctx.setActiveCell).toHaveBeenCalledWith(2, 0);
354
- });
355
- it('PageDown derives a page step from container height when no pagination', () => {
356
- // clientHeight 330 - headerHeight 30 = 300 usable / 30 row - 1 = 9 step.
357
- const ctx = navCtx({
358
- state: { activeCell: { rowIndex: 0, colIndex: 0, cellId: null } },
359
- allRows: new Array(40).fill({}),
360
- });
361
- const { onGridKeyDown } = createKeyboard(ctx);
362
- onGridKeyDown(rootKeyEvent({ key: 'PageDown' }));
363
- expect(ctx.setActiveCell).toHaveBeenCalledWith(9, 0);
364
- });
365
- it('PageDown handles a functional rowHeight prop', () => {
366
- const ctx = navCtx({
367
- state: { activeCell: { rowIndex: 0, colIndex: 0, cellId: null } },
368
- allRows: new Array(40).fill({}),
369
- props: { rowHeight: () => 60 },
370
- });
371
- const { onGridKeyDown } = createKeyboard(ctx);
372
- onGridKeyDown(rootKeyEvent({ key: 'PageDown' }));
373
- // 300 usable / 60 - 1 = 4
374
- expect(ctx.setActiveCell).toHaveBeenCalledWith(4, 0);
375
- });
376
- it('Home / Ctrl+Home move to row start and grid start', () => {
377
- const ctx = navCtx({ state: { activeCell: { rowIndex: 2, colIndex: 3, cellId: null } } });
378
- const { onGridKeyDown } = createKeyboard(ctx);
379
- onGridKeyDown(rootKeyEvent({ key: 'Home' }));
380
- expect(ctx.setActiveCell).toHaveBeenLastCalledWith(2, 0);
381
- onGridKeyDown(rootKeyEvent({ key: 'Home', ctrlKey: true }));
382
- expect(ctx.setActiveCell).toHaveBeenLastCalledWith(0, 0);
383
- });
384
- it('clearCells intent (Delete with non-empty range) wipes values', () => {
385
- const ctx = navCtx({ clearSelectedCells: vi.fn(() => false) });
386
- const { onGridKeyDown } = createKeyboard(ctx);
387
- onGridKeyDown(rootKeyEvent({ key: 'Delete' }));
388
- expect(ctx.clearSelectedCellValues).toHaveBeenCalled();
389
- expect(ctx.setActiveCell).not.toHaveBeenCalled();
390
- });
391
- });
392
- describe('onWindowKeydown', () => {
393
- it('Escape closes menus when a column menu is open', () => {
394
- const ctx = makeCtx({ columnMenuFor: 'col1' });
395
- const { onWindowKeydown } = createKeyboard(ctx);
396
- onWindowKeydown(new KeyboardEvent('keydown', { key: 'Escape' }));
397
- expect(ctx.closeMenus).toHaveBeenCalled();
398
- });
399
- it('Escape closes menus when an operator menu is open', () => {
400
- const ctx = makeCtx({ operatorMenuFor: 'op1' });
401
- const { onWindowKeydown } = createKeyboard(ctx);
402
- onWindowKeydown(new KeyboardEvent('keydown', { key: 'Escape' }));
403
- expect(ctx.closeMenus).toHaveBeenCalled();
404
- });
405
- it('does nothing when no menu is open', () => {
406
- const ctx = makeCtx();
407
- const { onWindowKeydown } = createKeyboard(ctx);
408
- onWindowKeydown(new KeyboardEvent('keydown', { key: 'Escape' }));
409
- expect(ctx.closeMenus).not.toHaveBeenCalled();
410
- });
411
- it('ignores non-Escape keys', () => {
412
- const ctx = makeCtx({ columnMenuFor: 'col1' });
413
- const { onWindowKeydown } = createKeyboard(ctx);
414
- onWindowKeydown(new KeyboardEvent('keydown', { key: 'Enter' }));
415
- expect(ctx.closeMenus).not.toHaveBeenCalled();
416
- });
417
- });
418
- describe('onHeaderSortClick', () => {
419
- function sortCtx(sorting = [], canSort = true) {
420
- const columns = [
421
- { id: 'name', getCanSort: () => canSort },
422
- { id: 'age', getCanSort: () => true },
423
- ];
424
- return makeCtx({ allColumns: columns, state: { sorting } });
425
- }
426
- it('ignores a click on a non-sortable column', () => {
427
- const ctx = sortCtx([], false);
428
- const { onHeaderSortClick } = createKeyboard(ctx);
429
- onHeaderSortClick(new MouseEvent('click'), 'name');
430
- expect(ctx._setState).not.toHaveBeenCalled();
431
- });
432
- it('ignores a click on an unknown column id', () => {
433
- const ctx = sortCtx();
434
- const { onHeaderSortClick } = createKeyboard(ctx);
435
- onHeaderSortClick(new MouseEvent('click'), 'nope');
436
- expect(ctx._setState).not.toHaveBeenCalled();
437
- });
438
- it('single-click on an unsorted column sorts ascending', () => {
439
- const ctx = sortCtx([]);
440
- const { onHeaderSortClick } = createKeyboard(ctx);
441
- onHeaderSortClick(new MouseEvent('click'), 'name');
442
- expect(ctx._state.sorting).toEqual([{ id: 'name', desc: false }]);
443
- });
444
- it('single-click cycles asc -> desc', () => {
445
- const ctx = sortCtx([{ id: 'name', desc: false }]);
446
- const { onHeaderSortClick } = createKeyboard(ctx);
447
- onHeaderSortClick(new MouseEvent('click'), 'name');
448
- expect(ctx._state.sorting).toEqual([{ id: 'name', desc: true }]);
449
- });
450
- it('single-click cycles desc -> none', () => {
451
- const ctx = sortCtx([{ id: 'name', desc: true }]);
452
- const { onHeaderSortClick } = createKeyboard(ctx);
453
- onHeaderSortClick(new MouseEvent('click'), 'name');
454
- expect(ctx._state.sorting).toEqual([]);
455
- });
456
- it('shift-click appends a new column to a multi-sort', () => {
457
- const ctx = sortCtx([{ id: 'age', desc: false }]);
458
- const { onHeaderSortClick } = createKeyboard(ctx);
459
- onHeaderSortClick(new MouseEvent('click', { shiftKey: true }), 'name');
460
- expect(ctx._state.sorting).toEqual([
461
- { id: 'age', desc: false },
462
- { id: 'name', desc: false },
463
- ]);
464
- });
465
- it('shift-click toggles an existing asc clause to desc', () => {
466
- const ctx = sortCtx([
467
- { id: 'age', desc: false },
468
- { id: 'name', desc: false },
469
- ]);
470
- const { onHeaderSortClick } = createKeyboard(ctx);
471
- onHeaderSortClick(new MouseEvent('click', { shiftKey: true }), 'name');
472
- expect(ctx._state.sorting).toEqual([
473
- { id: 'age', desc: false },
474
- { id: 'name', desc: true },
475
- ]);
476
- });
477
- it('shift-click removes a desc clause from the multi-sort', () => {
478
- const ctx = sortCtx([
479
- { id: 'age', desc: false },
480
- { id: 'name', desc: true },
481
- ]);
482
- const { onHeaderSortClick } = createKeyboard(ctx);
483
- onHeaderSortClick(new MouseEvent('click', { shiftKey: true }), 'name');
484
- expect(ctx._state.sorting).toEqual([{ id: 'age', desc: false }]);
485
- });
486
- it('handles a missing sorting array (defaults to [])', () => {
487
- const ctx = makeCtx({
488
- allColumns: [{ id: 'name', getCanSort: () => true }],
489
- state: { sorting: undefined },
490
- });
491
- const { onHeaderSortClick } = createKeyboard(ctx);
492
- onHeaderSortClick(new MouseEvent('click'), 'name');
493
- expect(ctx._state.sorting).toEqual([{ id: 'name', desc: false }]);
494
- });
495
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,27 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { getKeyboardIntent, getNextActiveCell } from './keyboard';
3
- describe('keyboard navigation engine', () => {
4
- it('maps keys to navigation intents', () => {
5
- expect(getKeyboardIntent(new KeyboardEvent('keydown', { key: 'ArrowLeft' }))).toBe('moveLeft');
6
- expect(getKeyboardIntent(new KeyboardEvent('keydown', { key: 'Home', ctrlKey: true }))).toBe('gridStart');
7
- expect(getKeyboardIntent(new KeyboardEvent('keydown', { key: ' ' }))).toBe('activate');
8
- });
9
- it('computes next active cell within bounds', () => {
10
- const next = getNextActiveCell({ rowIndex: 1, colIndex: 1, cellId: null }, 'moveDown', { maxRow: 2, maxCol: 3, pageSize: 10 });
11
- expect(next.rowIndex).toBe(2);
12
- expect(next.colIndex).toBe(1);
13
- });
14
- it('handles row and grid boundary intents', () => {
15
- const rowEnd = getNextActiveCell({ rowIndex: 2, colIndex: 1, cellId: null }, 'rowEnd', { maxRow: 2, maxCol: 4, pageSize: 2 });
16
- expect(rowEnd.colIndex).toBe(4);
17
- const gridStart = getNextActiveCell({ rowIndex: 2, colIndex: 4, cellId: null }, 'gridStart', { maxRow: 2, maxCol: 4, pageSize: 2 });
18
- expect(gridStart.rowIndex).toBe(0);
19
- expect(gridStart.colIndex).toBe(0);
20
- });
21
- it('clamps page navigation to valid bounds', () => {
22
- const pageUp = getNextActiveCell({ rowIndex: 1, colIndex: 0, cellId: null }, 'pageUp', { maxRow: 20, maxCol: 2, pageSize: 10 });
23
- const pageDown = getNextActiveCell({ rowIndex: 18, colIndex: 0, cellId: null }, 'pageDown', { maxRow: 20, maxCol: 2, pageSize: 10 });
24
- expect(pageUp.rowIndex).toBe(0);
25
- expect(pageDown.rowIndex).toBe(20);
26
- });
27
- });
@@ -1 +0,0 @@
1
- export {};