@svgrid/grid 1.2.34 → 2.0.1

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 (444) hide show
  1. package/README.md +6 -5
  2. package/dist/SvAccordion.svelte +109 -0
  3. package/dist/SvAccordion.svelte.d.ts +28 -0
  4. package/dist/SvAlert.svelte +92 -0
  5. package/dist/SvAlert.svelte.d.ts +27 -0
  6. package/dist/SvAutoComplete.svelte +47 -18
  7. package/dist/SvAutoComplete.svelte.d.ts +2 -5
  8. package/dist/SvAvatar.svelte +75 -0
  9. package/dist/SvAvatar.svelte.d.ts +14 -0
  10. package/dist/SvAvatarGroup.svelte +48 -0
  11. package/dist/SvAvatarGroup.svelte.d.ts +16 -0
  12. package/dist/SvBadge.svelte +54 -0
  13. package/dist/SvBadge.svelte.d.ts +23 -0
  14. package/dist/SvBreadcrumb.svelte +88 -0
  15. package/dist/SvBreadcrumb.svelte.d.ts +23 -0
  16. package/dist/SvButtonGroup.svelte +121 -0
  17. package/dist/SvButtonGroup.svelte.d.ts +16 -0
  18. package/dist/SvCalendar.svelte +72 -9
  19. package/dist/SvCalendar.svelte.d.ts +34 -1
  20. package/dist/SvCard.svelte +71 -0
  21. package/dist/SvCard.svelte.d.ts +28 -0
  22. package/dist/SvCarousel.svelte +134 -0
  23. package/dist/SvCarousel.svelte.d.ts +28 -0
  24. package/dist/SvCheckBox.svelte +36 -21
  25. package/dist/SvCheckBox.svelte.d.ts +2 -5
  26. package/dist/SvChip.svelte +88 -0
  27. package/dist/SvChip.svelte.d.ts +31 -0
  28. package/dist/SvCircularProgress.svelte +109 -0
  29. package/dist/SvCircularProgress.svelte.d.ts +31 -0
  30. package/dist/SvColorInput.svelte +54 -27
  31. package/dist/SvColorInput.svelte.d.ts +10 -6
  32. package/dist/SvComboBox.svelte +119 -33
  33. package/dist/SvComboBox.svelte.d.ts +18 -6
  34. package/dist/SvCommand.svelte +197 -0
  35. package/dist/SvCommand.svelte.d.ts +26 -0
  36. package/dist/SvContextMenu.svelte +96 -0
  37. package/dist/SvContextMenu.svelte.d.ts +25 -0
  38. package/dist/SvCountryInput.svelte +56 -21
  39. package/dist/SvCountryInput.svelte.d.ts +9 -5
  40. package/dist/SvDateRangeInput.svelte +256 -0
  41. package/dist/SvDateRangeInput.svelte.d.ts +37 -0
  42. package/dist/SvDateTimePicker.svelte +57 -20
  43. package/dist/SvDateTimePicker.svelte.d.ts +12 -4
  44. package/dist/SvDivider.svelte +61 -0
  45. package/dist/SvDivider.svelte.d.ts +22 -0
  46. package/dist/SvDrawer.svelte +226 -0
  47. package/dist/SvDrawer.svelte.d.ts +43 -0
  48. package/dist/SvDropDownList.svelte +114 -35
  49. package/dist/SvDropDownList.svelte.d.ts +7 -6
  50. package/dist/SvDurationInput.svelte +108 -0
  51. package/dist/SvDurationInput.svelte.d.ts +15 -0
  52. package/dist/SvEmptyState.svelte +59 -0
  53. package/dist/SvEmptyState.svelte.d.ts +25 -0
  54. package/dist/SvField.svelte +85 -0
  55. package/dist/SvField.svelte.d.ts +30 -0
  56. package/dist/SvFileUpload.svelte +169 -0
  57. package/dist/SvFileUpload.svelte.d.ts +24 -0
  58. package/dist/SvForm.svelte +7 -1
  59. package/dist/SvForm.svelte.d.ts +3 -0
  60. package/dist/SvGauge.svelte +7 -1
  61. package/dist/SvGauge.svelte.d.ts +11 -0
  62. package/dist/SvGrid.controller.svelte.d.ts +6 -0
  63. package/dist/SvGrid.controller.svelte.js +64 -9
  64. package/dist/SvGrid.css +33 -0
  65. package/dist/SvGrid.svelte +116 -3
  66. package/dist/SvGrid.types.d.ts +366 -0
  67. package/dist/SvGridBoard.svelte +2281 -0
  68. package/dist/SvGridBoard.svelte.d.ts +31 -0
  69. package/dist/SvGridDropdown.svelte +2 -1
  70. package/dist/SvGridSelect.svelte +255 -0
  71. package/dist/SvGridSelect.svelte.d.ts +32 -0
  72. package/dist/SvGroupCell.svelte +109 -0
  73. package/dist/SvGroupCell.svelte.d.ts +41 -0
  74. package/dist/SvListBox.svelte +123 -36
  75. package/dist/SvListBox.svelte.d.ts +12 -8
  76. package/dist/SvMaskedInput.svelte +53 -13
  77. package/dist/SvMaskedInput.svelte.d.ts +16 -6
  78. package/dist/SvMenu.svelte +101 -0
  79. package/dist/SvMenu.svelte.d.ts +26 -0
  80. package/dist/SvMenuList.svelte +143 -0
  81. package/dist/SvMenuList.svelte.d.ts +24 -0
  82. package/dist/SvModal.svelte +220 -0
  83. package/dist/SvModal.svelte.d.ts +31 -0
  84. package/dist/SvMultiSelect.svelte +277 -0
  85. package/dist/SvMultiSelect.svelte.d.ts +31 -0
  86. package/dist/SvNavPane.svelte +314 -0
  87. package/dist/SvNavPane.svelte.d.ts +59 -0
  88. package/dist/SvNumberInput.svelte +23 -9
  89. package/dist/SvNumberInput.svelte.d.ts +2 -0
  90. package/dist/SvOtpInput.svelte +157 -0
  91. package/dist/SvOtpInput.svelte.d.ts +16 -0
  92. package/dist/SvPagination.svelte +100 -0
  93. package/dist/SvPagination.svelte.d.ts +16 -0
  94. package/dist/SvPasswordInput.svelte +42 -12
  95. package/dist/SvPasswordInput.svelte.d.ts +13 -6
  96. package/dist/SvPhoneInput.svelte +43 -11
  97. package/dist/SvPhoneInput.svelte.d.ts +10 -11
  98. package/dist/SvPopover.svelte +127 -0
  99. package/dist/SvPopover.svelte.d.ts +35 -0
  100. package/dist/SvProgress.svelte +116 -0
  101. package/dist/SvProgress.svelte.d.ts +32 -0
  102. package/dist/SvRadioGroup.svelte +40 -22
  103. package/dist/SvRadioGroup.svelte.d.ts +2 -10
  104. package/dist/SvRating.svelte +51 -30
  105. package/dist/SvRating.svelte.d.ts +10 -7
  106. package/dist/SvRichText.svelte +192 -0
  107. package/dist/SvRichText.svelte.d.ts +16 -0
  108. package/dist/SvRowGroupPanel.svelte +147 -0
  109. package/dist/SvRowGroupPanel.svelte.d.ts +17 -0
  110. package/dist/SvScrollArea.svelte +50 -0
  111. package/dist/SvScrollArea.svelte.d.ts +22 -0
  112. package/dist/SvSkeleton.svelte +76 -0
  113. package/dist/SvSkeleton.svelte.d.ts +25 -0
  114. package/dist/SvSlider.svelte +41 -9
  115. package/dist/SvSlider.svelte.d.ts +4 -4
  116. package/dist/SvSparkline.svelte +77 -0
  117. package/dist/SvSparkline.svelte.d.ts +28 -0
  118. package/dist/SvSplitter.svelte +138 -0
  119. package/dist/SvSplitter.svelte.d.ts +32 -0
  120. package/dist/SvStat.svelte +86 -0
  121. package/dist/SvStat.svelte.d.ts +30 -0
  122. package/dist/SvStepper.svelte +91 -0
  123. package/dist/SvStepper.svelte.d.ts +24 -0
  124. package/dist/SvSwitchButton.svelte +39 -24
  125. package/dist/SvSwitchButton.svelte.d.ts +2 -5
  126. package/dist/SvTabs.svelte +147 -28
  127. package/dist/SvTabs.svelte.d.ts +16 -4
  128. package/dist/SvTagsInput.svelte +59 -22
  129. package/dist/SvTagsInput.svelte.d.ts +9 -4
  130. package/dist/SvTextArea.svelte +113 -0
  131. package/dist/SvTextArea.svelte.d.ts +18 -0
  132. package/dist/SvTextInput.svelte +115 -0
  133. package/dist/SvTextInput.svelte.d.ts +20 -0
  134. package/dist/SvTimePicker.svelte +19 -5
  135. package/dist/SvTimePicker.svelte.d.ts +12 -0
  136. package/dist/SvTimeline.svelte +75 -0
  137. package/dist/SvTimeline.svelte.d.ts +24 -0
  138. package/dist/SvToaster.svelte +124 -0
  139. package/dist/SvToaster.svelte.d.ts +10 -0
  140. package/dist/SvToggleButton.svelte +37 -12
  141. package/dist/SvToggleButton.svelte.d.ts +2 -4
  142. package/dist/SvTooltip.svelte +113 -0
  143. package/dist/SvTooltip.svelte.d.ts +22 -0
  144. package/dist/SvTour.svelte +151 -0
  145. package/dist/SvTour.svelte.d.ts +25 -0
  146. package/dist/SvTree.svelte +286 -44
  147. package/dist/SvTree.svelte.d.ts +26 -9
  148. package/dist/SvTreeSelect.svelte +222 -0
  149. package/dist/SvTreeSelect.svelte.d.ts +24 -0
  150. package/dist/a11y/dismissable.d.ts +32 -0
  151. package/dist/a11y/dismissable.js +54 -0
  152. package/dist/a11y/focus-trap.d.ts +39 -0
  153. package/dist/a11y/focus-trap.js +142 -0
  154. package/dist/a11y/live-region.d.ts +8 -0
  155. package/dist/a11y/live-region.js +60 -0
  156. package/dist/a11y/scroll-lock.d.ts +4 -0
  157. package/dist/a11y/scroll-lock.js +41 -0
  158. package/dist/a11y.d.ts +3 -1
  159. package/dist/a11y.js +1 -1
  160. package/dist/avatar.d.ts +10 -0
  161. package/dist/avatar.js +22 -0
  162. package/dist/builtin-editors.d.ts +5 -0
  163. package/dist/builtin-editors.js +55 -0
  164. package/dist/cdn/svgrid.js +16021 -8234
  165. package/dist/cdn/svgrid.svelte-external.js +14542 -6764
  166. package/dist/columns.js +3 -2
  167. package/dist/core.d.ts +7 -1
  168. package/dist/countries.d.ts +12 -0
  169. package/dist/countries.js +24 -0
  170. package/dist/createAccordion.svelte.d.ts +71 -0
  171. package/dist/createAccordion.svelte.js +99 -0
  172. package/dist/createAutocomplete.svelte.d.ts +13 -4
  173. package/dist/createAutocomplete.svelte.js +11 -1
  174. package/dist/createButtonGroup.svelte.d.ts +62 -0
  175. package/dist/createButtonGroup.svelte.js +154 -0
  176. package/dist/createCalendar.svelte.d.ts +5 -0
  177. package/dist/createCalendar.svelte.js +2 -0
  178. package/dist/createCheckbox.svelte.d.ts +9 -17
  179. package/dist/createCheckbox.svelte.js +28 -1
  180. package/dist/createColorInput.svelte.d.ts +12 -22
  181. package/dist/createColorInput.svelte.js +13 -0
  182. package/dist/createCombobox.svelte.d.ts +16 -5
  183. package/dist/createCombobox.svelte.js +13 -2
  184. package/dist/createCountryInput.svelte.d.ts +14 -3
  185. package/dist/createCountryInput.svelte.js +12 -2
  186. package/dist/createDateTimePicker.svelte.d.ts +17 -2
  187. package/dist/createDateTimePicker.svelte.js +12 -2
  188. package/dist/createDropdownList.svelte.d.ts +14 -5
  189. package/dist/createDropdownList.svelte.js +50 -1
  190. package/dist/createFileUpload.svelte.d.ts +68 -0
  191. package/dist/createFileUpload.svelte.js +122 -0
  192. package/dist/createListbox.svelte.d.ts +10 -1
  193. package/dist/createListbox.svelte.js +48 -1
  194. package/dist/createMaskedInput.svelte.d.ts +11 -2
  195. package/dist/createMaskedInput.svelte.js +11 -1
  196. package/dist/createNumberInput.svelte.d.ts +1 -0
  197. package/dist/createNumberInput.svelte.js +1 -0
  198. package/dist/createPasswordInput.svelte.d.ts +19 -21
  199. package/dist/createPasswordInput.svelte.js +16 -4
  200. package/dist/createPhoneInput.svelte.d.ts +26 -7
  201. package/dist/createPhoneInput.svelte.js +24 -3
  202. package/dist/createPopoverSelect.svelte.d.ts +86 -0
  203. package/dist/createPopoverSelect.svelte.js +196 -0
  204. package/dist/createRadioGroup.svelte.d.ts +9 -0
  205. package/dist/createRadioGroup.svelte.js +11 -1
  206. package/dist/createRating.svelte.d.ts +11 -0
  207. package/dist/createRating.svelte.js +12 -1
  208. package/dist/createSlider.svelte.d.ts +2 -0
  209. package/dist/createSlider.svelte.js +16 -5
  210. package/dist/createSplitter.svelte.d.ts +67 -0
  211. package/dist/createSplitter.svelte.js +99 -0
  212. package/dist/createSwitch.svelte.d.ts +9 -16
  213. package/dist/createSwitch.svelte.js +28 -1
  214. package/dist/createTabs.svelte.d.ts +7 -0
  215. package/dist/createTabs.svelte.js +17 -2
  216. package/dist/createTagsInput.svelte.d.ts +13 -1
  217. package/dist/createTagsInput.svelte.js +12 -3
  218. package/dist/createToggle.svelte.d.ts +9 -0
  219. package/dist/createToggle.svelte.js +12 -1
  220. package/dist/createTree.svelte.d.ts +19 -0
  221. package/dist/createTree.svelte.js +108 -15
  222. package/dist/debounce.d.ts +11 -0
  223. package/dist/debounce.js +30 -0
  224. package/dist/duration.d.ts +15 -0
  225. package/dist/duration.js +51 -0
  226. package/dist/editor-contract.d.ts +62 -7
  227. package/dist/editor-contract.js +45 -5
  228. package/dist/editor-registry.d.ts +67 -0
  229. package/dist/editor-registry.js +38 -0
  230. package/dist/editors/cell-editors.d.ts +1 -1
  231. package/dist/editors/cell-editors.js +5 -0
  232. package/dist/fuzzy.d.ts +3 -0
  233. package/dist/fuzzy.js +41 -0
  234. package/dist/grid-select.d.ts +9 -0
  235. package/dist/grid-select.js +8 -0
  236. package/dist/index.d.ts +79 -6
  237. package/dist/index.js +78 -4
  238. package/dist/keyboard-handlers.js +19 -0
  239. package/dist/list-option.d.ts +36 -0
  240. package/dist/list-option.js +68 -0
  241. package/dist/otp.d.ts +16 -0
  242. package/dist/otp.js +26 -0
  243. package/dist/paginate.d.ts +20 -0
  244. package/dist/paginate.js +40 -0
  245. package/dist/popover.d.ts +15 -1
  246. package/dist/popover.js +29 -3
  247. package/dist/recurrence.d.ts +36 -0
  248. package/dist/recurrence.js +90 -0
  249. package/dist/server-data-source.d.ts +89 -0
  250. package/dist/server-data-source.js +4 -0
  251. package/dist/server-group-model.d.ts +98 -0
  252. package/dist/server-group-model.js +263 -0
  253. package/dist/toast-store.svelte.d.ts +41 -0
  254. package/dist/toast-store.svelte.js +113 -0
  255. package/dist/tree-select.d.ts +19 -0
  256. package/dist/tree-select.js +40 -0
  257. package/dist/ui-app.types.d.ts +80 -0
  258. package/dist/ui-app.types.js +7 -0
  259. package/dist/validators.d.ts +43 -0
  260. package/dist/validators.js +53 -0
  261. package/dist/virtual.d.ts +35 -0
  262. package/dist/virtual.js +34 -0
  263. package/package.json +1 -1
  264. package/src/SvAccordion.svelte +109 -0
  265. package/src/SvAccordion.test.ts +66 -0
  266. package/src/SvAlert.svelte +92 -0
  267. package/src/SvAutoComplete.svelte +47 -18
  268. package/src/SvAvatar.svelte +75 -0
  269. package/src/SvAvatarGroup.svelte +48 -0
  270. package/src/SvBadge.svelte +54 -0
  271. package/src/SvBreadcrumb.svelte +88 -0
  272. package/src/SvButtonGroup.svelte +121 -0
  273. package/src/SvButtonGroup.test.ts +72 -0
  274. package/src/SvCalendar.svelte +72 -9
  275. package/src/SvCard.svelte +71 -0
  276. package/src/SvCarousel.svelte +134 -0
  277. package/src/SvCheckBox.svelte +36 -21
  278. package/src/SvChip.svelte +88 -0
  279. package/src/SvCircularProgress.svelte +109 -0
  280. package/src/SvColorInput.svelte +54 -27
  281. package/src/SvComboBox.svelte +119 -33
  282. package/src/SvCommand.svelte +197 -0
  283. package/src/SvContextMenu.svelte +96 -0
  284. package/src/SvCountryInput.svelte +56 -21
  285. package/src/SvDateRangeInput.svelte +256 -0
  286. package/src/SvDateRangeInput.test.ts +80 -0
  287. package/src/SvDateTimePicker.svelte +57 -20
  288. package/src/SvDivider.svelte +61 -0
  289. package/src/SvDrawer.svelte +226 -0
  290. package/src/SvDrawer.test.ts +125 -0
  291. package/src/SvDropDownList.svelte +114 -35
  292. package/src/SvDurationInput.svelte +108 -0
  293. package/src/SvEmptyState.svelte +59 -0
  294. package/src/SvField.svelte +85 -0
  295. package/src/SvFileUpload.svelte +169 -0
  296. package/src/SvFileUpload.test.ts +59 -0
  297. package/src/SvForm.svelte +7 -1
  298. package/src/SvGauge.svelte +7 -1
  299. package/src/SvGrid.controller.svelte.ts +68 -9
  300. package/src/SvGrid.css +33 -0
  301. package/src/SvGrid.svelte +116 -3
  302. package/src/SvGrid.types.ts +374 -0
  303. package/src/SvGridBoard.svelte +2281 -0
  304. package/src/SvGridDropdown.svelte +2 -1
  305. package/src/SvGridSelect.svelte +255 -0
  306. package/src/SvGridSelect.test.ts +100 -0
  307. package/src/SvGroupCell.svelte +109 -0
  308. package/src/SvListBox.svelte +123 -36
  309. package/src/SvMaskedInput.svelte +53 -13
  310. package/src/SvMenu.svelte +101 -0
  311. package/src/SvMenu.test.ts +73 -0
  312. package/src/SvMenuList.svelte +143 -0
  313. package/src/SvModal.svelte +220 -0
  314. package/src/SvMultiSelect.svelte +277 -0
  315. package/src/SvMultiSelect.test.ts +167 -0
  316. package/src/SvNavPane.svelte +314 -0
  317. package/src/SvNavPane.test.ts +70 -0
  318. package/src/SvNumberInput.svelte +23 -9
  319. package/src/SvOtpInput.svelte +157 -0
  320. package/src/SvPagination.svelte +100 -0
  321. package/src/SvPasswordInput.svelte +42 -12
  322. package/src/SvPhoneInput.svelte +43 -11
  323. package/src/SvPopover.svelte +127 -0
  324. package/src/SvProgress.svelte +116 -0
  325. package/src/SvRadioGroup.svelte +40 -22
  326. package/src/SvRating.svelte +51 -30
  327. package/src/SvRichText.svelte +192 -0
  328. package/src/SvRowGroupPanel.svelte +147 -0
  329. package/src/SvScrollArea.svelte +50 -0
  330. package/src/SvSkeleton.svelte +76 -0
  331. package/src/SvSlider.svelte +41 -9
  332. package/src/SvSparkline.svelte +77 -0
  333. package/src/SvSplitter.svelte +138 -0
  334. package/src/SvSplitter.test.ts +55 -0
  335. package/src/SvStat.svelte +86 -0
  336. package/src/SvStepper.svelte +91 -0
  337. package/src/SvSwitchButton.svelte +39 -24
  338. package/src/SvTabs.svelte +147 -28
  339. package/src/SvTagsInput.svelte +59 -22
  340. package/src/SvTextArea.svelte +113 -0
  341. package/src/SvTextInput.svelte +115 -0
  342. package/src/SvTimePicker.svelte +19 -5
  343. package/src/SvTimeline.svelte +75 -0
  344. package/src/SvToaster.svelte +124 -0
  345. package/src/SvToaster.test.ts +63 -0
  346. package/src/SvToggleButton.svelte +37 -12
  347. package/src/SvTooltip.svelte +113 -0
  348. package/src/SvTour.svelte +151 -0
  349. package/src/SvTree.svelte +286 -44
  350. package/src/SvTreeSelect.svelte +222 -0
  351. package/src/SvTreeSelect.test.ts +87 -0
  352. package/src/a11y/dismissable.test.ts +95 -0
  353. package/src/a11y/dismissable.ts +94 -0
  354. package/src/a11y/focus-trap.test.ts +110 -0
  355. package/src/a11y/focus-trap.ts +134 -0
  356. package/src/a11y/live-region.test.ts +40 -0
  357. package/src/a11y/live-region.ts +65 -0
  358. package/src/a11y/scroll-lock.test.ts +38 -0
  359. package/src/a11y/scroll-lock.ts +41 -0
  360. package/src/a11y.ts +3 -1
  361. package/src/avatar.test.ts +32 -0
  362. package/src/avatar.ts +21 -0
  363. package/src/board.test.ts +927 -0
  364. package/src/builtin-editors.grid.test.ts +85 -0
  365. package/src/builtin-editors.test.ts +58 -0
  366. package/src/builtin-editors.ts +56 -0
  367. package/src/columns.ts +3 -2
  368. package/src/core.ts +10 -0
  369. package/src/countries.ts +25 -0
  370. package/src/createAccordion.svelte.ts +126 -0
  371. package/src/createAutocomplete.svelte.ts +18 -1
  372. package/src/createButtonGroup.svelte.ts +166 -0
  373. package/src/createCalendar.svelte.ts +6 -0
  374. package/src/createCheckbox.svelte.ts +23 -1
  375. package/src/createColorInput.svelte.ts +20 -0
  376. package/src/createCombobox.svelte.ts +22 -2
  377. package/src/createCountryInput.svelte.ts +21 -2
  378. package/src/createDateTimePicker.svelte.ts +23 -2
  379. package/src/createDropdownList.svelte.ts +31 -2
  380. package/src/createFileUpload.svelte.ts +144 -0
  381. package/src/createListbox.svelte.ts +35 -3
  382. package/src/createMaskedInput.svelte.ts +18 -1
  383. package/src/createNumberInput.svelte.ts +2 -0
  384. package/src/createPasswordInput.svelte.ts +31 -5
  385. package/src/createPhoneInput.svelte.ts +38 -4
  386. package/src/createPopoverSelect.svelte.ts +208 -0
  387. package/src/createRadioGroup.svelte.ts +23 -1
  388. package/src/createRating.svelte.ts +26 -1
  389. package/src/createSlider.svelte.ts +14 -3
  390. package/src/createSplitter.svelte.ts +119 -0
  391. package/src/createSwitch.svelte.ts +26 -3
  392. package/src/createTabs.svelte.ts +20 -3
  393. package/src/createTagsInput.svelte.ts +24 -3
  394. package/src/createToggle.svelte.ts +25 -2
  395. package/src/createTree.svelte.ts +104 -14
  396. package/src/debounce.test.ts +50 -0
  397. package/src/debounce.ts +41 -0
  398. package/src/duration.test.ts +41 -0
  399. package/src/duration.ts +51 -0
  400. package/src/editor-contract.ts +87 -8
  401. package/src/editor-registry.grid.test.ts +98 -0
  402. package/src/editor-registry.test.ts +86 -0
  403. package/src/editor-registry.ts +106 -0
  404. package/src/editors/cell-editors.ts +10 -1
  405. package/src/fuzzy.test.ts +40 -0
  406. package/src/fuzzy.ts +39 -0
  407. package/src/grid-select.test.ts +28 -0
  408. package/src/grid-select.ts +20 -0
  409. package/src/index.ts +150 -5
  410. package/src/keyboard-handlers.ts +20 -0
  411. package/src/list-option.ts +74 -0
  412. package/src/list-power.test.ts +71 -0
  413. package/src/otp.test.ts +28 -0
  414. package/src/otp.ts +28 -0
  415. package/src/overlays.test.ts +85 -0
  416. package/src/paginate.test.ts +41 -0
  417. package/src/paginate.ts +61 -0
  418. package/src/phone-valid.test.ts +26 -0
  419. package/src/popover.ts +31 -3
  420. package/src/progress.test.ts +55 -0
  421. package/src/recurrence.test.ts +91 -0
  422. package/src/recurrence.ts +111 -0
  423. package/src/server-data-source.ts +102 -0
  424. package/src/server-group-model.test.ts +294 -0
  425. package/src/server-group-model.ts +370 -0
  426. package/src/svgrid.responsive.test.ts +72 -0
  427. package/src/toast-store.svelte.ts +148 -0
  428. package/src/toast-store.test.ts +86 -0
  429. package/src/tree-edit.test.ts +49 -0
  430. package/src/tree-filter.test.ts +56 -0
  431. package/src/tree-select.test.ts +48 -0
  432. package/src/tree-select.ts +62 -0
  433. package/src/ui-app.test.ts +130 -0
  434. package/src/ui-app.types.ts +88 -0
  435. package/src/ui-display.test.ts +79 -0
  436. package/src/ui-inputs.test.ts +1 -1
  437. package/src/ui-status.test.ts +141 -0
  438. package/src/ui-tier1.test.ts +142 -0
  439. package/src/ui-tier2.test.ts +124 -0
  440. package/src/ui-tier3.test.ts +139 -0
  441. package/src/validators.test.ts +58 -0
  442. package/src/validators.ts +105 -0
  443. package/src/virtual.test.ts +72 -0
  444. package/src/virtual.ts +67 -0
