@svgrid/grid 1.2.34 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (490) hide show
  1. package/README.md +6 -5
  2. package/dist/FlexRender.svelte +96 -96
  3. package/dist/GridFooter.svelte +178 -178
  4. package/dist/SvAccordion.svelte +109 -0
  5. package/dist/SvAccordion.svelte.d.ts +28 -0
  6. package/dist/SvAlert.svelte +92 -0
  7. package/dist/SvAlert.svelte.d.ts +27 -0
  8. package/dist/SvAutoComplete.svelte +47 -18
  9. package/dist/SvAutoComplete.svelte.d.ts +2 -5
  10. package/dist/SvAvatar.svelte +75 -0
  11. package/dist/SvAvatar.svelte.d.ts +14 -0
  12. package/dist/SvAvatarGroup.svelte +48 -0
  13. package/dist/SvAvatarGroup.svelte.d.ts +16 -0
  14. package/dist/SvBadge.svelte +54 -0
  15. package/dist/SvBadge.svelte.d.ts +23 -0
  16. package/dist/SvBreadcrumb.svelte +88 -0
  17. package/dist/SvBreadcrumb.svelte.d.ts +23 -0
  18. package/dist/SvButtonGroup.svelte +121 -0
  19. package/dist/SvButtonGroup.svelte.d.ts +16 -0
  20. package/dist/SvCalendar.svelte +72 -9
  21. package/dist/SvCalendar.svelte.d.ts +34 -1
  22. package/dist/SvCard.svelte +71 -0
  23. package/dist/SvCard.svelte.d.ts +28 -0
  24. package/dist/SvCarousel.svelte +134 -0
  25. package/dist/SvCarousel.svelte.d.ts +28 -0
  26. package/dist/SvCheckBox.svelte +36 -21
  27. package/dist/SvCheckBox.svelte.d.ts +2 -5
  28. package/dist/SvChip.svelte +88 -0
  29. package/dist/SvChip.svelte.d.ts +31 -0
  30. package/dist/SvCircularProgress.svelte +109 -0
  31. package/dist/SvCircularProgress.svelte.d.ts +31 -0
  32. package/dist/SvColorInput.svelte +54 -27
  33. package/dist/SvColorInput.svelte.d.ts +10 -6
  34. package/dist/SvComboBox.svelte +119 -33
  35. package/dist/SvComboBox.svelte.d.ts +18 -6
  36. package/dist/SvCommand.svelte +197 -0
  37. package/dist/SvCommand.svelte.d.ts +26 -0
  38. package/dist/SvContextMenu.svelte +96 -0
  39. package/dist/SvContextMenu.svelte.d.ts +25 -0
  40. package/dist/SvCountryInput.svelte +56 -21
  41. package/dist/SvCountryInput.svelte.d.ts +9 -5
  42. package/dist/SvDateRangeInput.svelte +256 -0
  43. package/dist/SvDateRangeInput.svelte.d.ts +37 -0
  44. package/dist/SvDateTimePicker.svelte +57 -20
  45. package/dist/SvDateTimePicker.svelte.d.ts +12 -4
  46. package/dist/SvDivider.svelte +61 -0
  47. package/dist/SvDivider.svelte.d.ts +22 -0
  48. package/dist/SvDrawer.svelte +226 -0
  49. package/dist/SvDrawer.svelte.d.ts +43 -0
  50. package/dist/SvDropDownList.svelte +114 -35
  51. package/dist/SvDropDownList.svelte.d.ts +7 -6
  52. package/dist/SvDurationInput.svelte +108 -0
  53. package/dist/SvDurationInput.svelte.d.ts +15 -0
  54. package/dist/SvEmptyState.svelte +59 -0
  55. package/dist/SvEmptyState.svelte.d.ts +25 -0
  56. package/dist/SvField.svelte +85 -0
  57. package/dist/SvField.svelte.d.ts +30 -0
  58. package/dist/SvFileUpload.svelte +169 -0
  59. package/dist/SvFileUpload.svelte.d.ts +24 -0
  60. package/dist/SvForm.svelte +7 -1
  61. package/dist/SvForm.svelte.d.ts +3 -0
  62. package/dist/SvGauge.svelte +7 -1
  63. package/dist/SvGauge.svelte.d.ts +11 -0
  64. package/dist/SvGrid.controller.svelte.d.ts +6 -0
  65. package/dist/SvGrid.controller.svelte.js +64 -9
  66. package/dist/SvGrid.css +2376 -2343
  67. package/dist/SvGrid.svelte +2764 -2651
  68. package/dist/SvGrid.types.d.ts +366 -0
  69. package/dist/SvGridBoard.svelte +2281 -0
  70. package/dist/SvGridBoard.svelte.d.ts +31 -0
  71. package/dist/SvGridDropdown.svelte +666 -665
  72. package/dist/SvGridSelect.svelte +255 -0
  73. package/dist/SvGridSelect.svelte.d.ts +32 -0
  74. package/dist/SvGroupCell.svelte +109 -0
  75. package/dist/SvGroupCell.svelte.d.ts +41 -0
  76. package/dist/SvListBox.svelte +123 -36
  77. package/dist/SvListBox.svelte.d.ts +12 -8
  78. package/dist/SvMaskedInput.svelte +53 -13
  79. package/dist/SvMaskedInput.svelte.d.ts +16 -6
  80. package/dist/SvMenu.svelte +101 -0
  81. package/dist/SvMenu.svelte.d.ts +26 -0
  82. package/dist/SvMenuList.svelte +143 -0
  83. package/dist/SvMenuList.svelte.d.ts +24 -0
  84. package/dist/SvModal.svelte +220 -0
  85. package/dist/SvModal.svelte.d.ts +31 -0
  86. package/dist/SvMultiSelect.svelte +277 -0
  87. package/dist/SvMultiSelect.svelte.d.ts +31 -0
  88. package/dist/SvNavPane.svelte +314 -0
  89. package/dist/SvNavPane.svelte.d.ts +59 -0
  90. package/dist/SvNumberInput.svelte +23 -9
  91. package/dist/SvNumberInput.svelte.d.ts +2 -0
  92. package/dist/SvOtpInput.svelte +157 -0
  93. package/dist/SvOtpInput.svelte.d.ts +16 -0
  94. package/dist/SvPagination.svelte +100 -0
  95. package/dist/SvPagination.svelte.d.ts +16 -0
  96. package/dist/SvPasswordInput.svelte +42 -12
  97. package/dist/SvPasswordInput.svelte.d.ts +13 -6
  98. package/dist/SvPhoneInput.svelte +43 -11
  99. package/dist/SvPhoneInput.svelte.d.ts +10 -11
  100. package/dist/SvPopover.svelte +127 -0
  101. package/dist/SvPopover.svelte.d.ts +35 -0
  102. package/dist/SvProgress.svelte +116 -0
  103. package/dist/SvProgress.svelte.d.ts +32 -0
  104. package/dist/SvRadioGroup.svelte +40 -22
  105. package/dist/SvRadioGroup.svelte.d.ts +2 -10
  106. package/dist/SvRating.svelte +51 -30
  107. package/dist/SvRating.svelte.d.ts +10 -7
  108. package/dist/SvRichText.svelte +192 -0
  109. package/dist/SvRichText.svelte.d.ts +16 -0
  110. package/dist/SvRowGroupPanel.svelte +147 -0
  111. package/dist/SvRowGroupPanel.svelte.d.ts +17 -0
  112. package/dist/SvScrollArea.svelte +50 -0
  113. package/dist/SvScrollArea.svelte.d.ts +22 -0
  114. package/dist/SvSkeleton.svelte +76 -0
  115. package/dist/SvSkeleton.svelte.d.ts +25 -0
  116. package/dist/SvSlider.svelte +41 -9
  117. package/dist/SvSlider.svelte.d.ts +4 -4
  118. package/dist/SvSparkline.svelte +77 -0
  119. package/dist/SvSparkline.svelte.d.ts +28 -0
  120. package/dist/SvSplitter.svelte +138 -0
  121. package/dist/SvSplitter.svelte.d.ts +32 -0
  122. package/dist/SvStat.svelte +86 -0
  123. package/dist/SvStat.svelte.d.ts +30 -0
  124. package/dist/SvStepper.svelte +91 -0
  125. package/dist/SvStepper.svelte.d.ts +24 -0
  126. package/dist/SvSwitchButton.svelte +39 -24
  127. package/dist/SvSwitchButton.svelte.d.ts +2 -5
  128. package/dist/SvTabs.svelte +147 -28
  129. package/dist/SvTabs.svelte.d.ts +16 -4
  130. package/dist/SvTagsInput.svelte +59 -22
  131. package/dist/SvTagsInput.svelte.d.ts +9 -4
  132. package/dist/SvTextArea.svelte +113 -0
  133. package/dist/SvTextArea.svelte.d.ts +18 -0
  134. package/dist/SvTextInput.svelte +115 -0
  135. package/dist/SvTextInput.svelte.d.ts +20 -0
  136. package/dist/SvTimePicker.svelte +19 -5
  137. package/dist/SvTimePicker.svelte.d.ts +12 -0
  138. package/dist/SvTimeline.svelte +75 -0
  139. package/dist/SvTimeline.svelte.d.ts +24 -0
  140. package/dist/SvToaster.svelte +124 -0
  141. package/dist/SvToaster.svelte.d.ts +10 -0
  142. package/dist/SvToggleButton.svelte +37 -12
  143. package/dist/SvToggleButton.svelte.d.ts +2 -4
  144. package/dist/SvTooltip.svelte +113 -0
  145. package/dist/SvTooltip.svelte.d.ts +22 -0
  146. package/dist/SvTour.svelte +151 -0
  147. package/dist/SvTour.svelte.d.ts +25 -0
  148. package/dist/SvTree.svelte +286 -44
  149. package/dist/SvTree.svelte.d.ts +26 -9
  150. package/dist/SvTreeSelect.svelte +222 -0
  151. package/dist/SvTreeSelect.svelte.d.ts +24 -0
  152. package/dist/a11y/dismissable.d.ts +32 -0
  153. package/dist/a11y/dismissable.js +54 -0
  154. package/dist/a11y/focus-trap.d.ts +39 -0
  155. package/dist/a11y/focus-trap.js +142 -0
  156. package/dist/a11y/live-region.d.ts +8 -0
  157. package/dist/a11y/live-region.js +60 -0
  158. package/dist/a11y/scroll-lock.d.ts +4 -0
  159. package/dist/a11y/scroll-lock.js +41 -0
  160. package/dist/a11y.d.ts +3 -1
  161. package/dist/a11y.js +1 -1
  162. package/dist/avatar.d.ts +10 -0
  163. package/dist/avatar.js +22 -0
  164. package/dist/builtin-editors.d.ts +5 -0
  165. package/dist/builtin-editors.js +55 -0
  166. package/dist/cdn/svgrid.js +16022 -8235
  167. package/dist/cdn/svgrid.svelte-external.js +14543 -6765
  168. package/dist/columns.js +3 -2
  169. package/dist/core.d.ts +7 -1
  170. package/dist/countries.d.ts +12 -0
  171. package/dist/countries.js +24 -0
  172. package/dist/createAccordion.svelte.d.ts +71 -0
  173. package/dist/createAccordion.svelte.js +99 -0
  174. package/dist/createAutocomplete.svelte.d.ts +13 -4
  175. package/dist/createAutocomplete.svelte.js +11 -1
  176. package/dist/createButtonGroup.svelte.d.ts +62 -0
  177. package/dist/createButtonGroup.svelte.js +154 -0
  178. package/dist/createCalendar.svelte.d.ts +5 -0
  179. package/dist/createCalendar.svelte.js +2 -0
  180. package/dist/createCheckbox.svelte.d.ts +9 -17
  181. package/dist/createCheckbox.svelte.js +28 -1
  182. package/dist/createColorInput.svelte.d.ts +12 -22
  183. package/dist/createColorInput.svelte.js +13 -0
  184. package/dist/createCombobox.svelte.d.ts +16 -5
  185. package/dist/createCombobox.svelte.js +13 -2
  186. package/dist/createCountryInput.svelte.d.ts +14 -3
  187. package/dist/createCountryInput.svelte.js +12 -2
  188. package/dist/createDateTimePicker.svelte.d.ts +17 -2
  189. package/dist/createDateTimePicker.svelte.js +12 -2
  190. package/dist/createDropdownList.svelte.d.ts +14 -5
  191. package/dist/createDropdownList.svelte.js +50 -1
  192. package/dist/createFileUpload.svelte.d.ts +68 -0
  193. package/dist/createFileUpload.svelte.js +122 -0
  194. package/dist/createListbox.svelte.d.ts +10 -1
  195. package/dist/createListbox.svelte.js +48 -1
  196. package/dist/createMaskedInput.svelte.d.ts +11 -2
  197. package/dist/createMaskedInput.svelte.js +11 -1
  198. package/dist/createNumberInput.svelte.d.ts +1 -0
  199. package/dist/createNumberInput.svelte.js +1 -0
  200. package/dist/createPasswordInput.svelte.d.ts +19 -21
  201. package/dist/createPasswordInput.svelte.js +16 -4
  202. package/dist/createPhoneInput.svelte.d.ts +26 -7
  203. package/dist/createPhoneInput.svelte.js +24 -3
  204. package/dist/createPopoverSelect.svelte.d.ts +86 -0
  205. package/dist/createPopoverSelect.svelte.js +196 -0
  206. package/dist/createRadioGroup.svelte.d.ts +9 -0
  207. package/dist/createRadioGroup.svelte.js +11 -1
  208. package/dist/createRating.svelte.d.ts +11 -0
  209. package/dist/createRating.svelte.js +12 -1
  210. package/dist/createSlider.svelte.d.ts +2 -0
  211. package/dist/createSlider.svelte.js +16 -5
  212. package/dist/createSplitter.svelte.d.ts +67 -0
  213. package/dist/createSplitter.svelte.js +99 -0
  214. package/dist/createSwitch.svelte.d.ts +9 -16
  215. package/dist/createSwitch.svelte.js +28 -1
  216. package/dist/createTabs.svelte.d.ts +7 -0
  217. package/dist/createTabs.svelte.js +17 -2
  218. package/dist/createTagsInput.svelte.d.ts +13 -1
  219. package/dist/createTagsInput.svelte.js +12 -3
  220. package/dist/createToggle.svelte.d.ts +9 -0
  221. package/dist/createToggle.svelte.js +12 -1
  222. package/dist/createTree.svelte.d.ts +19 -0
  223. package/dist/createTree.svelte.js +108 -15
  224. package/dist/debounce.d.ts +11 -0
  225. package/dist/debounce.js +30 -0
  226. package/dist/duration.d.ts +15 -0
  227. package/dist/duration.js +51 -0
  228. package/dist/editor-contract.d.ts +62 -7
  229. package/dist/editor-contract.js +45 -5
  230. package/dist/editor-registry.d.ts +67 -0
  231. package/dist/editor-registry.js +38 -0
  232. package/dist/editors/cell-editors.d.ts +1 -1
  233. package/dist/editors/cell-editors.js +5 -0
  234. package/dist/fuzzy.d.ts +3 -0
  235. package/dist/fuzzy.js +41 -0
  236. package/dist/grid-select.d.ts +9 -0
  237. package/dist/grid-select.js +8 -0
  238. package/dist/index.d.ts +79 -6
  239. package/dist/index.js +78 -4
  240. package/dist/keyboard-handlers.js +19 -0
  241. package/dist/list-option.d.ts +36 -0
  242. package/dist/list-option.js +68 -0
  243. package/dist/otp.d.ts +16 -0
  244. package/dist/otp.js +26 -0
  245. package/dist/paginate.d.ts +20 -0
  246. package/dist/paginate.js +40 -0
  247. package/dist/popover.d.ts +15 -1
  248. package/dist/popover.js +29 -3
  249. package/dist/recurrence.d.ts +36 -0
  250. package/dist/recurrence.js +90 -0
  251. package/dist/server-data-source.d.ts +89 -0
  252. package/dist/server-data-source.js +4 -0
  253. package/dist/server-group-model.d.ts +98 -0
  254. package/dist/server-group-model.js +263 -0
  255. package/dist/toast-store.svelte.d.ts +41 -0
  256. package/dist/toast-store.svelte.js +113 -0
  257. package/dist/tree-select.d.ts +19 -0
  258. package/dist/tree-select.js +40 -0
  259. package/dist/ui-app.types.d.ts +80 -0
  260. package/dist/ui-app.types.js +7 -0
  261. package/dist/validators.d.ts +43 -0
  262. package/dist/validators.js +53 -0
  263. package/dist/virtual.d.ts +35 -0
  264. package/dist/virtual.js +34 -0
  265. package/package.json +85 -85
  266. package/src/FlexRender.svelte +96 -96
  267. package/src/GridFooter.svelte +178 -178
  268. package/src/SvAccordion.svelte +109 -0
  269. package/src/SvAccordion.test.ts +66 -0
  270. package/src/SvAlert.svelte +92 -0
  271. package/src/SvAutoComplete.svelte +47 -18
  272. package/src/SvAvatar.svelte +75 -0
  273. package/src/SvAvatarGroup.svelte +48 -0
  274. package/src/SvBadge.svelte +54 -0
  275. package/src/SvBreadcrumb.svelte +88 -0
  276. package/src/SvButtonGroup.svelte +121 -0
  277. package/src/SvButtonGroup.test.ts +72 -0
  278. package/src/SvCalendar.svelte +72 -9
  279. package/src/SvCard.svelte +71 -0
  280. package/src/SvCarousel.svelte +134 -0
  281. package/src/SvCheckBox.svelte +36 -21
  282. package/src/SvChip.svelte +88 -0
  283. package/src/SvCircularProgress.svelte +109 -0
  284. package/src/SvColorInput.svelte +54 -27
  285. package/src/SvComboBox.svelte +119 -33
  286. package/src/SvCommand.svelte +197 -0
  287. package/src/SvContextMenu.svelte +96 -0
  288. package/src/SvCountryInput.svelte +56 -21
  289. package/src/SvDateRangeInput.svelte +256 -0
  290. package/src/SvDateRangeInput.test.ts +80 -0
  291. package/src/SvDateTimePicker.svelte +57 -20
  292. package/src/SvDivider.svelte +61 -0
  293. package/src/SvDrawer.svelte +226 -0
  294. package/src/SvDrawer.test.ts +125 -0
  295. package/src/SvDropDownList.svelte +114 -35
  296. package/src/SvDurationInput.svelte +108 -0
  297. package/src/SvEmptyState.svelte +59 -0
  298. package/src/SvField.svelte +85 -0
  299. package/src/SvFileUpload.svelte +169 -0
  300. package/src/SvFileUpload.test.ts +59 -0
  301. package/src/SvForm.svelte +7 -1
  302. package/src/SvGauge.svelte +7 -1
  303. package/src/SvGrid.controller.svelte.ts +2553 -2494
  304. package/src/SvGrid.css +2376 -2343
  305. package/src/SvGrid.svelte +2764 -2651
  306. package/src/SvGrid.types.ts +944 -570
  307. package/src/SvGridBoard.svelte +2281 -0
  308. package/src/SvGridDropdown.svelte +666 -665
  309. package/src/SvGridSelect.svelte +255 -0
  310. package/src/SvGridSelect.test.ts +100 -0
  311. package/src/SvGroupCell.svelte +109 -0
  312. package/src/SvListBox.svelte +123 -36
  313. package/src/SvMaskedInput.svelte +53 -13
  314. package/src/SvMenu.svelte +101 -0
  315. package/src/SvMenu.test.ts +73 -0
  316. package/src/SvMenuList.svelte +143 -0
  317. package/src/SvModal.svelte +220 -0
  318. package/src/SvMultiSelect.svelte +277 -0
  319. package/src/SvMultiSelect.test.ts +167 -0
  320. package/src/SvNavPane.svelte +314 -0
  321. package/src/SvNavPane.test.ts +70 -0
  322. package/src/SvNumberInput.svelte +23 -9
  323. package/src/SvOtpInput.svelte +157 -0
  324. package/src/SvPagination.svelte +100 -0
  325. package/src/SvPasswordInput.svelte +42 -12
  326. package/src/SvPhoneInput.svelte +43 -11
  327. package/src/SvPopover.svelte +127 -0
  328. package/src/SvProgress.svelte +116 -0
  329. package/src/SvRadioGroup.svelte +40 -22
  330. package/src/SvRating.svelte +51 -30
  331. package/src/SvRichText.svelte +192 -0
  332. package/src/SvRowGroupPanel.svelte +147 -0
  333. package/src/SvScrollArea.svelte +50 -0
  334. package/src/SvSkeleton.svelte +76 -0
  335. package/src/SvSlider.svelte +41 -9
  336. package/src/SvSparkline.svelte +77 -0
  337. package/src/SvSplitter.svelte +138 -0
  338. package/src/SvSplitter.test.ts +55 -0
  339. package/src/SvStat.svelte +86 -0
  340. package/src/SvStepper.svelte +91 -0
  341. package/src/SvSwitchButton.svelte +39 -24
  342. package/src/SvTabs.svelte +147 -28
  343. package/src/SvTagsInput.svelte +59 -22
  344. package/src/SvTextArea.svelte +113 -0
  345. package/src/SvTextInput.svelte +115 -0
  346. package/src/SvTimePicker.svelte +19 -5
  347. package/src/SvTimeline.svelte +75 -0
  348. package/src/SvToaster.svelte +124 -0
  349. package/src/SvToaster.test.ts +63 -0
  350. package/src/SvToggleButton.svelte +37 -12
  351. package/src/SvTooltip.svelte +113 -0
  352. package/src/SvTour.svelte +151 -0
  353. package/src/SvTree.svelte +286 -44
  354. package/src/SvTreeSelect.svelte +222 -0
  355. package/src/SvTreeSelect.test.ts +87 -0
  356. package/src/a11y/dismissable.test.ts +95 -0
  357. package/src/a11y/dismissable.ts +94 -0
  358. package/src/a11y/focus-trap.test.ts +110 -0
  359. package/src/a11y/focus-trap.ts +134 -0
  360. package/src/a11y/live-region.test.ts +40 -0
  361. package/src/a11y/live-region.ts +65 -0
  362. package/src/a11y/scroll-lock.test.ts +38 -0
  363. package/src/a11y/scroll-lock.ts +41 -0
  364. package/src/a11y.contract.test.ts +49 -49
  365. package/src/a11y.test.ts +59 -59
  366. package/src/a11y.ts +61 -59
  367. package/src/avatar.test.ts +32 -0
  368. package/src/avatar.ts +21 -0
  369. package/src/board.test.ts +927 -0
  370. package/src/build-api.ts +798 -798
  371. package/src/builtin-editors.grid.test.ts +85 -0
  372. package/src/builtin-editors.test.ts +58 -0
  373. package/src/builtin-editors.ts +56 -0
  374. package/src/cell-formatting.ts +169 -169
  375. package/src/cell-render.ts +469 -469
  376. package/src/collaboration.test.ts +104 -104
  377. package/src/collaboration.ts +167 -167
  378. package/src/columns.ts +3 -2
  379. package/src/core.performance.test.ts +30 -30
  380. package/src/core.ts +1111 -1101
  381. package/src/countries.ts +25 -0
  382. package/src/createAccordion.svelte.ts +126 -0
  383. package/src/createAutocomplete.svelte.ts +18 -1
  384. package/src/createButtonGroup.svelte.ts +166 -0
  385. package/src/createCalendar.svelte.ts +6 -0
  386. package/src/createCheckbox.svelte.ts +23 -1
  387. package/src/createColorInput.svelte.ts +20 -0
  388. package/src/createCombobox.svelte.ts +22 -2
  389. package/src/createCountryInput.svelte.ts +21 -2
  390. package/src/createDateTimePicker.svelte.ts +23 -2
  391. package/src/createDropdownList.svelte.ts +31 -2
  392. package/src/createFileUpload.svelte.ts +144 -0
  393. package/src/createGrid.svelte.ts +42 -42
  394. package/src/createGrid.test.ts +10 -10
  395. package/src/createGridState.svelte.ts +17 -17
  396. package/src/createListbox.svelte.ts +35 -3
  397. package/src/createMaskedInput.svelte.ts +18 -1
  398. package/src/createNumberInput.svelte.ts +2 -0
  399. package/src/createPasswordInput.svelte.ts +31 -5
  400. package/src/createPhoneInput.svelte.ts +38 -4
  401. package/src/createPopoverSelect.svelte.ts +208 -0
  402. package/src/createRadioGroup.svelte.ts +23 -1
  403. package/src/createRating.svelte.ts +26 -1
  404. package/src/createSlider.svelte.ts +14 -3
  405. package/src/createSplitter.svelte.ts +119 -0
  406. package/src/createSwitch.svelte.ts +26 -3
  407. package/src/createTabs.svelte.ts +20 -3
  408. package/src/createTagsInput.svelte.ts +24 -3
  409. package/src/createToggle.svelte.ts +25 -2
  410. package/src/createTree.svelte.ts +104 -14
  411. package/src/debounce.test.ts +50 -0
  412. package/src/debounce.ts +41 -0
  413. package/src/duration.test.ts +41 -0
  414. package/src/duration.ts +51 -0
  415. package/src/editing.test.ts +859 -859
  416. package/src/editing.ts +675 -675
  417. package/src/editor-contract.ts +87 -8
  418. package/src/editor-registry.grid.test.ts +98 -0
  419. package/src/editor-registry.test.ts +86 -0
  420. package/src/editor-registry.ts +106 -0
  421. package/src/editors/cell-editors.ts +10 -1
  422. package/src/export-data-api.test.ts +126 -126
  423. package/src/export-format.test.ts +107 -107
  424. package/src/export-format.ts +598 -598
  425. package/src/flex-render.ts +3 -3
  426. package/src/fuzzy.test.ts +40 -0
  427. package/src/fuzzy.ts +39 -0
  428. package/src/grid-select.test.ts +28 -0
  429. package/src/grid-select.ts +20 -0
  430. package/src/index.ts +463 -318
  431. package/src/keyboard-handlers.ts +20 -0
  432. package/src/keyboard.test.ts +59 -59
  433. package/src/keyboard.ts +97 -97
  434. package/src/list-option.ts +74 -0
  435. package/src/list-power.test.ts +71 -0
  436. package/src/menus.ts +582 -582
  437. package/src/merge-objects.ts +48 -48
  438. package/src/otp.test.ts +28 -0
  439. package/src/otp.ts +28 -0
  440. package/src/overlays.test.ts +85 -0
  441. package/src/paginate.test.ts +41 -0
  442. package/src/paginate.ts +61 -0
  443. package/src/phone-valid.test.ts +26 -0
  444. package/src/popover.ts +31 -3
  445. package/src/progress.test.ts +55 -0
  446. package/src/recurrence.test.ts +91 -0
  447. package/src/recurrence.ts +111 -0
  448. package/src/render-component.ts +28 -28
  449. package/src/selection.test.ts +754 -754
  450. package/src/selection.ts +600 -600
  451. package/src/server-data-source.test.ts +289 -289
  452. package/src/server-data-source.ts +413 -311
  453. package/src/server-group-model.test.ts +294 -0
  454. package/src/server-group-model.ts +370 -0
  455. package/src/sparkline.test.ts +68 -68
  456. package/src/sparkline.ts +169 -169
  457. package/src/spreadsheet.test.ts +489 -489
  458. package/src/spreadsheet.ts +304 -304
  459. package/src/static-functions.ts +11 -11
  460. package/src/subscribe.ts +38 -38
  461. package/src/svgrid-wrapper.types.ts +439 -439
  462. package/src/svgrid.behavior.test.ts +706 -706
  463. package/src/svgrid.features.test.ts +157 -157
  464. package/src/svgrid.new-features.wrapper.test.ts +251 -251
  465. package/src/svgrid.responsive.test.ts +72 -0
  466. package/src/svgrid.wrapper.test.ts +40 -40
  467. package/src/toast-store.svelte.ts +148 -0
  468. package/src/toast-store.test.ts +86 -0
  469. package/src/tree-edit.test.ts +49 -0
  470. package/src/tree-filter.test.ts +56 -0
  471. package/src/tree-select.test.ts +48 -0
  472. package/src/tree-select.ts +62 -0
  473. package/src/ui-app.test.ts +130 -0
  474. package/src/ui-app.types.ts +88 -0
  475. package/src/ui-display.test.ts +79 -0
  476. package/src/ui-inputs.test.ts +1 -1
  477. package/src/ui-status.test.ts +141 -0
  478. package/src/ui-tier1.test.ts +142 -0
  479. package/src/ui-tier2.test.ts +124 -0
  480. package/src/ui-tier3.test.ts +139 -0
  481. package/src/validators.test.ts +58 -0
  482. package/src/validators.ts +105 -0
  483. package/src/virtual.test.ts +72 -0
  484. package/src/virtual.ts +67 -0
  485. package/src/virtualization/column-virtualizer.test.ts +27 -27
  486. package/src/virtualization/column-virtualizer.ts +30 -30
  487. package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
  488. package/src/virtualization/types.ts +30 -30
  489. package/src/virtualization/virtualizer.test.ts +47 -47
  490. package/src/virtualization/virtualizer.ts +296 -296
