@svgrid/grid 1.2.34 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (444) hide show
  1. package/README.md +6 -5
  2. package/dist/SvAccordion.svelte +109 -0
  3. package/dist/SvAccordion.svelte.d.ts +28 -0
  4. package/dist/SvAlert.svelte +92 -0
  5. package/dist/SvAlert.svelte.d.ts +27 -0
  6. package/dist/SvAutoComplete.svelte +47 -18
  7. package/dist/SvAutoComplete.svelte.d.ts +2 -5
  8. package/dist/SvAvatar.svelte +75 -0
  9. package/dist/SvAvatar.svelte.d.ts +14 -0
  10. package/dist/SvAvatarGroup.svelte +48 -0
  11. package/dist/SvAvatarGroup.svelte.d.ts +16 -0
  12. package/dist/SvBadge.svelte +54 -0
  13. package/dist/SvBadge.svelte.d.ts +23 -0
  14. package/dist/SvBreadcrumb.svelte +88 -0
  15. package/dist/SvBreadcrumb.svelte.d.ts +23 -0
  16. package/dist/SvButtonGroup.svelte +121 -0
  17. package/dist/SvButtonGroup.svelte.d.ts +16 -0
  18. package/dist/SvCalendar.svelte +72 -9
  19. package/dist/SvCalendar.svelte.d.ts +34 -1
  20. package/dist/SvCard.svelte +71 -0
  21. package/dist/SvCard.svelte.d.ts +28 -0
  22. package/dist/SvCarousel.svelte +134 -0
  23. package/dist/SvCarousel.svelte.d.ts +28 -0
  24. package/dist/SvCheckBox.svelte +36 -21
  25. package/dist/SvCheckBox.svelte.d.ts +2 -5
  26. package/dist/SvChip.svelte +88 -0
  27. package/dist/SvChip.svelte.d.ts +31 -0
  28. package/dist/SvCircularProgress.svelte +109 -0
  29. package/dist/SvCircularProgress.svelte.d.ts +31 -0
  30. package/dist/SvColorInput.svelte +54 -27
  31. package/dist/SvColorInput.svelte.d.ts +10 -6
  32. package/dist/SvComboBox.svelte +119 -33
  33. package/dist/SvComboBox.svelte.d.ts +18 -6
  34. package/dist/SvCommand.svelte +197 -0
  35. package/dist/SvCommand.svelte.d.ts +26 -0
  36. package/dist/SvContextMenu.svelte +96 -0
  37. package/dist/SvContextMenu.svelte.d.ts +25 -0
  38. package/dist/SvCountryInput.svelte +56 -21
  39. package/dist/SvCountryInput.svelte.d.ts +9 -5
  40. package/dist/SvDateRangeInput.svelte +256 -0
  41. package/dist/SvDateRangeInput.svelte.d.ts +37 -0
  42. package/dist/SvDateTimePicker.svelte +57 -20
  43. package/dist/SvDateTimePicker.svelte.d.ts +12 -4
  44. package/dist/SvDivider.svelte +61 -0
  45. package/dist/SvDivider.svelte.d.ts +22 -0
  46. package/dist/SvDrawer.svelte +226 -0
  47. package/dist/SvDrawer.svelte.d.ts +43 -0
  48. package/dist/SvDropDownList.svelte +114 -35
  49. package/dist/SvDropDownList.svelte.d.ts +7 -6
  50. package/dist/SvDurationInput.svelte +108 -0
  51. package/dist/SvDurationInput.svelte.d.ts +15 -0
  52. package/dist/SvEmptyState.svelte +59 -0
  53. package/dist/SvEmptyState.svelte.d.ts +25 -0
  54. package/dist/SvField.svelte +85 -0
  55. package/dist/SvField.svelte.d.ts +30 -0
  56. package/dist/SvFileUpload.svelte +169 -0
  57. package/dist/SvFileUpload.svelte.d.ts +24 -0
  58. package/dist/SvForm.svelte +7 -1
  59. package/dist/SvForm.svelte.d.ts +3 -0
  60. package/dist/SvGauge.svelte +7 -1
  61. package/dist/SvGauge.svelte.d.ts +11 -0
  62. package/dist/SvGrid.controller.svelte.d.ts +6 -0
  63. package/dist/SvGrid.controller.svelte.js +64 -9
  64. package/dist/SvGrid.css +33 -0
  65. package/dist/SvGrid.svelte +116 -3
  66. package/dist/SvGrid.types.d.ts +366 -0
  67. package/dist/SvGridBoard.svelte +2281 -0
  68. package/dist/SvGridBoard.svelte.d.ts +31 -0
  69. package/dist/SvGridDropdown.svelte +2 -1
  70. package/dist/SvGridSelect.svelte +255 -0
  71. package/dist/SvGridSelect.svelte.d.ts +32 -0
  72. package/dist/SvGroupCell.svelte +109 -0
  73. package/dist/SvGroupCell.svelte.d.ts +41 -0
  74. package/dist/SvListBox.svelte +123 -36
  75. package/dist/SvListBox.svelte.d.ts +12 -8
  76. package/dist/SvMaskedInput.svelte +53 -13
  77. package/dist/SvMaskedInput.svelte.d.ts +16 -6
  78. package/dist/SvMenu.svelte +101 -0
  79. package/dist/SvMenu.svelte.d.ts +26 -0
  80. package/dist/SvMenuList.svelte +143 -0
  81. package/dist/SvMenuList.svelte.d.ts +24 -0
  82. package/dist/SvModal.svelte +220 -0
  83. package/dist/SvModal.svelte.d.ts +31 -0
  84. package/dist/SvMultiSelect.svelte +277 -0
  85. package/dist/SvMultiSelect.svelte.d.ts +31 -0
  86. package/dist/SvNavPane.svelte +314 -0
  87. package/dist/SvNavPane.svelte.d.ts +59 -0
  88. package/dist/SvNumberInput.svelte +23 -9
  89. package/dist/SvNumberInput.svelte.d.ts +2 -0
  90. package/dist/SvOtpInput.svelte +157 -0
  91. package/dist/SvOtpInput.svelte.d.ts +16 -0
  92. package/dist/SvPagination.svelte +100 -0
  93. package/dist/SvPagination.svelte.d.ts +16 -0
  94. package/dist/SvPasswordInput.svelte +42 -12
  95. package/dist/SvPasswordInput.svelte.d.ts +13 -6
  96. package/dist/SvPhoneInput.svelte +43 -11
  97. package/dist/SvPhoneInput.svelte.d.ts +10 -11
  98. package/dist/SvPopover.svelte +127 -0
  99. package/dist/SvPopover.svelte.d.ts +35 -0
  100. package/dist/SvProgress.svelte +116 -0
  101. package/dist/SvProgress.svelte.d.ts +32 -0
  102. package/dist/SvRadioGroup.svelte +40 -22
  103. package/dist/SvRadioGroup.svelte.d.ts +2 -10
  104. package/dist/SvRating.svelte +51 -30
  105. package/dist/SvRating.svelte.d.ts +10 -7
  106. package/dist/SvRichText.svelte +192 -0
  107. package/dist/SvRichText.svelte.d.ts +16 -0
  108. package/dist/SvRowGroupPanel.svelte +147 -0
  109. package/dist/SvRowGroupPanel.svelte.d.ts +17 -0
  110. package/dist/SvScrollArea.svelte +50 -0
  111. package/dist/SvScrollArea.svelte.d.ts +22 -0
  112. package/dist/SvSkeleton.svelte +76 -0
  113. package/dist/SvSkeleton.svelte.d.ts +25 -0
  114. package/dist/SvSlider.svelte +41 -9
  115. package/dist/SvSlider.svelte.d.ts +4 -4
  116. package/dist/SvSparkline.svelte +77 -0
  117. package/dist/SvSparkline.svelte.d.ts +28 -0
  118. package/dist/SvSplitter.svelte +138 -0
  119. package/dist/SvSplitter.svelte.d.ts +32 -0
  120. package/dist/SvStat.svelte +86 -0
  121. package/dist/SvStat.svelte.d.ts +30 -0
  122. package/dist/SvStepper.svelte +91 -0
  123. package/dist/SvStepper.svelte.d.ts +24 -0
  124. package/dist/SvSwitchButton.svelte +39 -24
  125. package/dist/SvSwitchButton.svelte.d.ts +2 -5
  126. package/dist/SvTabs.svelte +147 -28
  127. package/dist/SvTabs.svelte.d.ts +16 -4
  128. package/dist/SvTagsInput.svelte +59 -22
  129. package/dist/SvTagsInput.svelte.d.ts +9 -4
  130. package/dist/SvTextArea.svelte +113 -0
  131. package/dist/SvTextArea.svelte.d.ts +18 -0
  132. package/dist/SvTextInput.svelte +115 -0
  133. package/dist/SvTextInput.svelte.d.ts +20 -0
  134. package/dist/SvTimePicker.svelte +19 -5
  135. package/dist/SvTimePicker.svelte.d.ts +12 -0
  136. package/dist/SvTimeline.svelte +75 -0
  137. package/dist/SvTimeline.svelte.d.ts +24 -0
  138. package/dist/SvToaster.svelte +124 -0
  139. package/dist/SvToaster.svelte.d.ts +10 -0
  140. package/dist/SvToggleButton.svelte +37 -12
  141. package/dist/SvToggleButton.svelte.d.ts +2 -4
  142. package/dist/SvTooltip.svelte +113 -0
  143. package/dist/SvTooltip.svelte.d.ts +22 -0
  144. package/dist/SvTour.svelte +151 -0
  145. package/dist/SvTour.svelte.d.ts +25 -0
  146. package/dist/SvTree.svelte +286 -44
  147. package/dist/SvTree.svelte.d.ts +26 -9
  148. package/dist/SvTreeSelect.svelte +222 -0
  149. package/dist/SvTreeSelect.svelte.d.ts +24 -0
  150. package/dist/a11y/dismissable.d.ts +32 -0
  151. package/dist/a11y/dismissable.js +54 -0
  152. package/dist/a11y/focus-trap.d.ts +39 -0
  153. package/dist/a11y/focus-trap.js +142 -0
  154. package/dist/a11y/live-region.d.ts +8 -0
  155. package/dist/a11y/live-region.js +60 -0
  156. package/dist/a11y/scroll-lock.d.ts +4 -0
  157. package/dist/a11y/scroll-lock.js +41 -0
  158. package/dist/a11y.d.ts +3 -1
  159. package/dist/a11y.js +1 -1
  160. package/dist/avatar.d.ts +10 -0
  161. package/dist/avatar.js +22 -0
  162. package/dist/builtin-editors.d.ts +5 -0
  163. package/dist/builtin-editors.js +55 -0
  164. package/dist/cdn/svgrid.js +16021 -8234
  165. package/dist/cdn/svgrid.svelte-external.js +14542 -6764
  166. package/dist/columns.js +3 -2
  167. package/dist/core.d.ts +7 -1
  168. package/dist/countries.d.ts +12 -0
  169. package/dist/countries.js +24 -0
  170. package/dist/createAccordion.svelte.d.ts +71 -0
  171. package/dist/createAccordion.svelte.js +99 -0
  172. package/dist/createAutocomplete.svelte.d.ts +13 -4
  173. package/dist/createAutocomplete.svelte.js +11 -1
  174. package/dist/createButtonGroup.svelte.d.ts +62 -0
  175. package/dist/createButtonGroup.svelte.js +154 -0
  176. package/dist/createCalendar.svelte.d.ts +5 -0
  177. package/dist/createCalendar.svelte.js +2 -0
  178. package/dist/createCheckbox.svelte.d.ts +9 -17
  179. package/dist/createCheckbox.svelte.js +28 -1
  180. package/dist/createColorInput.svelte.d.ts +12 -22
  181. package/dist/createColorInput.svelte.js +13 -0
  182. package/dist/createCombobox.svelte.d.ts +16 -5
  183. package/dist/createCombobox.svelte.js +13 -2
  184. package/dist/createCountryInput.svelte.d.ts +14 -3
  185. package/dist/createCountryInput.svelte.js +12 -2
  186. package/dist/createDateTimePicker.svelte.d.ts +17 -2
  187. package/dist/createDateTimePicker.svelte.js +12 -2
  188. package/dist/createDropdownList.svelte.d.ts +14 -5
  189. package/dist/createDropdownList.svelte.js +50 -1
  190. package/dist/createFileUpload.svelte.d.ts +68 -0
  191. package/dist/createFileUpload.svelte.js +122 -0
  192. package/dist/createListbox.svelte.d.ts +10 -1
  193. package/dist/createListbox.svelte.js +48 -1
  194. package/dist/createMaskedInput.svelte.d.ts +11 -2
  195. package/dist/createMaskedInput.svelte.js +11 -1
  196. package/dist/createNumberInput.svelte.d.ts +1 -0
  197. package/dist/createNumberInput.svelte.js +1 -0
  198. package/dist/createPasswordInput.svelte.d.ts +19 -21
  199. package/dist/createPasswordInput.svelte.js +16 -4
  200. package/dist/createPhoneInput.svelte.d.ts +26 -7
  201. package/dist/createPhoneInput.svelte.js +24 -3
  202. package/dist/createPopoverSelect.svelte.d.ts +86 -0
  203. package/dist/createPopoverSelect.svelte.js +196 -0
  204. package/dist/createRadioGroup.svelte.d.ts +9 -0
  205. package/dist/createRadioGroup.svelte.js +11 -1
  206. package/dist/createRating.svelte.d.ts +11 -0
  207. package/dist/createRating.svelte.js +12 -1
  208. package/dist/createSlider.svelte.d.ts +2 -0
  209. package/dist/createSlider.svelte.js +16 -5
  210. package/dist/createSplitter.svelte.d.ts +67 -0
  211. package/dist/createSplitter.svelte.js +99 -0
  212. package/dist/createSwitch.svelte.d.ts +9 -16
  213. package/dist/createSwitch.svelte.js +28 -1
  214. package/dist/createTabs.svelte.d.ts +7 -0
  215. package/dist/createTabs.svelte.js +17 -2
  216. package/dist/createTagsInput.svelte.d.ts +13 -1
  217. package/dist/createTagsInput.svelte.js +12 -3
  218. package/dist/createToggle.svelte.d.ts +9 -0
  219. package/dist/createToggle.svelte.js +12 -1
  220. package/dist/createTree.svelte.d.ts +19 -0
  221. package/dist/createTree.svelte.js +108 -15
  222. package/dist/debounce.d.ts +11 -0
  223. package/dist/debounce.js +30 -0
  224. package/dist/duration.d.ts +15 -0
  225. package/dist/duration.js +51 -0
  226. package/dist/editor-contract.d.ts +62 -7
  227. package/dist/editor-contract.js +45 -5
  228. package/dist/editor-registry.d.ts +67 -0
  229. package/dist/editor-registry.js +38 -0
  230. package/dist/editors/cell-editors.d.ts +1 -1
  231. package/dist/editors/cell-editors.js +5 -0
  232. package/dist/fuzzy.d.ts +3 -0
  233. package/dist/fuzzy.js +41 -0
  234. package/dist/grid-select.d.ts +9 -0
  235. package/dist/grid-select.js +8 -0
  236. package/dist/index.d.ts +79 -6
  237. package/dist/index.js +78 -4
  238. package/dist/keyboard-handlers.js +19 -0
  239. package/dist/list-option.d.ts +36 -0
  240. package/dist/list-option.js +68 -0
  241. package/dist/otp.d.ts +16 -0
  242. package/dist/otp.js +26 -0
  243. package/dist/paginate.d.ts +20 -0
  244. package/dist/paginate.js +40 -0
  245. package/dist/popover.d.ts +15 -1
  246. package/dist/popover.js +29 -3
  247. package/dist/recurrence.d.ts +36 -0
  248. package/dist/recurrence.js +90 -0
  249. package/dist/server-data-source.d.ts +89 -0
  250. package/dist/server-data-source.js +4 -0
  251. package/dist/server-group-model.d.ts +98 -0
  252. package/dist/server-group-model.js +263 -0
  253. package/dist/toast-store.svelte.d.ts +41 -0
  254. package/dist/toast-store.svelte.js +113 -0
  255. package/dist/tree-select.d.ts +19 -0
  256. package/dist/tree-select.js +40 -0
  257. package/dist/ui-app.types.d.ts +80 -0
  258. package/dist/ui-app.types.js +7 -0
  259. package/dist/validators.d.ts +43 -0
  260. package/dist/validators.js +53 -0
  261. package/dist/virtual.d.ts +35 -0
  262. package/dist/virtual.js +34 -0
  263. package/package.json +1 -1
  264. package/src/SvAccordion.svelte +109 -0
  265. package/src/SvAccordion.test.ts +66 -0
  266. package/src/SvAlert.svelte +92 -0
  267. package/src/SvAutoComplete.svelte +47 -18
  268. package/src/SvAvatar.svelte +75 -0
  269. package/src/SvAvatarGroup.svelte +48 -0
  270. package/src/SvBadge.svelte +54 -0
  271. package/src/SvBreadcrumb.svelte +88 -0
  272. package/src/SvButtonGroup.svelte +121 -0
  273. package/src/SvButtonGroup.test.ts +72 -0
  274. package/src/SvCalendar.svelte +72 -9
  275. package/src/SvCard.svelte +71 -0
  276. package/src/SvCarousel.svelte +134 -0
  277. package/src/SvCheckBox.svelte +36 -21
  278. package/src/SvChip.svelte +88 -0
  279. package/src/SvCircularProgress.svelte +109 -0
  280. package/src/SvColorInput.svelte +54 -27
  281. package/src/SvComboBox.svelte +119 -33
  282. package/src/SvCommand.svelte +197 -0
  283. package/src/SvContextMenu.svelte +96 -0
  284. package/src/SvCountryInput.svelte +56 -21
  285. package/src/SvDateRangeInput.svelte +256 -0
  286. package/src/SvDateRangeInput.test.ts +80 -0
  287. package/src/SvDateTimePicker.svelte +57 -20
  288. package/src/SvDivider.svelte +61 -0
  289. package/src/SvDrawer.svelte +226 -0
  290. package/src/SvDrawer.test.ts +125 -0
  291. package/src/SvDropDownList.svelte +114 -35
  292. package/src/SvDurationInput.svelte +108 -0
  293. package/src/SvEmptyState.svelte +59 -0
  294. package/src/SvField.svelte +85 -0
  295. package/src/SvFileUpload.svelte +169 -0
  296. package/src/SvFileUpload.test.ts +59 -0
  297. package/src/SvForm.svelte +7 -1
  298. package/src/SvGauge.svelte +7 -1
  299. package/src/SvGrid.controller.svelte.ts +68 -9
  300. package/src/SvGrid.css +33 -0
  301. package/src/SvGrid.svelte +116 -3
  302. package/src/SvGrid.types.ts +374 -0
  303. package/src/SvGridBoard.svelte +2281 -0
  304. package/src/SvGridDropdown.svelte +2 -1
  305. package/src/SvGridSelect.svelte +255 -0
  306. package/src/SvGridSelect.test.ts +100 -0
  307. package/src/SvGroupCell.svelte +109 -0
  308. package/src/SvListBox.svelte +123 -36
  309. package/src/SvMaskedInput.svelte +53 -13
  310. package/src/SvMenu.svelte +101 -0
  311. package/src/SvMenu.test.ts +73 -0
  312. package/src/SvMenuList.svelte +143 -0
  313. package/src/SvModal.svelte +220 -0
  314. package/src/SvMultiSelect.svelte +277 -0
  315. package/src/SvMultiSelect.test.ts +167 -0
  316. package/src/SvNavPane.svelte +314 -0
  317. package/src/SvNavPane.test.ts +70 -0
  318. package/src/SvNumberInput.svelte +23 -9
  319. package/src/SvOtpInput.svelte +157 -0
  320. package/src/SvPagination.svelte +100 -0
  321. package/src/SvPasswordInput.svelte +42 -12
  322. package/src/SvPhoneInput.svelte +43 -11
  323. package/src/SvPopover.svelte +127 -0
  324. package/src/SvProgress.svelte +116 -0
  325. package/src/SvRadioGroup.svelte +40 -22
  326. package/src/SvRating.svelte +51 -30
  327. package/src/SvRichText.svelte +192 -0
  328. package/src/SvRowGroupPanel.svelte +147 -0
  329. package/src/SvScrollArea.svelte +50 -0
  330. package/src/SvSkeleton.svelte +76 -0
  331. package/src/SvSlider.svelte +41 -9
  332. package/src/SvSparkline.svelte +77 -0
  333. package/src/SvSplitter.svelte +138 -0
  334. package/src/SvSplitter.test.ts +55 -0
  335. package/src/SvStat.svelte +86 -0
  336. package/src/SvStepper.svelte +91 -0
  337. package/src/SvSwitchButton.svelte +39 -24
  338. package/src/SvTabs.svelte +147 -28
  339. package/src/SvTagsInput.svelte +59 -22
  340. package/src/SvTextArea.svelte +113 -0
  341. package/src/SvTextInput.svelte +115 -0
  342. package/src/SvTimePicker.svelte +19 -5
  343. package/src/SvTimeline.svelte +75 -0
  344. package/src/SvToaster.svelte +124 -0
  345. package/src/SvToaster.test.ts +63 -0
  346. package/src/SvToggleButton.svelte +37 -12
  347. package/src/SvTooltip.svelte +113 -0
  348. package/src/SvTour.svelte +151 -0
  349. package/src/SvTree.svelte +286 -44
  350. package/src/SvTreeSelect.svelte +222 -0
  351. package/src/SvTreeSelect.test.ts +87 -0
  352. package/src/a11y/dismissable.test.ts +95 -0
  353. package/src/a11y/dismissable.ts +94 -0
  354. package/src/a11y/focus-trap.test.ts +110 -0
  355. package/src/a11y/focus-trap.ts +134 -0
  356. package/src/a11y/live-region.test.ts +40 -0
  357. package/src/a11y/live-region.ts +65 -0
  358. package/src/a11y/scroll-lock.test.ts +38 -0
  359. package/src/a11y/scroll-lock.ts +41 -0
  360. package/src/a11y.ts +3 -1
  361. package/src/avatar.test.ts +32 -0
  362. package/src/avatar.ts +21 -0
  363. package/src/board.test.ts +927 -0
  364. package/src/builtin-editors.grid.test.ts +85 -0
  365. package/src/builtin-editors.test.ts +58 -0
  366. package/src/builtin-editors.ts +56 -0
  367. package/src/columns.ts +3 -2
  368. package/src/core.ts +10 -0
  369. package/src/countries.ts +25 -0
  370. package/src/createAccordion.svelte.ts +126 -0
  371. package/src/createAutocomplete.svelte.ts +18 -1
  372. package/src/createButtonGroup.svelte.ts +166 -0
  373. package/src/createCalendar.svelte.ts +6 -0
  374. package/src/createCheckbox.svelte.ts +23 -1
  375. package/src/createColorInput.svelte.ts +20 -0
  376. package/src/createCombobox.svelte.ts +22 -2
  377. package/src/createCountryInput.svelte.ts +21 -2
  378. package/src/createDateTimePicker.svelte.ts +23 -2
  379. package/src/createDropdownList.svelte.ts +31 -2
  380. package/src/createFileUpload.svelte.ts +144 -0
  381. package/src/createListbox.svelte.ts +35 -3
  382. package/src/createMaskedInput.svelte.ts +18 -1
  383. package/src/createNumberInput.svelte.ts +2 -0
  384. package/src/createPasswordInput.svelte.ts +31 -5
  385. package/src/createPhoneInput.svelte.ts +38 -4
  386. package/src/createPopoverSelect.svelte.ts +208 -0
  387. package/src/createRadioGroup.svelte.ts +23 -1
  388. package/src/createRating.svelte.ts +26 -1
  389. package/src/createSlider.svelte.ts +14 -3
  390. package/src/createSplitter.svelte.ts +119 -0
  391. package/src/createSwitch.svelte.ts +26 -3
  392. package/src/createTabs.svelte.ts +20 -3
  393. package/src/createTagsInput.svelte.ts +24 -3
  394. package/src/createToggle.svelte.ts +25 -2
  395. package/src/createTree.svelte.ts +104 -14
  396. package/src/debounce.test.ts +50 -0
  397. package/src/debounce.ts +41 -0
  398. package/src/duration.test.ts +41 -0
  399. package/src/duration.ts +51 -0
  400. package/src/editor-contract.ts +87 -8
  401. package/src/editor-registry.grid.test.ts +98 -0
  402. package/src/editor-registry.test.ts +86 -0
  403. package/src/editor-registry.ts +106 -0
  404. package/src/editors/cell-editors.ts +10 -1
  405. package/src/fuzzy.test.ts +40 -0
  406. package/src/fuzzy.ts +39 -0
  407. package/src/grid-select.test.ts +28 -0
  408. package/src/grid-select.ts +20 -0
  409. package/src/index.ts +150 -5
  410. package/src/keyboard-handlers.ts +20 -0
  411. package/src/list-option.ts +74 -0
  412. package/src/list-power.test.ts +71 -0
  413. package/src/otp.test.ts +28 -0
  414. package/src/otp.ts +28 -0
  415. package/src/overlays.test.ts +85 -0
  416. package/src/paginate.test.ts +41 -0
  417. package/src/paginate.ts +61 -0
  418. package/src/phone-valid.test.ts +26 -0
  419. package/src/popover.ts +31 -3
  420. package/src/progress.test.ts +55 -0
  421. package/src/recurrence.test.ts +91 -0
  422. package/src/recurrence.ts +111 -0
  423. package/src/server-data-source.ts +102 -0
  424. package/src/server-group-model.test.ts +294 -0
  425. package/src/server-group-model.ts +370 -0
  426. package/src/svgrid.responsive.test.ts +72 -0
  427. package/src/toast-store.svelte.ts +148 -0
  428. package/src/toast-store.test.ts +86 -0
  429. package/src/tree-edit.test.ts +49 -0
  430. package/src/tree-filter.test.ts +56 -0
  431. package/src/tree-select.test.ts +48 -0
  432. package/src/tree-select.ts +62 -0
  433. package/src/ui-app.test.ts +130 -0
  434. package/src/ui-app.types.ts +88 -0
  435. package/src/ui-display.test.ts +79 -0
  436. package/src/ui-inputs.test.ts +1 -1
  437. package/src/ui-status.test.ts +141 -0
  438. package/src/ui-tier1.test.ts +142 -0
  439. package/src/ui-tier2.test.ts +124 -0
  440. package/src/ui-tier3.test.ts +139 -0
  441. package/src/validators.test.ts +58 -0
  442. package/src/validators.ts +105 -0
  443. package/src/virtual.test.ts +72 -0
  444. package/src/virtual.ts +67 -0
