@svgrid/grid 2.2.27 → 2.2.29

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 (248) hide show
  1. package/README.md +199 -199
  2. package/dist/FlexRender.svelte +96 -96
  3. package/dist/GridFooter.svelte +179 -179
  4. package/dist/GridMenus.svelte +782 -734
  5. package/dist/SvAutoComplete.svelte +165 -165
  6. package/dist/SvCalendar.svelte +503 -503
  7. package/dist/SvCarousel.svelte +141 -141
  8. package/dist/SvCheckBox.svelte +101 -101
  9. package/dist/SvColorInput.svelte +181 -181
  10. package/dist/SvComboBox.svelte +275 -275
  11. package/dist/SvContextMenu.svelte +116 -116
  12. package/dist/SvCountryInput.svelte +160 -160
  13. package/dist/SvDrawer.svelte +254 -250
  14. package/dist/SvDropDownList.svelte +374 -374
  15. package/dist/SvDurationInput.svelte +126 -126
  16. package/dist/SvField.svelte +293 -293
  17. package/dist/SvForm.svelte +437 -437
  18. package/dist/SvGrid.controller.svelte.d.ts +14 -1
  19. package/dist/SvGrid.controller.svelte.js +112 -28
  20. package/dist/SvGrid.css +2704 -2723
  21. package/dist/SvGrid.svelte +3361 -3333
  22. package/dist/SvGrid.types.d.ts +20 -5
  23. package/dist/SvGridChart.svelte +1724 -1724
  24. package/dist/SvGridChartPanel.svelte +485 -485
  25. package/dist/SvGridChartView.svelte +70 -70
  26. package/dist/SvGridDropdown.svelte +696 -696
  27. package/dist/SvGridSelect.svelte +259 -259
  28. package/dist/SvGroupCell.svelte +109 -109
  29. package/dist/SvListBox.svelte +326 -287
  30. package/dist/SvListBox.svelte.d.ts +10 -1
  31. package/dist/SvMaskedInput.svelte +122 -122
  32. package/dist/SvMenu.svelte +124 -124
  33. package/dist/SvMenuList.svelte +146 -146
  34. package/dist/SvMultiSelect.svelte +281 -281
  35. package/dist/SvNumberInput.svelte +168 -168
  36. package/dist/SvOtpInput.svelte +158 -158
  37. package/dist/SvPasswordInput.svelte +151 -151
  38. package/dist/SvPhoneInput.svelte +133 -131
  39. package/dist/SvPopover.svelte +197 -197
  40. package/dist/SvRadioGroup.svelte +107 -107
  41. package/dist/SvRowGroupPanel.svelte +170 -170
  42. package/dist/SvScrollArea.svelte +61 -61
  43. package/dist/SvSlider.svelte +200 -200
  44. package/dist/SvSwitchButton.svelte +107 -107
  45. package/dist/SvTagsInput.svelte +112 -112
  46. package/dist/SvTextInput.svelte +147 -147
  47. package/dist/SvTimePicker.svelte +245 -245
  48. package/dist/SvToaster.svelte +159 -159
  49. package/dist/SvToggleButton.svelte +85 -85
  50. package/dist/SvTooltip.svelte +161 -161
  51. package/dist/SvTour.svelte +204 -204
  52. package/dist/SvTree.svelte +437 -437
  53. package/dist/SvTreeSelect.svelte +233 -233
  54. package/dist/cdn/GridMenus-Da_p7SKS.js +486 -0
  55. package/dist/cdn/GridMenus-Ds3OpzZT.js +490 -0
  56. package/dist/cdn/{SvDateTimePicker-B1sDc9Pz.js → SvDateTimePicker-B8GopjhC.js} +2 -2
  57. package/dist/cdn/{SvDateTimePicker-Bh0FTs1E.js → SvDateTimePicker-DB0aIEk8.js} +2 -2
  58. package/dist/cdn/{SvGridChart-Bgk4hDs8.js → SvGridChart-BcLCYvw9.js} +1 -1
  59. package/dist/cdn/{SvGridChart-BKQsO5fZ.js → SvGridChart-BhnFdLd0.js} +1 -1
  60. package/dist/cdn/{SvGridChartPanel-DkCc2Z7b.js → SvGridChartPanel-Cmx9DHAS.js} +1 -1
  61. package/dist/cdn/{SvGridChartPanel-DSkHIQDd.js → SvGridChartPanel-DvjwxLvK.js} +1 -1
  62. package/dist/cdn/{SvGridChartView-BrbVaELm.js → SvGridChartView-B40DTJSX.js} +1 -1
  63. package/dist/cdn/{SvGridChartView-i9rWvSKg.js → SvGridChartView-PwqI4s02.js} +1 -1
  64. package/dist/cdn/{SvGridDropdown-D08sVmAy.js → SvGridDropdown-DjALc_4f.js} +1 -1
  65. package/dist/cdn/{SvGridDropdown-CGxxopQ4.js → SvGridDropdown-hpnHvOGt.js} +1 -1
  66. package/dist/cdn/{src-cgs1wcUS.js → src-BLJfdyL0.js} +4034 -3889
  67. package/dist/cdn/{src-Cw8d9U8j.js → src-DYDCiC0D.js} +7053 -6908
  68. package/dist/cdn/svgrid.js +9 -9
  69. package/dist/cdn/svgrid.svelte-external.js +9 -9
  70. package/dist/chart-export.js +8 -8
  71. package/dist/column-groups.js +8 -10
  72. package/dist/column-id.d.ts +9 -0
  73. package/dist/column-id.js +25 -0
  74. package/dist/core.js +2 -1
  75. package/dist/createListbox.svelte.d.ts +15 -2
  76. package/dist/createListbox.svelte.js +27 -6
  77. package/dist/editing.d.ts +1 -0
  78. package/dist/editing.js +45 -10
  79. package/dist/filtering/excel-filters.d.ts +30 -10
  80. package/dist/filtering/excel-filters.js +96 -15
  81. package/dist/index.d.ts +2 -2
  82. package/dist/index.js +2 -2
  83. package/dist/menus.d.ts +2 -0
  84. package/dist/menus.js +25 -0
  85. package/dist/row-resize.js +40 -0
  86. package/dist/selection.js +1 -1
  87. package/package.json +10 -10
  88. package/src/FlexRender.svelte +96 -96
  89. package/src/GridFooter.svelte +179 -179
  90. package/src/GridMenus.svelte +782 -734
  91. package/src/SvAutoComplete.svelte +165 -165
  92. package/src/SvCalendar.svelte +503 -503
  93. package/src/SvCalendar.test.ts +226 -226
  94. package/src/SvCarousel.svelte +141 -141
  95. package/src/SvCheckBox.svelte +101 -101
  96. package/src/SvColorInput.svelte +181 -181
  97. package/src/SvComboBox.svelte +275 -275
  98. package/src/SvContextMenu.svelte +116 -116
  99. package/src/SvCountryInput.svelte +160 -160
  100. package/src/SvDrawer.svelte +254 -250
  101. package/src/SvDrawer.test.ts +18 -1
  102. package/src/SvDropDownList.svelte +374 -374
  103. package/src/SvDurationInput.svelte +126 -126
  104. package/src/SvField.svelte +293 -293
  105. package/src/SvForm.svelte +437 -437
  106. package/src/SvForm.test.ts +411 -411
  107. package/src/SvGrid.controller.svelte.ts +3335 -3239
  108. package/src/SvGrid.css +2704 -2723
  109. package/src/SvGrid.svelte +3361 -3333
  110. package/src/SvGrid.types.ts +1628 -1613
  111. package/src/SvGridChart.svelte +1724 -1724
  112. package/src/SvGridChartPanel.svelte +485 -485
  113. package/src/SvGridChartView.svelte +70 -70
  114. package/src/SvGridDropdown.svelte +696 -696
  115. package/src/SvGridSelect.svelte +259 -259
  116. package/src/SvGroupCell.svelte +109 -109
  117. package/src/SvListBox.svelte +326 -287
  118. package/src/SvMaskedInput.svelte +122 -122
  119. package/src/SvMenu.svelte +124 -124
  120. package/src/SvMenu.test.ts +97 -97
  121. package/src/SvMenuList.svelte +146 -146
  122. package/src/SvMultiSelect.svelte +281 -281
  123. package/src/SvNavPane.test.ts +15 -4
  124. package/src/SvNumberInput.svelte +168 -168
  125. package/src/SvOtpInput.svelte +158 -158
  126. package/src/SvPasswordInput.svelte +151 -151
  127. package/src/SvPhoneInput.svelte +133 -131
  128. package/src/SvPopover.svelte +197 -197
  129. package/src/SvRadioGroup.svelte +107 -107
  130. package/src/SvRowGroupPanel.svelte +170 -170
  131. package/src/SvScrollArea.svelte +61 -61
  132. package/src/SvSlider.svelte +200 -200
  133. package/src/SvSwitchButton.svelte +107 -107
  134. package/src/SvTagsInput.svelte +112 -112
  135. package/src/SvTextInput.svelte +147 -147
  136. package/src/SvTimePicker.svelte +245 -245
  137. package/src/SvToaster.svelte +159 -159
  138. package/src/SvToaster.test.ts +95 -95
  139. package/src/SvToggleButton.svelte +85 -85
  140. package/src/SvTooltip.svelte +161 -161
  141. package/src/SvTour.svelte +204 -204
  142. package/src/SvTree.svelte +437 -437
  143. package/src/SvTreeSelect.svelte +233 -233
  144. package/src/a11y/dismissable.test.ts +119 -119
  145. package/src/a11y/dismissable.ts +114 -114
  146. package/src/a11y.contract.test.ts +49 -49
  147. package/src/a11y.test.ts +59 -59
  148. package/src/a11y.ts +61 -61
  149. package/src/ai.test.ts +502 -502
  150. package/src/ai.ts +1391 -1391
  151. package/src/build-api.coverage.test.ts +633 -633
  152. package/src/build-api.ts +895 -895
  153. package/src/builtin-editors.grid.test.ts +85 -85
  154. package/src/cell-formatting.ts +169 -169
  155. package/src/cell-render.ts +469 -469
  156. package/src/chart-export.ts +201 -201
  157. package/src/chart-view.svelte.ts +36 -36
  158. package/src/chart.ts +2296 -2296
  159. package/src/collaboration.test.ts +104 -104
  160. package/src/collaboration.ts +167 -167
  161. package/src/column-groups.test.ts +48 -0
  162. package/src/column-groups.ts +8 -10
  163. package/src/column-id.ts +30 -0
  164. package/src/core.performance.test.ts +30 -30
  165. package/src/core.ts +1112 -1111
  166. package/src/createAutocomplete.svelte.ts +132 -132
  167. package/src/createCombobox.svelte.ts +191 -191
  168. package/src/createCountryInput.svelte.ts +157 -157
  169. package/src/createDropdownList.svelte.ts +168 -168
  170. package/src/createForm.svelte.ts +386 -386
  171. package/src/createGrid.svelte.ts +42 -42
  172. package/src/createGrid.test.ts +10 -10
  173. package/src/createGridState.svelte.ts +17 -17
  174. package/src/createListbox.svelte.ts +39 -7
  175. package/src/createMenu.svelte.ts +225 -225
  176. package/src/createPopoverSelect.svelte.ts +213 -213
  177. package/src/createSlider.svelte.ts +191 -191
  178. package/src/createTooltip.svelte.ts +144 -144
  179. package/src/createTree.svelte.ts +319 -319
  180. package/src/editing.test.ts +949 -859
  181. package/src/editing.ts +709 -675
  182. package/src/editor-contract.ts +157 -157
  183. package/src/export-data-api.test.ts +126 -126
  184. package/src/export-format.test.ts +107 -107
  185. package/src/export-format.ts +598 -598
  186. package/src/filter-operators.ts +157 -157
  187. package/src/filtering/excel-filters.test.ts +44 -1
  188. package/src/filtering/excel-filters.ts +91 -14
  189. package/src/flex-render.ts +3 -3
  190. package/src/form-field.ts +127 -127
  191. package/src/index.ts +715 -713
  192. package/src/js-scroller.svelte.ts +173 -173
  193. package/src/keyboard.test.ts +59 -59
  194. package/src/keyboard.ts +97 -97
  195. package/src/list-nav.test.ts +49 -49
  196. package/src/list-nav.ts +29 -29
  197. package/src/list-option.test.ts +56 -56
  198. package/src/list-option.ts +172 -172
  199. package/src/menus.test.ts +736 -700
  200. package/src/menus.ts +679 -652
  201. package/src/merge-objects.ts +48 -48
  202. package/src/overlays.test.ts +90 -90
  203. package/src/render-component.ts +28 -28
  204. package/src/row-resize.test.ts +100 -0
  205. package/src/row-resize.ts +36 -0
  206. package/src/scheduler-model.test.ts +562 -562
  207. package/src/scheduler-model.ts +872 -872
  208. package/src/selection.test.ts +754 -754
  209. package/src/selection.ts +600 -600
  210. package/src/server-data-source.test.ts +289 -289
  211. package/src/server-data-source.ts +413 -413
  212. package/src/sparkline.test.ts +68 -68
  213. package/src/sparkline.ts +169 -169
  214. package/src/spreadsheet.test.ts +489 -489
  215. package/src/spreadsheet.ts +312 -312
  216. package/src/static-functions.ts +11 -11
  217. package/src/subscribe.ts +38 -38
  218. package/src/svgrid-wrapper.types.ts +524 -524
  219. package/src/svgrid.api-extensions.test.ts +8 -3
  220. package/src/svgrid.behavior.test.ts +706 -706
  221. package/src/svgrid.cell-dom-ids.test.ts +96 -0
  222. package/src/svgrid.charting.test.ts +534 -534
  223. package/src/svgrid.conditional-stat-scope.test.ts +111 -0
  224. package/src/svgrid.features.test.ts +157 -157
  225. package/src/svgrid.filter-menu-listbox.svelte.test.ts +308 -0
  226. package/src/svgrid.filter-menu-scroll.test.ts +3 -1
  227. package/src/svgrid.group-header-ids.test.ts +121 -0
  228. package/src/svgrid.new-features.wrapper.test.ts +251 -251
  229. package/src/svgrid.wrapper.test.ts +40 -40
  230. package/src/test-setup.ts +62 -62
  231. package/src/themes/index.ts +215 -215
  232. package/src/toast-store.svelte.ts +246 -246
  233. package/src/toast-store.test.ts +147 -147
  234. package/src/ui-buttons.test.ts +144 -144
  235. package/src/ui-inputs.test.ts +118 -118
  236. package/src/ui-range.test.ts +70 -70
  237. package/src/ui-selection.test.ts +155 -155
  238. package/src/ui-tier1.test.ts +142 -142
  239. package/src/virtual.test.ts +88 -88
  240. package/src/virtualization/column-virtualizer.test.ts +27 -27
  241. package/src/virtualization/column-virtualizer.ts +30 -30
  242. package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
  243. package/src/virtualization/types.ts +30 -30
  244. package/src/virtualization/virtualizer.test.ts +47 -47
  245. package/src/virtualization/virtualizer.ts +296 -296
  246. package/themes/ember.css +71 -71
  247. package/dist/cdn/GridMenus-BzxEYNtW.js +0 -421
  248. package/dist/cdn/GridMenus-Dr-nGEXn.js +0 -417
