@svgrid/grid 1.1.1 → 1.2.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 (188) hide show
  1. package/dist/FlexRender.svelte +96 -96
  2. package/dist/SvGrid.controller.svelte.js +17 -6
  3. package/dist/SvGrid.css +2012 -2012
  4. package/dist/SvGrid.svelte +2571 -2346
  5. package/dist/SvGridDropdown.svelte +7 -3
  6. package/dist/build-api.js +2 -2
  7. package/dist/cdn/svgrid.js +13504 -0
  8. package/dist/cdn/svgrid.svelte-external.js +11188 -0
  9. package/dist/cell-values.d.ts +1 -1
  10. package/dist/cell-values.js +7 -7
  11. package/dist/column-types.js +1 -1
  12. package/dist/core.d.ts +1 -1
  13. package/dist/core.js +2 -2
  14. package/dist/summaries.js +4 -4
  15. package/package.json +12 -2
  16. package/src/FlexRender.svelte +96 -96
  17. package/src/SvGrid.controller.svelte.ts +2363 -2352
  18. package/src/SvGrid.css +2012 -2012
  19. package/src/SvGrid.svelte +2571 -2346
  20. package/src/SvGrid.types.ts +537 -537
  21. package/src/SvGridDropdown.svelte +7 -3
  22. package/src/a11y.contract.test.ts +49 -49
  23. package/src/a11y.test.ts +59 -59
  24. package/src/a11y.ts +59 -59
  25. package/src/build-api.ts +683 -683
  26. package/src/cell-formatting.ts +169 -169
  27. package/src/cell-values.ts +4 -4
  28. package/src/column-types.ts +1 -1
  29. package/src/core.performance.test.ts +30 -30
  30. package/src/core.ts +1077 -1077
  31. package/src/createGrid.svelte.ts +42 -42
  32. package/src/createGrid.test.ts +10 -10
  33. package/src/createGridState.svelte.ts +17 -17
  34. package/src/editing.ts +669 -669
  35. package/src/flex-render.ts +3 -3
  36. package/src/index.ts +208 -208
  37. package/src/keyboard.test.ts +59 -59
  38. package/src/keyboard.ts +97 -97
  39. package/src/merge-objects.ts +48 -48
  40. package/src/render-component.ts +28 -28
  41. package/src/spreadsheet.test.ts +489 -489
  42. package/src/spreadsheet.ts +304 -304
  43. package/src/static-functions.ts +11 -11
  44. package/src/subscribe.ts +38 -38
  45. package/src/summaries.ts +4 -4
  46. package/src/svgrid-wrapper.types.ts +412 -412
  47. package/src/svgrid.features.test.ts +157 -157
  48. package/src/svgrid.wrapper.test.ts +40 -40
  49. package/src/virtualization/column-virtualizer.test.ts +27 -27
  50. package/src/virtualization/column-virtualizer.ts +30 -30
  51. package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
  52. package/src/virtualization/types.ts +30 -30
  53. package/src/virtualization/virtualizer.test.ts +47 -47
  54. package/src/virtualization/virtualizer.ts +296 -296
  55. package/dist/SvGrid.helpers.test.d.ts +0 -1
  56. package/dist/SvGrid.helpers.test.js +0 -298
  57. package/dist/SvGridChart.test.d.ts +0 -1
  58. package/dist/SvGridChart.test.js +0 -62
  59. package/dist/a11y.contract.test.d.ts +0 -1
  60. package/dist/a11y.contract.test.js +0 -39
  61. package/dist/a11y.test.d.ts +0 -1
  62. package/dist/a11y.test.js +0 -44
  63. package/dist/aligned-grids.test.d.ts +0 -1
  64. package/dist/aligned-grids.test.js +0 -75
  65. package/dist/build-api.coverage.test.d.ts +0 -20
  66. package/dist/build-api.coverage.test.js +0 -505
  67. package/dist/cell-formatting.test.d.ts +0 -1
  68. package/dist/cell-formatting.test.js +0 -234
  69. package/dist/cell-render.test.d.ts +0 -1
  70. package/dist/cell-render.test.js +0 -338
  71. package/dist/chart-export.test.d.ts +0 -1
  72. package/dist/chart-export.test.js +0 -302
  73. package/dist/chart.coverage.test.d.ts +0 -1
  74. package/dist/chart.coverage.test.js +0 -748
  75. package/dist/chart.test.d.ts +0 -1
  76. package/dist/chart.test.js +0 -289
  77. package/dist/clipboard.test.d.ts +0 -1
  78. package/dist/clipboard.test.js +0 -700
  79. package/dist/collaboration.coverage.test.d.ts +0 -1
  80. package/dist/collaboration.coverage.test.js +0 -200
  81. package/dist/collaboration.test.d.ts +0 -1
  82. package/dist/collaboration.test.js +0 -65
  83. package/dist/column-groups.test.d.ts +0 -1
  84. package/dist/column-groups.test.js +0 -56
  85. package/dist/column-types.test.d.ts +0 -1
  86. package/dist/column-types.test.js +0 -62
  87. package/dist/columns.test.d.ts +0 -1
  88. package/dist/columns.test.js +0 -625
  89. package/dist/conditional-formatting.test.d.ts +0 -1
  90. package/dist/conditional-formatting.test.js +0 -87
  91. package/dist/core.coverage.test.d.ts +0 -1
  92. package/dist/core.coverage.test.js +0 -186
  93. package/dist/core.performance.test.d.ts +0 -1
  94. package/dist/core.performance.test.js +0 -29
  95. package/dist/createGrid.test.d.ts +0 -1
  96. package/dist/createGrid.test.js +0 -9
  97. package/dist/editing.test.d.ts +0 -1
  98. package/dist/editing.test.js +0 -732
  99. package/dist/editors/cell-editors.coverage.test.d.ts +0 -1
  100. package/dist/editors/cell-editors.coverage.test.js +0 -139
  101. package/dist/editors/cell-editors.test.d.ts +0 -1
  102. package/dist/editors/cell-editors.test.js +0 -75
  103. package/dist/facet-buckets.test.d.ts +0 -1
  104. package/dist/facet-buckets.test.js +0 -296
  105. package/dist/fill-patterns.test.d.ts +0 -1
  106. package/dist/fill-patterns.test.js +0 -81
  107. package/dist/filter-operators.test.d.ts +0 -1
  108. package/dist/filter-operators.test.js +0 -135
  109. package/dist/filtering/excel-filters.test.d.ts +0 -1
  110. package/dist/filtering/excel-filters.test.js +0 -116
  111. package/dist/filtering/locale-filter.test.d.ts +0 -13
  112. package/dist/filtering/locale-filter.test.js +0 -189
  113. package/dist/flex-render.test.d.ts +0 -1
  114. package/dist/flex-render.test.js +0 -142
  115. package/dist/group-aggregate.test.d.ts +0 -1
  116. package/dist/group-aggregate.test.js +0 -32
  117. package/dist/hyperformula-adapter.test.d.ts +0 -1
  118. package/dist/hyperformula-adapter.test.js +0 -205
  119. package/dist/keyboard-handlers.coverage.test.d.ts +0 -1
  120. package/dist/keyboard-handlers.coverage.test.js +0 -495
  121. package/dist/keyboard.test.d.ts +0 -1
  122. package/dist/keyboard.test.js +0 -27
  123. package/dist/menus.test.d.ts +0 -1
  124. package/dist/menus.test.js +0 -560
  125. package/dist/named-views.coverage.test.d.ts +0 -1
  126. package/dist/named-views.coverage.test.js +0 -180
  127. package/dist/named-views.test.d.ts +0 -1
  128. package/dist/named-views.test.js +0 -57
  129. package/dist/new-features.test.d.ts +0 -1
  130. package/dist/new-features.test.js +0 -183
  131. package/dist/render-component.test.d.ts +0 -1
  132. package/dist/render-component.test.js +0 -41
  133. package/dist/row-drag.test.d.ts +0 -1
  134. package/dist/row-drag.test.js +0 -142
  135. package/dist/row-resize.test.d.ts +0 -1
  136. package/dist/row-resize.test.js +0 -329
  137. package/dist/scroll-sync.test.d.ts +0 -1
  138. package/dist/scroll-sync.test.js +0 -290
  139. package/dist/selection.multi-range.test.d.ts +0 -1
  140. package/dist/selection.multi-range.test.js +0 -55
  141. package/dist/selection.test.d.ts +0 -1
  142. package/dist/selection.test.js +0 -647
  143. package/dist/server-data-source.coverage.test.d.ts +0 -1
  144. package/dist/server-data-source.coverage.test.js +0 -154
  145. package/dist/server-data-source.test.d.ts +0 -1
  146. package/dist/server-data-source.test.js +0 -81
  147. package/dist/sparkline.test.d.ts +0 -1
  148. package/dist/sparkline.test.js +0 -52
  149. package/dist/spreadsheet.test.d.ts +0 -1
  150. package/dist/spreadsheet.test.js +0 -446
  151. package/dist/subscribe.test.d.ts +0 -1
  152. package/dist/subscribe.test.js +0 -93
  153. package/dist/svgrid.api-extensions.test.d.ts +0 -14
  154. package/dist/svgrid.api-extensions.test.js +0 -334
  155. package/dist/svgrid.api.test.d.ts +0 -15
  156. package/dist/svgrid.api.test.js +0 -169
  157. package/dist/svgrid.behavior.test.d.ts +0 -15
  158. package/dist/svgrid.behavior.test.js +0 -625
  159. package/dist/svgrid.column-reorder.test.d.ts +0 -15
  160. package/dist/svgrid.column-reorder.test.js +0 -224
  161. package/dist/svgrid.comments-autocomplete.test.d.ts +0 -1
  162. package/dist/svgrid.comments-autocomplete.test.js +0 -96
  163. package/dist/svgrid.context-menu.test.d.ts +0 -1
  164. package/dist/svgrid.context-menu.test.js +0 -102
  165. package/dist/svgrid.features.test.d.ts +0 -1
  166. package/dist/svgrid.features.test.js +0 -129
  167. package/dist/svgrid.interaction.test.d.ts +0 -9
  168. package/dist/svgrid.interaction.test.js +0 -349
  169. package/dist/svgrid.locale-filtering.test.d.ts +0 -15
  170. package/dist/svgrid.locale-filtering.test.js +0 -261
  171. package/dist/svgrid.new-features.wrapper.test.d.ts +0 -1
  172. package/dist/svgrid.new-features.wrapper.test.js +0 -194
  173. package/dist/svgrid.row-pinning.test.d.ts +0 -14
  174. package/dist/svgrid.row-pinning.test.js +0 -250
  175. package/dist/svgrid.set-filter.test.d.ts +0 -17
  176. package/dist/svgrid.set-filter.test.js +0 -268
  177. package/dist/svgrid.wrapper.test.d.ts +0 -1
  178. package/dist/svgrid.wrapper.test.js +0 -59
  179. package/dist/test-setup.d.ts +0 -1
  180. package/dist/test-setup.js +0 -35
  181. package/dist/transaction.test.d.ts +0 -1
  182. package/dist/transaction.test.js +0 -89
  183. package/dist/virtualization/column-virtualizer.test.d.ts +0 -1
  184. package/dist/virtualization/column-virtualizer.test.js +0 -25
  185. package/dist/virtualization/scroll-scaling.test.d.ts +0 -1
  186. package/dist/virtualization/scroll-scaling.test.js +0 -127
  187. package/dist/virtualization/virtualizer.test.d.ts +0 -1
  188. package/dist/virtualization/virtualizer.test.js +0 -41
