@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
@@ -4,17 +4,15 @@
4
4
  * label. Parity: Smart `smart-check-box`. Controlled via `checked` + `onChange`.
5
5
  */
6
6
  import type { Snippet } from 'svelte'
7
+ import SvField from './SvField.svelte'
8
+ import { nextEditorId, type SvEditorProps } from './editor-contract'
7
9
  import { createCheckbox } from './createCheckbox.svelte'
8
10
 
9
- type Props = {
11
+ type Props = SvEditorProps & {
10
12
  checked?: boolean
11
13
  indeterminate?: boolean
12
14
  onChange?: (checked: boolean) => void
13
- disabled?: boolean
14
- size?: 'sm' | 'md' | 'lg'
15
- name?: string
16
15
  value?: string
17
- ariaLabel?: string
18
16
  children?: Snippet
19
17
  }
20
18
 
@@ -27,9 +25,18 @@
27
25
  name,
28
26
  value,
29
27
  ariaLabel,
28
+ invalid = false,
29
+ required = false,
30
+ error,
31
+ hint,
32
+ dir,
33
+ id,
30
34
  children,
31
35
  }: Props = $props()
32
36
 
37
+ const autoId = nextEditorId('sv-check')
38
+ const uid = $derived(id ?? autoId)
39
+
33
40
  // The styled checkbox is just a renderer over the headless core. The label
34
41
  // fallback depends on `children` (a render concern), so it is computed here.
35
42
  const cb = createCheckbox({
@@ -38,25 +45,32 @@
38
45
  onChange: (v) => onChange?.(v),
39
46
  disabled: () => disabled,
40
47
  ariaLabel: () => ariaLabel ?? (children ? undefined : 'checkbox'),
48
+ id: () => uid,
49
+ invalid: () => invalid,
50
+ required: () => required,
51
+ error: () => error,
52
+ hint: () => hint,
41
53
  })
42
54
  </script>
43
55
 
