@svgrid/grid 2.2.16 → 2.2.18

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 (290) hide show
  1. package/README.md +1 -1
  2. package/dist/GridFooter.svelte +12 -11
  3. package/dist/SvAnchor.svelte +65 -0
  4. package/dist/SvAnchor.svelte.d.ts +24 -0
  5. package/dist/SvAspectRatio.svelte +38 -0
  6. package/dist/SvAspectRatio.svelte.d.ts +16 -0
  7. package/dist/SvAutoComplete.svelte +52 -7
  8. package/dist/SvAutoComplete.svelte.d.ts +4 -1
  9. package/dist/SvBlockquote.svelte +43 -0
  10. package/dist/SvBlockquote.svelte.d.ts +16 -0
  11. package/dist/SvCalendar.svelte +33 -2
  12. package/dist/SvCalendar.svelte.d.ts +8 -1
  13. package/dist/SvCheckBox.svelte +6 -2
  14. package/dist/SvCode.svelte +52 -0
  15. package/dist/SvCode.svelte.d.ts +19 -0
  16. package/dist/SvCollapsible.svelte +75 -0
  17. package/dist/SvCollapsible.svelte.d.ts +27 -0
  18. package/dist/SvColorInput.svelte +11 -17
  19. package/dist/SvColorInput.svelte.d.ts +2 -0
  20. package/dist/SvComboBox.svelte +85 -8
  21. package/dist/SvComboBox.svelte.d.ts +14 -0
  22. package/dist/SvCountryInput.svelte +2 -2
  23. package/dist/SvCountryInput.svelte.d.ts +1 -1
  24. package/dist/SvDropDownList.svelte +154 -51
  25. package/dist/SvDropDownList.svelte.d.ts +17 -3
  26. package/dist/SvDurationInput.svelte +59 -41
  27. package/dist/SvDurationInput.svelte.d.ts +18 -0
  28. package/dist/SvField.svelte +203 -12
  29. package/dist/SvField.svelte.d.ts +45 -8
  30. package/dist/SvForm.svelte +195 -21
  31. package/dist/SvForm.svelte.d.ts +23 -0
  32. package/dist/SvGrid.controller.svelte.d.ts +12 -1
  33. package/dist/SvGrid.controller.svelte.js +87 -7
  34. package/dist/SvGrid.svelte +383 -181
  35. package/dist/SvGrid.types.d.ts +123 -2
  36. package/dist/SvGridChartView.svelte +70 -0
  37. package/dist/SvGridChartView.svelte.d.ts +11 -0
  38. package/dist/SvGridDropdown.svelte +9 -2
  39. package/dist/SvGridSelect.svelte +5 -2
  40. package/dist/SvGridSelect.svelte.d.ts +1 -1
  41. package/dist/SvGroup.svelte +41 -0
  42. package/dist/SvGroup.svelte.d.ts +27 -0
  43. package/dist/SvHoverCard.svelte +70 -0
  44. package/dist/SvHoverCard.svelte.d.ts +41 -0
  45. package/dist/SvKbd.svelte +51 -0
  46. package/dist/SvKbd.svelte.d.ts +18 -0
  47. package/dist/SvList.svelte +55 -0
  48. package/dist/SvList.svelte.d.ts +23 -0
  49. package/dist/SvListBox.svelte +166 -77
  50. package/dist/SvListBox.svelte.d.ts +17 -4
  51. package/dist/SvLoadingOverlay.svelte +53 -0
  52. package/dist/SvLoadingOverlay.svelte.d.ts +28 -0
  53. package/dist/SvMark.svelte +36 -0
  54. package/dist/SvMark.svelte.d.ts +14 -0
  55. package/dist/SvMaskedInput.svelte +50 -35
  56. package/dist/SvMaskedInput.svelte.d.ts +17 -2
  57. package/dist/SvMenuList.svelte +32 -3
  58. package/dist/SvMenubar.svelte +200 -0
  59. package/dist/SvMenubar.svelte.d.ts +18 -0
  60. package/dist/SvMultiSelect.svelte +7 -4
  61. package/dist/SvMultiSelect.svelte.d.ts +1 -1
  62. package/dist/SvNumberInput.svelte +79 -42
  63. package/dist/SvNumberInput.svelte.d.ts +25 -0
  64. package/dist/SvOtpInput.svelte +2 -2
  65. package/dist/SvPasswordInput.svelte +51 -31
  66. package/dist/SvPasswordInput.svelte.d.ts +17 -0
  67. package/dist/SvPhoneInput.svelte +39 -28
  68. package/dist/SvPhoneInput.svelte.d.ts +4 -0
  69. package/dist/SvPopconfirm.svelte +79 -0
  70. package/dist/SvPopconfirm.svelte.d.ts +42 -0
  71. package/dist/SvPopover.svelte +87 -29
  72. package/dist/SvPopover.svelte.d.ts +18 -4
  73. package/dist/SvRadioGroup.svelte +5 -1
  74. package/dist/SvResult.svelte +73 -0
  75. package/dist/SvResult.svelte.d.ts +28 -0
  76. package/dist/SvSegmented.svelte +114 -0
  77. package/dist/SvSegmented.svelte.d.ts +29 -0
  78. package/dist/SvSimpleGrid.svelte +41 -0
  79. package/dist/SvSimpleGrid.svelte.d.ts +25 -0
  80. package/dist/SvSlider.svelte +23 -10
  81. package/dist/SvSpinner.svelte +43 -0
  82. package/dist/SvSpinner.svelte.d.ts +20 -0
  83. package/dist/SvStack.svelte +37 -0
  84. package/dist/SvStack.svelte.d.ts +24 -0
  85. package/dist/SvSwitchButton.svelte +6 -1
  86. package/dist/SvTagsInput.svelte +9 -5
  87. package/dist/SvText.svelte +76 -0
  88. package/dist/SvText.svelte.d.ts +31 -0
  89. package/dist/SvTextInput.svelte +75 -43
  90. package/dist/SvTextInput.svelte.d.ts +28 -1
  91. package/dist/SvTimePicker.svelte +32 -2
  92. package/dist/SvTimePicker.svelte.d.ts +8 -9
  93. package/dist/SvTitle.svelte +57 -0
  94. package/dist/SvTitle.svelte.d.ts +24 -0
  95. package/dist/SvToaster.svelte +41 -6
  96. package/dist/SvToggleButton.svelte +4 -1
  97. package/dist/SvTooltip.svelte +68 -34
  98. package/dist/SvTooltip.svelte.d.ts +14 -2
  99. package/dist/SvTree.svelte +81 -21
  100. package/dist/SvTree.svelte.d.ts +3 -2
  101. package/dist/SvTreeSelect.svelte +5 -2
  102. package/dist/SvTreeSelect.svelte.d.ts +1 -1
  103. package/dist/SvVisuallyHidden.svelte +49 -0
  104. package/dist/SvVisuallyHidden.svelte.d.ts +19 -0
  105. package/dist/ai.d.ts +289 -0
  106. package/dist/ai.js +1013 -0
  107. package/dist/board-view.svelte.d.ts +22 -0
  108. package/dist/board-view.svelte.js +13 -0
  109. package/dist/build-api.js +36 -6
  110. package/dist/cdn/GridMenus-0AK9O7qG.js +420 -0
  111. package/dist/cdn/GridMenus-BjUoCXoD.js +416 -0
  112. package/dist/cdn/SvDateTimePicker-CYC7gRER.js +2176 -0
  113. package/dist/cdn/SvDateTimePicker-DtQKd4Ns.js +2198 -0
  114. package/dist/cdn/SvGridChart-BcLCYvw9.js +1195 -0
  115. package/dist/cdn/SvGridChart-BhnFdLd0.js +1203 -0
  116. package/dist/cdn/SvGridChartPanel-Cmx9DHAS.js +561 -0
  117. package/dist/cdn/SvGridChartPanel-DvjwxLvK.js +577 -0
  118. package/dist/cdn/SvGridChartView-B40DTJSX.js +54 -0
  119. package/dist/cdn/SvGridChartView-PwqI4s02.js +53 -0
  120. package/dist/cdn/SvGridDropdown-DjALc_4f.js +255 -0
  121. package/dist/cdn/SvGridDropdown-hpnHvOGt.js +254 -0
  122. package/dist/cdn/chart-T1usDN0o.js +1212 -0
  123. package/dist/cdn/disclose-version-DoD9AFD_.js +2958 -0
  124. package/dist/cdn/export-format-JT5pWVT8.js +561 -0
  125. package/dist/cdn/popover-P-9P2YYn.js +69 -0
  126. package/dist/cdn/rolldown-runtime-Dy4uBu1J.js +11 -0
  127. package/dist/cdn/src-BLhdLmoN.js +25707 -0
  128. package/dist/cdn/src-BwnUVj3T.js +25623 -0
  129. package/dist/cdn/svgrid.js +9 -32897
  130. package/dist/cdn/svgrid.svelte-external.js +9 -30052
  131. package/dist/cell-formatting.d.ts +2 -2
  132. package/dist/cell-render.js +4 -4
  133. package/dist/chart-view.svelte.d.ts +25 -0
  134. package/dist/chart-view.svelte.js +14 -0
  135. package/dist/createCombobox.svelte.d.ts +5 -0
  136. package/dist/createCombobox.svelte.js +21 -5
  137. package/dist/createDropdownList.svelte.d.ts +3 -0
  138. package/dist/createDropdownList.svelte.js +8 -2
  139. package/dist/createForm.svelte.d.ts +18 -0
  140. package/dist/createForm.svelte.js +51 -6
  141. package/dist/createMenu.svelte.js +27 -0
  142. package/dist/createPopoverSelect.svelte.d.ts +3 -0
  143. package/dist/createPopoverSelect.svelte.js +9 -1
  144. package/dist/createSlider.svelte.d.ts +8 -21
  145. package/dist/createSlider.svelte.js +34 -0
  146. package/dist/createTooltip.svelte.d.ts +12 -0
  147. package/dist/createTooltip.svelte.js +72 -9
  148. package/dist/editor-contract.d.ts +15 -0
  149. package/dist/export-provider.d.ts +27 -0
  150. package/dist/export-provider.js +9 -0
  151. package/dist/filter-operators.d.ts +6 -0
  152. package/dist/filter-operators.js +33 -0
  153. package/dist/form-field.d.ts +45 -2
  154. package/dist/grid-messages.d.ts +61 -0
  155. package/dist/grid-messages.js +72 -0
  156. package/dist/group-display.d.ts +62 -0
  157. package/dist/group-display.js +78 -0
  158. package/dist/index.d.ts +36 -4
  159. package/dist/index.js +43 -2
  160. package/dist/js-scroller.svelte.d.ts +35 -0
  161. package/dist/js-scroller.svelte.js +154 -0
  162. package/dist/list-option.d.ts +46 -0
  163. package/dist/list-option.js +53 -0
  164. package/dist/menus.js +25 -11
  165. package/dist/pivot-view.svelte.d.ts +72 -0
  166. package/dist/pivot-view.svelte.js +13 -0
  167. package/dist/positioning.d.ts +111 -0
  168. package/dist/positioning.js +191 -0
  169. package/dist/scheduler-model.d.ts +3 -0
  170. package/dist/scheduler-model.js +22 -5
  171. package/dist/svgrid-wrapper.types.d.ts +18 -0
  172. package/dist/toast-store.svelte.d.ts +54 -0
  173. package/dist/toast-store.svelte.js +65 -15
  174. package/package.json +9 -1
  175. package/src/GridFooter.svelte +12 -11
  176. package/src/SvAnchor.svelte +65 -0
  177. package/src/SvAspectRatio.svelte +38 -0
  178. package/src/SvAutoComplete.svelte +52 -7
  179. package/src/SvBlockquote.svelte +43 -0
  180. package/src/SvCalendar.svelte +33 -2
  181. package/src/SvCalendar.test.ts +15 -0
  182. package/src/SvCheckBox.svelte +6 -2
  183. package/src/SvCode.svelte +52 -0
  184. package/src/SvCollapsible.svelte +75 -0
  185. package/src/SvColorInput.svelte +11 -17
  186. package/src/SvComboBox.svelte +85 -8
  187. package/src/SvCountryInput.svelte +2 -2
  188. package/src/SvDropDownList.svelte +154 -51
  189. package/src/SvDurationInput.svelte +59 -41
  190. package/src/SvField.svelte +203 -12
  191. package/src/SvForm.svelte +195 -21
  192. package/src/SvForm.test.ts +112 -0
  193. package/src/SvGrid.controller.svelte.ts +88 -7
  194. package/src/SvGrid.svelte +383 -181
  195. package/src/SvGrid.types.ts +125 -1
  196. package/src/SvGridChartView.svelte +70 -0
  197. package/src/SvGridDropdown.svelte +9 -2
  198. package/src/SvGridSelect.svelte +5 -2
  199. package/src/SvGroup.svelte +41 -0
  200. package/src/SvHoverCard.svelte +70 -0
  201. package/src/SvKbd.svelte +51 -0
  202. package/src/SvList.svelte +55 -0
  203. package/src/SvListBox.svelte +166 -77
  204. package/src/SvLoadingOverlay.svelte +53 -0
  205. package/src/SvMark.svelte +36 -0
  206. package/src/SvMaskedInput.svelte +50 -35
  207. package/src/SvMenuList.svelte +32 -3
  208. package/src/SvMenubar.svelte +200 -0
  209. package/src/SvMultiSelect.svelte +7 -4
  210. package/src/SvNumberInput.svelte +79 -42
  211. package/src/SvOtpInput.svelte +2 -2
  212. package/src/SvPasswordInput.svelte +51 -31
  213. package/src/SvPhoneInput.svelte +39 -28
  214. package/src/SvPopconfirm.svelte +79 -0
  215. package/src/SvPopover.svelte +87 -29
  216. package/src/SvRadioGroup.svelte +5 -1
  217. package/src/SvResult.svelte +73 -0
  218. package/src/SvSegmented.svelte +114 -0
  219. package/src/SvSimpleGrid.svelte +41 -0
  220. package/src/SvSlider.svelte +23 -10
  221. package/src/SvSpinner.svelte +43 -0
  222. package/src/SvStack.svelte +37 -0
  223. package/src/SvSwitchButton.svelte +6 -1
  224. package/src/SvTagsInput.svelte +9 -5
  225. package/src/SvText.svelte +76 -0
  226. package/src/SvTextInput.svelte +75 -43
  227. package/src/SvTimePicker.svelte +32 -2
  228. package/src/SvTitle.svelte +57 -0
  229. package/src/SvToaster.svelte +41 -6
  230. package/src/SvToaster.test.ts +32 -0
  231. package/src/SvToggleButton.svelte +4 -1
  232. package/src/SvTooltip.svelte +68 -34
  233. package/src/SvTree.svelte +81 -21
  234. package/src/SvTreeSelect.svelte +5 -2
  235. package/src/SvVisuallyHidden.svelte +49 -0
  236. package/src/ai.test.ts +502 -0
  237. package/src/ai.ts +1391 -0
  238. package/src/board-view.svelte.ts +33 -0
  239. package/src/build-api.coverage.test.ts +101 -0
  240. package/src/build-api.ts +43 -18
  241. package/src/builtin-editors.grid.test.ts +1 -1
  242. package/src/cell-formatting.ts +5 -5
  243. package/src/cell-render.ts +4 -4
  244. package/src/chart-view.svelte.ts +36 -0
  245. package/src/createCombobox.svelte.ts +17 -5
  246. package/src/createDropdownList.svelte.ts +8 -2
  247. package/src/createForm.svelte.ts +59 -6
  248. package/src/createMenu.svelte.ts +23 -0
  249. package/src/createPopoverSelect.svelte.ts +8 -1
  250. package/src/createSlider.svelte.ts +20 -0
  251. package/src/createTooltip.svelte.ts +67 -9
  252. package/src/editor-contract.ts +16 -0
  253. package/src/export-provider.ts +40 -0
  254. package/src/filter-operators.ts +37 -0
  255. package/src/form-field.ts +42 -2
  256. package/src/grid-messages.test.ts +24 -0
  257. package/src/grid-messages.ts +137 -0
  258. package/src/group-display.test.ts +77 -0
  259. package/src/group-display.ts +117 -0
  260. package/src/i18n.grid.test.ts +73 -0
  261. package/src/index.ts +91 -2
  262. package/src/js-scroller.svelte.ts +173 -0
  263. package/src/list-option.test.ts +56 -0
  264. package/src/list-option.ts +81 -0
  265. package/src/menus.test.ts +32 -0
  266. package/src/menus.ts +25 -12
  267. package/src/pivot-view.svelte.ts +90 -0
  268. package/src/pivot-view.test.ts +38 -0
  269. package/src/pivot.grid.test.ts +102 -0
  270. package/src/positioning.test.ts +141 -0
  271. package/src/positioning.ts +266 -0
  272. package/src/scheduler-model.test.ts +16 -0
  273. package/src/scheduler-model.ts +24 -5
  274. package/src/svgrid-wrapper.types.ts +23 -0
  275. package/src/svgrid.charting.test.ts +8 -1
  276. package/src/svgrid.comments-autocomplete.test.ts +7 -2
  277. package/src/svgrid.context-menu.test.ts +8 -3
  278. package/src/toast-store.svelte.ts +98 -0
  279. package/src/toast-store.test.ts +62 -1
  280. package/src/ui-breadth.test.ts +138 -0
  281. package/src/ui-buttons.test.ts +39 -0
  282. package/src/ui-inputs.test.ts +2 -2
  283. package/src/ui-range.test.ts +9 -0
  284. package/src/ui-selection.test.ts +36 -0
  285. package/src/ui-tier1.test.ts +1 -1
  286. package/src/virtual.test.ts +20 -6
  287. package/dist/SvGridBoard.svelte +0 -2339
  288. package/dist/SvGridBoard.svelte.d.ts +0 -31
  289. package/src/SvGridBoard.svelte +0 -2339
  290. package/src/board.test.ts +0 -927
