@svgrid/grid 1.2.33 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (490) hide show
  1. package/README.md +6 -5
  2. package/dist/FlexRender.svelte +96 -96
  3. package/dist/GridFooter.svelte +178 -178
  4. package/dist/SvAccordion.svelte +109 -0
  5. package/dist/SvAccordion.svelte.d.ts +28 -0
  6. package/dist/SvAlert.svelte +92 -0
  7. package/dist/SvAlert.svelte.d.ts +27 -0
  8. package/dist/SvAutoComplete.svelte +47 -18
  9. package/dist/SvAutoComplete.svelte.d.ts +2 -5
  10. package/dist/SvAvatar.svelte +75 -0
  11. package/dist/SvAvatar.svelte.d.ts +14 -0
  12. package/dist/SvAvatarGroup.svelte +48 -0
  13. package/dist/SvAvatarGroup.svelte.d.ts +16 -0
  14. package/dist/SvBadge.svelte +54 -0
  15. package/dist/SvBadge.svelte.d.ts +23 -0
  16. package/dist/SvBreadcrumb.svelte +88 -0
  17. package/dist/SvBreadcrumb.svelte.d.ts +23 -0
  18. package/dist/SvButtonGroup.svelte +121 -0
  19. package/dist/SvButtonGroup.svelte.d.ts +16 -0
  20. package/dist/SvCalendar.svelte +72 -9
  21. package/dist/SvCalendar.svelte.d.ts +34 -1
  22. package/dist/SvCard.svelte +71 -0
  23. package/dist/SvCard.svelte.d.ts +28 -0
  24. package/dist/SvCarousel.svelte +134 -0
  25. package/dist/SvCarousel.svelte.d.ts +28 -0
  26. package/dist/SvCheckBox.svelte +36 -21
  27. package/dist/SvCheckBox.svelte.d.ts +2 -5
  28. package/dist/SvChip.svelte +88 -0
  29. package/dist/SvChip.svelte.d.ts +31 -0
  30. package/dist/SvCircularProgress.svelte +109 -0
  31. package/dist/SvCircularProgress.svelte.d.ts +31 -0
  32. package/dist/SvColorInput.svelte +54 -27
  33. package/dist/SvColorInput.svelte.d.ts +10 -6
  34. package/dist/SvComboBox.svelte +119 -33
  35. package/dist/SvComboBox.svelte.d.ts +18 -6
  36. package/dist/SvCommand.svelte +197 -0
  37. package/dist/SvCommand.svelte.d.ts +26 -0
  38. package/dist/SvContextMenu.svelte +96 -0
  39. package/dist/SvContextMenu.svelte.d.ts +25 -0
  40. package/dist/SvCountryInput.svelte +56 -21
  41. package/dist/SvCountryInput.svelte.d.ts +9 -5
  42. package/dist/SvDateRangeInput.svelte +256 -0
  43. package/dist/SvDateRangeInput.svelte.d.ts +37 -0
  44. package/dist/SvDateTimePicker.svelte +57 -20
  45. package/dist/SvDateTimePicker.svelte.d.ts +12 -4
  46. package/dist/SvDivider.svelte +61 -0
  47. package/dist/SvDivider.svelte.d.ts +22 -0
  48. package/dist/SvDrawer.svelte +226 -0
  49. package/dist/SvDrawer.svelte.d.ts +43 -0
  50. package/dist/SvDropDownList.svelte +114 -35
  51. package/dist/SvDropDownList.svelte.d.ts +7 -6
  52. package/dist/SvDurationInput.svelte +108 -0
  53. package/dist/SvDurationInput.svelte.d.ts +15 -0
  54. package/dist/SvEmptyState.svelte +59 -0
  55. package/dist/SvEmptyState.svelte.d.ts +25 -0
  56. package/dist/SvField.svelte +85 -0
  57. package/dist/SvField.svelte.d.ts +30 -0
  58. package/dist/SvFileUpload.svelte +169 -0
  59. package/dist/SvFileUpload.svelte.d.ts +24 -0
  60. package/dist/SvForm.svelte +7 -1
  61. package/dist/SvForm.svelte.d.ts +3 -0
  62. package/dist/SvGauge.svelte +7 -1
  63. package/dist/SvGauge.svelte.d.ts +11 -0
  64. package/dist/SvGrid.controller.svelte.d.ts +6 -0
  65. package/dist/SvGrid.controller.svelte.js +64 -9
  66. package/dist/SvGrid.css +2376 -2343
  67. package/dist/SvGrid.svelte +2764 -2651
  68. package/dist/SvGrid.types.d.ts +366 -0
  69. package/dist/SvGridBoard.svelte +2281 -0
  70. package/dist/SvGridBoard.svelte.d.ts +31 -0
  71. package/dist/SvGridDropdown.svelte +666 -665
  72. package/dist/SvGridSelect.svelte +255 -0
  73. package/dist/SvGridSelect.svelte.d.ts +32 -0
  74. package/dist/SvGroupCell.svelte +109 -0
  75. package/dist/SvGroupCell.svelte.d.ts +41 -0
  76. package/dist/SvListBox.svelte +123 -36
  77. package/dist/SvListBox.svelte.d.ts +12 -8
  78. package/dist/SvMaskedInput.svelte +53 -13
  79. package/dist/SvMaskedInput.svelte.d.ts +16 -6
  80. package/dist/SvMenu.svelte +101 -0
  81. package/dist/SvMenu.svelte.d.ts +26 -0
  82. package/dist/SvMenuList.svelte +143 -0
  83. package/dist/SvMenuList.svelte.d.ts +24 -0
  84. package/dist/SvModal.svelte +220 -0
  85. package/dist/SvModal.svelte.d.ts +31 -0
  86. package/dist/SvMultiSelect.svelte +277 -0
  87. package/dist/SvMultiSelect.svelte.d.ts +31 -0
  88. package/dist/SvNavPane.svelte +314 -0
  89. package/dist/SvNavPane.svelte.d.ts +59 -0
  90. package/dist/SvNumberInput.svelte +23 -9
  91. package/dist/SvNumberInput.svelte.d.ts +2 -0
  92. package/dist/SvOtpInput.svelte +157 -0
  93. package/dist/SvOtpInput.svelte.d.ts +16 -0
  94. package/dist/SvPagination.svelte +100 -0
  95. package/dist/SvPagination.svelte.d.ts +16 -0
  96. package/dist/SvPasswordInput.svelte +42 -12
  97. package/dist/SvPasswordInput.svelte.d.ts +13 -6
  98. package/dist/SvPhoneInput.svelte +43 -11
  99. package/dist/SvPhoneInput.svelte.d.ts +10 -11
  100. package/dist/SvPopover.svelte +127 -0
  101. package/dist/SvPopover.svelte.d.ts +35 -0
  102. package/dist/SvProgress.svelte +116 -0
  103. package/dist/SvProgress.svelte.d.ts +32 -0
  104. package/dist/SvRadioGroup.svelte +40 -22
  105. package/dist/SvRadioGroup.svelte.d.ts +2 -10
  106. package/dist/SvRating.svelte +51 -30
  107. package/dist/SvRating.svelte.d.ts +10 -7
  108. package/dist/SvRichText.svelte +192 -0
  109. package/dist/SvRichText.svelte.d.ts +16 -0
  110. package/dist/SvRowGroupPanel.svelte +147 -0
  111. package/dist/SvRowGroupPanel.svelte.d.ts +17 -0
  112. package/dist/SvScrollArea.svelte +50 -0
  113. package/dist/SvScrollArea.svelte.d.ts +22 -0
  114. package/dist/SvSkeleton.svelte +76 -0
  115. package/dist/SvSkeleton.svelte.d.ts +25 -0
  116. package/dist/SvSlider.svelte +41 -9
  117. package/dist/SvSlider.svelte.d.ts +4 -4
  118. package/dist/SvSparkline.svelte +77 -0
  119. package/dist/SvSparkline.svelte.d.ts +28 -0
  120. package/dist/SvSplitter.svelte +138 -0
  121. package/dist/SvSplitter.svelte.d.ts +32 -0
  122. package/dist/SvStat.svelte +86 -0
  123. package/dist/SvStat.svelte.d.ts +30 -0
  124. package/dist/SvStepper.svelte +91 -0
  125. package/dist/SvStepper.svelte.d.ts +24 -0
  126. package/dist/SvSwitchButton.svelte +39 -24
  127. package/dist/SvSwitchButton.svelte.d.ts +2 -5
  128. package/dist/SvTabs.svelte +147 -28
  129. package/dist/SvTabs.svelte.d.ts +16 -4
  130. package/dist/SvTagsInput.svelte +59 -22
  131. package/dist/SvTagsInput.svelte.d.ts +9 -4
  132. package/dist/SvTextArea.svelte +113 -0
  133. package/dist/SvTextArea.svelte.d.ts +18 -0
  134. package/dist/SvTextInput.svelte +115 -0
  135. package/dist/SvTextInput.svelte.d.ts +20 -0
  136. package/dist/SvTimePicker.svelte +19 -5
  137. package/dist/SvTimePicker.svelte.d.ts +12 -0
  138. package/dist/SvTimeline.svelte +75 -0
  139. package/dist/SvTimeline.svelte.d.ts +24 -0
  140. package/dist/SvToaster.svelte +124 -0
  141. package/dist/SvToaster.svelte.d.ts +10 -0
  142. package/dist/SvToggleButton.svelte +37 -12
  143. package/dist/SvToggleButton.svelte.d.ts +2 -4
  144. package/dist/SvTooltip.svelte +113 -0
  145. package/dist/SvTooltip.svelte.d.ts +22 -0
  146. package/dist/SvTour.svelte +151 -0
  147. package/dist/SvTour.svelte.d.ts +25 -0
  148. package/dist/SvTree.svelte +286 -44
  149. package/dist/SvTree.svelte.d.ts +26 -9
  150. package/dist/SvTreeSelect.svelte +222 -0
  151. package/dist/SvTreeSelect.svelte.d.ts +24 -0
  152. package/dist/a11y/dismissable.d.ts +32 -0
  153. package/dist/a11y/dismissable.js +54 -0
  154. package/dist/a11y/focus-trap.d.ts +39 -0
  155. package/dist/a11y/focus-trap.js +142 -0
  156. package/dist/a11y/live-region.d.ts +8 -0
  157. package/dist/a11y/live-region.js +60 -0
  158. package/dist/a11y/scroll-lock.d.ts +4 -0
  159. package/dist/a11y/scroll-lock.js +41 -0
  160. package/dist/a11y.d.ts +3 -1
  161. package/dist/a11y.js +1 -1
  162. package/dist/avatar.d.ts +10 -0
  163. package/dist/avatar.js +22 -0
  164. package/dist/builtin-editors.d.ts +5 -0
  165. package/dist/builtin-editors.js +55 -0
  166. package/dist/cdn/svgrid.js +16022 -8235
  167. package/dist/cdn/svgrid.svelte-external.js +14543 -6765
  168. package/dist/columns.js +3 -2
  169. package/dist/core.d.ts +7 -1
  170. package/dist/countries.d.ts +12 -0
  171. package/dist/countries.js +24 -0
  172. package/dist/createAccordion.svelte.d.ts +71 -0
  173. package/dist/createAccordion.svelte.js +99 -0
  174. package/dist/createAutocomplete.svelte.d.ts +13 -4
  175. package/dist/createAutocomplete.svelte.js +11 -1
  176. package/dist/createButtonGroup.svelte.d.ts +62 -0
  177. package/dist/createButtonGroup.svelte.js +154 -0
  178. package/dist/createCalendar.svelte.d.ts +5 -0
  179. package/dist/createCalendar.svelte.js +2 -0
  180. package/dist/createCheckbox.svelte.d.ts +9 -17
  181. package/dist/createCheckbox.svelte.js +28 -1
  182. package/dist/createColorInput.svelte.d.ts +12 -22
  183. package/dist/createColorInput.svelte.js +13 -0
  184. package/dist/createCombobox.svelte.d.ts +16 -5
  185. package/dist/createCombobox.svelte.js +13 -2
  186. package/dist/createCountryInput.svelte.d.ts +14 -3
  187. package/dist/createCountryInput.svelte.js +12 -2
  188. package/dist/createDateTimePicker.svelte.d.ts +17 -2
  189. package/dist/createDateTimePicker.svelte.js +12 -2
  190. package/dist/createDropdownList.svelte.d.ts +14 -5
  191. package/dist/createDropdownList.svelte.js +50 -1
  192. package/dist/createFileUpload.svelte.d.ts +68 -0
  193. package/dist/createFileUpload.svelte.js +122 -0
  194. package/dist/createListbox.svelte.d.ts +10 -1
  195. package/dist/createListbox.svelte.js +48 -1
  196. package/dist/createMaskedInput.svelte.d.ts +11 -2
  197. package/dist/createMaskedInput.svelte.js +11 -1
  198. package/dist/createNumberInput.svelte.d.ts +1 -0
  199. package/dist/createNumberInput.svelte.js +1 -0
  200. package/dist/createPasswordInput.svelte.d.ts +19 -21
  201. package/dist/createPasswordInput.svelte.js +16 -4
  202. package/dist/createPhoneInput.svelte.d.ts +26 -7
  203. package/dist/createPhoneInput.svelte.js +24 -3
  204. package/dist/createPopoverSelect.svelte.d.ts +86 -0
  205. package/dist/createPopoverSelect.svelte.js +196 -0
  206. package/dist/createRadioGroup.svelte.d.ts +9 -0
  207. package/dist/createRadioGroup.svelte.js +11 -1
  208. package/dist/createRating.svelte.d.ts +11 -0
  209. package/dist/createRating.svelte.js +12 -1
  210. package/dist/createSlider.svelte.d.ts +2 -0
  211. package/dist/createSlider.svelte.js +16 -5
  212. package/dist/createSplitter.svelte.d.ts +67 -0
  213. package/dist/createSplitter.svelte.js +99 -0
  214. package/dist/createSwitch.svelte.d.ts +9 -16
  215. package/dist/createSwitch.svelte.js +28 -1
  216. package/dist/createTabs.svelte.d.ts +7 -0
  217. package/dist/createTabs.svelte.js +17 -2
  218. package/dist/createTagsInput.svelte.d.ts +13 -1
  219. package/dist/createTagsInput.svelte.js +12 -3
  220. package/dist/createToggle.svelte.d.ts +9 -0
  221. package/dist/createToggle.svelte.js +12 -1
  222. package/dist/createTree.svelte.d.ts +19 -0
  223. package/dist/createTree.svelte.js +108 -15
  224. package/dist/debounce.d.ts +11 -0
  225. package/dist/debounce.js +30 -0
  226. package/dist/duration.d.ts +15 -0
  227. package/dist/duration.js +51 -0
  228. package/dist/editor-contract.d.ts +62 -7
  229. package/dist/editor-contract.js +45 -5
  230. package/dist/editor-registry.d.ts +67 -0
  231. package/dist/editor-registry.js +38 -0
  232. package/dist/editors/cell-editors.d.ts +1 -1
  233. package/dist/editors/cell-editors.js +5 -0
  234. package/dist/fuzzy.d.ts +3 -0
  235. package/dist/fuzzy.js +41 -0
  236. package/dist/grid-select.d.ts +9 -0
  237. package/dist/grid-select.js +8 -0
  238. package/dist/index.d.ts +79 -6
  239. package/dist/index.js +78 -4
  240. package/dist/keyboard-handlers.js +19 -0
  241. package/dist/list-option.d.ts +36 -0
  242. package/dist/list-option.js +68 -0
  243. package/dist/otp.d.ts +16 -0
  244. package/dist/otp.js +26 -0
  245. package/dist/paginate.d.ts +20 -0
  246. package/dist/paginate.js +40 -0
  247. package/dist/popover.d.ts +15 -1
  248. package/dist/popover.js +29 -3
  249. package/dist/recurrence.d.ts +36 -0
  250. package/dist/recurrence.js +90 -0
  251. package/dist/server-data-source.d.ts +89 -0
  252. package/dist/server-data-source.js +4 -0
  253. package/dist/server-group-model.d.ts +98 -0
  254. package/dist/server-group-model.js +263 -0
  255. package/dist/toast-store.svelte.d.ts +41 -0
  256. package/dist/toast-store.svelte.js +113 -0
  257. package/dist/tree-select.d.ts +19 -0
  258. package/dist/tree-select.js +40 -0
  259. package/dist/ui-app.types.d.ts +80 -0
  260. package/dist/ui-app.types.js +7 -0
  261. package/dist/validators.d.ts +43 -0
  262. package/dist/validators.js +53 -0
  263. package/dist/virtual.d.ts +35 -0
  264. package/dist/virtual.js +34 -0
  265. package/package.json +85 -85
  266. package/src/FlexRender.svelte +96 -96
  267. package/src/GridFooter.svelte +178 -178
  268. package/src/SvAccordion.svelte +109 -0
  269. package/src/SvAccordion.test.ts +66 -0
  270. package/src/SvAlert.svelte +92 -0
  271. package/src/SvAutoComplete.svelte +47 -18
  272. package/src/SvAvatar.svelte +75 -0
  273. package/src/SvAvatarGroup.svelte +48 -0
  274. package/src/SvBadge.svelte +54 -0
  275. package/src/SvBreadcrumb.svelte +88 -0
  276. package/src/SvButtonGroup.svelte +121 -0
  277. package/src/SvButtonGroup.test.ts +72 -0
  278. package/src/SvCalendar.svelte +72 -9
  279. package/src/SvCard.svelte +71 -0
  280. package/src/SvCarousel.svelte +134 -0
  281. package/src/SvCheckBox.svelte +36 -21
  282. package/src/SvChip.svelte +88 -0
  283. package/src/SvCircularProgress.svelte +109 -0
  284. package/src/SvColorInput.svelte +54 -27
  285. package/src/SvComboBox.svelte +119 -33
  286. package/src/SvCommand.svelte +197 -0
  287. package/src/SvContextMenu.svelte +96 -0
  288. package/src/SvCountryInput.svelte +56 -21
  289. package/src/SvDateRangeInput.svelte +256 -0
  290. package/src/SvDateRangeInput.test.ts +80 -0
  291. package/src/SvDateTimePicker.svelte +57 -20
  292. package/src/SvDivider.svelte +61 -0
  293. package/src/SvDrawer.svelte +226 -0
  294. package/src/SvDrawer.test.ts +125 -0
  295. package/src/SvDropDownList.svelte +114 -35
  296. package/src/SvDurationInput.svelte +108 -0
  297. package/src/SvEmptyState.svelte +59 -0
  298. package/src/SvField.svelte +85 -0
  299. package/src/SvFileUpload.svelte +169 -0
  300. package/src/SvFileUpload.test.ts +59 -0
  301. package/src/SvForm.svelte +7 -1
  302. package/src/SvGauge.svelte +7 -1
  303. package/src/SvGrid.controller.svelte.ts +2553 -2494
  304. package/src/SvGrid.css +2376 -2343
  305. package/src/SvGrid.svelte +2764 -2651
  306. package/src/SvGrid.types.ts +944 -570
  307. package/src/SvGridBoard.svelte +2281 -0
  308. package/src/SvGridDropdown.svelte +666 -665
  309. package/src/SvGridSelect.svelte +255 -0
  310. package/src/SvGridSelect.test.ts +100 -0
  311. package/src/SvGroupCell.svelte +109 -0
  312. package/src/SvListBox.svelte +123 -36
  313. package/src/SvMaskedInput.svelte +53 -13
  314. package/src/SvMenu.svelte +101 -0
  315. package/src/SvMenu.test.ts +73 -0
  316. package/src/SvMenuList.svelte +143 -0
  317. package/src/SvModal.svelte +220 -0
  318. package/src/SvMultiSelect.svelte +277 -0
  319. package/src/SvMultiSelect.test.ts +167 -0
  320. package/src/SvNavPane.svelte +314 -0
  321. package/src/SvNavPane.test.ts +70 -0
  322. package/src/SvNumberInput.svelte +23 -9
  323. package/src/SvOtpInput.svelte +157 -0
  324. package/src/SvPagination.svelte +100 -0
  325. package/src/SvPasswordInput.svelte +42 -12
  326. package/src/SvPhoneInput.svelte +43 -11
  327. package/src/SvPopover.svelte +127 -0
  328. package/src/SvProgress.svelte +116 -0
  329. package/src/SvRadioGroup.svelte +40 -22
  330. package/src/SvRating.svelte +51 -30
  331. package/src/SvRichText.svelte +192 -0
  332. package/src/SvRowGroupPanel.svelte +147 -0
  333. package/src/SvScrollArea.svelte +50 -0
  334. package/src/SvSkeleton.svelte +76 -0
  335. package/src/SvSlider.svelte +41 -9
  336. package/src/SvSparkline.svelte +77 -0
  337. package/src/SvSplitter.svelte +138 -0
  338. package/src/SvSplitter.test.ts +55 -0
  339. package/src/SvStat.svelte +86 -0
  340. package/src/SvStepper.svelte +91 -0
  341. package/src/SvSwitchButton.svelte +39 -24
  342. package/src/SvTabs.svelte +147 -28
  343. package/src/SvTagsInput.svelte +59 -22
  344. package/src/SvTextArea.svelte +113 -0
  345. package/src/SvTextInput.svelte +115 -0
  346. package/src/SvTimePicker.svelte +19 -5
  347. package/src/SvTimeline.svelte +75 -0
  348. package/src/SvToaster.svelte +124 -0
  349. package/src/SvToaster.test.ts +63 -0
  350. package/src/SvToggleButton.svelte +37 -12
  351. package/src/SvTooltip.svelte +113 -0
  352. package/src/SvTour.svelte +151 -0
  353. package/src/SvTree.svelte +286 -44
  354. package/src/SvTreeSelect.svelte +222 -0
  355. package/src/SvTreeSelect.test.ts +87 -0
  356. package/src/a11y/dismissable.test.ts +95 -0
  357. package/src/a11y/dismissable.ts +94 -0
  358. package/src/a11y/focus-trap.test.ts +110 -0
  359. package/src/a11y/focus-trap.ts +134 -0
  360. package/src/a11y/live-region.test.ts +40 -0
  361. package/src/a11y/live-region.ts +65 -0
  362. package/src/a11y/scroll-lock.test.ts +38 -0
  363. package/src/a11y/scroll-lock.ts +41 -0
  364. package/src/a11y.contract.test.ts +49 -49
  365. package/src/a11y.test.ts +59 -59
  366. package/src/a11y.ts +61 -59
  367. package/src/avatar.test.ts +32 -0
  368. package/src/avatar.ts +21 -0
  369. package/src/board.test.ts +927 -0
  370. package/src/build-api.ts +798 -798
  371. package/src/builtin-editors.grid.test.ts +85 -0
  372. package/src/builtin-editors.test.ts +58 -0
  373. package/src/builtin-editors.ts +56 -0
  374. package/src/cell-formatting.ts +169 -169
  375. package/src/cell-render.ts +469 -469
  376. package/src/collaboration.test.ts +104 -104
  377. package/src/collaboration.ts +167 -167
  378. package/src/columns.ts +3 -2
  379. package/src/core.performance.test.ts +30 -30
  380. package/src/core.ts +1111 -1101
  381. package/src/countries.ts +25 -0
  382. package/src/createAccordion.svelte.ts +126 -0
  383. package/src/createAutocomplete.svelte.ts +18 -1
  384. package/src/createButtonGroup.svelte.ts +166 -0
  385. package/src/createCalendar.svelte.ts +6 -0
  386. package/src/createCheckbox.svelte.ts +23 -1
  387. package/src/createColorInput.svelte.ts +20 -0
  388. package/src/createCombobox.svelte.ts +22 -2
  389. package/src/createCountryInput.svelte.ts +21 -2
  390. package/src/createDateTimePicker.svelte.ts +23 -2
  391. package/src/createDropdownList.svelte.ts +31 -2
  392. package/src/createFileUpload.svelte.ts +144 -0
  393. package/src/createGrid.svelte.ts +42 -42
  394. package/src/createGrid.test.ts +10 -10
  395. package/src/createGridState.svelte.ts +17 -17
  396. package/src/createListbox.svelte.ts +35 -3
  397. package/src/createMaskedInput.svelte.ts +18 -1
  398. package/src/createNumberInput.svelte.ts +2 -0
  399. package/src/createPasswordInput.svelte.ts +31 -5
  400. package/src/createPhoneInput.svelte.ts +38 -4
  401. package/src/createPopoverSelect.svelte.ts +208 -0
  402. package/src/createRadioGroup.svelte.ts +23 -1
  403. package/src/createRating.svelte.ts +26 -1
  404. package/src/createSlider.svelte.ts +14 -3
  405. package/src/createSplitter.svelte.ts +119 -0
  406. package/src/createSwitch.svelte.ts +26 -3
  407. package/src/createTabs.svelte.ts +20 -3
  408. package/src/createTagsInput.svelte.ts +24 -3
  409. package/src/createToggle.svelte.ts +25 -2
  410. package/src/createTree.svelte.ts +104 -14
  411. package/src/debounce.test.ts +50 -0
  412. package/src/debounce.ts +41 -0
  413. package/src/duration.test.ts +41 -0
  414. package/src/duration.ts +51 -0
  415. package/src/editing.test.ts +859 -859
  416. package/src/editing.ts +675 -675
  417. package/src/editor-contract.ts +87 -8
  418. package/src/editor-registry.grid.test.ts +98 -0
  419. package/src/editor-registry.test.ts +86 -0
  420. package/src/editor-registry.ts +106 -0
  421. package/src/editors/cell-editors.ts +10 -1
  422. package/src/export-data-api.test.ts +126 -126
  423. package/src/export-format.test.ts +107 -107
  424. package/src/export-format.ts +598 -598
  425. package/src/flex-render.ts +3 -3
  426. package/src/fuzzy.test.ts +40 -0
  427. package/src/fuzzy.ts +39 -0
  428. package/src/grid-select.test.ts +28 -0
  429. package/src/grid-select.ts +20 -0
  430. package/src/index.ts +463 -318
  431. package/src/keyboard-handlers.ts +20 -0
  432. package/src/keyboard.test.ts +59 -59
  433. package/src/keyboard.ts +97 -97
  434. package/src/list-option.ts +74 -0
  435. package/src/list-power.test.ts +71 -0
  436. package/src/menus.ts +582 -582
  437. package/src/merge-objects.ts +48 -48
  438. package/src/otp.test.ts +28 -0
  439. package/src/otp.ts +28 -0
  440. package/src/overlays.test.ts +85 -0
  441. package/src/paginate.test.ts +41 -0
  442. package/src/paginate.ts +61 -0
  443. package/src/phone-valid.test.ts +26 -0
  444. package/src/popover.ts +31 -3
  445. package/src/progress.test.ts +55 -0
  446. package/src/recurrence.test.ts +91 -0
  447. package/src/recurrence.ts +111 -0
  448. package/src/render-component.ts +28 -28
  449. package/src/selection.test.ts +754 -754
  450. package/src/selection.ts +600 -600
  451. package/src/server-data-source.test.ts +289 -289
  452. package/src/server-data-source.ts +413 -311
  453. package/src/server-group-model.test.ts +294 -0
  454. package/src/server-group-model.ts +370 -0
  455. package/src/sparkline.test.ts +68 -68
  456. package/src/sparkline.ts +169 -169
  457. package/src/spreadsheet.test.ts +489 -489
  458. package/src/spreadsheet.ts +304 -304
  459. package/src/static-functions.ts +11 -11
  460. package/src/subscribe.ts +38 -38
  461. package/src/svgrid-wrapper.types.ts +439 -439
  462. package/src/svgrid.behavior.test.ts +706 -706
  463. package/src/svgrid.features.test.ts +157 -157
  464. package/src/svgrid.new-features.wrapper.test.ts +251 -251
  465. package/src/svgrid.responsive.test.ts +72 -0
  466. package/src/svgrid.wrapper.test.ts +40 -40
  467. package/src/toast-store.svelte.ts +148 -0
  468. package/src/toast-store.test.ts +86 -0
  469. package/src/tree-edit.test.ts +49 -0
  470. package/src/tree-filter.test.ts +56 -0
  471. package/src/tree-select.test.ts +48 -0
  472. package/src/tree-select.ts +62 -0
  473. package/src/ui-app.test.ts +130 -0
  474. package/src/ui-app.types.ts +88 -0
  475. package/src/ui-display.test.ts +79 -0
  476. package/src/ui-inputs.test.ts +1 -1
  477. package/src/ui-status.test.ts +141 -0
  478. package/src/ui-tier1.test.ts +142 -0
  479. package/src/ui-tier2.test.ts +124 -0
  480. package/src/ui-tier3.test.ts +139 -0
  481. package/src/validators.test.ts +58 -0
  482. package/src/validators.ts +105 -0
  483. package/src/virtual.test.ts +72 -0
  484. package/src/virtual.ts +67 -0
  485. package/src/virtualization/column-virtualizer.test.ts +27 -27
  486. package/src/virtualization/column-virtualizer.ts +30 -30
  487. package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
  488. package/src/virtualization/types.ts +30 -30
  489. package/src/virtualization/virtualizer.test.ts +47 -47
  490. package/src/virtualization/virtualizer.ts +296 -296
