@svgrid/grid 1.2.34 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (444) hide show
  1. package/README.md +6 -5
  2. package/dist/SvAccordion.svelte +109 -0
  3. package/dist/SvAccordion.svelte.d.ts +28 -0
  4. package/dist/SvAlert.svelte +92 -0
  5. package/dist/SvAlert.svelte.d.ts +27 -0
  6. package/dist/SvAutoComplete.svelte +47 -18
  7. package/dist/SvAutoComplete.svelte.d.ts +2 -5
  8. package/dist/SvAvatar.svelte +75 -0
  9. package/dist/SvAvatar.svelte.d.ts +14 -0
  10. package/dist/SvAvatarGroup.svelte +48 -0
  11. package/dist/SvAvatarGroup.svelte.d.ts +16 -0
  12. package/dist/SvBadge.svelte +54 -0
  13. package/dist/SvBadge.svelte.d.ts +23 -0
  14. package/dist/SvBreadcrumb.svelte +88 -0
  15. package/dist/SvBreadcrumb.svelte.d.ts +23 -0
  16. package/dist/SvButtonGroup.svelte +121 -0
  17. package/dist/SvButtonGroup.svelte.d.ts +16 -0
  18. package/dist/SvCalendar.svelte +72 -9
  19. package/dist/SvCalendar.svelte.d.ts +34 -1
  20. package/dist/SvCard.svelte +71 -0
  21. package/dist/SvCard.svelte.d.ts +28 -0
  22. package/dist/SvCarousel.svelte +134 -0
  23. package/dist/SvCarousel.svelte.d.ts +28 -0
  24. package/dist/SvCheckBox.svelte +36 -21
  25. package/dist/SvCheckBox.svelte.d.ts +2 -5
  26. package/dist/SvChip.svelte +88 -0
  27. package/dist/SvChip.svelte.d.ts +31 -0
  28. package/dist/SvCircularProgress.svelte +109 -0
  29. package/dist/SvCircularProgress.svelte.d.ts +31 -0
  30. package/dist/SvColorInput.svelte +54 -27
  31. package/dist/SvColorInput.svelte.d.ts +10 -6
  32. package/dist/SvComboBox.svelte +119 -33
  33. package/dist/SvComboBox.svelte.d.ts +18 -6
  34. package/dist/SvCommand.svelte +197 -0
  35. package/dist/SvCommand.svelte.d.ts +26 -0
  36. package/dist/SvContextMenu.svelte +96 -0
  37. package/dist/SvContextMenu.svelte.d.ts +25 -0
  38. package/dist/SvCountryInput.svelte +56 -21
  39. package/dist/SvCountryInput.svelte.d.ts +9 -5
  40. package/dist/SvDateRangeInput.svelte +256 -0
  41. package/dist/SvDateRangeInput.svelte.d.ts +37 -0
  42. package/dist/SvDateTimePicker.svelte +57 -20
  43. package/dist/SvDateTimePicker.svelte.d.ts +12 -4
  44. package/dist/SvDivider.svelte +61 -0
  45. package/dist/SvDivider.svelte.d.ts +22 -0
  46. package/dist/SvDrawer.svelte +226 -0
  47. package/dist/SvDrawer.svelte.d.ts +43 -0
  48. package/dist/SvDropDownList.svelte +114 -35
  49. package/dist/SvDropDownList.svelte.d.ts +7 -6
  50. package/dist/SvDurationInput.svelte +108 -0
  51. package/dist/SvDurationInput.svelte.d.ts +15 -0
  52. package/dist/SvEmptyState.svelte +59 -0
  53. package/dist/SvEmptyState.svelte.d.ts +25 -0
  54. package/dist/SvField.svelte +85 -0
  55. package/dist/SvField.svelte.d.ts +30 -0
  56. package/dist/SvFileUpload.svelte +169 -0
  57. package/dist/SvFileUpload.svelte.d.ts +24 -0
  58. package/dist/SvForm.svelte +7 -1
  59. package/dist/SvForm.svelte.d.ts +3 -0
  60. package/dist/SvGauge.svelte +7 -1
  61. package/dist/SvGauge.svelte.d.ts +11 -0
  62. package/dist/SvGrid.controller.svelte.d.ts +6 -0
  63. package/dist/SvGrid.controller.svelte.js +64 -9
  64. package/dist/SvGrid.css +33 -0
  65. package/dist/SvGrid.svelte +116 -3
  66. package/dist/SvGrid.types.d.ts +366 -0
  67. package/dist/SvGridBoard.svelte +2281 -0
  68. package/dist/SvGridBoard.svelte.d.ts +31 -0
  69. package/dist/SvGridDropdown.svelte +2 -1
  70. package/dist/SvGridSelect.svelte +255 -0
  71. package/dist/SvGridSelect.svelte.d.ts +32 -0
  72. package/dist/SvGroupCell.svelte +109 -0
  73. package/dist/SvGroupCell.svelte.d.ts +41 -0
  74. package/dist/SvListBox.svelte +123 -36
  75. package/dist/SvListBox.svelte.d.ts +12 -8
  76. package/dist/SvMaskedInput.svelte +53 -13
  77. package/dist/SvMaskedInput.svelte.d.ts +16 -6
  78. package/dist/SvMenu.svelte +101 -0
  79. package/dist/SvMenu.svelte.d.ts +26 -0
  80. package/dist/SvMenuList.svelte +143 -0
  81. package/dist/SvMenuList.svelte.d.ts +24 -0
  82. package/dist/SvModal.svelte +220 -0
  83. package/dist/SvModal.svelte.d.ts +31 -0
  84. package/dist/SvMultiSelect.svelte +277 -0
  85. package/dist/SvMultiSelect.svelte.d.ts +31 -0
  86. package/dist/SvNavPane.svelte +314 -0
  87. package/dist/SvNavPane.svelte.d.ts +59 -0
  88. package/dist/SvNumberInput.svelte +23 -9
  89. package/dist/SvNumberInput.svelte.d.ts +2 -0
  90. package/dist/SvOtpInput.svelte +157 -0
  91. package/dist/SvOtpInput.svelte.d.ts +16 -0
  92. package/dist/SvPagination.svelte +100 -0
  93. package/dist/SvPagination.svelte.d.ts +16 -0
  94. package/dist/SvPasswordInput.svelte +42 -12
  95. package/dist/SvPasswordInput.svelte.d.ts +13 -6
  96. package/dist/SvPhoneInput.svelte +43 -11
  97. package/dist/SvPhoneInput.svelte.d.ts +10 -11
  98. package/dist/SvPopover.svelte +127 -0
  99. package/dist/SvPopover.svelte.d.ts +35 -0
  100. package/dist/SvProgress.svelte +116 -0
  101. package/dist/SvProgress.svelte.d.ts +32 -0
  102. package/dist/SvRadioGroup.svelte +40 -22
  103. package/dist/SvRadioGroup.svelte.d.ts +2 -10
  104. package/dist/SvRating.svelte +51 -30
  105. package/dist/SvRating.svelte.d.ts +10 -7
  106. package/dist/SvRichText.svelte +192 -0
  107. package/dist/SvRichText.svelte.d.ts +16 -0
  108. package/dist/SvRowGroupPanel.svelte +147 -0
  109. package/dist/SvRowGroupPanel.svelte.d.ts +17 -0
  110. package/dist/SvScrollArea.svelte +50 -0
  111. package/dist/SvScrollArea.svelte.d.ts +22 -0
  112. package/dist/SvSkeleton.svelte +76 -0
  113. package/dist/SvSkeleton.svelte.d.ts +25 -0
  114. package/dist/SvSlider.svelte +41 -9
  115. package/dist/SvSlider.svelte.d.ts +4 -4
  116. package/dist/SvSparkline.svelte +77 -0
  117. package/dist/SvSparkline.svelte.d.ts +28 -0
  118. package/dist/SvSplitter.svelte +138 -0
  119. package/dist/SvSplitter.svelte.d.ts +32 -0
  120. package/dist/SvStat.svelte +86 -0
  121. package/dist/SvStat.svelte.d.ts +30 -0
  122. package/dist/SvStepper.svelte +91 -0
  123. package/dist/SvStepper.svelte.d.ts +24 -0
  124. package/dist/SvSwitchButton.svelte +39 -24
  125. package/dist/SvSwitchButton.svelte.d.ts +2 -5
  126. package/dist/SvTabs.svelte +147 -28
  127. package/dist/SvTabs.svelte.d.ts +16 -4
  128. package/dist/SvTagsInput.svelte +59 -22
  129. package/dist/SvTagsInput.svelte.d.ts +9 -4
  130. package/dist/SvTextArea.svelte +113 -0
  131. package/dist/SvTextArea.svelte.d.ts +18 -0
  132. package/dist/SvTextInput.svelte +115 -0
  133. package/dist/SvTextInput.svelte.d.ts +20 -0
  134. package/dist/SvTimePicker.svelte +19 -5
  135. package/dist/SvTimePicker.svelte.d.ts +12 -0
  136. package/dist/SvTimeline.svelte +75 -0
  137. package/dist/SvTimeline.svelte.d.ts +24 -0
  138. package/dist/SvToaster.svelte +124 -0
  139. package/dist/SvToaster.svelte.d.ts +10 -0
  140. package/dist/SvToggleButton.svelte +37 -12
  141. package/dist/SvToggleButton.svelte.d.ts +2 -4
  142. package/dist/SvTooltip.svelte +113 -0
  143. package/dist/SvTooltip.svelte.d.ts +22 -0
  144. package/dist/SvTour.svelte +151 -0
  145. package/dist/SvTour.svelte.d.ts +25 -0
  146. package/dist/SvTree.svelte +286 -44
  147. package/dist/SvTree.svelte.d.ts +26 -9
  148. package/dist/SvTreeSelect.svelte +222 -0
  149. package/dist/SvTreeSelect.svelte.d.ts +24 -0
  150. package/dist/a11y/dismissable.d.ts +32 -0
  151. package/dist/a11y/dismissable.js +54 -0
  152. package/dist/a11y/focus-trap.d.ts +39 -0
  153. package/dist/a11y/focus-trap.js +142 -0
  154. package/dist/a11y/live-region.d.ts +8 -0
  155. package/dist/a11y/live-region.js +60 -0
  156. package/dist/a11y/scroll-lock.d.ts +4 -0
  157. package/dist/a11y/scroll-lock.js +41 -0
  158. package/dist/a11y.d.ts +3 -1
  159. package/dist/a11y.js +1 -1
  160. package/dist/avatar.d.ts +10 -0
  161. package/dist/avatar.js +22 -0
  162. package/dist/builtin-editors.d.ts +5 -0
  163. package/dist/builtin-editors.js +55 -0
  164. package/dist/cdn/svgrid.js +16021 -8234
  165. package/dist/cdn/svgrid.svelte-external.js +14542 -6764
  166. package/dist/columns.js +3 -2
  167. package/dist/core.d.ts +7 -1
  168. package/dist/countries.d.ts +12 -0
  169. package/dist/countries.js +24 -0
  170. package/dist/createAccordion.svelte.d.ts +71 -0
  171. package/dist/createAccordion.svelte.js +99 -0
  172. package/dist/createAutocomplete.svelte.d.ts +13 -4
  173. package/dist/createAutocomplete.svelte.js +11 -1
  174. package/dist/createButtonGroup.svelte.d.ts +62 -0
  175. package/dist/createButtonGroup.svelte.js +154 -0
  176. package/dist/createCalendar.svelte.d.ts +5 -0
  177. package/dist/createCalendar.svelte.js +2 -0
  178. package/dist/createCheckbox.svelte.d.ts +9 -17
  179. package/dist/createCheckbox.svelte.js +28 -1
  180. package/dist/createColorInput.svelte.d.ts +12 -22
  181. package/dist/createColorInput.svelte.js +13 -0
  182. package/dist/createCombobox.svelte.d.ts +16 -5
  183. package/dist/createCombobox.svelte.js +13 -2
  184. package/dist/createCountryInput.svelte.d.ts +14 -3
  185. package/dist/createCountryInput.svelte.js +12 -2
  186. package/dist/createDateTimePicker.svelte.d.ts +17 -2
  187. package/dist/createDateTimePicker.svelte.js +12 -2
  188. package/dist/createDropdownList.svelte.d.ts +14 -5
  189. package/dist/createDropdownList.svelte.js +50 -1
  190. package/dist/createFileUpload.svelte.d.ts +68 -0
  191. package/dist/createFileUpload.svelte.js +122 -0
  192. package/dist/createListbox.svelte.d.ts +10 -1
  193. package/dist/createListbox.svelte.js +48 -1
  194. package/dist/createMaskedInput.svelte.d.ts +11 -2
  195. package/dist/createMaskedInput.svelte.js +11 -1
  196. package/dist/createNumberInput.svelte.d.ts +1 -0
  197. package/dist/createNumberInput.svelte.js +1 -0
  198. package/dist/createPasswordInput.svelte.d.ts +19 -21
  199. package/dist/createPasswordInput.svelte.js +16 -4
  200. package/dist/createPhoneInput.svelte.d.ts +26 -7
  201. package/dist/createPhoneInput.svelte.js +24 -3
  202. package/dist/createPopoverSelect.svelte.d.ts +86 -0
  203. package/dist/createPopoverSelect.svelte.js +196 -0
  204. package/dist/createRadioGroup.svelte.d.ts +9 -0
  205. package/dist/createRadioGroup.svelte.js +11 -1
  206. package/dist/createRating.svelte.d.ts +11 -0
  207. package/dist/createRating.svelte.js +12 -1
  208. package/dist/createSlider.svelte.d.ts +2 -0
  209. package/dist/createSlider.svelte.js +16 -5
  210. package/dist/createSplitter.svelte.d.ts +67 -0
  211. package/dist/createSplitter.svelte.js +99 -0
  212. package/dist/createSwitch.svelte.d.ts +9 -16
  213. package/dist/createSwitch.svelte.js +28 -1
  214. package/dist/createTabs.svelte.d.ts +7 -0
  215. package/dist/createTabs.svelte.js +17 -2
  216. package/dist/createTagsInput.svelte.d.ts +13 -1
  217. package/dist/createTagsInput.svelte.js +12 -3
  218. package/dist/createToggle.svelte.d.ts +9 -0
  219. package/dist/createToggle.svelte.js +12 -1
  220. package/dist/createTree.svelte.d.ts +19 -0
  221. package/dist/createTree.svelte.js +108 -15
  222. package/dist/debounce.d.ts +11 -0
  223. package/dist/debounce.js +30 -0
  224. package/dist/duration.d.ts +15 -0
  225. package/dist/duration.js +51 -0
  226. package/dist/editor-contract.d.ts +62 -7
  227. package/dist/editor-contract.js +45 -5
  228. package/dist/editor-registry.d.ts +67 -0
  229. package/dist/editor-registry.js +38 -0
  230. package/dist/editors/cell-editors.d.ts +1 -1
  231. package/dist/editors/cell-editors.js +5 -0
  232. package/dist/fuzzy.d.ts +3 -0
  233. package/dist/fuzzy.js +41 -0
  234. package/dist/grid-select.d.ts +9 -0
  235. package/dist/grid-select.js +8 -0
  236. package/dist/index.d.ts +79 -6
  237. package/dist/index.js +78 -4
  238. package/dist/keyboard-handlers.js +19 -0
  239. package/dist/list-option.d.ts +36 -0
  240. package/dist/list-option.js +68 -0
  241. package/dist/otp.d.ts +16 -0
  242. package/dist/otp.js +26 -0
  243. package/dist/paginate.d.ts +20 -0
  244. package/dist/paginate.js +40 -0
  245. package/dist/popover.d.ts +15 -1
  246. package/dist/popover.js +29 -3
  247. package/dist/recurrence.d.ts +36 -0
  248. package/dist/recurrence.js +90 -0
  249. package/dist/server-data-source.d.ts +89 -0
  250. package/dist/server-data-source.js +4 -0
  251. package/dist/server-group-model.d.ts +98 -0
  252. package/dist/server-group-model.js +263 -0
  253. package/dist/toast-store.svelte.d.ts +41 -0
  254. package/dist/toast-store.svelte.js +113 -0
  255. package/dist/tree-select.d.ts +19 -0
  256. package/dist/tree-select.js +40 -0
  257. package/dist/ui-app.types.d.ts +80 -0
  258. package/dist/ui-app.types.js +7 -0
  259. package/dist/validators.d.ts +43 -0
  260. package/dist/validators.js +53 -0
  261. package/dist/virtual.d.ts +35 -0
  262. package/dist/virtual.js +34 -0
  263. package/package.json +1 -1
  264. package/src/SvAccordion.svelte +109 -0
  265. package/src/SvAccordion.test.ts +66 -0
  266. package/src/SvAlert.svelte +92 -0
  267. package/src/SvAutoComplete.svelte +47 -18
  268. package/src/SvAvatar.svelte +75 -0
  269. package/src/SvAvatarGroup.svelte +48 -0
  270. package/src/SvBadge.svelte +54 -0
  271. package/src/SvBreadcrumb.svelte +88 -0
  272. package/src/SvButtonGroup.svelte +121 -0
  273. package/src/SvButtonGroup.test.ts +72 -0
  274. package/src/SvCalendar.svelte +72 -9
  275. package/src/SvCard.svelte +71 -0
  276. package/src/SvCarousel.svelte +134 -0
  277. package/src/SvCheckBox.svelte +36 -21
  278. package/src/SvChip.svelte +88 -0
  279. package/src/SvCircularProgress.svelte +109 -0
  280. package/src/SvColorInput.svelte +54 -27
  281. package/src/SvComboBox.svelte +119 -33
  282. package/src/SvCommand.svelte +197 -0
  283. package/src/SvContextMenu.svelte +96 -0
  284. package/src/SvCountryInput.svelte +56 -21
  285. package/src/SvDateRangeInput.svelte +256 -0
  286. package/src/SvDateRangeInput.test.ts +80 -0
  287. package/src/SvDateTimePicker.svelte +57 -20
  288. package/src/SvDivider.svelte +61 -0
  289. package/src/SvDrawer.svelte +226 -0
  290. package/src/SvDrawer.test.ts +125 -0
  291. package/src/SvDropDownList.svelte +114 -35
  292. package/src/SvDurationInput.svelte +108 -0
  293. package/src/SvEmptyState.svelte +59 -0
  294. package/src/SvField.svelte +85 -0
  295. package/src/SvFileUpload.svelte +169 -0
  296. package/src/SvFileUpload.test.ts +59 -0
  297. package/src/SvForm.svelte +7 -1
  298. package/src/SvGauge.svelte +7 -1
  299. package/src/SvGrid.controller.svelte.ts +68 -9
  300. package/src/SvGrid.css +33 -0
  301. package/src/SvGrid.svelte +116 -3
  302. package/src/SvGrid.types.ts +374 -0
  303. package/src/SvGridBoard.svelte +2281 -0
  304. package/src/SvGridDropdown.svelte +2 -1
  305. package/src/SvGridSelect.svelte +255 -0
  306. package/src/SvGridSelect.test.ts +100 -0
  307. package/src/SvGroupCell.svelte +109 -0
  308. package/src/SvListBox.svelte +123 -36
  309. package/src/SvMaskedInput.svelte +53 -13
  310. package/src/SvMenu.svelte +101 -0
  311. package/src/SvMenu.test.ts +73 -0
  312. package/src/SvMenuList.svelte +143 -0
  313. package/src/SvModal.svelte +220 -0
  314. package/src/SvMultiSelect.svelte +277 -0
  315. package/src/SvMultiSelect.test.ts +167 -0
  316. package/src/SvNavPane.svelte +314 -0
  317. package/src/SvNavPane.test.ts +70 -0
  318. package/src/SvNumberInput.svelte +23 -9
  319. package/src/SvOtpInput.svelte +157 -0
  320. package/src/SvPagination.svelte +100 -0
  321. package/src/SvPasswordInput.svelte +42 -12
  322. package/src/SvPhoneInput.svelte +43 -11
  323. package/src/SvPopover.svelte +127 -0
  324. package/src/SvProgress.svelte +116 -0
  325. package/src/SvRadioGroup.svelte +40 -22
  326. package/src/SvRating.svelte +51 -30
  327. package/src/SvRichText.svelte +192 -0
  328. package/src/SvRowGroupPanel.svelte +147 -0
  329. package/src/SvScrollArea.svelte +50 -0
  330. package/src/SvSkeleton.svelte +76 -0
  331. package/src/SvSlider.svelte +41 -9
  332. package/src/SvSparkline.svelte +77 -0
  333. package/src/SvSplitter.svelte +138 -0
  334. package/src/SvSplitter.test.ts +55 -0
  335. package/src/SvStat.svelte +86 -0
  336. package/src/SvStepper.svelte +91 -0
  337. package/src/SvSwitchButton.svelte +39 -24
  338. package/src/SvTabs.svelte +147 -28
  339. package/src/SvTagsInput.svelte +59 -22
  340. package/src/SvTextArea.svelte +113 -0
  341. package/src/SvTextInput.svelte +115 -0
  342. package/src/SvTimePicker.svelte +19 -5
  343. package/src/SvTimeline.svelte +75 -0
  344. package/src/SvToaster.svelte +124 -0
  345. package/src/SvToaster.test.ts +63 -0
  346. package/src/SvToggleButton.svelte +37 -12
  347. package/src/SvTooltip.svelte +113 -0
  348. package/src/SvTour.svelte +151 -0
  349. package/src/SvTree.svelte +286 -44
  350. package/src/SvTreeSelect.svelte +222 -0
  351. package/src/SvTreeSelect.test.ts +87 -0
  352. package/src/a11y/dismissable.test.ts +95 -0
  353. package/src/a11y/dismissable.ts +94 -0
  354. package/src/a11y/focus-trap.test.ts +110 -0
  355. package/src/a11y/focus-trap.ts +134 -0
  356. package/src/a11y/live-region.test.ts +40 -0
  357. package/src/a11y/live-region.ts +65 -0
  358. package/src/a11y/scroll-lock.test.ts +38 -0
  359. package/src/a11y/scroll-lock.ts +41 -0
  360. package/src/a11y.ts +3 -1
  361. package/src/avatar.test.ts +32 -0
  362. package/src/avatar.ts +21 -0
  363. package/src/board.test.ts +927 -0
  364. package/src/builtin-editors.grid.test.ts +85 -0
  365. package/src/builtin-editors.test.ts +58 -0
  366. package/src/builtin-editors.ts +56 -0
  367. package/src/columns.ts +3 -2
  368. package/src/core.ts +10 -0
  369. package/src/countries.ts +25 -0
  370. package/src/createAccordion.svelte.ts +126 -0
  371. package/src/createAutocomplete.svelte.ts +18 -1
  372. package/src/createButtonGroup.svelte.ts +166 -0
  373. package/src/createCalendar.svelte.ts +6 -0
  374. package/src/createCheckbox.svelte.ts +23 -1
  375. package/src/createColorInput.svelte.ts +20 -0
  376. package/src/createCombobox.svelte.ts +22 -2
  377. package/src/createCountryInput.svelte.ts +21 -2
  378. package/src/createDateTimePicker.svelte.ts +23 -2
  379. package/src/createDropdownList.svelte.ts +31 -2
  380. package/src/createFileUpload.svelte.ts +144 -0
  381. package/src/createListbox.svelte.ts +35 -3
  382. package/src/createMaskedInput.svelte.ts +18 -1
  383. package/src/createNumberInput.svelte.ts +2 -0
  384. package/src/createPasswordInput.svelte.ts +31 -5
  385. package/src/createPhoneInput.svelte.ts +38 -4
  386. package/src/createPopoverSelect.svelte.ts +208 -0
  387. package/src/createRadioGroup.svelte.ts +23 -1
  388. package/src/createRating.svelte.ts +26 -1
  389. package/src/createSlider.svelte.ts +14 -3
  390. package/src/createSplitter.svelte.ts +119 -0
  391. package/src/createSwitch.svelte.ts +26 -3
  392. package/src/createTabs.svelte.ts +20 -3
  393. package/src/createTagsInput.svelte.ts +24 -3
  394. package/src/createToggle.svelte.ts +25 -2
  395. package/src/createTree.svelte.ts +104 -14
  396. package/src/debounce.test.ts +50 -0
  397. package/src/debounce.ts +41 -0
  398. package/src/duration.test.ts +41 -0
  399. package/src/duration.ts +51 -0
  400. package/src/editor-contract.ts +87 -8
  401. package/src/editor-registry.grid.test.ts +98 -0
  402. package/src/editor-registry.test.ts +86 -0
  403. package/src/editor-registry.ts +106 -0
  404. package/src/editors/cell-editors.ts +10 -1
  405. package/src/fuzzy.test.ts +40 -0
  406. package/src/fuzzy.ts +39 -0
  407. package/src/grid-select.test.ts +28 -0
  408. package/src/grid-select.ts +20 -0
  409. package/src/index.ts +150 -5
  410. package/src/keyboard-handlers.ts +20 -0
  411. package/src/list-option.ts +74 -0
  412. package/src/list-power.test.ts +71 -0
  413. package/src/otp.test.ts +28 -0
  414. package/src/otp.ts +28 -0
  415. package/src/overlays.test.ts +85 -0
  416. package/src/paginate.test.ts +41 -0
  417. package/src/paginate.ts +61 -0
  418. package/src/phone-valid.test.ts +26 -0
  419. package/src/popover.ts +31 -3
  420. package/src/progress.test.ts +55 -0
  421. package/src/recurrence.test.ts +91 -0
  422. package/src/recurrence.ts +111 -0
  423. package/src/server-data-source.ts +102 -0
  424. package/src/server-group-model.test.ts +294 -0
  425. package/src/server-group-model.ts +370 -0
  426. package/src/svgrid.responsive.test.ts +72 -0
  427. package/src/toast-store.svelte.ts +148 -0
  428. package/src/toast-store.test.ts +86 -0
  429. package/src/tree-edit.test.ts +49 -0
  430. package/src/tree-filter.test.ts +56 -0
  431. package/src/tree-select.test.ts +48 -0
  432. package/src/tree-select.ts +62 -0
  433. package/src/ui-app.test.ts +130 -0
  434. package/src/ui-app.types.ts +88 -0
  435. package/src/ui-display.test.ts +79 -0
  436. package/src/ui-inputs.test.ts +1 -1
  437. package/src/ui-status.test.ts +141 -0
  438. package/src/ui-tier1.test.ts +142 -0
  439. package/src/ui-tier2.test.ts +124 -0
  440. package/src/ui-tier3.test.ts +139 -0
  441. package/src/validators.test.ts +58 -0
  442. package/src/validators.ts +105 -0
  443. package/src/virtual.test.ts +72 -0
  444. package/src/virtual.ts +67 -0
