@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,48 +1,48 @@
1
- /**
2
- * Merges objects together while keeping their getters alive.
3
- * Taken from SolidJS:
4
- * https://github.com/solidjs/solid/blob/24abc825c0996fd2bc8c1de1491efe9a7e743aff/packages/solid/src/server/rendering.ts#L82-L115
5
- */
6
- export function mergeObjects<T>(source: T): T
7
- export function mergeObjects<T, U>(source: T, source1: U): T & U
8
- export function mergeObjects<T, U, V>(
9
- source: T,
10
- source1: U,
11
- source2: V,
12
- ): T & U & V
13
- export function mergeObjects<T, U, V, W>(
14
- source: T,
15
- source1: U,
16
- source2: V,
17
- source3: W,
18
- ): T & U & V & W
19
- export function mergeObjects(...sources: any): any {
20
- const target = {}
21
-
22
- for (let source of sources) {
23
- if (typeof source === 'function') source = source()
24
-
25
- if (source) {
26
- const descriptors = Object.getOwnPropertyDescriptors(source)
27
-
28
- for (const key in descriptors) {
29
- if (key in target) continue
30
-
31
- Object.defineProperty(target, key, {
32
- enumerable: true,
33
- get() {
34
- for (let i = sources.length - 1; i >= 0; i--) {
35
- let scopedSource = sources[i]
36
- if (typeof scopedSource === 'function') scopedSource = scopedSource()
37
-
38
- const value = (scopedSource || {})[key]
39
- if (value !== undefined) return value
40
- }
41
- },
42
- })
43
- }
44
- }
45
- }
46
-
47
- return target
48
- }
1
+ /**
2
+ * Merges objects together while keeping their getters alive.
3
+ * Taken from SolidJS:
4
+ * https://github.com/solidjs/solid/blob/24abc825c0996fd2bc8c1de1491efe9a7e743aff/packages/solid/src/server/rendering.ts#L82-L115
5
+ */
6
+ export function mergeObjects<T>(source: T): T
7
+ export function mergeObjects<T, U>(source: T, source1: U): T & U
8
+ export function mergeObjects<T, U, V>(
9
+ source: T,
10
+ source1: U,
11
+ source2: V,
12
+ ): T & U & V
13
+ export function mergeObjects<T, U, V, W>(
14
+ source: T,
15
+ source1: U,
16
+ source2: V,
17
+ source3: W,
18
+ ): T & U & V & W
19
+ export function mergeObjects(...sources: any): any {
20
+ const target = {}
21
+
22
+ for (let source of sources) {
23
+ if (typeof source === 'function') source = source()
24
+
25
+ if (source) {
26
+ const descriptors = Object.getOwnPropertyDescriptors(source)
27
+
28
+ for (const key in descriptors) {
29
+ if (key in target) continue
30
+
31
+ Object.defineProperty(target, key, {
32
+ enumerable: true,
33
+ get() {
34
+ for (let i = sources.length - 1; i >= 0; i--) {
35
+ let scopedSource = sources[i]
36
+ if (typeof scopedSource === 'function') scopedSource = scopedSource()
37
+
38
+ const value = (scopedSource || {})[key]
39
+ if (value !== undefined) return value
40
+ }
41
+ },
42
+ })
43
+ }
44
+ }
45
+ }
46
+
47
+ return target
48
+ }
@@ -1,90 +1,90 @@
1
- /**
2
- * Component tests for the overlay trio: SvModal (dialog semantics, Escape /
3
- * backdrop close), SvPopover (click open + outside close) and SvTooltip
4
- * (focus-triggered, aria-describedby wiring).
5
- */
6
- import { describe, expect, it, vi, afterEach } from 'vitest'
7
- import { mount, unmount, flushSync, createRawSnippet } from 'svelte'
8
- import SvModal from './SvModal.svelte'
9
- import SvPopover from './SvPopover.svelte'
10
- import SvTooltip from './SvTooltip.svelte'
11
-
12
- const body = createRawSnippet(() => ({ render: () => `<p class="body">hello</p>` }))
13
- const anchorBtn = createRawSnippet(() => ({ render: () => `<button class="anchor">open</button>` }))
14
-
15
- function mountC(Comp: any, props: Record<string, unknown>) {
16
- const target = document.createElement('div')
17
- document.body.appendChild(target)
18
- const app = mount(Comp, { target, props: props as any })
19
- flushSync()
20
- return { target, destroy: () => { unmount(app); target.remove() } }
21
- }
22
- afterEach(() => {
23
- document.querySelectorAll('.sv-modal__backdrop, .sv-pop__panel, .sv-tip').forEach((n) => n.remove())
24
- })
25
-
26
- describe('SvModal', () => {
27
- it('renders a labelled modal dialog when open', () => {
28
- const { destroy } = mountC(SvModal, { open: true, title: 'Edit', children: body })
29
- try {
30
- const dialog = document.querySelector<HTMLElement>('.sv-modal')!
31
- expect(dialog.getAttribute('role')).toBe('dialog')
32
- expect(dialog.getAttribute('aria-modal')).toBe('true')
33
- const titleId = dialog.getAttribute('aria-labelledby')!
34
- expect(document.getElementById(titleId)!.textContent).toBe('Edit')
35
- expect(document.querySelector('.sv-modal .body')!.textContent).toBe('hello')
36
- } finally { destroy() }
37
- })
38
-
39
- it('Escape and the close button both fire onClose', () => {
40
- let closed = 0
41
- const { destroy } = mountC(SvModal, { open: true, title: 'X', onClose: () => (closed++), children: body })
42
- try {
43
- document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
44
- flushSync()
45
- expect(closed).toBe(1)
46
- } finally { destroy() }
47
- })
48
- })
49
-
50
- describe('SvPopover', () => {
51
- it('opens the panel on anchor click and closes on outside pointerdown', () => {
52
- const { target, destroy } = mountC(SvPopover, { anchor: anchorBtn, children: body })
53
- try {
54
- expect(document.querySelector('.sv-pop__panel')).toBeNull()
55
- target.querySelector<HTMLElement>('.anchor')!.click()
56
- flushSync()
57
- const panel = document.querySelector<HTMLElement>('.sv-pop__panel')!
58
- expect(panel).not.toBeNull()
59
- expect(panel.getAttribute('role')).toBe('dialog')
60
- // Outside click closes.
61
- document.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true }))
62
- flushSync()
63
- expect(document.querySelector('.sv-pop__panel')).toBeNull()
64
- } finally { destroy() }
65
- })
66
- })
67
-
68
- describe('SvTooltip', () => {
69
- it('shows after the delay on focus and wires aria-describedby', () => {
70
- vi.useFakeTimers()
71
- try {
72
- const child = createRawSnippet(() => ({ render: () => `<button class="t">i</button>` }))
73
- const { target, destroy } = mountC(SvTooltip, { text: 'Tip text', delay: 200, children: child })
74
- const anchor = target.querySelector<HTMLElement>('.sv-tip__anchor')!
75
- anchor.dispatchEvent(new FocusEvent('focusin', { bubbles: true }))
76
- vi.advanceTimersByTime(210)
77
- flushSync()
78
- const tip = document.querySelector<HTMLElement>('.sv-tip')!
79
- expect(tip).not.toBeNull()
80
- expect(tip.getAttribute('role')).toBe('tooltip')
81
- expect(anchor.getAttribute('aria-describedby')).toBe(tip.id)
82
- // The wrapper span is never itself focusable, so AT users only ever land
83
- // on the real interactive child below - it must carry the description
84
- // too, or it's never announced on focus (see SvTooltip.svelte effect).
85
- const focusEl = target.querySelector<HTMLElement>('.t')!
86
- expect(focusEl.getAttribute('aria-describedby')).toBe(tip.id)
87
- destroy()
88
- } finally { vi.useRealTimers() }
89
- })
90
- })
1
+ /**
2
+ * Component tests for the overlay trio: SvModal (dialog semantics, Escape /
3
+ * backdrop close), SvPopover (click open + outside close) and SvTooltip
4
+ * (focus-triggered, aria-describedby wiring).
5
+ */
6
+ import { describe, expect, it, vi, afterEach } from 'vitest'
7
+ import { mount, unmount, flushSync, createRawSnippet } from 'svelte'
8
+ import SvModal from './SvModal.svelte'
9
+ import SvPopover from './SvPopover.svelte'
10
+ import SvTooltip from './SvTooltip.svelte'
11
+
12
+ const body = createRawSnippet(() => ({ render: () => `<p class="body">hello</p>` }))
13
+ const anchorBtn = createRawSnippet(() => ({ render: () => `<button class="anchor">open</button>` }))
14
+
15
+ function mountC(Comp: any, props: Record<string, unknown>) {
16
+ const target = document.createElement('div')
17
+ document.body.appendChild(target)
18
+ const app = mount(Comp, { target, props: props as any })
19
+ flushSync()
20
+ return { target, destroy: () => { unmount(app); target.remove() } }
21
+ }
22
+ afterEach(() => {
23
+ document.querySelectorAll('.sv-modal__backdrop, .sv-pop__panel, .sv-tip').forEach((n) => n.remove())
24
+ })
25
+
26
+ describe('SvModal', () => {
27
+ it('renders a labelled modal dialog when open', () => {
28
+ const { destroy } = mountC(SvModal, { open: true, title: 'Edit', children: body })
29
+ try {
30
+ const dialog = document.querySelector<HTMLElement>('.sv-modal')!
31
+ expect(dialog.getAttribute('role')).toBe('dialog')
32
+ expect(dialog.getAttribute('aria-modal')).toBe('true')
33
+ const titleId = dialog.getAttribute('aria-labelledby')!
34
+ expect(document.getElementById(titleId)!.textContent).toBe('Edit')
35
+ expect(document.querySelector('.sv-modal .body')!.textContent).toBe('hello')
36
+ } finally { destroy() }
37
+ })
38
+
39
+ it('Escape and the close button both fire onClose', () => {
40
+ let closed = 0
41
+ const { destroy } = mountC(SvModal, { open: true, title: 'X', onClose: () => (closed++), children: body })
42
+ try {
43
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
44
+ flushSync()
45
+ expect(closed).toBe(1)
46
+ } finally { destroy() }
47
+ })
48
+ })
49
+
50
+ describe('SvPopover', () => {
51
+ it('opens the panel on anchor click and closes on outside pointerdown', () => {
52
+ const { target, destroy } = mountC(SvPopover, { anchor: anchorBtn, children: body })
53
+ try {
54
+ expect(document.querySelector('.sv-pop__panel')).toBeNull()
55
+ target.querySelector<HTMLElement>('.anchor')!.click()
56
+ flushSync()
57
+ const panel = document.querySelector<HTMLElement>('.sv-pop__panel')!
58
+ expect(panel).not.toBeNull()
59
+ expect(panel.getAttribute('role')).toBe('dialog')
60
+ // Outside click closes.
61
+ document.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true }))
62
+ flushSync()
63
+ expect(document.querySelector('.sv-pop__panel')).toBeNull()
64
+ } finally { destroy() }
65
+ })
66
+ })
67
+
68
+ describe('SvTooltip', () => {
69
+ it('shows after the delay on focus and wires aria-describedby', () => {
70
+ vi.useFakeTimers()
71
+ try {
72
+ const child = createRawSnippet(() => ({ render: () => `<button class="t">i</button>` }))
73
+ const { target, destroy } = mountC(SvTooltip, { text: 'Tip text', delay: 200, children: child })
74
+ const anchor = target.querySelector<HTMLElement>('.sv-tip__anchor')!
75
+ anchor.dispatchEvent(new FocusEvent('focusin', { bubbles: true }))
76
+ vi.advanceTimersByTime(210)
77
+ flushSync()
78
+ const tip = document.querySelector<HTMLElement>('.sv-tip')!
79
+ expect(tip).not.toBeNull()
80
+ expect(tip.getAttribute('role')).toBe('tooltip')
81
+ expect(anchor.getAttribute('aria-describedby')).toBe(tip.id)
82
+ // The wrapper span is never itself focusable, so AT users only ever land
83
+ // on the real interactive child below - it must carry the description
84
+ // too, or it's never announced on focus (see SvTooltip.svelte effect).
85
+ const focusEl = target.querySelector<HTMLElement>('.t')!
86
+ expect(focusEl.getAttribute('aria-describedby')).toBe(tip.id)
87
+ destroy()
88
+ } finally { vi.useRealTimers() }
89
+ })
90
+ })
@@ -1,28 +1,28 @@
1
- import type { Component, ComponentProps, Snippet } from 'svelte'
2
-
3
- export class RenderComponentConfig<TComponent extends Component> {
4
- constructor(
5
- public component: TComponent,
6
- public props?: ComponentProps<TComponent> | Record<string, never>,
7
- ) {}
8
- }
9
-
10
- export class RenderSnippetConfig<TProps> {
11
- constructor(
12
- public snippet: Snippet<[TProps]>,
13
- public params?: TProps,
14
- ) {}
15
- }
16
-
17
- export const renderComponent = <
18
- TComponent extends Component<any>,
19
- TProps extends ComponentProps<TComponent>,
20
- >(
21
- component: TComponent,
22
- props?: TProps,
23
- ) => new RenderComponentConfig(component, props)
24
-
25
- export const renderSnippet = <TProps>(
26
- snippet: Snippet<[TProps]>,
27
- params?: TProps,
28
- ) => new RenderSnippetConfig(snippet, params)
1
+ import type { Component, ComponentProps, Snippet } from 'svelte'
2
+
3
+ export class RenderComponentConfig<TComponent extends Component> {
4
+ constructor(
5
+ public component: TComponent,
6
+ public props?: ComponentProps<TComponent> | Record<string, never>,
7
+ ) {}
8
+ }
9
+
10
+ export class RenderSnippetConfig<TProps> {
11
+ constructor(
12
+ public snippet: Snippet<[TProps]>,
13
+ public params?: TProps,
14
+ ) {}
15
+ }
16
+
17
+ export const renderComponent = <
18
+ TComponent extends Component<any>,
19
+ TProps extends ComponentProps<TComponent>,
20
+ >(
21
+ component: TComponent,
22
+ props?: TProps,
23
+ ) => new RenderComponentConfig(component, props)
24
+
25
+ export const renderSnippet = <TProps>(
26
+ snippet: Snippet<[TProps]>,
27
+ params?: TProps,
28
+ ) => new RenderSnippetConfig(snippet, params)
@@ -392,3 +392,103 @@ describe('rowResize - update + destroy', () => {
392
392
  action.destroy()
393
393
  })
