@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
@@ -8,23 +8,48 @@
8
8
  * roving active index + keyboard); only portal/measure/focus render concerns
9
9
  * live here.
10
10
  */
11
- import { anchoredRect, portalToBody, type AnchoredRect } from './popover'
11
+ import { anchoredRect, portalToBody, popIn, type AnchoredRect } from './popover'
12
12
  import { flagEmoji } from './countries'
13
+ import SvField from './SvField.svelte'
14
+ import { nextEditorId, resolveMessages, type SvEditorProps } from './editor-contract'
13
15
  import { createCountryInput } from './createCountryInput.svelte'
14
16
 
15
- type Props = {
17
+ /** User-facing strings (localizable via `messages`). */
18
+ type CountryMessages = { search: string; noResults: string }
19
+ const DEFAULT_MESSAGES: CountryMessages = { search: 'Search countries', noResults: 'No matches' }
20
+
21
+ type Props = SvEditorProps & {
16
22
  value?: string | null
17
23
  onChange?: (code: string) => void
18
24
  /** Show the dial code beside each country. */
19
25
  showDial?: boolean
20
26
  placeholder?: string
21
- disabled?: boolean
22
- name?: string
23
- size?: 'sm' | 'md' | 'lg'
24
- ariaLabel?: string
27
+ /** Override the built-in strings (search + empty-state). */
28
+ messages?: Partial<CountryMessages>
25
29
  }
26
30
 
27
- let { value = null, onChange, showDial = false, placeholder = 'Select country…', disabled = false, name, size = 'md', ariaLabel }: Props = $props()
31
+ let {
32
+ value = null,
33
+ onChange,
34
+ showDial = false,
35
+ placeholder = 'Select country…',
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
+ messages,
48
+ }: Props = $props()
49
+
50
+ const autoId = nextEditorId('sv-country')
51
+ const uid = $derived(id ?? autoId)
52
+ const M = $derived(resolveMessages(DEFAULT_MESSAGES, messages))
28
53
 
29
54
  let triggerEl = $state<HTMLButtonElement | null>(null)
30
55
  let searchEl = $state<HTMLInputElement | null>(null)
@@ -36,7 +61,13 @@
36
61
  onChange: (c) => onChange?.(c),
37
62
  disabled: () => disabled,
38
63
  ariaLabel: () => ariaLabel,
64
+ searchLabel: () => M.search,
39
65
  focusTrigger: () => triggerEl?.focus(),
66
+ id: () => uid,
67
+ invalid: () => invalid,
68
+ required: () => required,
69
+ error: () => error,
70
+ hint: () => hint,
40
71
  })
41
72
 
42
73
  const selected = $derived(ci.selected)
@@ -61,20 +92,22 @@
61
92
  })
62
93
  </script>
63
94
 