@@ -3,7 +3,7 @@
3
3
  * manual dismiss, sticky toasts, and the SR announcement side effect.
4
4
  */
5
5
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
6
- import { toast, dismissToast, clearToasts, pauseToast, resumeToast, toastStore } from './toast-store.svelte'
6
+ import { toast, dismissToast, updateToast, clearToasts, pauseToast, resumeToast, toastStore } from './toast-store.svelte'
7
7
 
8
8
  beforeEach(() => {
9
9
  vi.useFakeTimers()
@@ -83,4 +83,65 @@ describe('toast store', () => {
83
83
  clearToasts()
84
84
  expect(toastStore.toasts).toHaveLength(0)
85
85
  })
86
+
87
+ it('update patches a toast in place (same id) and restarts its timer', () => {
88
+ const id = toast('working', { duration: 0 })
89
+ updateToast(id, { message: 'done', variant: 'success', duration: 3000 })
90
+ expect(toastStore.toasts).toHaveLength(1)
91
+ const t = toastStore.toasts[0]!
92
+ expect(t.id).toBe(id)
93
+ expect(t.message).toBe('done')
94
+ expect(t.variant).toBe('success')
95
+ // The new duration now applies.
96
+ vi.advanceTimersByTime(3000)
97
+ expect(toastStore.toasts).toHaveLength(0)
98
+ })
99
+
100
+ it('update is a no-op for an unknown id', () => {
101
+ updateToast(999, { message: 'nope' })
102
+ expect(toastStore.toasts).toHaveLength(0)
103
+ })
104
+
105
+ it('promise shows a sticky loader, then updates in place to success', async () => {
106
+ vi.useRealTimers() // real microtask flush for the promise
107
+ let resolve!: (v: string) => void
108
+ const p = new Promise<string>((r) => (resolve = r))
109
+ const ret = toast.promise(p, { loading: 'Saving...', success: (v) => `Saved ${v}`, error: 'Failed' })
110
+ expect(ret).toBe(p) // returns the original promise
111
+ expect(toastStore.toasts).toHaveLength(1)
112
+ const id = toastStore.toasts[0]!.id
113
+ expect(toastStore.toasts[0]!.message).toBe('Saving...')
114
+ expect(toastStore.toasts[0]!.variant).toBe('info')
115
+ resolve('now')
116
+ await p
117
+ await Promise.resolve()
118
+ const t = toastStore.toasts.find((x) => x.id === id)!
119
+ expect(t.message).toBe('Saved now')
120
+ expect(t.variant).toBe('success')
121
+ })
122
+
123
+ it('promise updates in place to error on rejection', async () => {
124
+ vi.useRealTimers()
125
+ const p = Promise.reject(new Error('x'))
126
+ toast.promise(p, { loading: 'Load', success: 'ok', error: (e) => `Err: ${(e as Error).message}` })
127
+ await p.catch(() => {})
128
+ await Promise.resolve()
129
+ const t = toastStore.toasts[0]!
130
+ expect(t.variant).toBe('error')
131
+ expect(t.message).toBe('Err: x')
132
+ })
133
+
134
+ it('action + cancel are stored on the toast', () => {
135
+ const onClick = vi.fn()
136
+ toast('Undo?', { action: { label: 'Undo', onClick }, cancel: { label: 'Dismiss' } })
137
+ const t = toastStore.toasts[0]!
138
+ expect(t.action?.label).toBe('Undo')
139
+ expect(t.cancel?.label).toBe('Dismiss')
140
+ })
141
+
142
+ it('custom carries a render snippet', () => {
143
+ const render = (() => {}) as never
144
+ const id = toast.custom(render, { duration: 0 })
145
+ expect(toastStore.toasts.find((x) => x.id === id)!.render).toBe(render)
146
+ })
86
147
  })
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Tests for the Pillar C breadth components: SvCollapsible (accessible toggle),
3
+ * SvResult (status), SvLoadingOverlay (scrim), and the layout primitives.
4
+ */
5
+ import { describe, expect, it } from 'vitest'
6
+ import { mount, unmount, flushSync } from 'svelte'
7
+ import SvCollapsible from './SvCollapsible.svelte'
8
+ import SvResult from './SvResult.svelte'
9
+ import SvLoadingOverlay from './SvLoadingOverlay.svelte'
10
+ import SvSimpleGrid from './SvSimpleGrid.svelte'
11
+ import SvTitle from './SvTitle.svelte'
12
+ import SvText from './SvText.svelte'
13
+ import SvAnchor from './SvAnchor.svelte'
14
+ import SvKbd from './SvKbd.svelte'
15
+ import SvCode from './SvCode.svelte'
16
+ import SvVisuallyHidden from './SvVisuallyHidden.svelte'
17
+
18
+ function mnt(Comp: any, props: Record<string, unknown>) {
19
+ const target = document.createElement('div')
20
+ document.body.appendChild(target)
21
+ const app = mount(Comp, { target, props: props as any })
22
+ flushSync()
23
+ return { target, destroy: () => { unmount(app); target.remove() } }
24
+ }
25
+
26
+ describe('SvCollapsible', () => {
27
+ it('toggles aria-expanded and wires the region', () => {
28
+ let open: boolean | undefined
29
+ const { target, destroy } = mnt(SvCollapsible, { title: 'More', onOpenChange: (o: boolean) => (open = o) })
30
+ try {
31
+ const trigger = target.querySelector<HTMLButtonElement>('.sv-collapse__trigger')!
32
+ expect(trigger.getAttribute('aria-expanded')).toBe('false')
33
+ const region = target.querySelector('.sv-collapse__region')!
34
+ expect(trigger.getAttribute('aria-controls')).toBe(region.id)
35
+ trigger.click(); flushSync()
36
+ expect(trigger.getAttribute('aria-expanded')).toBe('true')
37
+ expect(open).toBe(true)
38
+ } finally { destroy() }
39
+ })
40
+ it('disabled does not toggle', () => {
41
+ const { target, destroy } = mnt(SvCollapsible, { title: 'X', disabled: true })
42
+ try {
43
+ const trigger = target.querySelector<HTMLButtonElement>('.sv-collapse__trigger')!
44
+ trigger.click(); flushSync()
45
+ expect(trigger.getAttribute('aria-expanded')).toBe('false')
46
+ } finally { destroy() }
47
+ })
48
+ })
49
+
50
+ describe('SvResult', () => {
51
+ it('renders the status class, title and description', () => {
52
+ const { target, destroy } = mnt(SvResult, { status: 'success', title: 'Done', description: 'All good' })
53
+ try {
54
+ expect(target.querySelector('.sv-result--success')).not.toBeNull()
55
+ expect(target.querySelector('.sv-result__title')?.textContent).toBe('Done')
56
+ expect(target.querySelector('.sv-result__desc')?.textContent).toBe('All good')
57
+ } finally { destroy() }
58
+ })
59
+ })
60
+
61
+ describe('SvLoadingOverlay', () => {
62
+ it('renders only when visible, with a polite status role', () => {
63
+ const hidden = mnt(SvLoadingOverlay, { visible: false })
64
+ try { expect(hidden.target.querySelector('.sv-loading')).toBeNull() } finally { hidden.destroy() }
65
+ const shown = mnt(SvLoadingOverlay, { visible: true, label: 'Saving' })
66
+ try {
67
+ const el = shown.target.querySelector('.sv-loading')!
68
+ expect(el).not.toBeNull()
69
+ expect(el.getAttribute('role')).toBe('status')
70
+ expect(el.getAttribute('aria-label')).toBe('Saving')
71
+ } finally { shown.destroy() }
72
+ })
73
+ })
74
+
75
+ describe('SvSimpleGrid', () => {
76
+ it('uses a fixed column template when cols is set', () => {
77
+ const { target, destroy } = mnt(SvSimpleGrid, { cols: 3 })
78
+ try {
79
+ const el = target.querySelector<HTMLElement>('.sv-simplegrid')!
80
+ expect(el.style.gridTemplateColumns).toContain('repeat(3')
81
+ } finally { destroy() }
82
+ })
83
+ })
84
+
85
+ describe('SvTitle', () => {
86
+ it('renders the heading tag for its order', () => {
87
+ const { target, destroy } = mnt(SvTitle, { order: 3, children: undefined })
88
+ try { expect(target.querySelector('h3')).not.toBeNull() } finally { destroy() }
89
+ })
90
+ })
91
+
92
+ describe('SvText', () => {
93
+ it('applies the line clamp when clamp is set', () => {
94
+ const { target, destroy } = mnt(SvText, { clamp: 2 })
95
+ try {
96
+ const el = target.querySelector<HTMLElement>('.sv-text')!
97
+ expect(el.classList.contains('is-clamp')).toBe(true)
98
+ expect(el.style.getPropertyValue('--sv-text-clamp')).toBe('2')
99
+ } finally { destroy() }
100
+ })
101
+ })
102
+
103
+ describe('SvAnchor', () => {
104
+ it('adds safe rel and target for external links', () => {
105
+ const { target, destroy } = mnt(SvAnchor, { href: 'https://svelte.dev', external: true })
106
+ try {
107
+ const a = target.querySelector('a')!
108
+ expect(a.getAttribute('target')).toBe('_blank')
109
+ expect(a.getAttribute('rel')).toBe('noopener noreferrer')
110
+ } finally { destroy() }
111
+ })
112
+ })
113
+
114
+ describe('SvKbd', () => {
115
+ it('renders one <kbd> per key in a combo', () => {
116
+ const { target, destroy } = mnt(SvKbd, { keys: ['Ctrl', 'K'] })
117
+ try { expect(target.querySelectorAll('kbd').length).toBe(2) } finally { destroy() }
118
+ })
119
+ })
120
+
121
+ describe('SvCode', () => {
122
+ it('renders a <pre> in block mode and inline <code> otherwise', () => {
123
+ const block = mnt(SvCode, { block: true, code: 'a' })
124
+ try { expect(block.target.querySelector('pre.sv-code--block')).not.toBeNull() } finally { block.destroy() }
125
+ const inline = mnt(SvCode, { code: 'a' })
126
+ try {
127
+ expect(inline.target.querySelector('pre')).toBeNull()
128
+ expect(inline.target.querySelector('code.sv-code--inline')).not.toBeNull()
129
+ } finally { inline.destroy() }
130
+ })
131
+ })
132
+
133
+ describe('SvVisuallyHidden', () => {
134
+ it('renders content in an off-screen element', () => {
135
+ const { target, destroy } = mnt(SvVisuallyHidden, { children: undefined })
136
+ try { expect(target.querySelector('.sv-vh')).not.toBeNull() } finally { destroy() }
137
+ })
138
+ })
@@ -9,6 +9,7 @@ import SvSwitchButton from './SvSwitchButton.svelte'
9
9
  import SvCheckBox from './SvCheckBox.svelte'