394
394
  })
395
+
396
+ // ---------------------------------------------------------------------------
397
+ // Keyboard resize (#79)
398
+ // ---------------------------------------------------------------------------
399
+
400
+ describe('rowResize - keyboard', () => {
401
+ function key(k: string, init: Partial<KeyboardEvent> = {}): KeyboardEvent {
402
+ return new KeyboardEvent('keydown', { key: k, bubbles: true, cancelable: true, ...init })
403
+ }
404
+
405
+ it('makes the strip focusable', () => {
406
+ const { host, rows } = buildGrid([{}])
407
+ track(host)
408
+ const action = rowResize(host, { onResize: vi.fn() })
409
+ expect(stripOf(rows[0]!)!.tabIndex).toBe(0)
410
+ action.destroy()
411
+ })
412
+
413
+ it('ArrowDown grows the row by 10px and reports the new height', () => {
414
+ const { host, rows } = buildGrid([{ rowIndex: 0 }])
415
+ track(host)
416
+ mockRowHeight(rows[0]!, 32)
417
+ const onResize = vi.fn()
418
+ const action = rowResize(host, { onResize })
419
+ stripOf(rows[0]!)!.dispatchEvent(key('ArrowDown'))
420
+ expect(rows[0]!.style.height).toBe('42px')
421
+ expect(onResize).toHaveBeenCalledWith(0, 42)
422
+ action.destroy()
423
+ })
424
+
425
+ it('ArrowUp shrinks the row and Shift gives a 1px step', () => {
426
+ const { host, rows } = buildGrid([{ rowIndex: 0 }])
427
+ track(host)
428
+ mockRowHeight(rows[0]!, 60)
429
+ const onResize = vi.fn()
430
+ const action = rowResize(host, { onResize })
431
+ const strip = stripOf(rows[0]!)!
432
+ strip.dispatchEvent(key('ArrowUp'))
433
+ expect(onResize).toHaveBeenLastCalledWith(0, 50)
434
+ strip.dispatchEvent(key('ArrowUp', { shiftKey: true }))
435
+ expect(onResize).toHaveBeenLastCalledWith(0, 59)
436
+ action.destroy()
437
+ })
438
+
439
+ it('clamps to the configured min and max', () => {
440
+ const { host, rows } = buildGrid([{ rowIndex: 0 }])
441
+ track(host)
442
+ mockRowHeight(rows[0]!, 42)
443
+ const onResize = vi.fn()
444
+ const action = rowResize(host, { onResize, min: 40, max: 44 })
445
+ const strip = stripOf(rows[0]!)!
446
+ strip.dispatchEvent(key('ArrowUp'))
447
+ expect(onResize).toHaveBeenLastCalledWith(0, 40)
448
+ strip.dispatchEvent(key('ArrowDown'))
449
+ expect(onResize).toHaveBeenLastCalledWith(0, 44)
450
+ action.destroy()
451
+ })
452
+
453
+ it('ignores keys other than the arrows and consumes the ones it handles', () => {
454
+ const { host, rows } = buildGrid([{ rowIndex: 0 }])
455
+ track(host)
456
+ mockRowHeight(rows[0]!, 32)
457
+ const onResize = vi.fn()
458
+ const action = rowResize(host, { onResize })
459
+ const strip = stripOf(rows[0]!)!
460
+ const other = key('Enter')
461
+ strip.dispatchEvent(other)
462
+ expect(onResize).not.toHaveBeenCalled()
463
+ expect(other.defaultPrevented).toBe(false)
464
+ const handled = key('ArrowDown')
465
+ strip.dispatchEvent(handled)
466
+ expect(handled.defaultPrevented).toBe(true)
467
+ action.destroy()
468
+ })
469
+
470
+ it('does nothing while disabled, and stops after destroy', () => {
471
+ const { host, rows } = buildGrid([{ rowIndex: 0 }])
472
+ track(host)
473
+ mockRowHeight(rows[0]!, 32)
474
+ const onResize = vi.fn()
475
+ const action = rowResize(host, { onResize, disabled: true })
476
+ // Disabled removes the strips entirely, so drive a hand-made one.
477
+ const strip = document.createElement('div')
478
+ strip.className = STRIP_CLASS
479
+ gutterOf(rows[0]!).appendChild(strip)
480
+ strip.dispatchEvent(key('ArrowDown'))
481
+ expect(onResize).not.toHaveBeenCalled()
482
+
483
+ action.update({ onResize })
484
+ stripOf(rows[0]!)!.dispatchEvent(key('ArrowDown'))
485
+ expect(onResize).toHaveBeenCalledTimes(1)
486
+
487
+ action.destroy()
488
+ const orphan = document.createElement('div')
489
+ orphan.className = STRIP_CLASS
490
+ gutterOf(rows[0]!).appendChild(orphan)
491
+ orphan.dispatchEvent(key('ArrowDown'))
492
+ expect(onResize).toHaveBeenCalledTimes(1)
493
+ })
494
+ })
package/src/row-resize.ts CHANGED
@@ -81,6 +81,37 @@ export function rowResize(node: HTMLElement, opts: RowResizeOptions) {
81
81
  document.body.style.cursor = ''
82
82
  }