64
- <button bind:this={triggerEl} class="sv-country sv-country--{size}" class:is-open={ci.open} class:is-disabled={disabled} {...ci.triggerProps()}>
65
- {#if selected}
66
- <span class="sv-country__flag" aria-hidden="true">{flagEmoji(selected.code)}</span>
67
- <span class="sv-country__name">{selected.name}</span>
68
- {#if showDial}<span class="sv-country__dial">{selected.dial}</span>{/if}
69
- {:else}
70
- <span class="sv-country__ph">{placeholder}</span>
71
- {/if}
72
- <svg class="sv-country__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>
73
- </button>
95
+ <SvField id={uid} {label} {hint} {error} {required} {dir}>
96
+ <button bind:this={triggerEl} class="sv-country sv-country--{size}" class:is-open={ci.open} class:is-disabled={disabled} class:is-invalid={invalid} {...ci.triggerProps()}>
97
+ {#if selected}
98
+ <span class="sv-country__flag" aria-hidden="true">{flagEmoji(selected.code)}</span>
99
+ <span class="sv-country__name">{selected.name}</span>
100
+ {#if showDial}<span class="sv-country__dial">{selected.dial}</span>{/if}
101
+ {:else}
102
+ <span class="sv-country__ph">{placeholder}</span>
103
+ {/if}
104
+ <svg class="sv-country__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>
105
+ </button>
106
+ </SvField>
74
107
 
75
108
  {#if ci.open}
76
- <div bind:this={panelEl} class="sv-country__panel" use:portalToBody style:position="fixed" style:top={`${rect.top}px`} style:left={`${rect.left}px`} style:min-width={`${rect.width}px`} role="dialog">
77
- <input bind:this={searchEl} class="sv-country__search" placeholder="Search countries…" {...ci.searchProps()} />
109
+ <div bind:this={panelEl} class="sv-country__panel" 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`} role="dialog">
110
+ <input bind:this={searchEl} class="sv-country__search" placeholder={`${M.search}…`} {...ci.searchProps()} />
78
111
  <div class="sv-country__list" {...ci.listboxProps()}>
79
112
  {#each ci.filtered as c, i (c.code)}
80
113
  <!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
@@ -84,7 +117,7 @@
84
117
  <span class="sv-country__dial">{c.dial}</span>
85
118
  </div>
86
119
  {:else}
87
- <div class="sv-ddl__empty">No matches</div>
120
+ <div class="sv-ddl__empty">{M.noResults}</div>
88
121
  {/each}
89
122
  </div>
90
123
  </div>
@@ -95,13 +128,15 @@
95
128
  .sv-country {
96
129
  --_accent: var(--sg-accent, #2563eb);
97
130
  display: inline-flex; align-items: center; gap: 8px; width: 220px;
98
- background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a); font: inherit; text-align: left;
131
+ background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a); font: inherit; text-align: start;
99
132
  border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px); padding: 0 10px; cursor: pointer;
100
133
  }
101
134
  .sv-country--sm { height: 28px; font-size: 12px; }
102
135
  .sv-country--md { height: 34px; font-size: 13px; }
103
136
  .sv-country--lg { height: 40px; font-size: 15px; }
104
137
  .sv-country.is-open, .sv-country:focus-visible { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); outline: none; }
138
+ .sv-country.is-invalid { border-color: var(--sg-danger, #dc2626); }
139
+ .sv-country.is-invalid.is-open, .sv-country.is-invalid:focus-visible { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
105
140
  .sv-country.is-disabled { opacity: 0.6; cursor: not-allowed; }
106
141
  .sv-country__flag { font-size: 16px; }
107
142
  .sv-country__name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -0,0 +1,256 @@
1
+ <script lang="ts" module>
2
+ export type DateRangeValue = [Date, Date] | null
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ /**
7
+ * SvDateRangeInput - a start/end date-range field with a portalled two-month
8
+ * range calendar and one-click presets (Today, Last 7 days, This month, ...).
9
+ * Parity target: Smart date-range pickers. Emits `[start, end]` (inclusive) or
10
+ * null. It composes the existing headless range engine: the popover is just a
11
+ * `<SvCalendar selectionMode="range">`, so hover-preview, min/max, restricted /
12
+ * important dates and keyboard all come for free.
13
+ *
14
+ * Part of the SvGrid editor kit: carries the shared editor contract (label,
15
+ * hint, error/validation a11y, size, `dir`/RTL and localizable `messages`) via
16
+ * `<SvField>`, exactly like the other field editors.
17
+ */
18
+ import SvCalendar from './SvCalendar.svelte'
19
+ import SvField from './SvField.svelte'
20
+ import { anchoredRect, portalToBody, popIn, type AnchoredRect } from './popover'
21
+ import { editorAria, nextEditorId, resolveMessages, type SvEditorProps } from './editor-contract'
22
+ import { formatDate } from './datetime/date-format'
23
+ import { toDate, startOfDay, type DateLike } from './datetime/date-core'
24
+ import { rangeDays } from './datetime/date-selection'
25
+ import type { CalendarPreset } from './createCalendar.svelte'
26
+
27
+ /** User-facing strings (localizable via `messages`). */
28
+ type RangeMessages = { dialog: string; clear: string; open: string; to: string }
29
+ const DEFAULT_MESSAGES: RangeMessages = {
30
+ dialog: 'Choose a date range', clear: 'Clear', open: 'Open calendar', to: 'to',
31
+ }
32
+
33
+ type Props = SvEditorProps & {
34
+ /** Inclusive [start, end] range, or null. */
35
+ value?: DateRangeValue
36
+ onChange?: (value: DateRangeValue) => void
37
+ /** Display format for each end (token engine, e.g. 'yyyy-MM-dd'). */
38
+ formatString?: string
39
+ min?: DateLike | null
40
+ max?: DateLike | null
41
+ /** Number of month panels shown side by side. Default 2. */
42
+ months?: number
43
+ firstDayOfWeek?: number
44
+ weekNumbers?: boolean
45
+ locale?: string
46
+ placeholder?: string
47
+ /** Quick shortcuts shown in the calendar's side rail. */
48
+ presets?: ReadonlyArray<CalendarPreset>
49
+ /** Animate the calendar's month navigation. */
50
+ animate?: boolean | 'slide' | 'fade'
51
+ /** Open the popover as soon as the field is focused. */
52
+ autoOpen?: boolean
53
+ /** Override the built-in strings. */
54
+ messages?: Partial<RangeMessages>
55
+ }
56
+
57
+ let {
58
+ value = null,
59
+ onChange,
60
+ formatString = 'yyyy-MM-dd',
61
+ min = null,
62
+ max = null,
63
+ months = 2,
64
+ firstDayOfWeek = 0,
65
+ weekNumbers = false,
66
+ locale = typeof navigator !== 'undefined' ? navigator.language : 'en-US',
67
+ placeholder = 'Select date range',
68
+ presets,
69
+ animate = false,
70
+ autoOpen = false,
71
+ disabled = false,
72
+ readonly = false,
73
+ name,
74
+ size = 'md',
75
+ ariaLabel,
76
+ invalid = false,
77
+ required = false,
78
+ error,
79
+ label,
80
+ hint,
81
+ dir,
82
+ id,
83
+ messages,
84
+ }: Props = $props()
85
+
86
+ const autoId = nextEditorId('sv-daterange')
87
+ const uid = $derived(id ?? autoId)
88
+ const M = $derived(resolveMessages(DEFAULT_MESSAGES, messages))
89
+ const resolvedDir = $derived(dir === 'ltr' || dir === 'rtl' ? dir : undefined)
90
+ const isInteractive = $derived(!disabled && !readonly)
91
+
92
+ const fmt = (d: Date) => formatDate(d, formatString, locale)
93
+ // The label shown in the field: "start to end", one end, or the placeholder.
94
+ const display = $derived.by(() => {
95
+ if (!value) return ''
96
+ const [a, b] = value
97
+ return a && b ? `${fmt(a)} ${M.to} ${fmt(b)}` : a ? fmt(a) : ''
98
+ })
99
+ // The calendar wants the FULL inclusive span as its value (every day renders
100
+ // selected); we only keep the two endpoints.
101
+ const calValue = $derived(value ? rangeDays(value[0], value[1]) : null)
102
+
103
+ let open = $state(false)
104
+ function openPanel() { if (isInteractive && !open) open = true }
105
+ function closePanel() { open = false }
106
+ function toggle() { open ? closePanel() : openPanel() }
107
+ function clear() { onChange?.(null) }
108
+
109
+ function onCalendarChange(dates: Date[]) {
110
+ if (!dates.length) return
111
+ const start = dates[0]!
112
+ const end = dates[dates.length - 1]!
113
+ onChange?.([startOfDay(start), startOfDay(end)])
114
+ // A completed range (2+ distinct days, or a single-day range) closes the panel.
115
+ if (dates.length >= 1 && start !== end) closePanel()
116
+ }
117
+
118
+ // --- Portalled popover positioning (DOM-bound; stays in the component) -------
119
+ let triggerEl = $state<HTMLDivElement | null>(null)
120
+ let panelEl = $state<HTMLDivElement | null>(null)
121
+ let panelRect = $state<AnchoredRect>({ top: 0, left: 0, width: 0, openUpward: false })
122
+
123
+ function updatePos() {
124
+ if (!triggerEl) return
125
+ panelRect = anchoredRect(triggerEl.getBoundingClientRect(), { estimatedHeight: 360, minWidth: 300 })
126
+ }
127
+
128
+ $effect(() => {
129
+ if (!open) return
130
+ updatePos()
131
+ const reposition = () => updatePos()
132
+ window.addEventListener('scroll', reposition, true)
133
+ window.addEventListener('resize', reposition)
134
+ const onDown = (e: PointerEvent) => {
135
+ const t = e.target as Node | null
136
+ if (!t) return
137
+ if (triggerEl?.contains(t) || panelEl?.contains(t)) return
138
+ closePanel()
139
+ }
140
+ document.addEventListener('pointerdown', onDown, true)
141
+ return () => {
142
+ window.removeEventListener('scroll', reposition, true)
143
+ window.removeEventListener('resize', reposition)
144
+ document.removeEventListener('pointerdown', onDown, true)
145
+ }
146
+ })
147
+
148
+ function onFieldKeydown(e: KeyboardEvent) {
149
+ if (e.key === 'Escape') { closePanel() }
150
+ else if (e.key === 'ArrowDown' && (e.altKey || !value)) { e.preventDefault(); openPanel() }
151
+ }
152
+ function focusOpen(node: HTMLInputElement) {
153
+ if (!autoOpen) return
154
+ node.focus()
155
+ openPanel()
156
+ }
157
+ </script>
158
+
159
+ <SvField id={uid} {label} {hint} {error} {required} {dir}>
160
+ <div class="sv-dri" class:sv-dri--disabled={disabled}>
161
+ <div class="sv-dri__field sv-dri__field--{size}" class:is-invalid={invalid} bind:this={triggerEl} dir={resolvedDir}>
162
+ <svg class="sv-dri__icon" viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="18" rx="2" /><path d="M16 2v4M8 2v4M3 10h18" /></svg>
163
+ <input
164
+ id={uid}
165
+ class="sv-dri__input"
166
+ type="text"
167
+ readonly
168
+ {placeholder}
169
+ value={display}
170
+ {disabled}
171
+ {...editorAria({ id: uid, invalid, required, error, hint, ariaLabel })}
172
+ aria-haspopup="dialog"
173
+ aria-expanded={open}
174
+ onclick={() => isInteractive && toggle()}
175
+ onkeydown={onFieldKeydown}
176
+ use:focusOpen
177
+ />
178
+ {#if value && isInteractive}
179
+ <button type="button" class="sv-dri__clear" aria-label={M.clear} onclick={(e) => { e.stopPropagation(); clear() }}>&times;</button>
180
+ {/if}
181
+ </div>
182
+
183
+ {#if open}
184
+ <div
185
+ class="sv-dri__panel"
186
+ bind:this={panelEl}
187
+ use:portalToBody
188
+ use:popIn={{ up: panelRect.openUpward }}
189
+ style:position="fixed"
190
+ style:top={`${panelRect.top}px`}
191
+ style:left={`${panelRect.left}px`}
192
+ role="dialog"
193
+ aria-label={M.dialog}
194
+ dir={resolvedDir}
195
+ >
196
+ <SvCalendar
197
+ value={calValue}
198
+ selectionMode="range"
199
+ {min}
200
+ {max}
201
+ {months}
202
+ {firstDayOfWeek}
203
+ {weekNumbers}
204
+ {locale}
205
+ {presets}
206
+ {animate}
207
+ dir={resolvedDir}
208
+ onChange={onCalendarChange}
209
+ />
210
+ </div>
211
+ {/if}
212
+
213
+ {#if name}
214
+ <input type="hidden" name={`${name}_start`} value={value?.[0] ? value[0].toISOString() : ''} />
215
+ <input type="hidden" name={`${name}_end`} value={value?.[1] ? value[1].toISOString() : ''} />
216
+ {/if}
217
+ </div>
218
+ </SvField>
219
+
220
+ <style>
221
+ .sv-dri {
222
+ --_accent: var(--sg-accent, #2563eb);
223
+ --_border: var(--sg-input-border, var(--sg-border, #cbd5e1));
224
+ --_radius: var(--sg-radius, 8px);
225
+ display: inline-block; position: relative; color: var(--sg-fg, #0f172a);
226
+ }
227
+ .sv-dri--disabled { opacity: 0.6; }
228
+ .sv-dri__field {
229
+ display: flex; align-items: center; gap: 6px; width: 260px;
230
+ background: var(--sg-input-bg, #fff); border: 1px solid var(--_border); border-radius: var(--_radius);
231
+ padding-inline: 10px 4px;
232
+ }
233
+ .sv-dri__field:focus-within { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
234
+ .sv-dri__field.is-invalid { border-color: var(--sg-danger, #dc2626); }
235
+ .sv-dri__field.is-invalid:focus-within { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
236
+ .sv-dri__field--sm { height: 28px; font-size: 12px; }
237
+ .sv-dri__field--md { height: 34px; font-size: 13px; }
238
+ .sv-dri__field--lg { height: 40px; font-size: 15px; }
239
+ .sv-dri__icon { color: var(--sg-muted, #64748b); flex: none; }
240
+ .sv-dri__input {
241
+ flex: 1; min-width: 0; border: 0; background: none; outline: none; color: inherit; font: inherit;
242
+ cursor: pointer; text-overflow: ellipsis;
243
+ }
244
+ .sv-dri__input::placeholder { color: var(--sg-muted, #94a3b8); }
245
+ .sv-dri__clear {
246
+ display: grid; place-items: center; width: 24px; height: 24px; flex: none;
247
+ background: none; border: 0; color: var(--sg-muted, #64748b); cursor: pointer; border-radius: 6px; font-size: 17px; line-height: 1;
248
+ }
249
+ .sv-dri__clear:hover { color: var(--_accent); background: color-mix(in srgb, var(--_accent) 10%, transparent); }
250
+
251
+ :global(.sv-dri__panel) {
252
+ z-index: 2147483647;
253
+ background: var(--sg-bg, #fff); border: 1px solid var(--sg-border, #e2e8f0);
254
+ border-radius: 12px; box-shadow: 0 16px 48px -12px rgba(15, 23, 42, 0.35);
255
+ }
256
+ </style>
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Component tests for SvDateRangeInput: the field renders the formatted range,
3
+ * the two-month range calendar portals to <body>, picking a range emits
4
+ * [start, end], and the shared editor contract (label / error) is wired.
5
+ */
6
+ import { describe, expect, it, afterEach } from 'vitest'
7
+ import { mount, unmount, flushSync } from 'svelte'
8
+ import SvDateRangeInput from './SvDateRangeInput.svelte'
9
+
10
+ function mountDri(props: Record<string, unknown>) {
11
+ const target = document.createElement('div')
12
+ document.body.appendChild(target)
13
+ const app = mount(SvDateRangeInput, { target, props: props as any })
14
+ flushSync()
15
+ return { target, destroy: () => { unmount(app); target.remove() } }
16
+ }
17
+
18
+ // The calendar panel portals to <body>, so query the document.
19
+ const panel = () => document.querySelector<HTMLElement>('.sv-dri__panel')
20
+
21
+ afterEach(() => {
22
+ document.querySelectorAll('.sv-dri__panel').forEach((n) => n.remove())
23
+ })
24
+
25
+ describe('SvDateRangeInput', () => {
26
+ it('formats the initial [start, end] range with the "to" separator', () => {
27
+ const { target, destroy } = mountDri({
28
+ value: [new Date(2026, 5, 1), new Date(2026, 5, 7)],
29
+ formatString: 'yyyy-MM-dd',
30
+ })
31
+ try {
32
+ const input = target.querySelector<HTMLInputElement>('.sv-dri__input')!
33
+ expect(input.value).toBe('2026-06-01 to 2026-06-07')
34
+ } finally { destroy() }
35
+ })
36
+
37
+ it('shows the placeholder when empty and opens the calendar on click', () => {
38
+ const { target, destroy } = mountDri({ value: null, placeholder: 'Pick a range' })
39
+ try {
40
+ const input = target.querySelector<HTMLInputElement>('.sv-dri__input')!
41
+ expect(input.value).toBe('')
42
+ expect(input.placeholder).toBe('Pick a range')
43
+ expect(panel()).toBeNull()
44
+ input.click()
45
+ flushSync()
46
+ expect(panel()).not.toBeNull()
47
+ // The portalled panel embeds a range-mode SvCalendar.
48
+ expect(panel()!.querySelector('.sv-cal')).not.toBeNull()
49
+ } finally { destroy() }
50
+ })
51
+
52
+ it('wires the shared editor contract (label + aria-invalid + error text)', () => {
53
+ const { target, destroy } = mountDri({
54
+ value: null, label: 'Period', required: true, invalid: true, error: 'Required',
55
+ })
56
+ try {
57
+ const input = target.querySelector<HTMLInputElement>('.sv-dri__input')!
58
+ const labelEl = target.querySelector<HTMLLabelElement>('.sv-field__label')!
59
+ expect(labelEl.textContent).toContain('Period')
60
+ expect(labelEl.getAttribute('for')).toBe(input.id)
61
+ expect(input.getAttribute('aria-invalid')).toBe('true')
62
+ expect(input.getAttribute('aria-required')).toBe('true')
63
+ const err = target.querySelector<HTMLElement>('.sv-field__error')!
64
+ expect(err.textContent).toBe('Required')
65
+ expect(err.id).toBe(input.getAttribute('aria-describedby'))
66
+ } finally { destroy() }
67
+ })
68
+
69
+ it('localizes the "to" separator via messages', () => {
70
+ const { target, destroy } = mountDri({
71
+ value: [new Date(2026, 5, 1), new Date(2026, 5, 7)],
72
+ formatString: 'dd/MM/yyyy',
73
+ messages: { to: 'au' },
74
+ })
75
+ try {
76
+ const input = target.querySelector<HTMLInputElement>('.sv-dri__input')!
77
+ expect(input.value).toBe('01/06/2026 au 07/06/2026')
78
+ } finally { destroy() }
79
+ })
80
+ })
@@ -15,11 +15,20 @@
15
15
  */
16
16
  import SvCalendar from './SvCalendar.svelte'
17
17
  import SvTimePicker from './SvTimePicker.svelte'
18
- import { anchoredRect, portalToBody, type AnchoredRect } from './popover'
18
+ import SvField from './SvField.svelte'
19
+ import { anchoredRect, portalToBody, popIn, type AnchoredRect } from './popover'
20
+ import { createDismissableLayer } from './a11y/dismissable'
21
+ import { nextEditorId, resolveMessages, type SvEditorProps } from './editor-contract'
19
22
  import { createDateTimePicker, type DateTimeValue, type DropDownDisplayMode } from './createDateTimePicker.svelte'
20
23
  import type { DateLike } from './datetime/date-core'
21
24
 
22
- type Props = {
25
+ /** User-facing strings (localizable via `messages`). */
26
+ type DateTimeMessages = { dialog: string; date: string; time: string; clear: string; open: string }
27
+ const DEFAULT_MESSAGES: DateTimeMessages = {
28
+ dialog: 'Choose date and time', date: 'DATE', time: 'TIME', clear: 'Clear', open: 'Open picker',
29
+ }
30
+
31
+ type Props = SvEditorProps & {
23
32
  value?: DateTimeValue
24
33
  onChange?: (value: Date | null) => void
25
34
  /** Fired when the value is finalized (Enter, blur, or a single-date pick).
@@ -33,9 +42,6 @@
33
42
  max?: DateLike | null
34
43
  nullable?: boolean
35
44
  placeholder?: string
36
- disabled?: boolean
37
- readonly?: boolean
38
- name?: string
39
45
  locale?: string
40
46
  firstDayOfWeek?: number
41
47
  weekNumbers?: boolean
@@ -50,6 +56,8 @@
50
56
  autoOpen?: boolean
51
57
  /** Animate the calendar's month/drill navigation (honors reduced-motion). */
52
58
  animate?: boolean | 'slide' | 'fade'
59
+ /** Override the built-in strings (tabs, dialog, clear/open aria-labels). */
60
+ messages?: Partial<DateTimeMessages>
53
61
  }
54
62
 
55
63
  let {
@@ -75,8 +83,22 @@
75
83
  stepMinutes = 1,
76
84
  autoOpen = false,
77
85
  animate = false,
86
+ ariaLabel,
87
+ invalid = false,
88
+ required = false,
89
+ error,
90
+ label,
91
+ hint,
92
+ dir,
93
+ id,
94
+ messages,
78
95
  }: Props = $props()
79
96
 
97
+ const autoId = nextEditorId('sv-dtp')
98
+ const uid = $derived(id ?? autoId)
99
+ const M = $derived(resolveMessages(DEFAULT_MESSAGES, messages))
100
+ const resolvedDir = $derived(dir === 'ltr' || dir === 'rtl' ? dir : undefined)
101
+
80
102
  // The headless core owns value math, parse/format, clamping, the dropdown
81
103
  // open/tab state and the sub-picker wiring. Reactive inputs are getters;
82
104
  // callbacks are closures.
@@ -95,6 +117,14 @@
95
117
  stepMinutes: () => stepMinutes,
96
118
  disabled: () => disabled,
97
119
  readonly: () => readonly,
120
+ toggleLabel: () => M.open,
121
+ clearLabel: () => M.clear,
122
+ id: () => uid,
123
+ invalid: () => invalid,
124
+ required: () => required,
125
+ error: () => error,
126
+ hint: () => hint,
127
+ ariaLabel: () => ariaLabel,
98
128
  })
99
129
 
100
130
  // --- Portalled dropdown positioning (DOM-bound; stays in the component) -----
@@ -114,17 +144,16 @@
114
144
  const reposition = () => updatePos()
115
145
  window.addEventListener('scroll', reposition, true)
116
146
  window.addEventListener('resize', reposition)
117
- const onDown = (e: PointerEvent) => {
118
- const t = e.target as Node | null
119
- if (!t) return
120
- if (triggerEl?.contains(t) || panelEl?.contains(t)) return
121
- dtp.closePanel()
122
- }
123
- document.addEventListener('pointerdown', onDown, true)
147
+ const layer = createDismissableLayer({
148
+ element: () => [triggerEl, panelEl],
149
+ onDismiss: () => dtp.closePanel(),
150
+ closeOnEscape: false,
151
+ })
152
+ layer.activate()
124
153
  return () => {
125
154
  window.removeEventListener('scroll', reposition, true)
126
155
  window.removeEventListener('resize', reposition)
127
- document.removeEventListener('pointerdown', onDown, true)
156
+ layer.release()
128
157
  }
129
158
  })
130
159
 
@@ -142,8 +171,9 @@
142
171
  }
143
172
  </script>
144
173
 
145
- <div class="sv-dtp" class:sv-dtp--disabled={disabled} onfocusout={onRootFocusOut}>
146
- <div class="sv-dtp__field" bind:this={triggerEl}>
174
+ <SvField id={uid} {label} {hint} {error} {required} {dir}>
175
+ <div class="sv-dtp" class:sv-dtp--disabled={disabled} dir={resolvedDir} onfocusout={onRootFocusOut}>
176
+ <div class="sv-dtp__field" class:is-invalid={invalid} bind:this={triggerEl}>
147
177
  {#if spinButtons}
148
178
  <div class="sv-dtp__spin">
149
179
  <button {...dtp.spinProps(1)}>▲</button>
@@ -170,16 +200,18 @@
170
200
  class="sv-dtp__panel"
171
201
  bind:this={panelEl}
172
202
  use:portalToBody
203
+ use:popIn={{ up: panelRect.openUpward }}
173
204
  style:position="fixed"
174
205
  style:top={`${panelRect.top}px`}
175
206
  style:left={`${panelRect.left}px`}
176
207
  role="dialog"
177
- aria-label="Choose date and time"
208
+ aria-label={M.dialog}
209
+ dir={resolvedDir}
178
210
  >
179
211
  {#if dtp.showDateTab && dtp.showTimeTab}
180
212
  <div class="sv-dtp__tabs" role="tablist">
181
- <button class:is-active={dtp.tab === 'date'} {...dtp.tabProps('date')}>DATE</button>
182
- <button class:is-active={dtp.tab === 'time'} {...dtp.tabProps('time')}>TIME</button>
213
+ <button class:is-active={dtp.tab === 'date'} {...dtp.tabProps('date')}>{M.date}</button>
214
+ <button class:is-active={dtp.tab === 'time'} {...dtp.tabProps('time')}>{M.time}</button>
183
215
  </div>
184
216
  {/if}
185
217
  <div class="sv-dtp__body">
@@ -193,6 +225,7 @@
193
225
  {weekNumbers}
194
226
  {locale}
195
227
  {animate}
228
+ dir={resolvedDir}
196
229
  onChange={dtp.onCalendarChange}
197
230
  />
198
231
  {:else if dtp.showTimeTab}
@@ -201,6 +234,7 @@
201
234
  format={hourFormat}
202
235
  {minuteInterval}
203
236
  footer
237
+ dir={resolvedDir}
204
238
  onChange={dtp.onTimeChange}
205
239
  />
206
240
  {/if}
@@ -210,6 +244,7 @@
210
244
 
211
245
  {#if name}<input type="hidden" {name} value={dtp.current ? dtp.current.toISOString() : ''} />{/if}
212
246
  </div>
247
+ </SvField>
213
248
 
214
249
  <style>
215
250
  .sv-dtp {
@@ -225,14 +260,16 @@
225
260
  .sv-dtp__field {
226
261
  display: flex; align-items: center; gap: 2px;
227
262
  background: var(--_bg); border: 1px solid var(--_border); border-radius: var(--_radius);
228
- padding: 0 4px 0 0; height: 34px;
263
+ padding-block: 0; padding-inline: 0 4px; height: 34px;
229
264
  }
230
265
  .sv-dtp__field:focus-within { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
266
+ .sv-dtp__field.is-invalid { border-color: var(--sg-danger, #dc2626); }
267
+ .sv-dtp__field.is-invalid:focus-within { box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-danger, #dc2626) 22%, transparent); }
231
268
  .sv-dtp__input {
232
269
  flex: 1; min-width: 0; border: 0; background: none; outline: none; color: inherit;
233
270
  font: inherit; font-size: 13px; padding: 0 8px; height: 100%;
234
271
  }
235
- .sv-dtp__spin { display: flex; flex-direction: column; border-right: 1px solid var(--_border); }
272
+ .sv-dtp__spin { display: flex; flex-direction: column; border-inline-end: 1px solid var(--_border); }
236
273
  .sv-dtp__spin button {
237
274
  flex: 1; width: 20px; border: 0; background: none; color: var(--_muted); cursor: pointer;
238
275
  font-size: 7px; line-height: 1; padding: 0;