@@ -0,0 +1,86 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SvStat - a KPI / metric card: a label, a big value, and an optional delta with
4
+ * an up/down trend (auto-coloured; green up / red down, or inverted for
5
+ * "lower is better" metrics), plus slots for an icon and an inline chart
6
+ * (e.g. a sparkline). Theme-driven. The dashboard building block.
7
+ *
8
+ * ```svelte
9
+ * <SvStat label="Revenue" value="$48.2k" delta={12.4} hint="vs last month" />
10
+ * ```
11
+ */
12
+ import type { Snippet } from 'svelte'
13
+
14
+ type Trend = 'up' | 'down' | 'flat'
15
+
16
+ type Props = {
17
+ label: string
18
+ value: string | number
19
+ /** A number (auto-signs + infers trend) or a preformatted string. */
20
+ delta?: string | number
21
+ /** Override the inferred trend. */
22
+ trend?: Trend
23
+ /** Text after the delta (e.g. "vs last month"). */
24
+ hint?: string
25
+ /** Treat a downward delta as good (green) - e.g. error rate, latency. */
26
+ invert?: boolean
27
+ icon?: Snippet
28
+ /** Inline chart / sparkline slot. */
29
+ chart?: Snippet
30
+ }
31
+
32
+ let { label, value, delta, trend, hint, invert = false, icon, chart }: Props = $props()
33
+
34
+ const resolvedTrend = $derived<Trend | undefined>(
35
+ trend ?? (typeof delta === 'number' ? (delta > 0 ? 'up' : delta < 0 ? 'down' : 'flat') : undefined),
36
+ )
37
+ const deltaText = $derived(
38
+ delta == null ? null : typeof delta === 'number' ? `${delta > 0 ? '+' : ''}${delta}%` : delta,
39
+ )
40
+ const arrow = $derived(resolvedTrend === 'up' ? '▲' : resolvedTrend === 'down' ? '▼' : '→')
41
+ // Which trend is "positive" (green): up, unless inverted.
42
+ const tone = $derived(
43
+ resolvedTrend === 'flat' || !resolvedTrend
44
+ ? 'flat'
45
+ : (resolvedTrend === 'up') !== invert
46
+ ? 'good'
47
+ : 'bad',
48
+ )
49
+ </script>
50
+
51
+ <div class="sv-stat">
52
+ <div class="sv-stat__head">
53
+ <span class="sv-stat__label">{label}</span>
54
+ {#if icon}<span class="sv-stat__icon" aria-hidden="true">{@render icon()}</span>{/if}
55
+ </div>
56
+ <div class="sv-stat__value">{value}</div>
57
+ {#if (deltaText != null) || hint || chart}
58
+ <div class="sv-stat__foot">
59
+ {#if deltaText != null && resolvedTrend}
60
+ <span class="sv-stat__delta is-{tone}"><span class="sv-stat__arrow" aria-hidden="true">{arrow}</span>{deltaText}</span>
61
+ {/if}
62
+ {#if hint}<span class="sv-stat__hint">{hint}</span>{/if}
63
+ {#if chart}<span class="sv-stat__chart">{@render chart()}</span>{/if}
64
+ </div>
65
+ {/if}
66
+ </div>
67
+
68
+ <style>
69
+ .sv-stat {
70
+ display: flex; flex-direction: column; gap: 5px;
71
+ padding: 14px 16px; border: 1px solid var(--sg-border, #e2e8f0); border-radius: var(--sg-radius-lg, 12px);
72
+ background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a); min-width: 0;
73
+ }
74
+ .sv-stat__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
75
+ .sv-stat__label { font-size: 12.5px; font-weight: 600; color: var(--sg-muted, #64748b); }
76
+ .sv-stat__icon { color: var(--sg-muted, #94a3b8); display: inline-flex; }
77
+ .sv-stat__value { font-size: 26px; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; }
78
+ .sv-stat__foot { display: flex; align-items: center; gap: 8px; font-size: 12px; }
79
+ .sv-stat__delta { display: inline-flex; align-items: center; gap: 3px; font-weight: 650; font-variant-numeric: tabular-nums; }
80
+ .sv-stat__arrow { font-size: 9px; }
81
+ .sv-stat__delta.is-good { color: var(--sg-success, #16a34a); }
82
+ .sv-stat__delta.is-bad { color: var(--sg-danger, #dc2626); }
83
+ .sv-stat__delta.is-flat { color: var(--sg-muted, #64748b); }
84
+ .sv-stat__hint { color: var(--sg-muted, #94a3b8); }
85
+ .sv-stat__chart { margin-inline-start: auto; }
86
+ </style>
@@ -0,0 +1,30 @@
1
+ /**
2
+ * SvStat - a KPI / metric card: a label, a big value, and an optional delta with
3
+ * an up/down trend (auto-coloured; green up / red down, or inverted for
4
+ * "lower is better" metrics), plus slots for an icon and an inline chart
5
+ * (e.g. a sparkline). Theme-driven. The dashboard building block.
6
+ *
7
+ * ```svelte
8
+ * <SvStat label="Revenue" value="$48.2k" delta={12.4} hint="vs last month" />
9
+ * ```
10
+ */
11
+ import type { Snippet } from 'svelte';
12
+ type Trend = 'up' | 'down' | 'flat';
13
+ type Props = {
14
+ label: string;
15
+ value: string | number;
16
+ /** A number (auto-signs + infers trend) or a preformatted string. */
17
+ delta?: string | number;
18
+ /** Override the inferred trend. */
19
+ trend?: Trend;
20
+ /** Text after the delta (e.g. "vs last month"). */
21
+ hint?: string;
22
+ /** Treat a downward delta as good (green) - e.g. error rate, latency. */
23
+ invert?: boolean;
24
+ icon?: Snippet;
25
+ /** Inline chart / sparkline slot. */
26
+ chart?: Snippet;
27
+ };
28
+ declare const SvStat: import("svelte").Component<Props, {}, "">;
29
+ type SvStat = ReturnType<typeof SvStat>;
30
+ export default SvStat;
@@ -0,0 +1,91 @@
1
+ <script lang="ts" module>
2
+ export type { StepItem } from './ui-app.types'
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ /**
7
+ * SvStepper - a progress stepper for wizards/multi-step forms. Shows completed
8
+ * / active / upcoming steps horizontally or vertically. In `linear` mode only
9
+ * completed steps (and the active one) are clickable; otherwise any step is.
10
+ * Parity: Smart steps / wizard nav.
11
+ *
12
+ * ```svelte
13
+ * <SvStepper steps={steps} current={i} onChange={(n) => (i = n)} />
14
+ * ```
15
+ */
16
+ import type { StepItem } from './ui-app.types'
17
+
18
+ type Props = {
19
+ steps: ReadonlyArray<StepItem>
20
+ /** 0-based index of the active step. */
21
+ current: number
22
+ onChange?: (index: number) => void
23
+ /** Only allow navigating to already-reached steps. Default true. */
24
+ linear?: boolean
25
+ orientation?: 'horizontal' | 'vertical'
26
+ }
27
+
28
+ let { steps, current, onChange, linear = true, orientation = 'horizontal' }: Props = $props()
29
+
30
+ const statusOf = (i: number): 'complete' | 'active' | 'upcoming' =>
31
+ i < current ? 'complete' : i === current ? 'active' : 'upcoming'
32
+ const clickable = (i: number) => (linear ? i <= current : true)
33
+ const go = (i: number) => { if (clickable(i) && i !== current) onChange?.(i) }
34
+ </script>
35
+
36
+ <ol class="sv-step sv-step--{orientation}">
37
+ {#each steps as step, i (i)}
38
+ {@const status = statusOf(i)}
39
+ <li class="sv-step__item is-{status}" class:is-clickable={clickable(i)}>
40
+ <button
41
+ type="button"
42
+ class="sv-step__btn"
43
+ aria-current={status === 'active' ? 'step' : undefined}
44
+ disabled={!clickable(i)}
45
+ onclick={() => go(i)}
46
+ >
47
+ <span class="sv-step__marker" aria-hidden="true">
48
+ {#if status === 'complete'}✓{:else}{i + 1}{/if}
49
+ </span>
50
+ <span class="sv-step__text">
51
+ <span class="sv-step__label">{step.label}{#if step.optional}<span class="sv-step__optional"> (optional)</span>{/if}</span>
52
+ {#if step.description}<span class="sv-step__desc">{step.description}</span>{/if}
53
+ </span>
54
+ </button>
55
+ {#if i < steps.length - 1}<span class="sv-step__line" aria-hidden="true"></span>{/if}
56
+ </li>
57
+ {/each}
58
+ </ol>
59
+
60
+ <style>
61
+ .sv-step { --_accent: var(--sg-accent, #2563eb); list-style: none; margin: 0; padding: 0; display: flex; }
62
+ .sv-step--horizontal { flex-direction: row; align-items: flex-start; }
63
+ .sv-step--vertical { flex-direction: column; }
64
+ .sv-step__item { display: flex; position: relative; flex: 1; }
65
+ .sv-step--vertical .sv-step__item { flex-direction: column; }
66
+
67
+ .sv-step__btn {
68
+ display: flex; align-items: center; gap: 10px; background: none; border: 0; padding: 4px; margin: 0;
69
+ font: inherit; text-align: start; cursor: default; color: var(--sg-muted, #64748b);
70
+ }
71
+ .sv-step__item.is-clickable .sv-step__btn { cursor: pointer; }
72
+ .sv-step__marker {
73
+ flex: none; width: 28px; height: 28px; display: grid; place-items: center; border-radius: 50%;
74
+ font-size: 13px; font-weight: 650;
75
+ background: var(--sg-row-hover-bg, #eef2f7); color: var(--sg-muted, #64748b);
76
+ border: 2px solid transparent; transition: background 0.15s, color 0.15s, border-color 0.15s;
77
+ }
78
+ .sv-step__item.is-active .sv-step__marker { background: var(--_accent); color: var(--sg-on-accent, #fff); }
79
+ .sv-step__item.is-complete .sv-step__marker { background: color-mix(in srgb, var(--_accent) 16%, transparent); color: var(--_accent); border-color: color-mix(in srgb, var(--_accent) 45%, transparent); }
80
+ .sv-step__item.is-active .sv-step__label, .sv-step__item.is-complete .sv-step__label { color: var(--sg-fg, #0f172a); }
81
+
82
+ .sv-step__text { display: flex; flex-direction: column; }
83
+ .sv-step__label { font-size: 13px; font-weight: 600; }
84
+ .sv-step__optional { font-weight: 400; color: var(--sg-muted, #94a3b8); font-size: 11.5px; }
85
+ .sv-step__desc { font-size: 11.5px; color: var(--sg-muted, #94a3b8); }
86
+
87
+ .sv-step__line { flex: 1; height: 2px; align-self: center; margin: 0 8px; background: var(--sg-border, #e2e8f0); }
88
+ .sv-step__item.is-complete .sv-step__line { background: color-mix(in srgb, var(--_accent) 45%, var(--sg-border, #e2e8f0)); }
89
+ .sv-step--vertical .sv-step__line { width: 2px; height: auto; min-height: 16px; margin: 4px 0 4px 17px; align-self: flex-start; }
90
+ .sv-step--vertical .sv-step__item { padding-bottom: 4px; }
91
+ </style>
@@ -0,0 +1,24 @@
1
+ export type { StepItem } from './ui-app.types';
2
+ /**
3
+ * SvStepper - a progress stepper for wizards/multi-step forms. Shows completed
4
+ * / active / upcoming steps horizontally or vertically. In `linear` mode only
5
+ * completed steps (and the active one) are clickable; otherwise any step is.
6
+ * Parity: Smart steps / wizard nav.
7
+ *
8
+ * ```svelte
9
+ * <SvStepper steps={steps} current={i} onChange={(n) => (i = n)} />
10
+ * ```
11
+ */
12
+ import type { StepItem } from './ui-app.types';
13
+ type Props = {
14
+ steps: ReadonlyArray<StepItem>;
15
+ /** 0-based index of the active step. */
16
+ current: number;
17
+ onChange?: (index: number) => void;
18
+ /** Only allow navigating to already-reached steps. Default true. */
19
+ linear?: boolean;
20
+ orientation?: 'horizontal' | 'vertical';
21
+ };
22
+ declare const SvStepper: import("svelte").Component<Props, {}, "">;
23
+ type SvStepper = ReturnType<typeof SvStepper>;
24
+ export default SvStepper;
@@ -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; }
@@ -1,10 +1,7 @@
1
- type Props = {
1
+ import { type SvEditorProps } from './editor-contract';
2
+ type Props = SvEditorProps & {
2
3
  checked?: boolean;
3
4
  onChange?: (checked: boolean) => void;
4
- disabled?: boolean;
5
- size?: 'sm' | 'md' | 'lg';
6
- name?: string;
7
- ariaLabel?: string;
8
5
  /** Optional labels rendered inside the track. */
9
6
  onLabel?: string;
10
7
  offLabel?: string;
@@ -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>
@@ -1,18 +1,25 @@
1
1
  export type { TabItem } from './createTabs.svelte';
2
2
  /**
3
- * SvTabs - a WAI-ARIA tabs widget with roving tabindex + arrow-key navigation.
3
+ * SvTabs - a WAI-ARIA tabs widget with roving tabindex + arrow-key navigation,
4
+ * four `tabPosition`s (top / bottom / left / right) and optional closable tabs.
4
5
  * Parity: Smart `smart-tabs`. Controlled via `value` + `onChange`; the active
5
- * panel is rendered through the `panel` snippet (receives the active id).
6
+ * panel is rendered through the `panel` snippet.
6
7
  *
7
- * The behavior (roving focus, activation, ARIA) lives in the headless
8
- * `createTabs` core; this component is just one styled renderer over it.
8
+ * Behavior (roving focus, activation, close, ARIA) lives in the headless
9
+ * `createTabs` core; this component is one styled renderer over it.
9
10
  */
10
11
  import type { Snippet } from 'svelte';
12
+ import { type EditorDir } from './editor-contract';
11
13
  import { type TabItem } from './createTabs.svelte';
12
14
  type Props = {
13
15
  tabs: ReadonlyArray<TabItem>;
14
16
  value?: string;
15
17
  onChange?: (id: string) => void;
18
+ /** Fired when a closable tab's x (or Delete key) closes it. */
19
+ onClose?: (id: string) => void;
20
+ /** Where the tab strip sits. Default 'top'. */
21
+ tabPosition?: 'top' | 'bottom' | 'left' | 'right';
22
+ /** @deprecated use `tabPosition`. horizontal -> top, vertical -> left. */
16
23
  orientation?: 'horizontal' | 'vertical';
17
24
  /** 'line' underline (default) or 'pill' segmented look. */
18
25
  variant?: 'line' | 'pill';
@@ -20,6 +27,11 @@ type Props = {
20
27
  activation?: 'automatic' | 'manual';
21
28
  panel?: Snippet<[string]>;
22
29
  ariaLabel?: string;
30
+ /** Text direction (rtl mirrors layout + flips horizontal arrow keys). */
31
+ dir?: EditorDir;
32
+ /** Allow dragging tabs to reorder; emits the new id order via onReorder. */
33
+ reorderable?: boolean;
34
+ onReorder?: (orderedIds: string[]) => void;
23
35
  };
24
36
  declare const SvTabs: import("svelte").Component<Props, {}, "value">;
25
37
  type SvTabs = ReturnType<typeof SvTabs>;