@@ -1,251 +1,251 @@
1
- /**
2
- * Source-level smoke tests for the wrapper additions: `getRowId` prop,
3
- * `cellClass` / `rowClass` rendering, the column-layout API surface
4
- * (`setColumnWidth` / `getColumnWidths` / `setColumnPinning` /
5
- * `getColumnPinning`), the `between` operator UI, and the multi-cell
6
- * paste / cut keyboard plumbing.
7
- *
8
- * These are deliberately source-string assertions, not jsdom mounts -
9
- * Svelte 5 mount inside vitest is brittle (the existing
10
- * `svgrid.wrapper.test.ts` follows the same pattern). The behaviour
11
- * itself is covered by integration probes in the gallery (see the
12
- * pivot expansion + paste/cut probes that were run during build) and
13
- * the upstream unit tests of the headless engine + Pro pivot.
14
- */
15
- import { readFileSync } from 'node:fs'
16
- import { resolve } from 'node:path'
17
- import { describe, expect, it } from 'vitest'
18
-
19
- // The wrapper implementation spans SvGrid.svelte plus its extracted
20
- // sibling modules (types + pure helpers). Concatenate them so these
21
- // "the wrapper declares/exposes X" assertions hold wherever the code
22
- // physically lives.
23
- const source = [
24
- './SvGrid.svelte',
25
- './SvGrid.controller.svelte.ts',
26
- './GridMenus.svelte',
27
- './GridFooter.svelte',
28
- './SvGrid.types.ts',
29
- './SvGrid.helpers.ts',
30
- './filter-operators.ts',
31
- './facet-buckets.ts',
32
- './cell-values.ts',
33
- './clipboard.ts',
34
- './build-api.ts',
35
- './columns.ts',
36
- './selection.ts',
37
- './editing.ts',
38
- './cell-render.ts',
39
- './menus.ts',
40
- './summaries.ts',
41
- './keyboard-handlers.ts',
42
- './scroll-sync.ts',
43
- './features.ts',
44
- ]
45
- .map((p) => readFileSync(resolve(__dirname, p), 'utf-8'))
46
- .join('\n')
47
- const typesPath = resolve(__dirname, './svgrid-wrapper.types.ts')
48
- const types = readFileSync(typesPath, 'utf-8')
49
-
50
- describe('SvGrid wrapper - getRowId prop', () => {
51
- it('declares the getRowId prop on the Props type', () => {
52
- expect(source).toMatch(/getRowId\?:\s*\(row:\s*TData,\s*index:\s*number\)\s*=>\s*string/)
53
- })
54
-
55
- it('forwards the prop into createSvGrid options', () => {
56
- expect(source).toMatch(/get getRowId\(\)[\s\S]*?return props\.getRowId/)
57
- })
58
- })
59
-
60
- describe('SvGrid wrapper - cellClass + rowClass', () => {
61
- it('declares the rowClass prop on the Props type', () => {
62
- expect(source).toMatch(/rowClass\?:\s*\(ctx:\s*\{/)
63
- })
64
-
65
- it('exposes a resolveClassList helper that normalises array / record / string forms', () => {
66
- expect(source).toMatch(/function resolveClassList\(/)
67
- expect(source).toMatch(/Array\.isArray\(value\)/)
68
- })
69
-
70
- it('computes the row-level class once per render row and threads it onto the <tr>', () => {
71
- expect(source).toMatch(/function computeRowClass/)
72
- expect(source).toMatch(/userRowClass = computeRowClass\(row, rowIndex\)/)
73
- expect(source).toMatch(/class=\{`sv-grid-row \$\{userRowClass\}/)
74
- })
75
-
76
- it('computes the cell-level class per column and threads it onto the <td>', () => {
77
- expect(source).toMatch(/function computeCellClass/)
78
- expect(source).toMatch(/userCellClass = computeCellClass\(\s*row,\s*rendered\.column,?\s*\)/)
79
- expect(source).toMatch(/class=\{`sv-grid-cell \$\{userCellClass\}`\}/)
80
- })
81
- })
82
-
83
- describe('SvGrid wrapper - declarative cell validation (validate hook)', () => {
84
- it('reads the column validate hook with the documented return union', () => {
85
- expect(source).toMatch(/column\.columnDef\.validate/)
86
- expect(source).toMatch(/=>\s*string\s*\|\s*boolean\s*\|\s*null\s*\|\s*undefined/)
87
- })
88
-
89
- it('computes per-cell validity in the render layer (runs for every rendered cell)', () => {
90
- expect(source).toMatch(/function computeCellValidity/)
91
- expect(source).toMatch(/columnDef\.validate/)
92
- // Valid when null / undefined / true; invalid on false or a message.
93
- expect(source).toMatch(/if \(out == null \|\| out === true\)/)
94
- })
95
-
96
- it('threads the invalid class + message tooltip onto both <td> render paths', () => {
97
- const hits = source.match(/class:sv-grid-cell-invalid=\{cellValidity\.invalid\}/g) ?? []
98
- expect(hits.length).toBeGreaterThanOrEqual(2)
99
- // Message wins over the plain column tooltip when the cell is invalid.
100
- expect(source).toMatch(/cellValidity\.invalid && cellValidity\.message/)
101
- })
102
-
103
- it('forwards computeCellValidity through the controller so the wrapper can read it', () => {
104
- expect(source).toMatch(/get computeCellValidity\(\)\s*\{\s*return computeCellValidity;/)
105
- expect(source).toMatch(/computeCellValidity\s*=\s*\$derived\(ctrl\.computeCellValidity\)/)
106
- })
107
- })
108
-
109
- describe('SvGrid wrapper - column-layout API', () => {
110
- it('declares setColumnWidth + getColumnWidths in the public type', () => {
111
- expect(types).toContain('setColumnWidth(columnId: string, width: number): void')
112
- expect(types).toContain('getColumnWidths(): Record<string, number>')
113
- })
114
-
115
- it('declares setColumnPinning + getColumnPinning in the public type', () => {
116
- expect(types).toMatch(/setColumnPinning\(pinning:\s*\{/)
117
- expect(types).toContain('getColumnPinning(): { left: string[]; right: string[] }')
118
- })
119
-
120
- it('implements all four methods in the api object', () => {
121
- expect(source).toMatch(/setColumnWidth\(columnId: string, width: number\)/)
122
- expect(source).toMatch(/getColumnWidths\(\)/)
123
- expect(source).toMatch(/setColumnPinning\(pinning\)/)
124
- expect(source).toMatch(/getColumnPinning\(\)/)
125
- })
126
-
127
- it('clamps setColumnWidth to MIN_COLUMN_WIDTH', () => {
128
- // Confirm the clamp is in the code path so a caller can't drop a
129
- // column below the minimum by passing 0 or a negative number.
130
- expect(source).toMatch(/Math\.max\(MIN_COLUMN_WIDTH/)
131
- })
132
-
133
- it('defensive-copies setColumnPinning inputs so callers cannot mutate state', () => {
134
- expect(source).toMatch(/Array\.from\(new Set\(pinning\.left/)
135
- expect(source).toMatch(/Array\.from\(new Set\(pinning\.right/)
136
- })
137
- })
138
-
139
- describe('SvGrid wrapper - between filter operator', () => {
140
- it("includes 'between' in the FilterOperator type", () => {
141
- expect(source).toMatch(/"between"/)
142
- })
143
-
144
- it("includes 'between' in NUMBER_OPERATORS + DATE_OPERATORS but NOT in TEXT_OPERATORS", () => {
145
- // The list literals appear in order, so we slice each block out
146
- // separately and check membership.
147
- const numOps = source.match(/const NUMBER_OPERATORS[^]*?\];/)?.[0] ?? ''
148
- expect(numOps).toContain('"between"')
149
- const dateOps = source.match(/const DATE_OPERATORS[^]*?\];/)?.[0] ?? ''
150
- expect(dateOps).toContain('"between"')
151
- const textOps = source.match(/const TEXT_OPERATORS[^]*?\];/)?.[0] ?? ''
152
- expect(textOps).not.toContain('"between"')
153
- })
154
-
155
- it('renders a second input ("To") when the operator is between', () => {
156
- expect(source).toMatch(/menuActiveOperator === "between"/)
157
- expect(source).toMatch(/placeholder="To"/)
158
- })
159
-
160
- it('treats the filter as inactive until BOTH endpoints are non-empty', () => {
161
- // The active-filters filter() should require value AND valueTo.
162
- expect(source).toMatch(/if \(f\.operator === "between"\) \{[^}]*f\.value\.trim\(\)\.length > 0[^}]*f\.valueTo[^}]*\.trim\(\)\.length > 0/)
163
- })
164
-
165
- it('forwards valueTo through applyExcelFilter', () => {
166
- expect(source).toMatch(/valueTo:\s*(filter\.operator|op) === "between" \? (filter\.valueTo|valueTo) : undefined/)
167
- })
168
-
169
- it('publishes valueTo in the onFiltersChange payload only for between clauses', () => {
170
- expect(source).toMatch(/f\.operator === "between" && f\.valueTo\s*\?\s*\{ valueTo: f\.valueTo \}/)
171
- })
172
-
173
- it('exposes valueTo on the imperative setFilter input + getFilters output', () => {
174
- expect(types).toMatch(/setFilter\(\s*columnId: string,[\s\S]*?valueTo\?: string/)
175
- expect(types).toMatch(/getFilters\(\)[\s\S]*?valueTo\?: string/)
176
- })
177
- })
178
-
179
- describe('SvGrid wrapper - multi-cell paste + cut + delete', () => {
180
- it('fills the selection range when the clipboard payload is 1x1', () => {
181
- expect(source).toMatch(/clipboardIsSingleCell\s*=\s*\n?\s*lines\.length === 1 && \(lines\[0\]\?\.split\("\\t"\)\.length \?\? 0\) === 1/)
182
- expect(source).toMatch(/selectionIsRange = startRow !== endRow \|\| startCol !== endCol/)
183
- expect(source).toMatch(/fillRange = clipboardIsSingleCell && selectionIsRange/)
184
- })
185
-
186
- it('walks the entire selection rect when filling', () => {
187
- expect(source).toMatch(/const rowSpan = fillRange \? endRow - startRow \+ 1 : lines\.length/)
188
- expect(source).toMatch(/const colSpan = fillRange/)
189
- })
190
-
191
- it('implements clearSelectedCells() that touches every editable cell in the range', () => {
192
- expect(source).toMatch(/function clearSelectedCells\(\): boolean/)
193
- expect(source).toMatch(/parseEditorValue\(editorType, ""\)/)
194
- })
195
-
196
- it('wires Ctrl/Cmd+X to cutSelectionToClipboard', () => {
197
- expect(source).toMatch(/lower === "x"/)
198
- expect(source).toMatch(/void (?:ctx\.)?cutSelectionToClipboard\(\)/)
199
- expect(source).toMatch(/async function cutSelectionToClipboard/)
200
- // Confirm both calls live inside the cutSelectionToClipboard body
201
- // (copy first, then clear). A comment between them is fine.
202
- const body = source.match(
203
- /async function cutSelectionToClipboard\(\)[^{]*\{([\s\S]*?)^\s\s\}/m,
204
- )?.[1] ?? ''
205
- expect(body).toContain('copySelectionToClipboard();')
206
- expect(body).toContain('clearSelectedCells();')
207
- expect(body.indexOf('copySelectionToClipboard()')).toBeLessThan(
208
- body.indexOf('clearSelectedCells()'),
209
- )
210
- })
211
-
212
- it('wires Delete + Backspace to clearSelectedCells (without clipboard interaction)', () => {
213
- expect(source).toMatch(/event\.key === "Delete" \|\| event\.key === "Backspace"/)
214
- expect(source).toMatch(/if \((?:ctx\.)?clearSelectedCells\(\)\)\s*\{/)
215
- })
216
-
217
- // Regression: the controller's ctx is an object literal of getters, and the
218
- // copy/cut/paste handlers in keyboard-handlers + menus reach the functions
219
- // via `ctx.<name>()`. A refactor once shipped the `ctx.cutSelectionToClipboard`
220
- // / `ctx.pasteFromClipboard` CALLS but dropped their getters, so those threw
221
- // "is not a function" at runtime - copy worked, cut + paste were dead. Assert
222
- // every clipboard function called through ctx has a matching getter.
223
- it('exposes a ctx getter for every clipboard command it calls through ctx', () => {
224
- for (const fn of [
225
- 'copySelectionToClipboard',
226
- 'cutSelectionToClipboard',
227
- 'pasteFromClipboard',
228
- 'onGridPaste',
229
- 'clearSelectedCells',
230
- ]) {
231
- expect(source).toMatch(new RegExp(`get ${fn}\\(\\)\\s*\\{\\s*return ${fn};?\\s*\\}`))
232
- }
233
- })
234
-
235
- it('copy/cut fall back to execCommand when navigator.clipboard is absent (insecure context)', () => {
236
- expect(source).toMatch(/function writeClipboardText\(/)
237
- expect(source).toMatch(/document\.execCommand\("copy"\)/)
238
- })
239
-
240
- it('paste falls back to a native paste event when the async API is unavailable', () => {
241
- // keydown only preventDefaults (and uses the async API) in a secure context;
242
- // otherwise it lets the browser deliver a native paste event to onGridPaste.
243
- // Accept either the positive branch or the inverted early-return guard
244
- // (`if (!navigator.clipboard?.readText) return`) - both gate on the async API.
245
- expect(source).toMatch(/if \(!?navigator\.clipboard\?\.readText\)/)
246
- expect(source).toMatch(/function onGridPaste\(event: ClipboardEvent\)/)
247
- expect(source).toMatch(/clipboardData\?\.getData\("text\/plain"\)/)
248
- // The grid root wires the handler.
249
- expect(source).toMatch(/onpaste=\{onGridPaste\}/)
250
- })
251
- })
1
+ /**
2
+ * Source-level smoke tests for the wrapper additions: `getRowId` prop,
3
+ * `cellClass` / `rowClass` rendering, the column-layout API surface
4
+ * (`setColumnWidth` / `getColumnWidths` / `setColumnPinning` /
5
+ * `getColumnPinning`), the `between` operator UI, and the multi-cell
6
+ * paste / cut keyboard plumbing.
7
+ *
8
+ * These are deliberately source-string assertions, not jsdom mounts -
9
+ * Svelte 5 mount inside vitest is brittle (the existing
10
+ * `svgrid.wrapper.test.ts` follows the same pattern). The behaviour
11
+ * itself is covered by integration probes in the gallery (see the
12
+ * pivot expansion + paste/cut probes that were run during build) and
13
+ * the upstream unit tests of the headless engine + Pro pivot.
14
+ */
15
+ import { readFileSync } from 'node:fs'
16
+ import { resolve } from 'node:path'
17
+ import { describe, expect, it } from 'vitest'
18
+
19
+ // The wrapper implementation spans SvGrid.svelte plus its extracted
20
+ // sibling modules (types + pure helpers). Concatenate them so these
21
+ // "the wrapper declares/exposes X" assertions hold wherever the code
22
+ // physically lives.
23
+ const source = [
24
+ './SvGrid.svelte',
25
+ './SvGrid.controller.svelte.ts',
26
+ './GridMenus.svelte',
27
+ './GridFooter.svelte',
28
+ './SvGrid.types.ts',
29
+ './SvGrid.helpers.ts',
30
+ './filter-operators.ts',
31
+ './facet-buckets.ts',
32
+ './cell-values.ts',
33
+ './clipboard.ts',
34
+ './build-api.ts',
35
+ './columns.ts',
36
+ './selection.ts',
37
+ './editing.ts',
38
+ './cell-render.ts',
39
+ './menus.ts',
40
+ './summaries.ts',
41
+ './keyboard-handlers.ts',
42
+ './scroll-sync.ts',
43
+ './features.ts',
44
+ ]
45
+ .map((p) => readFileSync(resolve(__dirname, p), 'utf-8'))
46
+ .join('\n')
47
+ const typesPath = resolve(__dirname, './svgrid-wrapper.types.ts')
48
+ const types = readFileSync(typesPath, 'utf-8')
49
+
50
+ describe('SvGrid wrapper - getRowId prop', () => {
51
+ it('declares the getRowId prop on the Props type', () => {
52
+ expect(source).toMatch(/getRowId\?:\s*\(row:\s*TData,\s*index:\s*number\)\s*=>\s*string/)
53
+ })
54
+
55
+ it('forwards the prop into createSvGrid options', () => {
56
+ expect(source).toMatch(/get getRowId\(\)[\s\S]*?return props\.getRowId/)
57
+ })
58
+ })
59
+
60
+ describe('SvGrid wrapper - cellClass + rowClass', () => {
61
+ it('declares the rowClass prop on the Props type', () => {
62
+ expect(source).toMatch(/rowClass\?:\s*\(ctx:\s*\{/)
63
+ })
64
+
65
+ it('exposes a resolveClassList helper that normalises array / record / string forms', () => {
66
+ expect(source).toMatch(/function resolveClassList\(/)
67
+ expect(source).toMatch(/Array\.isArray\(value\)/)
68
+ })
69
+
70
+ it('computes the row-level class once per render row and threads it onto the <tr>', () => {
71
+ expect(source).toMatch(/function computeRowClass/)
72
+ expect(source).toMatch(/userRowClass = computeRowClass\(row, rowIndex\)/)
73
+ expect(source).toMatch(/class=\{`sv-grid-row \$\{userRowClass\}/)
74
+ })
75
+
76
+ it('computes the cell-level class per column and threads it onto the <td>', () => {
77
+ expect(source).toMatch(/function computeCellClass/)
78
+ expect(source).toMatch(/userCellClass = computeCellClass\(\s*row,\s*rendered\.column,?\s*\)/)
79
+ expect(source).toMatch(/class=\{`sv-grid-cell \$\{userCellClass\}`\}/)
80
+ })
81
+ })
82
+
83
+ describe('SvGrid wrapper - declarative cell validation (validate hook)', () => {
84
+ it('reads the column validate hook with the documented return union', () => {
85
+ expect(source).toMatch(/column\.columnDef\.validate/)
86
+ expect(source).toMatch(/=>\s*string\s*\|\s*boolean\s*\|\s*null\s*\|\s*undefined/)
87
+ })
88
+
89
+ it('computes per-cell validity in the render layer (runs for every rendered cell)', () => {
90
+ expect(source).toMatch(/function computeCellValidity/)
91
+ expect(source).toMatch(/columnDef\.validate/)
92
+ // Valid when null / undefined / true; invalid on false or a message.
93
+ expect(source).toMatch(/if \(out == null \|\| out === true\)/)
94
+ })
95
+
96
+ it('threads the invalid class + message tooltip onto both <td> render paths', () => {
97
+ const hits = source.match(/class:sv-grid-cell-invalid=\{cellValidity\.invalid\}/g) ?? []
98
+ expect(hits.length).toBeGreaterThanOrEqual(2)
99
+ // Message wins over the plain column tooltip when the cell is invalid.
100
+ expect(source).toMatch(/cellValidity\.invalid && cellValidity\.message/)
101
+ })
102
+
103
+ it('forwards computeCellValidity through the controller so the wrapper can read it', () => {
104
+ expect(source).toMatch(/get computeCellValidity\(\)\s*\{\s*return computeCellValidity;/)
105
+ expect(source).toMatch(/computeCellValidity\s*=\s*\$derived\(ctrl\.computeCellValidity\)/)
106
+ })
107
+ })
108
+
109
+ describe('SvGrid wrapper - column-layout API', () => {
110
+ it('declares setColumnWidth + getColumnWidths in the public type', () => {
111
+ expect(types).toContain('setColumnWidth(columnId: string, width: number): void')
112
+ expect(types).toContain('getColumnWidths(): Record<string, number>')
113
+ })
114
+
115
+ it('declares setColumnPinning + getColumnPinning in the public type', () => {
116
+ expect(types).toMatch(/setColumnPinning\(pinning:\s*\{/)
117
+ expect(types).toContain('getColumnPinning(): { left: string[]; right: string[] }')
118
+ })
119
+
120
+ it('implements all four methods in the api object', () => {
121
+ expect(source).toMatch(/setColumnWidth\(columnId: string, width: number\)/)
122
+ expect(source).toMatch(/getColumnWidths\(\)/)
123
+ expect(source).toMatch(/setColumnPinning\(pinning\)/)
124
+ expect(source).toMatch(/getColumnPinning\(\)/)
125
+ })
126
+
127
+ it('clamps setColumnWidth to MIN_COLUMN_WIDTH', () => {
128
+ // Confirm the clamp is in the code path so a caller can't drop a
129
+ // column below the minimum by passing 0 or a negative number.
130
+ expect(source).toMatch(/Math\.max\(MIN_COLUMN_WIDTH/)
131
+ })
132
+
133
+ it('defensive-copies setColumnPinning inputs so callers cannot mutate state', () => {
134
+ expect(source).toMatch(/Array\.from\(new Set\(pinning\.left/)
135
+ expect(source).toMatch(/Array\.from\(new Set\(pinning\.right/)
136
+ })
137
+ })
138
+
139
+ describe('SvGrid wrapper - between filter operator', () => {
140
+ it("includes 'between' in the FilterOperator type", () => {
141
+ expect(source).toMatch(/"between"/)
142
+ })
143
+
144
+ it("includes 'between' in NUMBER_OPERATORS + DATE_OPERATORS but NOT in TEXT_OPERATORS", () => {
145
+ // The list literals appear in order, so we slice each block out
146
+ // separately and check membership.
147
+ const numOps = source.match(/const NUMBER_OPERATORS[^]*?\];/)?.[0] ?? ''
148
+ expect(numOps).toContain('"between"')
149
+ const dateOps = source.match(/const DATE_OPERATORS[^]*?\];/)?.[0] ?? ''
150
+ expect(dateOps).toContain('"between"')
151
+ const textOps = source.match(/const TEXT_OPERATORS[^]*?\];/)?.[0] ?? ''
152
+ expect(textOps).not.toContain('"between"')
153
+ })
154
+
155
+ it('renders a second input ("To") when the operator is between', () => {
156
+ expect(source).toMatch(/menuActiveOperator === "between"/)
157
+ expect(source).toMatch(/placeholder="To"/)
158
+ })
159
+
160
+ it('treats the filter as inactive until BOTH endpoints are non-empty', () => {
161
+ // The active-filters filter() should require value AND valueTo.
162
+ expect(source).toMatch(/if \(f\.operator === "between"\) \{[^}]*f\.value\.trim\(\)\.length > 0[^}]*f\.valueTo[^}]*\.trim\(\)\.length > 0/)
163
+ })
164
+
165
+ it('forwards valueTo through applyExcelFilter', () => {
166
+ expect(source).toMatch(/valueTo:\s*(filter\.operator|op) === "between" \? (filter\.valueTo|valueTo) : undefined/)
167
+ })
168
+
169
+ it('publishes valueTo in the onFiltersChange payload only for between clauses', () => {
170
+ expect(source).toMatch(/f\.operator === "between" && f\.valueTo\s*\?\s*\{ valueTo: f\.valueTo \}/)
171
+ })
172
+
173
+ it('exposes valueTo on the imperative setFilter input + getFilters output', () => {
174
+ expect(types).toMatch(/setFilter\(\s*columnId: string,[\s\S]*?valueTo\?: string/)
175
+ expect(types).toMatch(/getFilters\(\)[\s\S]*?valueTo\?: string/)
176
+ })
177
+ })
178
+
179
+ describe('SvGrid wrapper - multi-cell paste + cut + delete', () => {
180
+ it('fills the selection range when the clipboard payload is 1x1', () => {
181
+ expect(source).toMatch(/clipboardIsSingleCell\s*=\s*\n?\s*lines\.length === 1 && \(lines\[0\]\?\.split\("\\t"\)\.length \?\? 0\) === 1/)
182
+ expect(source).toMatch(/selectionIsRange = startRow !== endRow \|\| startCol !== endCol/)
183
+ expect(source).toMatch(/fillRange = clipboardIsSingleCell && selectionIsRange/)
184
+ })
185
+
186
+ it('walks the entire selection rect when filling', () => {
187
+ expect(source).toMatch(/const rowSpan = fillRange \? endRow - startRow \+ 1 : lines\.length/)
188
+ expect(source).toMatch(/const colSpan = fillRange/)
189
+ })
190
+
191
+ it('implements clearSelectedCells() that touches every editable cell in the range', () => {
192
+ expect(source).toMatch(/function clearSelectedCells\(\): boolean/)
193
+ expect(source).toMatch(/parseEditorValue\(editorType, ""\)/)
194
+ })
195
+
196
+ it('wires Ctrl/Cmd+X to cutSelectionToClipboard', () => {
197
+ expect(source).toMatch(/lower === "x"/)
198
+ expect(source).toMatch(/void (?:ctx\.)?cutSelectionToClipboard\(\)/)
199
+ expect(source).toMatch(/async function cutSelectionToClipboard/)
200
+ // Confirm both calls live inside the cutSelectionToClipboard body
201
+ // (copy first, then clear). A comment between them is fine.
202
+ const body = source.match(
203
+ /async function cutSelectionToClipboard\(\)[^{]*\{([\s\S]*?)^\s\s\}/m,
204
+ )?.[1] ?? ''
205
+ expect(body).toContain('copySelectionToClipboard();')
206
+ expect(body).toContain('clearSelectedCells();')
207
+ expect(body.indexOf('copySelectionToClipboard()')).toBeLessThan(
208
+ body.indexOf('clearSelectedCells()'),
209
+ )
210
+ })
211
+
212
+ it('wires Delete + Backspace to clearSelectedCells (without clipboard interaction)', () => {
213
+ expect(source).toMatch(/event\.key === "Delete" \|\| event\.key === "Backspace"/)
214
+ expect(source).toMatch(/if \((?:ctx\.)?clearSelectedCells\(\)\)\s*\{/)
215
+ })
216
+
217
+ // Regression: the controller's ctx is an object literal of getters, and the
218
+ // copy/cut/paste handlers in keyboard-handlers + menus reach the functions
219
+ // via `ctx.<name>()`. A refactor once shipped the `ctx.cutSelectionToClipboard`
220
+ // / `ctx.pasteFromClipboard` CALLS but dropped their getters, so those threw
221
+ // "is not a function" at runtime - copy worked, cut + paste were dead. Assert
222
+ // every clipboard function called through ctx has a matching getter.
223
+ it('exposes a ctx getter for every clipboard command it calls through ctx', () => {
224
+ for (const fn of [
225
+ 'copySelectionToClipboard',
226
+ 'cutSelectionToClipboard',
227
+ 'pasteFromClipboard',
228
+ 'onGridPaste',
229
+ 'clearSelectedCells',
230
+ ]) {
231
+ expect(source).toMatch(new RegExp(`get ${fn}\\(\\)\\s*\\{\\s*return ${fn};?\\s*\\}`))
232
+ }
233
+ })
234
+
235
+ it('copy/cut fall back to execCommand when navigator.clipboard is absent (insecure context)', () => {
236
+ expect(source).toMatch(/function writeClipboardText\(/)
237
+ expect(source).toMatch(/document\.execCommand\("copy"\)/)
238
+ })
239
+
240
+ it('paste falls back to a native paste event when the async API is unavailable', () => {
241
+ // keydown only preventDefaults (and uses the async API) in a secure context;
242
+ // otherwise it lets the browser deliver a native paste event to onGridPaste.
243
+ // Accept either the positive branch or the inverted early-return guard
244
+ // (`if (!navigator.clipboard?.readText) return`) - both gate on the async API.
245
+ expect(source).toMatch(/if \(!?navigator\.clipboard\?\.readText\)/)
246
+ expect(source).toMatch(/function onGridPaste\(event: ClipboardEvent\)/)
247
+ expect(source).toMatch(/clipboardData\?\.getData\("text\/plain"\)/)
248
+ // The grid root wires the handler.
249
+ expect(source).toMatch(/onpaste=\{onGridPaste\}/)
250
+ })
251
+ })
@@ -1,17 +1,17 @@
1
- import { readFileSync } from 'node:fs'
2
- import { resolve } from 'node:path'
3
- import { describe, expect, it } from 'vitest'
4
- import SvGrid from './SvGrid.svelte'
5
-
6
- describe('SvGrid wrapper', () => {
7
- it('exports a production wrapper component', () => {
8
- expect(SvGrid).toBeDefined()
9
- })
10
-
11
- it('contains full-wrapper controls and aria-activedescendant wiring', () => {
12
- // The wrapper spans SvGrid.svelte plus its extracted sibling modules
13
- // (types + pure helpers); read them together so these "contains X"
14
- // assertions hold wherever the code physically lives.
1
+ import { readFileSync } from 'node:fs'
2
+ import { resolve } from 'node:path'
3
+ import { describe, expect, it } from 'vitest'
4
+ import SvGrid from './SvGrid.svelte'
5
+
6
+ describe('SvGrid wrapper', () => {
7
+ it('exports a production wrapper component', () => {
8
+ expect(SvGrid).toBeDefined()
9
+ })
10
+
11
+ it('contains full-wrapper controls and aria-activedescendant wiring', () => {
12
+ // The wrapper spans SvGrid.svelte plus its extracted sibling modules
13
+ // (types + pure helpers); read them together so these "contains X"
14
+ // assertions hold wherever the code physically lives.
15
15
  const source = [
16
16
  './SvGrid.svelte',
17
17
  './SvGrid.controller.svelte.ts',
@@ -33,29 +33,29 @@ describe('SvGrid wrapper', () => {
33
33
  './keyboard-handlers.ts',
34
34
  './scroll-sync.ts',
35
35
  './features.ts',
36
- ]
37
- .map((p) => readFileSync(resolve(__dirname, p), 'utf-8'))
38
- .join('\n')
39
- expect(source).toContain('Filter all rows')
40
- expect(source).toContain('Previous')
41
- expect(source).toContain('role="status"')
42
- expect(source).toContain('role="alert"')
43
- expect(source).toContain('activeDescendantId')
44
- expect(source).toContain('getGridRootA11yProps')
45
- expect(source).toContain('data-svgrid-header-col')
46
- expect(source).toContain('data-svgrid-row')
47
- expect(source).toContain('createSvelteVirtualizer')
48
- expect(source).toContain('props.virtualization ?? true')
49
- expect(source).toContain('virtualizer.getVirtualItems()')
50
- expect(source).toContain('createColumnVirtualizer')
51
- expect(source).toContain('data-selected-range')
52
- expect(source).toContain('enableInlineEditing')
53
- expect(source).toContain('showFilterMenu')
54
- expect(source).toContain('<tfoot')
55
- expect(source).toContain('editorType === "number"')
56
- expect(source).toContain('editorType === "date"')
57
- expect(source).toContain('editorType === "checkbox"')
58
- expect(source).toContain('applyExcelFilter')
59
- expect(source).toContain('showFilterRow')
60
- })
61
- })
36
+ ]
37
+ .map((p) => readFileSync(resolve(__dirname, p), 'utf-8'))
38
+ .join('\n')
39
+ expect(source).toContain('Filter all rows')
40
+ expect(source).toContain('Previous')
41
+ expect(source).toContain('role="status"')
42
+ expect(source).toContain('role="alert"')
43
+ expect(source).toContain('activeDescendantId')
44
+ expect(source).toContain('getGridRootA11yProps')
45
+ expect(source).toContain('data-svgrid-header-col')
46
+ expect(source).toContain('data-svgrid-row')
47
+ expect(source).toContain('createSvelteVirtualizer')
48
+ expect(source).toContain('props.virtualization ?? true')
49
+ expect(source).toContain('virtualizer.getVirtualItems()')
50
+ expect(source).toContain('createColumnVirtualizer')
51
+ expect(source).toContain('data-selected-range')
52
+ expect(source).toContain('enableInlineEditing')
53
+ expect(source).toContain('showFilterMenu')
54
+ expect(source).toContain('<tfoot')
55
+ expect(source).toContain('editorType === "number"')
56
+ expect(source).toContain('editorType === "date"')
57
+ expect(source).toContain('editorType === "checkbox"')
58
+ expect(source).toContain('applyExcelFilter')
59
+ expect(source).toContain('showFilterRow')
60
+ })
61
+ })