@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
@@ -0,0 +1,263 @@
1
+ export function serverGroupRows(state) {
2
+ return (state?.displayRows ?? []).map((r) => ({ ...('data' in r ? r.data : {}), __group: r }));
3
+ }
4
+ /**
5
+ * Build the `serverGroup` prop for `<SvGrid>` from a controller, so the grid's
6
+ * built-in tree keyboard (ArrowRight / ArrowLeft expand / collapse) and treegrid
7
+ * a11y work with no app-level key handling. Reads the `__group` marker that
8
+ * `serverGroupRows` attaches. Usage: `<SvGrid ... serverGroup={serverGroupNav(ctl)} />`.
9
+ */
10
+ export function serverGroupNav(ctl) {
11
+ return {
12
+ isGroup: (row) => row.__group?.kind === 'group',
13
+ level: (row) => row.__group?.level ?? 0,
14
+ expanded: (row) => row.__group?.kind === 'group' ? row.__group.expanded : false,
15
+ onToggle: (row) => {
16
+ if (row.__group?.kind === 'group')
17
+ ctl.toggleGroup(row.__group);
18
+ else if (row.__group?.kind === 'more')
19
+ ctl.loadMoreChildren(row.__group.path);
20
+ },
21
+ };
22
+ }
23
+ const keyOf = (path) => JSON.stringify(path);
24
+ export function createServerGroupModel(source, options) {
25
+ let groupBy = [...(options.groupBy ?? [])];
26
+ const aggregations = options.aggregations ?? [];
27
+ const pageSize = options.pageSize ?? 200;
28
+ const treeData = !!options.treeData;
29
+ if (treeData && (!options.getRowId || !options.hasChildren)) {
30
+ throw new Error('createServerGroupModel: treeData requires getRowId and hasChildren');
31
+ }
32
+ const getRowId = options.getRowId;
33
+ const hasChildren = options.hasChildren;
34
+ const groupFooters = !!options.groupFooters;
35
+ const skeletonRows = Math.max(0, options.skeletonRows ?? 3);
36
+ let sortModel = [];
37
+ let filterModel = {};
38
+ const nodes = new Map();
39
+ const expanded = new Set(); // path keys
40
+ // Generation guard: bumped on any tree reset so every fetch in flight for the
41
+ // previous shape is discarded when it lands (the classic stale-response bug).
42
+ let generation = 0;
43
+ let disposed = false;
44
+ const rootKey = keyOf([]);
45
+ function ensureNode(key) {
46
+ let node = nodes.get(key);
47
+ if (!node) {
48
+ node = { loaded: false, loading: false, error: null, count: 0, children: [] };
49
+ nodes.set(key, node);
50
+ }
51
+ return node;
52
+ }
53
+ // Walk the loaded tree from the root, splicing expanded groups' children in.
54
+ function flatten() {
55
+ const out = [];
56
+ const walk = (path) => {
57
+ const node = nodes.get(keyOf(path));
58
+ if (!node?.loaded)
59
+ return;
60
+ for (const child of node.children) {
61
+ if (child.kind === 'leaf') {
62
+ out.push({ kind: 'leaf', id: child.id, level: child.level, data: child.data });
63
+ continue;
64
+ }
65
+ const cid = keyOf(child.path);
66
+ const cnode = nodes.get(cid);
67
+ const isExp = expanded.has(cid);
68
+ out.push({
69
+ kind: 'group',
70
+ id: cid,
71
+ path: child.path,
72
+ field: child.field,
73
+ key: child.key,
74
+ level: child.level,
75
+ expanded: isExp,
76
+ loading: !!cnode?.loading,
77
+ aggregates: child.aggregates,
78
+ data: child.data,
79
+ });
80
+ if (isExp) {
81
+ if (cnode?.loaded) {
82
+ walk(child.path);
83
+ // Subtotal row after the group's children.
84
+ if (groupFooters) {
85
+ out.push({
86
+ kind: 'footer',
87
+ id: cid + 'footer',
88
+ level: child.level + 1,
89
+ path: child.path,
90
+ key: child.key,
91
+ aggregates: child.aggregates,
92
+ data: child.data,
93
+ });
94
+ }
95
+ }
96
+ else if (cnode?.loading) {
97
+ // First block in flight: placeholder skeleton rows.
98
+ for (let i = 0; i < skeletonRows; i += 1) {
99
+ out.push({ kind: 'skeleton', id: cid + 'sk' + i, level: child.level + 1 });
100
+ }
101
+ }
102
+ }
103
+ }
104
+ // Intra-group paging: emit a "load more" affordance when only part of this
105
+ // node's children have been fetched.
106
+ if (node.children.length < node.count) {
107
+ out.push({
108
+ kind: 'more',
109
+ id: keyOf([...path, 'more']),
110
+ level: path.length,
111
+ path,
112
+ remaining: node.count - node.children.length,
113
+ loading: node.loading,
114
+ });
115
+ }
116
+ };
117
+ walk([]);
118
+ return out;
119
+ }
120
+ function emit() {
121
+ if (disposed)
122
+ return;
123
+ const root = nodes.get(rootKey);
124
+ options.onChange({
125
+ displayRows: flatten(),
126
+ groupBy: [...groupBy],
127
+ loading: !!root?.loading && !root?.loaded,
128
+ error: root?.error ?? null,
129
+ sortModel,
130
+ filterModel,
131
+ expandedGroups: [...expanded],
132
+ });
133
+ }
134
+ // append=false loads the first block of a node's children (on expand);
135
+ // append=true fetches the NEXT block and concatenates (intra-group paging).
136
+ async function fetchNode(path, append = false) {
137
+ if (disposed)
138
+ return;
139
+ const gen = generation;
140
+ const key = keyOf(path);
141
+ const node = ensureNode(key);
142
+ if (node.loading)
143
+ return; // a fetch for this node is already in flight
144
+ node.loading = true;
145
+ node.error = null;
146
+ emit();
147
+ const level = path.length;
148
+ const groupField = groupBy[level];
149
+ const startRow = append ? node.children.length : 0;
150
+ try {
151
+ const res = await source.getRows({
152
+ startRow,
153
+ endRow: startRow + pageSize,
154
+ pageIndex: Math.floor(startRow / pageSize),
155
+ pageSize,
156
+ sortModel,
157
+ filterModel,
158
+ groupBy,
159
+ groupKeys: path,
160
+ aggregations,
161
+ });
162
+ if (disposed || gen !== generation)
163
+ return; // stale: the tree was reset
164
+ node.count = res.rowCount;
165
+ const block = res.rows.map((data, i) => {
166
+ // A row is an expandable "group" when: (tree mode) the source says it has
167
+ // children, or (grouping mode) we are above the innermost groupBy level.
168
+ const isGroup = treeData ? hasChildren(data) : level < groupBy.length;
169
+ if (isGroup) {
170
+ const gk = treeData
171
+ ? getRowId(data)
172
+ : ((v) => (v == null ? '' : String(v)))(data[groupField]);
173
+ const aggregates = {};
174
+ for (const a of aggregations)
175
+ aggregates[a.col] = data[a.col];
176
+ return { kind: 'group', path: [...path, gk], field: treeData ? '' : groupField, key: gk, level, aggregates, data };
177
+ }
178
+ const id = treeData && getRowId ? `${key}:${getRowId(data)}` : `${key}:${startRow + i}`;
179
+ return { kind: 'leaf', id, level, data };
180
+ });
181
+ node.children = append ? [...node.children, ...block] : block;
182
+ node.loaded = true;
183
+ node.loading = false;
184
+ emit();
185
+ }
186
+ catch (err) {
187
+ if (disposed || gen !== generation)
188
+ return;
189
+ node.error = err;
190
+ if (!append)
191
+ node.loaded = false;
192
+ node.loading = false;
193
+ emit();
194
+ }
195
+ }
196
+ // Clear the cache and reload the root plus any still-expanded paths (in
197
+ // parallel; flatten only descends into loaded nodes, so children appear as
198
+ // each level lands).
199
+ function resetTree() {
200
+ generation += 1;
201
+ nodes.clear();
202
+ void fetchNode([]);
203
+ for (const k of expanded)
204
+ void fetchNode(JSON.parse(k));
205
+ }
206
+ function expandGroup(path) {
207
+ const key = keyOf(path);
208
+ if (expanded.has(key))
209
+ return;
210
+ expanded.add(key);
211
+ const node = nodes.get(key);
212
+ if (!node?.loaded && !node?.loading)
213
+ void fetchNode(path);
214
+ else
215
+ emit();
216
+ }
217
+ function collapseGroup(path) {
218
+ if (expanded.delete(keyOf(path)))
219
+ emit();
220
+ }
221
+ return {
222
+ refresh: resetTree,
223
+ setSort(next) {
224
+ sortModel = next;
225
+ resetTree();
226
+ },
227
+ setFilter(next) {
228
+ filterModel = next;
229
+ resetTree();
230
+ },
231
+ setGroupBy(next) {
232
+ groupBy = [...next];
233
+ expanded.clear();
234
+ resetTree();
235
+ },
236
+ toggleGroup(row) {
237
+ if (expanded.has(row.id))
238
+ collapseGroup(row.path);
239
+ else
240
+ expandGroup(row.path);
241
+ },
242
+ expandGroup,
243
+ collapseGroup,
244
+ loadMoreChildren: (path) => void fetchNode(path, true),
245
+ isExpanded: (path) => expanded.has(keyOf(path)),
246
+ getState() {
247
+ const root = nodes.get(rootKey);
248
+ return {
249
+ displayRows: flatten(),
250
+ groupBy: [...groupBy],
251
+ loading: !!root?.loading && !root?.loaded,
252
+ error: root?.error ?? null,
253
+ sortModel,
254
+ filterModel,
255
+ expandedGroups: [...expanded],
256
+ };
257
+ },
258
+ dispose() {
259
+ disposed = true;
260
+ generation += 1;
261
+ },
262
+ };
263
+ }
@@ -0,0 +1,41 @@
1
+ export type ToastVariant = 'info' | 'success' | 'warning' | 'error';
2
+ export type ToastOptions = {
3
+ variant?: ToastVariant;
4
+ /** Auto-dismiss after N ms. `0` = sticky (dismiss manually). Default 4000. */
5
+ duration?: number;
6
+ /** Show the dismiss (x) button. Default true. */
7
+ dismissible?: boolean;
8
+ /** Optional bold title above the message. */
9
+ title?: string;
10
+ };
11
+ export type Toast = {
12
+ id: number;
13
+ message: string;
14
+ variant: ToastVariant;
15
+ duration: number;
16
+ dismissible: boolean;
17
+ title?: string;
18
+ };
19
+ type VariantOptions = Omit<ToastOptions, 'variant'>;
20
+ export type ToastFn = {
21
+ (message: string, options?: ToastOptions): number;
22
+ info: (message: string, options?: VariantOptions) => number;
23
+ success: (message: string, options?: VariantOptions) => number;
24
+ warning: (message: string, options?: VariantOptions) => number;
25
+ error: (message: string, options?: VariantOptions) => number;
26
+ };
27
+ /** Live reactive list of active toasts (read inside a component/effect). */
28
+ export declare const toastStore: {
29
+ readonly toasts: Toast[];
30
+ };
31
+ /** Show a toast. Returns its id. Has `.info/.success/.warning/.error` helpers. */
32
+ export declare const toast: ToastFn;
33
+ /** Dismiss a toast by id. */
34
+ export declare const dismissToast: (id: number) => void;
35
+ /** Remove every toast (and cancel their timers). */
36
+ export declare const clearToasts: () => void;
37
+ /** Pause a toast's auto-dismiss countdown (e.g. while hovered). */
38
+ export declare const pauseToast: (id: number) => void;
39
+ /** Resume a paused toast's countdown with the time it had left. */
40
+ export declare const resumeToast: (id: number) => void;
41
+ export {};
@@ -0,0 +1,113 @@
1
+ /**
2
+ * toast-store - the headless model behind <SvToaster>: a singleton reactive
3
+ * queue of transient notifications with auto-dismiss timers, plus the `toast()`
4
+ * API you call from anywhere. Every toast is also pushed to the shared ARIA
5
+ * live region via `announce()`, so screen-reader users hear it even though the
6
+ * visual toast is off to the side.
7
+ *
8
+ * ```ts
9
+ * import { toast } from '@svgrid/grid'
10
+ * toast.success('Saved')
11
+ * toast.error('Could not save', { duration: 0 }) // sticky
12
+ * ```
13
+ *
14
+ * Mount a single <SvToaster /> once near the app root to render the queue.
15
+ */
16
+ import { announce } from './a11y/live-region';
17
+ function createToastStore() {
18
+ const toasts = $state([]);
19
+ const timers = new Map();
20
+ // Remaining ms + when the current run started, so hovering can pause/resume
21
+ // the auto-dismiss countdown instead of losing it.
22
+ const meta = new Map();
23
+ let seq = 0;
24
+ function schedule(id, ms) {
25
+ meta.set(id, { remaining: ms, startedAt: Date.now() });
26
+ timers.set(id, setTimeout(() => dismiss(id), ms));
27
+ }
28
+ /** Pause a toast's auto-dismiss (e.g. on hover), banking the time left. */
29
+ function pause(id) {
30
+ const timer = timers.get(id);
31
+ const m = meta.get(id);
32
+ if (!timer || !m)
33
+ return;
34
+ clearTimeout(timer);
35
+ timers.delete(id);
36
+ m.remaining = Math.max(0, m.remaining - (Date.now() - m.startedAt));
37
+ }
38
+ /** Resume a paused toast with the banked time remaining. */
39
+ function resume(id) {
40
+ const m = meta.get(id);
41
+ if (!m || timers.has(id) || m.remaining <= 0)
42
+ return;
43
+ m.startedAt = Date.now();
44
+ timers.set(id, setTimeout(() => dismiss(id), m.remaining));
45
+ }
46
+ function dismiss(id) {
47
+ const i = toasts.findIndex((t) => t.id === id);
48
+ if (i >= 0)
49
+ toasts.splice(i, 1);
50
+ const timer = timers.get(id);
51
+ if (timer) {
52
+ clearTimeout(timer);
53
+ timers.delete(id);
54
+ }
55
+ meta.delete(id);
56
+ }
57
+ function clear() {
58
+ for (const timer of timers.values())
59
+ clearTimeout(timer);
60
+ timers.clear();
61
+ meta.clear();
62
+ toasts.splice(0, toasts.length);
63
+ }
64
+ function push(message, options = {}) {
65
+ const id = ++seq;
66
+ const variant = options.variant ?? 'info';
67
+ const duration = options.duration ?? 4000;
68
+ const entry = {
69
+ id,
70
+ message,
71
+ variant,
72
+ duration,
73
+ dismissible: options.dismissible ?? true,
74
+ title: options.title,
75
+ };
76
+ toasts.push(entry);
77
+ // Errors/warnings interrupt (assertive); info/success are polite.
78
+ announce(entry.title ? `${entry.title}: ${message}` : message, {
79
+ assertive: variant === 'error' || variant === 'warning',
80
+ });
81
+ if (duration > 0)
82
+ schedule(id, duration);
83
+ return id;
84
+ }
85
+ const toast = push;
86
+ toast.info = (m, o = {}) => push(m, { ...o, variant: 'info' });
87
+ toast.success = (m, o = {}) => push(m, { ...o, variant: 'success' });
88
+ toast.warning = (m, o = {}) => push(m, { ...o, variant: 'warning' });
89
+ toast.error = (m, o = {}) => push(m, { ...o, variant: 'error' });
90
+ return {
91
+ get toasts() {
92
+ return toasts;
93
+ },
94
+ toast,
95
+ dismiss,
96
+ clear,
97
+ pause,
98
+ resume,
99
+ };
100
+ }
101
+ const store = createToastStore();
102
+ /** Live reactive list of active toasts (read inside a component/effect). */
103
+ export const toastStore = { get toasts() { return store.toasts; } };
104
+ /** Show a toast. Returns its id. Has `.info/.success/.warning/.error` helpers. */
105
+ export const toast = store.toast;
106
+ /** Dismiss a toast by id. */
107
+ export const dismissToast = (id) => store.dismiss(id);
108
+ /** Remove every toast (and cancel their timers). */
109
+ export const clearToasts = () => store.clear();
110
+ /** Pause a toast's auto-dismiss countdown (e.g. while hovered). */
111
+ export const pauseToast = (id) => store.pause(id);
112
+ /** Resume a paused toast's countdown with the time it had left. */
113
+ export const resumeToast = (id) => store.resume(id);
@@ -0,0 +1,19 @@
1
+ /**
2
+ * tree-select - pure tree helpers behind <SvTreeSelect>: flatten the visible
3
+ * rows for the current expansion state (so keyboard nav is a flat index), and
4
+ * resolve the path from root to a value (for the trigger label). Framework-free
5
+ * + pure so they are unit-tested directly.
6
+ */
7
+ import type { TreeSelectNode } from './ui-app.types';
8
+ export type FlatRow = {
9
+ node: TreeSelectNode;
10
+ depth: number;
11
+ hasChildren: boolean;
12
+ expanded: boolean;
13
+ };
14
+ /** The rows currently visible given the `expanded` set, in display order. */
15
+ export declare function flattenVisible(nodes: ReadonlyArray<TreeSelectNode>, expanded: ReadonlySet<string | number>, depth?: number): FlatRow[];
16
+ /** Path of nodes from a root down to `value`, or null if not found. */
17
+ export declare function findNodePath(nodes: ReadonlyArray<TreeSelectNode>, value: string | number): TreeSelectNode[] | null;
18
+ /** Every value that has children (useful for "expand all"). */
19
+ export declare function branchValues(nodes: ReadonlyArray<TreeSelectNode>): Array<string | number>;
@@ -0,0 +1,40 @@
1
+ /** The rows currently visible given the `expanded` set, in display order. */
2
+ export function flattenVisible(nodes, expanded, depth = 0) {
3
+ const rows = [];
4
+ for (const node of nodes) {
5
+ const hasChildren = !!node.children && node.children.length > 0;
6
+ const isExpanded = expanded.has(node.value);
7
+ rows.push({ node, depth, hasChildren, expanded: isExpanded });
8
+ if (hasChildren && isExpanded) {
9
+ rows.push(...flattenVisible(node.children, expanded, depth + 1));
10
+ }
11
+ }
12
+ return rows;
13
+ }
14
+ /** Path of nodes from a root down to `value`, or null if not found. */
15
+ export function findNodePath(nodes, value) {
16
+ for (const node of nodes) {
17
+ if (node.value === value)
18
+ return [node];
19
+ if (node.children) {
20
+ const sub = findNodePath(node.children, value);
21
+ if (sub)
22
+ return [node, ...sub];
23
+ }
24
+ }
25
+ return null;
26
+ }
27
+ /** Every value that has children (useful for "expand all"). */
28
+ export function branchValues(nodes) {
29
+ const out = [];
30
+ const walk = (list) => {
31
+ for (const n of list) {
32
+ if (n.children && n.children.length) {
33
+ out.push(n.value);
34
+ walk(n.children);
35
+ }
36
+ }
37
+ };
38
+ walk(nodes);
39
+ return out;
40
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Shared item shapes for the app-UI components that have no headless core
3
+ * (SvBreadcrumb, SvStepper). Kept in a plain .ts so they can be exported from a
4
+ * component's module script by re-export - a bare `export type` DECLARATION in a
5
+ * `<script module>` trips the Svelte/Vite parser, but a re-export is fine.
6
+ */
7
+ /** One crumb in a breadcrumb trail. `href` renders a link; `onClick` a button. */
8
+ export type BreadcrumbItem = {
9
+ label: string;
10
+ href?: string;
11
+ onClick?: () => void;
12
+ /** Optional leading icon (any string: emoji, char, or a font-icon class glyph). */
13
+ icon?: string;
14
+ };
15
+ /** One step in a stepper. */
16
+ export type StepItem = {
17
+ label: string;
18
+ description?: string;
19
+ /** Marks the step skippable (shown as "Optional"). */
20
+ optional?: boolean;
21
+ };
22
+ /** One option in a multi-select. */
23
+ export type MultiSelectOption = {
24
+ value: string | number;
25
+ label: string;
26
+ disabled?: boolean;
27
+ };
28
+ /** A node in a tree-select. */
29
+ export type TreeSelectNode = {
30
+ value: string | number;
31
+ label: string;
32
+ children?: TreeSelectNode[];
33
+ disabled?: boolean;
34
+ };
35
+ /** A column shown in a grid-select dropdown. */
36
+ export type GridSelectColumn = {
37
+ /** Key into each option row. */
38
+ field: string;
39
+ header: string;
40
+ /** Any CSS grid track size (e.g. '1fr', '80px'). Default '1fr'. */
41
+ width?: string;
42
+ };
43
+ /** One action in a command palette (SvCommand). */
44
+ export type CommandItem = {
45
+ id: string;
46
+ label: string;
47
+ /** Right-aligned hint / description. */
48
+ hint?: string;
49
+ /** Section header this command groups under. */
50
+ group?: string;
51
+ /** Leading glyph (emoji / char). */
52
+ icon?: string;
53
+ /** Right-aligned keyboard shortcut hint, e.g. "⌘S". */
54
+ shortcut?: string;
55
+ /** Extra searchable terms beyond the label. */
56
+ keywords?: string;
57
+ disabled?: boolean;
58
+ /** Run when chosen. */
59
+ onRun?: () => void;
60
+ };
61
+ /** One step in a guided tour. */
62
+ export type TourStep = {
63
+ /** Element to spotlight: a CSS selector or the element itself. Omit for a
64
+ * centered, target-less step (intro / outro). */
65
+ target?: string | HTMLElement;
66
+ title?: string;
67
+ content: string;
68
+ };
69
+ /** One entry in a timeline. */
70
+ export type TimelineItem = {
71
+ id?: string | number;
72
+ title: string;
73
+ description?: string;
74
+ /** Right-aligned timestamp/label. */
75
+ time?: string;
76
+ /** Marker colour (any CSS colour). Defaults to the accent. */
77
+ color?: string;
78
+ /** Optional leading glyph inside the marker (emoji / char). */
79
+ icon?: string;
80
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Shared item shapes for the app-UI components that have no headless core
3
+ * (SvBreadcrumb, SvStepper). Kept in a plain .ts so they can be exported from a
4
+ * component's module script by re-export - a bare `export type` DECLARATION in a
5
+ * `<script module>` trips the Svelte/Vite parser, but a re-export is fine.
6
+ */
7
+ export {};
@@ -0,0 +1,43 @@
1
+ /**
2
+ * validators - a declarative, framework-free validation-rule library for SvForm
3
+ * (and any editor). Each rule builder returns a `Validator`: a pure function
4
+ * `(value, allValues?) => errorMessage | null`. Compose them per field; the form
5
+ * runs them in order and shows the first failure. Parity: Smart `smart.validator`
6
+ * (required / email / pattern / min / max / range / stringLength / compare ...).
7
+ *
8
+ * ```ts
9
+ * import { rules } from '@svgrid/grid'
10
+ * const field = { name: 'email', label: 'Email', rules: [rules.required(), rules.email()] }
11
+ * ```
12
+ */
13
+ export type Validator = (value: any, values?: Record<string, any>) => string | null | undefined;
14
+ export type RuleOptions = {
15
+ message?: string;
16
+ };
17
+ export type CompareOp = '==' | '===' | '!=' | '<' | '<=' | '>' | '>=';
18
+ /** Empty for validation purposes: null/undefined, '', or an empty array. */
19
+ export declare function isEmptyValue(v: any): boolean;
20
+ /** The declarative rule builders. Every builder skips empty values (except
21
+ * `required`) so optional fields validate format only when filled. */
22
+ export declare const rules: {
23
+ required: (o?: RuleOptions) => Validator;
24
+ email: (o?: RuleOptions) => Validator;
25
+ url: (o?: RuleOptions) => Validator;
26
+ zipCode: (o?: RuleOptions) => Validator;
27
+ pattern: (re: RegExp, o?: RuleOptions) => Validator;
28
+ numeric: (o?: RuleOptions) => Validator;
29
+ integer: (o?: RuleOptions) => Validator;
30
+ min: (n: number, o?: RuleOptions) => Validator;
31
+ max: (n: number, o?: RuleOptions) => Validator;
32
+ range: (lo: number, hi: number, o?: RuleOptions) => Validator;
33
+ minLength: (n: number, o?: RuleOptions) => Validator;
34
+ maxLength: (n: number, o?: RuleOptions) => Validator;
35
+ stringLength: (lo: number, hi: number, o?: RuleOptions) => Validator;
36
+ oneOf: (allowed: ReadonlyArray<string | number>, o?: RuleOptions) => Validator;
37
+ /** Compare against another field's value (cross-field): e.g. confirm password. */
38
+ compare: (otherField: string, op?: CompareOp, o?: RuleOptions) => Validator;
39
+ /** Wrap an arbitrary predicate/function as a rule. */
40
+ custom: (fn: Validator) => Validator;
41
+ };
42
+ /** Run a list of rules against a value; return the first error message, or null. */
43
+ export declare function runRules(value: any, ruleList: ReadonlyArray<Validator> | undefined, values?: Record<string, any>): string | null;
@@ -0,0 +1,53 @@
1
+ const EMAIL_RE = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
2
+ const URL_RE = /^(https?:\/\/)?([\w-]+\.)+[\w-]{2,}(\/\S*)?$/i;
3
+ const ZIP_RE = /^\d{5}(-\d{4})?$/;
4
+ /** Empty for validation purposes: null/undefined, '', or an empty array. */
5
+ export function isEmptyValue(v) {
6
+ return v == null || v === '' || (Array.isArray(v) && v.length === 0);
7
+ }
8
+ function compareOk(a, b, op) {
9
+ switch (op) {
10
+ case '==': return a == b; // eslint-disable-line eqeqeq
11
+ case '===': return a === b;
12
+ case '!=': return a != b; // eslint-disable-line eqeqeq
13
+ case '<': return Number(a) < Number(b);
14
+ case '<=': return Number(a) <= Number(b);
15
+ case '>': return Number(a) > Number(b);
16
+ case '>=': return Number(a) >= Number(b);
17
+ }
18
+ }
19
+ /** The declarative rule builders. Every builder skips empty values (except
20
+ * `required`) so optional fields validate format only when filled. */
21
+ export const rules = {
22
+ required: (o) => (v) => isEmptyValue(v) ? o?.message ?? 'This field is required' : null,
23
+ email: (o) => (v) => isEmptyValue(v) || EMAIL_RE.test(String(v)) ? null : o?.message ?? 'Enter a valid email address',
24
+ url: (o) => (v) => isEmptyValue(v) || URL_RE.test(String(v)) ? null : o?.message ?? 'Enter a valid URL',
25
+ zipCode: (o) => (v) => isEmptyValue(v) || ZIP_RE.test(String(v)) ? null : o?.message ?? 'Enter a valid ZIP code',
26
+ pattern: (re, o) => (v) => isEmptyValue(v) || re.test(String(v)) ? null : o?.message ?? 'Invalid format',
27
+ numeric: (o) => (v) => isEmptyValue(v) || !isNaN(Number(v)) ? null : o?.message ?? 'Must be a number',
28
+ integer: (o) => (v) => isEmptyValue(v) || Number.isInteger(Number(v)) ? null : o?.message ?? 'Must be a whole number',
29
+ min: (n, o) => (v) => isEmptyValue(v) || Number(v) >= n ? null : o?.message ?? `Must be at least ${n}`,
30
+ max: (n, o) => (v) => isEmptyValue(v) || Number(v) <= n ? null : o?.message ?? `Must be at most ${n}`,
31
+ range: (lo, hi, o) => (v) => isEmptyValue(v) || (Number(v) >= lo && Number(v) <= hi) ? null : o?.message ?? `Must be between ${lo} and ${hi}`,
32
+ minLength: (n, o) => (v) => isEmptyValue(v) || String(v).length >= n ? null : o?.message ?? `Must be at least ${n} characters`,
33
+ maxLength: (n, o) => (v) => isEmptyValue(v) || String(v).length <= n ? null : o?.message ?? `Must be at most ${n} characters`,
34
+ stringLength: (lo, hi, o) => (v) => isEmptyValue(v) || (String(v).length >= lo && String(v).length <= hi)
35
+ ? null
36
+ : o?.message ?? `Must be ${lo}-${hi} characters`,
37
+ oneOf: (allowed, o) => (v) => isEmptyValue(v) || allowed.includes(v) ? null : o?.message ?? 'Invalid choice',
38
+ /** Compare against another field's value (cross-field): e.g. confirm password. */
39
+ compare: (otherField, op = '===', o) => (v, values) => compareOk(v, values?.[otherField], op) ? null : o?.message ?? 'Values do not match',
40
+ /** Wrap an arbitrary predicate/function as a rule. */
41
+ custom: (fn) => fn,
42
+ };
43
+ /** Run a list of rules against a value; return the first error message, or null. */
44
+ export function runRules(value, ruleList, values) {
45
+ if (!ruleList)
46
+ return null;
47
+ for (const rule of ruleList) {
48
+ const err = rule(value, values);
49
+ if (err)
50
+ return err;
51
+ }
52
+ return null;
53
+ }