@@ -4,15 +4,13 @@
4
4
  * `smart-switch-button`. Controlled via `checked` + `onChange`; keyboard
5
5
  * togglable; optional inline on/off labels.
6
6
  */
7
+ import SvField from './SvField.svelte'
8
+ import { nextEditorId, type SvEditorProps } from './editor-contract'
7
9
  import { createSwitch } from './createSwitch.svelte'
8
10
 
9
- type Props = {
11
+ type Props = SvEditorProps & {
10
12
  checked?: boolean
11
13
  onChange?: (checked: boolean) => void
12
- disabled?: boolean
13
- size?: 'sm' | 'md' | 'lg'
14
- name?: string
15
- ariaLabel?: string
16
14
  /** Optional labels rendered inside the track. */
17
15
  onLabel?: string
18
16
  offLabel?: string
@@ -25,33 +23,50 @@
25
23
  size = 'md',
26
24
  name,
27
25
  ariaLabel,
26
+ invalid = false,
27
+ required = false,
28
+ error,
29
+ label,
30
+ hint,
31
+ dir,
32
+ id,
28
33
  onLabel,
29
34
  offLabel,
30
35
  }: Props = $props()
31
36
 
37
+ const autoId = nextEditorId('sv-switch')
38
+ const uid = $derived(id ?? autoId)
39
+
32
40
  // The styled switch is just a renderer over the headless core.
33
41
  const sw = createSwitch({
34
42
  checked: () => checked,
35
43
  onChange: (v) => onChange?.(v),
36
44
  disabled: () => disabled,
37
45
  ariaLabel: () => ariaLabel,
46
+ id: () => uid,
47
+ invalid: () => invalid,
48
+ required: () => required,
49
+ error: () => error,
50
+ hint: () => hint,
38
51
  })
39
52
  </script>
40
53
 
41
- <button
42
- class="sv-switch sv-switch--{size}"
43
- class:is-on={checked}
44
- {...sw.switchProps()}
45
- >
46
- <span class="sv-switch__track">
47
- {#if onLabel || offLabel}
48
- <span class="sv-switch__txt sv-switch__txt--on">{onLabel ?? ''}</span>
49
- <span class="sv-switch__txt sv-switch__txt--off">{offLabel ?? ''}</span>
50
- {/if}
51
- <span class="sv-switch__thumb"></span>
52
- </span>
53
- {#if name}<input type="hidden" {name} value={checked ? 'true' : 'false'} />{/if}
54
- </button>
54
+ <SvField id={uid} {label} {hint} {error} {required} {dir}>
55
+ <button
56
+ class="sv-switch sv-switch--{size}"
57
+ class:is-on={checked}
58
+ {...sw.switchProps()}
59
+ >
60
+ <span class="sv-switch__track">
61
+ {#if onLabel || offLabel}
62
+ <span class="sv-switch__txt sv-switch__txt--on">{onLabel ?? ''}</span>
63
+ <span class="sv-switch__txt sv-switch__txt--off">{offLabel ?? ''}</span>
64
+ {/if}
65
+ <span class="sv-switch__thumb"></span>
66
+ </span>
67
+ {#if name}<input type="hidden" {name} value={checked ? 'true' : 'false'} />{/if}
68
+ </button>
69
+ </SvField>
55
70
 
56
71
  <style>
57
72
  .sv-switch {
@@ -69,17 +84,17 @@
69
84
  }
70
85
  .sv-switch.is-on .sv-switch__track { background: var(--_accent); }
71
86
  .sv-switch__thumb {
72
- position: absolute; left: 2px; top: 50%; transform: translateY(-50%);
87
+ position: absolute; inset-inline-start: 2px; top: 50%; transform: translateY(-50%);
73
88
  width: calc(var(--_h) - 4px); height: calc(var(--_h) - 4px); border-radius: 50%;
74
- background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.3); transition: left 0.16s;
89
+ background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.3); transition: inset-inline-start 0.16s;
75
90
  }
76
- .sv-switch.is-on .sv-switch__thumb { left: calc(var(--_w) - var(--_h) + 2px); }
91
+ .sv-switch.is-on .sv-switch__thumb { inset-inline-start: calc(var(--_w) - var(--_h) + 2px); }
77
92
  .sv-switch__txt {
78
93
  position: absolute; font-size: 9px; font-weight: 700; color: #fff; text-transform: uppercase;
79
94
  top: 50%; transform: translateY(-50%);
80
95
  }
81
- .sv-switch__txt--on { left: 6px; opacity: 0; }
82
- .sv-switch__txt--off { right: 5px; color: var(--sg-muted, #64748b); }
96
+ .sv-switch__txt--on { inset-inline-start: 6px; opacity: 0; }
97
+ .sv-switch__txt--off { inset-inline-end: 5px; color: var(--sg-muted, #64748b); }
83
98
  .sv-switch.is-on .sv-switch__txt--on { opacity: 1; }
84
99
  .sv-switch.is-on .sv-switch__txt--off { opacity: 0; }
85
100
  .sv-switch:focus-visible { outline: none; }
package/src/SvTabs.svelte CHANGED
@@ -4,20 +4,27 @@
4
4
 
5
5
  <script lang="ts">
6
6
  /**
7
- * SvTabs - a WAI-ARIA tabs widget with roving tabindex + arrow-key navigation.
7
+ * SvTabs - a WAI-ARIA tabs widget with roving tabindex + arrow-key navigation,
8
+ * four `tabPosition`s (top / bottom / left / right) and optional closable tabs.
8
9
  * Parity: Smart `smart-tabs`. Controlled via `value` + `onChange`; the active
9
- * panel is rendered through the `panel` snippet (receives the active id).
10
+ * panel is rendered through the `panel` snippet.
10
11
  *
11
- * The behavior (roving focus, activation, ARIA) lives in the headless
12
- * `createTabs` core; this component is just one styled renderer over it.
12
+ * Behavior (roving focus, activation, close, ARIA) lives in the headless
13
+ * `createTabs` core; this component is one styled renderer over it.
13
14
  */
14
15
  import type { Snippet } from 'svelte'
16
+ import { type EditorDir } from './editor-contract'
15
17
  import { createTabs, type TabItem } from './createTabs.svelte'
16
18
 
17
19
  type Props = {
18
20
  tabs: ReadonlyArray<TabItem>
19
21
  value?: string
20
22
  onChange?: (id: string) => void
23
+ /** Fired when a closable tab's x (or Delete key) closes it. */
24
+ onClose?: (id: string) => void
25
+ /** Where the tab strip sits. Default 'top'. */
26
+ tabPosition?: 'top' | 'bottom' | 'left' | 'right'
27
+ /** @deprecated use `tabPosition`. horizontal -> top, vertical -> left. */
21
28
  orientation?: 'horizontal' | 'vertical'
22
29
  /** 'line' underline (default) or 'pill' segmented look. */
23
30
  variant?: 'line' | 'pill'
@@ -25,25 +32,78 @@
25
32
  activation?: 'automatic' | 'manual'
26
33
  panel?: Snippet<[string]>
27
34
  ariaLabel?: string
35
+ /** Text direction (rtl mirrors layout + flips horizontal arrow keys). */
36
+ dir?: EditorDir
37
+ /** Allow dragging tabs to reorder; emits the new id order via onReorder. */
38
+ reorderable?: boolean
39
+ onReorder?: (orderedIds: string[]) => void
28
40
  }
29
41
 
30
42
  let {
31
43
  tabs,
32
44
  value = $bindable(),
33
45
  onChange,
34
- orientation = 'horizontal',
46
+ onClose,
47
+ tabPosition,
48
+ orientation,
35
49
  variant = 'line',
36
50
  activation = 'automatic',
37
51
  panel,
38
52
  ariaLabel,
53
+ dir,
54
+ reorderable = false,
55
+ onReorder,
39
56
  }: Props = $props()
40
57
 
58
+ const pos = $derived(tabPosition ?? (orientation === 'vertical' ? 'left' : 'top'))
59
+ const orient = $derived(pos === 'left' || pos === 'right' ? 'vertical' : 'horizontal')
60
+ const resolvedDir = $derived(dir === 'ltr' || dir === 'rtl' ? dir : undefined)
61
+
62
+ // --- Overflow scroll (horizontal): show prev/next buttons when the strip
63
+ // overflows its container, and scroll the list.
64
+ let scrollLeft = $state(0)
65
+ let clientW = $state(0)
66
+ let scrollW = $state(0)
67
+ const overflowing = $derived(orient === 'horizontal' && scrollW > clientW + 1)
68
+ const atStart = $derived(scrollLeft <= 1)
69
+ const atEnd = $derived(scrollLeft + clientW >= scrollW - 1)
70
+ function measure() {
71
+ if (!tablistEl) return
72
+ clientW = tablistEl.clientWidth
73
+ scrollW = tablistEl.scrollWidth
74
+ scrollLeft = tablistEl.scrollLeft
75
+ }
76
+ $effect(() => { tabs.length; queueMicrotask(measure) }) // re-measure when tabs change
77
+ $effect(() => {
78
+ const onResize = () => measure()
79
+ window.addEventListener('resize', onResize)
80
+ return () => window.removeEventListener('resize', onResize)
81
+ })
82
+ function scrollBy(dir: 1 | -1) {
83
+ tablistEl?.scrollBy({ left: dir * Math.max(120, clientW * 0.7), behavior: 'smooth' })
84
+ }
85
+
86
+ // --- Drag reorder -----------------------------------------------------------
87
+ let dragId = $state<string | null>(null)
88
+ let overId = $state<string | null>(null)
89
+ function reorder(fromId: string, toId: string) {
90
+ if (fromId === toId) return
91
+ const ids = tabs.map((t) => t.id)
92
+ const from = ids.indexOf(fromId)
93
+ const to = ids.indexOf(toId)
94
+ if (from < 0 || to < 0) return
95
+ ids.splice(to, 0, ids.splice(from, 1)[0]!)
96
+ onReorder?.(ids)
97
+ }
98
+
41
99
  const tabsCtl = createTabs({
42
100
  tabs: () => tabs,
43
101
  value: () => value,
44
102
  onChange: (id) => { value = id; onChange?.(id) },
45
- orientation: () => orientation,
103
+ onClose: (id) => onClose?.(id),
104
+ orientation: () => orient,
46
105
  activation: () => activation,
106
+ dir: () => dir,
47
107
  ariaLabel: () => ariaLabel,
48
108
  })
49
109
  const active = $derived(tabsCtl.activeId)
@@ -60,16 +120,43 @@
60
120
  })
61
121
  </script>
62
122
 
63
- <div class="sv-tabs sv-tabs--{orientation} sv-tabs--{variant}">
64
- <div bind:this={tablistEl} class="sv-tabs__list" {...tabsCtl.tablistProps()}>
65
- {#each tabs as tab (tab.id)}
66
- <button
67
- type="button"
68
- class="sv-tabs__tab"
69
- class:is-active={tab.id === active}
70
- {...tabsCtl.tabProps(tab.id)}
71
- >{tab.label}</button>
72
- {/each}
123
+ <div class="sv-tabs sv-tabs--{pos} sv-tabs--{orient} sv-tabs--{variant}" dir={resolvedDir}>
124
+ <div class="sv-tabs__strip">
125
+ {#if overflowing}
126
+ <button type="button" class="sv-tabs__scroll" tabindex="-1" aria-hidden="true" disabled={atStart} onclick={() => scrollBy(-1)}>
127
+ <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6" /></svg>
128
+ </button>
129
+ {/if}
130
+ <div bind:this={tablistEl} class="sv-tabs__list" bind:clientWidth={clientW} onscroll={(e) => (scrollLeft = e.currentTarget.scrollLeft)} {...tabsCtl.tablistProps()}>
131
+ {#each tabs as tab (tab.id)}
132
+ <span
133
+ class="sv-tabs__tabwrap"
134
+ class:is-active={tab.id === active}
135
+ class:is-closable={tab.closable}
136
+ class:is-drag={dragId === tab.id}
137
+ class:is-over={overId === tab.id}
138
+ role="presentation"
139
+ draggable={reorderable && !tab.disabled}
140
+ ondragstart={reorderable ? (e) => { dragId = tab.id; if (e.dataTransfer) e.dataTransfer.effectAllowed = 'move' } : undefined}
141
+ ondragover={reorderable ? (e) => { e.preventDefault(); overId = tab.id } : undefined}
142
+ ondragleave={reorderable ? () => { if (overId === tab.id) overId = null } : undefined}
143
+ ondrop={reorderable ? (e) => { e.preventDefault(); if (dragId) reorder(dragId, tab.id); dragId = null; overId = null } : undefined}
144
+ ondragend={reorderable ? () => { dragId = null; overId = null } : undefined}
145
+ >
146
+ <button type="button" class="sv-tabs__tab" class:is-active={tab.id === active} {...tabsCtl.tabProps(tab.id)}>{tab.label}</button>
147
+ {#if tab.closable}
148
+ <button type="button" class="sv-tabs__close" tabindex="-1" aria-label={`Close ${tab.label}`} onclick={(e) => { e.stopPropagation(); tabsCtl.close(tab.id) }}>
149
+ <svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M6 6l12 12M18 6L6 18" /></svg>
150
+ </button>
151
+ {/if}
152
+ </span>
153
+ {/each}
154
+ </div>
155
+ {#if overflowing}
156
+ <button type="button" class="sv-tabs__scroll" tabindex="-1" aria-hidden="true" disabled={atEnd} onclick={() => scrollBy(1)}>
157
+ <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6" /></svg>
158
+ </button>
159
+ {/if}
73
160
  </div>
74
161
  {#if panel}
75
162
  <div class="sv-tabs__panel" {...tabsCtl.panelProps(active)}>
@@ -80,30 +167,62 @@
80
167
 
81
168
  <style>
82
169
  .sv-tabs { --_accent: var(--sg-accent, #2563eb); display: flex; gap: 0; }
83
- .sv-tabs--horizontal { flex-direction: column; }
84
- .sv-tabs--vertical { flex-direction: row; }
170
+ .sv-tabs--top { flex-direction: column; }
171
+ .sv-tabs--bottom { flex-direction: column-reverse; }
172
+ .sv-tabs--left { flex-direction: row; }
173
+ .sv-tabs--right { flex-direction: row-reverse; }
174
+
175
+ .sv-tabs__strip { display: flex; align-items: center; min-width: 0; }
176
+ .sv-tabs--vertical .sv-tabs__strip { align-items: stretch; }
85
177
  .sv-tabs__list { display: flex; gap: 2px; }
86
- .sv-tabs--horizontal .sv-tabs__list { border-bottom: 1px solid var(--sg-border, #e2e8f0); }
87
- .sv-tabs--vertical .sv-tabs__list { flex-direction: column; border-right: 1px solid var(--sg-border, #e2e8f0); }
178
+ .sv-tabs--horizontal .sv-tabs__list { overflow-x: auto; flex: 1 1 auto; min-width: 0; scrollbar-width: none; scroll-behavior: smooth; }
179
+ .sv-tabs--horizontal .sv-tabs__list::-webkit-scrollbar { display: none; }
180
+ .sv-tabs--horizontal .sv-tabs__tabwrap { flex: none; }
181
+ .sv-tabs--vertical .sv-tabs__list { flex-direction: column; }
182
+ .sv-tabs__scroll {
183
+ flex: none; display: grid; place-items: center; width: 26px; align-self: stretch;
184
+ background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer;
185
+ }
186
+ .sv-tabs__scroll:hover:not(:disabled) { color: var(--_accent); }
187
+ .sv-tabs__scroll:disabled { opacity: 0.3; cursor: default; }
188
+ /* Drag-reorder feedback */
189
+ .sv-tabs__tabwrap.is-drag { opacity: 0.4; }
190
+ .sv-tabs__tabwrap.is-over::before {
191
+ content: ''; position: absolute; inset-inline-start: -1px; top: 5px; bottom: 5px; width: 2px;
192
+ background: var(--_accent); border-radius: 2px;
193
+ }
194
+ .sv-tabs__tabwrap[draggable='true'] .sv-tabs__tab { cursor: grab; }
195
+ .sv-tabs--top .sv-tabs__list { border-bottom: 1px solid var(--sg-border, #e2e8f0); }
196
+ .sv-tabs--bottom .sv-tabs__list { border-top: 1px solid var(--sg-border, #e2e8f0); }
197
+ .sv-tabs--left .sv-tabs__list { border-inline-end: 1px solid var(--sg-border, #e2e8f0); }
198
+ .sv-tabs--right .sv-tabs__list { border-inline-start: 1px solid var(--sg-border, #e2e8f0); }
88
199
  .sv-tabs--pill .sv-tabs__list { border: 0; background: var(--sg-header-bg, #f1f5f9); padding: 3px; border-radius: 9px; gap: 3px; }
89
200
 
201
+ .sv-tabs__tabwrap { position: relative; display: inline-flex; align-items: center; }
90
202
  .sv-tabs__tab {
91
203
  background: none; border: 0; font: inherit; font-size: 13px; font-weight: 600; color: var(--sg-muted, #64748b);
92
- padding: 9px 14px; cursor: pointer; white-space: nowrap; position: relative;
204
+ padding: 9px 14px; cursor: pointer; white-space: nowrap;
93
205
  }
206
+ .sv-tabs__tabwrap.is-closable .sv-tabs__tab { padding-inline-end: 4px; }
94
207
  .sv-tabs__tab:hover:not(:disabled):not(.is-active) { color: var(--sg-fg, #0f172a); }
95
208
  .sv-tabs__tab:disabled { opacity: 0.45; cursor: not-allowed; }
96
209
  .sv-tabs__tab:focus-visible { outline: 2px solid var(--sg-focus-ring, var(--_accent)); outline-offset: -2px; border-radius: 6px; }
97
210
 
98
- .sv-tabs--line .sv-tabs__tab.is-active { color: var(--_accent); }
99
- .sv-tabs--line .sv-tabs__tab.is-active::after {
100
- content: ''; position: absolute; left: 8px; right: 8px; height: 2px; background: var(--_accent);
211
+ .sv-tabs__close {
212
+ display: grid; place-items: center; width: 18px; height: 18px; margin-inline-end: 8px; flex: none;
213
+ background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; border-radius: 4px;
101
214
  }
102
- .sv-tabs--horizontal.sv-tabs--line .sv-tabs__tab.is-active::after { bottom: -1px; }
103
- .sv-tabs--vertical.sv-tabs--line .sv-tabs__tab.is-active::after { top: 8px; bottom: 8px; left: auto; right: -1px; width: 2px; height: auto; }
215
+ .sv-tabs__close:hover { background: var(--sg-row-hover-bg, #f1f5f9); color: var(--sg-danger, #dc2626); }
216
+
217
+ .sv-tabs--line .sv-tabs__tab.is-active { color: var(--_accent); }
218
+ .sv-tabs--line .sv-tabs__tabwrap.is-active::after { content: ''; position: absolute; background: var(--_accent); }
219
+ .sv-tabs--line.sv-tabs--top .sv-tabs__tabwrap.is-active::after { left: 8px; right: 8px; height: 2px; bottom: -1px; }
220
+ .sv-tabs--line.sv-tabs--bottom .sv-tabs__tabwrap.is-active::after { left: 8px; right: 8px; height: 2px; top: -1px; }
221
+ .sv-tabs--line.sv-tabs--left .sv-tabs__tabwrap.is-active::after { top: 8px; bottom: 8px; width: 2px; inset-inline-end: -1px; }
222
+ .sv-tabs--line.sv-tabs--right .sv-tabs__tabwrap.is-active::after { top: 8px; bottom: 8px; width: 2px; inset-inline-start: -1px; }
104
223
 
105
224
  .sv-tabs--pill .sv-tabs__tab { border-radius: 7px; padding: 7px 14px; }
106
- .sv-tabs--pill .sv-tabs__tab.is-active { background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a); box-shadow: 0 1px 2px rgba(0,0,0,0.08); }
225
+ .sv-tabs--pill .sv-tabs__tabwrap.is-active .sv-tabs__tab, .sv-tabs--pill .sv-tabs__tab.is-active { background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a); box-shadow: 0 1px 2px rgba(0,0,0,0.08); }
107
226
 
108
- .sv-tabs__panel { padding: 16px 2px; outline: none; }
227
+ .sv-tabs__panel { padding: 16px 2px; outline: none; flex: 1; min-width: 0; }
109
228
  </style>
@@ -7,21 +7,47 @@
7
7
  * One styled renderer over the headless `createTagsInput` core (draft +
8
8
  * add/remove + keyboard).
9
9
  */
10
+ import SvField from './SvField.svelte'
11
+ import { nextEditorId, resolveMessages, type SvEditorProps } from './editor-contract'
10
12
  import { createTagsInput } from './createTagsInput.svelte'
11
13
 
12
- type Props = {
14
+ /** User-facing strings (localizable via `messages`). */
15
+ type TagsMessages = { add: string; remove: string }
16
+ const DEFAULT_MESSAGES: TagsMessages = { add: 'Add tag', remove: 'Remove' }
17
+
18
+ type Props = SvEditorProps & {
13
19
  value?: string[]
14
20
  onChange?: (tags: string[]) => void
15
21
  placeholder?: string
16
- disabled?: boolean
17
22
  /** Reject duplicate tags. Default true. */
18
23
  unique?: boolean
19
24
  max?: number
20
- name?: string
21
- ariaLabel?: string
25
+ /** Override the built-in strings (draft + remove aria-labels). */
26
+ messages?: Partial<TagsMessages>
22
27
  }
23
28
 
24
- let { value = [], onChange, placeholder = 'Add tag…', disabled = false, unique = true, max = Infinity, name, ariaLabel }: Props = $props()
29
+ let {
30
+ value = [],
31
+ onChange,
32
+ placeholder = 'Add tag…',
33
+ disabled = false,
34
+ unique = true,
35
+ max = Infinity,
36
+ name,
37
+ ariaLabel,
38
+ invalid = false,
39
+ required = false,
40
+ error,
41
+ label,
42
+ hint,
43
+ dir,
44
+ id,
45
+ messages,
46
+ }: Props = $props()
47
+
48
+ const autoId = nextEditorId('sv-tags')
49
+ const uid = $derived(id ?? autoId)
50
+ const M = $derived(resolveMessages(DEFAULT_MESSAGES, messages))
25
51
 
26
52
  const ti = createTagsInput({
27
53
  value: () => value,
@@ -30,25 +56,34 @@
30
56
  unique: () => unique,
31
57
  max: () => max,
32
58
  ariaLabel: () => ariaLabel,
59
+ addLabel: () => M.add,
60
+ removeLabel: () => M.remove,
61
+ id: () => uid,
62
+ invalid: () => invalid,
63
+ required: () => required,
64
+ error: () => error,
65
+ hint: () => hint,
33
66
  })
34
67
  </script>
35
68
 
36
- <div class="sv-tags" class:is-disabled={disabled} {...ti.rootProps()}>
37
- {#each value as tag, i (tag + i)}
38
- <span class="sv-tags__chip" {...ti.tagProps(i)}>
39
- <span class="sv-tags__label">{tag}</span>
40
- {#if !disabled}
41
- <button class="sv-tags__x" {...ti.removeProps(i)}>&times;</button>
42
- {/if}
43
- </span>
44
- {/each}
45
- <input
46
- class="sv-tags__input"
47
- placeholder={value.length ? '' : placeholder}
48
- {...ti.inputProps()}
49
- />
50
- {#if name}<input type="hidden" {name} value={value.join(',')} />{/if}
51
- </div>
69
+ <SvField id={uid} {label} {hint} {error} {required} {dir}>
70
+ <div class="sv-tags" class:is-disabled={disabled} class:is-invalid={invalid} {...ti.rootProps()}>
71
+ {#each value as tag, i (tag + i)}
72
+ <span class="sv-tags__chip" {...ti.tagProps(i)}>
73
+ <span class="sv-tags__label">{tag}</span>
74
+ {#if !disabled}
75
+ <button class="sv-tags__x" {...ti.removeProps(i)}>&times;</button>
76
+ {/if}
77
+ </span>
78
+ {/each}
79
+ <input
80
+ class="sv-tags__input"
81
+ placeholder={value.length ? '' : placeholder}
82
+ {...ti.inputProps()}
83
+ />
84
+ {#if name}<input type="hidden" {name} value={value.join(',')} />{/if}
85
+ </div>
86
+ </SvField>
52
87
 
53
88
  <style>
54
89
  .sv-tags {
@@ -58,9 +93,11 @@
58
93
  border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
59
94
  }
60
95
  .sv-tags:focus-within { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
96
+ .sv-tags.is-invalid { border-color: var(--sg-danger, #dc2626); }
97
+ .sv-tags.is-invalid:focus-within { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
61
98
  .sv-tags.is-disabled { opacity: 0.6; }
62
99
  .sv-tags__chip {
63
- display: inline-flex; align-items: center; gap: 4px; height: 22px; padding: 0 4px 0 8px;
100
+ display: inline-flex; align-items: center; gap: 4px; height: 22px; padding-block: 0; padding-inline: 8px 4px;
64
101
  background: color-mix(in srgb, var(--_accent) 14%, transparent); color: var(--_accent);
65
102
  border-radius: 5px; font-size: 12px; font-weight: 600;
66
103
  }
@@ -0,0 +1,113 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SvTextArea - a multi-line text editor on the shared editor contract, with an
4
+ * optional auto-grow (the box height follows the content) and a character
5
+ * counter. Parity: Smart multiline input. As a grid cell editor: Escape
6
+ * cancels; Ctrl/Cmd+Enter commits (a bare Enter inserts a newline).
7
+ */
8
+ import SvField from './SvField.svelte'
9
+ import { editorAria, nextEditorId, type SvEditorProps } from './editor-contract'
10
+
11
+ type Props = SvEditorProps & {
12
+ value?: string
13
+ onChange?: (value: string) => void
14
+ onCommit?: (value: string) => void
15
+ onCancel?: () => void
16
+ placeholder?: string
17
+ rows?: number
18
+ maxlength?: number
19
+ /** Grow the box to fit content instead of scrolling. */
20
+ autoGrow?: boolean
21
+ /** Show a "n / max" character counter (needs `maxlength`). */
22
+ showCount?: boolean
23
+ autofocus?: boolean
24
+ }
25
+
26
+ let {
27
+ value = $bindable(''),
28
+ onChange,
29
+ onCommit,
30
+ onCancel,
31
+ placeholder,
32
+ disabled = false,
33
+ readonly = false,
34
+ rows = 3,
35
+ maxlength,
36
+ autoGrow = false,
37
+ showCount = false,
38
+ name,
39
+ size = 'md',
40
+ ariaLabel,
41
+ invalid = false,
42
+ required = false,
43
+ error,
44
+ label,
45
+ hint,
46
+ dir,
47
+ id,
48
+ autofocus = false,
49
+ }: Props = $props()
50
+
51
+ const autoId = nextEditorId('sv-textarea')
52
+ const uid = $derived(id ?? autoId)
53
+
54
+ function grow(node: HTMLTextAreaElement) {
55
+ if (!autoGrow) return
56
+ const resize = () => { node.style.height = 'auto'; node.style.height = `${node.scrollHeight}px` }
57
+ resize()
58
+ node.addEventListener('input', resize)
59
+ return { destroy: () => node.removeEventListener('input', resize) }
60
+ }
61
+ function mountFocus(node: HTMLTextAreaElement) {
62
+ if (autofocus) { node.focus(); node.select() }
63
+ }
64
+ function onInput(e: Event) {
65
+ const v = (e.currentTarget as HTMLTextAreaElement).value
66
+ value = v // enables bind:value
67
+ onChange?.(v)
68
+ }
69
+ function onKeydown(e: KeyboardEvent) {
70
+ if (e.key === 'Escape') onCancel?.()
71
+ else if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { e.preventDefault(); onCommit?.(value) }
72
+ }
73
+ </script>
74
+
75
+ <SvField id={uid} {label} {hint} {error} {required} {dir}>
76
+ <div class="sv-ta sv-ta--{size}" class:is-disabled={disabled}>
77
+ <textarea
78
+ use:grow
79
+ use:mountFocus
80
+ class="sv-ta__input"
81
+ class:is-invalid={invalid}
82
+ {value}
83
+ {placeholder}
84
+ {disabled}
85
+ {readonly}
86
+ {rows}
87
+ {maxlength}
88
+ oninput={onInput}
89
+ onkeydown={onKeydown}
90
+ {...editorAria({ id: uid, invalid, required, error, hint, ariaLabel })}
91
+ ></textarea>
92
+ {#if showCount && maxlength}
93
+ <span class="sv-ta__count" aria-hidden="true">{value.length} / {maxlength}</span>
94
+ {/if}
95
+ {#if name}<input type="hidden" {name} value={value} />{/if}
96
+ </div>
97
+ </SvField>
98
+
99
+ <style>
100
+ .sv-ta { --_accent: var(--sg-accent, #2563eb); display: inline-flex; flex-direction: column; gap: 3px; width: 260px; }
101
+ .sv-ta.is-disabled { opacity: 0.6; }
102
+ .sv-ta__input {
103
+ width: 100%; box-sizing: border-box; resize: vertical; font: inherit; padding: 8px 10px;
104
+ background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
105
+ border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px); outline: none;
106
+ }
107
+ .sv-ta--sm .sv-ta__input { font-size: 12px; }
108
+ .sv-ta--md .sv-ta__input { font-size: 13px; }
109
+ .sv-ta--lg .sv-ta__input { font-size: 15px; }
110
+ .sv-ta__input:focus { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
111
+ .sv-ta__input.is-invalid { border-color: var(--sg-danger, #dc2626); }
112
+ .sv-ta__count { align-self: flex-end; font-size: 11px; color: var(--sg-muted, #64748b); }
113
+ </style>