package/src/index.ts CHANGED
@@ -69,12 +69,60 @@ export { createGrid, createSvGrid, type SvelteGrid } from './createGrid.svelte'
69
69
  export { createGridState, createSvGridState } from './createGridState.svelte'
70
70
  export { subscribeGrid, subscribeSvGrid } from './subscribe'
71
71
  export { default as SvGrid } from './SvGrid.svelte'
72
+ export { default as SvGridBoard } from './SvGridBoard.svelte'
73
+ export type { BoardConfig, BoardLane, BoardCardMoveEvent, BoardCardCommitEvent, BoardLayout, BoardSubtask, BoardComment, BoardMenuContext, BoardLaneMenuContext, BoardDrawerConfig } from './SvGrid.types'
72
74
  export { default as SvGridDropdown } from './SvGridDropdown.svelte'
73
75
  export { default as SvCalendar, type CalendarValue, type CalendarPreset, type CalendarAnimation } from './SvCalendar.svelte'
76
+ export { matchesRecurrence, expandRecurrence, type RecurrenceRule, type RecurrenceFreq } from './recurrence'
74
77
  export { default as SvTimePicker, type TimeValue } from './SvTimePicker.svelte'
75
78
  export { default as SvDateTimePicker, type DateTimeValue } from './SvDateTimePicker.svelte'