@@ -7,24 +7,43 @@
7
7
  * One styled renderer over the headless `createAutocomplete` core; only
8
8
  * portal/measure render concerns live here.
9
9
  */
10
- import { anchoredRect, portalToBody, type AnchoredRect } from './popover'
10
+ import { anchoredRect, portalToBody, popIn, type AnchoredRect } from './popover'
11
+ import { createDismissableLayer } from './a11y/dismissable'
11
12
  import { type ListOption } from './list-option'
13
+ import SvField from './SvField.svelte'
14
+ import { nextEditorId, type SvEditorProps } from './editor-contract'
12
15
  import { createAutocomplete } from './createAutocomplete.svelte'
13
16
 
14
- type Props = {
17
+ type Props = SvEditorProps & {
15
18
  value?: string
16
19
  onChange?: (value: string) => void
17
20
  /** Suggestions - strings or {value,label} (label is shown, value inserted). */
18
21
  suggestions?: ReadonlyArray<string | ListOption>
19
22
  minChars?: number
20
23
  placeholder?: string
21
- disabled?: boolean
22
- name?: string
23
- size?: 'sm' | 'md' | 'lg'
24
- ariaLabel?: string
25
24
  }
26
25
 
27
- let { value = '', onChange, suggestions = [], minChars = 1, placeholder, disabled = false, name, size = 'md', ariaLabel }: Props = $props()
26
+ let {
27
+ value = '',
28
+ onChange,
29
+ suggestions = [],
30
+ minChars = 1,
31
+ placeholder,
32
+ disabled = false,
33
+ name,
34
+ size = 'md',
35
+ ariaLabel,
36
+ invalid = false,
37
+ required = false,
38
+ error,
39
+ label,
40
+ hint,
41
+ dir,
42
+ id,
43
+ }: Props = $props()
44
+
45
+ const autoId = nextEditorId('sv-ac')
46
+ const uid = $derived(id ?? autoId)
28
47
 
29
48
  let fieldEl = $state<HTMLInputElement | null>(null)
30
49
  let panelEl = $state<HTMLDivElement | null>(null)
@@ -38,6 +57,11 @@
38
57
  disabled: () => disabled,
39
58
  ariaLabel: () => ariaLabel,
40
59
  focusInput: () => fieldEl?.focus(),
60
+ id: () => uid,
61
+ invalid: () => invalid,
62
+ required: () => required,
63
+ error: () => error,
64
+ hint: () => hint,
41
65
  })
