@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,61 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SvDivider - a separator line, horizontal or vertical, optionally with a
4
+ * centered / start / end label. Theme-driven. Parity: Smart divider.
5
+ *
6
+ * ```svelte
7
+ * <SvDivider />
8
+ * <SvDivider label="OR" />
9
+ * <SvDivider orientation="vertical" />
10
+ * ```
11
+ */
12
+ import type { Snippet } from 'svelte'
13
+
14
+ type Props = {
15
+ orientation?: 'horizontal' | 'vertical'
16
+ label?: string
17
+ /** Label position along a horizontal divider. */
18
+ align?: 'start' | 'center' | 'end'
19
+ dashed?: boolean
20
+ children?: Snippet
21
+ }
22
+
23
+ let { orientation = 'horizontal', label, align = 'center', dashed = false, children }: Props = $props()
24
+ const hasLabel = $derived(!!label || !!children)
25
+ </script>
26
+
27
+ {#if orientation === 'vertical'}
28
+ <span class="sv-divider sv-divider--v" class:is-dashed={dashed} role="separator" aria-orientation="vertical"></span>
29
+ {:else}
30
+ <div class="sv-divider sv-divider--h sv-divider--{align}" class:is-dashed={dashed} class:has-label={hasLabel} role="separator">
31
+ {#if hasLabel}<span class="sv-divider__label">{#if children}{@render children()}{:else}{label}{/if}</span>{/if}
32
+ </div>
33
+ {/if}
34
+
35
+ <style>
36
+ .sv-divider--h {
37
+ --_line: var(--sg-border, #e2e8f0);
38
+ height: 1px; background: var(--_line); border: 0; margin: 12px 0;
39
+ }
40
+ .sv-divider--h.is-dashed { height: 0; background: none; border-top: 1px dashed var(--_line); }
41
+ .sv-divider--h.has-label {
42
+ height: auto; background: none; display: flex; align-items: center; gap: 12px;
43
+ color: var(--sg-muted, #64748b); font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em;
44
+ }
45
+ .sv-divider--h.has-label::before, .sv-divider--h.has-label::after {
46
+ content: ''; height: 1px; background: var(--_line);
47
+ }
48
+ .sv-divider--h.has-label.is-dashed::before, .sv-divider--h.has-label.is-dashed::after { height: 0; border-top: 1px dashed var(--_line); }
49
+ .sv-divider--center::before, .sv-divider--center::after { flex: 1; }
50
+ .sv-divider--start::before { width: 16px; flex: none; }
51
+ .sv-divider--start::after { flex: 1; }
52
+ .sv-divider--end::before { flex: 1; }
53
+ .sv-divider--end::after { width: 16px; flex: none; }
54
+ .sv-divider__label { flex: none; }
55
+
56
+ .sv-divider--v {
57
+ display: inline-block; width: 1px; align-self: stretch; min-height: 1em;
58
+ background: var(--sg-border, #e2e8f0); margin: 0 12px; vertical-align: middle;
59
+ }
60
+ .sv-divider--v.is-dashed { width: 0; background: none; border-inline-start: 1px dashed var(--sg-border, #e2e8f0); }
61
+ </style>
@@ -0,0 +1,226 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SvDrawer - an edge-anchored side sheet (right / left / top / bottom): a
4
+ * backdrop, a sliding panel, and the full dialog a11y contract. Built entirely
5
+ * on the shared primitives - `createFocusTrap`, `lockScroll` and
6
+ * `createDismissableLayer` - so its focus, scroll-lock and Escape/backdrop
7
+ * behaviour is identical to SvModal and nested overlays close top-first.
8
+ * Portalled to <body>. Controlled via `open`.
9
+ *
10
+ * ```svelte
11
+ * <SvDrawer bind:open side="right" title="Filters">
12
+ * <p>Body</p>
13
+ * {#snippet footer()}<button onclick={() => open = false}>Apply</button>{/snippet}
14
+ * </SvDrawer>
15
+ * ```
16
+ */
17
+ import type { Snippet } from 'svelte'
18
+ import { portalToBody } from './popover'
19
+ import { nextEditorId } from './editor-contract'
20
+ import { createFocusTrap } from './a11y/focus-trap'
21
+ import { lockScroll } from './a11y/scroll-lock'
22
+ import { createDismissableLayer } from './a11y/dismissable'
23
+
24
+ /** Edge the drawer slides from. Kept in sync with the `DrawerSide` export in
25
+ * index.ts (a bare type-only module script trips the Svelte/Vite parser). */
26
+ type DrawerSide = 'right' | 'left' | 'top' | 'bottom'
27
+
28
+ type Props = {
29
+ open?: boolean
30
+ onClose?: () => void
31
+ /** Edge the drawer slides from. Default 'right'. */
32
+ side?: DrawerSide
33
+ /** Panel size along its axis (width for left/right, height for top/bottom).
34
+ * Any CSS length; defaults to a sensible per-axis value. */
35
+ size?: string
36
+ title?: string
37
+ closeOnBackdrop?: boolean
38
+ closeOnEsc?: boolean
39
+ /** Hide the header close (x) button. */
40
+ hideClose?: boolean
41
+ /** Render as a mobile bottom-sheet: bottom-anchored, rounded top, a grab
42
+ * handle, and swipe-down-to-close. Overrides `side`. */
43
+ sheet?: boolean
44
+ /** Accessible name when there is no visible `title`. */
45
+ ariaLabel?: string
46
+ children?: Snippet
47
+ footer?: Snippet
48
+ }
49
+
50
+ let {
51
+ open = $bindable(false),
52
+ onClose,
53
+ side = 'right',
54
+ size,
55
+ title,
56
+ closeOnBackdrop = true,
57
+ closeOnEsc = true,
58
+ hideClose = false,
59
+ sheet = false,
60
+ ariaLabel,
61
+ children,
62
+ footer,
63
+ }: Props = $props()
64
+
65
+ const titleId = nextEditorId('sv-drawer-title')
66
+ let dialogEl = $state<HTMLDivElement | null>(null)
67
+
68
+ // A sheet is a bottom drawer with mobile affordances.
69
+ const effectiveSide = $derived<DrawerSide>(sheet ? 'bottom' : side)
70
+ const horizontal = $derived(effectiveSide === 'left' || effectiveSide === 'right')
71
+ const sizeVar = $derived(size ?? (horizontal ? 'min(400px, 92vw)' : 'min(360px, 85vh)'))
72
+
73
+ function close() { open = false; onClose?.() }
74
+
75
+ // Swipe-down-to-close for the sheet grab handle.
76
+ let dragY = $state(0)
77
+ let dragging = $state(false)
78
+ let dragStartY = 0
79
+ function onGrabDown(e: PointerEvent) {
80
+ dragging = true
81
+ dragStartY = e.clientY
82
+ ;(e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId)
83
+ }
84
+ function onGrabMove(e: PointerEvent) {
85
+ if (!dragging) return
86
+ dragY = Math.max(0, e.clientY - dragStartY)
87
+ }
88
+ function onGrabUp(e: PointerEvent) {
89
+ if (!dragging) return
90
+ dragging = false
91
+ ;(e.currentTarget as HTMLElement).releasePointerCapture?.(e.pointerId)
92
+ if (dragY > 80) close()
93
+ else dragY = 0
94
+ }
95
+
96
+ // Focus trap + scroll lock + Escape/backdrop dismissal, all from the shared
97
+ // primitives - one tested implementation across every dialog-like overlay.
98
+ $effect(() => {
99
+ if (!open || !dialogEl) return
100
+ dragY = 0
101
+ const trap = createFocusTrap(dialogEl)
102
+ trap.activate()
103
+ const unlockScroll = lockScroll()
104
+ const layer = createDismissableLayer({
105
+ element: () => dialogEl,
106
+ onDismiss: (reason) => {
107
+ if (reason === 'escape' ? closeOnEsc : closeOnBackdrop) close()
108
+ },
109
+ })
110
+ layer.activate()
111
+ return () => {
112
+ layer.release()
113
+ unlockScroll()
114
+ trap.release()
115
+ }
116
+ })
117
+ </script>
118
+
119
+ {#if open}
120
+ <div class="sv-drawer__backdrop" use:portalToBody>
121
+ <div
122
+ bind:this={dialogEl}
123
+ class="sv-drawer sv-drawer--{effectiveSide}"
124
+ class:is-sheet={sheet}
125
+ class:is-dragging={dragging}
126
+ role="dialog"
127
+ aria-modal="true"
128
+ aria-label={!title ? ariaLabel : undefined}
129
+ aria-labelledby={title ? titleId : undefined}
130
+ tabindex="-1"
131
+ style:--sv-drawer-size={sizeVar}
132
+ style:transform={sheet && dragY ? `translateY(${dragY}px)` : undefined}
133
+ >
134
+ {#if sheet}
135
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
136
+ <div
137
+ class="sv-drawer__grab"
138
+ aria-hidden="true"
139
+ onpointerdown={onGrabDown}
140
+ onpointermove={onGrabMove}
141
+ onpointerup={onGrabUp}
142
+ onpointercancel={onGrabUp}
143
+ ><span></span></div>
144
+ {/if}
145
+ {#if title || !hideClose}
146
+ <div class="sv-drawer__header">
147
+ <h2 class="sv-drawer__title" id={titleId}>{title ?? ''}</h2>
148
+ {#if !hideClose}
149
+ <button type="button" class="sv-drawer__x" aria-label="Close" onclick={close}>&times;</button>
150
+ {/if}
151
+ </div>
152
+ {/if}
153
+ <div class="sv-drawer__body">{@render children?.()}</div>
154
+ {#if footer}<div class="sv-drawer__footer">{@render footer()}</div>{/if}
155
+ </div>
156
+ </div>
157
+ {/if}
158
+
159
+ <style>
160
+ :global(.sv-drawer__backdrop) {
161
+ position: fixed; inset: 0; z-index: 2147483000;
162
+ background: rgba(15, 23, 42, 0.45); backdrop-filter: blur(1.5px);
163
+ animation: sv-drawer-fade 0.16s ease both;
164
+ }
165
+ @keyframes sv-drawer-fade { from { opacity: 0 } to { opacity: 1 } }
166
+
167
+ :global(.sv-drawer) {
168
+ position: fixed;
169
+ display: flex; flex-direction: column;
170
+ background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
171
+ box-shadow: 0 24px 64px -16px rgba(15, 23, 42, 0.5);
172
+ overflow: hidden;
173
+ }
174
+ /* Left / right: full height, fixed width, slide along X. */
175
+ :global(.sv-drawer--right), :global(.sv-drawer--left) {
176
+ top: 0; bottom: 0; width: var(--sv-drawer-size); max-width: 100vw;
177
+ }
178
+ :global(.sv-drawer--right) { inset-inline-end: 0; border-inline-start: 1px solid var(--sg-border, #e2e8f0); animation: sv-drawer-in-right 0.22s cubic-bezier(0.16, 1, 0.3, 1) both; }
179
+ :global(.sv-drawer--left) { inset-inline-start: 0; border-inline-end: 1px solid var(--sg-border, #e2e8f0); animation: sv-drawer-in-left 0.22s cubic-bezier(0.16, 1, 0.3, 1) both; }
180
+ /* Top / bottom: full width, fixed height, slide along Y. */
181
+ :global(.sv-drawer--top), :global(.sv-drawer--bottom) {
182
+ left: 0; right: 0; height: var(--sv-drawer-size); max-height: 100vh;
183
+ }
184
+ :global(.sv-drawer--top) { top: 0; border-bottom: 1px solid var(--sg-border, #e2e8f0); animation: sv-drawer-in-top 0.22s cubic-bezier(0.16, 1, 0.3, 1) both; }
185
+ :global(.sv-drawer--bottom) { bottom: 0; border-top: 1px solid var(--sg-border, #e2e8f0); animation: sv-drawer-in-bottom 0.22s cubic-bezier(0.16, 1, 0.3, 1) both; }
186
+
187
+ /* Mobile bottom-sheet: centered, fixed width, rounded top, grab handle. */
188
+ :global(.sv-drawer.is-sheet) {
189
+ left: 0; right: 0; margin: 0 auto; top: auto;
190
+ width: min(560px, 100vw); height: auto; max-height: 85vh;
191
+ border: 1px solid var(--sg-border, #e2e8f0); border-bottom: 0;
192
+ border-radius: 16px 16px 0 0;
193
+ transition: transform 0.22s cubic-bezier(0.16, 1, 0.3, 1);
194
+ }
195
+ :global(.sv-drawer.is-sheet.is-dragging) { transition: none; }
196
+ :global(.sv-drawer__grab) {
197
+ flex: none; display: grid; place-items: center; padding: 8px 0 2px; cursor: grab; touch-action: none;
198
+ }
199
+ :global(.sv-drawer__grab span) {
200
+ display: block; width: 40px; height: 4px; border-radius: 999px; background: var(--sg-border, #cbd5e1);
201
+ }
202
+
203
+ @keyframes sv-drawer-in-right { from { transform: translateX(100%) } to { transform: translateX(0) } }
204
+ @keyframes sv-drawer-in-left { from { transform: translateX(-100%) } to { transform: translateX(0) } }
205
+ @keyframes sv-drawer-in-top { from { transform: translateY(-100%) } to { transform: translateY(0) } }
206
+ @keyframes sv-drawer-in-bottom { from { transform: translateY(100%) } to { transform: translateY(0) } }
207
+ @media (prefers-reduced-motion: reduce) {
208
+ :global(.sv-drawer__backdrop), :global(.sv-drawer) { animation: none }
209
+ }
210
+
211
+ :global(.sv-drawer__header) {
212
+ display: flex; align-items: center; gap: 12px; padding: 13px 16px;
213
+ border-bottom: 1px solid var(--sg-border, #e2e8f0);
214
+ }
215
+ :global(.sv-drawer__title) { flex: 1; margin: 0; font-size: 15px; font-weight: 650; }
216
+ :global(.sv-drawer__x) {
217
+ flex: none; width: 28px; height: 28px; display: grid; place-items: center;
218
+ background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; border-radius: 6px; font-size: 20px; line-height: 1;
219
+ }
220
+ :global(.sv-drawer__x:hover) { background: var(--sg-row-hover-bg, #f1f5f9); color: var(--sg-fg, #0f172a); }
221
+ :global(.sv-drawer__body) { padding: 16px; overflow: auto; flex: 1; font-size: 13.5px; line-height: 1.6; }
222
+ :global(.sv-drawer__footer) {
223
+ display: flex; justify-content: flex-end; gap: 8px; padding: 12px 16px;
224
+ border-top: 1px solid var(--sg-border, #e2e8f0);
225
+ }
226
+ </style>
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Component tests for SvDrawer: dialog semantics, side variants, focus trap,
3
+ * scroll lock, and Escape / backdrop dismissal - all inherited from the shared
4
+ * a11y primitives.
5
+ */
6
+ import { describe, expect, it } from 'vitest'
7
+ import { mount, unmount, flushSync } from 'svelte'
8
+ import SvDrawer from './SvDrawer.svelte'
9
+
10
+ function mountDrawer(props: Record<string, unknown>) {
11
+ const target = document.createElement('div')
12
+ document.body.appendChild(target)
13
+ const app = mount(SvDrawer, { target, props: props as any })
14
+ flushSync()
15
+ return { target, destroy: () => { unmount(app); target.remove() } }
16
+ }
17
+
18
+ const dialog = () => document.body.querySelector<HTMLElement>('.sv-drawer')
19
+
20
+ describe('SvDrawer', () => {
21
+ it('renders nothing when closed', () => {
22
+ const { destroy } = mountDrawer({ open: false })
23
+ try {
24
+ expect(dialog()).toBeNull()
25
+ } finally { destroy() }
26
+ })
27
+
28
+ it('renders a modal dialog with the title wired to aria-labelledby', () => {
29
+ const { destroy } = mountDrawer({ open: true, title: 'Filters' })
30
+ try {
31
+ const el = dialog()!
32
+ expect(el.getAttribute('role')).toBe('dialog')
33
+ expect(el.getAttribute('aria-modal')).toBe('true')
34
+ const labelledby = el.getAttribute('aria-labelledby')
35
+ expect(labelledby).toBeTruthy()
36
+ expect(document.getElementById(labelledby!)?.textContent).toBe('Filters')
37
+ } finally { destroy() }
38
+ })
39
+
40
+ it('applies the side modifier class', () => {
41
+ const { destroy } = mountDrawer({ open: true, side: 'left' })
42
+ try {
43
+ expect(dialog()!.classList.contains('sv-drawer--left')).toBe(true)
44
+ } finally { destroy() }
45
+ })
46
+
47
+ it('locks body scroll while open and restores it on close', () => {
48
+ document.body.style.overflow = 'auto'
49
+ const { destroy } = mountDrawer({ open: true })
50
+ try {
51
+ expect(document.body.style.overflow).toBe('hidden')
52
+ } finally {
53
+ destroy()
54
+ flushSync()
55
+ expect(document.body.style.overflow).toBe('auto')
56
+ }
57
+ })
58
+
59
+ it('closes on Escape and fires onClose', () => {
60
+ let closed = 0
61
+ const { destroy } = mountDrawer({ open: true, onClose: () => closed++ })
62
+ try {
63
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }))
64
+ flushSync()
65
+ expect(closed).toBe(1)
66
+ expect(dialog()).toBeNull()
67
+ } finally { destroy() }
68
+ })
69
+
70
+ it('does not close on Escape when closeOnEsc is false', () => {
71
+ const { destroy } = mountDrawer({ open: true, closeOnEsc: false })
72
+ try {
73
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }))
74
+ flushSync()
75
+ expect(dialog()).not.toBeNull()
76
+ } finally { destroy() }
77
+ })
78
+
79
+ it('dismisses on outside (backdrop) pointerdown', () => {
80
+ const { destroy } = mountDrawer({ open: true })
81
+ try {
82
+ const backdrop = document.body.querySelector<HTMLElement>('.sv-drawer__backdrop')!
83
+ backdrop.dispatchEvent(new MouseEvent('pointerdown', { bubbles: true }))
84
+ flushSync()
85
+ expect(dialog()).toBeNull()
86
+ } finally { destroy() }
87
+ })
88
+
89
+ // ---- Mobile bottom-sheet (Track D) -------------------------------------
90
+ it('sheet mode renders bottom-anchored with a grab handle', () => {
91
+ const { destroy } = mountDrawer({ open: true, sheet: true, side: 'right' })
92
+ try {
93
+ const el = dialog()!
94
+ expect(el.classList.contains('is-sheet')).toBe(true)
95
+ expect(el.classList.contains('sv-drawer--bottom')).toBe(true) // sheet overrides side
96
+ expect(document.body.querySelector('.sv-drawer__grab')).not.toBeNull()
97
+ } finally { destroy() }
98
+ })
99
+
100
+ it('swiping the grab handle down past the threshold closes the sheet', () => {
101
+ let closed = 0
102
+ const { destroy } = mountDrawer({ open: true, sheet: true, onClose: () => closed++ })
103
+ try {
104
+ const grab = document.body.querySelector<HTMLElement>('.sv-drawer__grab')!
105
+ grab.dispatchEvent(new MouseEvent('pointerdown', { bubbles: true, clientY: 100 }))
106
+ grab.dispatchEvent(new MouseEvent('pointermove', { bubbles: true, clientY: 220 })) // dy = 120 > 80
107
+ grab.dispatchEvent(new MouseEvent('pointerup', { bubbles: true, clientY: 220 }))
108
+ flushSync()
109
+ expect(closed).toBe(1)
110
+ expect(dialog()).toBeNull()
111
+ } finally { destroy() }
112
+ })
113
+
114
+ it('a small swipe snaps back without closing', () => {
115
+ const { destroy } = mountDrawer({ open: true, sheet: true })
116
+ try {
117
+ const grab = document.body.querySelector<HTMLElement>('.sv-drawer__grab')!
118
+ grab.dispatchEvent(new MouseEvent('pointerdown', { bubbles: true, clientY: 100 }))
119
+ grab.dispatchEvent(new MouseEvent('pointermove', { bubbles: true, clientY: 140 })) // dy = 40 < 80
120
+ grab.dispatchEvent(new MouseEvent('pointerup', { bubbles: true, clientY: 140 }))
121
+ flushSync()
122
+ expect(dialog()).not.toBeNull()
123
+ } finally { destroy() }
124
+ })
125
+ })
@@ -7,23 +7,57 @@
7
7
  * One styled renderer over the headless `createDropdownList` core (state +
8
8
  * keyboard + ARIA); only portal/measure/scroll render concerns live here.
9
9
  */
10
- import { anchoredRect, portalToBody, type AnchoredRect } from './popover'
11
- import type { ListOption } from './list-option'
10
+ import { flushSync } from 'svelte'
11
+ import { anchoredRect, portalToBody, popIn, type AnchoredRect } from './popover'
12
+ import { createDismissableLayer } from './a11y/dismissable'
13
+ import { groupOptions, hasGroups, type ListOption } from './list-option'
14
+ import { virtualRange, scrollToIndex } from './virtual'
15
+ import SvField from './SvField.svelte'
16
+ import { nextEditorId, type SvEditorProps } from './editor-contract'
12
17
  import { createDropdownList } from './createDropdownList.svelte'
13
18
 
14
- type Props = {
19
+ type Props = SvEditorProps & {
15
20
  options: ReadonlyArray<ListOption>
16
21
  value?: string | number | null
17
22
  onChange?: (value: string | number) => void
18
23
  placeholder?: string
19
- disabled?: boolean
20
- name?: string
21
- size?: 'sm' | 'md' | 'lg'
22
- ariaLabel?: string
23
24
  autoOpen?: boolean
25
+ /** Window the option list (render only visible) for large sets. */
26
+ virtual?: boolean
27
+ /** Fixed option height in px (must match the CSS). Default 34. */
28
+ rowHeight?: number
24
29
  }
25
30
 
26
- let { options, value = null, onChange, placeholder = 'Select…', disabled = false, name, size = 'md', ariaLabel, autoOpen = false }: Props = $props()
31
+ let {
32
+ options,
33
+ value = null,
34
+ onChange,
35
+ placeholder = 'Select…',
36
+ disabled = false,
37
+ name,
38
+ size = 'md',
39
+ ariaLabel,
40
+ invalid = false,
41
+ required = false,
42
+ error,
43
+ label,
44
+ hint,
45
+ dir,
46
+ id,
47
+ autoOpen = false,
48
+ virtual = false,
49
+ rowHeight = 34,
50
+ }: Props = $props()
51
+
52
+ const autoId = nextEditorId('sv-ddl')
53
+ const uid = $derived(id ?? autoId)
54
+
55
+ // Virtualization applies to a flat (ungrouped) list.
56
+ const useVirtual = $derived(virtual && !hasGroups(options))
57
+ let scrollTop = $state(0)
58
+ let viewportH = $state(0)
59
+ const vr = $derived(virtualRange({ scrollTop, viewportHeight: viewportH, rowHeight, count: options.length, overscan: 10 }))
60
+ const windowed = $derived(useVirtual ? options.slice(vr.start, vr.end).map((o, i) => ({ ...o, index: vr.start + i })) : [])
27
61
 
28
62
  let triggerEl = $state<HTMLButtonElement | null>(null)
29
63
  let panelEl = $state<HTMLDivElement | null>(null)
@@ -36,6 +70,11 @@
36
70
  disabled: () => disabled,
37
71
  ariaLabel: () => ariaLabel,
38
72
  focusTrigger: () => triggerEl?.focus(),
73
+ id: () => uid,
74
+ invalid: () => invalid,
75
+ required: () => required,
76
+ error: () => error,
77
+ hint: () => hint,
39
78
  })
40
79
 
41
80
  const selected = $derived(ddl.selected)
@@ -50,43 +89,74 @@
50
89
  updatePos()
51
90
  const rp = () => updatePos()
52
91
  window.addEventListener('scroll', rp, true); window.addEventListener('resize', rp)
53
- const od = (e: PointerEvent) => { const t = e.target as Node | null; if (t && (triggerEl?.contains(t) || panelEl?.contains(t))) return; ddl.close() }
54
- document.addEventListener('pointerdown', od, true)
55
- return () => { window.removeEventListener('scroll', rp, true); window.removeEventListener('resize', rp); document.removeEventListener('pointerdown', od, true) }
92
+ // Outside-pointer dismissal via the shared layer stack (Escape stays with
93
+ // the core keyboard handler). Because this layer is pushed ON TOP, a
94
+ // dropdown opened inside a dialog/drawer closes itself first and never
95
+ // dismisses the dialog underneath it.
96
+ const layer = createDismissableLayer({
97
+ element: () => [triggerEl, panelEl],
98
+ onDismiss: () => ddl.close(),
99
+ closeOnEscape: false,
100
+ })
101
+ layer.activate()
102
+ return () => { window.removeEventListener('scroll', rp, true); window.removeEventListener('resize', rp); layer.release() }
56
103
  })
57
104
  // Keep the active option scrolled into view (render concern).
58
105
  $effect(() => {
59
106
  if (!ddl.open) return
60
107
  const i = ddl.activeIndex
61
- queueMicrotask(() => panelEl?.querySelector<HTMLElement>(`[data-idx="${i}"]`)?.scrollIntoView({ block: 'nearest' }))
108
+ if (useVirtual) {
109
+ if (!panelEl) return
110
+ const next = scrollToIndex(i, panelEl.scrollTop, panelEl.clientHeight, rowHeight)
111
+ if (next !== panelEl.scrollTop) { panelEl.scrollTop = next; scrollTop = next }
112
+ } else {
113
+ queueMicrotask(() => panelEl?.querySelector<HTMLElement>(`[data-idx="${i}"]`)?.scrollIntoView({ block: 'nearest' }))
114
+ }
62
115
  })
63
116
  function focusOpen(node: HTMLButtonElement) { if (autoOpen) { node.focus(); ddl.openPanel() } }
64
117
  </script>
65
118
 
66
- <button
67
- bind:this={triggerEl}
68
- class="sv-ddl sv-ddl--{size}"
69
- class:is-open={ddl.open}
70
- class:is-disabled={disabled}
71
- {...ddl.triggerProps()}
72
- use:focusOpen
73
- >
74
- <span class="sv-ddl__value" class:is-placeholder={!selected}>{selected?.label ?? placeholder}</span>
75
- <svg class="sv-ddl__chev" viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
76
- </button>
119
+ <SvField id={uid} {label} {hint} {error} {required} {dir}>
120
+ <button
121
+ bind:this={triggerEl}
122
+ class="sv-ddl sv-ddl--{size}"
123
+ class:is-open={ddl.open}
124
+ class:is-disabled={disabled}
125
+ class:is-invalid={invalid}
126
+ {...ddl.triggerProps()}
127
+ use:focusOpen
128
+ >
129
+ <span class="sv-ddl__value" class:is-placeholder={!selected}>{selected?.label ?? placeholder}</span>
130
+ <svg class="sv-ddl__chev" viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
131
+ </button>
132
+ </SvField>
77
133
 
78
134
  {#if ddl.open}
79
- <div bind:this={panelEl} class="sv-ddl__panel" use:portalToBody style:position="fixed" style:top={`${rect.top}px`} style:left={`${rect.left}px`} style:min-width={`${rect.width}px`} {...ddl.listboxProps()}>
80
- {#each options as opt, i (opt.value)}
81
- <!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
82
- <div
83
- class="sv-ddl__opt"
84
- class:is-active={ddl.isActive(i)}
85
- class:is-selected={ddl.isSelected(opt)}
86
- class:is-disabled={opt.disabled}
87
- {...ddl.optionProps(i)}
88
- >{opt.label}</div>
135
+ <div bind:this={panelEl} class="sv-ddl__panel" class:is-virtual={useVirtual} use:portalToBody use:popIn={{ up: rect.openUpward }} style:position="fixed" style:top={`${rect.top}px`} style:left={`${rect.left}px`} style:min-width={`${rect.width}px`} onscroll={(e) => { scrollTop = e.currentTarget.scrollTop; if (useVirtual) flushSync() }} bind:clientHeight={viewportH} {...ddl.listboxProps()}>
136
+ {#if useVirtual}
137
+ <div class="sv-ddl__sizer" aria-hidden="true" style:height={`${vr.totalHeight}px`}></div>
138
+ {#each windowed as opt (opt.value)}
139
+ <!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
140
+ <div class="sv-ddl__opt sv-ddl__opt--abs" style:height={`${rowHeight}px`} style:transform={`translateY(${opt.index * rowHeight}px)`} class:is-active={ddl.isActive(opt.index)} class:is-selected={ddl.isSelected(opt)} class:is-disabled={opt.disabled} {...ddl.optionProps(opt.index)}>{opt.label}</div>
141
+ {/each}
142
+ {:else}
143
+ {#each groupOptions(options) as g (g.group ?? '')}
144
+ {#if g.group != null}
145
+ <div class="sv-ddl__group" role="group" aria-label={g.group}>
146
+ <div class="sv-ddl__group-label" aria-hidden="true">{g.group}</div>
147
+ {#each g.options as opt (opt.value)}
148
+ <!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
149
+ <div class="sv-ddl__opt" class:is-active={ddl.isActive(opt.index)} class:is-selected={ddl.isSelected(opt)} class:is-disabled={opt.disabled} {...ddl.optionProps(opt.index)}>{opt.label}</div>
150
+ {/each}
151
+ </div>
152
+ {:else}
153
+ {#each g.options as opt (opt.value)}
154
+ <!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
155
+ <div class="sv-ddl__opt" class:is-active={ddl.isActive(opt.index)} class:is-selected={ddl.isSelected(opt)} class:is-disabled={opt.disabled} {...ddl.optionProps(opt.index)}>{opt.label}</div>
156
+ {/each}
157
+ {/if}
89
158
  {/each}
159
+ {/if}
90
160
  </div>
91
161
  {/if}
92
162
  {#if name}<input type="hidden" {name} value={value ?? ''} />{/if}
@@ -95,7 +165,7 @@
95
165
  .sv-ddl {
96
166
  --_accent: var(--sg-accent, #2563eb);
97
167
  display: inline-flex; align-items: center; justify-content: space-between; gap: 8px; width: 200px;
98
- background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a); font: inherit; text-align: left;
168
+ background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a); font: inherit; text-align: start;
99
169
  border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
100
170
  padding: 0 10px; cursor: pointer;
101
171
  }
@@ -103,6 +173,8 @@
103
173
  .sv-ddl--md { height: 34px; font-size: 13px; }
104
174
  .sv-ddl--lg { height: 40px; font-size: 15px; }
105
175
  .sv-ddl.is-open, .sv-ddl:focus-visible { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); outline: none; }
176
+ .sv-ddl.is-invalid { border-color: var(--sg-danger, #dc2626); }
177
+ .sv-ddl.is-invalid.is-open, .sv-ddl.is-invalid:focus-visible { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
106
178
  .sv-ddl.is-disabled { opacity: 0.6; cursor: not-allowed; }
107
179
  .sv-ddl__value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
108
180
  .sv-ddl__value.is-placeholder { color: var(--sg-muted, #94a3b8); }
@@ -113,8 +185,15 @@
113
185
  background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
114
186
  border: 1px solid var(--sg-border, #e2e8f0); border-radius: 10px; box-shadow: 0 16px 48px -12px rgba(15,23,42,0.35);
115
187
  }
116
- :global(.sv-ddl__opt) { padding: 7px 10px; border-radius: 6px; cursor: pointer; font-size: 13px; }
188
+ :global(.sv-ddl__opt) { box-sizing: border-box; display: flex; align-items: center; padding: 7px 10px; border-radius: 6px; cursor: pointer; font-size: 13px; }
189
+ :global(.sv-ddl__panel.is-virtual) { position: fixed; will-change: scroll-position; }
190
+ :global(.sv-ddl__sizer) { padding: 0; margin: 0; pointer-events: none; }
191
+ :global(.sv-ddl__opt--abs) { position: absolute; inset-inline: 4px; top: 4px; }
117
192
  :global(.sv-ddl__opt.is-active) { background: var(--sg-row-hover-bg, #f1f5f9); }
118
193
  :global(.sv-ddl__opt.is-selected) { color: var(--sg-accent, #2563eb); font-weight: 600; }
119
194
  :global(.sv-ddl__opt.is-disabled) { opacity: 0.4; cursor: not-allowed; }
195
+ :global(.sv-ddl__group-label) {
196
+ padding: 8px 10px 3px; font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
197
+ color: var(--sg-muted, #94a3b8);
198
+ }
120
199
  </style>