@svgrid/grid 1.2.33 → 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,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
+ }
@@ -0,0 +1,28 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { sanitizeOtp, otpCells, isOtpComplete } from './otp'
3
+
4
+ describe('sanitizeOtp', () => {
5
+ it('keeps only digits in numeric mode and clamps to length', () => {
6
+ expect(sanitizeOtp('12ab34-56', 4, true)).toBe('1234')
7
+ })
8
+ it('keeps non-whitespace in text mode', () => {
9
+ expect(sanitizeOtp('a b\tc d', 3, false)).toBe('abc')
10
+ })
11
+ it('clamps to length', () => {
12
+ expect(sanitizeOtp('123456789', 6, true)).toBe('123456')
13
+ })
14
+ })
15
+
16
+ describe('otpCells', () => {
17
+ it('pads to exactly length cells', () => {
18
+ expect(otpCells('12', 4)).toEqual(['1', '2', '', ''])
19
+ })
20
+ })
21
+
22
+ describe('isOtpComplete', () => {
23
+ it('is true only when every cell is filled', () => {
24
+ expect(isOtpComplete('1234', 4)).toBe(true)
25
+ expect(isOtpComplete('12', 4)).toBe(false)
26
+ expect(isOtpComplete('', 4)).toBe(false)
27
+ })
28
+ })
package/src/otp.ts ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * otp - pure helpers behind <SvOtpInput>: sanitize/spread a raw string across N
3
+ * segments (used on paste and per-cell input) and detect completion.
4
+ * Framework-free + pure so they are unit-tested directly.
5
+ */
6
+
7
+ /**
8
+ * Keep only the characters allowed for `numeric` mode (digits) or any
9
+ * non-whitespace otherwise, then clamp to `length`. Returns the cleaned string
10
+ * (<= length chars).
11
+ */
12
+ export function sanitizeOtp(raw: string, length: number, numeric: boolean): string {
13
+ const kept = numeric ? raw.replace(/\D/g, '') : raw.replace(/\s/g, '')
14
+ return kept.slice(0, Math.max(0, length))
15
+ }
16
+
17
+ /** Split a value into exactly `length` single-char cells (padding with ''). */
18
+ export function otpCells(value: string, length: number): string[] {
19
+ const cells: string[] = []
20
+ for (let i = 0; i < length; i++) cells[i] = value[i] ?? ''
21
+ return cells
22
+ }
23
+
24
+ /** Whether `value` fills every cell (a joined OTP has no internal gaps, so an
25
+ * exact-length string is complete). */
26
+ export function isOtpComplete(value: string, length: number): boolean {
27
+ return value.length === length
28
+ }
@@ -0,0 +1,85 @@
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
+ destroy()
83
+ } finally { vi.useRealTimers() }
84
+ })
85
+ })
@@ -0,0 +1,41 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { paginationRange } from './paginate'
3
+
4
+ describe('paginationRange', () => {
5
+ it('returns an empty list for zero pages', () => {
6
+ expect(paginationRange({ page: 1, pageCount: 0 })).toEqual([])
7
+ })
8
+
9
+ it('shows every page when the count is small', () => {
10
+ expect(paginationRange({ page: 3, pageCount: 5 })).toEqual([1, 2, 3, 4, 5])
11
+ })
12
+
13
+ it('adds a trailing ellipsis near the start', () => {
14
+ expect(paginationRange({ page: 1, pageCount: 10 })).toEqual([1, 2, 'ellipsis-right', 10])
15
+ })
16
+
17
+ it('adds ellipses on both sides in the middle', () => {
18
+ expect(paginationRange({ page: 5, pageCount: 10 })).toEqual([
19
+ 1, 'ellipsis-left', 4, 5, 6, 'ellipsis-right', 10,
20
+ ])
21
+ })
22
+
23
+ it('adds a leading ellipsis near the end', () => {
24
+ expect(paginationRange({ page: 10, pageCount: 10 })).toEqual([1, 'ellipsis-left', 9, 10])
25
+ })
26
+
27
+ it('collapses a single-page gap to that page number, not an ellipsis', () => {
28
+ // Between boundary 1 and sibling-start 3 there is exactly one page (2): show it.
29
+ expect(paginationRange({ page: 4, pageCount: 8 })).toEqual([1, 2, 3, 4, 5, 'ellipsis-right', 8])
30
+ })
31
+
32
+ it('clamps an out-of-range page', () => {
33
+ expect(paginationRange({ page: 99, pageCount: 5 })).toEqual([1, 2, 3, 4, 5])
34
+ })
35
+
36
+ it('honours a larger siblingCount', () => {
37
+ expect(paginationRange({ page: 6, pageCount: 12, siblingCount: 2 })).toEqual([
38
+ 1, 'ellipsis-left', 4, 5, 6, 7, 8, 'ellipsis-right', 12,
39
+ ])
40
+ })
41
+ })
@@ -0,0 +1,61 @@
1
+ /**
2
+ * paginate - the pure page-range algorithm behind <SvPagination>. Given the
3
+ * current page and total page count, returns the sequence of page numbers and
4
+ * ellipsis markers to render (boundary pages + a window of siblings around the
5
+ * current page, with gaps collapsed to an ellipsis). Framework-free + pure so
6
+ * it is unit-tested on its own.
7
+ */
8
+ export type PaginationItem = number | 'ellipsis-left' | 'ellipsis-right'
9
+
10
+ export type PaginationRangeOptions = {
11
+ /** 1-based current page. */
12
+ page: number
13
+ /** Total number of pages. */
14
+ pageCount: number
15
+ /** Pages shown on each side of the current page. Default 1. */
16
+ siblingCount?: number
17
+ /** Pages pinned at each end. Default 1. */
18
+ boundaryCount?: number
19
+ }
20
+
21
+ const clamp = (n: number, lo: number, hi: number) => Math.min(Math.max(n, lo), hi)
22
+
23
+ /** Build the page/ellipsis sequence (e.g. `[1, 'ellipsis-left', 4, 5, 6, 'ellipsis-right', 20]`). */
24
+ export function paginationRange(options: PaginationRangeOptions): PaginationItem[] {
25
+ const { pageCount } = options
26
+ if (pageCount <= 0) return []
27
+ const siblingCount = Math.max(0, options.siblingCount ?? 1)
28
+ const boundaryCount = Math.max(1, options.boundaryCount ?? 1)
29
+ const page = clamp(options.page, 1, pageCount)
30
+
31
+ // If ellipsis could hide at most one page, just show every page.
32
+ if (pageCount <= boundaryCount * 2 + siblingCount * 2 + 3) {
33
+ return Array.from({ length: pageCount }, (_, i) => i + 1)
34
+ }
35
+
36
+ const pages = new Set<number>()
37
+ // End boundaries (both ends).
38
+ for (let i = 1; i <= boundaryCount && i <= pageCount; i++) {
39
+ pages.add(i)
40
+ pages.add(pageCount - i + 1)
41
+ }
42
+ // Siblings around the current page.
43
+ for (let i = page - siblingCount; i <= page + siblingCount; i++) {
44
+ if (i >= 1 && i <= pageCount) pages.add(i)
45
+ }
46
+
47
+ const sorted = [...pages].sort((a, b) => a - b)
48
+ const result: PaginationItem[] = []
49
+ let prev = 0
50
+ for (const p of sorted) {
51
+ if (p - prev === 2) {
52
+ // Exactly one page in the gap: show it instead of an ellipsis.
53
+ result.push(prev + 1)
54
+ } else if (p - prev > 2) {
55
+ result.push(p <= page ? 'ellipsis-left' : 'ellipsis-right')
56
+ }
57
+ result.push(p)
58
+ prev = p
59
+ }
60
+ return result
61
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Per-country phone length validation (phoneDigitsValid): exact lengths, ranges,
3
+ * unknown countries (any non-empty), and empty handling.
4
+ */
5
+ import { describe, expect, it } from 'vitest'
6
+ import { phoneDigitsValid } from './countries'
7
+
8
+ describe('phoneDigitsValid', () => {
9
+ it('checks exact national lengths', () => {
10
+ expect(phoneDigitsValid('US', '4155551234')).toBe(true) // 10
11
+ expect(phoneDigitsValid('US', '415555')).toBe(false)
12
+ expect(phoneDigitsValid('IN', '9876543210')).toBe(true) // 10
13
+ expect(phoneDigitsValid('SG', '81234567')).toBe(true) // 8
14
+ expect(phoneDigitsValid('SG', '8123456')).toBe(false)
15
+ })
16
+ it('accepts ranges', () => {
17
+ expect(phoneDigitsValid('DE', '1701234567')).toBe(true) // 10 in [10,11]
18
+ expect(phoneDigitsValid('DE', '17012345678')).toBe(true) // 11
19
+ expect(phoneDigitsValid('DE', '170')).toBe(false)
20
+ })
21
+ it('ignores non-digits and unknown countries', () => {
22
+ expect(phoneDigitsValid('US', '(415) 555-1234')).toBe(true)
23
+ expect(phoneDigitsValid('ZZ', '12345')).toBe(true) // unknown -> any non-empty
24
+ expect(phoneDigitsValid('ZZ', '')).toBe(false)
25
+ })
26
+ })
package/src/popover.ts CHANGED
@@ -16,9 +16,12 @@
16
16
  * any popover panel actually consumes.
17
17
  */
18
18
  export const PANEL_THEME_VARS = [
19
- '--sg-accent', '--sg-bg', '--sg-fg', '--sg-border', '--sg-muted',
20
- '--sg-header-bg', '--sg-row-hover-bg', '--sg-selection-bg',
21
- '--sg-input-bg', '--sg-input-border', '--sg-selection-fg',
19
+ '--sg-accent', '--sg-on-accent', '--sg-bg', '--sg-fg', '--sg-muted', '--sg-border',
20
+ '--sg-header-bg', '--sg-header-fg', '--sg-row-hover-bg', '--sg-row-alt-bg',
21
+ '--sg-selection-bg', '--sg-selection-fg', '--sg-input-bg', '--sg-input-border',
22
+ '--sg-danger', '--sg-focus-ring', '--sg-radius', '--sg-font',
23
+ '--sg-invalid-bg', '--sg-invalid-border', '--sg-invalid-fg',
24
+ '--sg-rating-on', '--sg-rating-empty', '--sg-rating-hover',
22
25
  ] as const
23
26
 
24
27
  /**
@@ -40,6 +43,31 @@ export function portalToBody(node: HTMLElement, vars: ReadonlyArray<string> = PA
40
43
  }
41
44
  }
42
45
 
46
+ /**
47
+ * Svelte action: play a short enter animation when a portalled panel mounts
48
+ * (dropdowns, popovers, dialogs). Slides from the trigger side + fades/scales in.
49
+ * A no-op under `prefers-reduced-motion` and in environments without the Web
50
+ * Animations API (jsdom), so tests and reduced-motion users are unaffected.
51
+ *
52
+ * `use:popIn={{ up: rect.openUpward }}` - pass `up` for panels that flipped above
53
+ * their trigger so the slide direction matches.
54
+ */
55
+ export function popIn(node: HTMLElement, param: { up?: boolean; duration?: number; scale?: number } = {}) {
56
+ if (typeof node.animate !== 'function') return
57
+ const reduce = typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches
58
+ if (reduce) return
59
+ const dy = param.up ? 6 : -6
60
+ const s = param.scale ?? 0.97
61
+ node.animate(
62
+ [
63
+ { opacity: 0, transform: `translateY(${dy}px) scale(${s})` },
64
+ { opacity: 1, transform: 'translateY(0) scale(1)' },
65
+ ],
66
+ { duration: param.duration ?? 140, easing: 'cubic-bezier(0.16, 1, 0.3, 1)' },
67
+ )
68
+ return {}
69
+ }
70
+
43
71
  export type AnchoredRect = { top: number; left: number; width: number; openUpward: boolean }
44
72
 
45
73
  export type AnchorOptions = {
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Component tests for SvProgress + SvCircularProgress: progressbar ARIA,
3
+ * determinate fill width / ring offset, and indeterminate (no valuenow).
4
+ */
5
+ import { describe, expect, it } from 'vitest'
6
+ import { mount, unmount, flushSync } from 'svelte'
7
+ import SvProgress from './SvProgress.svelte'
8
+ import SvCircularProgress from './SvCircularProgress.svelte'
9
+
10
+ function mountC(Comp: any, props: Record<string, unknown>) {
11
+ const target = document.createElement('div')
12
+ document.body.appendChild(target)
13
+ const app = mount(Comp, { target, props: props as any })
14
+ flushSync()
15
+ return { target, destroy: () => { unmount(app); target.remove() } }
16
+ }
17
+
18
+ describe('SvProgress', () => {
19
+ it('exposes progressbar ARIA + fill width for a determinate value', () => {
20
+ const { target, destroy } = mountC(SvProgress, { value: 40, max: 200, showLabel: true })
21
+ try {
22
+ const bar = target.querySelector<HTMLElement>('[role="progressbar"]')!
23
+ expect(bar.getAttribute('aria-valuemin')).toBe('0')
24
+ expect(bar.getAttribute('aria-valuemax')).toBe('200')
25
+ expect(bar.getAttribute('aria-valuenow')).toBe('40')
26
+ expect(target.querySelector<HTMLElement>('.sv-prog__fill')!.style.width).toBe('20%')
27
+ expect(target.querySelector('.sv-prog__label')!.textContent).toBe('20%')
28
+ } finally { destroy() }
29
+ })
30
+
31
+ it('indeterminate drops valuenow and animates', () => {
32
+ const { target, destroy } = mountC(SvProgress, { indeterminate: true })
33
+ try {
34
+ const bar = target.querySelector<HTMLElement>('[role="progressbar"]')!
35
+ expect(bar.getAttribute('aria-valuenow')).toBeNull()
36
+ expect(target.querySelector('.sv-prog__fill.is-indeterminate')).not.toBeNull()
37
+ } finally { destroy() }
38
+ })
39
+ })
40
+
41
+ describe('SvCircularProgress', () => {
42
+ it('sets the ring dashoffset from the value', () => {
43
+ const { target, destroy } = mountC(SvCircularProgress, { value: 75, size: 48, thickness: 4, showLabel: true })
44
+ try {
45
+ const bar = target.querySelector<HTMLElement>('[role="progressbar"]')!
46
+ expect(bar.getAttribute('aria-valuenow')).toBe('75')
47
+ const arc = target.querySelector<SVGCircleElement>('.sv-circ__arc')!
48
+ const circ = 2 * Math.PI * ((48 - 4) / 2)
49
+ expect(Number(arc.getAttribute('stroke-dasharray'))).toBeCloseTo(circ, 3)
50
+ // 75% -> 25% of the circumference remains as offset.
51
+ expect(Number(arc.getAttribute('stroke-dashoffset'))).toBeCloseTo(circ * 0.25, 3)
52
+ expect(target.querySelector('.sv-circ__label')!.textContent).toBe('75%')
53
+ } finally { destroy() }
54
+ })
55
+ })
@@ -0,0 +1,91 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { matchesRecurrence, expandRecurrence, type RecurrenceRule } from './recurrence'
3
+
4
+ // Fixed reference dates (local time). 2026-07-01 is a Wednesday.
5
+ const D = (s: string) => new Date(`${s}T00:00:00`)
6
+
7
+ describe('matchesRecurrence - weekly', () => {
8
+ const weekdays: RecurrenceRule = { freq: 'weekly', weekdays: [1, 2, 3, 4, 5] } // Mon..Fri
9
+ it('matches weekdays, not weekends', () => {
10
+ expect(matchesRecurrence(D('2026-07-06'), weekdays)).toBe(true) // Monday
11
+ expect(matchesRecurrence(D('2026-07-10'), weekdays)).toBe(true) // Friday
12
+ expect(matchesRecurrence(D('2026-07-11'), weekdays)).toBe(false) // Saturday
13
+ expect(matchesRecurrence(D('2026-07-12'), weekdays)).toBe(false) // Sunday
14
+ })
15
+
16
+ it('every other Monday with an interval + anchor', () => {
17
+ const r: RecurrenceRule = { freq: 'weekly', weekdays: [1], interval: 2, from: D('2026-07-06') }
18
+ expect(matchesRecurrence(D('2026-07-06'), r)).toBe(true) // anchor Monday
19
+ expect(matchesRecurrence(D('2026-07-13'), r)).toBe(false) // next Monday (odd week)
20
+ expect(matchesRecurrence(D('2026-07-20'), r)).toBe(true) // +2 weeks
21
+ expect(matchesRecurrence(D('2026-07-07'), r)).toBe(false) // Tuesday
22
+ })
23
+
24
+ it("falls back to the anchor's weekday when no weekdays given", () => {
25
+ const r: RecurrenceRule = { freq: 'weekly', from: D('2026-07-01') } // Wednesday
26
+ expect(matchesRecurrence(D('2026-07-08'), r)).toBe(true) // Wednesday
27
+ expect(matchesRecurrence(D('2026-07-09'), r)).toBe(false) // Thursday
28
+ })
29
+ })
30
+
31
+ describe('matchesRecurrence - monthly / yearly / daily', () => {
32
+ it('monthly on a day-of-month', () => {
33
+ const r: RecurrenceRule = { freq: 'monthly', day: 1 }
34
+ expect(matchesRecurrence(D('2026-07-01'), r)).toBe(true)
35
+ expect(matchesRecurrence(D('2026-08-01'), r)).toBe(true)
36
+ expect(matchesRecurrence(D('2026-08-02'), r)).toBe(false)
37
+ })
38
+
39
+ it('monthly with an interval + anchor (every 3 months, the 15th)', () => {
40
+ const r: RecurrenceRule = { freq: 'monthly', day: 15, interval: 3, from: D('2026-01-15') }
41
+ expect(matchesRecurrence(D('2026-01-15'), r)).toBe(true)
42
+ expect(matchesRecurrence(D('2026-02-15'), r)).toBe(false)
43
+ expect(matchesRecurrence(D('2026-04-15'), r)).toBe(true)
44
+ })
45
+
46
+ it('yearly on a month + day', () => {
47
+ const r: RecurrenceRule = { freq: 'yearly', month: 11, day: 25 } // Dec 25
48
+ expect(matchesRecurrence(D('2026-12-25'), r)).toBe(true)
49
+ expect(matchesRecurrence(D('2027-12-25'), r)).toBe(true)
50
+ expect(matchesRecurrence(D('2026-12-24'), r)).toBe(false)
51
+ })
52
+
53
+ it('daily every N days from an anchor', () => {
54
+ const r: RecurrenceRule = { freq: 'daily', interval: 3, from: D('2026-07-01') }
55
+ expect(matchesRecurrence(D('2026-07-01'), r)).toBe(true)
56
+ expect(matchesRecurrence(D('2026-07-02'), r)).toBe(false)
57
+ expect(matchesRecurrence(D('2026-07-04'), r)).toBe(true)
58
+ })
59
+ })
60
+
61
+ describe('matchesRecurrence - bounds + combining', () => {
62
+ it('respects from / until', () => {
63
+ const r: RecurrenceRule = { freq: 'daily', from: D('2026-07-05'), until: D('2026-07-08') }
64
+ expect(matchesRecurrence(D('2026-07-04'), r)).toBe(false) // before
65
+ expect(matchesRecurrence(D('2026-07-06'), r)).toBe(true) // inside
66
+ expect(matchesRecurrence(D('2026-07-09'), r)).toBe(false) // after until
67
+ })
68
+
69
+ it('matches if ANY rule in a list matches', () => {
70
+ const rules: RecurrenceRule[] = [
71
+ { freq: 'weekly', weekdays: [1] }, // Mondays
72
+ { freq: 'monthly', day: 1 }, // the 1st
73
+ ]
74
+ expect(matchesRecurrence(D('2026-07-06'), rules)).toBe(true) // Monday
75
+ expect(matchesRecurrence(D('2026-07-01'), rules)).toBe(true) // the 1st (a Wednesday)
76
+ expect(matchesRecurrence(D('2026-07-02'), rules)).toBe(false)
77
+ })
78
+
79
+ it('returns false for empty / nullish rules', () => {
80
+ expect(matchesRecurrence(D('2026-07-01'), null)).toBe(false)
81
+ expect(matchesRecurrence(D('2026-07-01'), [])).toBe(false)
82
+ })
83
+ })
84
+
85
+ describe('expandRecurrence', () => {
86
+ it('lists the matching dates in a range', () => {
87
+ const r: RecurrenceRule = { freq: 'weekly', weekdays: [1] } // Mondays
88
+ const dates = expandRecurrence(r, D('2026-07-01'), D('2026-07-31')).map((d) => d.getDate())
89
+ expect(dates).toEqual([6, 13, 20, 27])
90
+ })
91
+ })