@svgrid/grid 1.2.33 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (490) hide show
  1. package/README.md +6 -5
  2. package/dist/FlexRender.svelte +96 -96
  3. package/dist/GridFooter.svelte +178 -178
  4. package/dist/SvAccordion.svelte +109 -0
  5. package/dist/SvAccordion.svelte.d.ts +28 -0
  6. package/dist/SvAlert.svelte +92 -0
  7. package/dist/SvAlert.svelte.d.ts +27 -0
  8. package/dist/SvAutoComplete.svelte +47 -18
  9. package/dist/SvAutoComplete.svelte.d.ts +2 -5
  10. package/dist/SvAvatar.svelte +75 -0
  11. package/dist/SvAvatar.svelte.d.ts +14 -0
  12. package/dist/SvAvatarGroup.svelte +48 -0
  13. package/dist/SvAvatarGroup.svelte.d.ts +16 -0
  14. package/dist/SvBadge.svelte +54 -0
  15. package/dist/SvBadge.svelte.d.ts +23 -0
  16. package/dist/SvBreadcrumb.svelte +88 -0
  17. package/dist/SvBreadcrumb.svelte.d.ts +23 -0
  18. package/dist/SvButtonGroup.svelte +121 -0
  19. package/dist/SvButtonGroup.svelte.d.ts +16 -0
  20. package/dist/SvCalendar.svelte +72 -9
  21. package/dist/SvCalendar.svelte.d.ts +34 -1
  22. package/dist/SvCard.svelte +71 -0
  23. package/dist/SvCard.svelte.d.ts +28 -0
  24. package/dist/SvCarousel.svelte +134 -0
  25. package/dist/SvCarousel.svelte.d.ts +28 -0
  26. package/dist/SvCheckBox.svelte +36 -21
  27. package/dist/SvCheckBox.svelte.d.ts +2 -5
  28. package/dist/SvChip.svelte +88 -0
  29. package/dist/SvChip.svelte.d.ts +31 -0
  30. package/dist/SvCircularProgress.svelte +109 -0
  31. package/dist/SvCircularProgress.svelte.d.ts +31 -0
  32. package/dist/SvColorInput.svelte +54 -27
  33. package/dist/SvColorInput.svelte.d.ts +10 -6
  34. package/dist/SvComboBox.svelte +119 -33
  35. package/dist/SvComboBox.svelte.d.ts +18 -6
  36. package/dist/SvCommand.svelte +197 -0
  37. package/dist/SvCommand.svelte.d.ts +26 -0
  38. package/dist/SvContextMenu.svelte +96 -0
  39. package/dist/SvContextMenu.svelte.d.ts +25 -0
  40. package/dist/SvCountryInput.svelte +56 -21
  41. package/dist/SvCountryInput.svelte.d.ts +9 -5
  42. package/dist/SvDateRangeInput.svelte +256 -0
  43. package/dist/SvDateRangeInput.svelte.d.ts +37 -0
  44. package/dist/SvDateTimePicker.svelte +57 -20
  45. package/dist/SvDateTimePicker.svelte.d.ts +12 -4
  46. package/dist/SvDivider.svelte +61 -0
  47. package/dist/SvDivider.svelte.d.ts +22 -0
  48. package/dist/SvDrawer.svelte +226 -0
  49. package/dist/SvDrawer.svelte.d.ts +43 -0
  50. package/dist/SvDropDownList.svelte +114 -35
  51. package/dist/SvDropDownList.svelte.d.ts +7 -6
  52. package/dist/SvDurationInput.svelte +108 -0
  53. package/dist/SvDurationInput.svelte.d.ts +15 -0
  54. package/dist/SvEmptyState.svelte +59 -0
  55. package/dist/SvEmptyState.svelte.d.ts +25 -0
  56. package/dist/SvField.svelte +85 -0
  57. package/dist/SvField.svelte.d.ts +30 -0
  58. package/dist/SvFileUpload.svelte +169 -0
  59. package/dist/SvFileUpload.svelte.d.ts +24 -0
  60. package/dist/SvForm.svelte +7 -1
  61. package/dist/SvForm.svelte.d.ts +3 -0
  62. package/dist/SvGauge.svelte +7 -1
  63. package/dist/SvGauge.svelte.d.ts +11 -0
  64. package/dist/SvGrid.controller.svelte.d.ts +6 -0
  65. package/dist/SvGrid.controller.svelte.js +64 -9
  66. package/dist/SvGrid.css +2376 -2343
  67. package/dist/SvGrid.svelte +2764 -2651
  68. package/dist/SvGrid.types.d.ts +366 -0
  69. package/dist/SvGridBoard.svelte +2281 -0
  70. package/dist/SvGridBoard.svelte.d.ts +31 -0
  71. package/dist/SvGridDropdown.svelte +666 -665
  72. package/dist/SvGridSelect.svelte +255 -0
  73. package/dist/SvGridSelect.svelte.d.ts +32 -0
  74. package/dist/SvGroupCell.svelte +109 -0
  75. package/dist/SvGroupCell.svelte.d.ts +41 -0
  76. package/dist/SvListBox.svelte +123 -36
  77. package/dist/SvListBox.svelte.d.ts +12 -8
  78. package/dist/SvMaskedInput.svelte +53 -13
  79. package/dist/SvMaskedInput.svelte.d.ts +16 -6
  80. package/dist/SvMenu.svelte +101 -0
  81. package/dist/SvMenu.svelte.d.ts +26 -0
  82. package/dist/SvMenuList.svelte +143 -0
  83. package/dist/SvMenuList.svelte.d.ts +24 -0
  84. package/dist/SvModal.svelte +220 -0
  85. package/dist/SvModal.svelte.d.ts +31 -0
  86. package/dist/SvMultiSelect.svelte +277 -0
  87. package/dist/SvMultiSelect.svelte.d.ts +31 -0
  88. package/dist/SvNavPane.svelte +314 -0
  89. package/dist/SvNavPane.svelte.d.ts +59 -0
  90. package/dist/SvNumberInput.svelte +23 -9
  91. package/dist/SvNumberInput.svelte.d.ts +2 -0
  92. package/dist/SvOtpInput.svelte +157 -0
  93. package/dist/SvOtpInput.svelte.d.ts +16 -0
  94. package/dist/SvPagination.svelte +100 -0
  95. package/dist/SvPagination.svelte.d.ts +16 -0
  96. package/dist/SvPasswordInput.svelte +42 -12
  97. package/dist/SvPasswordInput.svelte.d.ts +13 -6
  98. package/dist/SvPhoneInput.svelte +43 -11
  99. package/dist/SvPhoneInput.svelte.d.ts +10 -11
  100. package/dist/SvPopover.svelte +127 -0
  101. package/dist/SvPopover.svelte.d.ts +35 -0
  102. package/dist/SvProgress.svelte +116 -0
  103. package/dist/SvProgress.svelte.d.ts +32 -0
  104. package/dist/SvRadioGroup.svelte +40 -22
  105. package/dist/SvRadioGroup.svelte.d.ts +2 -10
  106. package/dist/SvRating.svelte +51 -30
  107. package/dist/SvRating.svelte.d.ts +10 -7
  108. package/dist/SvRichText.svelte +192 -0
  109. package/dist/SvRichText.svelte.d.ts +16 -0
  110. package/dist/SvRowGroupPanel.svelte +147 -0
  111. package/dist/SvRowGroupPanel.svelte.d.ts +17 -0
  112. package/dist/SvScrollArea.svelte +50 -0
  113. package/dist/SvScrollArea.svelte.d.ts +22 -0
  114. package/dist/SvSkeleton.svelte +76 -0
  115. package/dist/SvSkeleton.svelte.d.ts +25 -0
  116. package/dist/SvSlider.svelte +41 -9
  117. package/dist/SvSlider.svelte.d.ts +4 -4
  118. package/dist/SvSparkline.svelte +77 -0
  119. package/dist/SvSparkline.svelte.d.ts +28 -0
  120. package/dist/SvSplitter.svelte +138 -0
  121. package/dist/SvSplitter.svelte.d.ts +32 -0
  122. package/dist/SvStat.svelte +86 -0
  123. package/dist/SvStat.svelte.d.ts +30 -0
  124. package/dist/SvStepper.svelte +91 -0
  125. package/dist/SvStepper.svelte.d.ts +24 -0
  126. package/dist/SvSwitchButton.svelte +39 -24
  127. package/dist/SvSwitchButton.svelte.d.ts +2 -5
  128. package/dist/SvTabs.svelte +147 -28
  129. package/dist/SvTabs.svelte.d.ts +16 -4
  130. package/dist/SvTagsInput.svelte +59 -22
  131. package/dist/SvTagsInput.svelte.d.ts +9 -4
  132. package/dist/SvTextArea.svelte +113 -0
  133. package/dist/SvTextArea.svelte.d.ts +18 -0
  134. package/dist/SvTextInput.svelte +115 -0
  135. package/dist/SvTextInput.svelte.d.ts +20 -0
  136. package/dist/SvTimePicker.svelte +19 -5
  137. package/dist/SvTimePicker.svelte.d.ts +12 -0
  138. package/dist/SvTimeline.svelte +75 -0
  139. package/dist/SvTimeline.svelte.d.ts +24 -0
  140. package/dist/SvToaster.svelte +124 -0
  141. package/dist/SvToaster.svelte.d.ts +10 -0
  142. package/dist/SvToggleButton.svelte +37 -12
  143. package/dist/SvToggleButton.svelte.d.ts +2 -4
  144. package/dist/SvTooltip.svelte +113 -0
  145. package/dist/SvTooltip.svelte.d.ts +22 -0
  146. package/dist/SvTour.svelte +151 -0
  147. package/dist/SvTour.svelte.d.ts +25 -0
  148. package/dist/SvTree.svelte +286 -44
  149. package/dist/SvTree.svelte.d.ts +26 -9
  150. package/dist/SvTreeSelect.svelte +222 -0
  151. package/dist/SvTreeSelect.svelte.d.ts +24 -0
  152. package/dist/a11y/dismissable.d.ts +32 -0
  153. package/dist/a11y/dismissable.js +54 -0
  154. package/dist/a11y/focus-trap.d.ts +39 -0
  155. package/dist/a11y/focus-trap.js +142 -0
  156. package/dist/a11y/live-region.d.ts +8 -0
  157. package/dist/a11y/live-region.js +60 -0
  158. package/dist/a11y/scroll-lock.d.ts +4 -0
  159. package/dist/a11y/scroll-lock.js +41 -0
  160. package/dist/a11y.d.ts +3 -1
  161. package/dist/a11y.js +1 -1
  162. package/dist/avatar.d.ts +10 -0
  163. package/dist/avatar.js +22 -0
  164. package/dist/builtin-editors.d.ts +5 -0
  165. package/dist/builtin-editors.js +55 -0
  166. package/dist/cdn/svgrid.js +16022 -8235
  167. package/dist/cdn/svgrid.svelte-external.js +14543 -6765
  168. package/dist/columns.js +3 -2
  169. package/dist/core.d.ts +7 -1
  170. package/dist/countries.d.ts +12 -0
  171. package/dist/countries.js +24 -0
  172. package/dist/createAccordion.svelte.d.ts +71 -0
  173. package/dist/createAccordion.svelte.js +99 -0
  174. package/dist/createAutocomplete.svelte.d.ts +13 -4
  175. package/dist/createAutocomplete.svelte.js +11 -1
  176. package/dist/createButtonGroup.svelte.d.ts +62 -0
  177. package/dist/createButtonGroup.svelte.js +154 -0
  178. package/dist/createCalendar.svelte.d.ts +5 -0
  179. package/dist/createCalendar.svelte.js +2 -0
  180. package/dist/createCheckbox.svelte.d.ts +9 -17
  181. package/dist/createCheckbox.svelte.js +28 -1
  182. package/dist/createColorInput.svelte.d.ts +12 -22
  183. package/dist/createColorInput.svelte.js +13 -0
  184. package/dist/createCombobox.svelte.d.ts +16 -5
  185. package/dist/createCombobox.svelte.js +13 -2
  186. package/dist/createCountryInput.svelte.d.ts +14 -3
  187. package/dist/createCountryInput.svelte.js +12 -2
  188. package/dist/createDateTimePicker.svelte.d.ts +17 -2
  189. package/dist/createDateTimePicker.svelte.js +12 -2
  190. package/dist/createDropdownList.svelte.d.ts +14 -5
  191. package/dist/createDropdownList.svelte.js +50 -1
  192. package/dist/createFileUpload.svelte.d.ts +68 -0
  193. package/dist/createFileUpload.svelte.js +122 -0
  194. package/dist/createListbox.svelte.d.ts +10 -1
  195. package/dist/createListbox.svelte.js +48 -1
  196. package/dist/createMaskedInput.svelte.d.ts +11 -2
  197. package/dist/createMaskedInput.svelte.js +11 -1
  198. package/dist/createNumberInput.svelte.d.ts +1 -0
  199. package/dist/createNumberInput.svelte.js +1 -0
  200. package/dist/createPasswordInput.svelte.d.ts +19 -21
  201. package/dist/createPasswordInput.svelte.js +16 -4
  202. package/dist/createPhoneInput.svelte.d.ts +26 -7
  203. package/dist/createPhoneInput.svelte.js +24 -3
  204. package/dist/createPopoverSelect.svelte.d.ts +86 -0
  205. package/dist/createPopoverSelect.svelte.js +196 -0
  206. package/dist/createRadioGroup.svelte.d.ts +9 -0
  207. package/dist/createRadioGroup.svelte.js +11 -1
  208. package/dist/createRating.svelte.d.ts +11 -0
  209. package/dist/createRating.svelte.js +12 -1
  210. package/dist/createSlider.svelte.d.ts +2 -0
  211. package/dist/createSlider.svelte.js +16 -5
  212. package/dist/createSplitter.svelte.d.ts +67 -0
  213. package/dist/createSplitter.svelte.js +99 -0
  214. package/dist/createSwitch.svelte.d.ts +9 -16
  215. package/dist/createSwitch.svelte.js +28 -1
  216. package/dist/createTabs.svelte.d.ts +7 -0
  217. package/dist/createTabs.svelte.js +17 -2
  218. package/dist/createTagsInput.svelte.d.ts +13 -1
  219. package/dist/createTagsInput.svelte.js +12 -3
  220. package/dist/createToggle.svelte.d.ts +9 -0
  221. package/dist/createToggle.svelte.js +12 -1
  222. package/dist/createTree.svelte.d.ts +19 -0
  223. package/dist/createTree.svelte.js +108 -15
  224. package/dist/debounce.d.ts +11 -0
  225. package/dist/debounce.js +30 -0
  226. package/dist/duration.d.ts +15 -0
  227. package/dist/duration.js +51 -0
  228. package/dist/editor-contract.d.ts +62 -7
  229. package/dist/editor-contract.js +45 -5
  230. package/dist/editor-registry.d.ts +67 -0
  231. package/dist/editor-registry.js +38 -0
  232. package/dist/editors/cell-editors.d.ts +1 -1
  233. package/dist/editors/cell-editors.js +5 -0
  234. package/dist/fuzzy.d.ts +3 -0
  235. package/dist/fuzzy.js +41 -0
  236. package/dist/grid-select.d.ts +9 -0
  237. package/dist/grid-select.js +8 -0
  238. package/dist/index.d.ts +79 -6
  239. package/dist/index.js +78 -4
  240. package/dist/keyboard-handlers.js +19 -0
  241. package/dist/list-option.d.ts +36 -0
  242. package/dist/list-option.js +68 -0
  243. package/dist/otp.d.ts +16 -0
  244. package/dist/otp.js +26 -0
  245. package/dist/paginate.d.ts +20 -0
  246. package/dist/paginate.js +40 -0
  247. package/dist/popover.d.ts +15 -1
  248. package/dist/popover.js +29 -3
  249. package/dist/recurrence.d.ts +36 -0
  250. package/dist/recurrence.js +90 -0
  251. package/dist/server-data-source.d.ts +89 -0
  252. package/dist/server-data-source.js +4 -0
  253. package/dist/server-group-model.d.ts +98 -0
  254. package/dist/server-group-model.js +263 -0
  255. package/dist/toast-store.svelte.d.ts +41 -0
  256. package/dist/toast-store.svelte.js +113 -0
  257. package/dist/tree-select.d.ts +19 -0
  258. package/dist/tree-select.js +40 -0
  259. package/dist/ui-app.types.d.ts +80 -0
  260. package/dist/ui-app.types.js +7 -0
  261. package/dist/validators.d.ts +43 -0
  262. package/dist/validators.js +53 -0
  263. package/dist/virtual.d.ts +35 -0
  264. package/dist/virtual.js +34 -0
  265. package/package.json +85 -85
  266. package/src/FlexRender.svelte +96 -96
  267. package/src/GridFooter.svelte +178 -178
  268. package/src/SvAccordion.svelte +109 -0
  269. package/src/SvAccordion.test.ts +66 -0
  270. package/src/SvAlert.svelte +92 -0
  271. package/src/SvAutoComplete.svelte +47 -18
  272. package/src/SvAvatar.svelte +75 -0
  273. package/src/SvAvatarGroup.svelte +48 -0
  274. package/src/SvBadge.svelte +54 -0
  275. package/src/SvBreadcrumb.svelte +88 -0
  276. package/src/SvButtonGroup.svelte +121 -0
  277. package/src/SvButtonGroup.test.ts +72 -0
  278. package/src/SvCalendar.svelte +72 -9
  279. package/src/SvCard.svelte +71 -0
  280. package/src/SvCarousel.svelte +134 -0
  281. package/src/SvCheckBox.svelte +36 -21
  282. package/src/SvChip.svelte +88 -0
  283. package/src/SvCircularProgress.svelte +109 -0
  284. package/src/SvColorInput.svelte +54 -27
  285. package/src/SvComboBox.svelte +119 -33
  286. package/src/SvCommand.svelte +197 -0
  287. package/src/SvContextMenu.svelte +96 -0
  288. package/src/SvCountryInput.svelte +56 -21
  289. package/src/SvDateRangeInput.svelte +256 -0
  290. package/src/SvDateRangeInput.test.ts +80 -0
  291. package/src/SvDateTimePicker.svelte +57 -20
  292. package/src/SvDivider.svelte +61 -0
  293. package/src/SvDrawer.svelte +226 -0
  294. package/src/SvDrawer.test.ts +125 -0
  295. package/src/SvDropDownList.svelte +114 -35
  296. package/src/SvDurationInput.svelte +108 -0
  297. package/src/SvEmptyState.svelte +59 -0
  298. package/src/SvField.svelte +85 -0
  299. package/src/SvFileUpload.svelte +169 -0
  300. package/src/SvFileUpload.test.ts +59 -0
  301. package/src/SvForm.svelte +7 -1
  302. package/src/SvGauge.svelte +7 -1
  303. package/src/SvGrid.controller.svelte.ts +2553 -2494
  304. package/src/SvGrid.css +2376 -2343
  305. package/src/SvGrid.svelte +2764 -2651
  306. package/src/SvGrid.types.ts +944 -570
  307. package/src/SvGridBoard.svelte +2281 -0
  308. package/src/SvGridDropdown.svelte +666 -665
  309. package/src/SvGridSelect.svelte +255 -0
  310. package/src/SvGridSelect.test.ts +100 -0
  311. package/src/SvGroupCell.svelte +109 -0
  312. package/src/SvListBox.svelte +123 -36
  313. package/src/SvMaskedInput.svelte +53 -13
  314. package/src/SvMenu.svelte +101 -0
  315. package/src/SvMenu.test.ts +73 -0
  316. package/src/SvMenuList.svelte +143 -0
  317. package/src/SvModal.svelte +220 -0
  318. package/src/SvMultiSelect.svelte +277 -0
  319. package/src/SvMultiSelect.test.ts +167 -0
  320. package/src/SvNavPane.svelte +314 -0
  321. package/src/SvNavPane.test.ts +70 -0
  322. package/src/SvNumberInput.svelte +23 -9
  323. package/src/SvOtpInput.svelte +157 -0
  324. package/src/SvPagination.svelte +100 -0
  325. package/src/SvPasswordInput.svelte +42 -12
  326. package/src/SvPhoneInput.svelte +43 -11
  327. package/src/SvPopover.svelte +127 -0
  328. package/src/SvProgress.svelte +116 -0
  329. package/src/SvRadioGroup.svelte +40 -22
  330. package/src/SvRating.svelte +51 -30
  331. package/src/SvRichText.svelte +192 -0
  332. package/src/SvRowGroupPanel.svelte +147 -0
  333. package/src/SvScrollArea.svelte +50 -0
  334. package/src/SvSkeleton.svelte +76 -0
  335. package/src/SvSlider.svelte +41 -9
  336. package/src/SvSparkline.svelte +77 -0
  337. package/src/SvSplitter.svelte +138 -0
  338. package/src/SvSplitter.test.ts +55 -0
  339. package/src/SvStat.svelte +86 -0
  340. package/src/SvStepper.svelte +91 -0
  341. package/src/SvSwitchButton.svelte +39 -24
  342. package/src/SvTabs.svelte +147 -28
  343. package/src/SvTagsInput.svelte +59 -22
  344. package/src/SvTextArea.svelte +113 -0
  345. package/src/SvTextInput.svelte +115 -0
  346. package/src/SvTimePicker.svelte +19 -5
  347. package/src/SvTimeline.svelte +75 -0
  348. package/src/SvToaster.svelte +124 -0
  349. package/src/SvToaster.test.ts +63 -0
  350. package/src/SvToggleButton.svelte +37 -12
  351. package/src/SvTooltip.svelte +113 -0
  352. package/src/SvTour.svelte +151 -0
  353. package/src/SvTree.svelte +286 -44
  354. package/src/SvTreeSelect.svelte +222 -0
  355. package/src/SvTreeSelect.test.ts +87 -0
  356. package/src/a11y/dismissable.test.ts +95 -0
  357. package/src/a11y/dismissable.ts +94 -0
  358. package/src/a11y/focus-trap.test.ts +110 -0
  359. package/src/a11y/focus-trap.ts +134 -0
  360. package/src/a11y/live-region.test.ts +40 -0
  361. package/src/a11y/live-region.ts +65 -0
  362. package/src/a11y/scroll-lock.test.ts +38 -0
  363. package/src/a11y/scroll-lock.ts +41 -0
  364. package/src/a11y.contract.test.ts +49 -49
  365. package/src/a11y.test.ts +59 -59
  366. package/src/a11y.ts +61 -59
  367. package/src/avatar.test.ts +32 -0
  368. package/src/avatar.ts +21 -0
  369. package/src/board.test.ts +927 -0
  370. package/src/build-api.ts +798 -798
  371. package/src/builtin-editors.grid.test.ts +85 -0
  372. package/src/builtin-editors.test.ts +58 -0
  373. package/src/builtin-editors.ts +56 -0
  374. package/src/cell-formatting.ts +169 -169
  375. package/src/cell-render.ts +469 -469
  376. package/src/collaboration.test.ts +104 -104
  377. package/src/collaboration.ts +167 -167
  378. package/src/columns.ts +3 -2
  379. package/src/core.performance.test.ts +30 -30
  380. package/src/core.ts +1111 -1101
  381. package/src/countries.ts +25 -0
  382. package/src/createAccordion.svelte.ts +126 -0
  383. package/src/createAutocomplete.svelte.ts +18 -1
  384. package/src/createButtonGroup.svelte.ts +166 -0
  385. package/src/createCalendar.svelte.ts +6 -0
  386. package/src/createCheckbox.svelte.ts +23 -1
  387. package/src/createColorInput.svelte.ts +20 -0
  388. package/src/createCombobox.svelte.ts +22 -2
  389. package/src/createCountryInput.svelte.ts +21 -2
  390. package/src/createDateTimePicker.svelte.ts +23 -2
  391. package/src/createDropdownList.svelte.ts +31 -2
  392. package/src/createFileUpload.svelte.ts +144 -0
  393. package/src/createGrid.svelte.ts +42 -42
  394. package/src/createGrid.test.ts +10 -10
  395. package/src/createGridState.svelte.ts +17 -17
  396. package/src/createListbox.svelte.ts +35 -3
  397. package/src/createMaskedInput.svelte.ts +18 -1
  398. package/src/createNumberInput.svelte.ts +2 -0
  399. package/src/createPasswordInput.svelte.ts +31 -5
  400. package/src/createPhoneInput.svelte.ts +38 -4
  401. package/src/createPopoverSelect.svelte.ts +208 -0
  402. package/src/createRadioGroup.svelte.ts +23 -1
  403. package/src/createRating.svelte.ts +26 -1
  404. package/src/createSlider.svelte.ts +14 -3
  405. package/src/createSplitter.svelte.ts +119 -0
  406. package/src/createSwitch.svelte.ts +26 -3
  407. package/src/createTabs.svelte.ts +20 -3
  408. package/src/createTagsInput.svelte.ts +24 -3
  409. package/src/createToggle.svelte.ts +25 -2
  410. package/src/createTree.svelte.ts +104 -14
  411. package/src/debounce.test.ts +50 -0
  412. package/src/debounce.ts +41 -0
  413. package/src/duration.test.ts +41 -0
  414. package/src/duration.ts +51 -0
  415. package/src/editing.test.ts +859 -859
  416. package/src/editing.ts +675 -675
  417. package/src/editor-contract.ts +87 -8
  418. package/src/editor-registry.grid.test.ts +98 -0
  419. package/src/editor-registry.test.ts +86 -0
  420. package/src/editor-registry.ts +106 -0
  421. package/src/editors/cell-editors.ts +10 -1
  422. package/src/export-data-api.test.ts +126 -126
  423. package/src/export-format.test.ts +107 -107
  424. package/src/export-format.ts +598 -598
  425. package/src/flex-render.ts +3 -3
  426. package/src/fuzzy.test.ts +40 -0
  427. package/src/fuzzy.ts +39 -0
  428. package/src/grid-select.test.ts +28 -0
  429. package/src/grid-select.ts +20 -0
  430. package/src/index.ts +463 -318
  431. package/src/keyboard-handlers.ts +20 -0
  432. package/src/keyboard.test.ts +59 -59
  433. package/src/keyboard.ts +97 -97
  434. package/src/list-option.ts +74 -0
  435. package/src/list-power.test.ts +71 -0
  436. package/src/menus.ts +582 -582
  437. package/src/merge-objects.ts +48 -48
  438. package/src/otp.test.ts +28 -0
  439. package/src/otp.ts +28 -0
  440. package/src/overlays.test.ts +85 -0
  441. package/src/paginate.test.ts +41 -0
  442. package/src/paginate.ts +61 -0
  443. package/src/phone-valid.test.ts +26 -0
  444. package/src/popover.ts +31 -3
  445. package/src/progress.test.ts +55 -0
  446. package/src/recurrence.test.ts +91 -0
  447. package/src/recurrence.ts +111 -0
  448. package/src/render-component.ts +28 -28
  449. package/src/selection.test.ts +754 -754
  450. package/src/selection.ts +600 -600
  451. package/src/server-data-source.test.ts +289 -289
  452. package/src/server-data-source.ts +413 -311
  453. package/src/server-group-model.test.ts +294 -0
  454. package/src/server-group-model.ts +370 -0
  455. package/src/sparkline.test.ts +68 -68
  456. package/src/sparkline.ts +169 -169
  457. package/src/spreadsheet.test.ts +489 -489
  458. package/src/spreadsheet.ts +304 -304
  459. package/src/static-functions.ts +11 -11
  460. package/src/subscribe.ts +38 -38
  461. package/src/svgrid-wrapper.types.ts +439 -439
  462. package/src/svgrid.behavior.test.ts +706 -706
  463. package/src/svgrid.features.test.ts +157 -157
  464. package/src/svgrid.new-features.wrapper.test.ts +251 -251
  465. package/src/svgrid.responsive.test.ts +72 -0
  466. package/src/svgrid.wrapper.test.ts +40 -40
  467. package/src/toast-store.svelte.ts +148 -0
  468. package/src/toast-store.test.ts +86 -0
  469. package/src/tree-edit.test.ts +49 -0
  470. package/src/tree-filter.test.ts +56 -0
  471. package/src/tree-select.test.ts +48 -0
  472. package/src/tree-select.ts +62 -0
  473. package/src/ui-app.test.ts +130 -0
  474. package/src/ui-app.types.ts +88 -0
  475. package/src/ui-display.test.ts +79 -0
  476. package/src/ui-inputs.test.ts +1 -1
  477. package/src/ui-status.test.ts +141 -0
  478. package/src/ui-tier1.test.ts +142 -0
  479. package/src/ui-tier2.test.ts +124 -0
  480. package/src/ui-tier3.test.ts +139 -0
  481. package/src/validators.test.ts +58 -0
  482. package/src/validators.ts +105 -0
  483. package/src/virtual.test.ts +72 -0
  484. package/src/virtual.ts +67 -0
  485. package/src/virtualization/column-virtualizer.test.ts +27 -27
  486. package/src/virtualization/column-virtualizer.ts +30 -30
  487. package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
  488. package/src/virtualization/types.ts +30 -30
  489. package/src/virtualization/virtualizer.test.ts +47 -47
  490. package/src/virtualization/virtualizer.ts +296 -296
