@svgrid/grid 1.2.34 → 2.0.0

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 (490) hide show
  1. package/README.md +6 -5
  2. package/dist/FlexRender.svelte +96 -96
  3. package/dist/GridFooter.svelte +178 -178
  4. package/dist/SvAccordion.svelte +109 -0
  5. package/dist/SvAccordion.svelte.d.ts +28 -0
  6. package/dist/SvAlert.svelte +92 -0
  7. package/dist/SvAlert.svelte.d.ts +27 -0
  8. package/dist/SvAutoComplete.svelte +47 -18
  9. package/dist/SvAutoComplete.svelte.d.ts +2 -5
  10. package/dist/SvAvatar.svelte +75 -0
  11. package/dist/SvAvatar.svelte.d.ts +14 -0
  12. package/dist/SvAvatarGroup.svelte +48 -0
  13. package/dist/SvAvatarGroup.svelte.d.ts +16 -0
  14. package/dist/SvBadge.svelte +54 -0
  15. package/dist/SvBadge.svelte.d.ts +23 -0
  16. package/dist/SvBreadcrumb.svelte +88 -0
  17. package/dist/SvBreadcrumb.svelte.d.ts +23 -0
  18. package/dist/SvButtonGroup.svelte +121 -0
  19. package/dist/SvButtonGroup.svelte.d.ts +16 -0
  20. package/dist/SvCalendar.svelte +72 -9
  21. package/dist/SvCalendar.svelte.d.ts +34 -1
  22. package/dist/SvCard.svelte +71 -0
  23. package/dist/SvCard.svelte.d.ts +28 -0
  24. package/dist/SvCarousel.svelte +134 -0
  25. package/dist/SvCarousel.svelte.d.ts +28 -0
  26. package/dist/SvCheckBox.svelte +36 -21
  27. package/dist/SvCheckBox.svelte.d.ts +2 -5
  28. package/dist/SvChip.svelte +88 -0
  29. package/dist/SvChip.svelte.d.ts +31 -0
  30. package/dist/SvCircularProgress.svelte +109 -0
  31. package/dist/SvCircularProgress.svelte.d.ts +31 -0
  32. package/dist/SvColorInput.svelte +54 -27
  33. package/dist/SvColorInput.svelte.d.ts +10 -6
  34. package/dist/SvComboBox.svelte +119 -33
  35. package/dist/SvComboBox.svelte.d.ts +18 -6
  36. package/dist/SvCommand.svelte +197 -0
  37. package/dist/SvCommand.svelte.d.ts +26 -0
  38. package/dist/SvContextMenu.svelte +96 -0
  39. package/dist/SvContextMenu.svelte.d.ts +25 -0
  40. package/dist/SvCountryInput.svelte +56 -21
  41. package/dist/SvCountryInput.svelte.d.ts +9 -5
  42. package/dist/SvDateRangeInput.svelte +256 -0
  43. package/dist/SvDateRangeInput.svelte.d.ts +37 -0
  44. package/dist/SvDateTimePicker.svelte +57 -20
  45. package/dist/SvDateTimePicker.svelte.d.ts +12 -4
  46. package/dist/SvDivider.svelte +61 -0
  47. package/dist/SvDivider.svelte.d.ts +22 -0
  48. package/dist/SvDrawer.svelte +226 -0
  49. package/dist/SvDrawer.svelte.d.ts +43 -0
  50. package/dist/SvDropDownList.svelte +114 -35
  51. package/dist/SvDropDownList.svelte.d.ts +7 -6
  52. package/dist/SvDurationInput.svelte +108 -0
  53. package/dist/SvDurationInput.svelte.d.ts +15 -0
  54. package/dist/SvEmptyState.svelte +59 -0
  55. package/dist/SvEmptyState.svelte.d.ts +25 -0
  56. package/dist/SvField.svelte +85 -0
  57. package/dist/SvField.svelte.d.ts +30 -0
  58. package/dist/SvFileUpload.svelte +169 -0
  59. package/dist/SvFileUpload.svelte.d.ts +24 -0
  60. package/dist/SvForm.svelte +7 -1
  61. package/dist/SvForm.svelte.d.ts +3 -0
  62. package/dist/SvGauge.svelte +7 -1
  63. package/dist/SvGauge.svelte.d.ts +11 -0
  64. package/dist/SvGrid.controller.svelte.d.ts +6 -0
  65. package/dist/SvGrid.controller.svelte.js +64 -9
  66. package/dist/SvGrid.css +2376 -2343
  67. package/dist/SvGrid.svelte +2764 -2651
  68. package/dist/SvGrid.types.d.ts +366 -0
  69. package/dist/SvGridBoard.svelte +2281 -0
  70. package/dist/SvGridBoard.svelte.d.ts +31 -0
  71. package/dist/SvGridDropdown.svelte +666 -665
  72. package/dist/SvGridSelect.svelte +255 -0
  73. package/dist/SvGridSelect.svelte.d.ts +32 -0
  74. package/dist/SvGroupCell.svelte +109 -0
  75. package/dist/SvGroupCell.svelte.d.ts +41 -0
  76. package/dist/SvListBox.svelte +123 -36
  77. package/dist/SvListBox.svelte.d.ts +12 -8
  78. package/dist/SvMaskedInput.svelte +53 -13
  79. package/dist/SvMaskedInput.svelte.d.ts +16 -6
  80. package/dist/SvMenu.svelte +101 -0
  81. package/dist/SvMenu.svelte.d.ts +26 -0
  82. package/dist/SvMenuList.svelte +143 -0
  83. package/dist/SvMenuList.svelte.d.ts +24 -0
  84. package/dist/SvModal.svelte +220 -0
  85. package/dist/SvModal.svelte.d.ts +31 -0
  86. package/dist/SvMultiSelect.svelte +277 -0
  87. package/dist/SvMultiSelect.svelte.d.ts +31 -0
  88. package/dist/SvNavPane.svelte +314 -0
  89. package/dist/SvNavPane.svelte.d.ts +59 -0
  90. package/dist/SvNumberInput.svelte +23 -9
  91. package/dist/SvNumberInput.svelte.d.ts +2 -0
  92. package/dist/SvOtpInput.svelte +157 -0
  93. package/dist/SvOtpInput.svelte.d.ts +16 -0
  94. package/dist/SvPagination.svelte +100 -0
  95. package/dist/SvPagination.svelte.d.ts +16 -0
  96. package/dist/SvPasswordInput.svelte +42 -12
  97. package/dist/SvPasswordInput.svelte.d.ts +13 -6
  98. package/dist/SvPhoneInput.svelte +43 -11
  99. package/dist/SvPhoneInput.svelte.d.ts +10 -11
  100. package/dist/SvPopover.svelte +127 -0
  101. package/dist/SvPopover.svelte.d.ts +35 -0
  102. package/dist/SvProgress.svelte +116 -0
  103. package/dist/SvProgress.svelte.d.ts +32 -0
  104. package/dist/SvRadioGroup.svelte +40 -22
  105. package/dist/SvRadioGroup.svelte.d.ts +2 -10
  106. package/dist/SvRating.svelte +51 -30
  107. package/dist/SvRating.svelte.d.ts +10 -7
  108. package/dist/SvRichText.svelte +192 -0
  109. package/dist/SvRichText.svelte.d.ts +16 -0
  110. package/dist/SvRowGroupPanel.svelte +147 -0
  111. package/dist/SvRowGroupPanel.svelte.d.ts +17 -0
  112. package/dist/SvScrollArea.svelte +50 -0
  113. package/dist/SvScrollArea.svelte.d.ts +22 -0
  114. package/dist/SvSkeleton.svelte +76 -0
  115. package/dist/SvSkeleton.svelte.d.ts +25 -0
  116. package/dist/SvSlider.svelte +41 -9
  117. package/dist/SvSlider.svelte.d.ts +4 -4
  118. package/dist/SvSparkline.svelte +77 -0
  119. package/dist/SvSparkline.svelte.d.ts +28 -0
  120. package/dist/SvSplitter.svelte +138 -0
  121. package/dist/SvSplitter.svelte.d.ts +32 -0
  122. package/dist/SvStat.svelte +86 -0
  123. package/dist/SvStat.svelte.d.ts +30 -0
  124. package/dist/SvStepper.svelte +91 -0
  125. package/dist/SvStepper.svelte.d.ts +24 -0
  126. package/dist/SvSwitchButton.svelte +39 -24
  127. package/dist/SvSwitchButton.svelte.d.ts +2 -5
  128. package/dist/SvTabs.svelte +147 -28
  129. package/dist/SvTabs.svelte.d.ts +16 -4
  130. package/dist/SvTagsInput.svelte +59 -22
  131. package/dist/SvTagsInput.svelte.d.ts +9 -4
  132. package/dist/SvTextArea.svelte +113 -0
  133. package/dist/SvTextArea.svelte.d.ts +18 -0
  134. package/dist/SvTextInput.svelte +115 -0
  135. package/dist/SvTextInput.svelte.d.ts +20 -0
  136. package/dist/SvTimePicker.svelte +19 -5
  137. package/dist/SvTimePicker.svelte.d.ts +12 -0
  138. package/dist/SvTimeline.svelte +75 -0
  139. package/dist/SvTimeline.svelte.d.ts +24 -0
  140. package/dist/SvToaster.svelte +124 -0
  141. package/dist/SvToaster.svelte.d.ts +10 -0
  142. package/dist/SvToggleButton.svelte +37 -12
  143. package/dist/SvToggleButton.svelte.d.ts +2 -4
  144. package/dist/SvTooltip.svelte +113 -0
  145. package/dist/SvTooltip.svelte.d.ts +22 -0
  146. package/dist/SvTour.svelte +151 -0
  147. package/dist/SvTour.svelte.d.ts +25 -0
  148. package/dist/SvTree.svelte +286 -44
  149. package/dist/SvTree.svelte.d.ts +26 -9
  150. package/dist/SvTreeSelect.svelte +222 -0
  151. package/dist/SvTreeSelect.svelte.d.ts +24 -0
  152. package/dist/a11y/dismissable.d.ts +32 -0
  153. package/dist/a11y/dismissable.js +54 -0
  154. package/dist/a11y/focus-trap.d.ts +39 -0
  155. package/dist/a11y/focus-trap.js +142 -0
  156. package/dist/a11y/live-region.d.ts +8 -0
  157. package/dist/a11y/live-region.js +60 -0
  158. package/dist/a11y/scroll-lock.d.ts +4 -0
  159. package/dist/a11y/scroll-lock.js +41 -0
  160. package/dist/a11y.d.ts +3 -1
  161. package/dist/a11y.js +1 -1
  162. package/dist/avatar.d.ts +10 -0
  163. package/dist/avatar.js +22 -0
  164. package/dist/builtin-editors.d.ts +5 -0
  165. package/dist/builtin-editors.js +55 -0
  166. package/dist/cdn/svgrid.js +16022 -8235
  167. package/dist/cdn/svgrid.svelte-external.js +14543 -6765
  168. package/dist/columns.js +3 -2
  169. package/dist/core.d.ts +7 -1
  170. package/dist/countries.d.ts +12 -0
  171. package/dist/countries.js +24 -0
  172. package/dist/createAccordion.svelte.d.ts +71 -0
  173. package/dist/createAccordion.svelte.js +99 -0
  174. package/dist/createAutocomplete.svelte.d.ts +13 -4
  175. package/dist/createAutocomplete.svelte.js +11 -1
  176. package/dist/createButtonGroup.svelte.d.ts +62 -0
  177. package/dist/createButtonGroup.svelte.js +154 -0
  178. package/dist/createCalendar.svelte.d.ts +5 -0
  179. package/dist/createCalendar.svelte.js +2 -0
  180. package/dist/createCheckbox.svelte.d.ts +9 -17
  181. package/dist/createCheckbox.svelte.js +28 -1
  182. package/dist/createColorInput.svelte.d.ts +12 -22
  183. package/dist/createColorInput.svelte.js +13 -0
  184. package/dist/createCombobox.svelte.d.ts +16 -5
  185. package/dist/createCombobox.svelte.js +13 -2
  186. package/dist/createCountryInput.svelte.d.ts +14 -3
  187. package/dist/createCountryInput.svelte.js +12 -2
  188. package/dist/createDateTimePicker.svelte.d.ts +17 -2
  189. package/dist/createDateTimePicker.svelte.js +12 -2
  190. package/dist/createDropdownList.svelte.d.ts +14 -5
  191. package/dist/createDropdownList.svelte.js +50 -1
  192. package/dist/createFileUpload.svelte.d.ts +68 -0
  193. package/dist/createFileUpload.svelte.js +122 -0
  194. package/dist/createListbox.svelte.d.ts +10 -1
  195. package/dist/createListbox.svelte.js +48 -1
  196. package/dist/createMaskedInput.svelte.d.ts +11 -2
  197. package/dist/createMaskedInput.svelte.js +11 -1
  198. package/dist/createNumberInput.svelte.d.ts +1 -0
  199. package/dist/createNumberInput.svelte.js +1 -0
  200. package/dist/createPasswordInput.svelte.d.ts +19 -21
  201. package/dist/createPasswordInput.svelte.js +16 -4
  202. package/dist/createPhoneInput.svelte.d.ts +26 -7
  203. package/dist/createPhoneInput.svelte.js +24 -3
  204. package/dist/createPopoverSelect.svelte.d.ts +86 -0
  205. package/dist/createPopoverSelect.svelte.js +196 -0
  206. package/dist/createRadioGroup.svelte.d.ts +9 -0
  207. package/dist/createRadioGroup.svelte.js +11 -1
  208. package/dist/createRating.svelte.d.ts +11 -0
  209. package/dist/createRating.svelte.js +12 -1
  210. package/dist/createSlider.svelte.d.ts +2 -0
  211. package/dist/createSlider.svelte.js +16 -5
  212. package/dist/createSplitter.svelte.d.ts +67 -0
  213. package/dist/createSplitter.svelte.js +99 -0
  214. package/dist/createSwitch.svelte.d.ts +9 -16
  215. package/dist/createSwitch.svelte.js +28 -1
  216. package/dist/createTabs.svelte.d.ts +7 -0
  217. package/dist/createTabs.svelte.js +17 -2
  218. package/dist/createTagsInput.svelte.d.ts +13 -1
  219. package/dist/createTagsInput.svelte.js +12 -3
  220. package/dist/createToggle.svelte.d.ts +9 -0
  221. package/dist/createToggle.svelte.js +12 -1
  222. package/dist/createTree.svelte.d.ts +19 -0
  223. package/dist/createTree.svelte.js +108 -15
  224. package/dist/debounce.d.ts +11 -0
  225. package/dist/debounce.js +30 -0
  226. package/dist/duration.d.ts +15 -0
  227. package/dist/duration.js +51 -0
  228. package/dist/editor-contract.d.ts +62 -7
  229. package/dist/editor-contract.js +45 -5
  230. package/dist/editor-registry.d.ts +67 -0
  231. package/dist/editor-registry.js +38 -0
  232. package/dist/editors/cell-editors.d.ts +1 -1
  233. package/dist/editors/cell-editors.js +5 -0
  234. package/dist/fuzzy.d.ts +3 -0
  235. package/dist/fuzzy.js +41 -0
  236. package/dist/grid-select.d.ts +9 -0
  237. package/dist/grid-select.js +8 -0
  238. package/dist/index.d.ts +79 -6
  239. package/dist/index.js +78 -4
  240. package/dist/keyboard-handlers.js +19 -0
  241. package/dist/list-option.d.ts +36 -0
  242. package/dist/list-option.js +68 -0
  243. package/dist/otp.d.ts +16 -0
  244. package/dist/otp.js +26 -0
  245. package/dist/paginate.d.ts +20 -0
  246. package/dist/paginate.js +40 -0
  247. package/dist/popover.d.ts +15 -1
  248. package/dist/popover.js +29 -3
  249. package/dist/recurrence.d.ts +36 -0
  250. package/dist/recurrence.js +90 -0
  251. package/dist/server-data-source.d.ts +89 -0
  252. package/dist/server-data-source.js +4 -0
  253. package/dist/server-group-model.d.ts +98 -0
  254. package/dist/server-group-model.js +263 -0
  255. package/dist/toast-store.svelte.d.ts +41 -0
  256. package/dist/toast-store.svelte.js +113 -0
  257. package/dist/tree-select.d.ts +19 -0
  258. package/dist/tree-select.js +40 -0
  259. package/dist/ui-app.types.d.ts +80 -0
  260. package/dist/ui-app.types.js +7 -0
  261. package/dist/validators.d.ts +43 -0
  262. package/dist/validators.js +53 -0
  263. package/dist/virtual.d.ts +35 -0
  264. package/dist/virtual.js +34 -0
  265. package/package.json +85 -85
  266. package/src/FlexRender.svelte +96 -96
  267. package/src/GridFooter.svelte +178 -178
  268. package/src/SvAccordion.svelte +109 -0
  269. package/src/SvAccordion.test.ts +66 -0
  270. package/src/SvAlert.svelte +92 -0
  271. package/src/SvAutoComplete.svelte +47 -18
  272. package/src/SvAvatar.svelte +75 -0
  273. package/src/SvAvatarGroup.svelte +48 -0
  274. package/src/SvBadge.svelte +54 -0
  275. package/src/SvBreadcrumb.svelte +88 -0
  276. package/src/SvButtonGroup.svelte +121 -0
  277. package/src/SvButtonGroup.test.ts +72 -0
  278. package/src/SvCalendar.svelte +72 -9
  279. package/src/SvCard.svelte +71 -0
  280. package/src/SvCarousel.svelte +134 -0
  281. package/src/SvCheckBox.svelte +36 -21
  282. package/src/SvChip.svelte +88 -0
  283. package/src/SvCircularProgress.svelte +109 -0
  284. package/src/SvColorInput.svelte +54 -27
  285. package/src/SvComboBox.svelte +119 -33
  286. package/src/SvCommand.svelte +197 -0
  287. package/src/SvContextMenu.svelte +96 -0
  288. package/src/SvCountryInput.svelte +56 -21
  289. package/src/SvDateRangeInput.svelte +256 -0
  290. package/src/SvDateRangeInput.test.ts +80 -0
  291. package/src/SvDateTimePicker.svelte +57 -20
  292. package/src/SvDivider.svelte +61 -0
  293. package/src/SvDrawer.svelte +226 -0
  294. package/src/SvDrawer.test.ts +125 -0
  295. package/src/SvDropDownList.svelte +114 -35
  296. package/src/SvDurationInput.svelte +108 -0
  297. package/src/SvEmptyState.svelte +59 -0
  298. package/src/SvField.svelte +85 -0
  299. package/src/SvFileUpload.svelte +169 -0
  300. package/src/SvFileUpload.test.ts +59 -0
  301. package/src/SvForm.svelte +7 -1
  302. package/src/SvGauge.svelte +7 -1
  303. package/src/SvGrid.controller.svelte.ts +2553 -2494
  304. package/src/SvGrid.css +2376 -2343
  305. package/src/SvGrid.svelte +2764 -2651
  306. package/src/SvGrid.types.ts +944 -570
  307. package/src/SvGridBoard.svelte +2281 -0
  308. package/src/SvGridDropdown.svelte +666 -665
  309. package/src/SvGridSelect.svelte +255 -0
  310. package/src/SvGridSelect.test.ts +100 -0
  311. package/src/SvGroupCell.svelte +109 -0
  312. package/src/SvListBox.svelte +123 -36
  313. package/src/SvMaskedInput.svelte +53 -13
  314. package/src/SvMenu.svelte +101 -0
  315. package/src/SvMenu.test.ts +73 -0
  316. package/src/SvMenuList.svelte +143 -0
  317. package/src/SvModal.svelte +220 -0
  318. package/src/SvMultiSelect.svelte +277 -0
  319. package/src/SvMultiSelect.test.ts +167 -0
  320. package/src/SvNavPane.svelte +314 -0
  321. package/src/SvNavPane.test.ts +70 -0
  322. package/src/SvNumberInput.svelte +23 -9
  323. package/src/SvOtpInput.svelte +157 -0
  324. package/src/SvPagination.svelte +100 -0
  325. package/src/SvPasswordInput.svelte +42 -12
  326. package/src/SvPhoneInput.svelte +43 -11
  327. package/src/SvPopover.svelte +127 -0
  328. package/src/SvProgress.svelte +116 -0
  329. package/src/SvRadioGroup.svelte +40 -22
  330. package/src/SvRating.svelte +51 -30
  331. package/src/SvRichText.svelte +192 -0
  332. package/src/SvRowGroupPanel.svelte +147 -0
  333. package/src/SvScrollArea.svelte +50 -0
  334. package/src/SvSkeleton.svelte +76 -0
  335. package/src/SvSlider.svelte +41 -9
  336. package/src/SvSparkline.svelte +77 -0
  337. package/src/SvSplitter.svelte +138 -0
  338. package/src/SvSplitter.test.ts +55 -0
  339. package/src/SvStat.svelte +86 -0
  340. package/src/SvStepper.svelte +91 -0
  341. package/src/SvSwitchButton.svelte +39 -24
  342. package/src/SvTabs.svelte +147 -28
  343. package/src/SvTagsInput.svelte +59 -22
  344. package/src/SvTextArea.svelte +113 -0
  345. package/src/SvTextInput.svelte +115 -0
  346. package/src/SvTimePicker.svelte +19 -5
  347. package/src/SvTimeline.svelte +75 -0
  348. package/src/SvToaster.svelte +124 -0
  349. package/src/SvToaster.test.ts +63 -0
  350. package/src/SvToggleButton.svelte +37 -12
  351. package/src/SvTooltip.svelte +113 -0
  352. package/src/SvTour.svelte +151 -0
  353. package/src/SvTree.svelte +286 -44
  354. package/src/SvTreeSelect.svelte +222 -0
  355. package/src/SvTreeSelect.test.ts +87 -0
  356. package/src/a11y/dismissable.test.ts +95 -0
  357. package/src/a11y/dismissable.ts +94 -0
  358. package/src/a11y/focus-trap.test.ts +110 -0
  359. package/src/a11y/focus-trap.ts +134 -0
  360. package/src/a11y/live-region.test.ts +40 -0
  361. package/src/a11y/live-region.ts +65 -0
  362. package/src/a11y/scroll-lock.test.ts +38 -0
  363. package/src/a11y/scroll-lock.ts +41 -0
  364. package/src/a11y.contract.test.ts +49 -49
  365. package/src/a11y.test.ts +59 -59
  366. package/src/a11y.ts +61 -59
  367. package/src/avatar.test.ts +32 -0
  368. package/src/avatar.ts +21 -0
  369. package/src/board.test.ts +927 -0
  370. package/src/build-api.ts +798 -798
  371. package/src/builtin-editors.grid.test.ts +85 -0
  372. package/src/builtin-editors.test.ts +58 -0
  373. package/src/builtin-editors.ts +56 -0
  374. package/src/cell-formatting.ts +169 -169
  375. package/src/cell-render.ts +469 -469
  376. package/src/collaboration.test.ts +104 -104
  377. package/src/collaboration.ts +167 -167
  378. package/src/columns.ts +3 -2
  379. package/src/core.performance.test.ts +30 -30
  380. package/src/core.ts +1111 -1101
  381. package/src/countries.ts +25 -0
  382. package/src/createAccordion.svelte.ts +126 -0
  383. package/src/createAutocomplete.svelte.ts +18 -1
  384. package/src/createButtonGroup.svelte.ts +166 -0
  385. package/src/createCalendar.svelte.ts +6 -0
  386. package/src/createCheckbox.svelte.ts +23 -1
  387. package/src/createColorInput.svelte.ts +20 -0
  388. package/src/createCombobox.svelte.ts +22 -2
  389. package/src/createCountryInput.svelte.ts +21 -2
  390. package/src/createDateTimePicker.svelte.ts +23 -2
  391. package/src/createDropdownList.svelte.ts +31 -2
  392. package/src/createFileUpload.svelte.ts +144 -0
  393. package/src/createGrid.svelte.ts +42 -42
  394. package/src/createGrid.test.ts +10 -10
  395. package/src/createGridState.svelte.ts +17 -17
  396. package/src/createListbox.svelte.ts +35 -3
  397. package/src/createMaskedInput.svelte.ts +18 -1
  398. package/src/createNumberInput.svelte.ts +2 -0
  399. package/src/createPasswordInput.svelte.ts +31 -5
  400. package/src/createPhoneInput.svelte.ts +38 -4
  401. package/src/createPopoverSelect.svelte.ts +208 -0
  402. package/src/createRadioGroup.svelte.ts +23 -1
  403. package/src/createRating.svelte.ts +26 -1
  404. package/src/createSlider.svelte.ts +14 -3
  405. package/src/createSplitter.svelte.ts +119 -0
  406. package/src/createSwitch.svelte.ts +26 -3
  407. package/src/createTabs.svelte.ts +20 -3
  408. package/src/createTagsInput.svelte.ts +24 -3
  409. package/src/createToggle.svelte.ts +25 -2
  410. package/src/createTree.svelte.ts +104 -14
  411. package/src/debounce.test.ts +50 -0
  412. package/src/debounce.ts +41 -0
  413. package/src/duration.test.ts +41 -0
  414. package/src/duration.ts +51 -0
  415. package/src/editing.test.ts +859 -859
  416. package/src/editing.ts +675 -675
  417. package/src/editor-contract.ts +87 -8
  418. package/src/editor-registry.grid.test.ts +98 -0
  419. package/src/editor-registry.test.ts +86 -0
  420. package/src/editor-registry.ts +106 -0
  421. package/src/editors/cell-editors.ts +10 -1
  422. package/src/export-data-api.test.ts +126 -126
  423. package/src/export-format.test.ts +107 -107
  424. package/src/export-format.ts +598 -598
  425. package/src/flex-render.ts +3 -3
  426. package/src/fuzzy.test.ts +40 -0
  427. package/src/fuzzy.ts +39 -0
  428. package/src/grid-select.test.ts +28 -0
  429. package/src/grid-select.ts +20 -0
  430. package/src/index.ts +463 -318
  431. package/src/keyboard-handlers.ts +20 -0
  432. package/src/keyboard.test.ts +59 -59
  433. package/src/keyboard.ts +97 -97
  434. package/src/list-option.ts +74 -0
  435. package/src/list-power.test.ts +71 -0
  436. package/src/menus.ts +582 -582
  437. package/src/merge-objects.ts +48 -48
  438. package/src/otp.test.ts +28 -0
  439. package/src/otp.ts +28 -0
  440. package/src/overlays.test.ts +85 -0
  441. package/src/paginate.test.ts +41 -0
  442. package/src/paginate.ts +61 -0
  443. package/src/phone-valid.test.ts +26 -0
  444. package/src/popover.ts +31 -3
  445. package/src/progress.test.ts +55 -0
  446. package/src/recurrence.test.ts +91 -0
  447. package/src/recurrence.ts +111 -0
  448. package/src/render-component.ts +28 -28
  449. package/src/selection.test.ts +754 -754
  450. package/src/selection.ts +600 -600
  451. package/src/server-data-source.test.ts +289 -289
  452. package/src/server-data-source.ts +413 -311
  453. package/src/server-group-model.test.ts +294 -0
  454. package/src/server-group-model.ts +370 -0
  455. package/src/sparkline.test.ts +68 -68
  456. package/src/sparkline.ts +169 -169
  457. package/src/spreadsheet.test.ts +489 -489
  458. package/src/spreadsheet.ts +304 -304
  459. package/src/static-functions.ts +11 -11
  460. package/src/subscribe.ts +38 -38
  461. package/src/svgrid-wrapper.types.ts +439 -439
  462. package/src/svgrid.behavior.test.ts +706 -706
  463. package/src/svgrid.features.test.ts +157 -157
  464. package/src/svgrid.new-features.wrapper.test.ts +251 -251
  465. package/src/svgrid.responsive.test.ts +72 -0
  466. package/src/svgrid.wrapper.test.ts +40 -40
  467. package/src/toast-store.svelte.ts +148 -0
  468. package/src/toast-store.test.ts +86 -0
  469. package/src/tree-edit.test.ts +49 -0
  470. package/src/tree-filter.test.ts +56 -0
  471. package/src/tree-select.test.ts +48 -0
  472. package/src/tree-select.ts +62 -0
  473. package/src/ui-app.test.ts +130 -0
  474. package/src/ui-app.types.ts +88 -0
  475. package/src/ui-display.test.ts +79 -0
  476. package/src/ui-inputs.test.ts +1 -1
  477. package/src/ui-status.test.ts +141 -0
  478. package/src/ui-tier1.test.ts +142 -0
  479. package/src/ui-tier2.test.ts +124 -0
  480. package/src/ui-tier3.test.ts +139 -0
  481. package/src/validators.test.ts +58 -0
  482. package/src/validators.ts +105 -0
  483. package/src/virtual.test.ts +72 -0
  484. package/src/virtual.ts +67 -0
  485. package/src/virtualization/column-virtualizer.test.ts +27 -27
  486. package/src/virtualization/column-virtualizer.ts +30 -30
  487. package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
  488. package/src/virtualization/types.ts +30 -30
  489. package/src/virtualization/virtualizer.test.ts +47 -47
  490. package/src/virtualization/virtualizer.ts +296 -296