10
10
  import SvRadioGroup from './SvRadioGroup.svelte'
11
11
  import SvRating from './SvRating.svelte'
12
+ import SvSegmented from './SvSegmented.svelte'
12
13
 
13
14
  function mnt(Comp: any, props: Record<string, unknown>) {
14
15
  const target = document.createElement('div')
@@ -52,6 +53,17 @@ describe('SvSwitchButton', () => {
52
53
  expect(states.at(-1)).toBe(true)
53
54
  } finally { destroy() }
54
55
  })
56
+ it('readonly blocks the change but stays focusable + aria-readonly', () => {
57
+ let got: boolean | undefined
58
+ const { target, destroy } = mnt(SvSwitchButton, { checked: false, readonly: true, onChange: (v: boolean) => (got = v) })
59
+ try {
60
+ const sw = target.querySelector<HTMLButtonElement>('[role="switch"]')!
61
+ expect(sw.getAttribute('aria-readonly')).toBe('true')
62
+ expect(sw.hasAttribute('disabled')).toBe(false) // focusable, unlike disabled
63
+ sw.click(); flushSync()
64
+ expect(got).toBeUndefined() // no change emitted
65
+ } finally { destroy() }
66
+ })
55
67
  })
56
68
 
57
69
  describe('SvCheckBox', () => {
@@ -68,6 +80,16 @@ describe('SvCheckBox', () => {
68
80
  expect(b.target.querySelector('[role="checkbox"]')!.getAttribute('aria-checked')).toBe('mixed')
69
81
  } finally { b.destroy() }
70
82
  })
83
+ it('readonly blocks the toggle', () => {
84
+ let got: boolean | undefined
85
+ const { target, destroy } = mnt(SvCheckBox, { checked: false, readonly: true, onChange: (v: boolean) => (got = v) })
86
+ try {
87
+ const box = target.querySelector<HTMLButtonElement>('[role="checkbox"]')!
88
+ expect(box.getAttribute('aria-readonly')).toBe('true')
89
+ box.click(); flushSync()
90
+ expect(got).toBeUndefined()
91
+ } finally { destroy() }
92
+ })
71
93
  })