@@ -0,0 +1,115 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SvTextInput - the base single-line text editor on the shared editor contract
4
+ * (label / hint / error / RTL / a11y via SvField). Handles `text`, `email`,
5
+ * `url`, `tel` and `search`. As a grid cell editor it honours the interaction
6
+ * contract: Enter commits, Escape cancels. Parity: Smart `smart-input`.
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
+ /** Commit + stop editing (Enter, or supplied by the grid in a cell). */
15
+ onCommit?: (value: string) => void
16
+ /** Cancel editing (Escape). */
17
+ onCancel?: () => void
18
+ placeholder?: string
19
+ type?: 'text' | 'email' | 'url' | 'tel' | 'search'
20
+ maxlength?: number
21
+ /** Show an inline clear (x) button when non-empty. */
22
+ clearable?: boolean
23
+ autocomplete?: AutoFill
24
+ /** Autofocus + select on mount (used when mounted as a cell editor). */
25
+ autofocus?: boolean
26
+ }
27
+
28
+ let {
29
+ value = $bindable(''),
30
+ onChange,
31
+ onCommit,
32
+ onCancel,
33
+ placeholder,
34
+ disabled = false,
35
+ readonly = false,
36
+ type = 'text',
37
+ maxlength,
38
+ clearable = false,
39
+ name,
40
+ size = 'md',
41
+ ariaLabel,
42
+ invalid = false,
43
+ required = false,
44
+ error,
45
+ label,
46
+ hint,
47
+ dir,
48
+ id,
49
+ autocomplete,
50
+ autofocus = false,
51
+ }: Props = $props()
52
+
53
+ const autoId = nextEditorId('sv-text')
54
+ const uid = $derived(id ?? autoId)
55
+
56
+ // Write `value` (so `bind:value` works) AND fire `onChange` (callback users).
57
+ function set(v: string) {
58
+ value = v
59
+ onChange?.(v)
60
+ }
61
+ function onInput(e: Event) {
62
+ set((e.currentTarget as HTMLInputElement).value)
63
+ }
64
+ function onKeydown(e: KeyboardEvent) {
65
+ if (e.key === 'Enter') onCommit?.(value)
66
+ else if (e.key === 'Escape') onCancel?.()
67
+ }
68
+ function mountFocus(node: HTMLInputElement) {
69
+ if (autofocus) { node.focus(); node.select() }
70
+ }
71
+ </script>
72
+
73
+ <SvField id={uid} {label} {hint} {error} {required} {dir}>
74
+ <div class="sv-text sv-text--{size}" class:is-disabled={disabled}>
75
+ <div class="sv-text__field" class:is-invalid={invalid}>
76
+ <input
77
+ use:mountFocus
78
+ class="sv-text__input"
79
+ {type}
80
+ {value}
81
+ {placeholder}
82
+ {disabled}
83
+ {readonly}
84
+ {maxlength}
85
+ {autocomplete}
86
+ oninput={onInput}
87
+ onkeydown={onKeydown}
88
+ {...editorAria({ id: uid, invalid, required, error, hint, ariaLabel })}
89
+ />
90
+ {#if clearable && value && !disabled && !readonly}
91
+ <button class="sv-text__clear" type="button" tabindex="-1" aria-label="Clear" onclick={() => set('')}>&times;</button>
92
+ {/if}
93
+ </div>
94
+ {#if name}<input type="hidden" {name} value={value} />{/if}
95
+ </div>
96
+ </SvField>
97
+
98
+ <style>
99
+ .sv-text { --_accent: var(--sg-accent, #2563eb); display: inline-flex; flex-direction: column; width: 220px; }
100
+ .sv-text.is-disabled { opacity: 0.6; }
101
+ .sv-text__field {
102
+ display: flex; align-items: center;
103
+ background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
104
+ border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
105
+ }
106
+ .sv-text__field:focus-within { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
107
+ .sv-text__field.is-invalid { border-color: var(--sg-danger, #dc2626); }
108
+ .sv-text__field.is-invalid:focus-within { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
109
+ .sv-text__input { flex: 1; min-width: 0; border: 0; background: none; outline: none; color: inherit; font: inherit; padding: 0 10px; }
110
+ .sv-text--sm .sv-text__field { height: 28px; font-size: 12px; }
111
+ .sv-text--md .sv-text__field { height: 34px; font-size: 13px; }
112
+ .sv-text--lg .sv-text__field { height: 40px; font-size: 15px; }
113
+ .sv-text__clear { display: grid; place-items: center; width: 28px; align-self: stretch; background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; font-size: 17px; line-height: 1; }
114
+ .sv-text__clear:hover { color: var(--sg-fg, #0f172a); }
115
+ </style>
@@ -11,8 +11,13 @@
11
11
  * and ARIA; this component keeps the DOM-bound pointer capture + hit-testing.
12
12
  * All visuals come from `--sg-*` tokens so every theme applies for free.
13
13
  */
14
+ import { resolveMessages, type EditorDir } from './editor-contract'
14
15
  import { createTimePicker, type TimeValue, type TimeFormat, type TimeSelection } from './createTimePicker.svelte'
15
16
 
17
+ /** User-facing strings (localizable via `messages`). */
18
+ type TimeMessages = { label: string; am: string; pm: string; now: string }
19
+ const DEFAULT_MESSAGES: TimeMessages = { label: 'Time picker', am: 'AM', pm: 'PM', now: 'Now' }
20
+
16
21
  type Props = {
17
22
  /** Date, "HH:MM[:SS]" string, or epoch ms. */
18
23
  value?: TimeValue
@@ -29,6 +34,10 @@
29
34
  name?: string
30
35
  /** Which dial opens first. */
31
36
  selection?: TimeSelection
37
+ /** Text direction (rtl mirrors layout; auto/undefined inherits). */
38
+ dir?: EditorDir
39
+ /** Override the built-in strings (group label + AM/PM/Now). */
40
+ messages?: Partial<TimeMessages>
32
41
  }
33
42
 
34
43
  let {
@@ -42,8 +51,13 @@
42
51
  readonly = false,
43
52
  name,
44
53
  selection = 'hour',
54
+ dir,
55
+ messages,
45
56
  }: Props = $props()
46
57
 
58
+ const M = $derived(resolveMessages(DEFAULT_MESSAGES, messages))
59
+ const resolvedDir = $derived(dir === 'ltr' || dir === 'rtl' ? dir : undefined)
60
+
47
61
  // The headless core owns all state, geometry, keyboard + ARIA. Reactive inputs
48
62
  // are getters; callbacks are closures.
49
63
  const tp = createTimePicker({
@@ -93,7 +107,7 @@
93
107
  }
94
108
  </script>
95
109
 
96
- <div class="sv-tp" class:sv-tp--disabled={disabled} role="group" aria-label="Time picker">
110
+ <div class="sv-tp" class:sv-tp--disabled={disabled} role="group" aria-label={M.label} dir={resolvedDir}>
97
111
  <div class="sv-tp__head">
98
112
  <button class="sv-tp__seg" class:is-active={tp.selection === 'hour'} {...tp.segProps('hour')}>
99
113
  {String(tp.displayHour).padStart(2, '0')}
@@ -104,8 +118,8 @@
104
118
  </button>
105
119
  {#if tp.is12}
106
120
  <div class="sv-tp__ampm">
107
- <button class:is-active={!tp.isPm} {...tp.ampmProps(false)}>AM</button>
108
- <button class:is-active={tp.isPm} {...tp.ampmProps(true)}>PM</button>
121
+ <button class:is-active={!tp.isPm} {...tp.ampmProps(false)}>{M.am}</button>
122
+ <button class:is-active={tp.isPm} {...tp.ampmProps(true)}>{M.pm}</button>
109
123
  </div>
110
124
  {/if}
111
125
  </div>
@@ -139,7 +153,7 @@
139
153
 
140
154
  {#if footer}
141
155
  <div class="sv-tp__footer">
142
- <button class="sv-tp__now" {...tp.nowProps()}>Now</button>
156
+ <button class="sv-tp__now" {...tp.nowProps()}>{M.now}</button>
143
157
  </div>
144
158
  {/if}
145
159
 
@@ -171,7 +185,7 @@
171
185
  }
172
186
  .sv-tp__seg.is-active { color: var(--_accent); background: color-mix(in srgb, var(--_accent) 12%, transparent); }
173
187
  .sv-tp__colon { font-size: 28px; font-weight: 700; color: var(--_muted); }
174
- .sv-tp__ampm { display: flex; flex-direction: column; margin-left: 8px; gap: 2px; }
188
+ .sv-tp__ampm { display: flex; flex-direction: column; margin-inline-start: 8px; gap: 2px; }
175
189
  .sv-tp__ampm button {
176
190
  font-size: 11px; font-weight: 700; padding: 3px 8px;
177
191
  background: none; border: 1px solid var(--_border); border-radius: 6px; color: var(--_muted); cursor: pointer;
@@ -0,0 +1,75 @@
1
+ <script lang="ts" module>
2
+ export type { TimelineItem } from './ui-app.types'
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ /**
7
+ * SvTimeline - a vertical activity / history feed: a connecting line with a
8
+ * marker (colour + optional glyph) per entry, and a title / time / description.
9
+ * Data-driven via `items`, with an optional `item` snippet for custom content.
10
+ * Theme-driven. Parity: Smart timeline.
11
+ *
12
+ * ```svelte
13
+ * <SvTimeline items={[
14
+ * { title: 'Order placed', time: '09:12', color: '#16a34a' },
15
+ * { title: 'Shipped', time: '14:40', description: 'Left the warehouse' },
16
+ * ]} />
17
+ * ```
18
+ */
19
+ import type { Snippet } from 'svelte'
20
+ import type { TimelineItem } from './ui-app.types'
21
+
22
+ type Props = {
23
+ items: ReadonlyArray<TimelineItem>
24
+ /** Render custom content per entry (receives the item). */
25
+ item?: Snippet<[TimelineItem]>
26
+ }
27
+
28
+ let { items, item }: Props = $props()
29
+ </script>
30
+
31
+ <ol class="sv-timeline">
32
+ {#each items as it, i (it.id ?? i)}
33
+ <li class="sv-timeline__item">
34
+ <div class="sv-timeline__rail">
35
+ <span class="sv-timeline__marker" style:--c={it.color}>
36
+ {#if it.icon}<span class="sv-timeline__glyph">{it.icon}</span>{/if}
37
+ </span>
38
+ </div>
39
+ <div class="sv-timeline__content">
40
+ {#if item}
41
+ {@render item(it)}
42
+ {:else}
43
+ <div class="sv-timeline__row">
44
+ <span class="sv-timeline__title">{it.title}</span>
45
+ {#if it.time}<span class="sv-timeline__time">{it.time}</span>{/if}
46
+ </div>
47
+ {#if it.description}<p class="sv-timeline__desc">{it.description}</p>{/if}
48
+ {/if}
49
+ </div>
50
+ </li>
51
+ {/each}
52
+ </ol>
53
+
54
+ <style>
55
+ .sv-timeline { list-style: none; margin: 0; padding: 0; color: var(--sg-fg, #0f172a); }
56
+ .sv-timeline__item { display: grid; grid-template-columns: 22px 1fr; gap: 10px; }
57
+ .sv-timeline__rail { display: flex; flex-direction: column; align-items: center; }
58
+ .sv-timeline__marker {
59
+ --c: var(--sg-accent, #2563eb);
60
+ flex: none; width: 14px; height: 14px; margin-top: 2px; border-radius: 50%;
61
+ display: grid; place-items: center; font-size: 8px; color: var(--sg-on-accent, #fff);
62
+ background: var(--c); box-shadow: 0 0 0 3px color-mix(in srgb, var(--c) 18%, transparent);
63
+ }
64
+ .sv-timeline__glyph { line-height: 1; }
65
+ /* The connecting line: grows to fill the rail between markers. */
66
+ .sv-timeline__item:not(:last-child) .sv-timeline__rail::after {
67
+ content: ''; flex: 1; width: 2px; margin: 3px 0 -2px; background: var(--sg-border, #e2e8f0); border-radius: 1px;
68
+ }
69
+ .sv-timeline__content { padding-bottom: 16px; min-width: 0; }
70
+ .sv-timeline__item:last-child .sv-timeline__content { padding-bottom: 0; }
71
+ .sv-timeline__row { display: flex; align-items: baseline; gap: 10px; }
72
+ .sv-timeline__title { font-size: 13.5px; font-weight: 600; }
73
+ .sv-timeline__time { margin-inline-start: auto; flex: none; font-size: 11.5px; color: var(--sg-muted, #94a3b8); font-variant-numeric: tabular-nums; }
74
+ .sv-timeline__desc { margin: 2px 0 0; font-size: 12.5px; color: var(--sg-muted, #64748b); line-height: 1.5; }
75
+ </style>
@@ -0,0 +1,124 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SvToaster - renders the shared toast queue (see `toast-store`). Mount ONCE
4
+ * near the app root; call `toast()` from anywhere to push notifications. The
5
+ * container is an `aria-live` region as a visual backstop, though each toast
6
+ * is also announced through the shared live region by the store.
7
+ *
8
+ * ```svelte
9
+ * <SvToaster position="bottom-right" />
10
+ * ```
11
+ */
12
+ import { portalToBody } from './popover'
13
+ import { toastStore, dismissToast, pauseToast, resumeToast, type Toast } from './toast-store.svelte'
14
+
15
+ type Position =
16
+ | 'top-left' | 'top-center' | 'top-right'
17
+ | 'bottom-left' | 'bottom-center' | 'bottom-right'
18
+
19
+ type Props = {
20
+ /** Corner/edge the stack anchors to. Default 'bottom-right'. */
21
+ position?: Position
22
+ /** Max toasts shown at once (older ones stay queued in the store). */
23
+ max?: number
24
+ }
25
+
26
+ let { position = 'bottom-right', max = 5 }: Props = $props()
27
+
28
+ const visible = $derived(toastStore.toasts.slice(-max))
29
+ const icon = (v: Toast['variant']) =>
30
+ v === 'success' ? '✓' : v === 'error' ? '✕' : v === 'warning' ? '⚠' : 'ℹ'
31
+
32
+ // Swipe-to-dismiss: drag a toast sideways past ~60px to dismiss it.
33
+ let drag = $state<{ id: number; startX: number; dx: number } | null>(null)
34
+ function onDown(e: PointerEvent, id: number) {
35
+ drag = { id, startX: e.clientX, dx: 0 }
36
+ ;(e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId)
37
+ pauseToast(id)
38
+ }
39
+ function onMove(e: PointerEvent) {
40
+ if (drag) drag = { ...drag, dx: e.clientX - drag.startX }
41
+ }
42
+ function onUp(id: number) {
43
+ if (!drag || drag.id !== id) return
44
+ const dx = drag.dx
45
+ drag = null
46
+ if (Math.abs(dx) > 60) dismissToast(id)
47
+ else resumeToast(id)
48
+ }
49
+ const dragDx = (id: number) => (drag?.id === id ? drag.dx : 0)
50
+ </script>
51
+
52
+ <div class="sv-toaster sv-toaster--{position}" use:portalToBody role="region" aria-label="Notifications">
53
+ {#each visible as t (t.id)}
54
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
55
+ <div
56
+ class="sv-toast sv-toast--{t.variant}"
57
+ class:is-dragging={drag?.id === t.id}
58
+ role={t.variant === 'error' || t.variant === 'warning' ? 'alert' : 'status'}
59
+ style:transform={dragDx(t.id) ? `translateX(${dragDx(t.id)}px)` : undefined}
60
+ style:opacity={dragDx(t.id) ? 1 - Math.min(Math.abs(dragDx(t.id)) / 160, 0.7) : undefined}
61
+ onmouseenter={() => pauseToast(t.id)}
62
+ onmouseleave={() => resumeToast(t.id)}
63
+ onfocusin={() => pauseToast(t.id)}
64
+ onfocusout={() => resumeToast(t.id)}
65
+ onpointerdown={(e) => onDown(e, t.id)}
66
+ onpointermove={onMove}
67
+ onpointerup={() => onUp(t.id)}
68
+ onpointercancel={() => onUp(t.id)}
69
+ >
70
+ <span class="sv-toast__icon" aria-hidden="true">{icon(t.variant)}</span>
71
+ <div class="sv-toast__content">
72
+ {#if t.title}<div class="sv-toast__title">{t.title}</div>{/if}
73
+ <div class="sv-toast__msg">{t.message}</div>
74
+ </div>
75
+ {#if t.dismissible}
76
+ <button type="button" class="sv-toast__x" aria-label="Dismiss" onclick={() => dismissToast(t.id)}>&times;</button>
77
+ {/if}
78
+ </div>
79
+ {/each}
80
+ </div>
81
+
82
+ <style>
83
+ :global(.sv-toaster) {
84
+ position: fixed; z-index: 2147483001; display: flex; flex-direction: column; gap: 8px;
85
+ padding: 16px; max-width: min(92vw, 380px); pointer-events: none;
86
+ }
87
+ :global(.sv-toaster--top-left) { top: 0; left: 0; }
88
+ :global(.sv-toaster--top-center) { top: 0; left: 50%; transform: translateX(-50%); align-items: center; }
89
+ :global(.sv-toaster--top-right) { top: 0; right: 0; }
90
+ :global(.sv-toaster--bottom-left) { bottom: 0; left: 0; flex-direction: column-reverse; }
91
+ :global(.sv-toaster--bottom-center) { bottom: 0; left: 50%; transform: translateX(-50%); align-items: center; flex-direction: column-reverse; }
92
+ :global(.sv-toaster--bottom-right) { bottom: 0; right: 0; flex-direction: column-reverse; }
93
+
94
+ :global(.sv-toast) {
95
+ pointer-events: auto;
96
+ display: flex; align-items: flex-start; gap: 10px; width: 100%; box-sizing: border-box;
97
+ padding: 11px 12px; border-radius: 10px; font-size: 13px; line-height: 1.45;
98
+ background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
99
+ border: 1px solid var(--sg-border, #e2e8f0);
100
+ border-inline-start: 3px solid var(--_accent, var(--sg-accent, #2563eb));
101
+ box-shadow: 0 12px 32px -12px rgba(15, 23, 42, 0.4);
102
+ animation: sv-toast-in 0.18s cubic-bezier(0.16, 1, 0.3, 1) both;
103
+ transition: transform 0.2s ease, opacity 0.2s ease;
104
+ touch-action: pan-y; /* let vertical scroll through; capture horizontal swipe */
105
+ }
106
+ :global(.sv-toast.is-dragging) { transition: none; cursor: grabbing; user-select: none; }
107
+ @keyframes sv-toast-in { from { opacity: 0; transform: translateY(6px) scale(0.98) } to { opacity: 1 } }
108
+ @media (prefers-reduced-motion: reduce) { :global(.sv-toast) { animation: none } }
109
+
110
+ :global(.sv-toast--success) { --_accent: var(--sg-success, #16a34a); }
111
+ :global(.sv-toast--error) { --_accent: var(--sg-danger, #dc2626); }
112
+ :global(.sv-toast--warning) { --_accent: var(--sg-warning, #f59e0b); }
113
+ :global(.sv-toast--info) { --_accent: var(--sg-accent, #2563eb); }
114
+
115
+ :global(.sv-toast__icon) { flex: none; width: 18px; height: 18px; display: grid; place-items: center; color: var(--_accent); font-weight: 700; }
116
+ :global(.sv-toast__content) { flex: 1; min-width: 0; }
117
+ :global(.sv-toast__title) { font-weight: 650; margin-bottom: 1px; }
118
+ :global(.sv-toast__msg) { color: var(--sg-fg, #0f172a); overflow-wrap: anywhere; }
119
+ :global(.sv-toast__x) {
120
+ flex: none; width: 22px; height: 22px; display: grid; place-items: center; margin: -2px -2px 0 0;
121
+ background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; border-radius: 5px; font-size: 17px; line-height: 1;
122
+ }
123
+ :global(.sv-toast__x:hover) { background: var(--sg-row-hover-bg, #f1f5f9); color: var(--sg-fg, #0f172a); }
124
+ </style>
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Component tests for SvToaster: renders the queue, and swipe-to-dismiss.
3
+ */
4
+ import { afterEach, describe, expect, it } from 'vitest'
5
+ import { mount, unmount, flushSync } from 'svelte'
6
+ import SvToaster from './SvToaster.svelte'
7
+ import { toast, clearToasts, toastStore } from './toast-store.svelte'
8
+
9
+ function mountToaster() {
10
+ const target = document.createElement('div')
11
+ document.body.appendChild(target)
12
+ const app = mount(SvToaster, { target, props: {} })
13
+ flushSync()
14
+ return { destroy: () => { unmount(app); target.remove() } }
15
+ }
16
+
17
+ const toastEl = () => document.body.querySelector<HTMLElement>('.sv-toast')
18
+
19
+ afterEach(() => {
20
+ clearToasts()
21
+ document.body.innerHTML = ''
22
+ })
23
+
24
+ describe('SvToaster', () => {
25
+ it('renders a queued toast', () => {
26
+ const { destroy } = mountToaster()
27
+ try {
28
+ toast('Saved', { duration: 0 })
29
+ flushSync()
30
+ expect(toastEl()).not.toBeNull()
31
+ expect(toastEl()!.textContent).toContain('Saved')
32
+ } finally { destroy() }
33
+ })
34
+
35
+ it('swiping a toast past the threshold dismisses it', () => {
36
+ const { destroy } = mountToaster()
37
+ try {
38
+ toast('Swipe me', { duration: 0 })
39
+ flushSync()
40
+ const el = toastEl()!
41
+ el.dispatchEvent(new MouseEvent('pointerdown', { bubbles: true, clientX: 100 }))
42
+ el.dispatchEvent(new MouseEvent('pointermove', { bubbles: true, clientX: 220 })) // dx 120 > 60
43
+ el.dispatchEvent(new MouseEvent('pointerup', { bubbles: true, clientX: 220 }))
44
+ flushSync()
45
+ expect(toastStore.toasts).toHaveLength(0)
46
+ expect(toastEl()).toBeNull()
47
+ } finally { destroy() }
48
+ })
49
+
50
+ it('a small swipe does not dismiss', () => {
51
+ const { destroy } = mountToaster()
52
+ try {
53
+ toast('Keep me', { duration: 0 })
54
+ flushSync()
55
+ const el = toastEl()!
56
+ el.dispatchEvent(new MouseEvent('pointerdown', { bubbles: true, clientX: 100 }))
57
+ el.dispatchEvent(new MouseEvent('pointermove', { bubbles: true, clientX: 130 })) // dx 30 < 60
58
+ el.dispatchEvent(new MouseEvent('pointerup', { bubbles: true, clientX: 130 }))
59
+ flushSync()
60
+ expect(toastStore.toasts).toHaveLength(1)
61
+ } finally { destroy() }
62
+ })
63
+ })
@@ -4,18 +4,34 @@
4
4
  * Parity: Smart `smart-toggle-button`. Controlled via `pressed` + `onChange`.
5
5
  */
6
6
  import type { Snippet } from 'svelte'
7
+ import SvField from './SvField.svelte'
8
+ import { nextEditorId, type SvEditorProps } from './editor-contract'
7
9
  import { createToggle } from './createToggle.svelte'
8
10
 
9
- type Props = {
11
+ type Props = SvEditorProps & {
10
12
  pressed?: boolean
11
13
  onChange?: (pressed: boolean) => void
12
- disabled?: boolean
13
- size?: 'sm' | 'md' | 'lg'
14
- ariaLabel?: string
15
14
  children?: Snippet
16
15
  }
17
16
 
18
- let { pressed = false, onChange, disabled = false, size = 'md', ariaLabel, children }: Props = $props()
17
+ let {
18
+ pressed = false,
19
+ onChange,
20
+ disabled = false,
21
+ size = 'md',
22
+ ariaLabel,
23
+ invalid = false,
24
+ required = false,
25
+ error,
26
+ label,
27
+ hint,
28
+ dir,
29
+ id,
30
+ children,
31
+ }: Props = $props()
32
+
33
+ const autoId = nextEditorId('sv-toggle')
34
+ const uid = $derived(id ?? autoId)
19
35
 
20
36
  // The styled toggle is just a renderer over the headless core.
21
37
  const t = createToggle({
@@ -23,16 +39,24 @@
23
39
  onChange: (v) => onChange?.(v),
24
40
  disabled: () => disabled,
25
41
  ariaLabel: () => ariaLabel,
42
+ id: () => uid,
43
+ invalid: () => invalid,
44
+ required: () => required,
45
+ error: () => error,
46
+ hint: () => hint,
26
47
  })
27
48
  </script>
28
49
 
29
- <button
30
- class="sv-toggle sv-toggle--{size}"
31
- class:is-pressed={pressed}
32
- {...t.buttonProps()}
33
- >
34
- {#if children}{@render children()}{/if}
35
- </button>
50
+ <SvField id={uid} {label} {hint} {error} {required} {dir}>
51
+ <button
52
+ class="sv-toggle sv-toggle--{size}"
53
+ class:is-pressed={pressed}
54
+ class:is-invalid={invalid}
55
+ {...t.buttonProps()}
56
+ >
57
+ {#if children}{@render children()}{/if}
58
+ </button>
59
+ </SvField>
36
60
 
37
61
  <style>
38
62
  .sv-toggle {
@@ -53,5 +77,6 @@
53
77
  background: var(--_accent); color: var(--sg-on-accent, #fff); border-color: var(--_accent);
54
78
  }
55
79
  .sv-toggle[disabled] { opacity: 0.55; cursor: not-allowed; }
80
+ .sv-toggle.is-invalid { border-color: var(--sg-danger, #dc2626); }
56
81
  .sv-toggle:focus-visible { outline: 2px solid var(--sg-focus-ring, var(--_accent)); outline-offset: 2px; }
57
82
  </style>
@@ -0,0 +1,113 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SvTooltip - a small hover/focus tooltip anchored to its child, portalled to
4
+ * <body> so it is never clipped. Shows after a short delay, hides on leave /
5
+ * blur / Escape, and is wired via `aria-describedby` (role="tooltip"). Respects
6
+ * keyboard users (opens on focus).
7
+ *
8
+ * ```svelte
9
+ * <SvTooltip text="Delete row"><button aria-label="Delete">🗑</button></SvTooltip>
10
+ * ```
11
+ */
12
+ import type { Snippet } from 'svelte'
13
+ import { portalToBody, popIn } from './popover'
14
+ import { nextEditorId } from './editor-contract'
15
+
16
+ type Props = {
17
+ text: string
18
+ placement?: 'top' | 'bottom'
19
+ /** Show delay in ms. Default 300. */
20
+ delay?: number
21
+ disabled?: boolean
22
+ children?: Snippet
23
+ }
24
+
25
+ let { text, placement = 'top', delay = 300, disabled = false, children }: Props = $props()
26
+
27
+ let anchorEl = $state<HTMLSpanElement | null>(null)
28
+ let tipEl = $state<HTMLDivElement | null>(null)
29
+ let open = $state(false)
30
+ let pos = $state({ top: 0, left: 0, arrowX: 12 })
31
+ const tid = nextEditorId('sv-tip')
32
+ let showTimer: ReturnType<typeof setTimeout> | undefined
33
+
34
+ function place() {
35
+ if (!anchorEl) return
36
+ const r = anchorEl.getBoundingClientRect()
37
+ const tipH = tipEl?.offsetHeight ?? 30
38
+ const tipW = tipEl?.offsetWidth ?? 0
39
+ const top = placement === 'top' ? r.top - tipH - 8 : r.bottom + 8
40
+ let left = r.left + r.width / 2 - tipW / 2
41
+ left = Math.max(4, Math.min(left, window.innerWidth - tipW - 4))
42
+ // Keep the arrow under the anchor's center even when the tip is clamped.
43
+ const arrowX = Math.max(10, Math.min(tipW - 10, r.left + r.width / 2 - left))
44
+ pos = { top, left, arrowX }
45
+ }
46
+
47
+ function show() {
48
+ if (disabled || !text) return
49
+ clearTimeout(showTimer)
50
+ showTimer = setTimeout(() => { open = true; queueMicrotask(place) }, delay)
51
+ }
52
+ function hide() { clearTimeout(showTimer); open = false }
53
+
54
+ $effect(() => {
55
+ if (!open) return
56
+ place()
57
+ const rp = () => place()
58
+ window.addEventListener('scroll', rp, true)
59
+ window.addEventListener('resize', rp)
60
+ const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') hide() }
61
+ document.addEventListener('keydown', onKey)
62
+ return () => {
63
+ window.removeEventListener('scroll', rp, true)
64
+ window.removeEventListener('resize', rp)
65
+ document.removeEventListener('keydown', onKey)
66
+ }
67
+ })
68
+ </script>
69
+
70
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
71
+ <span
72
+ bind:this={anchorEl}
73
+ class="sv-tip__anchor"
74
+ aria-describedby={open ? tid : undefined}
75
+ onpointerenter={show}
76
+ onpointerleave={hide}
77
+ onfocusin={show}
78
+ onfocusout={hide}
79
+ >
80
+ {@render children?.()}
81
+ </span>
82
+
83
+ {#if open}
84
+ <div
85
+ bind:this={tipEl}
86
+ id={tid}
87
+ class="sv-tip"
88
+ role="tooltip"
89
+ use:portalToBody
90
+ use:popIn={{ up: placement === 'top', duration: 110, scale: 0.9 }}
91
+ class:sv-tip--bottom={placement === 'bottom'}
92
+ style:position="fixed"
93
+ style:top={`${pos.top}px`}
94
+ style:left={`${pos.left}px`}
95
+ style:--tip-arrow-x={`${pos.arrowX}px`}
96
+ >{text}<span class="sv-tip__arrow" aria-hidden="true"></span></div>
97
+ {/if}
98
+
99
+ <style>
100
+ .sv-tip__anchor { display: inline-flex; }
101
+ :global(.sv-tip) {
102
+ z-index: 2147483647; pointer-events: none; position: relative;
103
+ max-width: 240px; padding: 5px 9px; font-size: 12px; line-height: 1.4; font-weight: 500;
104
+ background: var(--sg-fg, #0f172a); color: var(--sg-bg, #fff);
105
+ border-radius: 6px; box-shadow: 0 6px 20px -6px rgba(15, 23, 42, 0.5); white-space: normal;
106
+ }
107
+ :global(.sv-tip__arrow) {
108
+ position: absolute; inset-inline-start: var(--tip-arrow-x, 12px); width: 8px; height: 8px;
109
+ background: var(--sg-fg, #0f172a); transform: translateX(-50%) rotate(45deg);
110
+ }
111
+ :global(.sv-tip:not(.sv-tip--bottom) .sv-tip__arrow) { bottom: -3px; }
112
+ :global(.sv-tip--bottom .sv-tip__arrow) { top: -3px; }
113
+ </style>