79
+ export { default as SvDateRangeInput, type DateRangeValue } from './SvDateRangeInput.svelte'
80
+ export { default as SvButtonGroup, type ButtonGroupItem, type ButtonGroupMode, type ButtonGroupValue } from './SvButtonGroup.svelte'
76
81
  // UI kit - shared editor contract (common props + a11y wiring for every editor)
77
- export { editorAria, editorErrorId, type SvEditorProps, type EditorSize, type EditorAriaState } from './editor-contract'
82
+ export {
83
+ editorAria,
84
+ editorErrorId,
85
+ editorHintId,
86
+ nextEditorId,
87
+ resolveMessages,
88
+ type SvEditorProps,
89
+ type EditorSize,
90
+ type EditorDir,
91
+ type EditorAriaState,
92
+ } from './editor-contract'
93
+ // UI kit - editor interaction surface (commit/cancel/close for grid-cell use)
94
+ export { type EditorInteraction } from './editor-contract'
95
+ // UI kit - shared a11y primitives (focus trap, scroll lock, dismissable layers, live region)
96
+ export {
97
+ FOCUSABLE_SELECTOR,
98
+ getFocusable,
99
+ createFocusTrap,
100
+ type FocusTrap,
101
+ type FocusTrapOptions,
102
+ } from './a11y/focus-trap'
103
+ export { lockScroll, scrollLockDepth } from './a11y/scroll-lock'
104
+ export {
105
+ createDismissableLayer,
106
+ dismissableDepth,
107
+ type DismissableLayer,
108
+ type DismissableOptions,
109
+ } from './a11y/dismissable'
110
+ export { announce, type AnnounceOptions } from './a11y/live-region'
111
+ // UI kit - custom cell-editor registry (register any component as a grid editor)
112
+ export {
113
+ registerCellEditor,
114
+ getCellEditor,
115
+ hasCellEditor,
116
+ unregisterCellEditor,
117
+ registeredCellEditorTypes,
118
+ defaultEditorProps,
119
+ resolveEditorProps,
120
+ type CellEditorContext,
121
+ type CellEditorRegistration,
122
+ } from './editor-registry'
123
+ export { registerBuiltinEditors } from './builtin-editors'
124
+ // UI kit - shared field chrome (label + hint + error + direction wrapper)
125
+ export { default as SvField } from './SvField.svelte'
78
126
  // UI kit - headless cores (state + prop-getters you render yourself, like createSvGrid)