72
94
 
73
95
  describe('SvRadioGroup', () => {
@@ -103,3 +125,20 @@ describe('SvRating', () => {
103
125
  } finally { destroy() }
104
126
  })
105
127
  })
128
+
129
+ describe('SvSegmented', () => {
130
+ const opts = [{ value: 'list', label: 'List' }, { value: 'grid', label: 'Grid' }, { value: 'map', label: 'Map' }]
131
+ it('is a radiogroup; clicking selects and reports the value', () => {
132
+ let got: unknown
133
+ const { target, destroy } = mnt(SvSegmented, { options: opts, value: 'list', onChange: (v: unknown) => (got = v) })
134
+ try {
135
+ expect(target.querySelector('[role="radiogroup"]')).not.toBeNull()
136
+ const radios = target.querySelectorAll<HTMLButtonElement>('[role="radio"]')
137
+ expect(radios.length).toBe(3)
138
+ expect(radios[0]!.getAttribute('aria-checked')).toBe('true')
139
+ radios[2]!.click(); flushSync()
140
+ expect(got).toBe('map')
141
+ expect(radios[2]!.getAttribute('aria-checked')).toBe('true')
142
+ } finally { destroy() }
143
+ })
144
+ })
@@ -25,7 +25,7 @@ describe('SvNumberInput validation a11y (editor contract)', () => {
25
25
  const err = target.querySelector('#qty__error')!
26
26
  expect(err.getAttribute('role')).toBe('alert')
27
27
  expect(err.textContent).toBe('Too big')
28
- expect(target.querySelector('.sv-num.is-invalid')).toBeTruthy()
28
+ expect(target.querySelector('.sv-field__control.is-invalid')).toBeTruthy()
29
29
  } finally { destroy() }
30
30
  })