42
66
 
43
67
  function updatePos() {
@@ -50,22 +74,25 @@
50
74
  updatePos()
51
75
  const rp = () => updatePos()
52
76
  window.addEventListener('scroll', rp, true); window.addEventListener('resize', rp)
53
- const od = (e: PointerEvent) => { const t = e.target as Node | null; if (t && (fieldEl?.contains(t) || panelEl?.contains(t))) return; ac.close() }
54
- document.addEventListener('pointerdown', od, true)
55
- return () => { window.removeEventListener('scroll', rp, true); window.removeEventListener('resize', rp); document.removeEventListener('pointerdown', od, true) }
77
+ const layer = createDismissableLayer({ element: () => [fieldEl, panelEl], onDismiss: () => ac.close(), closeOnEscape: false })
78
+ layer.activate()
79
+ return () => { window.removeEventListener('scroll', rp, true); window.removeEventListener('resize', rp); layer.release() }
56
80
  })
57
81
  </script>
58
82
 
59
- <input
60
- bind:this={fieldEl}
61
- class="sv-ac sv-ac--{size}"
62
- type="text"
63
- {placeholder}
64
- {...ac.inputProps()}
65
- />
83
+ <SvField id={uid} {label} {hint} {error} {required} {dir}>
84
+ <input
85
+ bind:this={fieldEl}
86
+ class="sv-ac sv-ac--{size}"
87
+ class:is-invalid={invalid}
88
+ type="text"
89
+ {placeholder}
90
+ {...ac.inputProps()}
91
+ />
92
+ </SvField>
66
93
 