@@ -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
+ })
@@ -0,0 +1,148 @@
1
+ /**
2
+ * toast-store - the headless model behind <SvToaster>: a singleton reactive
3
+ * queue of transient notifications with auto-dismiss timers, plus the `toast()`
4
+ * API you call from anywhere. Every toast is also pushed to the shared ARIA
5
+ * live region via `announce()`, so screen-reader users hear it even though the
6
+ * visual toast is off to the side.
7
+ *
8
+ * ```ts
9
+ * import { toast } from '@svgrid/grid'
10
+ * toast.success('Saved')
11
+ * toast.error('Could not save', { duration: 0 }) // sticky
12
+ * ```
13
+ *
14
+ * Mount a single <SvToaster /> once near the app root to render the queue.
15
+ */
16
+ import { announce } from './a11y/live-region'
17
+
18
+ export type ToastVariant = 'info' | 'success' | 'warning' | 'error'
19
+
20
+ export type ToastOptions = {
21
+ variant?: ToastVariant
22
+ /** Auto-dismiss after N ms. `0` = sticky (dismiss manually). Default 4000. */
23
+ duration?: number
24
+ /** Show the dismiss (x) button. Default true. */
25
+ dismissible?: boolean
26
+ /** Optional bold title above the message. */
27
+ title?: string
28
+ }
29
+
30
+ export type Toast = {
31
+ id: number
32
+ message: string
33
+ variant: ToastVariant
34
+ duration: number
35
+ dismissible: boolean
36
+ title?: string
37
+ }
38
+
39
+ type VariantOptions = Omit<ToastOptions, 'variant'>
40
+
41
+ export type ToastFn = {
42
+ (message: string, options?: ToastOptions): number
43
+ info: (message: string, options?: VariantOptions) => number
44
+ success: (message: string, options?: VariantOptions) => number
45
+ warning: (message: string, options?: VariantOptions) => number
46
+ error: (message: string, options?: VariantOptions) => number
47
+ }
48
+
49
+ function createToastStore() {
50
+ const toasts = $state<Toast[]>([])
51
+ const timers = new Map<number, ReturnType<typeof setTimeout>>()
52
+ // Remaining ms + when the current run started, so hovering can pause/resume
53
+ // the auto-dismiss countdown instead of losing it.
54
+ const meta = new Map<number, { remaining: number; startedAt: number }>()
55
+ let seq = 0
56
+
57
+ function schedule(id: number, ms: number) {
58
+ meta.set(id, { remaining: ms, startedAt: Date.now() })
59
+ timers.set(id, setTimeout(() => dismiss(id), ms))
60
+ }
61
+ /** Pause a toast's auto-dismiss (e.g. on hover), banking the time left. */
62
+ function pause(id: number): void {
63
+ const timer = timers.get(id)
64
+ const m = meta.get(id)
65
+ if (!timer || !m) return
66
+ clearTimeout(timer)
67
+ timers.delete(id)
68
+ m.remaining = Math.max(0, m.remaining - (Date.now() - m.startedAt))
69
+ }
70
+ /** Resume a paused toast with the banked time remaining. */
71
+ function resume(id: number): void {
72
+ const m = meta.get(id)
73
+ if (!m || timers.has(id) || m.remaining <= 0) return
74
+ m.startedAt = Date.now()
75
+ timers.set(id, setTimeout(() => dismiss(id), m.remaining))
76
+ }
77
+
78
+ function dismiss(id: number): void {
79
+ const i = toasts.findIndex((t) => t.id === id)
80
+ if (i >= 0) toasts.splice(i, 1)
81
+ const timer = timers.get(id)
82
+ if (timer) {
83
+ clearTimeout(timer)
84
+ timers.delete(id)
85
+ }
86
+ meta.delete(id)
87
+ }
88
+
89
+ function clear(): void {
90
+ for (const timer of timers.values()) clearTimeout(timer)
91
+ timers.clear()
92
+ meta.clear()
93
+ toasts.splice(0, toasts.length)
94
+ }
95
+
96
+ function push(message: string, options: ToastOptions = {}): number {
97
+ const id = ++seq
98
+ const variant = options.variant ?? 'info'
99
+ const duration = options.duration ?? 4000
100
+ const entry: Toast = {
101
+ id,
102
+ message,
103
+ variant,
104
+ duration,
105
+ dismissible: options.dismissible ?? true,
106
+ title: options.title,
107
+ }
108
+ toasts.push(entry)
109
+ // Errors/warnings interrupt (assertive); info/success are polite.
110
+ announce(entry.title ? `${entry.title}: ${message}` : message, {
111
+ assertive: variant === 'error' || variant === 'warning',
112
+ })
113
+ if (duration > 0) schedule(id, duration)
114
+ return id
115
+ }
116
+
117
+ const toast = push as ToastFn
118
+ toast.info = (m, o = {}) => push(m, { ...o, variant: 'info' })
119
+ toast.success = (m, o = {}) => push(m, { ...o, variant: 'success' })
120
+ toast.warning = (m, o = {}) => push(m, { ...o, variant: 'warning' })
121
+ toast.error = (m, o = {}) => push(m, { ...o, variant: 'error' })
122
+
123
+ return {
124
+ get toasts() {
125
+ return toasts
126
+ },
127
+ toast,
128
+ dismiss,
129
+ clear,
130
+ pause,
131
+ resume,
132
+ }
133
+ }
134
+
135
+ const store = createToastStore()
136
+
137
+ /** Live reactive list of active toasts (read inside a component/effect). */
138
+ export const toastStore = { get toasts() { return store.toasts } }
139
+ /** Show a toast. Returns its id. Has `.info/.success/.warning/.error` helpers. */
140
+ export const toast: ToastFn = store.toast
141
+ /** Dismiss a toast by id. */
142
+ export const dismissToast = (id: number): void => store.dismiss(id)
143
+ /** Remove every toast (and cancel their timers). */
144
+ export const clearToasts = (): void => store.clear()
145
+ /** Pause a toast's auto-dismiss countdown (e.g. while hovered). */
146
+ export const pauseToast = (id: number): void => store.pause(id)
147
+ /** Resume a paused toast's countdown with the time it had left. */
148
+ export const resumeToast = (id: number): void => store.resume(id)
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Tests for the headless toast store: queueing, variants, auto-dismiss timers,
3
+ * manual dismiss, sticky toasts, and the SR announcement side effect.
4
+ */
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
6
+ import { toast, dismissToast, clearToasts, pauseToast, resumeToast, toastStore } from './toast-store.svelte'
7
+
8
+ beforeEach(() => {
9
+ vi.useFakeTimers()
10
+ })
11
+
12
+ afterEach(() => {
13
+ clearToasts()
14
+ vi.useRealTimers()
15
+ document.body.innerHTML = ''
16
+ })
17
+
18
+ describe('toast store', () => {
19
+ it('queues a toast with defaults', () => {
20
+ const id = toast('Hello')
21
+ expect(toastStore.toasts).toHaveLength(1)
22
+ const t = toastStore.toasts[0]!
23
+ expect(t.id).toBe(id)
24
+ expect(t.message).toBe('Hello')
25
+ expect(t.variant).toBe('info')
26
+ expect(t.dismissible).toBe(true)
27
+ })
28
+
29
+ it('variant helpers set the variant', () => {
30
+ toast.success('ok')
31
+ toast.error('bad')
32
+ toast.warning('hmm')
33
+ expect(toastStore.toasts.map((t) => t.variant)).toEqual(['success', 'error', 'warning'])
34
+ })
35
+
36
+ it('auto-dismisses after its duration', () => {
37
+ toast('bye', { duration: 3000 })
38
+ expect(toastStore.toasts).toHaveLength(1)
39
+ vi.advanceTimersByTime(2999)
40
+ expect(toastStore.toasts).toHaveLength(1)
41
+ vi.advanceTimersByTime(1)
42
+ expect(toastStore.toasts).toHaveLength(0)
43
+ })
44
+
45
+ it('duration 0 is sticky (no auto-dismiss)', () => {
46
+ toast('stay', { duration: 0 })
47
+ vi.advanceTimersByTime(60_000)
48
+ expect(toastStore.toasts).toHaveLength(1)
49
+ })
50
+
51
+ it('dismissToast removes by id and cancels its timer', () => {
52
+ const id = toast('x', { duration: 5000 })
53
+ dismissToast(id)
54
+ expect(toastStore.toasts).toHaveLength(0)
55
+ // Advancing past the original duration must not throw or double-remove.
56
+ vi.advanceTimersByTime(5000)
57
+ expect(toastStore.toasts).toHaveLength(0)
58
+ })
59
+
60
+ it('announces via a live region (assertive for errors)', () => {
61
+ toast.error('boom')
62
+ vi.runOnlyPendingTimers()
63
+ // The store routes errors to the assertive live region.
64
+ expect(document.querySelector('[aria-live="assertive"]')).not.toBeNull()
65
+ })
66
+
67
+ it('pause banks the remaining time and resume finishes it (hover to keep)', () => {
68
+ const id = toast('hover me', { duration: 3000 })
69
+ vi.advanceTimersByTime(1000) // 2000 left
70
+ pauseToast(id)
71
+ vi.advanceTimersByTime(10_000) // stays while paused
72
+ expect(toastStore.toasts).toHaveLength(1)
73
+ resumeToast(id)
74
+ vi.advanceTimersByTime(1999)
75
+ expect(toastStore.toasts).toHaveLength(1)
76
+ vi.advanceTimersByTime(1) // banked 2000ms elapsed
77
+ expect(toastStore.toasts).toHaveLength(0)
78
+ })
79
+
80
+ it('clearToasts empties the queue', () => {
81
+ toast('a'); toast('b'); toast('c')
82
+ expect(toastStore.toasts).toHaveLength(3)
83
+ clearToasts()
84
+ expect(toastStore.toasts).toHaveLength(0)
85
+ })
86
+ })
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Tests for the pure tree edit helpers: moveTreeNode (before/after/inside +
3
+ * invalid-move guards) and sortTreeNodes (recursive), plus treeContains.
4
+ */
5
+ import { describe, expect, it } from 'vitest'
6
+ import { moveTreeNode, sortTreeNodes, treeContains, type TreeNode } from './createTree.svelte'
7
+
8
+ const tree: TreeNode[] = [
9
+ { id: 'a', label: 'A', children: [{ id: 'a1', label: 'A1' }, { id: 'a2', label: 'A2' }] },
10
+ { id: 'b', label: 'B' },
11
+ { id: 'c', label: 'C' },
12
+ ]
13
+ const ids = (list: TreeNode[]) => list.map((n) => n.id)
14
+
15
+ describe('moveTreeNode', () => {
16
+ it('moves before / after a sibling', () => {
17
+ expect(ids(moveTreeNode(tree, 'c', 'a', 'before'))).toEqual(['c', 'a', 'b'])
18
+ expect(ids(moveTreeNode(tree, 'b', 'c', 'after'))).toEqual(['a', 'c', 'b'])
19
+ })
20
+ it('moves inside a folder (appends to children)', () => {
21
+ const out = moveTreeNode(tree, 'b', 'a', 'inside')
22
+ expect(ids(out)).toEqual(['a', 'c'])
23
+ expect(out[0].children!.map((n) => n.id)).toEqual(['a1', 'a2', 'b'])
24
+ })
25
+ it('refuses to drop a node into its own descendant, or onto itself', () => {
26
+ expect(moveTreeNode(tree, 'a', 'a1', 'inside')).toEqual(tree)
27
+ expect(moveTreeNode(tree, 'a', 'a', 'before')).toEqual(tree)
28
+ })
29
+ })
30
+
31
+ describe('sortTreeNodes', () => {
32
+ it('sorts siblings recursively', () => {
33
+ const unsorted: TreeNode[] = [
34
+ { id: 'z', label: 'Zebra' },
35
+ { id: 'm', label: 'Mango', children: [{ id: 'p', label: 'Pear' }, { id: 'a', label: 'Apple' }] },
36
+ { id: 'a', label: 'Ant' },
37
+ ]
38
+ const asc = sortTreeNodes(unsorted, (x, y) => x.label.localeCompare(y.label))
39
+ expect(ids(asc)).toEqual(['a', 'm', 'z'])
40
+ expect(asc[1].children!.map((n) => n.label)).toEqual(['Apple', 'Pear'])
41
+ })
42
+ })
43
+
44
+ describe('treeContains', () => {
45
+ it('detects descendants', () => {
46
+ expect(treeContains(tree[0], 'a2')).toBe(true)
47
+ expect(treeContains(tree[0], 'b')).toBe(false)
48
+ })
49
+ })
@@ -0,0 +1,56 @@
1
+ /**
2
+ * SvTree filter/search: typing a query shows only matching nodes + their
3
+ * ancestors (auto-expanded), and clearing restores the tree.
4
+ */
5
+ import { describe, expect, it } from 'vitest'
6
+ import { mount, unmount, flushSync } from 'svelte'
7
+ import SvTree from './SvTree.svelte'
8
+
9
+ const nodes = [
10
+ { id: 'src', label: 'src', children: [
11
+ { id: 'a', label: 'App.svelte' },
12
+ { id: 'b', label: 'utils.ts' },
13
+ ] },
14
+ { id: 'pub', label: 'public', children: [
15
+ { id: 'c', label: 'logo.svg' },
16
+ ] },
17
+ ]
18
+
19
+ function mountTree(props: Record<string, unknown>) {
20
+ const target = document.createElement('div')
21
+ document.body.appendChild(target)
22
+ const app = mount(SvTree, { target, props: { nodes, expandedIds: ['src', 'pub'], ...props } as any })
23
+ flushSync()
24
+ return { target, destroy: () => { unmount(app); target.remove() } }
25
+ }
26
+ const labels = (t: HTMLElement) => [...t.querySelectorAll('.sv-tree__label')].map((n) => n.textContent)
27
+
28
+ describe('SvTree filter', () => {
29
+ it('shows only matches + ancestors (auto-expanded)', () => {
30
+ const { target, destroy } = mountTree({ filter: 'utils' })
31
+ try {
32
+ const shown = labels(target)
33
+ expect(shown).toContain('src') // ancestor kept
34
+ expect(shown).toContain('utils.ts') // match
35
+ expect(shown).not.toContain('App.svelte')
36
+ expect(shown).not.toContain('public')
37
+ } finally { destroy() }
38
+ })
39
+
40
+ it('matching an ancestor label shows it (and its subtree stays reachable)', () => {
41
+ const { target, destroy } = mountTree({ filter: 'public' })
42
+ try {
43
+ const shown = labels(target)
44
+ expect(shown).toContain('public')
45
+ expect(shown).not.toContain('src')
46
+ } finally { destroy() }
47
+ })
48
+
49
+ it('renders a built-in search box when searchable', () => {
50
+ const { target, destroy } = mountTree({ searchable: true })
51
+ try {
52
+ expect(target.querySelector('.sv-tree__search-input')).not.toBeNull()
53
+ expect(labels(target)).toContain('App.svelte') // unfiltered initially
54
+ } finally { destroy() }
55
+ })
56
+ })
@@ -0,0 +1,48 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { flattenVisible, findNodePath, branchValues } from './tree-select'
3
+ import type { TreeSelectNode } from './ui-app.types'
4
+
5
+ const tree: TreeSelectNode[] = [
6
+ { value: 'asia', label: 'Asia', children: [
7
+ { value: 'jp', label: 'Japan', children: [{ value: 'tokyo', label: 'Tokyo' }] },
8
+ { value: 'cn', label: 'China' },
9
+ ] },
10
+ { value: 'eu', label: 'Europe', children: [{ value: 'fr', label: 'France' }] },
11
+ ]
12
+
13
+ describe('flattenVisible', () => {
14
+ it('shows only roots when nothing is expanded', () => {
15
+ const rows = flattenVisible(tree, new Set())
16
+ expect(rows.map((r) => r.node.value)).toEqual(['asia', 'eu'])
17
+ expect(rows.every((r) => r.depth === 0)).toBe(true)
18
+ expect(rows[0]!.hasChildren).toBe(true)
19
+ })
20
+
21
+ it('reveals children of expanded nodes with increasing depth', () => {
22
+ const rows = flattenVisible(tree, new Set(['asia']))
23
+ expect(rows.map((r) => r.node.value)).toEqual(['asia', 'jp', 'cn', 'eu'])
24
+ expect(rows.find((r) => r.node.value === 'jp')!.depth).toBe(1)
25
+ })
26
+
27
+ it('nests deeply when a branch chain is expanded', () => {
28
+ const rows = flattenVisible(tree, new Set(['asia', 'jp']))
29
+ expect(rows.map((r) => r.node.value)).toEqual(['asia', 'jp', 'tokyo', 'cn', 'eu'])
30
+ expect(rows.find((r) => r.node.value === 'tokyo')!.depth).toBe(2)
31
+ })
32
+ })
33
+
34
+ describe('findNodePath', () => {
35
+ it('returns the root-to-node path', () => {
36
+ expect(findNodePath(tree, 'tokyo')!.map((n) => n.value)).toEqual(['asia', 'jp', 'tokyo'])
37
+ expect(findNodePath(tree, 'eu')!.map((n) => n.value)).toEqual(['eu'])
38
+ })
39
+ it('returns null for unknown values', () => {
40
+ expect(findNodePath(tree, 'nope')).toBeNull()
41
+ })
42
+ })
43
+
44
+ describe('branchValues', () => {
45
+ it('lists every node that has children', () => {
46
+ expect(branchValues(tree).sort()).toEqual(['asia', 'eu', 'jp'])
47
+ })
48
+ })
@@ -0,0 +1,62 @@
1
+ /**
2
+ * tree-select - pure tree helpers behind <SvTreeSelect>: flatten the visible
3
+ * rows for the current expansion state (so keyboard nav is a flat index), and
4
+ * resolve the path from root to a value (for the trigger label). Framework-free
5
+ * + pure so they are unit-tested directly.
6
+ */
7
+ import type { TreeSelectNode } from './ui-app.types'
8
+
9
+ export type FlatRow = {
10
+ node: TreeSelectNode
11
+ depth: number
12
+ hasChildren: boolean
13
+ expanded: boolean
14
+ }
15
+
16
+ /** The rows currently visible given the `expanded` set, in display order. */
17
+ export function flattenVisible(
18
+ nodes: ReadonlyArray<TreeSelectNode>,
19
+ expanded: ReadonlySet<string | number>,
20
+ depth = 0,
21
+ ): FlatRow[] {
22
+ const rows: FlatRow[] = []
23
+ for (const node of nodes) {
24
+ const hasChildren = !!node.children && node.children.length > 0
25
+ const isExpanded = expanded.has(node.value)
26
+ rows.push({ node, depth, hasChildren, expanded: isExpanded })
27
+ if (hasChildren && isExpanded) {
28
+ rows.push(...flattenVisible(node.children!, expanded, depth + 1))
29
+ }
30
+ }
31
+ return rows
32
+ }
33
+
34
+ /** Path of nodes from a root down to `value`, or null if not found. */
35
+ export function findNodePath(
36
+ nodes: ReadonlyArray<TreeSelectNode>,
37
+ value: string | number,
38
+ ): TreeSelectNode[] | null {
39
+ for (const node of nodes) {
40
+ if (node.value === value) return [node]
41
+ if (node.children) {
42
+ const sub = findNodePath(node.children, value)
43
+ if (sub) return [node, ...sub]
44
+ }
45
+ }
46
+ return null
47
+ }
48
+
49
+ /** Every value that has children (useful for "expand all"). */
50
+ export function branchValues(nodes: ReadonlyArray<TreeSelectNode>): Array<string | number> {
51
+ const out: Array<string | number> = []
52
+ const walk = (list: ReadonlyArray<TreeSelectNode>) => {
53
+ for (const n of list) {
54
+ if (n.children && n.children.length) {
55
+ out.push(n.value)
56
+ walk(n.children)
57
+ }
58
+ }
59
+ }
60
+ walk(nodes)
61
+ return out
62
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Component tests for the app-UI set: SvPagination, SvBreadcrumb, SvStepper,
3
+ * SvAvatar, SvContextMenu - semantics, active/current state, and interaction.
4
+ */
5
+ import { describe, expect, it } from 'vitest'
6
+ import { mount, unmount, flushSync } from 'svelte'
7
+ import SvPagination from './SvPagination.svelte'
8
+ import SvBreadcrumb from './SvBreadcrumb.svelte'
9
+ import SvStepper from './SvStepper.svelte'
10
+ import SvAvatar from './SvAvatar.svelte'
11
+ import SvContextMenu from './SvContextMenu.svelte'
12
+
13
+ function mountOn(Comp: any, props: Record<string, unknown>) {
14
+ const target = document.createElement('div')
15
+ document.body.appendChild(target)
16
+ const app = mount(Comp, { target, props: props as any })
17
+ flushSync()
18
+ return { target, destroy: () => { unmount(app); target.remove() } }
19
+ }
20
+
21
+ describe('SvPagination', () => {
22
+ it('marks the current page with aria-current and disables prev on page 1', () => {
23
+ const { target, destroy } = mountOn(SvPagination, { page: 1, pageCount: 10 })
24
+ try {
25
+ const active = target.querySelector('.sv-pg__btn.is-active')!
26
+ expect(active.getAttribute('aria-current')).toBe('page')
27
+ expect(active.textContent).toBe('1')
28
+ const prev = target.querySelector<HTMLButtonElement>('[aria-label="Previous page"]')!
29
+ expect(prev.disabled).toBe(true)
30
+ } finally { destroy() }
31
+ })
32
+
33
+ it('emits the page number on click', () => {
34
+ let got: number | undefined
35
+ const { target, destroy } = mountOn(SvPagination, { page: 1, pageCount: 10, onChange: (n: number) => (got = n) })
36
+ try {
37
+ const next = target.querySelector<HTMLButtonElement>('[aria-label="Next page"]')!
38
+ next.click()
39
+ flushSync()
40
+ expect(got).toBe(2)
41
+ } finally { destroy() }
42
+ })
43
+ })
44
+
45
+ describe('SvBreadcrumb', () => {
46
+ const items = [{ label: 'Home', href: '/' }, { label: 'Orders', href: '/orders' }, { label: '#1024' }]
47
+
48
+ it('renders links for non-last crumbs and marks the last as current', () => {
49
+ const { target, destroy } = mountOn(SvBreadcrumb, { items })
50
+ try {
51
+ expect(target.querySelectorAll('a.sv-bc__link')).toHaveLength(2)
52
+ expect(target.querySelector('[aria-current="page"]')!.textContent).toContain('#1024')
53
+ } finally { destroy() }
54
+ })
55
+
56
+ it('collapses the middle when over maxItems', () => {
57
+ const many = [
58
+ { label: 'A', href: '/a' }, { label: 'B', href: '/b' }, { label: 'C', href: '/c' },
59
+ { label: 'D', href: '/d' }, { label: 'E' },
60
+ ]
61
+ const { target, destroy } = mountOn(SvBreadcrumb, { items: many, maxItems: 3 })
62
+ try {
63
+ expect(target.querySelector('.sv-bc__ellipsis')).not.toBeNull()
64
+ target.querySelector<HTMLButtonElement>('.sv-bc__ellipsis')!.click()
65
+ flushSync()
66
+ expect(target.querySelector('.sv-bc__ellipsis')).toBeNull() // expanded
67
+ } finally { destroy() }
68
+ })
69
+ })
70
+
71
+ describe('SvStepper', () => {
72
+ const steps = [{ label: 'Cart' }, { label: 'Shipping' }, { label: 'Pay' }]
73
+
74
+ it('flags the active step and completes earlier ones', () => {
75
+ const { target, destroy } = mountOn(SvStepper, { steps, current: 1 })
76
+ try {
77
+ const items = target.querySelectorAll('.sv-step__item')
78
+ expect(items[0]!.classList.contains('is-complete')).toBe(true)
79
+ expect(items[1]!.classList.contains('is-active')).toBe(true)
80
+ expect(items[2]!.classList.contains('is-upcoming')).toBe(true)
81
+ expect(target.querySelector('[aria-current="step"]')).not.toBeNull()
82
+ } finally { destroy() }
83
+ })
84
+
85
+ it('disables upcoming steps in linear mode', () => {
86
+ const { target, destroy } = mountOn(SvStepper, { steps, current: 1, linear: true })
87
+ try {
88
+ const btns = target.querySelectorAll<HTMLButtonElement>('.sv-step__btn')
89
+ expect(btns[2]!.disabled).toBe(true) // upcoming, not reachable
90
+ expect(btns[0]!.disabled).toBe(false) // completed, reachable
91
+ } finally { destroy() }
92
+ })
93
+ })
94
+
95
+ describe('SvAvatar', () => {
96
+ it('renders initials with an img role when there is no src', () => {
97
+ const { target, destroy } = mountOn(SvAvatar, { name: 'Ada Lovelace' })
98
+ try {
99
+ const el = target.querySelector('.sv-avatar')!
100
+ expect(el.getAttribute('role')).toBe('img')
101
+ expect(el.getAttribute('aria-label')).toBe('Ada Lovelace')
102
+ expect(target.querySelector('.sv-avatar__initials')!.textContent).toBe('AL')
103
+ } finally { destroy() }
104
+ })
105
+
106
+ it('renders a status dot when status is set', () => {
107
+ const { target, destroy } = mountOn(SvAvatar, { name: 'X', status: 'online' })
108
+ try {
109
+ expect(target.querySelector('.sv-avatar__status.is-online')).not.toBeNull()
110
+ } finally { destroy() }
111
+ })
112
+ })
113
+
114
+ describe('SvContextMenu', () => {
115
+ it('opens a menu at the pointer on contextmenu and closes on Escape', () => {
116
+ const items = [{ label: 'Copy' }, { label: 'Delete' }]
117
+ const { target, destroy } = mountOn(SvContextMenu, { items })
118
+ try {
119
+ const zone = target.querySelector('.sv-ctx__zone')!
120
+ zone.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true, cancelable: true, clientX: 40, clientY: 40 }))
121
+ flushSync()
122
+ expect(document.body.querySelector('.sv-ctx')).not.toBeNull()
123
+ expect(document.body.querySelectorAll('[role="menuitem"]').length).toBe(2)
124
+
125
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }))
126
+ flushSync()
127
+ expect(document.body.querySelector('.sv-ctx')).toBeNull()
128
+ } finally { destroy() }
129
+ })
130
+ })