@@ -7,20 +7,23 @@
7
7
  * The styled renderer over the headless `createMaskedInput` core; the mask
8
8
  * formatting + state live in the core, spread here via prop-getters.
9
9
  */
10
+ import type { Snippet } from 'svelte'
10
11
  import { unmask } from './datetime/mask'
12
+ import SvField from './SvField.svelte'
13
+ import { nextEditorId, type SvEditorProps } from './editor-contract'
11
14
  import { createMaskedInput } from './createMaskedInput.svelte'
12
15
 
13
- type Props = {
16
+ type Props = SvEditorProps & {
14
17
  value?: string
15
18
  /** Called with (masked, raw, complete). */
16
19
  onChange?: (masked: string, raw: string, complete: boolean) => void
17
20
  mask?: string
18
21
  placeholder?: string
19
- disabled?: boolean
20
- readonly?: boolean
21
- name?: string
22
- size?: 'sm' | 'md' | 'lg'
23
- ariaLabel?: string
22
+ /** Show a clear (x) button when there is a value. */
23
+ clearable?: boolean
24
+ /** Leading / trailing icon adornments. */
25
+ prefixIcon?: Snippet
26
+ suffixIcon?: Snippet
24
27
  }
25
28
 
26
29
  let {
@@ -33,8 +36,21 @@
33
36
  name,
34
37
  size = 'md',
35
38
  ariaLabel,
39
+ invalid = false,
40
+ required = false,
41
+ error,
42
+ label,
43
+ hint,
44
+ dir,
45
+ id,
46
+ clearable = false,
47
+ prefixIcon,
48
+ suffixIcon,
36
49
  }: Props = $props()
37
50
 
51
+ const autoId = nextEditorId('sv-mask')
52
+ const uid = $derived(id ?? autoId)
53
+
38
54
  const mi = createMaskedInput({
39
55
  value: () => value,
40
56
  onChange: (m, r, c) => onChange?.(m, r, c),
@@ -42,25 +58,49 @@
42
58
  placeholder: () => placeholder,
43
59
  disabled: () => disabled,
44
60
  readonly: () => readonly,
61
+ id: () => uid,
62
+ invalid: () => invalid,
63
+ required: () => required,
64
+ error: () => error,
65
+ hint: () => hint,
45
66
  ariaLabel: () => ariaLabel,
46
67
  })
47
68
  </script>
48
69
 
49
- <input class="sv-masked sv-masked--{size}" {...mi.inputProps()} />
50
- {#if name}<input type="hidden" {name} value={mask ? unmask(mi.masked, mask) : mi.masked} />{/if}
70
+ <SvField id={uid} {label} {hint} {error} {required} {dir}>
71
+ <div class="sv-masked sv-masked--{size}" class:is-invalid={invalid} class:is-disabled={disabled}>
72
+ {#if prefixIcon}<span class="sv-masked__adorn">{@render prefixIcon()}</span>{/if}
73
+ <input class="sv-masked__input" {...mi.inputProps()} />
74
+ {#if clearable && mi.masked && !disabled && !readonly}
75
+ <button type="button" class="sv-masked__clear" aria-label="Clear" tabindex="-1" onclick={() => onChange?.('', '', false)}>
76
+ <svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M6 6l12 12M18 6L6 18" /></svg>
77
+ </button>
78
+ {/if}
79
+ {#if suffixIcon}<span class="sv-masked__adorn">{@render suffixIcon()}</span>{/if}
80
+ {#if name}<input type="hidden" {name} value={mask ? unmask(mi.masked, mask) : mi.masked} />{/if}
81
+ </div>
82
+ </SvField>
51
83
 
52
84
  <style>
53
85
  .sv-masked {
54
86
  --_accent: var(--sg-accent, #2563eb);
55
- box-sizing: border-box; width: 200px;
87
+ box-sizing: border-box; width: 200px; display: inline-flex; align-items: center; gap: 6px;
56
88
  background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
57
89
  border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
58
- font: inherit; padding: 0 10px; outline: none;
59
- font-variant-numeric: tabular-nums; letter-spacing: 0.02em;
90
+ font: inherit; padding: 0 8px 0 10px;
60
91
  }
61
92
  .sv-masked--sm { height: 28px; font-size: 12px; }
62
93
  .sv-masked--md { height: 34px; font-size: 13px; }
63
94
  .sv-masked--lg { height: 40px; font-size: 15px; }
64
- .sv-masked:focus { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
65
- .sv-masked:disabled { opacity: 0.6; }
95
+ .sv-masked:focus-within { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
96
+ .sv-masked.is-invalid { border-color: var(--sg-danger, #dc2626); }
97
+ .sv-masked.is-invalid:focus-within { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
98
+ .sv-masked.is-disabled { opacity: 0.6; }
99
+ .sv-masked__input {
100
+ flex: 1; min-width: 0; border: 0; background: none; outline: none; color: inherit; font: inherit;
101
+ font-variant-numeric: tabular-nums; letter-spacing: 0.02em;
102
+ }
103
+ .sv-masked__adorn { display: inline-flex; flex: none; color: var(--sg-muted, #64748b); }
104
+ .sv-masked__clear { display: grid; place-items: center; width: 20px; flex: none; background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; border-radius: 4px; }
105
+ .sv-masked__clear:hover { color: var(--sg-danger, #dc2626); }
66
106
  </style>
@@ -0,0 +1,101 @@
1
+ <script lang="ts" module>
2
+ export type { MenuItem } from './SvMenuList.svelte'
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ /**
7
+ * SvMenu - a dropdown / context menu: a trigger opens a portalled, animated
8
+ * menu surface with submenus, separators, icons, shortcuts and full keyboard
9
+ * (arrows, Enter, Escape, ArrowRight/Left for submenus). Portals to <body> so it
10
+ * escapes any clipping. Parity: Smart menu / context-menu.
11
+ *
12
+ * ```svelte
13
+ * <SvMenu items={items} onSelect={(i) => run(i)}>
14
+ * {#snippet anchor()}<button>Actions</button>{/snippet}
15
+ * </SvMenu>
16
+ * ```
17
+ */
18
+ import type { Snippet } from 'svelte'
19
+ import { anchoredRect, portalToBody, popIn, type AnchoredRect } from './popover'
20
+ import { createDismissableLayer } from './a11y/dismissable'
21
+ import SvMenuList, { type MenuItem } from './SvMenuList.svelte'
22
+
23
+ let {
24
+ items,
25
+ open = $bindable(false),
26
+ onOpenChange,
27
+ onSelect,
28
+ ariaLabel,
29
+ anchor,
30
+ }: {
31
+ items: ReadonlyArray<MenuItem>
32
+ open?: boolean
33
+ onOpenChange?: (open: boolean) => void
34
+ onSelect?: (item: MenuItem) => void
35
+ ariaLabel?: string
36
+ anchor?: Snippet
37
+ } = $props()
38
+
39
+ let anchorEl = $state<HTMLSpanElement | null>(null)
40
+ let panelEl = $state<HTMLDivElement | null>(null)
41
+ let rect = $state<AnchoredRect>({ top: 0, left: 0, width: 0, openUpward: false })
42
+
43
+ function setOpen(v: boolean) { if (v !== open) { open = v; onOpenChange?.(v) } }
44
+ function toggle() { setOpen(!open) }
45
+ function updatePos() {
46
+ if (!anchorEl) return
47
+ rect = anchoredRect(anchorEl.getBoundingClientRect(), { estimatedHeight: Math.min(items.length, 10) * 34 + 8, minWidth: 180 })
48
+ }
49
+
50
+ $effect(() => {
51
+ if (!open) return
52
+ updatePos()
53
+ queueMicrotask(() => panelEl?.querySelector<HTMLElement>('[role="menuitem"]:not([disabled])')?.focus())
54
+ const rp = () => updatePos()
55
+ window.addEventListener('scroll', rp, true)
56
+ window.addEventListener('resize', rp)
57
+ // Outside-click dismissal via the shared layer stack (Escape stays owned by
58
+ // SvMenuList's keyboard handling, so closeOnEscape is off here).
59
+ const layer = createDismissableLayer({
60
+ element: () => [anchorEl, panelEl],
61
+ onDismiss: () => setOpen(false),
62
+ closeOnEscape: false,
63
+ })
64
+ layer.activate()
65
+ return () => {
66
+ window.removeEventListener('scroll', rp, true)
67
+ window.removeEventListener('resize', rp)
68
+ layer.release()
69
+ }
70
+ })
71
+ </script>
72
+
73
+ <!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
74
+ <span bind:this={anchorEl} class="sv-menu__anchor" onclick={toggle}>
75
+ {@render anchor?.()}
76
+ </span>
77
+
78
+ {#if open}
79
+ <div
80
+ bind:this={panelEl}
81
+ class="sv-menu"
82
+ use:portalToBody
83
+ use:popIn={{ up: rect.openUpward }}
84
+ style:position="fixed"
85
+ style:top={`${rect.top}px`}
86
+ style:left={`${rect.left}px`}
87
+ aria-label={ariaLabel}
88
+ >
89
+ <SvMenuList {items} onclose={() => setOpen(false)} onselect={(i) => onSelect?.(i)} />
90
+ </div>
91
+ {/if}
92
+
93
+ <style>
94
+ .sv-menu__anchor { display: inline-flex; }
95
+ :global(.sv-menu) {
96
+ z-index: 2147483646; min-width: 180px;
97
+ background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
98
+ border: 1px solid var(--sg-border, #e2e8f0); border-radius: 10px;
99
+ box-shadow: 0 16px 48px -12px rgba(15, 23, 42, 0.35); font-size: 13px;
100
+ }
101
+ </style>
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Component tests for SvMenu: the trigger opens a portalled menu, items/separators
3
+ * render with menu semantics, choosing a leaf fires onSelect + closes, and a
4
+ * submenu opens on pointer-enter.
5
+ */
6
+ import { describe, expect, it, afterEach } from 'vitest'
7
+ import { mount, unmount, flushSync, createRawSnippet } from 'svelte'
8
+ import SvMenu, { type MenuItem } from './SvMenu.svelte'
9
+
10
+ const anchor = createRawSnippet(() => ({ render: () => `<button class="trigger">Actions</button>` }))
11
+
12
+ function mountMenu(props: Record<string, unknown>) {
13
+ const target = document.createElement('div')
14
+ document.body.appendChild(target)
15
+ const app = mount(SvMenu, { target, props: { anchor, ...props } as any })
16
+ flushSync()
17
+ return { target, destroy: () => { unmount(app); target.remove() } }
18
+ }
19
+ const menu = () => document.querySelector<HTMLElement>('.sv-menu')
20
+ afterEach(() => document.querySelectorAll('.sv-menu').forEach((n) => n.remove()))
21
+
22
+ describe('SvMenu', () => {
23
+ const items: MenuItem[] = [
24
+ { label: 'Cut', shortcut: 'Ctrl+X', onSelect: () => {} },
25
+ { label: 'Copy', shortcut: 'Ctrl+C' },
26
+ { separator: true },
27
+ { label: 'Share', children: [{ label: 'Email' }, { label: 'Link' }] },
28
+ { label: 'Delete', disabled: true },
29
+ ]
30
+
31
+ it('opens on trigger click with menu/menuitem roles + a separator', () => {
32
+ const { target, destroy } = mountMenu({ items })
33
+ try {
34
+ expect(menu()).toBeNull()
35
+ target.querySelector<HTMLElement>('.trigger')!.click()
36
+ flushSync()
37
+ const m = menu()!
38
+ expect(m.querySelector('[role="menu"]')).not.toBeNull()
39
+ expect(m.querySelectorAll('[role="menuitem"]').length).toBe(4) // 4 items, separator excluded
40
+ expect(m.querySelector('[role="separator"]')).not.toBeNull()
41
+ expect(m.querySelector('[aria-disabled="true"]')).not.toBeNull()
42
+ } finally { destroy() }
43
+ })
44
+
45
+ it('choosing a leaf fires onSelect and closes', () => {
46
+ let chosen: MenuItem | undefined
47
+ const { target, destroy } = mountMenu({ items, onSelect: (i: MenuItem) => (chosen = i) })
48
+ try {
49
+ target.querySelector<HTMLElement>('.trigger')!.click()
50
+ flushSync()
51
+ const copy = [...menu()!.querySelectorAll<HTMLElement>('[role="menuitem"]')].find((b) => b.textContent?.includes('Copy'))!
52
+ copy.click()
53
+ flushSync()
54
+ expect(chosen?.label).toBe('Copy')
55
+ expect(menu()).toBeNull()
56
+ } finally { destroy() }
57
+ })
58
+
59
+ it('opens a submenu on pointer-enter of a parent item', () => {
60
+ const { target, destroy } = mountMenu({ items })
61
+ try {
62
+ target.querySelector<HTMLElement>('.trigger')!.click()
63
+ flushSync()
64
+ const share = [...menu()!.querySelectorAll<HTMLElement>('[role="menuitem"]')].find((b) => b.textContent?.includes('Share'))!
65
+ expect(share.getAttribute('aria-haspopup')).toBe('menu')
66
+ share.dispatchEvent(new PointerEvent('pointerenter', { bubbles: true }))
67
+ flushSync()
68
+ expect(menu()!.querySelector('.sv-menu__flyout')).not.toBeNull()
69
+ const sub = [...menu()!.querySelectorAll('[role="menuitem"]')].map((b) => b.textContent)
70
+ expect(sub.some((t) => t?.includes('Email'))).toBe(true)
71
+ } finally { destroy() }
72
+ })
73
+ })
@@ -0,0 +1,143 @@
1
+ <script lang="ts" module>
2
+ import type { Snippet } from 'svelte'
3
+ export type MenuItem = {
4
+ /** Item text. Omit + set `separator` for a divider. */
5
+ label?: string
6
+ separator?: boolean
7
+ icon?: Snippet
8
+ /** Right-aligned hint, e.g. a keyboard shortcut. */
9
+ shortcut?: string
10
+ disabled?: boolean
11
+ /** Nested submenu items (renders a flyout). */
12
+ children?: MenuItem[]
13
+ onSelect?: () => void
14
+ }
15
+ </script>
16
+
17
+ <script lang="ts">
18
+ /**
19
+ * SvMenuList - the recursive menu surface behind SvMenu: a WAI-ARIA `menu` with
20
+ * roving focus, submenus (hover + ArrowRight), separators, icons and shortcuts.
21
+ * Rendered inside a portalled panel by SvMenu; it self-imports for submenus.
22
+ */
23
+ import Self from './SvMenuList.svelte'
24
+
25
+ let {
26
+ items,
27
+ onclose,
28
+ onselect,
29
+ submenu = false,
30
+ }: {
31
+ items: ReadonlyArray<MenuItem>
32
+ /** Close the WHOLE menu (a leaf was chosen or Escape at the root). */
33
+ onclose: () => void
34
+ /** Report the chosen leaf item. */
35
+ onselect: (item: MenuItem) => void
36
+ submenu?: boolean
37
+ } = $props()
38
+
39
+ // Indexes of real (non-separator) items, for roving focus.
40
+ const focusable = $derived(items.map((it, i) => (it.separator || it.disabled ? -1 : i)).filter((i) => i >= 0))
41
+ let active = $state(-1)
42
+ let openSub = $state(-1)
43
+ let listEl = $state<HTMLDivElement | null>(null)
44
+
45
+ function focusItem(i: number) {
46
+ active = i
47
+ queueMicrotask(() => listEl?.querySelector<HTMLElement>(`[data-mi="${i}"]`)?.focus())
48
+ }
49
+ function move(delta: number) {
50
+ if (!focusable.length) return
51
+ const pos = focusable.indexOf(active)
52
+ const next = focusable[(pos + delta + focusable.length) % focusable.length]
53
+ if (next != null) { openSub = -1; focusItem(next) }
54
+ }
55
+ function choose(item: MenuItem) {
56
+ if (item.disabled || item.separator) return
57
+ if (item.children?.length) { openSub = items.indexOf(item); return }
58
+ item.onSelect?.()
59
+ onselect(item)
60
+ onclose()
61
+ }
62
+ function onKeydown(e: KeyboardEvent, i: number) {
63
+ const item = items[i]
64
+ switch (e.key) {
65
+ case 'ArrowDown': e.preventDefault(); move(1); break
66
+ case 'ArrowUp': e.preventDefault(); move(-1); break
67
+ case 'Home': e.preventDefault(); focusItem(focusable[0] ?? 0); break
68
+ case 'End': e.preventDefault(); focusItem(focusable.at(-1) ?? 0); break
69
+ case 'ArrowRight':
70
+ if (item?.children?.length) { e.preventDefault(); openSub = i }
71
+ break
72
+ case 'ArrowLeft':
73
+ if (submenu) { e.preventDefault(); onclose() } // parent listens via its own onclose wiring
74
+ break
75
+ case 'Enter':
76
+ case ' ': e.preventDefault(); if (item) choose(item); break
77
+ case 'Escape': e.preventDefault(); onclose(); break
78
+ }
79
+ }
80
+ </script>
81
+
82
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
83
+ <div bind:this={listEl} class="sv-menu__list" class:is-submenu={submenu} role="menu">
84
+ {#each items as item, i (i)}
85
+ {#if item.separator}
86
+ <div class="sv-menu__sep" role="separator"></div>
87
+ {:else}
88
+ <div class="sv-menu__itemwrap">
89
+ <button
90
+ type="button"
91
+ class="sv-menu__item"
92
+ class:is-active={active === i}
93
+ data-mi={i}
94
+ role="menuitem"
95
+ aria-haspopup={item.children?.length ? 'menu' : undefined}
96
+ aria-expanded={item.children?.length ? openSub === i : undefined}
97
+ aria-disabled={item.disabled || undefined}
98
+ disabled={item.disabled}
99
+ tabindex={active === i || (active === -1 && i === focusable[0]) ? 0 : -1}
100
+ onpointerenter={() => { if (!item.disabled) { active = i; openSub = item.children?.length ? i : -1 } }}
101
+ onkeydown={(e) => onKeydown(e, i)}
102
+ onclick={() => choose(item)}
103
+ >
104
+ <span class="sv-menu__icon">{#if item.icon}{@render item.icon()}{/if}</span>
105
+ <span class="sv-menu__label">{item.label}</span>
106
+ {#if item.shortcut}<span class="sv-menu__shortcut">{item.shortcut}</span>{/if}
107
+ {#if item.children?.length}<svg class="sv-menu__chev" viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6" /></svg>{/if}
108
+ </button>
109
+ {#if item.children?.length && openSub === i}
110
+ <div class="sv-menu__flyout">
111
+ <Self items={item.children} onclose={onclose} onselect={onselect} submenu />
112
+ </div>
113
+ {/if}
114
+ </div>
115
+ {/if}
116
+ {/each}
117
+ </div>
118
+
119
+ <style>
120
+ .sv-menu__list { display: flex; flex-direction: column; min-width: 180px; padding: 4px; }
121
+ .sv-menu__sep { height: 1px; background: var(--sg-border, #e2e8f0); margin: 4px 6px; }
122
+ .sv-menu__itemwrap { position: relative; }
123
+ .sv-menu__item {
124
+ display: flex; align-items: center; gap: 9px; width: 100%; box-sizing: border-box;
125
+ padding: 7px 10px; background: none; border: 0; border-radius: 6px; cursor: pointer;
126
+ font: inherit; font-size: 13px; color: var(--sg-fg, #0f172a); text-align: start;
127
+ transition: background 0.1s;
128
+ }
129
+ .sv-menu__item.is-active:not([disabled]), .sv-menu__item:hover:not([disabled]) { background: var(--sg-row-hover-bg, #f1f5f9); }
130
+ .sv-menu__item:focus-visible { outline: 2px solid var(--sg-focus-ring, var(--sg-accent, #2563eb)); outline-offset: -2px; }
131
+ .sv-menu__item[disabled] { opacity: 0.45; cursor: default; }
132
+ .sv-menu__icon { display: inline-flex; width: 16px; flex: none; color: var(--sg-muted, #64748b); }
133
+ .sv-menu__icon:empty { width: 0; }
134
+ .sv-menu__label { flex: 1; white-space: nowrap; }
135
+ .sv-menu__shortcut { color: var(--sg-muted, #94a3b8); font-size: 11.5px; margin-inline-start: 12px; }
136
+ .sv-menu__chev { color: var(--sg-muted, #94a3b8); flex: none; }
137
+ .sv-menu[dir='rtl'] .sv-menu__chev, .sv-menu__list.is-submenu .sv-menu__chev { }
138
+ .sv-menu__flyout {
139
+ position: absolute; top: -5px; inset-inline-start: 100%; z-index: 1;
140
+ background: var(--sg-bg, #fff); border: 1px solid var(--sg-border, #e2e8f0);
141
+ border-radius: 10px; box-shadow: 0 16px 40px -12px rgba(15,23,42,0.32); margin-inline-start: 2px;
142
+ }
143
+ </style>
@@ -0,0 +1,220 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SvModal - an accessible modal dialog: backdrop, focus trap, Escape / backdrop
4
+ * close, and a scale-in animation. Optionally draggable (by its header) and
5
+ * resizable (bottom-right handle). Parity: Smart `smart-window` (modal mode).
6
+ * Portalled to <body> so it overlays everything. Controlled via `open`.
7
+ *
8
+ * ```svelte
9
+ * <SvModal bind:open title="Edit row" draggable resizable>
10
+ * <p>Body</p>
11
+ * {#snippet footer()}<button onclick={() => open = false}>Close</button>{/snippet}
12
+ * </SvModal>
13
+ * ```
14
+ */
15
+ import type { Snippet } from 'svelte'
16
+ import { portalToBody } from './popover'
17
+ import { nextEditorId } from './editor-contract'
18
+ import { createFocusTrap } from './a11y/focus-trap'
19
+ import { lockScroll } from './a11y/scroll-lock'
20
+ import { createDismissableLayer } from './a11y/dismissable'
21
+
22
+ type Props = {
23
+ open?: boolean
24
+ onClose?: () => void
25
+ title?: string
26
+ size?: 'sm' | 'md' | 'lg'
27
+ draggable?: boolean
28
+ resizable?: boolean
29
+ closeOnBackdrop?: boolean
30
+ closeOnEsc?: boolean
31
+ /** Hide the header close (x) button. */
32
+ hideClose?: boolean
33
+ children?: Snippet
34
+ footer?: Snippet
35
+ }
36
+
37
+ let {
38
+ open = $bindable(false),
39
+ onClose,
40
+ title,
41
+ size = 'md',
42
+ draggable = false,
43
+ resizable = false,
44
+ closeOnBackdrop = true,
45
+ closeOnEsc = true,
46
+ hideClose = false,
47
+ children,
48
+ footer,
49
+ }: Props = $props()
50
+
51
+ const titleId = nextEditorId('sv-modal-title')
52
+ let dialogEl = $state<HTMLDivElement | null>(null)
53
+ let drag = $state({ x: 0, y: 0 })
54
+ let box = $state<{ w: number; h: number } | null>(null)
55
+
56
+ function close() { open = false; onClose?.() }
57
+
58
+ // Reset transient drag/resize each time it opens, then hand focus, scroll-lock
59
+ // and Escape/backdrop dismissal to the shared a11y primitives (one tested
60
+ // implementation, and nested overlays close top-first via the layer stack).
61
+ $effect(() => {
62
+ if (!open || !dialogEl) return
63
+ drag = { x: 0, y: 0 }
64
+ box = null
65
+ const trap = createFocusTrap(dialogEl)
66
+ trap.activate()
67
+ const unlockScroll = lockScroll()
68
+ const layer = createDismissableLayer({
69
+ element: () => dialogEl,
70
+ onDismiss: (reason) => {
71
+ if (reason === 'escape' ? closeOnEsc : closeOnBackdrop) close()
72
+ },
73
+ })
74
+ layer.activate()
75
+ return () => {
76
+ layer.release()
77
+ unlockScroll()
78
+ trap.release()
79
+ }
80
+ })
81
+
82
+ // --- Drag (header) ---------------------------------------------------------
83
+ let dragging = false
84
+ let dragStart = { x: 0, y: 0, ox: 0, oy: 0 }
85
+ function onHeaderDown(e: PointerEvent) {
86
+ if (!draggable || (e.target as HTMLElement).closest('.sv-modal__x')) return
87
+ dragging = true
88
+ dragStart = { x: e.clientX, y: e.clientY, ox: drag.x, oy: drag.y }
89
+ ;(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId)
90
+ }
91
+ function onHeaderMove(e: PointerEvent) {
92
+ if (!dragging) return
93
+ drag = { x: dragStart.ox + (e.clientX - dragStart.x), y: dragStart.oy + (e.clientY - dragStart.y) }
94
+ }
95
+ function onHeaderUp(e: PointerEvent) {
96
+ if (!dragging) return
97
+ dragging = false
98
+ ;(e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId)
99
+ }
100
+
101
+ // --- Resize (bottom-right handle) ------------------------------------------
102
+ let resizing = false
103
+ let resizeStart = { x: 0, y: 0, w: 0, h: 0 }
104
+ function onResizeDown(e: PointerEvent) {
105
+ if (!resizable || !dialogEl) return
106
+ e.stopPropagation()
107
+ resizing = true
108
+ const r = dialogEl.getBoundingClientRect()
109
+ resizeStart = { x: e.clientX, y: e.clientY, w: r.width, h: r.height }
110
+ box = { w: r.width, h: r.height }
111
+ ;(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId)
112
+ }
113
+ function onResizeMove(e: PointerEvent) {
114
+ if (!resizing) return
115
+ box = {
116
+ w: Math.max(240, resizeStart.w + (e.clientX - resizeStart.x)),
117
+ h: Math.max(160, resizeStart.h + (e.clientY - resizeStart.y)),
118
+ }
119
+ }
120
+ function onResizeUp(e: PointerEvent) {
121
+ if (!resizing) return
122
+ resizing = false
123
+ ;(e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId)
124
+ }
125
+ </script>
126
+
127
+ {#if open}
128
+ <div class="sv-modal__backdrop" use:portalToBody>
129
+ <div
130
+ bind:this={dialogEl}
131
+ class="sv-modal sv-modal--{size}"
132
+ class:is-resized={!!box}
133
+ role="dialog"
134
+ aria-modal="true"
135
+ aria-labelledby={title ? titleId : undefined}
136
+ tabindex="-1"
137
+ style:transform={`translate(calc(-50% + ${drag.x}px), calc(-50% + ${drag.y}px))`}
138
+ style:width={box ? `${box.w}px` : undefined}
139
+ style:height={box ? `${box.h}px` : undefined}
140
+ >
141
+ {#if title || !hideClose}
142
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
143
+ <div
144
+ class="sv-modal__header"
145
+ class:is-draggable={draggable}
146
+ onpointerdown={onHeaderDown}
147
+ onpointermove={onHeaderMove}
148
+ onpointerup={onHeaderUp}
149
+ onpointercancel={onHeaderUp}
150
+ >
151
+ <h2 class="sv-modal__title" id={titleId}>{title ?? ''}</h2>
152
+ {#if !hideClose}
153
+ <button type="button" class="sv-modal__x" aria-label="Close" onclick={close}>&times;</button>
154
+ {/if}
155
+ </div>
156
+ {/if}
157
+ <div class="sv-modal__body">{@render children?.()}</div>
158
+ {#if footer}<div class="sv-modal__footer">{@render footer()}</div>{/if}
159
+ {#if resizable}
160
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
161
+ <div
162
+ class="sv-modal__resize"
163
+ aria-hidden="true"
164
+ onpointerdown={onResizeDown}
165
+ onpointermove={onResizeMove}
166
+ onpointerup={onResizeUp}
167
+ onpointercancel={onResizeUp}
168
+ ></div>
169
+ {/if}
170
+ </div>
171
+ </div>
172
+ {/if}
173
+
174
+ <style>
175
+ :global(.sv-modal__backdrop) {
176
+ position: fixed; inset: 0; z-index: 2147483000;
177
+ background: rgba(15, 23, 42, 0.45); backdrop-filter: blur(1.5px);
178
+ animation: sv-modal-fade 0.16s ease both;
179
+ }
180
+ @keyframes sv-modal-fade { from { opacity: 0 } to { opacity: 1 } }
181
+ @media (prefers-reduced-motion: reduce) { :global(.sv-modal__backdrop) { animation: none } }
182
+
183
+ :global(.sv-modal) {
184
+ position: fixed; top: 50%; left: 50%;
185
+ display: flex; flex-direction: column; max-width: 94vw; max-height: 88vh; min-width: 240px;
186
+ background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
187
+ border: 1px solid var(--sg-border, #e2e8f0); border-radius: 12px; overflow: hidden;
188
+ box-shadow: 0 24px 64px -16px rgba(15, 23, 42, 0.5);
189
+ animation: sv-modal-in 0.18s cubic-bezier(0.16, 1, 0.3, 1) both;
190
+ }
191
+ @keyframes sv-modal-in { from { opacity: 0; transform: translate(-50%, -50%) scale(0.94) } to { opacity: 1 } }
192
+ @media (prefers-reduced-motion: reduce) { :global(.sv-modal) { animation: none } }
193
+ :global(.sv-modal--sm) { width: 360px; }
194
+ :global(.sv-modal--md) { width: 520px; }
195
+ :global(.sv-modal--lg) { width: 720px; }
196
+ :global(.sv-modal.is-resized) { max-width: none; max-height: none; }
197
+
198
+ :global(.sv-modal__header) {
199
+ display: flex; align-items: center; gap: 12px; padding: 13px 16px;
200
+ border-bottom: 1px solid var(--sg-border, #e2e8f0);
201
+ }
202
+ :global(.sv-modal__header.is-draggable) { cursor: move; touch-action: none; user-select: none; }
203
+ :global(.sv-modal__title) { flex: 1; margin: 0; font-size: 15px; font-weight: 650; }
204
+ :global(.sv-modal__x) {
205
+ flex: none; width: 28px; height: 28px; display: grid; place-items: center;
206
+ background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; border-radius: 6px; font-size: 20px; line-height: 1;
207
+ }
208
+ :global(.sv-modal__x:hover) { background: var(--sg-row-hover-bg, #f1f5f9); color: var(--sg-fg, #0f172a); }
209
+ :global(.sv-modal__body) { padding: 16px; overflow: auto; flex: 1; font-size: 13.5px; line-height: 1.6; }
210
+ :global(.sv-modal__footer) {
211
+ display: flex; justify-content: flex-end; gap: 8px; padding: 12px 16px;
212
+ border-top: 1px solid var(--sg-border, #e2e8f0);
213
+ }
214
+ :global(.sv-modal__resize) {
215
+ position: absolute; inset-inline-end: 0; bottom: 0; width: 16px; height: 16px;
216
+ cursor: nwse-resize; touch-action: none;
217
+ background: linear-gradient(135deg, transparent 50%, var(--sg-muted, #94a3b8) 50%, var(--sg-muted, #94a3b8) 60%, transparent 60%, transparent 72%, var(--sg-muted, #94a3b8) 72%, var(--sg-muted, #94a3b8) 82%, transparent 82%);
218
+ opacity: 0.6;
219
+ }
220
+ </style>