67
94
  {#if ac.open}
68
- <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`} {...ac.listboxProps()}>
95
+ <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`} {...ac.listboxProps()}>
69
96
  {#each ac.filtered as opt, i (opt.value)}
70
97
  <!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
71
98
  <div class="sv-ddl__opt" class:is-active={ac.isActive(i)} {...ac.optionProps(i)}>{opt.label}</div>
@@ -86,4 +113,6 @@
86
113
  .sv-ac--md { height: 34px; font-size: 13px; }
87
114
  .sv-ac--lg { height: 40px; font-size: 15px; }
88
115
  .sv-ac:focus { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
116
+ .sv-ac.is-invalid { border-color: var(--sg-danger, #dc2626); }
117
+ .sv-ac.is-invalid:focus { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
89
118
  </style>
@@ -0,0 +1,75 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SvAvatar - a user/entity avatar: an image with an initials + colour-hash
4
+ * fallback (used when there is no `src` or the image fails to load), plus an
5
+ * optional presence status dot. Parity: Smart avatar.
6
+ *
7
+ * ```svelte
8
+ * <SvAvatar name="Ada Lovelace" src="/ada.jpg" status="online" />
9
+ * ```
10
+ */
11
+ import { avatarInitials, avatarColorHue } from './avatar'
12
+
13
+ type Props = {
14
+ name?: string
15
+ src?: string
16
+ alt?: string
17
+ /** Preset (sm 28 / md 36 / lg 48) or an explicit pixel size. */
18
+ size?: 'sm' | 'md' | 'lg' | number
19
+ shape?: 'circle' | 'square'
20
+ status?: 'online' | 'offline' | 'busy' | 'away'
21
+ /** Override the hash-derived fallback colour (any CSS colour). */
22
+ color?: string
23
+ }
24
+
25
+ let { name = '', src, alt, size = 'md', shape = 'circle', status, color }: Props = $props()
26
+
27
+ const px = $derived(typeof size === 'number' ? size : size === 'sm' ? 28 : size === 'lg' ? 48 : 36)
28
+ const initials = $derived(avatarInitials(name))
29
+ const bg = $derived(color ?? `hsl(${avatarColorHue(name || '?')} 62% 45%)`)
30
+ // Fall back to initials when the image errors.
31
+ let broken = $state(false)
32
+ const showImg = $derived(!!src && !broken)
33
+ </script>
34
+
35
+ <span
36
+ class="sv-avatar sv-avatar--{shape}"
37
+ style:width={`${px}px`}
38
+ style:height={`${px}px`}
39
+ style:font-size={`${Math.round(px * 0.4)}px`}
40
+ style:background={showImg ? undefined : bg}
41
+ role="img"
42
+ aria-label={alt ?? (name || 'avatar')}
43
+ title={name || undefined}
44
+ >
45
+ {#if showImg}
46
+ <img class="sv-avatar__img" src={src} alt={alt ?? name} onerror={() => (broken = true)} />
47
+ {:else if initials}
48
+ <span class="sv-avatar__initials" aria-hidden="true">{initials}</span>
49
+ {:else}
50
+ <svg class="sv-avatar__ph" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 12a5 5 0 1 0 0-10 5 5 0 0 0 0 10Zm0 2c-4.4 0-8 2.5-8 5.5V22h16v-2.5C20 16.5 16.4 14 12 14Z" /></svg>
51
+ {/if}
52
+ {#if status}<span class="sv-avatar__status is-{status}" aria-hidden="true"></span>{/if}
53
+ </span>
54
+
55
+ <style>
56
+ .sv-avatar {
57
+ position: relative; display: inline-grid; place-items: center; overflow: visible;
58
+ color: #fff; font-weight: 600; line-height: 1; user-select: none; flex: none;
59
+ vertical-align: middle;
60
+ }
61
+ .sv-avatar--circle { border-radius: 50%; }
62
+ .sv-avatar--square { border-radius: 22%; }
63
+ .sv-avatar__img { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; display: block; }
64
+ .sv-avatar--circle .sv-avatar__img, .sv-avatar--circle { }
65
+ .sv-avatar__initials { letter-spacing: 0.02em; }
66
+ .sv-avatar__ph { width: 62%; height: 62%; opacity: 0.9; }
67
+ .sv-avatar__status {
68
+ position: absolute; right: -1px; bottom: -1px; width: 28%; height: 28%; min-width: 8px; min-height: 8px;
69
+ border-radius: 50%; border: 2px solid var(--sg-bg, #fff);
70
+ }
71
+ .sv-avatar__status.is-online { background: var(--sg-success, #16a34a); }
72
+ .sv-avatar__status.is-offline { background: var(--sg-muted, #94a3b8); }
73
+ .sv-avatar__status.is-busy { background: var(--sg-danger, #dc2626); }
74
+ .sv-avatar__status.is-away { background: var(--sg-warning, #f59e0b); }
75
+ </style>
@@ -0,0 +1,48 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SvAvatarGroup - overlapping stacked avatars with a "+N" overflow pill, for
4
+ * "assigned to" / "members" rows. Each entry is a set of SvAvatar props.
5
+ *
6
+ * ```svelte
7
+ * <SvAvatarGroup max={3} avatars={[{ name: 'Ada' }, { name: 'Alan' }, { name: 'Grace' }, { name: 'Ken' }]} />
8
+ * ```
9
+ */
10
+ import SvAvatar from './SvAvatar.svelte'
11
+
12
+ type AvatarProps = { name?: string; src?: string; alt?: string; color?: string }
13
+ type Size = 'sm' | 'md' | 'lg' | number
14
+
15
+ type Props = {
16
+ avatars: ReadonlyArray<AvatarProps>
17
+ /** Max avatars shown before collapsing to "+N". */
18
+ max?: number
19
+ size?: Size
20
+ }
21
+
22
+ let { avatars, max = 4, size = 'md' }: Props = $props()
23
+
24
+ const shown = $derived(avatars.slice(0, max))
25
+ const overflow = $derived(Math.max(0, avatars.length - max))
26
+ const px = $derived(typeof size === 'number' ? size : size === 'sm' ? 28 : size === 'lg' ? 48 : 36)
27
+ const overlap = $derived(Math.round(px * 0.28))
28
+ </script>
29
+
30
+ <div class="sv-avgroup" role="group" style:--ov={`${overlap}px`}>
31
+ {#each shown as a, i (i)}
32
+ <span class="sv-avgroup__it" style:z-index={shown.length - i}><SvAvatar {...a} {size} /></span>
33
+ {/each}
34
+ {#if overflow}
35
+ <span class="sv-avgroup__more" style:width={`${px}px`} style:height={`${px}px`} style:font-size={`${Math.round(px * 0.36)}px`}>+{overflow}</span>
36
+ {/if}
37
+ </div>
38
+
39
+ <style>
40
+ .sv-avgroup { display: inline-flex; align-items: center; }
41
+ .sv-avgroup__it { display: inline-flex; margin-inline-start: calc(-1 * var(--ov, 10px)); border-radius: 50%; box-shadow: 0 0 0 2px var(--sg-bg, #fff); }
42
+ .sv-avgroup__it:first-child { margin-inline-start: 0; }
43
+ .sv-avgroup__more {
44
+ display: grid; place-items: center; margin-inline-start: calc(-1 * var(--ov, 10px)); border-radius: 50%;
45
+ background: var(--sg-row-hover-bg, #e2e8f0); color: var(--sg-muted, #64748b); font-weight: 650;
46
+ box-shadow: 0 0 0 2px var(--sg-bg, #fff);
47
+ }
48
+ </style>
@@ -0,0 +1,54 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SvBadge - a small status pill / count label. Themed via `--sg-*` tokens with
4
+ * soft `color-mix` tints so it sits well on light and dark. Parity: Smart badge.
5
+ *
6
+ * ```svelte
7
+ * <SvBadge variant="success">Active</SvBadge>
8
+ * <SvBadge variant="danger" dot /> 3 failed
9
+ * ```
10
+ */
11
+ import type { Snippet } from 'svelte'
12
+
13
+ type Variant = 'neutral' | 'accent' | 'success' | 'warning' | 'danger' | 'info'
14
+
15
+ type Props = {
16
+ variant?: Variant
17
+ size?: 'sm' | 'md'
18
+ /** Rounded-pill shape (default) vs a softer rounded rect. */
19
+ pill?: boolean
20
+ /** Show a leading status dot instead of / before content. */
21
+ dot?: boolean
22
+ children?: Snippet
23
+ }
24
+
25
+ let { variant = 'neutral', size = 'md', pill = true, dot = false, children }: Props = $props()
26
+ </script>
27
+
28
+ <span class="sv-badge sv-badge--{variant} sv-badge--{size}" class:is-pill={pill} class:has-dot={dot}>
29
+ {#if dot}<span class="sv-badge__dot" aria-hidden="true"></span>{/if}
30
+ {@render children?.()}
31
+ </span>
32
+
33
+ <style>
34
+ .sv-badge {
35
+ --_c: var(--sg-muted, #64748b);
36
+ display: inline-flex; align-items: center; gap: 5px; box-sizing: border-box;
37
+ font-weight: 600; line-height: 1; white-space: nowrap;
38
+ color: var(--_c);
39
+ background: color-mix(in srgb, var(--_c) 14%, transparent);
40
+ border: 1px solid color-mix(in srgb, var(--_c) 26%, transparent);
41
+ border-radius: 6px;
42
+ }
43
+ .sv-badge.is-pill { border-radius: 999px; }
44
+ .sv-badge--sm { font-size: 10.5px; padding: 2px 7px; }
45
+ .sv-badge--md { font-size: 12px; padding: 3px 9px; }
46
+ .sv-badge__dot { width: 6px; height: 6px; border-radius: 50%; background: var(--_c); }
47
+
48
+ .sv-badge--neutral { --_c: var(--sg-muted, #64748b); }
49
+ .sv-badge--accent { --_c: var(--sg-accent, #2563eb); }
50
+ .sv-badge--success { --_c: var(--sg-success, #16a34a); }
51
+ .sv-badge--warning { --_c: var(--sg-warning, #d97706); }
52
+ .sv-badge--danger { --_c: var(--sg-danger, #dc2626); }
53
+ .sv-badge--info { --_c: var(--sg-info, #0891b2); }
54
+ </style>
@@ -0,0 +1,88 @@
1
+ <script lang="ts" module>
2
+ export type { BreadcrumbItem } from './ui-app.types'
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ /**
7
+ * SvBreadcrumb - a navigation trail. Each item is a link (`href`), a button
8
+ * (`onClick`), or plain text; the last item is the current page and is never
9
+ * interactive. Long trails collapse the middle to an expandable ellipsis.
10
+ * Parity: Smart breadcrumb.
11
+ *
12
+ * ```svelte
13
+ * <SvBreadcrumb items={[{label:'Home', href:'/'}, {label:'Orders', href:'/orders'}, {label:'#1024'}]} />
14
+ * ```
15
+ */
16
+ import type { BreadcrumbItem } from './ui-app.types'
17
+
18
+ type Props = {
19
+ items: ReadonlyArray<BreadcrumbItem>
20
+ /** Separator between crumbs. Default a chevron. */
21
+ separator?: string
22
+ /** Collapse the middle when there are more than this many crumbs. */
23
+ maxItems?: number
24
+ ariaLabel?: string
25
+ }
26
+
27
+ let { items, separator = '/', maxItems, ariaLabel = 'Breadcrumb' }: Props = $props()
28
+
29
+ let expanded = $state(false)
30
+
31
+ // When collapsed, show the first crumb, an ellipsis, and the last two.
32
+ const collapsed = $derived(!!maxItems && !expanded && items.length > maxItems)
33
+ type Display = { item: BreadcrumbItem; index: number } | 'ellipsis'
34
+ const display = $derived<Display[]>(
35
+ collapsed
36
+ ? [
37
+ { item: items[0]!, index: 0 },
38
+ 'ellipsis',
39
+ ...items.slice(-2).map((item, i) => ({ item, index: items.length - 2 + i })),
40
+ ]
41
+ : items.map((item, index) => ({ item, index })),
42
+ )
43
+ </script>
44
+
45
+ <nav class="sv-bc" aria-label={ariaLabel}>
46
+ <ol class="sv-bc__list">
47
+ {#each display as entry, i (entry === 'ellipsis' ? `e${i}` : entry.index)}
48
+ {#if i > 0}<li class="sv-bc__sep" aria-hidden="true">{separator}</li>{/if}
49
+ {#if entry === 'ellipsis'}
50
+ <li>
51
+ <button type="button" class="sv-bc__ellipsis" aria-label="Show collapsed crumbs" onclick={() => (expanded = true)}>&hellip;</button>
52
+ </li>
53
+ {:else}
54
+ {@const isLast = entry.index === items.length - 1}
55
+ <li class="sv-bc__item" class:is-current={isLast}>
56
+ {#if isLast}
57
+ <span aria-current="page">{#if entry.item.icon}<span class="sv-bc__icon" aria-hidden="true">{entry.item.icon}</span>{/if}{entry.item.label}</span>
58
+ {:else if entry.item.href}
59
+ <a class="sv-bc__link" href={entry.item.href}>{#if entry.item.icon}<span class="sv-bc__icon" aria-hidden="true">{entry.item.icon}</span>{/if}{entry.item.label}</a>
60
+ {:else if entry.item.onClick}
61
+ <button type="button" class="sv-bc__link sv-bc__btn" onclick={entry.item.onClick}>{#if entry.item.icon}<span class="sv-bc__icon" aria-hidden="true">{entry.item.icon}</span>{/if}{entry.item.label}</button>
62
+ {:else}
63
+ <span>{#if entry.item.icon}<span class="sv-bc__icon" aria-hidden="true">{entry.item.icon}</span>{/if}{entry.item.label}</span>
64
+ {/if}
65
+ </li>
66
+ {/if}
67
+ {/each}
68
+ </ol>
69
+ </nav>
70
+
71
+ <style>
72
+ .sv-bc__list { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; list-style: none; margin: 0; padding: 0; font-size: 13px; }
73
+ .sv-bc__sep { color: var(--sg-muted, #94a3b8); user-select: none; }
74
+ .sv-bc__item { color: var(--sg-muted, #64748b); }
75
+ .sv-bc__item.is-current { color: var(--sg-fg, #0f172a); font-weight: 600; }
76
+ .sv-bc__icon { margin-inline-end: 4px; }
77
+ .sv-bc__link {
78
+ color: var(--sg-muted, #64748b); text-decoration: none; border-radius: 4px;
79
+ display: inline-flex; align-items: center;
80
+ }
81
+ .sv-bc__link:hover { color: var(--sg-accent, #2563eb); text-decoration: underline; }
82
+ .sv-bc__btn { background: none; border: 0; padding: 0; font: inherit; cursor: pointer; }
83
+ .sv-bc__ellipsis {
84
+ background: var(--sg-row-hover-bg, #f1f5f9); border: 0; border-radius: 5px; cursor: pointer;
85
+ color: var(--sg-muted, #64748b); padding: 0 7px; height: 20px; line-height: 1; font: inherit;
86
+ }
87
+ .sv-bc__ellipsis:hover { color: var(--sg-fg, #0f172a); }
88
+ </style>
@@ -0,0 +1,121 @@
1
+ <script lang="ts" module>
2
+ export type { ButtonGroupItem, ButtonGroupMode, ButtonGroupValue } from './createButtonGroup.svelte'
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ /**
7
+ * SvButtonGroup - a segmented button bar with single-select (radio semantics),
8
+ * multi-select (toggle set) or plain action buttons. Parity: Smart
9
+ * `smart-button-group`. Controlled via `value` + `onChange`; roving tabindex +
10
+ * arrow-key navigation come from the headless `createButtonGroup` core.
11
+ *
12
+ * Part of the editor kit: carries the shared contract (label, hint, error
13
+ * validation, size, `dir`/RTL) via `<SvField>`.
14
+ */
15
+ import SvField from './SvField.svelte'
16
+ import { nextEditorId, type SvEditorProps } from './editor-contract'
17
+ import { createButtonGroup, type ButtonGroupItem, type ButtonGroupMode, type ButtonGroupValue } from './createButtonGroup.svelte'
18
+
19
+ type Props = SvEditorProps & {
20
+ items: ReadonlyArray<ButtonGroupItem>
21
+ value?: ButtonGroupValue
22
+ onChange?: (value: string | number | Array<string | number>) => void
23
+ /** 'single' (radio), 'multiple' (toggle set), or 'none' (action buttons). */
24
+ mode?: ButtonGroupMode
25
+ orientation?: 'horizontal' | 'vertical'
26
+ /** 'solid' segmented fill (default) or 'outline' pills. */
27
+ variant?: 'solid' | 'outline'
28
+ }
29
+
30
+ let {
31
+ items,
32
+ value = null,
33
+ onChange,
34
+ mode = 'single',
35
+ orientation = 'horizontal',
36
+ variant = 'solid',
37
+ disabled = false,
38
+ size = 'md',
39
+ name,
40
+ ariaLabel,
41
+ invalid = false,
42
+ required = false,
43
+ error,
44
+ label,
45
+ hint,
46
+ dir,
47
+ id,
48
+ }: Props = $props()
49
+
50
+ const autoId = nextEditorId('sv-bg')
51
+ const uid = $derived(id ?? autoId)
52
+
53
+ const g = createButtonGroup({
54
+ items: () => items,
55
+ value: () => value,
56
+ onChange: (v) => onChange?.(v),
57
+ mode: () => mode,
58
+ orientation: () => orientation,
59
+ disabled: () => disabled,
60
+ dir: () => dir,
61
+ id: () => uid,
62
+ invalid: () => invalid,
63
+ required: () => required,
64
+ error: () => error,
65
+ hint: () => hint,
66
+ ariaLabel: () => ariaLabel ?? label,
67
+ })
68
+ </script>
69
+
70
+ <SvField id={uid} {label} {hint} {error} {required} {dir}>
71
+ <div
72
+ class="sv-bg sv-bg--{orientation} sv-bg--{variant} sv-bg--{size}"
73
+ class:is-disabled={disabled}
74
+ class:is-invalid={invalid}
75
+ {...g.rootProps()}
76
+ >
77
+ {#each items as item, i (item.value)}
78
+ <button
79
+ class="sv-bg__btn"
80
+ class:is-selected={g.isSelected(item.value)}
81
+ {...g.buttonProps(i)}
82
+ >{item.label ?? item.value}</button>
83
+ {/each}
84
+ {#if name}
85
+ {#each g.selectedValues as v (v)}<input type="hidden" {name} value={v} />{/each}
86
+ {/if}
87
+ </div>
88
+ </SvField>
89
+
90
+ <style>
91
+ .sv-bg {
92
+ --_accent: var(--sg-accent, #2563eb);
93
+ --_border: var(--sg-input-border, var(--sg-border, #cbd5e1));
94
+ --_radius: var(--sg-radius, 8px);
95
+ display: inline-flex; background: var(--sg-input-bg, #fff);
96
+ border: 1px solid var(--_border); border-radius: var(--_radius); overflow: hidden;
97
+ }
98
+ .sv-bg--vertical { flex-direction: column; }
99
+ .sv-bg.is-disabled { opacity: 0.6; }
100
+ .sv-bg.is-invalid { border-color: var(--sg-danger, #dc2626); }
101
+ .sv-bg:focus-within { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
102
+ .sv-bg.is-invalid:focus-within { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
103
+
104
+ .sv-bg__btn {
105
+ background: none; border: 0; font: inherit; font-weight: 600; color: var(--sg-fg, #0f172a);
106
+ cursor: pointer; white-space: nowrap; transition: background 0.12s, color 0.12s;
107
+ }
108
+ .sv-bg--sm .sv-bg__btn { padding: 5px 11px; font-size: 12px; }
109
+ .sv-bg--md .sv-bg__btn { padding: 7px 14px; font-size: 13px; }
110
+ .sv-bg--lg .sv-bg__btn { padding: 10px 18px; font-size: 15px; }
111
+ /* Segment dividers use a logical border so the bar mirrors under RTL. */
112
+ .sv-bg--horizontal .sv-bg__btn + .sv-bg__btn { border-inline-start: 1px solid var(--_border); }
113
+ .sv-bg--vertical .sv-bg__btn + .sv-bg__btn { border-top: 1px solid var(--_border); }
114
+ .sv-bg__btn:hover:not(:disabled):not(.is-selected) { background: var(--sg-row-hover-bg, #f1f5f9); }
115
+ .sv-bg__btn:focus-visible { outline: 2px solid var(--sg-focus-ring, var(--_accent)); outline-offset: -2px; }
116
+ .sv-bg__btn:disabled { opacity: 0.45; cursor: not-allowed; }
117
+
118
+ .sv-bg--solid .sv-bg__btn.is-selected { background: var(--_accent); color: var(--sg-on-accent, #fff); }
119
+ .sv-bg--solid .sv-bg__btn.is-selected + .sv-bg__btn, .sv-bg--solid .sv-bg__btn.is-selected { border-color: var(--_accent); }
120
+ .sv-bg--outline .sv-bg__btn.is-selected { color: var(--_accent); background: color-mix(in srgb, var(--_accent) 12%, transparent); }
121
+ </style>
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Component tests for SvButtonGroup: single-select radio semantics, multi-select
3
+ * toggle set, roving tabindex, and the shared editor contract wiring.
4
+ */
5
+ import { describe, expect, it } from 'vitest'
6
+ import { mount, unmount, flushSync } from 'svelte'
7
+ import SvButtonGroup from './SvButtonGroup.svelte'
8
+
9
+ function mountBg(props: Record<string, unknown>) {
10
+ const target = document.createElement('div')
11
+ document.body.appendChild(target)
12
+ const app = mount(SvButtonGroup, { target, props: props as any })
13
+ flushSync()
14
+ return { target, destroy: () => { unmount(app); target.remove() } }
15
+ }
16
+
17
+ const items = [
18
+ { value: 'day', label: 'Day' },
19
+ { value: 'week', label: 'Week' },
20
+ { value: 'month', label: 'Month' },
21
+ ]
22
+
23
+ describe('SvButtonGroup', () => {
24
+ it('single mode uses radiogroup/radio semantics and checks the value', () => {
25
+ const { target, destroy } = mountBg({ items, value: 'week', mode: 'single' })
26
+ try {
27
+ const group = target.querySelector<HTMLElement>('.sv-bg')!
28
+ expect(group.getAttribute('role')).toBe('radiogroup')
29
+ const btns = [...target.querySelectorAll<HTMLButtonElement>('.sv-bg__btn')]
30
+ expect(btns.map((b) => b.getAttribute('role'))).toEqual(['radio', 'radio', 'radio'])
31
+ expect(btns[1].getAttribute('aria-checked')).toBe('true')
32
+ // Roving tabindex: only the checked/active button is tabbable.
33
+ expect(btns[1].tabIndex).toBe(0)
34
+ expect(btns[0].tabIndex).toBe(-1)
35
+ } finally { destroy() }
36
+ })
37
+
38
+ it('single mode emits a scalar on click', () => {
39
+ let got: unknown
40
+ const { target, destroy } = mountBg({ items, value: 'day', mode: 'single', onChange: (v: unknown) => (got = v) })
41
+ try {
42
+ const btns = target.querySelectorAll<HTMLButtonElement>('.sv-bg__btn')
43
+ btns[2].click()
44
+ flushSync()
45
+ expect(got).toBe('month')
46
+ } finally { destroy() }
47
+ })
48
+
49
+ it('multiple mode toggles a set and uses aria-pressed', () => {
50
+ let got: unknown
51
+ const { target, destroy } = mountBg({ items, value: ['day'], mode: 'multiple', onChange: (v: unknown) => (got = v) })
52
+ try {
53
+ const group = target.querySelector<HTMLElement>('.sv-bg')!
54
+ expect(group.getAttribute('role')).toBe('group')
55
+ const btns = target.querySelectorAll<HTMLButtonElement>('.sv-bg__btn')
56
+ expect(btns[0].getAttribute('aria-pressed')).toBe('true')
57
+ btns[1].click()
58
+ flushSync()
59
+ expect(got).toEqual(['day', 'week'])
60
+ } finally { destroy() }
61
+ })
62
+
63
+ it('wires label + aria-invalid via the editor contract', () => {
64
+ const { target, destroy } = mountBg({ items, value: null, mode: 'single', label: 'View', invalid: true, error: 'Pick one' })
65
+ try {
66
+ const group = target.querySelector<HTMLElement>('.sv-bg')!
67
+ expect(group.getAttribute('aria-invalid')).toBe('true')
68
+ expect(target.querySelector('.sv-field__label')!.textContent).toContain('View')
69
+ expect(target.querySelector('.sv-field__error')!.textContent).toBe('Pick one')
70
+ } finally { destroy() }
71
+ })
72
+ })