44
- <label class="sv-check sv-check--{size}" class:is-disabled={disabled}>
45
- <button
46
- class="sv-check__box"
47
- class:is-checked={checked && !indeterminate}
48
- class:is-indeterminate={indeterminate}
49
- {...cb.boxProps()}
50
- >
51
- {#if indeterminate}
52
- <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M4 8h8" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" /></svg>
53
- {:else if checked}
54
- <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3.5 8.5l3 3 6-6.5" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" /></svg>
55
- {/if}
56
- </button>
57
- {#if children}<span class="sv-check__label">{@render children()}</span>{/if}
58
- {#if name}<input type="hidden" {name} value={checked ? (value ?? 'true') : ''} />{/if}
59
- </label>
56
+ <SvField id={uid} {hint} {error} {required} {dir}>
57
+ <label class="sv-check sv-check--{size}" class:is-disabled={disabled} class:is-invalid={invalid}>
58
+ <button
59
+ class="sv-check__box"
60
+ class:is-checked={checked && !indeterminate}
61
+ class:is-indeterminate={indeterminate}
62
+ {...cb.boxProps()}
63
+ >
64
+ {#if indeterminate}
65
+ <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M4 8h8" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" /></svg>
66
+ {:else if checked}
67
+ <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3.5 8.5l3 3 6-6.5" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" /></svg>
68
+ {/if}
69
+ </button>
70
+ {#if children}<span class="sv-check__label">{@render children()}</span>{/if}
71
+ {#if name}<input type="hidden" {name} value={checked ? (value ?? 'true') : ''} />{/if}
72
+ </label>
73
+ </SvField>
60
74
 
61
75
  <style>
62
76
  .sv-check {
@@ -78,5 +92,6 @@
78
92
  .sv-check__box.is-checked, .sv-check__box.is-indeterminate {
79
93
  background: var(--_accent); border-color: var(--_accent);
80
94
  }
95
+ .sv-check.is-invalid .sv-check__box { border-color: var(--sg-danger, #dc2626); }
81
96
  .sv-check__box:focus-visible { outline: 2px solid var(--sg-focus-ring, var(--_accent)); outline-offset: 2px; }
82
97
  </style>
@@ -0,0 +1,88 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SvChip - a compact pill for a selected/filter/entity value: an optional
4
+ * leading avatar/icon, a label, and an optional remove (x) button; optionally
5
+ * clickable. Larger + more interactive than SvBadge (a static status pill).
6
+ * Theme-driven. Parity: Smart chip / tag.
7
+ *
8
+ * ```svelte
9
+ * <SvChip removable onRemove={() => drop(tag)}>{tag}</SvChip>
10
+ * <SvChip variant="accent" onClick={select}>{#snippet leading()}<SvAvatar size="sm" name="Ada" />{/snippet}Ada</SvChip>
11
+ * ```
12
+ */
13
+ import type { Snippet } from 'svelte'
14
+
15
+ type Variant = 'neutral' | 'accent' | 'success' | 'warning' | 'danger' | 'info'
16
+
17
+ type Props = {
18
+ variant?: Variant
19
+ size?: 'sm' | 'md'
20
+ /** Show a remove (x) button. */
21
+ removable?: boolean
22
+ onRemove?: () => void
23
+ /** Make the chip's label a button. */
24
+ onClick?: () => void
25
+ /** Filled solid vs soft tint (default soft). */
26
+ solid?: boolean
27
+ disabled?: boolean
28
+ children?: Snippet
29
+ /** Leading content (avatar / icon). */
30
+ leading?: Snippet
31
+ }
32
+
33
+ let {
34
+ variant = 'neutral',
35
+ size = 'md',
36
+ removable = false,
37
+ onRemove,
38
+ onClick,
39
+ solid = false,
40
+ disabled = false,
41
+ children,
42
+ leading,
43
+ }: Props = $props()
44
+ </script>
45
+
46
+ <span class="sv-chip sv-chip--{variant} sv-chip--{size}" class:is-solid={solid} class:is-disabled={disabled}>
47
+ {#if leading}<span class="sv-chip__lead">{@render leading()}</span>{/if}
48
+ {#if onClick}
49
+ <button type="button" class="sv-chip__label sv-chip__label--btn" {disabled} onclick={onClick}>{@render children?.()}</button>
50
+ {:else}
51
+ <span class="sv-chip__label">{@render children?.()}</span>
52
+ {/if}
53
+ {#if removable}
54
+ <button type="button" class="sv-chip__x" aria-label="Remove" {disabled} onclick={onRemove}>&times;</button>
55
+ {/if}
56
+ </span>
57
+
58
+ <style>
59
+ .sv-chip {
60
+ --_c: var(--sg-muted, #64748b);
61
+ display: inline-flex; align-items: center; gap: 5px; box-sizing: border-box;
62
+ border-radius: 999px; font-weight: 550; white-space: nowrap; max-width: 100%;
63
+ color: var(--_c);
64
+ background: color-mix(in srgb, var(--_c) 13%, transparent);
65
+ border: 1px solid color-mix(in srgb, var(--_c) 22%, transparent);
66
+ }
67
+ .sv-chip.is-solid { color: var(--sg-on-accent, #fff); background: var(--_c); border-color: var(--_c); }
68
+ .sv-chip.is-disabled { opacity: 0.55; pointer-events: none; }
69
+ .sv-chip--sm { font-size: 11.5px; padding: 1px 4px 1px 8px; height: 22px; }
70
+ .sv-chip--md { font-size: 12.5px; padding: 2px 5px 2px 10px; height: 26px; }
71
+ .sv-chip:not(:has(.sv-chip__x)).sv-chip--sm { padding-inline-end: 8px; }
72
+ .sv-chip:not(:has(.sv-chip__x)).sv-chip--md { padding-inline-end: 10px; }
73
+ .sv-chip__lead { display: inline-flex; margin-inline-start: -4px; }
74
+ .sv-chip__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
75
+ .sv-chip__label--btn { background: none; border: 0; padding: 0; font: inherit; color: inherit; cursor: pointer; }
76
+ .sv-chip__x {
77
+ flex: none; display: grid; place-items: center; width: 18px; height: 18px; border-radius: 50%;
78
+ background: none; border: 0; color: currentColor; cursor: pointer; font-size: 14px; line-height: 1; opacity: 0.7;
79
+ }
80
+ .sv-chip__x:hover { opacity: 1; background: color-mix(in srgb, currentColor 20%, transparent); }
81
+
82
+ .sv-chip--neutral { --_c: var(--sg-muted, #64748b); }
83
+ .sv-chip--accent { --_c: var(--sg-accent, #2563eb); }
84
+ .sv-chip--success { --_c: var(--sg-success, #16a34a); }
85
+ .sv-chip--warning { --_c: var(--sg-warning, #d97706); }
86
+ .sv-chip--danger { --_c: var(--sg-danger, #dc2626); }
87
+ .sv-chip--info { --_c: var(--sg-info, #0891b2); }
88
+ </style>
@@ -0,0 +1,109 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SvCircularProgress - a circular / ring progress indicator (WAI-ARIA
4
+ * `progressbar`). Determinate (value/max) or `indeterminate` (spinning), with
5
+ * color intents, sizing/thickness, and an optional center label or `children`
6
+ * snippet. Theme-driven; respects prefers-reduced-motion.
7
+ *
8
+ * ```svelte
9
+ * <SvCircularProgress value={72} showLabel />
10
+ * <SvCircularProgress indeterminate size={28} color="accent" />
11
+ * ```
12
+ */
13
+ import type { Snippet } from 'svelte'
14
+
15
+ type Props = {
16
+ value?: number
17
+ max?: number
18
+ indeterminate?: boolean
19
+ /** Diameter in px. */
20
+ size?: number
21
+ /** Ring stroke width in px. */
22
+ thickness?: number
23
+ color?: 'accent' | 'success' | 'warning' | 'danger'
24
+ /** Show a percentage label in the center. */
25
+ showLabel?: boolean
26
+ formatLabel?: (value: number, max: number) => string
27
+ ariaLabel?: string
28
+ /** Custom center content (overrides showLabel). */
29
+ children?: Snippet
30
+ }
31
+
32
+ let {
33
+ value = 0,
34
+ max = 100,
35
+ indeterminate = false,
36
+ size = 48,
37
+ thickness = 5,
38
+ color = 'accent',
39
+ showLabel = false,
40
+ formatLabel,
41
+ ariaLabel,
42
+ children,
43
+ }: Props = $props()
44
+
45
+ const r = $derived((size - thickness) / 2)
46
+ const circ = $derived(2 * Math.PI * r)
47
+ const pct = $derived(max > 0 ? Math.min(100, Math.max(0, (value / max) * 100)) : 0)
48
+ const offset = $derived(circ * (1 - pct / 100))
49
+ const label = $derived(formatLabel ? formatLabel(value, max) : `${Math.round(pct)}%`)
50
+ </script>
51
+
52
+ <div
53
+ class="sv-circ sv-circ--{color}"
54
+ class:is-indeterminate={indeterminate}
55
+ style:width={`${size}px`}
56
+ style:height={`${size}px`}
57
+ role="progressbar"
58
+ aria-label={ariaLabel}
59
+ aria-valuemin={indeterminate ? undefined : 0}
60
+ aria-valuemax={indeterminate ? undefined : max}
61
+ aria-valuenow={indeterminate ? undefined : value}
62
+ aria-valuetext={indeterminate ? undefined : label}
63
+ >
64
+ <svg class="sv-circ__svg" viewBox={`0 0 ${size} ${size}`} width={size} height={size}>
65
+ <circle class="sv-circ__track" cx={size / 2} cy={size / 2} r={r} fill="none" stroke-width={thickness} />
66
+ <circle
67
+ class="sv-circ__arc"
68
+ cx={size / 2}
69
+ cy={size / 2}
70
+ r={r}
71
+ fill="none"
72
+ stroke-width={thickness}
73
+ stroke-linecap="round"
74
+ stroke-dasharray={circ}
75
+ stroke-dashoffset={indeterminate ? circ * 0.7 : offset}
76
+ />
77
+ </svg>
78
+ {#if children}
79
+ <div class="sv-circ__center">{@render children()}</div>
80
+ {:else if showLabel && !indeterminate}
81
+ <div class="sv-circ__center sv-circ__label" style:font-size={`${Math.max(9, size * 0.26)}px`}>{label}</div>
82
+ {/if}
83
+ </div>
84
+
85
+ <style>
86
+ .sv-circ {
87
+ --_accent: var(--sg-accent, #2563eb);
88
+ --_c: var(--_accent);
89
+ position: relative; display: inline-flex; flex: none;
90
+ }
91
+ .sv-circ--success { --_c: var(--sg-success, #16a34a); }
92
+ .sv-circ--warning { --_c: var(--sg-warning, #f59e0b); }
93
+ .sv-circ--danger { --_c: var(--sg-danger, #dc2626); }
94
+ .sv-circ__svg { display: block; transform: rotate(-90deg); }
95
+ .sv-circ__track { stroke: var(--sg-border, #e2e8f0); }
96
+ .sv-circ__arc { stroke: var(--_c); transition: stroke-dashoffset 0.35s cubic-bezier(0.16, 1, 0.3, 1); }
97
+ .sv-circ.is-indeterminate .sv-circ__svg { animation: sv-circ-spin 0.9s linear infinite; }
98
+ .sv-circ.is-indeterminate .sv-circ__arc { transition: none; }
99
+ @keyframes sv-circ-spin { to { transform: rotate(270deg); } }
100
+ @media (prefers-reduced-motion: reduce) {
101
+ .sv-circ.is-indeterminate .sv-circ__svg { animation-duration: 2.4s; }
102
+ .sv-circ__arc { transition: none; }
103
+ }
104
+ .sv-circ__center {
105
+ position: absolute; inset: 0; display: grid; place-items: center;
106
+ color: var(--sg-fg, #0f172a);
107
+ }
108
+ .sv-circ__label { font-weight: 700; font-variant-numeric: tabular-nums; }
109
+ </style>
@@ -8,20 +8,24 @@
8
8
  * normalization, palette, draft field and open/close state come from the core;
9
9
  * the portal + anchored measurement stay here (render concerns).
10
10
  */
11
- import { anchoredRect, portalToBody, type AnchoredRect } from './popover'
11
+ import { anchoredRect, portalToBody, popIn, type AnchoredRect } from './popover'
12
+ import { createDismissableLayer } from './a11y/dismissable'
13
+ import SvField from './SvField.svelte'
14
+ import { nextEditorId, resolveMessages, type SvEditorProps } from './editor-contract'
12
15
  import { createColorInput } from './createColorInput.svelte'
13
16
 
14
- type Props = {
17
+ /** User-facing strings for the color popover (localizable via `messages`). */
18
+ type ColorMessages = { dialog: string; picker: string; hex: string }
19
+ const DEFAULT_MESSAGES: ColorMessages = { dialog: 'Choose color', picker: 'Color picker', hex: 'Hex value' }
20
+
21
+ type Props = SvEditorProps & {
15
22
  value?: string
16
23
  onChange?: (hex: string) => void
17
- disabled?: boolean
18
- readonly?: boolean
19
24
  /** Preset swatches. */
20
25
  palette?: string[]
21
- name?: string
22
- size?: 'sm' | 'md' | 'lg'
23
- ariaLabel?: string
24
26
  autoOpen?: boolean
27
+ /** Override the built-in popover strings. */
28
+ messages?: Partial<ColorMessages>
25
29
  }
26
30
 
27
31
  let {
@@ -33,15 +37,32 @@
33
37
  name,
34
38
  size = 'md',
35
39
  ariaLabel,
40
+ invalid = false,
41
+ required = false,
42
+ error,
43
+ label,
44
+ hint,
45
+ dir,
46
+ id,
36
47
  autoOpen = false,
48
+ messages,
37
49
  }: Props = $props()
38
50
 
51
+ const autoId = nextEditorId('sv-color')
52
+ const uid = $derived(id ?? autoId)
53
+ const M = $derived(resolveMessages(DEFAULT_MESSAGES, messages))
54
+
39
55
  const col = createColorInput({
40
56
  value: () => value,
41
57
  onChange: (hex) => onChange?.(hex),
42
58
  disabled: () => disabled,
43
59
  readonly: () => readonly,
44
60
  palette: () => palette as string[],
61
+ id: () => uid,
62
+ invalid: () => invalid,
63
+ required: () => required,
64
+ error: () => error,
65
+ hint: () => hint,
45
66
  ariaLabel: () => ariaLabel,
46
67
  })
47
68
 
@@ -60,16 +81,16 @@
60
81
  const reposition = () => updatePos()
61
82
  window.addEventListener('scroll', reposition, true)
62
83
  window.addEventListener('resize', reposition)
63
- const onDown = (e: PointerEvent) => {
64
- const t = e.target as Node | null
65
- if (t && (triggerEl?.contains(t) || panelEl?.contains(t))) return
66
- col.popover.close()
67
- }
68
- document.addEventListener('pointerdown', onDown, true)
84
+ const layer = createDismissableLayer({
85
+ element: () => [triggerEl, panelEl],
86
+ onDismiss: () => col.popover.close(),
87
+ closeOnEscape: false,
88
+ })
89
+ layer.activate()
69
90
  return () => {
70
91
  window.removeEventListener('scroll', reposition, true)
71
92
  window.removeEventListener('resize', reposition)
72
- document.removeEventListener('pointerdown', onDown, true)
93
+ layer.release()
73
94
  }
74
95
  })
75
96
 
@@ -79,36 +100,40 @@
79
100
  }
80
101
  </script>
81
102
 
82
- <button
83
- bind:this={triggerEl}
84
- class="sv-color sv-color--{size}"
85
- class:is-disabled={disabled}
86
- {...col.swatchProps()}
87
- use:focusOpen
88
- >
89
- <span class="sv-color__swatch" style:background={col.normalized}></span>
90
- <span class="sv-color__hex">{col.normalized}</span>
91
- </button>
103
+ <SvField id={uid} {label} {hint} {error} {required} {dir}>
104
+ <button
105
+ bind:this={triggerEl}
106
+ class="sv-color sv-color--{size}"
107
+ class:is-disabled={disabled}
108
+ class:is-invalid={invalid}
109
+ {...col.swatchProps()}
110
+ use:focusOpen
111
+ >
112
+ <span class="sv-color__swatch" style:background={col.normalized}></span>
113
+ <span class="sv-color__hex">{col.normalized}</span>
114
+ </button>
115
+ </SvField>
92
116
 
93
117
  {#if col.popover.open}
94
118
  <div
95
119
  class="sv-color__panel"
96
120
  bind:this={panelEl}
97
121
  use:portalToBody
122
+ use:popIn={{ up: panelRect.openUpward }}
98
123
  style:position="fixed"
99
124
  style:top={`${panelRect.top}px`}
100
125
  style:left={`${panelRect.left}px`}
101
126
  role="dialog"
102
- aria-label="Choose color"
127
+ aria-label={M.dialog}
103
128
  >
104
129
  <div class="sv-color__top">
105
- <input class="sv-color__native" type="color" value={col.normalized} oninput={(e) => col.pick((e.currentTarget as HTMLInputElement).value)} aria-label="Color picker" />
130
+ <input class="sv-color__native" type="color" value={col.normalized} oninput={(e) => col.pick((e.currentTarget as HTMLInputElement).value)} aria-label={M.picker} />
106
131
  <input
107
132
  class="sv-color__field"
108
133
  type="text"
109
134
  bind:value={col.hexDraft}
110
135
  spellcheck="false"
111
- aria-label="Hex value"
136
+ aria-label={M.hex}
112
137
  onblur={col.commitHex}
113
138
  onkeydown={(e) => { if (e.key === 'Enter') col.commitHex() }}
114
139
  />
@@ -139,6 +164,8 @@
139
164
  .sv-color--md { height: 34px; font-size: 13px; }
140
165
  .sv-color--lg { height: 40px; font-size: 15px; }
141
166
  .sv-color.is-disabled { opacity: 0.6; cursor: not-allowed; }
167
+ .sv-color.is-invalid { border-color: var(--sg-danger, #dc2626); }
168
+ .sv-color.is-invalid:focus-within { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
142
169
  .sv-color:focus-visible { outline: 2px solid var(--sg-focus-ring, var(--_accent)); outline-offset: 2px; }
143
170
  .sv-color__swatch { width: 18px; height: 18px; border-radius: 4px; border: 1px solid rgba(0,0,0,0.15); flex: none; }
144
171
  .sv-color__hex { font-variant-numeric: tabular-nums; text-transform: lowercase; }
@@ -7,37 +7,106 @@
7
7
  * This is one styled renderer over the headless `createCombobox` core (state +
8
8
  * keyboard + ARIA); only portal/measure/scroll render concerns live here.
9
9
  */
10
- import { anchoredRect, portalToBody, type AnchoredRect } from './popover'
11
- import { type ListOption } from './list-option'
10
+ import { anchoredRect, portalToBody, popIn, type AnchoredRect } from './popover'
11
+ import { createDismissableLayer } from './a11y/dismissable'
12
+ import { groupOptions, type ListOption } from './list-option'
13
+ import SvField from './SvField.svelte'
14
+ import { nextEditorId, resolveMessages, type SvEditorProps } from './editor-contract'
12
15
  import { createCombobox } from './createCombobox.svelte'
13
16
 
14
- type Props = {
15
- options: ReadonlyArray<ListOption>
17
+ /** User-facing strings (localizable via `messages`). */
18
+ type ComboMessages = { noResults: string; loading: string; typeMore: string }
19
+ const DEFAULT_MESSAGES: ComboMessages = { noResults: 'No matches', loading: 'Loading...', typeMore: 'Type to search' }
20
+
21
+ type Props = SvEditorProps & {
22
+ options?: ReadonlyArray<ListOption>
16
23
  value?: string | number | null
17
24
  onChange?: (value: string | number | null) => void
18
25
  placeholder?: string
19
- disabled?: boolean
20
- name?: string
21
- size?: 'sm' | 'md' | 'lg'
22
- ariaLabel?: string
23
26
  autoOpen?: boolean
27
+ /** Load options from a server as the user types (debounced). Disables local
28
+ * filtering; the returned list is shown as-is. */
29
+ loadOptions?: (query: string) => Promise<ListOption[]>
30
+ /** Min chars before a remote search fires. Default 1. */
31
+ minLength?: number
32
+ /** Debounce (ms) before a remote search fires. Default 250. */
33
+ debounce?: number
34
+ /** Override the built-in strings (empty-state / loading text). */
35
+ messages?: Partial<ComboMessages>
24
36
  }
25
37
 
26
- let { options, value = null, onChange, placeholder = 'Select…', disabled = false, name, size = 'md', ariaLabel, autoOpen = false }: Props = $props()
38
+ let {
39
+ options = [],
40
+ value = null,
41
+ onChange,
42
+ placeholder = 'Select…',
43
+ disabled = false,
44
+ name,
45
+ size = 'md',
46
+ ariaLabel,
47
+ invalid = false,
48
+ required = false,
49
+ error,
50
+ label,
51
+ hint,
52
+ dir,
53
+ id,
54
+ autoOpen = false,
55
+ loadOptions,
56
+ minLength = 1,
57
+ debounce = 250,
58
+ messages,
59
+ }: Props = $props()
60
+
61
+ const autoId = nextEditorId('sv-combo')
62
+ const uid = $derived(id ?? autoId)
63
+ const M = $derived(resolveMessages(DEFAULT_MESSAGES, messages))
27
64
 
28
65
  let inputEl = $state<HTMLInputElement | null>(null)
29
66
  let fieldEl = $state<HTMLDivElement | null>(null)
30
67
  let panelEl = $state<HTMLDivElement | null>(null)
31
68
  let rect = $state<AnchoredRect>({ top: 0, left: 0, width: 0, openUpward: false })
32
69
 
70
+ // Remote data source: fetch options as the query changes (debounced).
71
+ let remoteOptions = $state<ListOption[]>([])
72
+ let loading = $state(false)
73
+ let searched = $state(false)
74
+ let debTimer: ReturnType<typeof setTimeout> | undefined
75
+ let reqSeq = 0
76
+ const effectiveOptions = $derived(loadOptions ? remoteOptions : options)
77
+
33
78
  const combo = createCombobox({
34
- options: () => options,
79
+ options: () => effectiveOptions,
35
80
  value: () => value,
36
81
  onChange: (v) => onChange?.(v),
37
82
  disabled: () => disabled,
38
83
  ariaLabel: () => ariaLabel,
84
+ localFilter: () => !loadOptions,
39
85
  focusInput: () => inputEl?.focus(),
40
86
  blurInput: () => inputEl?.blur(),
87
+ id: () => uid,
88
+ invalid: () => invalid,
89
+ required: () => required,
90
+ error: () => error,
91
+ hint: () => hint,
92
+ })
93
+
94
+ // Debounced remote search driven by the combobox query while editing.
95
+ $effect(() => {
96
+ if (!loadOptions) return
97
+ const q = combo.editing ? combo.query : ''
98
+ if (debTimer) clearTimeout(debTimer)
99
+ if (q.trim().length < minLength) { remoteOptions = []; loading = false; searched = false; return }
100
+ loading = true
101
+ const seq = ++reqSeq
102
+ debTimer = setTimeout(async () => {
103
+ try {
104
+ const res = await loadOptions!(q.trim())
105
+ if (seq === reqSeq) { remoteOptions = res; searched = true }
106
+ } finally {
107
+ if (seq === reqSeq) loading = false
108
+ }
109
+ }, debounce)
41
110
  })
42
111
 
43
112
  function updatePos() {
@@ -51,9 +120,9 @@
51
120
  updatePos()
52
121
  const rp = () => updatePos()
53
122
  window.addEventListener('scroll', rp, true); window.addEventListener('resize', rp)
54
- const od = (e: PointerEvent) => { const t = e.target as Node | null; if (t && (fieldEl?.contains(t) || panelEl?.contains(t))) return; combo.close() }
55
- document.addEventListener('pointerdown', od, true)
56
- return () => { window.removeEventListener('scroll', rp, true); window.removeEventListener('resize', rp); document.removeEventListener('pointerdown', od, true) }
123
+ const layer = createDismissableLayer({ element: () => [fieldEl, panelEl], onDismiss: () => combo.close(), closeOnEscape: false })
124
+ layer.activate()
125
+ return () => { window.removeEventListener('scroll', rp, true); window.removeEventListener('resize', rp); layer.release() }
57
126
  })
58
127
  // Keep the active option scrolled into view (render concern).
59
128
  $effect(() => {
@@ -64,28 +133,39 @@
64
133
  function focusOpen(node: HTMLInputElement) { if (autoOpen) { node.focus() } }
65
134
  </script>
66
135
 
67
- <div bind:this={fieldEl} class="sv-combo sv-combo--{size}" class:is-open={combo.open} class:is-disabled={disabled}>
68
- <input
69
- bind:this={inputEl}
70
- class="sv-combo__input"
71
- type="text"
72
- {placeholder}
73
- {...combo.inputProps()}
74
- use:focusOpen
75
- />
76
- <button class="sv-combo__chev" {...combo.triggerProps()}>
77
- <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></svg>
78
- </button>
79
- </div>
136
+ <SvField id={uid} {label} {hint} {error} {required} {dir}>
137
+ <div bind:this={fieldEl} class="sv-combo sv-combo--{size}" class:is-open={combo.open} class:is-disabled={disabled} class:is-invalid={invalid}>
138
+ <input
139
+ bind:this={inputEl}
140
+ class="sv-combo__input"
141
+ type="text"
142
+ {placeholder}
143
+ {...combo.inputProps()}
144
+ use:focusOpen
145
+ />
146
+ <button class="sv-combo__chev" {...combo.triggerProps()}>
147
+ <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></svg>
148
+ </button>
149
+ </div>
150
+ </SvField>
80
151
 
81
152
  {#if combo.open}
82
- <div bind:this={panelEl} class="sv-ddl__panel" use:portalToBody style:position="fixed" style:top={`${rect.top}px`} style:left={`${rect.left}px`} style:min-width={`${rect.width}px`} {...combo.listboxProps()}>
83
- {#each combo.filtered as opt, i (opt.value)}
84
- <!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
85
- <div class="sv-ddl__opt" class:is-active={combo.isActive(i)} class:is-selected={combo.isSelected(opt)} class:is-disabled={opt.disabled} {...combo.optionProps(i)}>{opt.label}</div>
153
+ <div bind:this={panelEl} class="sv-ddl__panel" use:portalToBody use:popIn={{ up: rect.openUpward }} style:position="fixed" style:top={`${rect.top}px`} style:left={`${rect.left}px`} style:min-width={`${rect.width}px`} {...combo.listboxProps()}>
154
+ {#if loading}
155
+ <div class="sv-ddl__empty sv-ddl__loading"><svg class="sv-ddl__spin" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M21 12a9 9 0 1 1-6.2-8.6" /></svg>{M.loading}</div>
156
+ {:else if loadOptions && !searched && combo.query.trim().length < minLength}
157
+ <div class="sv-ddl__empty">{M.typeMore}</div>
158
+ {:else if combo.filtered.length}
159
+ {#each groupOptions(combo.filtered) as g (g.group ?? ' ')}
160
+ {#if g.group != null}<div class="sv-ddl__group-label" aria-hidden="true">{g.group}</div>{/if}
161
+ {#each g.options as opt (opt.value)}
162
+ <!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
163
+ <div class="sv-ddl__opt" class:is-active={combo.isActive(opt.index)} class:is-selected={combo.isSelected(opt)} class:is-disabled={opt.disabled} {...combo.optionProps(opt.index)}>{opt.label}</div>
164
+ {/each}
165
+ {/each}
86
166
  {:else}
87
- <div class="sv-ddl__empty">No matches</div>
88
- {/each}
167
+ <div class="sv-ddl__empty">{M.noResults}</div>
168
+ {/if}
89
169
  </div>
90
170
  {/if}
91
171
  {#if name}<input type="hidden" {name} value={value ?? ''} />{/if}
@@ -101,8 +181,14 @@
101
181
  .sv-combo--md { height: 34px; font-size: 13px; }
102
182
  .sv-combo--lg { height: 40px; font-size: 15px; }
103
183
  .sv-combo.is-open, .sv-combo:focus-within { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
184
+ .sv-combo.is-invalid { border-color: var(--sg-danger, #dc2626); }
185
+ .sv-combo.is-invalid.is-open, .sv-combo.is-invalid:focus-within { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
104
186
  .sv-combo.is-disabled { opacity: 0.6; }
105
- .sv-combo__input { flex: 1; min-width: 0; border: 0; background: none; outline: none; color: inherit; font: inherit; padding: 0 4px 0 10px; }
187
+ .sv-combo__input { flex: 1; min-width: 0; border: 0; background: none; outline: none; color: inherit; font: inherit; padding-block: 0; padding-inline: 10px 4px; }
106
188
  .sv-combo__chev { display: grid; place-items: center; width: 28px; align-self: stretch; background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; }
107
189
  :global(.sv-ddl__empty) { padding: 8px 10px; color: var(--sg-muted, #94a3b8); font-size: 13px; }
190
+ :global(.sv-ddl__loading) { display: flex; align-items: center; gap: 7px; }
191
+ :global(.sv-ddl__spin) { animation: sv-combo-spin 0.7s linear infinite; }
192
+ @keyframes sv-combo-spin { to { transform: rotate(360deg); } }
193
+ @media (prefers-reduced-motion: reduce) { :global(.sv-ddl__spin) { animation: none; } }
108
194
  </style>