@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
@@ -15,18 +15,30 @@
15
15
  *
16
16
  * The styled <SvCheckBox> is just one renderer over this core.
17
17
  */
18
+ import { editorAria, type EditorAriaState } from './editor-contract'
19
+
18
20
  export type CheckboxConfig = {
19
21
  checked: () => boolean
20
22
  indeterminate?: () => boolean
21
23
  onChange?: (checked: boolean) => void
22
24
  disabled?: () => boolean
25
+ // Editor contract (ARIA + validation) - folded into boxProps().
26
+ id?: () => string | undefined
27
+ invalid?: () => boolean
28
+ required?: () => boolean
29
+ error?: () => string | undefined
30
+ hint?: () => string | undefined
23
31
  ariaLabel?: () => string | undefined
24
32
  }
25
33
 
26
34
  export type CheckboxBoxProps = {
27
35
  type: 'button'
28
36
  role: 'checkbox'
37
+ id: string | undefined
29
38
  'aria-checked': boolean | 'mixed'
39
+ 'aria-invalid': 'true' | undefined
40
+ 'aria-required': 'true' | undefined
41
+ 'aria-describedby': string | undefined
30
42
  'aria-label': string | undefined
31
43
  disabled: boolean
32
44
  'data-checked': '' | undefined
@@ -55,6 +67,15 @@ export function createCheckbox(config: CheckboxConfig): Checkbox {
55
67
  config.onChange?.(indeterminate() ? true : !checked())
56
68
  }
57
69
 
70
+ const ariaState = (): EditorAriaState => ({
71
+ id: config.id?.(),
72
+ invalid: config.invalid?.(),
73
+ required: config.required?.(),
74
+ error: config.error?.(),
75
+ hint: config.hint?.(),
76
+ ariaLabel: config.ariaLabel?.(),
77
+ })
78
+
58
79
  return {
59
80
  get checked() { return checked() },
60
81
  get indeterminate() { return indeterminate() },
@@ -63,11 +84,12 @@ export function createCheckbox(config: CheckboxConfig): Checkbox {
63
84
  boxProps: () => ({
64
85
  type: 'button',
65
86
  role: 'checkbox',
87
+ id: config.id?.(),
66
88
  'aria-checked': indeterminate() ? 'mixed' : checked(),
67
- 'aria-label': config.ariaLabel?.(),
68
89
  disabled: disabled(),
69
90
  'data-checked': checked() && !indeterminate() ? '' : undefined,
70
91
  'data-indeterminate': indeterminate() ? '' : undefined,
92
+ ...editorAria(ariaState()),
71
93
  onclick: toggle,
72
94
  }),
73
95
  }
@@ -17,6 +17,7 @@
17
17
  * The styled <SvColorInput> is just one renderer over this core (it adds the
18
18
  * portal + anchored positioning).
19
19
  */
20
+ import { editorAria, type EditorAriaState } from './editor-contract'
20
21
 
21
22
  export const DEFAULT_PALETTE = [
22
23
  '#ef4444', '#f97316', '#f59e0b', '#eab308', '#84cc16', '#22c55e', '#10b981', '#14b8a6',
@@ -39,6 +40,12 @@ export type ColorInputConfig = {
39
40
  disabled?: () => boolean
40
41
  readonly?: () => boolean
41
42
  palette?: () => string[]
43
+ // Editor contract (ARIA + validation) - folded into swatchProps().
44
+ id?: () => string | undefined
45
+ invalid?: () => boolean
46
+ required?: () => boolean
47
+ error?: () => string | undefined
48
+ hint?: () => string | undefined
42
49
  ariaLabel?: () => string | undefined
43
50
  }
44
51
 
@@ -71,6 +78,15 @@ export function createColorInput(config: ColorInputConfig) {
71
78
  }
72
79
  const isActive = (c: string) => normalizeHex(c) === normalized
73
80
 
81
+ const ariaState = (): EditorAriaState => ({
82
+ id: config.id?.(),
83
+ invalid: config.invalid?.(),
84
+ required: config.required?.(),
85
+ error: config.error?.(),
86
+ hint: config.hint?.(),
87
+ ariaLabel: config.ariaLabel?.(),
88
+ })
89
+
74
90
  // Popover control surface (open state + show/close/toggle), no DOM measurement.
75
91
  const popover = {
76
92
  get open() { return open },
@@ -96,9 +112,13 @@ export function createColorInput(config: ColorInputConfig) {
96
112
  isActive,
97
113
  /** Spread onto the swatch trigger button. */
98
114
  swatchProps: () => ({
115
+ id: config.id?.(),
99
116
  type: 'button' as const,
100
117
  'aria-haspopup': 'dialog' as const,
101
118
  'aria-expanded': open,
119
+ // aria-invalid/required/describedby come from the editor contract; the
120
+ // aria-label default names the swatch by its current color when unset.
121
+ ...editorAria(ariaState()),
102
122
  'aria-label': config.ariaLabel?.() ?? `Color ${normalized}`,
103
123
  disabled: !isInteractive,
104
124
  onclick: toggle,
@@ -20,6 +20,7 @@
20
20
  * ```
21
21
  */
22
22
  import { filterOptions, type ListOption } from './list-option'
23
+ import { editorAria, type EditorAriaState } from './editor-contract'
23
24
 
24
25
  export type ComboboxValue = string | number | null
25
26
 
@@ -30,9 +31,17 @@ export type ComboboxConfig = {
30
31
  onChange?: (value: ComboboxValue) => void
31
32
  disabled?: () => boolean
32
33
  ariaLabel?: () => string | undefined
34
+ /** Filter the options locally by the query. Set false for server-side search. */
35
+ localFilter?: () => boolean
33
36
  /** DOM focus hooks provided by the renderer; the core itself never touches the DOM. */
34
37
  focusInput?: () => void
35
38
  blurInput?: () => void
39
+ // Editor contract (ARIA + validation) - folded into inputProps().
40
+ id?: () => string | undefined
41
+ invalid?: () => boolean
42
+ required?: () => boolean
43
+ error?: () => string | undefined
44
+ hint?: () => string | undefined
36
45
  }
37
46
 
38
47
  let uid = 0
@@ -48,8 +57,9 @@ export function createCombobox(config: ComboboxConfig) {
48
57
  let active = $state(0)
49
58
  let editing = $state(false)
50
59
 
60
+ const localFilter = () => config.localFilter?.() ?? true
51
61
  const selected = $derived(opts().find((o) => o.value === config.value()) ?? null)
52
- const filtered = $derived(editing ? filterOptions(opts(), query) : [...opts()])
62
+ const filtered = $derived(editing && localFilter() ? filterOptions(opts(), query) : [...opts()])
53
63
  const shownText = $derived(editing ? query : selected?.label ?? '')
54
64
 
55
65
  // While not actively editing, keep the shown text in sync with the selection.
@@ -57,6 +67,15 @@ export function createCombobox(config: ComboboxConfig) {
57
67
 
58
68
  const optionId = (i: number) => `${listId}-opt-${i}`
59
69
 
70
+ const ariaState = (): EditorAriaState => ({
71
+ id: config.id?.(),
72
+ invalid: config.invalid?.(),
73
+ required: config.required?.(),
74
+ error: config.error?.(),
75
+ hint: config.hint?.(),
76
+ ariaLabel: config.ariaLabel?.(),
77
+ })
78
+
60
79
  function openPanel() { if (disabled() || open) return; open = true; active = 0 }
61
80
  function close(revert = true) {
62
81
  open = false; editing = false
@@ -114,11 +133,12 @@ export function createCombobox(config: ComboboxConfig) {
114
133
  onKeydown,
115
134
  /** Spread onto the editable <input>. */
116
135
  inputProps: () => ({
136
+ id: config.id?.(),
117
137
  role: 'combobox' as const,
118
138
  'aria-expanded': open,
119
139
  'aria-controls': listId,
120
140
  'aria-autocomplete': 'list' as const,
121
- 'aria-label': config.ariaLabel?.(),
141
+ ...editorAria(ariaState()),
122
142
  value: shownText,
123
143
  disabled: disabled(),
124
144
  oninput: onInput,
@@ -21,6 +21,7 @@
21
21
  * ```
22
22
  */
23
23
  import { COUNTRIES, COUNTRY_BY_CODE, type Country } from './countries'
24
+ import { editorAria, type EditorAriaState } from './editor-contract'
24
25
 
25
26
  /** Reactive inputs are passed as getters so the core tracks live prop changes. */
26
27
  export type CountryInputConfig = {
@@ -28,13 +29,30 @@ export type CountryInputConfig = {
28
29
  onChange?: (code: string) => void
29
30
  disabled?: () => boolean
30
31
  ariaLabel?: () => string | undefined
32
+ /** Accessible label for the in-panel search field (localizable). */
33
+ searchLabel?: () => string | undefined
31
34
  /** DOM focus hook provided by the renderer; the core never touches the DOM. */
32
35
  focusTrigger?: () => void
36
+ // Editor contract (ARIA + validation) - folded into triggerProps().
37
+ id?: () => string | undefined
38
+ invalid?: () => boolean
39
+ required?: () => boolean
40
+ error?: () => string | undefined
41
+ hint?: () => string | undefined
33
42
  }
34
43
 
35
44
  export function createCountryInput(config: CountryInputConfig) {
36
45
  const disabled = () => config.disabled?.() ?? false
37
46
 
47
+ const ariaState = (): EditorAriaState => ({
48
+ id: config.id?.(),
49
+ invalid: config.invalid?.(),
50
+ required: config.required?.(),
51
+ error: config.error?.(),
52
+ hint: config.hint?.(),
53
+ ariaLabel: config.ariaLabel?.(),
54
+ })
55
+
38
56
  let open = $state(false)
39
57
  let query = $state('')
40
58
  let active = $state(0)
@@ -89,10 +107,11 @@ export function createCountryInput(config: CountryInputConfig) {
89
107
  onKeydown,
90
108
  /** Spread onto the trigger <button>. */
91
109
  triggerProps: () => ({
110
+ id: config.id?.(),
92
111
  type: 'button' as const,
93
112
  'aria-haspopup': 'listbox' as const,
94
113
  'aria-expanded': open,
95
- 'aria-label': config.ariaLabel?.(),
114
+ ...editorAria(ariaState()),
96
115
  disabled: disabled(),
97
116
  onclick: toggle,
98
117
  }),
@@ -100,7 +119,7 @@ export function createCountryInput(config: CountryInputConfig) {
100
119
  searchProps: () => ({
101
120
  type: 'text' as const,
102
121
  value: query,
103
- 'aria-label': 'Search countries',
122
+ 'aria-label': config.searchLabel?.() ?? 'Search countries',
104
123
  oninput: onSearchInput,
105
124
  onkeydown: onKeydown,
106
125
  }),
@@ -17,6 +17,7 @@
17
17
  */
18
18
  import { formatDate, parseDate } from './datetime/date-format'
19
19
  import { toDate, clampDate, withTime, startOfDay, type DateLike } from './datetime/date-core'
20
+ import { editorAria, type EditorAriaState } from './editor-contract'
20
21
 
21
22
  export type DateTimeValue = Date | string | number | null
22
23
  export type DropDownDisplayMode = 'both' | 'calendar' | 'time'
@@ -40,6 +41,17 @@ export type DateTimePickerConfig = {
40
41
  stepMinutes?: () => number
41
42
  disabled?: () => boolean
42
43
  readonly?: () => boolean
44
+ /** Accessible label for the open-picker toggle (localizable). */
45
+ toggleLabel?: () => string | undefined
46
+ /** Accessible label for the clear button (localizable). */
47
+ clearLabel?: () => string | undefined
48
+ // Editor contract (ARIA + validation) - folded into inputProps().
49
+ id?: () => string | undefined
50
+ invalid?: () => boolean
51
+ required?: () => boolean
52
+ error?: () => string | undefined
53
+ hint?: () => string | undefined
54
+ ariaLabel?: () => string | undefined
43
55
  }
44
56
 
45
57
  const defaultLocale = () => (typeof navigator !== 'undefined' ? navigator.language : 'en-US')
@@ -164,9 +176,18 @@ export function createDateTimePicker(config: DateTimePickerConfig) {
164
176
  * alongside `bind:value={dtp.text}`. */
165
177
  inputProps() {
166
178
  return {
179
+ id: config.id?.(),
167
180
  type: 'text' as const,
168
181
  disabled: isDisabled(),
169
182
  readonly: isReadonly(),
183
+ ...editorAria({
184
+ id: config.id?.(),
185
+ invalid: config.invalid?.(),
186
+ required: config.required?.(),
187
+ error: config.error?.(),
188
+ hint: config.hint?.(),
189
+ ariaLabel: config.ariaLabel?.(),
190
+ } satisfies EditorAriaState),
170
191
  onblur: commitFromText,
171
192
  onkeydown: onInputKeydown,
172
193
  }
@@ -175,7 +196,7 @@ export function createDateTimePicker(config: DateTimePickerConfig) {
175
196
  toggleProps() {
176
197
  return {
177
198
  type: 'button' as const,
178
- 'aria-label': 'Open picker',
199
+ 'aria-label': config.toggleLabel?.() ?? 'Open picker',
179
200
  'aria-haspopup': 'dialog' as const,
180
201
  'aria-expanded': open,
181
202
  disabled: !isInteractive,
@@ -187,7 +208,7 @@ export function createDateTimePicker(config: DateTimePickerConfig) {
187
208
  clearProps() {
188
209
  return {
189
210
  type: 'button' as const,
190
- 'aria-label': 'Clear',
211
+ 'aria-label': config.clearLabel?.() ?? 'Clear',
191
212
  onclick: () => commit(null),
192
213
  }
193
214
  },
@@ -19,7 +19,8 @@
19
19
  * {/if}
20
20
  * ```
21
21
  */
22
- import type { ListOption } from './list-option'
22
+ import { createTypeaheadBuffer, isTypeaheadKey, nextTypeaheadIndex, type ListOption } from './list-option'
23
+ import { editorAria, type EditorAriaState } from './editor-contract'
23
24
 
24
25
  export type DropdownValue = string | number | null
25
26
 
@@ -32,12 +33,27 @@ export type DropdownListConfig = {
32
33
  ariaLabel?: () => string | undefined
33
34
  /** DOM focus hook provided by the renderer; the core never touches the DOM. */
34
35
  focusTrigger?: () => void
36
+ // Editor contract (ARIA + validation) - folded into triggerProps().
37
+ id?: () => string | undefined
38
+ invalid?: () => boolean
39
+ required?: () => boolean
40
+ error?: () => string | undefined
41
+ hint?: () => string | undefined
35
42
  }
36
43
 
37
44
  export function createDropdownList(config: DropdownListConfig) {
38
45
  const opts = () => config.options()
39
46
  const disabled = () => config.disabled?.() ?? false
40
47
 
48
+ const ariaState = (): EditorAriaState => ({
49
+ id: config.id?.(),
50
+ invalid: config.invalid?.(),
51
+ required: config.required?.(),
52
+ error: config.error?.(),
53
+ hint: config.hint?.(),
54
+ ariaLabel: config.ariaLabel?.(),
55
+ })
56
+
41
57
  let open = $state(false)
42
58
  let active = $state(-1)
43
59
 
@@ -61,8 +77,20 @@ export function createDropdownList(config: DropdownListConfig) {
61
77
  const pos = enabledIdx.indexOf(active)
62
78
  active = enabledIdx[(pos + d + enabledIdx.length) % enabledIdx.length] ?? active
63
79
  }
80
+ // Type-ahead: typing a label prefix jumps the active option (and selects it
81
+ // when the list is closed, matching a native <select>).
82
+ const typeahead = createTypeaheadBuffer()
83
+ function onTypeahead(char: string) {
84
+ const buffer = typeahead.push(char.toLowerCase())
85
+ const from = active >= 0 ? active : opts().findIndex((o) => o.value === config.value())
86
+ const next = nextTypeaheadIndex(opts(), buffer, from)
87
+ if (next < 0) return
88
+ active = next
89
+ if (!open) config.onChange?.(opts()[next]!.value)
90
+ }
64
91
  function onKeydown(e: KeyboardEvent) {
65
92
  if (disabled()) return
93
+ if (isTypeaheadKey(e)) { onTypeahead(e.key); return }
66
94
  if (!open && (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); openPanel(); return }
67
95
  if (!open) return
68
96
  if (e.key === 'ArrowDown') { e.preventDefault(); move(1) }
@@ -93,10 +121,11 @@ export function createDropdownList(config: DropdownListConfig) {
93
121
  onKeydown,
94
122
  /** Spread onto the trigger <button>. */
95
123
  triggerProps: () => ({
124
+ id: config.id?.(),
96
125
  type: 'button' as const,
97
126
  'aria-haspopup': 'listbox' as const,
98
127
  'aria-expanded': open,
99
- 'aria-label': config.ariaLabel?.(),
128
+ ...editorAria(ariaState()),
100
129
  disabled: disabled(),
101
130
  onclick: toggle,
102
131
  onkeydown: onKeydown,
@@ -0,0 +1,144 @@
1
+ /**
2
+ * createFileUpload - the HEADLESS core behind <SvFileUpload>: a drag-and-drop
3
+ * file field with accept / size / count validation, exposed as **prop-getters**
4
+ * you spread onto YOUR OWN markup. The DataTransfer / File reading is a DOM
5
+ * concern that stays in the renderer; the core owns the list, validation, drag
6
+ * state and ARIA. Parity: Smart `smart-file-upload`.
7
+ *
8
+ * ```svelte
9
+ * <script lang="ts">
10
+ * import { createFileUpload } from '@svgrid/grid'
11
+ * let files = $state<File[]>([])
12
+ * const fu = createFileUpload({ files: () => files, onChange: (f) => (files = f) })
13
+ * </script>
14
+ * <div {...fu.dropzoneProps()} ondrop={(e) => fu.addFiles([...e.dataTransfer.files])}>...</div>
15
+ * <input {...fu.inputProps()} />
16
+ * ```
17
+ */
18
+ import { editorAria, type EditorAriaState } from './editor-contract'
19
+
20
+ export type FileRejectReason = 'type' | 'size' | 'count'
21
+ export type FileRejection = { file: File; reason: FileRejectReason }
22
+
23
+ /** Reactive inputs are passed as getters so the core tracks live prop changes. */
24
+ export type FileUploadConfig = {
25
+ files: () => File[]
26
+ onChange?: (files: File[]) => void
27
+ /** Rejected files (wrong type / too big / over the count cap). */
28
+ onReject?: (rejections: FileRejection[]) => void
29
+ /** Comma list like "image/*,.pdf" (matches MIME or extension). */
30
+ accept?: () => string | undefined
31
+ multiple?: () => boolean
32
+ /** Max size per file in bytes. */
33
+ maxSize?: () => number | undefined
34
+ /** Max number of files (multiple mode). */
35
+ maxFiles?: () => number | undefined
36
+ disabled?: () => boolean
37
+ readonly?: () => boolean
38
+ // Editor contract (ARIA + validation) - folded into dropzoneProps().
39
+ id?: () => string | undefined
40
+ invalid?: () => boolean
41
+ required?: () => boolean
42
+ error?: () => string | undefined
43
+ hint?: () => string | undefined
44
+ ariaLabel?: () => string | undefined
45
+ }
46
+
47
+ /** Whether `file` satisfies an `accept` list (MIME globs or extensions). Pure. */
48
+ export function fileMatchesAccept(file: File, accept?: string): boolean {
49
+ if (!accept || !accept.trim()) return true
50
+ const name = file.name.toLowerCase()
51
+ const type = file.type.toLowerCase()
52
+ return accept.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean).some((tok) => {
53
+ if (tok.startsWith('.')) return name.endsWith(tok)
54
+ if (tok.endsWith('/*')) return type.startsWith(tok.slice(0, -1))
55
+ return type === tok
56
+ })
57
+ }
58
+
59
+ export function createFileUpload(config: FileUploadConfig) {
60
+ const accept = () => config.accept?.()
61
+ const multiple = () => config.multiple?.() ?? false
62
+ const maxSize = () => config.maxSize?.()
63
+ const maxFiles = () => config.maxFiles?.()
64
+ const disabled = () => config.disabled?.() ?? false
65
+ const readonly = () => config.readonly?.() ?? false
66
+ const isInteractive = $derived(!disabled() && !readonly())
67
+
68
+ let dragging = $state(false)
69
+
70
+ function addFiles(incoming: File[]) {
71
+ if (!isInteractive || !incoming.length) return
72
+ const accepted: File[] = []
73
+ const rejected: FileRejection[] = []
74
+ for (const f of incoming) {
75
+ if (!fileMatchesAccept(f, accept())) { rejected.push({ file: f, reason: 'type' }); continue }
76
+ const cap = maxSize()
77
+ if (cap != null && f.size > cap) { rejected.push({ file: f, reason: 'size' }); continue }
78
+ accepted.push(f)
79
+ }
80
+ let next = multiple() ? [...config.files(), ...accepted] : accepted.slice(-1)
81
+ const limit = maxFiles()
82
+ if (multiple() && limit != null && next.length > limit) {
83
+ for (const f of next.slice(limit)) rejected.push({ file: f, reason: 'count' })
84
+ next = next.slice(0, limit)
85
+ }
86
+ if (rejected.length) config.onReject?.(rejected)
87
+ config.onChange?.(next)
88
+ }
89
+
90
+ function removeAt(index: number) {
91
+ if (!isInteractive) return
92
+ config.onChange?.(config.files().filter((_, i) => i !== index))
93
+ }
94
+ function clear() { if (isInteractive) config.onChange?.([]) }
95
+
96
+ const setDragging = (v: boolean) => { if (isInteractive) dragging = v }
97
+
98
+ const ariaState = (): EditorAriaState => ({
99
+ id: config.id?.(),
100
+ invalid: config.invalid?.(),
101
+ required: config.required?.(),
102
+ error: config.error?.(),
103
+ hint: config.hint?.(),
104
+ ariaLabel: config.ariaLabel?.(),
105
+ })
106
+
107
+ return {
108
+ /** Whether files are being dragged over the dropzone. */
109
+ get dragging() { return dragging },
110
+ /** Whether interaction is allowed. */
111
+ get isInteractive() { return isInteractive },
112
+ /** Selected files (controlled value). */
113
+ get files() { return config.files() },
114
+ addFiles,
115
+ removeAt,
116
+ clear,
117
+ setDragging,
118
+ /** Spread onto the dropzone element (click opens the picker; Enter/Space too).
119
+ * Wire drag events yourself (they carry a DataTransfer): call
120
+ * `setDragging(true/false)` and `addFiles([...e.dataTransfer.files])`. */
121
+ dropzoneProps: () => ({
122
+ role: 'button' as const,
123
+ tabindex: isInteractive ? 0 : -1,
124
+ 'data-dragging': dragging ? '' : undefined,
125
+ 'aria-disabled': !isInteractive || undefined,
126
+ ...editorAria(ariaState()),
127
+ }),
128
+ /** Spread onto the visually-hidden <input type="file">. */
129
+ inputProps: () => ({
130
+ type: 'file' as const,
131
+ accept: accept(),
132
+ multiple: multiple(),
133
+ disabled: !isInteractive,
134
+ tabindex: -1,
135
+ }),
136
+ /** Spread onto a selected file's remove <button> at `index`. */
137
+ removeProps: (index: number) => ({
138
+ type: 'button' as const,
139
+ onclick: () => removeAt(index),
140
+ }),
141
+ }
142
+ }
143
+
144
+ export type FileUpload = ReturnType<typeof createFileUpload>
@@ -22,7 +22,8 @@
22
22
  *
23
23
  * The styled <SvListBox> is just one renderer over this core.
24
24
  */
25
- import type { ListOption } from './list-option'
25
+ import { createTypeaheadBuffer, isTypeaheadKey, nextTypeaheadIndex, type ListOption } from './list-option'
26
+ import { editorAria, type EditorAriaState } from './editor-contract'
26
27
 
27
28
  export type ListboxValue = string | number | Array<string | number> | null
28
29
 
@@ -35,6 +36,12 @@ export type ListboxConfig = {
35
36
  multiple?: () => boolean
36
37
  disabled?: () => boolean
37
38
  ariaLabel?: () => string | undefined
39
+ // Editor contract (ARIA + validation) - folded into rootProps().
40
+ id?: () => string | undefined
41
+ invalid?: () => boolean
42
+ required?: () => boolean
43
+ error?: () => string | undefined
44
+ hint?: () => string | undefined
38
45
  }
39
46
 
40
47
  export type OptionProps = {
@@ -51,10 +58,14 @@ export type OptionProps = {
51
58
 
52
59
  export type ListboxRootProps = {
53
60
  role: 'listbox'
61
+ id: string | undefined
54
62
  'aria-multiselectable': boolean
55
63
  'aria-label': string | undefined
56
64
  'aria-disabled': boolean
57
65
  'aria-activedescendant': string | undefined
66
+ 'aria-invalid': 'true' | undefined
67
+ 'aria-required': 'true' | undefined
68
+ 'aria-describedby': string | undefined
58
69
  tabindex: number
59
70
  onkeydown: (e: KeyboardEvent) => void
60
71
  }
@@ -136,6 +147,14 @@ export function createListbox(config: ListboxConfig): Listbox {
136
147
  const first = () => { active = enabledIdx[0] ?? 0 }
137
148
  const last = () => { active = enabledIdx.at(-1) ?? 0 }
138
149
 
150
+ // Type-ahead: typing a label prefix jumps the active highlight to it.
151
+ const typeahead = createTypeaheadBuffer()
152
+ function onTypeahead(char: string) {
153
+ const buffer = typeahead.push(char.toLowerCase())
154
+ const next = nextTypeaheadIndex(opts(), buffer, active)
155
+ if (next >= 0) active = next
156
+ }
157
+
139
158
  function onKeydown(e: KeyboardEvent) {
140
159
  if (disabled()) return
141
160
  switch (e.key) {
@@ -145,9 +164,20 @@ export function createListbox(config: ListboxConfig): Listbox {
145
164
  case 'End': e.preventDefault(); last(); break
146
165
  case ' ':
147
166
  case 'Enter': e.preventDefault(); pick(active); break
167
+ default: if (isTypeaheadKey(e)) onTypeahead(e.key)
148
168
  }
149
169
  }
150
170
 
171
+ // Editor-contract ARIA state, folded onto the focusable listbox root.
172
+ const ariaState = (): EditorAriaState => ({
173
+ id: config.id?.(),
174
+ invalid: config.invalid?.(),
175
+ required: config.required?.(),
176
+ error: config.error?.(),
177
+ hint: config.hint?.(),
178
+ ariaLabel: config.ariaLabel?.(),
179
+ })
180
+
151
181
  return {
152
182
  get activeIndex() { return active },
153
183
  get selectedValues() { return selected },
@@ -160,11 +190,13 @@ export function createListbox(config: ListboxConfig): Listbox {
160
190
  last,
161
191
  onKeydown,
162
192
  rootProps: () => ({
163
- role: 'listbox',
193
+ role: 'listbox' as const,
194
+ id: config.id?.(),
164
195
  'aria-multiselectable': multiple(),
165
- 'aria-label': config.ariaLabel?.(),
166
196
  'aria-disabled': disabled(),
167
197
  'aria-activedescendant': opts()[active] ? optionId(active) : undefined,
198
+ // aria-label + aria-invalid/required/describedby from the editor contract.
199
+ ...editorAria(ariaState()),
168
200
  tabindex: disabled() ? -1 : 0,
169
201
  onkeydown: onKeydown,
170
202
  }),
@@ -16,6 +16,7 @@
16
16
  * The styled <SvMaskedInput> is just one renderer over this core.
17
17
  */
18
18
  import { applyMask, unmask, isMaskComplete } from './datetime/mask'
19
+ import { editorAria, type EditorAriaState } from './editor-contract'
19
20
 
20
21
  /** Reactive inputs are passed as getters so the core tracks live prop changes. */
21
22
  export type MaskedInputConfig = {
@@ -26,6 +27,12 @@ export type MaskedInputConfig = {
26
27
  placeholder?: () => string | undefined
27
28
  disabled?: () => boolean
28
29
  readonly?: () => boolean
30
+ // Editor contract (ARIA + validation) - folded into inputProps().
31
+ id?: () => string | undefined
32
+ invalid?: () => boolean
33
+ required?: () => boolean
34
+ error?: () => string | undefined
35
+ hint?: () => string | undefined
29
36
  ariaLabel?: () => string | undefined
30
37
  }
31
38
 
@@ -58,6 +65,15 @@ export function createMaskedInput(config: MaskedInputConfig) {
58
65
  config.onChange?.(masked, raw, m ? isMaskComplete(masked, m) : true)
59
66
  }
60
67
 
68
+ const ariaState = (): EditorAriaState => ({
69
+ id: config.id?.(),
70
+ invalid: config.invalid?.(),
71
+ required: config.required?.(),
72
+ error: config.error?.(),
73
+ hint: config.hint?.(),
74
+ ariaLabel: config.ariaLabel?.(),
75
+ })
76
+
61
77
  return {
62
78
  /** The current masked display text. */
63
79
  get masked() { return text },
@@ -67,12 +83,13 @@ export function createMaskedInput(config: MaskedInputConfig) {
67
83
  get complete() { return mask() ? isMaskComplete(text, mask()) : true },
68
84
  /** Spread onto the text input element. */
69
85
  inputProps: () => ({
86
+ id: config.id?.(),
70
87
  value: text,
71
88
  type: 'text' as const,
72
89
  placeholder: config.placeholder?.() ?? mask(),
73
90
  disabled: disabled(),
74
91
  readonly: readonly(),
75
- 'aria-label': config.ariaLabel?.(),
92
+ ...editorAria(ariaState()),
76
93
  oninput: onInput,
77
94
  }),
78
95
  }
@@ -39,6 +39,7 @@ export type NumberInputConfig = {
39
39
  invalid?: () => boolean
40
40
  required?: () => boolean
41
41
  error?: () => string | undefined
42
+ hint?: () => string | undefined
42
43
  ariaLabel?: () => string | undefined
43
44
  }
44
45
 
@@ -138,6 +139,7 @@ export function createNumberInput(config: NumberInputConfig) {
138
139
  invalid: config.invalid?.(),
139
140
  required: config.required?.(),
140
141
  error: config.error?.(),
142
+ hint: config.hint?.(),
141
143
  ariaLabel: config.ariaLabel?.(),
142
144
  })
143
145