31
31
 
@@ -35,7 +35,7 @@ describe('SvNumberInput validation a11y (editor contract)', () => {
35
35
  const input = target.querySelector<HTMLInputElement>('.sv-num__input')!
36
36
  expect(input.getAttribute('aria-invalid')).toBeNull()
37
37
  expect(input.getAttribute('aria-describedby')).toBeNull()
38
- expect(target.querySelector('.sv-num.is-invalid')).toBeNull()
38
+ expect(target.querySelector('.sv-field__control.is-invalid')).toBeNull()
39
39
  } finally { destroy() }
40
40
  })
41
41
  })
@@ -38,6 +38,15 @@ describe('SvSlider', () => {
38
38
  expect(got).toEqual([30, 80])
39
39
  } finally { destroy() }
40
40
  })
41
+ it('wires validation ARIA onto the thumb (aria-invalid + describedby)', () => {
42
+ const { target, destroy } = mnt(SvSlider, { value: 50, invalid: true, error: 'Out of range', id: 'vol', required: true })
43
+ try {
44
+ const thumb = target.querySelector<HTMLElement>('[role="slider"]')!
45
+ expect(thumb.getAttribute('aria-invalid')).toBe('true')
46
+ expect(thumb.getAttribute('aria-required')).toBe('true')
47
+ expect(thumb.getAttribute('aria-describedby')).toContain('vol__error')
48
+ } finally { destroy() }
49
+ })
41
50
  })