79
127
  export {
80
128
  createListbox,
@@ -94,7 +142,7 @@ export { createCountryInput, type CountryInput, type CountryInputConfig } from '
94
142
  // Text-input family cores
95
143
  export { createNumberInput, type NumberInputConfig, type NumberInputCore } from './createNumberInput.svelte'
96
144
  export { createMaskedInput, type MaskedInputConfig, type MaskedInputCore } from './createMaskedInput.svelte'
97
- export { createPhoneInput, type PhoneInputConfig, type PhoneInputCore } from './createPhoneInput.svelte'
145
+ export { createPhoneInput, type PhoneInputConfig, type PhoneInputCore, type PhoneParts } from './createPhoneInput.svelte'
98
146
  export { createColorInput, normalizeHex, type ColorInputConfig, type ColorInputCore } from './createColorInput.svelte'
99
147
  export { createPasswordInput, passwordStrength, STRENGTH_LABELS, type PasswordInputConfig, type PasswordInputCore } from './createPasswordInput.svelte'
100
148
  // Buttons & toggles family cores
@@ -109,9 +157,10 @@ export { createTimePicker, parseTimeValue, type TimePicker, type TimePickerConfi
109
157
  export { createDateTimePicker, type DateTimePicker, type DateTimePickerConfig, type DropDownDisplayMode, type DateTimeTab } from './createDateTimePicker.svelte'
110
158
  // Layout & range family cores
111
159
  export { createTabs, type Tabs, type TabsConfig, type TabsOrientation, type TabsActivation } from './createTabs.svelte'
112
- export { createTree, treeDescendantIds, treeCheckState, type Tree, type TreeConfig, type TreeRow, type CheckState } from './createTree.svelte'
160
+ export { createTree, treeDescendantIds, treeCheckState, moveTreeNode, sortTreeNodes, treeContains, type Tree, type TreeConfig, type TreeRow, type CheckState, type TreeDropPosition } from './createTree.svelte'
113
161
  export { createSlider, type Slider, type SliderConfig, type SliderValue, type SliderOrientation, type SliderThumb, type SliderPoint } from './createSlider.svelte'
114
162
  export { createGauge, type Gauge, type GaugeConfig, type GaugePoint } from './createGauge.svelte'
163
+ export { createButtonGroup, toGroupArray, type ButtonGroup, type ButtonGroupConfig } from './createButtonGroup.svelte'
115
164
  // UI kit - Group B (buttons & toggles)
116
165
  export { default as SvButton } from './SvButton.svelte'
117
166
  export { default as SvRepeatButton } from './SvRepeatButton.svelte'
@@ -121,28 +170,106 @@ export { default as SvCheckBox } from './SvCheckBox.svelte'
121
170
  export { default as SvRadioGroup, type RadioOption } from './SvRadioGroup.svelte'
122
171
  export { default as SvRating } from './SvRating.svelte'
123
172
  // UI kit - Group C (text inputs)
173
+ export { default as SvTextInput } from './SvTextInput.svelte'
174
+ export { default as SvTextArea } from './SvTextArea.svelte'
175
+ export { default as SvOtpInput } from './SvOtpInput.svelte'
176
+ export { sanitizeOtp, otpCells, isOtpComplete } from './otp'
177
+ export { default as SvDurationInput } from './SvDurationInput.svelte'
178
+ export { parseDuration, formatDuration } from './duration'
124
179
  export { default as SvNumberInput } from './SvNumberInput.svelte'
125
180
  export { default as SvPasswordInput } from './SvPasswordInput.svelte'
126
181
  export { default as SvMaskedInput } from './SvMaskedInput.svelte'
127
182
  export { default as SvPhoneInput } from './SvPhoneInput.svelte'
128
183
  export { default as SvColorInput } from './SvColorInput.svelte'
129
184
  export { applyMask, unmask, isMaskComplete } from './datetime/mask'
130
- export { COUNTRIES, COUNTRY_BY_CODE, flagEmoji, type Country } from './countries'
185
+ export { COUNTRIES, COUNTRY_BY_CODE, flagEmoji, phoneDigitsValid, PHONE_NATIONAL_LENGTHS, type Country } from './countries'
131
186
  // UI kit - Group D (selection overlays)
132
187
  export { default as SvListBox } from './SvListBox.svelte'
133
188
  export { default as SvDropDownList } from './SvDropDownList.svelte'
134
189
  export { default as SvComboBox } from './SvComboBox.svelte'
135
190
  export { default as SvAutoComplete } from './SvAutoComplete.svelte'
136
191
  export { default as SvTagsInput } from './SvTagsInput.svelte'
192
+ export { default as SvMultiSelect, type MultiSelectOption } from './SvMultiSelect.svelte'
193
+ export { debounce, type Debounced } from './debounce'
194
+ export { default as SvTreeSelect, type TreeSelectNode } from './SvTreeSelect.svelte'
195
+ export { flattenVisible, findNodePath, branchValues, type FlatRow } from './tree-select'
196
+ export { default as SvGridSelect, type GridSelectColumn } from './SvGridSelect.svelte'
197
+ export { filterGridRows } from './grid-select'
137
198
  export { default as SvCountryInput } from './SvCountryInput.svelte'
138
- export { filterOptions, type ListOption } from './list-option'
199
+ export {
200
+ filterOptions,
201
+ groupOptions,
202
+ hasGroups,
203
+ nextTypeaheadIndex,
204
+ createTypeaheadBuffer,
205
+ isTypeaheadKey,
206
+ type ListOption,
207
+ type IndexedOption,
208
+ type OptionGroup,
209
+ } from './list-option'
210
+ export { virtualRange, scrollToIndex, type VirtualRange, type VirtualParams } from './virtual'
139
211
  // UI kit - Group E (range & feedback)
140
212
  export { default as SvSlider } from './SvSlider.svelte'
141
213
  export { default as SvGauge } from './SvGauge.svelte'
142
214
  // UI kit - Group F (composite / layout)
143
215
  export { default as SvTabs, type TabItem } from './SvTabs.svelte'
144
216
  export { default as SvTree, type TreeNode as SvTreeNode } from './SvTree.svelte'
217
+ export { default as SvAccordion, type AccordionItem, type AccordionExpandMode } from './SvAccordion.svelte'
218
+ export { createAccordion, type Accordion, type AccordionConfig } from './createAccordion.svelte'
219
+ export { default as SvSplitter, type SplitterOrientation } from './SvSplitter.svelte'
220
+ export { createSplitter, type Splitter, type SplitterConfig, type SplitterPoint } from './createSplitter.svelte'
221
+ export { default as SvFileUpload, type FileRejection, type FileRejectReason } from './SvFileUpload.svelte'
222
+ export { createFileUpload, fileMatchesAccept, type FileUpload, type FileUploadConfig } from './createFileUpload.svelte'
223
+ // UI kit - overlays (popover / tooltip / modal), sharing the popover.ts positioning + animation
224
+ export { default as SvPopover } from './SvPopover.svelte'
225
+ export { default as SvTooltip } from './SvTooltip.svelte'
226
+ export { default as SvModal } from './SvModal.svelte'
227
+ export { default as SvDrawer } from './SvDrawer.svelte'
228
+ export type DrawerSide = 'right' | 'left' | 'top' | 'bottom'
229
+ export { default as SvToaster } from './SvToaster.svelte'
230
+ export { default as SvBadge } from './SvBadge.svelte'
231
+ export { default as SvSkeleton } from './SvSkeleton.svelte'
232
+ export { default as SvCard } from './SvCard.svelte'
233
+ export { default as SvAlert } from './SvAlert.svelte'
234
+ export { default as SvEmptyState } from './SvEmptyState.svelte'
235
+ export { default as SvDivider } from './SvDivider.svelte'
236
+ export { default as SvChip } from './SvChip.svelte'
237
+ export { default as SvStat } from './SvStat.svelte'
238
+ export { default as SvTimeline, type TimelineItem } from './SvTimeline.svelte'
239
+ export { default as SvSparkline } from './SvSparkline.svelte'
240
+ export { default as SvScrollArea } from './SvScrollArea.svelte'
241
+ export { default as SvAvatarGroup } from './SvAvatarGroup.svelte'
242
+ export { default as SvCommand, type CommandItem } from './SvCommand.svelte'
243
+ export { fuzzyScore, fuzzyMatch } from './fuzzy'
244
+ export { default as SvRichText, type RichTextTool } from './SvRichText.svelte'
245
+ export { default as SvCarousel } from './SvCarousel.svelte'
246
+ export { default as SvTour, type TourStep } from './SvTour.svelte'
247
+ export { default as SvPagination } from './SvPagination.svelte'
248
+ export { paginationRange, type PaginationItem, type PaginationRangeOptions } from './paginate'
249
+ export { default as SvBreadcrumb, type BreadcrumbItem } from './SvBreadcrumb.svelte'
250
+ export { default as SvStepper, type StepItem } from './SvStepper.svelte'
251
+ export { default as SvAvatar } from './SvAvatar.svelte'
252
+ export { avatarInitials, avatarColorHue } from './avatar'
253
+ export { default as SvContextMenu } from './SvContextMenu.svelte'
254
+ export {
255
+ toast,
256
+ dismissToast,
257
+ clearToasts,
258
+ pauseToast,
259
+ resumeToast,
260
+ toastStore,
261
+ type Toast,
262
+ type ToastVariant,
263
+ type ToastOptions,
264
+ type ToastFn,
265
+ } from './toast-store.svelte'
266
+ export { default as SvMenu, type MenuItem } from './SvMenu.svelte'
267
+ export { default as SvNavPane, type NavItem, type NavSection, type NavModule } from './SvNavPane.svelte'
268
+ export { default as SvProgress } from './SvProgress.svelte'
269
+ export { default as SvCircularProgress } from './SvCircularProgress.svelte'
270
+ export { popIn } from './popover'
145
271
  export { default as SvForm, type FormField, type FormFieldType } from './SvForm.svelte'
272
+ export { rules, runRules, isEmptyValue, type Validator, type RuleOptions, type CompareOp } from './validators'
146
273
  export { anchoredRect, portalToBody, PANEL_THEME_VARS, type AnchoredRect } from './popover'
147
274
  export {
148
275
  toDate, startOfDay, addDays, addMonths, addYears, clampDate, isSameDay,
@@ -258,7 +385,25 @@ export {
258
385
  type ServerState,
259
386
  type ServerSortModel,
260
387
  type ServerFilterModel,
388
+ type ServerAggregation,
389
+ type ServerGroupRow,
390
+ type ServerLeafRow,
391
+ type ServerMoreRow,
392
+ type ServerFooterRow,
393
+ type ServerSkeletonRow,
394
+ type ServerDisplayRow,
261
395
  } from './server-data-source'
396
+ export {
397
+ createServerGroupModel,
398
+ serverGroupRows,
399
+ serverGroupNav,
400
+ type ServerGroupController,
401
+ type ServerGroupControllerOptions,
402
+ type ServerGroupState,
403
+ type ServerGroupGridRow,
404
+ } from './server-group-model'
405
+ export { default as SvGroupCell } from './SvGroupCell.svelte'
406
+ export { default as SvRowGroupPanel } from './SvRowGroupPanel.svelte'
262
407
  export {
263
408
  createNamedViews,
264
409
  memoryViews,
@@ -245,6 +245,26 @@ export function createKeyboard<
245
245
  }
246
246
  event.preventDefault();
247
247
 
248
+ // Server-side group / tree navigation, built into the grid: ArrowRight
249
+ // expands a collapsed group row, ArrowLeft collapses an expanded one,
250
+ // instead of moving the active cell (treegrid-style). Leaves fall through
251
+ // to normal navigation.
252
+ const serverGroup = ctx.props.serverGroup;
253
+ if (serverGroup && (intent === "moveRight" || intent === "moveLeft")) {
254
+ const data = ctx.allRows[current.rowIndex]?.original;
255
+ if (data !== undefined && serverGroup.isGroup(data)) {
256
+ const expanded = serverGroup.expanded?.(data) ?? false;
257
+ if (intent === "moveRight" && !expanded) {
258
+ serverGroup.onToggle(data);
259
+ return;
260
+ }
261
+ if (intent === "moveLeft" && expanded) {
262
+ serverGroup.onToggle(data);
263
+ return;
264
+ }
265
+ }
266
+ }
267
+
248
268
  if (intent === "clearCells") {
249
269
  // Excel `Delete` - clear contents of every cell in the selection
250
270
  // (or the active cell if no range). Formatting is left alone, only
@@ -13,3 +13,77 @@ export function filterOptions(options: ReadonlyArray<ListOption>, query: string)
13
13
  if (!q) return [...options]
14
14
  return options.filter((o) => o.label.toLowerCase().includes(q))
15
15
  }
16
+
17
+ /** An option carrying its position in the flat source array (for prop-getters). */
18
+ export type IndexedOption = ListOption & { index: number }
19
+ /** A section of options that share a `group` heading (null = ungrouped). */
20
+ export type OptionGroup = { group: string | null; options: IndexedOption[] }
21
+
22
+ /**
23
+ * Bucket options by their `group` heading, preserving each option's flat index
24
+ * so grouped rendering still drives `optionProps(index)` etc. Groups appear in
25
+ * first-seen order. When no option sets `group`, the result is a single
26
+ * `group: null` section (render it flat).
27
+ */
28
+ export function groupOptions(options: ReadonlyArray<ListOption>): OptionGroup[] {
29
+ const groups: OptionGroup[] = []
30
+ const byName = new Map<string | null, OptionGroup>()
31
+ options.forEach((o, index) => {
32
+ const key = o.group ?? null
33
+ let g = byName.get(key)
34
+ if (!g) { g = { group: key, options: [] }; byName.set(key, g); groups.push(g) }
35
+ g.options.push({ ...o, index })
36
+ })
37
+ return groups
38
+ }
39
+
40
+ /** Whether any option declares a `group` (so headings are worth rendering). */
41
+ export function hasGroups(options: ReadonlyArray<ListOption>): boolean {
42
+ return options.some((o) => o.group != null)
43
+ }
44
+
45
+ /**
46
+ * Next enabled option index whose label starts with `buffer` (type-ahead),
47
+ * searching forward from just after `from` and wrapping. Returns -1 for no
48
+ * match / empty buffer. Case-insensitive.
49
+ */
50
+ export function nextTypeaheadIndex(
51
+ options: ReadonlyArray<ListOption>,
52
+ buffer: string,
53
+ from: number,
54
+ ): number {
55
+ const b = buffer.trim().toLowerCase()
56
+ if (!b || !options.length) return -1
57
+ const n = options.length
58
+ for (let k = 1; k <= n; k++) {
59
+ const i = (from + k) % n
60
+ const o = options[i]
61
+ if (o && !o.disabled && o.label.toLowerCase().startsWith(b)) return i
62
+ }
63
+ return -1
64
+ }
65
+
66
+ /**
67
+ * A tiny type-ahead buffer: accumulates printable characters within `timeout`
68
+ * ms of each other, then resets. Frameworkfree; the caller supplies scheduling.
69
+ */
70
+ export function createTypeaheadBuffer(timeout = 600) {
71
+ let buffer = ''
72
+ let timer: ReturnType<typeof setTimeout> | undefined
73
+ return {
74
+ get value() { return buffer },
75
+ /** Append a character and (re)arm the reset timer. Returns the new buffer. */
76
+ push(char: string): string {
77
+ buffer += char
78
+ if (timer) clearTimeout(timer)
79
+ timer = setTimeout(() => { buffer = '' }, timeout)
80
+ return buffer
81
+ },
82
+ clear() { buffer = ''; if (timer) clearTimeout(timer) },
83
+ }
84
+ }
85
+
86
+ /** Whether a keydown is a bare printable character (a type-ahead candidate). */
87
+ export function isTypeaheadKey(e: KeyboardEvent): boolean {
88
+ return e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey && e.key !== ' '
89
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Tests for the list "power" features: option grouping (preserving flat index),
3
+ * type-ahead matching, and their wiring into SvListBox (group headings render;
4
+ * typing a prefix moves the active option).
5
+ */
6
+ import { describe, expect, it } from 'vitest'
7
+ import { mount, unmount, flushSync } from 'svelte'
8
+ import SvListBox from './SvListBox.svelte'
9
+ import { groupOptions, hasGroups, nextTypeaheadIndex } from './list-option'
10
+
11
+ const opts = [
12
+ { value: 'us', label: 'United States', group: 'Americas' },
13
+ { value: 'ca', label: 'Canada', group: 'Americas' },
14
+ { value: 'fr', label: 'France', group: 'Europe' },
15
+ { value: 'de', label: 'Germany', group: 'Europe' },
16
+ ]
17
+
18
+ describe('groupOptions', () => {
19
+ it('buckets by group in first-seen order and preserves the flat index', () => {
20
+ const groups = groupOptions(opts)
21
+ expect(groups.map((g) => g.group)).toEqual(['Americas', 'Europe'])
22
+ expect(groups[0].options.map((o) => o.index)).toEqual([0, 1])
23
+ expect(groups[1].options.map((o) => o.index)).toEqual([2, 3])
24
+ expect(hasGroups(opts)).toBe(true)
25
+ })
26
+ it('returns a single null group when nothing is grouped', () => {
27
+ const flat = [{ value: 'a', label: 'A' }, { value: 'b', label: 'B' }]
28
+ expect(groupOptions(flat)).toEqual([{ group: null, options: [{ value: 'a', label: 'A', index: 0 }, { value: 'b', label: 'B', index: 1 }] }])
29
+ expect(hasGroups(flat)).toBe(false)
30
+ })
31
+ })
32
+
33
+ describe('nextTypeaheadIndex', () => {
34
+ it('finds the next enabled label-prefix match, wrapping from `from`', () => {
35
+ expect(nextTypeaheadIndex(opts, 'f', -1)).toBe(2) // France
36
+ expect(nextTypeaheadIndex(opts, 'g', 2)).toBe(3) // Germany
37
+ expect(nextTypeaheadIndex(opts, 'can', 0)).toBe(1) // Canada
38
+ expect(nextTypeaheadIndex(opts, 'z', 0)).toBe(-1) // no match
39
+ expect(nextTypeaheadIndex(opts, '', 0)).toBe(-1)
40
+ })
41
+ })
42
+
43
+ function mountLb(props: Record<string, unknown>) {
44
+ const target = document.createElement('div')
45
+ document.body.appendChild(target)
46
+ const app = mount(SvListBox, { target, props: props as any })
47
+ flushSync()
48
+ return { target, destroy: () => { unmount(app); target.remove() } }
49
+ }
50
+
51
+ describe('SvListBox grouping + type-ahead', () => {
52
+ it('renders group headings above their options', () => {
53
+ const { target, destroy } = mountLb({ options: opts, value: 'us' })
54
+ try {
55
+ const headings = [...target.querySelectorAll('.sv-listbox__group')].map((n) => n.textContent)
56
+ expect(headings).toEqual(['Americas', 'Europe'])
57
+ expect(target.querySelectorAll('.sv-listbox__opt')).toHaveLength(4)
58
+ } finally { destroy() }
59
+ })
60
+
61
+ it('type-ahead moves the active option to a label prefix', () => {
62
+ const { target, destroy } = mountLb({ options: opts, value: 'us' })
63
+ try {
64
+ const list = target.querySelector<HTMLElement>('.sv-listbox')!
65
+ list.dispatchEvent(new KeyboardEvent('keydown', { key: 'g', bubbles: true }))
66
+ flushSync()
67
+ const active = target.querySelector('.sv-listbox__opt.is-active .sv-listbox__label')!
68
+ expect(active.textContent).toBe('Germany')
69
+ } finally { destroy() }
70
+ })
71
+ })
@@ -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
+ })