83
83
 
84
+ /**
85
+ * Keyboard resize (#79). The strip is `role="separator"` acting as a
86
+ * splitter, so it has to be reachable by Tab and respond to arrow keys.
87
+ * Up/Down shrink/grow the row, Shift gives a fine 1px step - the same
88
+ * contract the column resize handle uses (Left/Right, Shift = 1px).
89
+ */
90
+ function onKeyDown(e: KeyboardEvent) {
91
+ if (current.disabled) return
92
+ const t = e.target as HTMLElement | null
93
+ if (!t?.classList.contains(STRIP_CLASS)) return
94
+ const step = e.shiftKey ? 1 : 10
95
+ let delta = 0
96
+ if (e.key === 'ArrowUp') delta = -step
97
+ else if (e.key === 'ArrowDown') delta = step
98
+ else return
99
+ const tr = t.closest<HTMLTableRowElement>('tr.sv-grid-row')
100
+ if (!tr) return
101
+ const rowIndex = rowIndexOf(tr)
102
+ if (!Number.isFinite(rowIndex)) return
103
+ e.preventDefault()
104
+ e.stopPropagation()
105
+ const min = current.min ?? 20
106
+ const max = current.max ?? 320
107
+ const next = Math.round(
108
+ Math.max(min, Math.min(max, tr.getBoundingClientRect().height + delta)),
109
+ )
110
+ tr.style.height = `${next}px`
111
+ current.onResizeMove?.(rowIndex, next)
112
+ current.onResize(rowIndex, next)
113
+ }
114
+
84
115
  function onPointerDown(e: PointerEvent) {
85
116
  if (current.disabled) return
86
117
  const t = e.target as HTMLElement | null
@@ -134,6 +165,9 @@ export function rowResize(node: HTMLElement, opts: RowResizeOptions) {
134
165
  strip.setAttribute('role', 'separator')
135
166
  strip.setAttribute('aria-orientation', 'horizontal')
136
167
  strip.setAttribute('aria-label', 'Resize row')
168
+ // A separator used as an interactive splitter has to be focusable so
169
+ // keyboard-only users can reach it and drive `onKeyDown` (#79).
170
+ strip.tabIndex = 0
137
171
  // Inline the geometry + pointer-events so the strip works even
138
172
  // before SvGrid.css is parsed; the hover tint still comes from
139
173
  // the stylesheet. We keep the strip fully inside the gutter
@@ -152,6 +186,7 @@ export function rowResize(node: HTMLElement, opts: RowResizeOptions) {
152
186
  }
153
187
 
154
188
  node.addEventListener('pointerdown', onPointerDown, { capture: true })
189
+ node.addEventListener('keydown', onKeyDown, { capture: true })
155
190
  const observer = new MutationObserver(() => decorate())
156
191
  observer.observe(node, { childList: true, subtree: true })
157
192
  decorate()
@@ -165,6 +200,7 @@ export function rowResize(node: HTMLElement, opts: RowResizeOptions) {
165
200
  },
166
201
  destroy() {
167
202
  node.removeEventListener('pointerdown', onPointerDown, { capture: true })
203
+ node.removeEventListener('keydown', onKeyDown, { capture: true })
168
204
  window.removeEventListener('pointermove', onPointerMove)
169
205
  window.removeEventListener('pointerup', onPointerUp)
170
206
  observer.disconnect()