@svgrid/grid 1.2.34 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (444) hide show
  1. package/README.md +6 -5
  2. package/dist/SvAccordion.svelte +109 -0
  3. package/dist/SvAccordion.svelte.d.ts +28 -0
  4. package/dist/SvAlert.svelte +92 -0
  5. package/dist/SvAlert.svelte.d.ts +27 -0
  6. package/dist/SvAutoComplete.svelte +47 -18
  7. package/dist/SvAutoComplete.svelte.d.ts +2 -5
  8. package/dist/SvAvatar.svelte +75 -0
  9. package/dist/SvAvatar.svelte.d.ts +14 -0
  10. package/dist/SvAvatarGroup.svelte +48 -0
  11. package/dist/SvAvatarGroup.svelte.d.ts +16 -0
  12. package/dist/SvBadge.svelte +54 -0
  13. package/dist/SvBadge.svelte.d.ts +23 -0
  14. package/dist/SvBreadcrumb.svelte +88 -0
  15. package/dist/SvBreadcrumb.svelte.d.ts +23 -0
  16. package/dist/SvButtonGroup.svelte +121 -0
  17. package/dist/SvButtonGroup.svelte.d.ts +16 -0
  18. package/dist/SvCalendar.svelte +72 -9
  19. package/dist/SvCalendar.svelte.d.ts +34 -1
  20. package/dist/SvCard.svelte +71 -0
  21. package/dist/SvCard.svelte.d.ts +28 -0
  22. package/dist/SvCarousel.svelte +134 -0
  23. package/dist/SvCarousel.svelte.d.ts +28 -0
  24. package/dist/SvCheckBox.svelte +36 -21
  25. package/dist/SvCheckBox.svelte.d.ts +2 -5
  26. package/dist/SvChip.svelte +88 -0
  27. package/dist/SvChip.svelte.d.ts +31 -0
  28. package/dist/SvCircularProgress.svelte +109 -0
  29. package/dist/SvCircularProgress.svelte.d.ts +31 -0
  30. package/dist/SvColorInput.svelte +54 -27
  31. package/dist/SvColorInput.svelte.d.ts +10 -6
  32. package/dist/SvComboBox.svelte +119 -33
  33. package/dist/SvComboBox.svelte.d.ts +18 -6
  34. package/dist/SvCommand.svelte +197 -0
  35. package/dist/SvCommand.svelte.d.ts +26 -0
  36. package/dist/SvContextMenu.svelte +96 -0
  37. package/dist/SvContextMenu.svelte.d.ts +25 -0
  38. package/dist/SvCountryInput.svelte +56 -21
  39. package/dist/SvCountryInput.svelte.d.ts +9 -5
  40. package/dist/SvDateRangeInput.svelte +256 -0
  41. package/dist/SvDateRangeInput.svelte.d.ts +37 -0
  42. package/dist/SvDateTimePicker.svelte +57 -20
  43. package/dist/SvDateTimePicker.svelte.d.ts +12 -4
  44. package/dist/SvDivider.svelte +61 -0
  45. package/dist/SvDivider.svelte.d.ts +22 -0
  46. package/dist/SvDrawer.svelte +226 -0
  47. package/dist/SvDrawer.svelte.d.ts +43 -0
  48. package/dist/SvDropDownList.svelte +114 -35
  49. package/dist/SvDropDownList.svelte.d.ts +7 -6
  50. package/dist/SvDurationInput.svelte +108 -0
  51. package/dist/SvDurationInput.svelte.d.ts +15 -0
  52. package/dist/SvEmptyState.svelte +59 -0
  53. package/dist/SvEmptyState.svelte.d.ts +25 -0
  54. package/dist/SvField.svelte +85 -0
  55. package/dist/SvField.svelte.d.ts +30 -0
  56. package/dist/SvFileUpload.svelte +169 -0
  57. package/dist/SvFileUpload.svelte.d.ts +24 -0
  58. package/dist/SvForm.svelte +7 -1
  59. package/dist/SvForm.svelte.d.ts +3 -0
  60. package/dist/SvGauge.svelte +7 -1
  61. package/dist/SvGauge.svelte.d.ts +11 -0
  62. package/dist/SvGrid.controller.svelte.d.ts +6 -0
  63. package/dist/SvGrid.controller.svelte.js +64 -9
  64. package/dist/SvGrid.css +33 -0
  65. package/dist/SvGrid.svelte +116 -3
  66. package/dist/SvGrid.types.d.ts +366 -0
  67. package/dist/SvGridBoard.svelte +2281 -0
  68. package/dist/SvGridBoard.svelte.d.ts +31 -0
  69. package/dist/SvGridDropdown.svelte +2 -1
  70. package/dist/SvGridSelect.svelte +255 -0
  71. package/dist/SvGridSelect.svelte.d.ts +32 -0
  72. package/dist/SvGroupCell.svelte +109 -0
  73. package/dist/SvGroupCell.svelte.d.ts +41 -0
  74. package/dist/SvListBox.svelte +123 -36
  75. package/dist/SvListBox.svelte.d.ts +12 -8
  76. package/dist/SvMaskedInput.svelte +53 -13
  77. package/dist/SvMaskedInput.svelte.d.ts +16 -6
  78. package/dist/SvMenu.svelte +101 -0
  79. package/dist/SvMenu.svelte.d.ts +26 -0
  80. package/dist/SvMenuList.svelte +143 -0
  81. package/dist/SvMenuList.svelte.d.ts +24 -0
  82. package/dist/SvModal.svelte +220 -0
  83. package/dist/SvModal.svelte.d.ts +31 -0
  84. package/dist/SvMultiSelect.svelte +277 -0
  85. package/dist/SvMultiSelect.svelte.d.ts +31 -0
  86. package/dist/SvNavPane.svelte +314 -0
  87. package/dist/SvNavPane.svelte.d.ts +59 -0
  88. package/dist/SvNumberInput.svelte +23 -9
  89. package/dist/SvNumberInput.svelte.d.ts +2 -0
  90. package/dist/SvOtpInput.svelte +157 -0
  91. package/dist/SvOtpInput.svelte.d.ts +16 -0
  92. package/dist/SvPagination.svelte +100 -0
  93. package/dist/SvPagination.svelte.d.ts +16 -0
  94. package/dist/SvPasswordInput.svelte +42 -12
  95. package/dist/SvPasswordInput.svelte.d.ts +13 -6
  96. package/dist/SvPhoneInput.svelte +43 -11
  97. package/dist/SvPhoneInput.svelte.d.ts +10 -11
  98. package/dist/SvPopover.svelte +127 -0
  99. package/dist/SvPopover.svelte.d.ts +35 -0
  100. package/dist/SvProgress.svelte +116 -0
  101. package/dist/SvProgress.svelte.d.ts +32 -0
  102. package/dist/SvRadioGroup.svelte +40 -22
  103. package/dist/SvRadioGroup.svelte.d.ts +2 -10
  104. package/dist/SvRating.svelte +51 -30
  105. package/dist/SvRating.svelte.d.ts +10 -7
  106. package/dist/SvRichText.svelte +192 -0
  107. package/dist/SvRichText.svelte.d.ts +16 -0
  108. package/dist/SvRowGroupPanel.svelte +147 -0
  109. package/dist/SvRowGroupPanel.svelte.d.ts +17 -0
  110. package/dist/SvScrollArea.svelte +50 -0
  111. package/dist/SvScrollArea.svelte.d.ts +22 -0
  112. package/dist/SvSkeleton.svelte +76 -0
  113. package/dist/SvSkeleton.svelte.d.ts +25 -0
  114. package/dist/SvSlider.svelte +41 -9
  115. package/dist/SvSlider.svelte.d.ts +4 -4
  116. package/dist/SvSparkline.svelte +77 -0
  117. package/dist/SvSparkline.svelte.d.ts +28 -0
  118. package/dist/SvSplitter.svelte +138 -0
  119. package/dist/SvSplitter.svelte.d.ts +32 -0
  120. package/dist/SvStat.svelte +86 -0
  121. package/dist/SvStat.svelte.d.ts +30 -0
  122. package/dist/SvStepper.svelte +91 -0
  123. package/dist/SvStepper.svelte.d.ts +24 -0
  124. package/dist/SvSwitchButton.svelte +39 -24
  125. package/dist/SvSwitchButton.svelte.d.ts +2 -5
  126. package/dist/SvTabs.svelte +147 -28
  127. package/dist/SvTabs.svelte.d.ts +16 -4
  128. package/dist/SvTagsInput.svelte +59 -22
  129. package/dist/SvTagsInput.svelte.d.ts +9 -4
  130. package/dist/SvTextArea.svelte +113 -0
  131. package/dist/SvTextArea.svelte.d.ts +18 -0
  132. package/dist/SvTextInput.svelte +115 -0
  133. package/dist/SvTextInput.svelte.d.ts +20 -0
  134. package/dist/SvTimePicker.svelte +19 -5
  135. package/dist/SvTimePicker.svelte.d.ts +12 -0
  136. package/dist/SvTimeline.svelte +75 -0
  137. package/dist/SvTimeline.svelte.d.ts +24 -0
  138. package/dist/SvToaster.svelte +124 -0
  139. package/dist/SvToaster.svelte.d.ts +10 -0
  140. package/dist/SvToggleButton.svelte +37 -12
  141. package/dist/SvToggleButton.svelte.d.ts +2 -4
  142. package/dist/SvTooltip.svelte +113 -0
  143. package/dist/SvTooltip.svelte.d.ts +22 -0
  144. package/dist/SvTour.svelte +151 -0
  145. package/dist/SvTour.svelte.d.ts +25 -0
  146. package/dist/SvTree.svelte +286 -44
  147. package/dist/SvTree.svelte.d.ts +26 -9
  148. package/dist/SvTreeSelect.svelte +222 -0
  149. package/dist/SvTreeSelect.svelte.d.ts +24 -0
  150. package/dist/a11y/dismissable.d.ts +32 -0
  151. package/dist/a11y/dismissable.js +54 -0
  152. package/dist/a11y/focus-trap.d.ts +39 -0
  153. package/dist/a11y/focus-trap.js +142 -0
  154. package/dist/a11y/live-region.d.ts +8 -0
  155. package/dist/a11y/live-region.js +60 -0
  156. package/dist/a11y/scroll-lock.d.ts +4 -0
  157. package/dist/a11y/scroll-lock.js +41 -0
  158. package/dist/a11y.d.ts +3 -1
  159. package/dist/a11y.js +1 -1
  160. package/dist/avatar.d.ts +10 -0
  161. package/dist/avatar.js +22 -0
  162. package/dist/builtin-editors.d.ts +5 -0
  163. package/dist/builtin-editors.js +55 -0
  164. package/dist/cdn/svgrid.js +16021 -8234
  165. package/dist/cdn/svgrid.svelte-external.js +14542 -6764
  166. package/dist/columns.js +3 -2
  167. package/dist/core.d.ts +7 -1
  168. package/dist/countries.d.ts +12 -0
  169. package/dist/countries.js +24 -0
  170. package/dist/createAccordion.svelte.d.ts +71 -0
  171. package/dist/createAccordion.svelte.js +99 -0
  172. package/dist/createAutocomplete.svelte.d.ts +13 -4
  173. package/dist/createAutocomplete.svelte.js +11 -1
  174. package/dist/createButtonGroup.svelte.d.ts +62 -0
  175. package/dist/createButtonGroup.svelte.js +154 -0
  176. package/dist/createCalendar.svelte.d.ts +5 -0
  177. package/dist/createCalendar.svelte.js +2 -0
  178. package/dist/createCheckbox.svelte.d.ts +9 -17
  179. package/dist/createCheckbox.svelte.js +28 -1
  180. package/dist/createColorInput.svelte.d.ts +12 -22
  181. package/dist/createColorInput.svelte.js +13 -0
  182. package/dist/createCombobox.svelte.d.ts +16 -5
  183. package/dist/createCombobox.svelte.js +13 -2
  184. package/dist/createCountryInput.svelte.d.ts +14 -3
  185. package/dist/createCountryInput.svelte.js +12 -2
  186. package/dist/createDateTimePicker.svelte.d.ts +17 -2
  187. package/dist/createDateTimePicker.svelte.js +12 -2
  188. package/dist/createDropdownList.svelte.d.ts +14 -5
  189. package/dist/createDropdownList.svelte.js +50 -1
  190. package/dist/createFileUpload.svelte.d.ts +68 -0
  191. package/dist/createFileUpload.svelte.js +122 -0
  192. package/dist/createListbox.svelte.d.ts +10 -1
  193. package/dist/createListbox.svelte.js +48 -1
  194. package/dist/createMaskedInput.svelte.d.ts +11 -2
  195. package/dist/createMaskedInput.svelte.js +11 -1
  196. package/dist/createNumberInput.svelte.d.ts +1 -0
  197. package/dist/createNumberInput.svelte.js +1 -0
  198. package/dist/createPasswordInput.svelte.d.ts +19 -21
  199. package/dist/createPasswordInput.svelte.js +16 -4
  200. package/dist/createPhoneInput.svelte.d.ts +26 -7
  201. package/dist/createPhoneInput.svelte.js +24 -3
  202. package/dist/createPopoverSelect.svelte.d.ts +86 -0
  203. package/dist/createPopoverSelect.svelte.js +196 -0
  204. package/dist/createRadioGroup.svelte.d.ts +9 -0
  205. package/dist/createRadioGroup.svelte.js +11 -1
  206. package/dist/createRating.svelte.d.ts +11 -0
  207. package/dist/createRating.svelte.js +12 -1
  208. package/dist/createSlider.svelte.d.ts +2 -0
  209. package/dist/createSlider.svelte.js +16 -5
  210. package/dist/createSplitter.svelte.d.ts +67 -0
  211. package/dist/createSplitter.svelte.js +99 -0
  212. package/dist/createSwitch.svelte.d.ts +9 -16
  213. package/dist/createSwitch.svelte.js +28 -1
  214. package/dist/createTabs.svelte.d.ts +7 -0
  215. package/dist/createTabs.svelte.js +17 -2
  216. package/dist/createTagsInput.svelte.d.ts +13 -1
  217. package/dist/createTagsInput.svelte.js +12 -3
  218. package/dist/createToggle.svelte.d.ts +9 -0
  219. package/dist/createToggle.svelte.js +12 -1
  220. package/dist/createTree.svelte.d.ts +19 -0
  221. package/dist/createTree.svelte.js +108 -15
  222. package/dist/debounce.d.ts +11 -0
  223. package/dist/debounce.js +30 -0
  224. package/dist/duration.d.ts +15 -0
  225. package/dist/duration.js +51 -0
  226. package/dist/editor-contract.d.ts +62 -7
  227. package/dist/editor-contract.js +45 -5
  228. package/dist/editor-registry.d.ts +67 -0
  229. package/dist/editor-registry.js +38 -0
  230. package/dist/editors/cell-editors.d.ts +1 -1
  231. package/dist/editors/cell-editors.js +5 -0
  232. package/dist/fuzzy.d.ts +3 -0
  233. package/dist/fuzzy.js +41 -0
  234. package/dist/grid-select.d.ts +9 -0
  235. package/dist/grid-select.js +8 -0
  236. package/dist/index.d.ts +79 -6
  237. package/dist/index.js +78 -4
  238. package/dist/keyboard-handlers.js +19 -0
  239. package/dist/list-option.d.ts +36 -0
  240. package/dist/list-option.js +68 -0
  241. package/dist/otp.d.ts +16 -0
  242. package/dist/otp.js +26 -0
  243. package/dist/paginate.d.ts +20 -0
  244. package/dist/paginate.js +40 -0
  245. package/dist/popover.d.ts +15 -1
  246. package/dist/popover.js +29 -3
  247. package/dist/recurrence.d.ts +36 -0
  248. package/dist/recurrence.js +90 -0
  249. package/dist/server-data-source.d.ts +89 -0
  250. package/dist/server-data-source.js +4 -0
  251. package/dist/server-group-model.d.ts +98 -0
  252. package/dist/server-group-model.js +263 -0
  253. package/dist/toast-store.svelte.d.ts +41 -0
  254. package/dist/toast-store.svelte.js +113 -0
  255. package/dist/tree-select.d.ts +19 -0
  256. package/dist/tree-select.js +40 -0
  257. package/dist/ui-app.types.d.ts +80 -0
  258. package/dist/ui-app.types.js +7 -0
  259. package/dist/validators.d.ts +43 -0
  260. package/dist/validators.js +53 -0
  261. package/dist/virtual.d.ts +35 -0
  262. package/dist/virtual.js +34 -0
  263. package/package.json +1 -1
  264. package/src/SvAccordion.svelte +109 -0
  265. package/src/SvAccordion.test.ts +66 -0
  266. package/src/SvAlert.svelte +92 -0
  267. package/src/SvAutoComplete.svelte +47 -18
  268. package/src/SvAvatar.svelte +75 -0
  269. package/src/SvAvatarGroup.svelte +48 -0
  270. package/src/SvBadge.svelte +54 -0
  271. package/src/SvBreadcrumb.svelte +88 -0
  272. package/src/SvButtonGroup.svelte +121 -0
  273. package/src/SvButtonGroup.test.ts +72 -0
  274. package/src/SvCalendar.svelte +72 -9
  275. package/src/SvCard.svelte +71 -0
  276. package/src/SvCarousel.svelte +134 -0
  277. package/src/SvCheckBox.svelte +36 -21
  278. package/src/SvChip.svelte +88 -0
  279. package/src/SvCircularProgress.svelte +109 -0
  280. package/src/SvColorInput.svelte +54 -27
  281. package/src/SvComboBox.svelte +119 -33
  282. package/src/SvCommand.svelte +197 -0
  283. package/src/SvContextMenu.svelte +96 -0
  284. package/src/SvCountryInput.svelte +56 -21
  285. package/src/SvDateRangeInput.svelte +256 -0
  286. package/src/SvDateRangeInput.test.ts +80 -0
  287. package/src/SvDateTimePicker.svelte +57 -20
  288. package/src/SvDivider.svelte +61 -0
  289. package/src/SvDrawer.svelte +226 -0
  290. package/src/SvDrawer.test.ts +125 -0
  291. package/src/SvDropDownList.svelte +114 -35
  292. package/src/SvDurationInput.svelte +108 -0
  293. package/src/SvEmptyState.svelte +59 -0
  294. package/src/SvField.svelte +85 -0
  295. package/src/SvFileUpload.svelte +169 -0
  296. package/src/SvFileUpload.test.ts +59 -0
  297. package/src/SvForm.svelte +7 -1
  298. package/src/SvGauge.svelte +7 -1
  299. package/src/SvGrid.controller.svelte.ts +68 -9
  300. package/src/SvGrid.css +33 -0
  301. package/src/SvGrid.svelte +116 -3
  302. package/src/SvGrid.types.ts +374 -0
  303. package/src/SvGridBoard.svelte +2281 -0
  304. package/src/SvGridDropdown.svelte +2 -1
  305. package/src/SvGridSelect.svelte +255 -0
  306. package/src/SvGridSelect.test.ts +100 -0
  307. package/src/SvGroupCell.svelte +109 -0
  308. package/src/SvListBox.svelte +123 -36
  309. package/src/SvMaskedInput.svelte +53 -13
  310. package/src/SvMenu.svelte +101 -0
  311. package/src/SvMenu.test.ts +73 -0
  312. package/src/SvMenuList.svelte +143 -0
  313. package/src/SvModal.svelte +220 -0
  314. package/src/SvMultiSelect.svelte +277 -0
  315. package/src/SvMultiSelect.test.ts +167 -0
  316. package/src/SvNavPane.svelte +314 -0
  317. package/src/SvNavPane.test.ts +70 -0
  318. package/src/SvNumberInput.svelte +23 -9
  319. package/src/SvOtpInput.svelte +157 -0
  320. package/src/SvPagination.svelte +100 -0
  321. package/src/SvPasswordInput.svelte +42 -12
  322. package/src/SvPhoneInput.svelte +43 -11
  323. package/src/SvPopover.svelte +127 -0
  324. package/src/SvProgress.svelte +116 -0
  325. package/src/SvRadioGroup.svelte +40 -22
  326. package/src/SvRating.svelte +51 -30
  327. package/src/SvRichText.svelte +192 -0
  328. package/src/SvRowGroupPanel.svelte +147 -0
  329. package/src/SvScrollArea.svelte +50 -0
  330. package/src/SvSkeleton.svelte +76 -0
  331. package/src/SvSlider.svelte +41 -9
  332. package/src/SvSparkline.svelte +77 -0
  333. package/src/SvSplitter.svelte +138 -0
  334. package/src/SvSplitter.test.ts +55 -0
  335. package/src/SvStat.svelte +86 -0
  336. package/src/SvStepper.svelte +91 -0
  337. package/src/SvSwitchButton.svelte +39 -24
  338. package/src/SvTabs.svelte +147 -28
  339. package/src/SvTagsInput.svelte +59 -22
  340. package/src/SvTextArea.svelte +113 -0
  341. package/src/SvTextInput.svelte +115 -0
  342. package/src/SvTimePicker.svelte +19 -5
  343. package/src/SvTimeline.svelte +75 -0
  344. package/src/SvToaster.svelte +124 -0
  345. package/src/SvToaster.test.ts +63 -0
  346. package/src/SvToggleButton.svelte +37 -12
  347. package/src/SvTooltip.svelte +113 -0
  348. package/src/SvTour.svelte +151 -0
  349. package/src/SvTree.svelte +286 -44
  350. package/src/SvTreeSelect.svelte +222 -0
  351. package/src/SvTreeSelect.test.ts +87 -0
  352. package/src/a11y/dismissable.test.ts +95 -0
  353. package/src/a11y/dismissable.ts +94 -0
  354. package/src/a11y/focus-trap.test.ts +110 -0
  355. package/src/a11y/focus-trap.ts +134 -0
  356. package/src/a11y/live-region.test.ts +40 -0
  357. package/src/a11y/live-region.ts +65 -0
  358. package/src/a11y/scroll-lock.test.ts +38 -0
  359. package/src/a11y/scroll-lock.ts +41 -0
  360. package/src/a11y.ts +3 -1
  361. package/src/avatar.test.ts +32 -0
  362. package/src/avatar.ts +21 -0
  363. package/src/board.test.ts +927 -0
  364. package/src/builtin-editors.grid.test.ts +85 -0
  365. package/src/builtin-editors.test.ts +58 -0
  366. package/src/builtin-editors.ts +56 -0
  367. package/src/columns.ts +3 -2
  368. package/src/core.ts +10 -0
  369. package/src/countries.ts +25 -0
  370. package/src/createAccordion.svelte.ts +126 -0
  371. package/src/createAutocomplete.svelte.ts +18 -1
  372. package/src/createButtonGroup.svelte.ts +166 -0
  373. package/src/createCalendar.svelte.ts +6 -0
  374. package/src/createCheckbox.svelte.ts +23 -1
  375. package/src/createColorInput.svelte.ts +20 -0
  376. package/src/createCombobox.svelte.ts +22 -2
  377. package/src/createCountryInput.svelte.ts +21 -2
  378. package/src/createDateTimePicker.svelte.ts +23 -2
  379. package/src/createDropdownList.svelte.ts +31 -2
  380. package/src/createFileUpload.svelte.ts +144 -0
  381. package/src/createListbox.svelte.ts +35 -3
  382. package/src/createMaskedInput.svelte.ts +18 -1
  383. package/src/createNumberInput.svelte.ts +2 -0
  384. package/src/createPasswordInput.svelte.ts +31 -5
  385. package/src/createPhoneInput.svelte.ts +38 -4
  386. package/src/createPopoverSelect.svelte.ts +208 -0
  387. package/src/createRadioGroup.svelte.ts +23 -1
  388. package/src/createRating.svelte.ts +26 -1
  389. package/src/createSlider.svelte.ts +14 -3
  390. package/src/createSplitter.svelte.ts +119 -0
  391. package/src/createSwitch.svelte.ts +26 -3
  392. package/src/createTabs.svelte.ts +20 -3
  393. package/src/createTagsInput.svelte.ts +24 -3
  394. package/src/createToggle.svelte.ts +25 -2
  395. package/src/createTree.svelte.ts +104 -14
  396. package/src/debounce.test.ts +50 -0
  397. package/src/debounce.ts +41 -0
  398. package/src/duration.test.ts +41 -0
  399. package/src/duration.ts +51 -0
  400. package/src/editor-contract.ts +87 -8
  401. package/src/editor-registry.grid.test.ts +98 -0
  402. package/src/editor-registry.test.ts +86 -0
  403. package/src/editor-registry.ts +106 -0
  404. package/src/editors/cell-editors.ts +10 -1
  405. package/src/fuzzy.test.ts +40 -0
  406. package/src/fuzzy.ts +39 -0
  407. package/src/grid-select.test.ts +28 -0
  408. package/src/grid-select.ts +20 -0
  409. package/src/index.ts +150 -5
  410. package/src/keyboard-handlers.ts +20 -0
  411. package/src/list-option.ts +74 -0
  412. package/src/list-power.test.ts +71 -0
  413. package/src/otp.test.ts +28 -0
  414. package/src/otp.ts +28 -0
  415. package/src/overlays.test.ts +85 -0
  416. package/src/paginate.test.ts +41 -0
  417. package/src/paginate.ts +61 -0
  418. package/src/phone-valid.test.ts +26 -0
  419. package/src/popover.ts +31 -3
  420. package/src/progress.test.ts +55 -0
  421. package/src/recurrence.test.ts +91 -0
  422. package/src/recurrence.ts +111 -0
  423. package/src/server-data-source.ts +102 -0
  424. package/src/server-group-model.test.ts +294 -0
  425. package/src/server-group-model.ts +370 -0
  426. package/src/svgrid.responsive.test.ts +72 -0
  427. package/src/toast-store.svelte.ts +148 -0
  428. package/src/toast-store.test.ts +86 -0
  429. package/src/tree-edit.test.ts +49 -0
  430. package/src/tree-filter.test.ts +56 -0
  431. package/src/tree-select.test.ts +48 -0
  432. package/src/tree-select.ts +62 -0
  433. package/src/ui-app.test.ts +130 -0
  434. package/src/ui-app.types.ts +88 -0
  435. package/src/ui-display.test.ts +79 -0
  436. package/src/ui-inputs.test.ts +1 -1
  437. package/src/ui-status.test.ts +141 -0
  438. package/src/ui-tier1.test.ts +142 -0
  439. package/src/ui-tier2.test.ts +124 -0
  440. package/src/ui-tier3.test.ts +139 -0
  441. package/src/validators.test.ts +58 -0
  442. package/src/validators.ts +105 -0
  443. package/src/virtual.test.ts +72 -0
  444. package/src/virtual.ts +67 -0
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Component tests for the Tier-3 batch: SvRichText, SvCarousel, SvTour.
3
+ * (execCommand-driven formatting is a no-op in jsdom, so RichText tests cover
4
+ * structure + value/onChange, not the rendered rich output.)
5
+ */
6
+ import { afterEach, describe, expect, it, vi } from 'vitest'
7
+ import { mount, unmount, flushSync } from 'svelte'
8
+ import SvRichText from './SvRichText.svelte'
9
+ import SvCarousel from './SvCarousel.svelte'
10
+ import SvTour from './SvTour.svelte'
11
+
12
+ function mountOn(Comp: any, props: Record<string, unknown>) {
13
+ const target = document.createElement('div')
14
+ document.body.appendChild(target)
15
+ const app = mount(Comp, { target, props: props as any })
16
+ flushSync()
17
+ return { target, destroy: () => { unmount(app); target.remove() } }
18
+ }
19
+
20
+ afterEach(() => { document.body.innerHTML = '' })
21
+
22
+ describe('SvRichText', () => {
23
+ it('renders a toolbar and an editable region seeded from value', () => {
24
+ const { target, destroy } = mountOn(SvRichText, { value: '<p>Hello</p>' })
25
+ try {
26
+ expect(target.querySelector('.sv-rt__toolbar')).not.toBeNull()
27
+ expect(target.querySelectorAll('.sv-rt__btn').length).toBeGreaterThan(6)
28
+ const body = target.querySelector<HTMLElement>('.sv-rt__body')!
29
+ expect(body.getAttribute('contenteditable')).toBe('true')
30
+ expect(body.innerHTML).toBe('<p>Hello</p>')
31
+ } finally { destroy() }
32
+ })
33
+
34
+ it('emits onChange on input', () => {
35
+ const onChange = vi.fn()
36
+ const { target, destroy } = mountOn(SvRichText, { value: '', onChange })
37
+ try {
38
+ const body = target.querySelector<HTMLElement>('.sv-rt__body')!
39
+ body.innerHTML = '<b>hi</b>'
40
+ body.dispatchEvent(new Event('input', { bubbles: true }))
41
+ expect(onChange).toHaveBeenCalledWith('<b>hi</b>')
42
+ } finally { destroy() }
43
+ })
44
+
45
+ it('readonly hides the toolbar and disables editing', () => {
46
+ const { target, destroy } = mountOn(SvRichText, { value: 'x', readonly: true })
47
+ try {
48
+ expect(target.querySelector('.sv-rt__toolbar')).toBeNull()
49
+ expect(target.querySelector('.sv-rt__body')!.getAttribute('contenteditable')).toBe('false')
50
+ } finally { destroy() }
51
+ })
52
+
53
+ it('honours a custom tools list', () => {
54
+ const { target, destroy } = mountOn(SvRichText, { value: '', tools: ['bold', 'italic'] })
55
+ try {
56
+ expect(target.querySelectorAll('.sv-rt__btn')).toHaveLength(2)
57
+ } finally { destroy() }
58
+ })
59
+ })
60
+
61
+ describe('SvCarousel', () => {
62
+ const slideProps = { count: 3, slide: undefined as any }
63
+ // Provide a minimal slide snippet via a wrapper is awkward; render count-only.
64
+
65
+ it('renders one slide element per count with arrows + dots', () => {
66
+ const { target, destroy } = mountOn(SvCarousel, { ...slideProps })
67
+ try {
68
+ expect(target.querySelectorAll('.sv-carousel__slide')).toHaveLength(3)
69
+ expect(target.querySelectorAll('.sv-carousel__dot')).toHaveLength(3)
70
+ expect(target.querySelector('.sv-carousel__arrow.is-next')).not.toBeNull()
71
+ } finally { destroy() }
72
+ })
73
+
74
+ it('next advances the active dot (with loop wrap)', () => {
75
+ const { target, destroy } = mountOn(SvCarousel, { count: 3, current: 0 })
76
+ try {
77
+ target.querySelector<HTMLButtonElement>('.sv-carousel__arrow.is-next')!.click()
78
+ flushSync()
79
+ const active = [...target.querySelectorAll('.sv-carousel__dot')].findIndex((d) => d.classList.contains('is-active'))
80
+ expect(active).toBe(1)
81
+ } finally { destroy() }
82
+ })
83
+
84
+ it('clicking a dot jumps to that slide', () => {
85
+ const { target, destroy } = mountOn(SvCarousel, { count: 4, current: 0 })
86
+ try {
87
+ target.querySelectorAll<HTMLButtonElement>('.sv-carousel__dot')[2]!.click()
88
+ flushSync()
89
+ expect(target.querySelectorAll('.sv-carousel__dot')[2]!.classList.contains('is-active')).toBe(true)
90
+ } finally { destroy() }
91
+ })
92
+ })
93
+
94
+ describe('SvTour', () => {
95
+ const steps = [
96
+ { title: 'Welcome', content: 'Intro.' },
97
+ { title: 'Step two', content: 'More.' },
98
+ { title: 'Done', content: 'Finish.' },
99
+ ]
100
+
101
+ it('shows the first step with a counter and Next', () => {
102
+ const { destroy } = mountOn(SvTour, { open: true, steps })
103
+ try {
104
+ expect(document.body.querySelector('.sv-tour__title')!.textContent).toBe('Welcome')
105
+ expect(document.body.querySelector('.sv-tour__count')!.textContent).toBe('1 / 3')
106
+ expect(document.body.querySelector('.sv-tour__back')).toBeNull() // no Back on step 1
107
+ } finally { destroy() }
108
+ })
109
+
110
+ it('Next advances, Back returns, Done finishes', () => {
111
+ const onFinish = vi.fn()
112
+ const { destroy } = mountOn(SvTour, { open: true, steps, onFinish })
113
+ try {
114
+ document.body.querySelector<HTMLButtonElement>('.sv-tour__next')!.click()
115
+ flushSync()
116
+ expect(document.body.querySelector('.sv-tour__title')!.textContent).toBe('Step two')
117
+ document.body.querySelector<HTMLButtonElement>('.sv-tour__back')!.click()
118
+ flushSync()
119
+ expect(document.body.querySelector('.sv-tour__title')!.textContent).toBe('Welcome')
120
+ // to the end
121
+ document.body.querySelector<HTMLButtonElement>('.sv-tour__next')!.click(); flushSync()
122
+ document.body.querySelector<HTMLButtonElement>('.sv-tour__next')!.click(); flushSync()
123
+ document.body.querySelector<HTMLButtonElement>('.sv-tour__next')!.click(); flushSync() // Done
124
+ expect(onFinish).toHaveBeenCalled()
125
+ expect(document.body.querySelector('.sv-tour__pop')).toBeNull()
126
+ } finally { destroy() }
127
+ })
128
+
129
+ it('Skip fires onSkip and closes', () => {
130
+ const onSkip = vi.fn()
131
+ const { destroy } = mountOn(SvTour, { open: true, steps, onSkip })
132
+ try {
133
+ document.body.querySelector<HTMLButtonElement>('.sv-tour__skip')!.click()
134
+ flushSync()
135
+ expect(onSkip).toHaveBeenCalled()
136
+ expect(document.body.querySelector('.sv-tour__pop')).toBeNull()
137
+ } finally { destroy() }
138
+ })
139
+ })
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Tests for the declarative validation-rule library: each builder skips empty
3
+ * values (except required), enforces its constraint, honors a custom message,
4
+ * and cross-field compare reads sibling values. runRules returns the first error.
5
+ */
6
+ import { describe, expect, it } from 'vitest'
7
+ import { rules, runRules, isEmptyValue } from './validators'
8
+
9
+ describe('validators', () => {
10
+ it('required flags empty values only', () => {
11
+ const r = rules.required()
12
+ expect(r('')).toBeTruthy()
13
+ expect(r(null)).toBeTruthy()
14
+ expect(r([])).toBeTruthy()
15
+ expect(r('x')).toBeNull()
16
+ expect(isEmptyValue([])).toBe(true)
17
+ })
18
+
19
+ it('format rules pass when empty, fail on bad input', () => {
20
+ expect(rules.email()('')).toBeNull()
21
+ expect(rules.email()('nope')).toBeTruthy()
22
+ expect(rules.email()('a@b.co')).toBeNull()
23
+ expect(rules.url()('example.com')).toBeNull()
24
+ expect(rules.pattern(/^\d+$/)('12a')).toBeTruthy()
25
+ expect(rules.zipCode()('90210')).toBeNull()
26
+ })
27
+
28
+ it('numeric ranges + lengths', () => {
29
+ expect(rules.min(10)(5)).toBeTruthy()
30
+ expect(rules.max(10)(11)).toBeTruthy()
31
+ expect(rules.range(1, 5)(3)).toBeNull()
32
+ expect(rules.range(1, 5)(9)).toBeTruthy()
33
+ expect(rules.minLength(3)('ab')).toBeTruthy()
34
+ expect(rules.stringLength(2, 4)('abc')).toBeNull()
35
+ expect(rules.integer()(3.5)).toBeTruthy()
36
+ expect(rules.oneOf(['a', 'b'])('c')).toBeTruthy()
37
+ })
38
+
39
+ it('compare reads a sibling field', () => {
40
+ const r = rules.compare('password')
41
+ expect(r('x', { password: 'x' })).toBeNull()
42
+ expect(r('y', { password: 'x' })).toBeTruthy()
43
+ expect(rules.compare('age', '>')(20, { age: 18 })).toBeNull()
44
+ })
45
+
46
+ it('custom message overrides the default', () => {
47
+ expect(rules.required({ message: 'Pick one' })('')).toBe('Pick one')
48
+ expect(rules.min(5, { message: 'too small' })(1)).toBe('too small')
49
+ })
50
+
51
+ it('runRules returns the first failing message in order', () => {
52
+ const list = [rules.required(), rules.minLength(4), rules.pattern(/[A-Z]/, { message: 'needs a capital' })]
53
+ expect(runRules('', list)).toBe('This field is required')
54
+ expect(runRules('ab', list)).toContain('at least 4')
55
+ expect(runRules('abcd', list)).toBe('needs a capital')
56
+ expect(runRules('Abcd', list)).toBeNull()
57
+ })
58
+ })
@@ -0,0 +1,105 @@
1
+ /**
2
+ * validators - a declarative, framework-free validation-rule library for SvForm
3
+ * (and any editor). Each rule builder returns a `Validator`: a pure function
4
+ * `(value, allValues?) => errorMessage | null`. Compose them per field; the form
5
+ * runs them in order and shows the first failure. Parity: Smart `smart.validator`
6
+ * (required / email / pattern / min / max / range / stringLength / compare ...).
7
+ *
8
+ * ```ts
9
+ * import { rules } from '@svgrid/grid'
10
+ * const field = { name: 'email', label: 'Email', rules: [rules.required(), rules.email()] }
11
+ * ```
12
+ */
13
+ export type Validator = (value: any, values?: Record<string, any>) => string | null | undefined
14
+ export type RuleOptions = { message?: string }
15
+ export type CompareOp = '==' | '===' | '!=' | '<' | '<=' | '>' | '>='
16
+
17
+ const EMAIL_RE = /^[^@\s]+@[^@\s]+\.[^@\s]+$/
18
+ const URL_RE = /^(https?:\/\/)?([\w-]+\.)+[\w-]{2,}(\/\S*)?$/i
19
+ const ZIP_RE = /^\d{5}(-\d{4})?$/
20
+
21
+ /** Empty for validation purposes: null/undefined, '', or an empty array. */
22
+ export function isEmptyValue(v: any): boolean {
23
+ return v == null || v === '' || (Array.isArray(v) && v.length === 0)
24
+ }
25
+
26
+ function compareOk(a: any, b: any, op: CompareOp): boolean {
27
+ switch (op) {
28
+ case '==': return a == b // eslint-disable-line eqeqeq
29
+ case '===': return a === b
30
+ case '!=': return a != b // eslint-disable-line eqeqeq
31
+ case '<': return Number(a) < Number(b)
32
+ case '<=': return Number(a) <= Number(b)
33
+ case '>': return Number(a) > Number(b)
34
+ case '>=': return Number(a) >= Number(b)
35
+ }
36
+ }
37
+
38
+ /** The declarative rule builders. Every builder skips empty values (except
39
+ * `required`) so optional fields validate format only when filled. */
40
+ export const rules = {
41
+ required: (o?: RuleOptions): Validator => (v) =>
42
+ isEmptyValue(v) ? o?.message ?? 'This field is required' : null,
43
+
44
+ email: (o?: RuleOptions): Validator => (v) =>
45
+ isEmptyValue(v) || EMAIL_RE.test(String(v)) ? null : o?.message ?? 'Enter a valid email address',
46
+
47
+ url: (o?: RuleOptions): Validator => (v) =>
48
+ isEmptyValue(v) || URL_RE.test(String(v)) ? null : o?.message ?? 'Enter a valid URL',
49
+
50
+ zipCode: (o?: RuleOptions): Validator => (v) =>
51
+ isEmptyValue(v) || ZIP_RE.test(String(v)) ? null : o?.message ?? 'Enter a valid ZIP code',
52
+
53
+ pattern: (re: RegExp, o?: RuleOptions): Validator => (v) =>
54
+ isEmptyValue(v) || re.test(String(v)) ? null : o?.message ?? 'Invalid format',
55
+
56
+ numeric: (o?: RuleOptions): Validator => (v) =>
57
+ isEmptyValue(v) || !isNaN(Number(v)) ? null : o?.message ?? 'Must be a number',
58
+
59
+ integer: (o?: RuleOptions): Validator => (v) =>
60
+ isEmptyValue(v) || Number.isInteger(Number(v)) ? null : o?.message ?? 'Must be a whole number',
61
+
62
+ min: (n: number, o?: RuleOptions): Validator => (v) =>
63
+ isEmptyValue(v) || Number(v) >= n ? null : o?.message ?? `Must be at least ${n}`,
64
+
65
+ max: (n: number, o?: RuleOptions): Validator => (v) =>
66
+ isEmptyValue(v) || Number(v) <= n ? null : o?.message ?? `Must be at most ${n}`,
67
+
68
+ range: (lo: number, hi: number, o?: RuleOptions): Validator => (v) =>
69
+ isEmptyValue(v) || (Number(v) >= lo && Number(v) <= hi) ? null : o?.message ?? `Must be between ${lo} and ${hi}`,
70
+
71
+ minLength: (n: number, o?: RuleOptions): Validator => (v) =>
72
+ isEmptyValue(v) || String(v).length >= n ? null : o?.message ?? `Must be at least ${n} characters`,
73
+
74
+ maxLength: (n: number, o?: RuleOptions): Validator => (v) =>
75
+ isEmptyValue(v) || String(v).length <= n ? null : o?.message ?? `Must be at most ${n} characters`,
76
+
77
+ stringLength: (lo: number, hi: number, o?: RuleOptions): Validator => (v) =>
78
+ isEmptyValue(v) || (String(v).length >= lo && String(v).length <= hi)
79
+ ? null
80
+ : o?.message ?? `Must be ${lo}-${hi} characters`,
81
+
82
+ oneOf: (allowed: ReadonlyArray<string | number>, o?: RuleOptions): Validator => (v) =>
83
+ isEmptyValue(v) || allowed.includes(v) ? null : o?.message ?? 'Invalid choice',
84
+
85
+ /** Compare against another field's value (cross-field): e.g. confirm password. */
86
+ compare: (otherField: string, op: CompareOp = '===', o?: RuleOptions): Validator => (v, values) =>
87
+ compareOk(v, values?.[otherField], op) ? null : o?.message ?? 'Values do not match',
88
+
89
+ /** Wrap an arbitrary predicate/function as a rule. */
90
+ custom: (fn: Validator): Validator => fn,
91
+ }
92
+
93
+ /** Run a list of rules against a value; return the first error message, or null. */
94
+ export function runRules(
95
+ value: any,
96
+ ruleList: ReadonlyArray<Validator> | undefined,
97
+ values?: Record<string, any>,
98
+ ): string | null {
99
+ if (!ruleList) return null
100
+ for (const rule of ruleList) {
101
+ const err = rule(value, values)
102
+ if (err) return err
103
+ }
104
+ return null
105
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Tests for the fixed-row windowing helper and SvListBox `virtual` mode: a huge
3
+ * option set renders only a small window (plus spacer rows), and itemTemplate
4
+ * customizes the row.
5
+ */
6
+ import { describe, expect, it } from 'vitest'
7
+ import { mount, unmount, flushSync, createRawSnippet } from 'svelte'
8
+ import SvListBox from './SvListBox.svelte'
9
+ import { virtualRange, scrollToIndex } from './virtual'
10
+
11
+ describe('virtualRange', () => {
12
+ it('returns the visible window + spacer heights for a fixed-row list', () => {
13
+ const r = virtualRange({ scrollTop: 0, viewportHeight: 320, rowHeight: 32, count: 1000, overscan: 2 })
14
+ expect(r.start).toBe(0)
15
+ expect(r.totalHeight).toBe(32000)
16
+ expect(r.padTop).toBe(0)
17
+ // ~10 visible + 2 overscan.
18
+ expect(r.end).toBeLessThanOrEqual(14)
19
+ expect(r.padBottom).toBe((1000 - r.end) * 32)
20
+ })
21
+ it('scrolls the window when scrolled down', () => {
22
+ const r = virtualRange({ scrollTop: 3200, viewportHeight: 320, rowHeight: 32, count: 1000, overscan: 2 })
23
+ expect(r.start).toBe(98) // floor(3200/32)=100, minus overscan 2
24
+ expect(r.padTop).toBe(98 * 32)
25
+ })
26
+ it('handles empty + zero-height gracefully', () => {
27
+ expect(virtualRange({ scrollTop: 0, viewportHeight: 100, rowHeight: 32, count: 0 })).toMatchObject({ start: 0, end: 0, totalHeight: 0 })
28
+ expect(virtualRange({ scrollTop: 0, viewportHeight: 100, rowHeight: 0, count: 5 }).totalHeight).toBe(0)
29
+ })
30
+ })
31
+
32
+ describe('scrollToIndex', () => {
33
+ it('scrolls up to an item above and down to an item below the viewport', () => {
34
+ expect(scrollToIndex(2, 320, 320, 32)).toBe(64) // item 2 above -> its top
35
+ expect(scrollToIndex(20, 0, 320, 32)).toBe(20 * 32 + 32 - 320) // item 20 below -> bottom-align
36
+ expect(scrollToIndex(5, 100, 320, 32)).toBe(100) // already visible -> unchanged
37
+ })
38
+ })
39
+
40
+ describe('SvListBox virtual', () => {
41
+ it('renders only a small window of a huge list', () => {
42
+ const options = Array.from({ length: 5000 }, (_, i) => ({ value: i, label: `Item ${i}` }))
43
+ const target = document.createElement('div')
44
+ document.body.appendChild(target)
45
+ const app = mount(SvListBox, { target, props: { options, virtual: true } as any })
46
+ flushSync()
47
+ try {
48
+ const rendered = target.querySelectorAll('.sv-listbox__opt').length
49
+ expect(rendered).toBeGreaterThan(0)
50
+ expect(rendered).toBeLessThan(60) // nowhere near 5000
51
+ // A fixed-height sizer keeps the scrollbar honest.
52
+ const sizer = target.querySelector<HTMLElement>('.sv-listbox__sizer')!
53
+ expect(sizer).not.toBeNull()
54
+ expect(sizer.style.height).toBe(`${5000 * 32}px`)
55
+ } finally { unmount(app); target.remove() }
56
+ })
57
+
58
+ it('renders a custom itemTemplate per option', () => {
59
+ const options = [{ value: 'a', label: 'Apple' }, { value: 'b', label: 'Banana' }]
60
+ const itemTemplate = createRawSnippet((opt: () => { label: string }) => ({
61
+ render: () => `<span class="tpl">${opt().label.toUpperCase()}</span>`,
62
+ }))
63
+ const target = document.createElement('div')
64
+ document.body.appendChild(target)
65
+ const app = mount(SvListBox, { target, props: { options, itemTemplate } as any })
66
+ flushSync()
67
+ try {
68
+ const tpls = [...target.querySelectorAll('.tpl')].map((n) => n.textContent)
69
+ expect(tpls).toEqual(['APPLE', 'BANANA'])
70
+ } finally { unmount(app); target.remove() }
71
+ })
72
+ })
package/src/virtual.ts ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * virtual - a tiny, framework-free fixed-row windowing helper shared by the
3
+ * selection controls so their option lists scale to tens of thousands of rows
4
+ * without rendering them all. The renderer measures the viewport + tracks
5
+ * scrollTop (DOM concerns) and passes them in; this returns the slice to render
6
+ * plus the top/bottom spacer heights that keep the scrollbar honest.
7
+ */
8
+ export type VirtualRange = {
9
+ /** First item index to render (inclusive). */
10
+ start: number
11
+ /** Last item index to render (exclusive). */
12
+ end: number
13
+ /** Spacer height above the rendered slice (px). */
14
+ padTop: number
15
+ /** Spacer height below the rendered slice (px). */
16
+ padBottom: number
17
+ /** Total scrollable height of all rows (px). */
18
+ totalHeight: number
19
+ }
20
+
21
+ export type VirtualParams = {
22
+ scrollTop: number
23
+ viewportHeight: number
24
+ rowHeight: number
25
+ count: number
26
+ /** Extra rows rendered above/below the viewport to avoid blank flashes. */
27
+ overscan?: number
28
+ }
29
+
30
+ /** Compute the visible window for a fixed-row-height list (pure). */
31
+ export function virtualRange(params: VirtualParams): VirtualRange {
32
+ const { scrollTop, viewportHeight, rowHeight, count } = params
33
+ const overscan = params.overscan ?? 6
34
+ const total = Math.max(0, count) * Math.max(0, rowHeight)
35
+ if (rowHeight <= 0 || count <= 0) {
36
+ return { start: 0, end: Math.max(0, count), padTop: 0, padBottom: 0, totalHeight: total }
37
+ }
38
+ const first = Math.floor(Math.max(0, scrollTop) / rowHeight)
39
+ const start = Math.max(0, first - overscan)
40
+ const visible = Math.max(1, Math.ceil(Math.max(0, viewportHeight) / rowHeight))
41
+ const end = Math.min(count, first + visible + overscan)
42
+ return {
43
+ start,
44
+ end,
45
+ padTop: start * rowHeight,
46
+ padBottom: Math.max(0, (count - end) * rowHeight),
47
+ totalHeight: total,
48
+ }
49
+ }
50
+
51
+ /**
52
+ * The scrollTop that brings item `index` fully into a fixed-row viewport,
53
+ * scrolling the least amount (returns the current scrollTop if already visible).
54
+ * Pure - the renderer applies it to the element.
55
+ */
56
+ export function scrollToIndex(
57
+ index: number,
58
+ scrollTop: number,
59
+ viewportHeight: number,
60
+ rowHeight: number,
61
+ ): number {
62
+ const top = index * rowHeight
63
+ const bottom = top + rowHeight
64
+ if (top < scrollTop) return top
65
+ if (bottom > scrollTop + viewportHeight) return bottom - viewportHeight
66
+ return scrollTop
67
+ }