@@ -1,625 +0,0 @@
1
- /**
2
- * Unit tests for the column handlers produced by createColumns(ctx).
3
- *
4
- * createColumns is a factory: it closes over a mutable `ctx` handle (the
5
- * controller's reactive state in production) and returns imperative event
6
- * handlers. We drive it here with a plain fake ctx object so we can assert on
7
- * pinning / ordering / sizing / visibility logic without mounting the grid.
8
- */
9
- import { beforeEach, describe, expect, it, vi } from 'vitest';
10
- import { createColumns } from './columns';
11
- /** A minimally-populated ctx covering every field the handlers read/write. */
12
- function makeCtx(overrides = {}) {
13
- const allColumns = [
14
- { id: 'a', columnDef: { width: 120 } },
15
- { id: 'b', columnDef: {} },
16
- { id: 'c', columnDef: { width: 200 } },
17
- { id: 'd', columnDef: {} },
18
- ];
19
- const ctx = {
20
- allColumns,
21
- toolPanelColumns: allColumns.map((c) => ({ id: c.id })),
22
- props: {},
23
- userColumnOrder: undefined,
24
- pinnedOffsets: { left: {}, right: {} },
25
- columnPinning: { left: [], right: [] },
26
- hiddenColumns: {},
27
- columnWidths: {},
28
- fittedColumnWidths: {},
29
- MIN_COLUMN_WIDTH: 40,
30
- closeMenus: vi.fn(),
31
- // drag state
32
- colDragId: null,
33
- colDropOnId: null,
34
- colDropSide: null,
35
- // resize state
36
- resizingColumnId: null,
37
- resizeStartX: 0,
38
- resizeStartWidth: 0,
39
- resizePendingWidth: 0,
40
- resizeRaf: null,
41
- measureCanvas: null,
42
- gridRootEl: null,
43
- grid: {
44
- getAllColumns: () => allColumns,
45
- getState: () => ({ grouping: [] }),
46
- setGrouping: vi.fn(),
47
- },
48
- ...overrides,
49
- };
50
- return ctx;
51
- }
52
- describe('cellPinStyle', () => {
53
- it('returns a left sticky style when the column has a left offset', () => {
54
- const ctx = makeCtx({ pinnedOffsets: { left: { a: 0, b: 120 }, right: {} } });
55
- const h = createColumns(ctx);
56
- expect(h.cellPinStyle('a')).toBe('position: sticky; left: 0px; z-index: 30;');
57
- expect(h.cellPinStyle('b')).toBe('position: sticky; left: 120px; z-index: 30;');
58
- });
59
- it('returns a right sticky style when the column has a right offset', () => {
60
- const ctx = makeCtx({ pinnedOffsets: { left: {}, right: { d: 0 } } });
61
- const h = createColumns(ctx);
62
- expect(h.cellPinStyle('d')).toBe('position: sticky; right: 0px; z-index: 30;');
63
- });
64
- it('returns empty string for an unpinned column', () => {
65
- const h = createColumns(makeCtx());
66
- expect(h.cellPinStyle('a')).toBe('');
67
- });
68
- it('prefers the left offset when a column appears in both maps', () => {
69
- const ctx = makeCtx({ pinnedOffsets: { left: { a: 5 }, right: { a: 9 } } });
70
- const h = createColumns(ctx);
71
- expect(h.cellPinStyle('a')).toBe('position: sticky; left: 5px; z-index: 30;');
72
- });
73
- });
74
- describe('isColumnPinned', () => {
75
- it('reports left / right / null', () => {
76
- const ctx = makeCtx({ columnPinning: { left: ['a'], right: ['d'] } });
77
- const h = createColumns(ctx);
78
- expect(h.isColumnPinned('a')).toBe('left');
79
- expect(h.isColumnPinned('d')).toBe('right');
80
- expect(h.isColumnPinned('b')).toBeNull();
81
- });
82
- });
83
- describe('getCurrentColumnOrder / emitColumnOrder / setColumnOrderInternal', () => {
84
- it('reads the order from allColumns ids', () => {
85
- const h = createColumns(makeCtx());
86
- expect(h.getCurrentColumnOrder()).toEqual(['a', 'b', 'c', 'd']);
87
- });
88
- it('emitColumnOrder invokes the callback with the current order', () => {
89
- const onColumnOrderChange = vi.fn();
90
- const h = createColumns(makeCtx({ props: { onColumnOrderChange } }));
91
- h.emitColumnOrder();
92
- expect(onColumnOrderChange).toHaveBeenCalledWith(['a', 'b', 'c', 'd']);
93
- });
94
- it('emitColumnOrder is a no-op when no callback is provided', () => {
95
- const h = createColumns(makeCtx());
96
- expect(() => h.emitColumnOrder()).not.toThrow();
97
- });
98
- it('setColumnOrderInternal writes userColumnOrder and emits', () => {
99
- const onColumnOrderChange = vi.fn();
100
- const ctx = makeCtx({ props: { onColumnOrderChange } });
101
- const h = createColumns(ctx);
102
- h.setColumnOrderInternal(['d', 'c', 'b', 'a']);
103
- expect(ctx.userColumnOrder).toEqual(['d', 'c', 'b', 'a']);
104
- expect(onColumnOrderChange).toHaveBeenCalledWith(['a', 'b', 'c', 'd']);
105
- });
106
- });
107
- describe('applyColumnDrop', () => {
108
- let ctx;
109
- let h;
110
- beforeEach(() => {
111
- ctx = makeCtx();
112
- h = createColumns(ctx);
113
- });
114
- it('does nothing when dragId === targetId', () => {
115
- h.applyColumnDrop('a', 'a', 'before');
116
- expect(ctx.userColumnOrder).toBeUndefined();
117
- });
118
- it('drops before the target', () => {
119
- h.applyColumnDrop('a', 'c', 'before');
120
- expect(ctx.userColumnOrder).toEqual(['b', 'a', 'c', 'd']);
121
- });
122
- it('drops after the target', () => {
123
- h.applyColumnDrop('a', 'c', 'after');
124
- expect(ctx.userColumnOrder).toEqual(['b', 'c', 'a', 'd']);
125
- });
126
- it('moving the last column to the front', () => {
127
- h.applyColumnDrop('d', 'a', 'before');
128
- expect(ctx.userColumnOrder).toEqual(['d', 'a', 'b', 'c']);
129
- });
130
- it('does nothing when the target id is unknown', () => {
131
- h.applyColumnDrop('a', 'zzz', 'before');
132
- expect(ctx.userColumnOrder).toBeUndefined();
133
- });
134
- });
135
- describe('column header drag handlers', () => {
136
- function dragEvent() {
137
- return {
138
- preventDefault: vi.fn(),
139
- dataTransfer: { setData: vi.fn(), effectAllowed: '', dropEffect: '' },
140
- clientX: 0,
141
- currentTarget: {
142
- getBoundingClientRect: () => ({ left: 0, width: 100 }),
143
- },
144
- };
145
- }
146
- it('dragStart is gated off unless enableColumnReorder', () => {
147
- const ctx = makeCtx();
148
- const h = createColumns(ctx);
149
- const e = dragEvent();
150
- h.onColumnHeaderDragStart(e, 'a');
151
- expect(ctx.colDragId).toBeNull();
152
- expect(e.dataTransfer.setData).not.toHaveBeenCalled();
153
- });
154
- it('dragStart records the drag id when reorder is enabled', () => {
155
- const ctx = makeCtx({ props: { enableColumnReorder: true } });
156
- const h = createColumns(ctx);
157
- const e = dragEvent();
158
- h.onColumnHeaderDragStart(e, 'a');
159
- expect(ctx.colDragId).toBe('a');
160
- expect(e.dataTransfer.setData).toHaveBeenCalledWith('text/plain', 'a');
161
- expect(e.dataTransfer.effectAllowed).toBe('move');
162
- });
163
- it('dragOver does nothing when no drag is active or over self', () => {
164
- const ctx = makeCtx();
165
- const h = createColumns(ctx);
166
- const e = dragEvent();
167
- h.onColumnHeaderDragOver(e, 'b');
168
- expect(e.preventDefault).not.toHaveBeenCalled();
169
- ctx.colDragId = 'b';
170
- h.onColumnHeaderDragOver(e, 'b'); // over the dragged column itself
171
- expect(e.preventDefault).not.toHaveBeenCalled();
172
- });
173
- it('dragOver sets side=before on the left half', () => {
174
- const ctx = makeCtx({ colDragId: 'a' });
175
- const h = createColumns(ctx);
176
- const e = dragEvent();
177
- e.clientX = 10; // < left + width/2 (50)
178
- h.onColumnHeaderDragOver(e, 'b');
179
- expect(e.preventDefault).toHaveBeenCalled();
180
- expect(ctx.colDropSide).toBe('before');
181
- expect(ctx.colDropOnId).toBe('b');
182
- });
183
- it('dragOver sets side=after on the right half', () => {
184
- const ctx = makeCtx({ colDragId: 'a' });
185
- const h = createColumns(ctx);
186
- const e = dragEvent();
187
- e.clientX = 80; // > 50
188
- h.onColumnHeaderDragOver(e, 'b');
189
- expect(ctx.colDropSide).toBe('after');
190
- });
191
- it('dragLeave clears state only for the matching column', () => {
192
- const ctx = makeCtx({ colDropOnId: 'b', colDropSide: 'after' });
193
- const h = createColumns(ctx);
194
- h.onColumnHeaderDragLeave('a'); // different column - no change
195
- expect(ctx.colDropOnId).toBe('b');
196
- h.onColumnHeaderDragLeave('b');
197
- expect(ctx.colDropOnId).toBeNull();
198
- expect(ctx.colDropSide).toBeNull();
199
- });
200
- it('drop applies the pending reorder then clears drag state', () => {
201
- const ctx = makeCtx({ colDragId: 'a', colDropSide: 'after' });
202
- const h = createColumns(ctx);
203
- const e = dragEvent();
204
- h.onColumnHeaderDrop(e, 'c');
205
- expect(e.preventDefault).toHaveBeenCalled();
206
- expect(ctx.userColumnOrder).toEqual(['b', 'c', 'a', 'd']);
207
- expect(ctx.colDragId).toBeNull();
208
- expect(ctx.colDropOnId).toBeNull();
209
- expect(ctx.colDropSide).toBeNull();
210
- });
211
- it('drop without an active drag just clears state', () => {
212
- const ctx = makeCtx();
213
- const h = createColumns(ctx);
214
- const e = dragEvent();
215
- h.onColumnHeaderDrop(e, 'c');
216
- expect(ctx.userColumnOrder).toBeUndefined();
217
- expect(ctx.colDragId).toBeNull();
218
- });
219
- it('dragEnd clears all drag state', () => {
220
- const ctx = makeCtx({ colDragId: 'a', colDropOnId: 'b', colDropSide: 'after' });
221
- const h = createColumns(ctx);
222
- h.onColumnHeaderDragEnd();
223
- expect(ctx.colDragId).toBeNull();
224
- expect(ctx.colDropOnId).toBeNull();
225
- expect(ctx.colDropSide).toBeNull();
226
- });
227
- });
228
- describe('pinning handlers', () => {
229
- it('pinColumnLeft appends to left, removes from right, closes menus', () => {
230
- const ctx = makeCtx({ columnPinning: { left: ['a'], right: ['b'] } });
231
- const h = createColumns(ctx);
232
- h.pinColumnLeft('b');
233
- expect(ctx.columnPinning).toEqual({ left: ['a', 'b'], right: [] });
234
- expect(ctx.closeMenus).toHaveBeenCalled();
235
- });
236
- it('pinColumnLeft de-duplicates a column already pinned left', () => {
237
- const ctx = makeCtx({ columnPinning: { left: ['a', 'b'], right: [] } });
238
- const h = createColumns(ctx);
239
- h.pinColumnLeft('a');
240
- expect(ctx.columnPinning).toEqual({ left: ['b', 'a'], right: [] });
241
- });
242
- it('pinColumnRight prepends to right, removes from left', () => {
243
- const ctx = makeCtx({ columnPinning: { left: ['a'], right: ['c'] } });
244
- const h = createColumns(ctx);
245
- h.pinColumnRight('a');
246
- expect(ctx.columnPinning).toEqual({ left: [], right: ['a', 'c'] });
247
- expect(ctx.closeMenus).toHaveBeenCalled();
248
- });
249
- it('unpinColumn removes from both sides', () => {
250
- const ctx = makeCtx({ columnPinning: { left: ['a'], right: ['a'] } });
251
- const h = createColumns(ctx);
252
- h.unpinColumn('a');
253
- expect(ctx.columnPinning).toEqual({ left: [], right: [] });
254
- expect(ctx.closeMenus).toHaveBeenCalled();
255
- });
256
- });
257
- describe('toggleColumnVisibleInPanel', () => {
258
- it('hides a visible column', () => {
259
- const ctx = makeCtx();
260
- const h = createColumns(ctx);
261
- h.toggleColumnVisibleInPanel('a');
262
- expect(ctx.hiddenColumns).toEqual({ a: true });
263
- });
264
- it('shows a hidden column (removes the key)', () => {
265
- const ctx = makeCtx({ hiddenColumns: { a: true, b: true } });
266
- const h = createColumns(ctx);
267
- h.toggleColumnVisibleInPanel('a');
268
- expect(ctx.hiddenColumns).toEqual({ b: true });
269
- });
270
- });
271
- describe('moveColumnInPanel', () => {
272
- it('moves a column down (dir +1)', () => {
273
- const ctx = makeCtx();
274
- const h = createColumns(ctx);
275
- h.moveColumnInPanel('a', 1);
276
- expect(ctx.userColumnOrder).toEqual(['b', 'a', 'c', 'd']);
277
- });
278
- it('moves a column up (dir -1)', () => {
279
- const ctx = makeCtx();
280
- const h = createColumns(ctx);
281
- h.moveColumnInPanel('c', -1);
282
- expect(ctx.userColumnOrder).toEqual(['a', 'c', 'b', 'd']);
283
- });
284
- it('refuses to move the first column up (out of bounds)', () => {
285
- const ctx = makeCtx();
286
- const h = createColumns(ctx);
287
- h.moveColumnInPanel('a', -1);
288
- expect(ctx.userColumnOrder).toBeUndefined();
289
- });
290
- it('refuses to move the last column down (out of bounds)', () => {
291
- const ctx = makeCtx();
292
- const h = createColumns(ctx);
293
- h.moveColumnInPanel('d', 1);
294
- expect(ctx.userColumnOrder).toBeUndefined();
295
- });
296
- it('does nothing for an unknown column id', () => {
297
- const ctx = makeCtx();
298
- const h = createColumns(ctx);
299
- h.moveColumnInPanel('zzz', 1);
300
- expect(ctx.userColumnOrder).toBeUndefined();
301
- });
302
- });
303
- describe('toggleGroupInPanel', () => {
304
- it('adds a column to grouping when absent', () => {
305
- const setGrouping = vi.fn();
306
- const ctx = makeCtx();
307
- ctx.grid.getState = () => ({ grouping: ['x'] });
308
- ctx.grid.setGrouping = setGrouping;
309
- const h = createColumns(ctx);
310
- h.toggleGroupInPanel('a');
311
- expect(setGrouping).toHaveBeenCalledWith(['x', 'a']);
312
- });
313
- it('removes a column from grouping when present', () => {
314
- const setGrouping = vi.fn();
315
- const ctx = makeCtx();
316
- ctx.grid.getState = () => ({ grouping: ['x', 'a'] });
317
- ctx.grid.setGrouping = setGrouping;
318
- const h = createColumns(ctx);
319
- h.toggleGroupInPanel('a');
320
- expect(setGrouping).toHaveBeenCalledWith(['x']);
321
- });
322
- it('handles a missing grouping array (defaults to empty)', () => {
323
- const setGrouping = vi.fn();
324
- const ctx = makeCtx();
325
- ctx.grid.getState = () => ({});
326
- ctx.grid.setGrouping = setGrouping;
327
- const h = createColumns(ctx);
328
- h.toggleGroupInPanel('a');
329
- expect(setGrouping).toHaveBeenCalledWith(['a']);
330
- });
331
- });
332
- describe('getColumnBaseWidth', () => {
333
- it('returns an explicit override from columnWidths', () => {
334
- const ctx = makeCtx({ columnWidths: { a: 333 } });
335
- const h = createColumns(ctx);
336
- expect(h.getColumnBaseWidth('a')).toBe(333);
337
- });
338
- it('falls back to the columnDef width', () => {
339
- const h = createColumns(makeCtx());
340
- expect(h.getColumnBaseWidth('c')).toBe(200);
341
- });
342
- it('falls back to props.columnWidth when neither override nor def width set', () => {
343
- const ctx = makeCtx({ props: { columnWidth: 99 } });
344
- const h = createColumns(ctx);
345
- expect(h.getColumnBaseWidth('b')).toBe(99);
346
- });
347
- it('falls back to the hard default 140 with no config', () => {
348
- const h = createColumns(makeCtx());
349
- expect(h.getColumnBaseWidth('b')).toBe(140);
350
- });
351
- it('returns the default for an unknown column id', () => {
352
- const h = createColumns(makeCtx());
353
- expect(h.getColumnBaseWidth('zzz')).toBe(140);
354
- });
355
- });
356
- describe('getColumnWidth', () => {
357
- it('uses the fitted width when no explicit override exists', () => {
358
- const ctx = makeCtx({ fittedColumnWidths: { a: 77 } });
359
- const h = createColumns(ctx);
360
- expect(h.getColumnWidth('a')).toBe(77);
361
- });
362
- it('prefers an explicit override over the fitted width', () => {
363
- const ctx = makeCtx({
364
- fittedColumnWidths: { a: 77 },
365
- columnWidths: { a: 300 },
366
- });
367
- const h = createColumns(ctx);
368
- expect(h.getColumnWidth('a')).toBe(300);
369
- });
370
- it('falls through to base width when there is no fitted width', () => {
371
- const h = createColumns(makeCtx());
372
- expect(h.getColumnWidth('c')).toBe(200);
373
- });
374
- });
375
- describe('column resize lifecycle', () => {
376
- let rafCb;
377
- beforeEach(() => {
378
- rafCb = null;
379
- vi.stubGlobal('requestAnimationFrame', (cb) => {
380
- rafCb = cb;
381
- return 1;
382
- });
383
- vi.stubGlobal('cancelAnimationFrame', vi.fn());
384
- });
385
- it('startColumnResize seeds the resize state and registers listeners', () => {
386
- const ctx = makeCtx({ columnWidths: { a: 150 } });
387
- const h = createColumns(ctx);
388
- const addSpy = vi.spyOn(document, 'addEventListener');
389
- const e = {
390
- stopPropagation: vi.fn(),
391
- preventDefault: vi.fn(),
392
- clientX: 500,
393
- };
394
- h.startColumnResize(e, 'a');
395
- expect(ctx.resizingColumnId).toBe('a');
396
- expect(ctx.resizeStartX).toBe(500);
397
- expect(ctx.resizeStartWidth).toBe(150);
398
- expect(addSpy).toHaveBeenCalledWith('pointermove', expect.any(Function));
399
- addSpy.mockRestore();
400
- });
401
- it('onColumnResizeMove bails when no column is resizing', () => {
402
- const ctx = makeCtx({ resizingColumnId: null });
403
- const h = createColumns(ctx);
404
- h.onColumnResizeMove({ clientX: 100 });
405
- expect(ctx.resizePendingWidth).toBe(0);
406
- });
407
- it('onColumnResizeMove clamps to MIN_COLUMN_WIDTH and commits on the frame', () => {
408
- const ctx = makeCtx({
409
- resizingColumnId: 'a',
410
- resizeStartX: 100,
411
- resizeStartWidth: 120,
412
- });
413
- const h = createColumns(ctx);
414
- // moving far left should clamp at MIN_COLUMN_WIDTH (40)
415
- h.onColumnResizeMove({ clientX: -1000 });
416
- expect(ctx.resizePendingWidth).toBe(40);
417
- expect(rafCb).not.toBeNull();
418
- rafCb(0);
419
- expect(ctx.columnWidths.a).toBe(40);
420
- });
421
- it('onColumnResizeMove grows the column and coalesces multiple moves into one frame', () => {
422
- const ctx = makeCtx({
423
- resizingColumnId: 'a',
424
- resizeStartX: 100,
425
- resizeStartWidth: 120,
426
- });
427
- const h = createColumns(ctx);
428
- h.onColumnResizeMove({ clientX: 150 }); // +50 -> 170, schedules raf
429
- const firstCb = rafCb;
430
- h.onColumnResizeMove({ clientX: 200 }); // +100 -> 220, raf already pending
431
- expect(ctx.resizePendingWidth).toBe(220);
432
- expect(rafCb).toBe(firstCb); // not rescheduled
433
- rafCb(0);
434
- expect(ctx.columnWidths.a).toBe(220);
435
- });
436
- it('onColumnResizeMove skips the commit if the column stopped resizing mid-frame', () => {
437
- const ctx = makeCtx({
438
- resizingColumnId: 'a',
439
- resizeStartX: 100,
440
- resizeStartWidth: 120,
441
- });
442
- const h = createColumns(ctx);
443
- h.onColumnResizeMove({ clientX: 150 });
444
- ctx.resizingColumnId = null; // resize ended before the frame ran
445
- rafCb(0);
446
- expect(ctx.columnWidths.a).toBeUndefined();
447
- });
448
- it('endColumnResize commits the final pending width and tears down listeners', () => {
449
- const ctx = makeCtx({
450
- resizingColumnId: 'a',
451
- resizePendingWidth: 222,
452
- resizeRaf: 9,
453
- });
454
- const h = createColumns(ctx);
455
- const removeSpy = vi.spyOn(document, 'removeEventListener');
456
- h.endColumnResize();
457
- expect(ctx.columnWidths.a).toBe(222);
458
- expect(ctx.resizingColumnId).toBeNull();
459
- expect(ctx.resizeRaf).toBeNull();
460
- expect(removeSpy).toHaveBeenCalledWith('pointermove', expect.any(Function));
461
- removeSpy.mockRestore();
462
- });
463
- it('endColumnResize is safe when nothing was resizing', () => {
464
- const ctx = makeCtx();
465
- const h = createColumns(ctx);
466
- expect(() => h.endColumnResize()).not.toThrow();
467
- expect(ctx.columnWidths).toEqual({});
468
- });
469
- });
470
- describe('measureText', () => {
471
- it('returns 0 for empty text', () => {
472
- const h = createColumns(makeCtx());
473
- expect(h.measureText('', '16px sans-serif')).toBe(0);
474
- });
475
- it('measures via a lazily-created canvas and caches it', () => {
476
- const fakeCtx2d = { font: '', measureText: vi.fn(() => ({ width: 42 })) };
477
- const fakeCanvas = { getContext: vi.fn(() => fakeCtx2d) };
478
- const createElement = vi
479
- .spyOn(document, 'createElement')
480
- .mockReturnValue(fakeCanvas);
481
- const ctx = makeCtx();
482
- const h = createColumns(ctx);
483
- expect(h.measureText('hello', '16px sans-serif')).toBe(42);
484
- expect(ctx.measureCanvas).toBe(fakeCanvas);
485
- // second call reuses the cached canvas
486
- h.measureText('again', '16px sans-serif');
487
- expect(createElement).toHaveBeenCalledTimes(1);
488
- createElement.mockRestore();
489
- });
490
- it('returns 0 when the 2d context is unavailable', () => {
491
- const fakeCanvas = { getContext: vi.fn(() => null) };
492
- const createElement = vi
493
- .spyOn(document, 'createElement')
494
- .mockReturnValue(fakeCanvas);
495
- const h = createColumns(makeCtx());
496
- expect(h.measureText('hello', '16px sans-serif')).toBe(0);
497
- createElement.mockRestore();
498
- });
499
- });
500
- describe('autosizeColumn / autosizeAllColumns', () => {
501
- // jsdom doesn't provide the global CSS object; autosizeColumn uses
502
- // CSS.escape to build attribute selectors. Provide a minimal stub.
503
- beforeEach(() => {
504
- if (typeof globalThis.CSS === 'undefined') {
505
- ;
506
- globalThis.CSS = { escape: (s) => s };
507
- }
508
- else if (typeof globalThis.CSS.escape !== 'function') {
509
- ;
510
- globalThis.CSS.escape = (s) => s;
511
- }
512
- });
513
- function fakeRoot(opts) {
514
- const { sampleCell, header, cells = [] } = opts;
515
- return {
516
- querySelector: (sel) => {
517
- if (sel.startsWith('[data-col-id='))
518
- return sampleCell ?? null;
519
- if (sel.startsWith('[data-svgrid-header-col='))
520
- return header ?? null;
521
- return null;
522
- },
523
- querySelectorAll: () => cells,
524
- };
525
- }
526
- it('bails when there is no grid root element', () => {
527
- const ctx = makeCtx({ gridRootEl: null });
528
- const h = createColumns(ctx);
529
- expect(() => h.autosizeColumn('a')).not.toThrow();
530
- expect(ctx.columnWidths).toEqual({});
531
- });
532
- it('bails when no sample cell is found', () => {
533
- const ctx = makeCtx({ gridRootEl: fakeRoot({ sampleCell: null }) });
534
- const h = createColumns(ctx);
535
- h.autosizeColumn('a');
536
- expect(ctx.columnWidths).toEqual({});
537
- });
538
- it('snaps the width to the widest measured content', () => {
539
- // header label is 100px wide, one body cell is 200px wide.
540
- const labelEl = { textContent: 'Header' };
541
- const header = {
542
- querySelector: () => labelEl,
543
- };
544
- const sampleCell = { textContent: 'cell' };
545
- const bodyCell = { textContent: 'a very wide cell value' };
546
- const ctx = makeCtx({
547
- gridRootEl: fakeRoot({ sampleCell, header, cells: [bodyCell] }),
548
- });
549
- const h = createColumns(ctx);
550
- // stub getComputedStyle so .font is defined for measureText calls
551
- const gcs = vi
552
- .spyOn(window, 'getComputedStyle')
553
- .mockReturnValue({ font: '16px sans-serif' });
554
- // Make measureText deterministic: header label -> 100, body cell -> 200.
555
- const fakeCtx2d = {
556
- font: '',
557
- measureText: (t) => ({ width: t === 'Header' ? 100 : 200 }),
558
- };
559
- const createElement = vi
560
- .spyOn(document, 'createElement')
561
- .mockReturnValue({ getContext: () => fakeCtx2d });
562
- h.autosizeColumn('a');
563
- // widest is the 200px body cell -> 200 + 24 padding
564
- expect(ctx.columnWidths.a).toBe(224);
565
- gcs.mockRestore();
566
- createElement.mockRestore();
567
- });
568
- it('floors at MIN_COLUMN_WIDTH (+24 padding) when content measures zero', () => {
569
- // No label element -> the header itself is used as the measure target
570
- // (covering the `labelEl ?? header` fallback branch). Every measureText
571
- // returns 0, so max stays at MIN_COLUMN_WIDTH and the +24 padding is added.
572
- const header = { querySelector: () => null };
573
- const sampleCell = { textContent: '' };
574
- const ctx = makeCtx({
575
- MIN_COLUMN_WIDTH: 80,
576
- gridRootEl: fakeRoot({ sampleCell, header, cells: [] }),
577
- });
578
- const h = createColumns(ctx);
579
- const gcs = vi
580
- .spyOn(window, 'getComputedStyle')
581
- .mockReturnValue({ font: '16px sans-serif' });
582
- const createElement = vi.spyOn(document, 'createElement').mockReturnValue({
583
- getContext: () => ({ font: '', measureText: () => ({ width: 0 }) }),
584
- });
585
- h.autosizeColumn('a');
586
- // max = MIN(80); width = max(80, ceil(80) + 24) = 104
587
- expect(ctx.columnWidths.a).toBe(104);
588
- gcs.mockRestore();
589
- createElement.mockRestore();
590
- });
591
- it('autosizeAllColumns iterates every column and writes each width', () => {
592
- // Every querySelector returns a cell, every measured width is 50.
593
- const cell = { textContent: 'cell' };
594
- const header = { querySelector: () => ({ textContent: 'H' }) };
595
- const ctx = makeCtx({
596
- gridRootEl: fakeRoot({ sampleCell: cell, header, cells: [cell] }),
597
- });
598
- const h = createColumns(ctx);
599
- const gcs = vi
600
- .spyOn(window, 'getComputedStyle')
601
- .mockReturnValue({ font: '16px sans-serif' });
602
- const createElement = vi.spyOn(document, 'createElement').mockReturnValue({
603
- getContext: () => ({ font: '', measureText: () => ({ width: 50 }) }),
604
- });
605
- h.autosizeAllColumns();
606
- // a width was computed and stored for every column id
607
- expect(Object.keys(ctx.columnWidths).sort()).toEqual(['a', 'b', 'c', 'd']);
608
- gcs.mockRestore();
609
- createElement.mockRestore();
610
- });
611
- });
612
- describe('resetColumns', () => {
613
- it('clears widths, hidden columns and pinning', () => {
614
- const ctx = makeCtx({
615
- columnWidths: { a: 10 },
616
- hiddenColumns: { b: true },
617
- columnPinning: { left: ['a'], right: ['d'] },
618
- });
619
- const h = createColumns(ctx);
620
- h.resetColumns();
621
- expect(ctx.columnWidths).toEqual({});
622
- expect(ctx.hiddenColumns).toEqual({});
623
- expect(ctx.columnPinning).toEqual({ left: [], right: [] });
624
- });
625
- });
@@ -1 +0,0 @@
1
- export {};