42
51
 
43
52
  describe('SvGauge', () => {
@@ -67,6 +67,31 @@ describe('SvDropDownList', () => {
67
67
  expect(document.querySelector('.sv-ddl__panel')).toBeNull() // closed
68
68
  } finally { destroy() }
69
69
  })
70
+ it('readonly does not open the panel and marks aria-readonly', () => {
71
+ let got: any
72
+ const { target, destroy } = mnt(SvDropDownList, { options: OPTS, value: 'a', readonly: true, onChange: (v: any) => (got = v) })
73
+ try {
74
+ const trigger = target.querySelector<HTMLButtonElement>('.sv-ddl')!
75
+ expect(trigger.getAttribute('aria-readonly')).toBe('true')
76
+ expect(trigger.hasAttribute('disabled')).toBe(false) // still focusable
77
+ trigger.click(); flushSync()
78
+ expect(document.querySelector('.sv-ddl__panel')).toBeNull() // stayed closed
79
+ expect(got).toBeUndefined()
80
+ } finally { destroy() }
81
+ })
82
+ it('renders a resize grip only when resizable and the panel opens downward', () => {
83
+ const off = mnt(SvDropDownList, { options: OPTS, value: null })
84
+ try {
85
+ off.target.querySelector<HTMLButtonElement>('.sv-ddl')!.click(); flushSync()
86
+ expect(document.querySelector('.sv-ddl__grip')).toBeNull() // no grip by default
87
+ } finally { off.destroy() }
88
+ const on = mnt(SvDropDownList, { options: OPTS, value: null, resizable: true })
89
+ try {
90
+ on.target.querySelector<HTMLButtonElement>('.sv-ddl')!.click(); flushSync()
91
+ expect(document.querySelector('.sv-ddl__panel.is-resizable')).not.toBeNull()
92
+ expect(document.querySelector('.sv-ddl__grip')).not.toBeNull()
93
+ } finally { on.destroy() }
94
+ })
70
95
  })
71
96
 
72
97
  describe('SvComboBox', () => {
@@ -84,6 +109,17 @@ describe('SvComboBox', () => {
84
109
  expect(got).toBe('b')
85
110
  } finally { destroy() }
86
111
  })
112
+ it('shows a resize grip on the open panel when resizable', () => {
113
+ const { target, destroy } = mnt(SvComboBox, { options: OPTS, value: null, resizable: true })
114
+ try {
115
+ const input = target.querySelector<HTMLInputElement>('.sv-combo__input')!
116
+ input.dispatchEvent(new FocusEvent('focus')); flushSync()
117
+ input.value = 'b'
118
+ input.dispatchEvent(new Event('input', { bubbles: true })); flushSync()
119
+ expect(document.querySelector('.sv-ddl__panel.is-resizable')).not.toBeNull()
120
+ expect(document.querySelector('.sv-ddl__grip')).not.toBeNull()
121
+ } finally { destroy() }
122
+ })
87
123
  })
88
124
 
89
125
  describe('SvTagsInput', () => {
@@ -46,7 +46,7 @@ describe('SvTextInput', () => {
46
46
  const onChange = vi.fn()
47
47
  const { target, destroy } = mountOn(SvTextInput, { value: 'x', clearable: true, onChange })
48
48
  try {
49
- const clear = target.querySelector<HTMLButtonElement>('.sv-text__clear')!
49
+ const clear = target.querySelector<HTMLButtonElement>('.sv-field__clear')!
50
50
  expect(clear).not.toBeNull()
51
51
  clear.click()
52
52
  expect(onChange).toHaveBeenCalledWith('')
@@ -48,12 +48,26 @@ describe('SvListBox virtual', () => {
48
48
  const rendered = target.querySelectorAll('.sv-listbox__opt').length
49
49
  expect(rendered).toBeGreaterThan(0)
50
50
  expect(rendered).toBeLessThan(60) // nowhere near 5000
51
- // Top + bottom spacers + the windowed rows reserve the full scroll height,
52
- // keeping the scrollbar honest without positioning rows out of flow.
53
- const spacers = target.querySelectorAll<HTMLElement>('.sv-listbox__spacer')
54
- expect(spacers.length).toBe(2)
55
- const spacerPx = [...spacers].reduce((sum, s) => sum + parseFloat(s.style.height || '0'), 0)
56
- expect(spacerPx + rendered * 32).toBe(5000 * 32)
51
+ // Virtual mode is a JS-driven scroller: one clipped viewport holds the
52
+ // windowed rows (moved by `scrollOffset`, not native scroll) plus a custom
53
+ // scrollbar - so there is no native async-scroll gap to flash blank.
54
+ expect(target.querySelector('.sv-listbox__vp')).not.toBeNull()
55
+ expect(target.querySelector('.sv-listbox__sb')).not.toBeNull()
56
+ } finally { unmount(app); target.remove() }
57
+ })
58
+
59
+ it('windows a GROUPED list too (group headings render inside the window)', () => {
60
+ const roles = ['Eng', 'Design', 'Ops']
61
+ const options = Array.from({ length: 3000 }, (_, i) => ({ value: i, label: `User ${i}`, group: roles[i % 3] }))
62
+ const target = document.createElement('div')
63
+ document.body.appendChild(target)
64
+ const app = mount(SvListBox, { target, props: { options, virtual: true } as any })
65
+ flushSync()
66
+ try {
67
+ const rendered = target.querySelectorAll('.sv-listbox__opt').length
68
+ expect(rendered).toBeGreaterThan(0)
69
+ expect(rendered).toBeLessThan(60) // grouped list is windowed, not fully rendered
70
+ expect(target.querySelector('.sv-listbox__vp')).not.toBeNull()
57
71
  } finally { unmount(app); target.remove() }
58
72
  })
59
73