@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
@@ -1,289 +1,289 @@
1
- import { describe, expect, it, vi } from 'vitest'
2
- import { createServerDataSource, type ServerDataSource } from './server-data-source'
3
-
4
- type Row = { id: number }
5
-
6
- function makeSource(total = 250, delay = 0): ServerDataSource<Row> & { calls: number } {
7
- const all: Row[] = Array.from({ length: total }, (_, i) => ({ id: i }))
8
- return {
9
- calls: 0,
10
- async getRows(req) {
11
- this.calls += 1
12
- if (delay) await new Promise((r) => setTimeout(r, delay))
13
- // server applies sort: reverse when desc on 'id'
14
- let rows = all
15
- if (req.sortModel[0]?.id === 'id' && req.sortModel[0]?.desc) rows = [...all].reverse()
16
- return { rows: rows.slice(req.startRow, req.endRow), rowCount: total }
17
- },
18
- }
19
- }
20
-
21
- const flush = () => new Promise((r) => setTimeout(r, 0))
22
-
23
- describe('createServerDataSource', () => {
24
- it('fetches a page and reports total + pageCount', async () => {
25
- const src = makeSource(250)
26
- const states: any[] = []
27
- const ctl = createServerDataSource(src, { pageSize: 50, onChange: (s) => states.push(s) })
28
- ctl.refresh()
29
- await flush()
30
- const last = ctl.getState()
31
- expect(last.rows).toHaveLength(50)
32
- expect(last.total).toBe(250)
33
- expect(last.pageCount).toBe(5)
34
- expect(last.loading).toBe(false)
35
- // emitted a loading=true then loading=false
36
- expect(states.some((s) => s.loading)).toBe(true)
37
- })
38
-
39
- it('dispose() during an in-flight fetch clears the loading state (#83)', async () => {
40
- const src = makeSource(250, 20) // 20ms delay keeps the fetch in-flight
41
- const ctl = createServerDataSource(src, { pageSize: 50, onChange: () => {} })
42
- ctl.refresh()
43
- expect(ctl.getState().loading).toBe(true)
44
- ctl.dispose()
45
- expect(ctl.getState().loading).toBe(false)
46
- await new Promise((r) => setTimeout(r, 30)) // let the fetch settle
47
- expect(ctl.getState().loading).toBe(false) // stays cleared, not stuck
48
- })
49
-
50
- it('setPage fetches the right slice', async () => {
51
- const src = makeSource(250)
52
- const ctl = createServerDataSource(src, { pageSize: 50, onChange: () => {} })
53
- ctl.setPage(2)
54
- await flush()
55
- expect(ctl.getState().rows[0]).toEqual({ id: 100 })
56
- })
57
-
58
- it('setSort resets to page 0 and re-fetches', async () => {
59
- const src = makeSource(250)
60
- const ctl = createServerDataSource(src, { pageSize: 50, onChange: () => {} })
61
- ctl.setPage(2)
62
- await flush()
63
- ctl.setSort([{ id: 'id', desc: true }])
64
- await flush()
65
- const s = ctl.getState()
66
- expect(s.pageIndex).toBe(0)
67
- expect(s.rows[0]).toEqual({ id: 249 }) // reversed
68
- })
69
-
70
- it('ignores a stale response when a newer request supersedes it', async () => {
71
- // slow source: first call (page 0) resolves AFTER the second (page 1)
72
- const all = Array.from({ length: 100 }, (_, i) => ({ id: i }))
73
- let call = 0
74
- const src: ServerDataSource<Row> = {
75
- getRows: vi.fn(async (req) => {
76
- call += 1
77
- const delay = call === 1 ? 40 : 5 // first call is slow
78
- await new Promise((r) => setTimeout(r, delay))
79
- return { rows: all.slice(req.startRow, req.endRow), rowCount: 100 }
80
- }),
81
- }
82
- const ctl = createServerDataSource(src, { pageSize: 10, onChange: () => {} })
83
- ctl.refresh() // page 0 (slow)
84
- ctl.setPage(3) // page 30 (fast) - should win
85
- await new Promise((r) => setTimeout(r, 80))
86
- expect(ctl.getState().rows[0]).toEqual({ id: 30 })
87
- })
88
-
89
- it('dispose stops further updates', async () => {
90
- const src = makeSource(50, 20)
91
- const onChange = vi.fn()
92
- const ctl = createServerDataSource(src, { pageSize: 10, onChange })
93
- ctl.refresh()
94
- ctl.dispose()
95
- await new Promise((r) => setTimeout(r, 40))
96
- // only the synchronous loading=true emit happened before dispose
97
- expect(ctl.getState().rows).toHaveLength(0)
98
- })
99
- })
100
-
101
- describe('createServerDataSource mutations', () => {
102
- type URow = { id: number; name: string }
103
-
104
- function writable() {
105
- let store: URow[] = [
106
- { id: 0, name: 'a' },
107
- { id: 1, name: 'b' },
108
- { id: 2, name: 'c' },
109
- ]
110
- return {
111
- getRows: vi.fn(async (req: { startRow: number; endRow: number }) => ({
112
- rows: store.slice(req.startRow, req.endRow),
113
- rowCount: store.length,
114
- })),
115
- createRow: vi.fn(async (input: Partial<URow>) => {
116
- const row: URow = { id: store.length, name: input.name ?? '' }
117
- store = [...store, row]
118
- return row
119
- }),
120
- updateRow: vi.fn(async (id: string, patch: Partial<URow>) => {
121
- store = store.map((r) => (String(r.id) === id ? { ...r, ...patch } : r))
122
- return store.find((r) => String(r.id) === id)!
123
- }),
124
- deleteRow: vi.fn(async (id: string) => {
125
- store = store.filter((r) => String(r.id) !== id)
126
- }),
127
- } as unknown as ServerDataSource<URow> & {
128
- getRows: ReturnType<typeof vi.fn>
129
- createRow: ReturnType<typeof vi.fn>
130
- updateRow: ReturnType<typeof vi.fn>
131
- deleteRow: ReturnType<typeof vi.fn>
132
- }
133
- }
134
-
135
- it('createRow writes through and refreshes the current page', async () => {
136
- const src = writable()
137
- const ctl = createServerDataSource(src, { pageSize: 50, onChange: () => {} })
138
- ctl.refresh()
139
- await flush()
140
- const created = await ctl.createRow({ name: 'z' })
141
- expect(created).toEqual({ id: 3, name: 'z' })
142
- expect(src.createRow).toHaveBeenCalledWith({ name: 'z' })
143
- // the follow-up refresh re-fetched, so total + rows reflect the write
144
- expect(ctl.getState().total).toBe(4)
145
- expect(ctl.getState().rows.some((r) => r.name === 'z')).toBe(true)
146
- })
147
-
148
- it('toggles `saving` around a mutation', async () => {
149
- const src = writable()
150
- const states: Array<{ saving: boolean }> = []
151
- const ctl = createServerDataSource(src, { pageSize: 50, onChange: (s) => states.push(s) })
152
- const p = ctl.createRow({ name: 'x' })
153
- expect(ctl.getState().saving).toBe(true) // synchronous emit before the await
154
- await p
155
- expect(ctl.getState().saving).toBe(false)
156
- expect(states.some((s) => s.saving)).toBe(true)
157
- })
158
-
159
- it('updateRow and deleteRow call through and refresh', async () => {
160
- const src = writable()
161
- const ctl = createServerDataSource(src, { pageSize: 50, onChange: () => {} })
162
- ctl.refresh()
163
- await flush()
164
- const updated = await ctl.updateRow('1', { name: 'B!' })
165
- expect(updated).toEqual({ id: 1, name: 'B!' })
166
- expect(src.updateRow).toHaveBeenCalledWith('1', { name: 'B!' })
167
- expect(ctl.getState().rows.find((r) => r.id === 1)?.name).toBe('B!')
168
-
169
- await ctl.deleteRow('0')
170
- expect(src.deleteRow).toHaveBeenCalledWith('0')
171
- expect(ctl.getState().total).toBe(2)
172
- expect(ctl.getState().rows.some((r) => r.id === 0)).toBe(false)
173
- })
174
-
175
- it('rejects when the source has no matching writer', async () => {
176
- const readonly: ServerDataSource<URow> = {
177
- getRows: async () => ({ rows: [], rowCount: 0 }),
178
- }
179
- const ctl = createServerDataSource(readonly, { onChange: () => {} })
180
- await expect(ctl.createRow({ name: 'x' })).rejects.toThrow(/does not implement createRow/)
181
- await expect(ctl.updateRow('1', {})).rejects.toThrow(/does not implement updateRow/)
182
- await expect(ctl.deleteRow('1')).rejects.toThrow(/does not implement deleteRow/)
183
- })
184
-
185
- it('rejects mutations after dispose, without calling the source', async () => {
186
- const src = writable()
187
- const ctl = createServerDataSource(src, { onChange: () => {} })
188
- ctl.dispose()
189
- await expect(ctl.deleteRow('0')).rejects.toThrow(/disposed/)
190
- expect(src.deleteRow).not.toHaveBeenCalled()
191
- })
192
-
193
- it('leaves read state untouched when a mutation fails', async () => {
194
- const src = writable()
195
- src.createRow.mockRejectedValueOnce(new Error('boom'))
196
- const ctl = createServerDataSource(src, { pageSize: 50, onChange: () => {} })
197
- ctl.refresh()
198
- await flush()
199
- await expect(ctl.createRow({ name: 'x' })).rejects.toThrow('boom')
200
- expect(ctl.getState().saving).toBe(false)
201
- expect(ctl.getState().total).toBe(3) // unchanged; no refresh on failure
202
- })
203
- })
204
-
205
- describe('createServerDataSource optimistic mutations', () => {
206
- type URow = { id: number; name: string }
207
- const opts = (onChange: (s: any) => void = () => {}) => ({
208
- pageSize: 50,
209
- optimistic: true,
210
- getRowId: (r: URow) => String(r.id),
211
- onChange,
212
- })
213
-
214
- function writable() {
215
- let store: URow[] = [
216
- { id: 0, name: 'a' },
217
- { id: 1, name: 'b' },
218
- { id: 2, name: 'c' },
219
- ]
220
- return {
221
- getRows: vi.fn(async (req: { startRow: number; endRow: number }) => ({
222
- rows: store.slice(req.startRow, req.endRow),
223
- rowCount: store.length,
224
- })),
225
- updateRow: vi.fn(async (id: string, patch: Partial<URow>) => {
226
- store = store.map((r) => (String(r.id) === id ? { ...r, ...patch } : r))
227
- return store.find((r) => String(r.id) === id)!
228
- }),
229
- deleteRow: vi.fn(async (id: string) => {
230
- store = store.filter((r) => String(r.id) !== id)
231
- }),
232
- } as any
233
- }
234
-
235
- it('applies an update to local rows immediately, before the server resolves', async () => {
236
- const src = writable()
237
- src.updateRow.mockImplementationOnce(
238
- (id: string, patch: any) => new Promise((r) => setTimeout(() => r({ id: Number(id), ...patch }), 30)),
239
- )
240
- const ctl = createServerDataSource(src, opts())
241
- ctl.refresh()
242
- await flush()
243
- const p = ctl.updateRow('1', { name: 'B!' })
244
- // synchronously reflected, no refetch
245
- expect(ctl.getState().rows.find((r) => r.id === 1)?.name).toBe('B!')
246
- expect(ctl.getState().saving).toBe(true)
247
- await p
248
- expect(ctl.getState().saving).toBe(false)
249
- // getRows only called once (the initial refresh) - no refetch for the update
250
- expect(src.getRows).toHaveBeenCalledTimes(1)
251
- })
252
-
253
- it('rolls an update back on error', async () => {
254
- const src = writable()
255
- src.updateRow.mockRejectedValueOnce(new Error('nope'))
256
- const ctl = createServerDataSource(src, opts())
257
- ctl.refresh()
258
- await flush()
259
- await expect(ctl.updateRow('1', { name: 'B!' })).rejects.toThrow('nope')
260
- expect(ctl.getState().rows.find((r) => r.id === 1)?.name).toBe('b') // restored
261
- })
262
-
263
- it('removes a row immediately on delete and decrements total, restoring on error', async () => {
264
- const src = writable()
265
- const ctl = createServerDataSource(src, opts())
266
- ctl.refresh()
267
- await flush()
268
-
269
- const p = ctl.deleteRow('0')
270
- expect(ctl.getState().rows.some((r) => r.id === 0)).toBe(false)
271
- expect(ctl.getState().total).toBe(2)
272
- await p
273
-
274
- src.deleteRow.mockRejectedValueOnce(new Error('locked'))
275
- await expect(ctl.deleteRow('1')).rejects.toThrow('locked')
276
- expect(ctl.getState().rows.some((r) => r.id === 1)).toBe(true) // restored
277
- expect(ctl.getState().total).toBe(2)
278
- })
279
-
280
- it('falls back to a refetch when the row is not on the current page', async () => {
281
- const src = writable()
282
- const ctl = createServerDataSource(src, opts())
283
- ctl.refresh()
284
- await flush()
285
- await ctl.updateRow('999', { name: 'ghost' }) // not present locally
286
- // fell back to mutate() which refreshes: getRows called again
287
- expect(src.getRows).toHaveBeenCalledTimes(2)
288
- })
289
- })
1
+ import { describe, expect, it, vi } from 'vitest'
2
+ import { createServerDataSource, type ServerDataSource } from './server-data-source'
3
+
4
+ type Row = { id: number }
5
+
6
+ function makeSource(total = 250, delay = 0): ServerDataSource<Row> & { calls: number } {
7
+ const all: Row[] = Array.from({ length: total }, (_, i) => ({ id: i }))
8
+ return {
9
+ calls: 0,
10
+ async getRows(req) {
11
+ this.calls += 1
12
+ if (delay) await new Promise((r) => setTimeout(r, delay))
13
+ // server applies sort: reverse when desc on 'id'
14
+ let rows = all
15
+ if (req.sortModel[0]?.id === 'id' && req.sortModel[0]?.desc) rows = [...all].reverse()
16
+ return { rows: rows.slice(req.startRow, req.endRow), rowCount: total }
17
+ },
18
+ }
19
+ }
20
+
21
+ const flush = () => new Promise((r) => setTimeout(r, 0))
22
+
23
+ describe('createServerDataSource', () => {
24
+ it('fetches a page and reports total + pageCount', async () => {
25
+ const src = makeSource(250)
26
+ const states: any[] = []
27
+ const ctl = createServerDataSource(src, { pageSize: 50, onChange: (s) => states.push(s) })
28
+ ctl.refresh()
29
+ await flush()
30
+ const last = ctl.getState()
31
+ expect(last.rows).toHaveLength(50)
32
+ expect(last.total).toBe(250)
33
+ expect(last.pageCount).toBe(5)
34
+ expect(last.loading).toBe(false)
35
+ // emitted a loading=true then loading=false
36
+ expect(states.some((s) => s.loading)).toBe(true)
37
+ })
38
+
39
+ it('dispose() during an in-flight fetch clears the loading state (#83)', async () => {
40
+ const src = makeSource(250, 20) // 20ms delay keeps the fetch in-flight
41
+ const ctl = createServerDataSource(src, { pageSize: 50, onChange: () => {} })
42
+ ctl.refresh()
43
+ expect(ctl.getState().loading).toBe(true)
44
+ ctl.dispose()
45
+ expect(ctl.getState().loading).toBe(false)
46
+ await new Promise((r) => setTimeout(r, 30)) // let the fetch settle
47
+ expect(ctl.getState().loading).toBe(false) // stays cleared, not stuck
48
+ })
49
+
50
+ it('setPage fetches the right slice', async () => {
51
+ const src = makeSource(250)
52
+ const ctl = createServerDataSource(src, { pageSize: 50, onChange: () => {} })
53
+ ctl.setPage(2)
54
+ await flush()
55
+ expect(ctl.getState().rows[0]).toEqual({ id: 100 })
56
+ })
57
+
58
+ it('setSort resets to page 0 and re-fetches', async () => {
59
+ const src = makeSource(250)
60
+ const ctl = createServerDataSource(src, { pageSize: 50, onChange: () => {} })
61
+ ctl.setPage(2)
62
+ await flush()
63
+ ctl.setSort([{ id: 'id', desc: true }])
64
+ await flush()
65
+ const s = ctl.getState()
66
+ expect(s.pageIndex).toBe(0)
67
+ expect(s.rows[0]).toEqual({ id: 249 }) // reversed
68
+ })
69
+
70
+ it('ignores a stale response when a newer request supersedes it', async () => {
71
+ // slow source: first call (page 0) resolves AFTER the second (page 1)
72
+ const all = Array.from({ length: 100 }, (_, i) => ({ id: i }))
73
+ let call = 0
74
+ const src: ServerDataSource<Row> = {
75
+ getRows: vi.fn(async (req) => {
76
+ call += 1
77
+ const delay = call === 1 ? 40 : 5 // first call is slow
78
+ await new Promise((r) => setTimeout(r, delay))
79
+ return { rows: all.slice(req.startRow, req.endRow), rowCount: 100 }
80
+ }),
81
+ }
82
+ const ctl = createServerDataSource(src, { pageSize: 10, onChange: () => {} })
83
+ ctl.refresh() // page 0 (slow)
84
+ ctl.setPage(3) // page 30 (fast) - should win
85
+ await new Promise((r) => setTimeout(r, 80))
86
+ expect(ctl.getState().rows[0]).toEqual({ id: 30 })
87
+ })
88
+
89
+ it('dispose stops further updates', async () => {
90
+ const src = makeSource(50, 20)
91
+ const onChange = vi.fn()
92
+ const ctl = createServerDataSource(src, { pageSize: 10, onChange })
93
+ ctl.refresh()
94
+ ctl.dispose()
95
+ await new Promise((r) => setTimeout(r, 40))
96
+ // only the synchronous loading=true emit happened before dispose
97
+ expect(ctl.getState().rows).toHaveLength(0)
98
+ })
99
+ })
100
+
101
+ describe('createServerDataSource mutations', () => {
102
+ type URow = { id: number; name: string }
103
+
104
+ function writable() {
105
+ let store: URow[] = [
106
+ { id: 0, name: 'a' },
107
+ { id: 1, name: 'b' },
108
+ { id: 2, name: 'c' },
109
+ ]
110
+ return {
111
+ getRows: vi.fn(async (req: { startRow: number; endRow: number }) => ({
112
+ rows: store.slice(req.startRow, req.endRow),
113
+ rowCount: store.length,
114
+ })),
115
+ createRow: vi.fn(async (input: Partial<URow>) => {
116
+ const row: URow = { id: store.length, name: input.name ?? '' }
117
+ store = [...store, row]
118
+ return row
119
+ }),
120
+ updateRow: vi.fn(async (id: string, patch: Partial<URow>) => {
121
+ store = store.map((r) => (String(r.id) === id ? { ...r, ...patch } : r))
122
+ return store.find((r) => String(r.id) === id)!
123
+ }),
124
+ deleteRow: vi.fn(async (id: string) => {
125
+ store = store.filter((r) => String(r.id) !== id)
126
+ }),
127
+ } as unknown as ServerDataSource<URow> & {
128
+ getRows: ReturnType<typeof vi.fn>
129
+ createRow: ReturnType<typeof vi.fn>
130
+ updateRow: ReturnType<typeof vi.fn>
131
+ deleteRow: ReturnType<typeof vi.fn>
132
+ }
133
+ }
134
+
135
+ it('createRow writes through and refreshes the current page', async () => {
136
+ const src = writable()
137
+ const ctl = createServerDataSource(src, { pageSize: 50, onChange: () => {} })
138
+ ctl.refresh()
139
+ await flush()
140
+ const created = await ctl.createRow({ name: 'z' })
141
+ expect(created).toEqual({ id: 3, name: 'z' })
142
+ expect(src.createRow).toHaveBeenCalledWith({ name: 'z' })
143
+ // the follow-up refresh re-fetched, so total + rows reflect the write
144
+ expect(ctl.getState().total).toBe(4)
145
+ expect(ctl.getState().rows.some((r) => r.name === 'z')).toBe(true)
146
+ })
147
+
148
+ it('toggles `saving` around a mutation', async () => {
149
+ const src = writable()
150
+ const states: Array<{ saving: boolean }> = []
151
+ const ctl = createServerDataSource(src, { pageSize: 50, onChange: (s) => states.push(s) })
152
+ const p = ctl.createRow({ name: 'x' })
153
+ expect(ctl.getState().saving).toBe(true) // synchronous emit before the await
154
+ await p
155
+ expect(ctl.getState().saving).toBe(false)
156
+ expect(states.some((s) => s.saving)).toBe(true)
157
+ })
158
+
159
+ it('updateRow and deleteRow call through and refresh', async () => {
160
+ const src = writable()
161
+ const ctl = createServerDataSource(src, { pageSize: 50, onChange: () => {} })
162
+ ctl.refresh()
163
+ await flush()
164
+ const updated = await ctl.updateRow('1', { name: 'B!' })
165
+ expect(updated).toEqual({ id: 1, name: 'B!' })
166
+ expect(src.updateRow).toHaveBeenCalledWith('1', { name: 'B!' })
167
+ expect(ctl.getState().rows.find((r) => r.id === 1)?.name).toBe('B!')
168
+
169
+ await ctl.deleteRow('0')
170
+ expect(src.deleteRow).toHaveBeenCalledWith('0')
171
+ expect(ctl.getState().total).toBe(2)
172
+ expect(ctl.getState().rows.some((r) => r.id === 0)).toBe(false)
173
+ })
174
+
175
+ it('rejects when the source has no matching writer', async () => {
176
+ const readonly: ServerDataSource<URow> = {
177
+ getRows: async () => ({ rows: [], rowCount: 0 }),
178
+ }
179
+ const ctl = createServerDataSource(readonly, { onChange: () => {} })
180
+ await expect(ctl.createRow({ name: 'x' })).rejects.toThrow(/does not implement createRow/)
181
+ await expect(ctl.updateRow('1', {})).rejects.toThrow(/does not implement updateRow/)
182
+ await expect(ctl.deleteRow('1')).rejects.toThrow(/does not implement deleteRow/)
183
+ })
184
+
185
+ it('rejects mutations after dispose, without calling the source', async () => {
186
+ const src = writable()
187
+ const ctl = createServerDataSource(src, { onChange: () => {} })
188
+ ctl.dispose()
189
+ await expect(ctl.deleteRow('0')).rejects.toThrow(/disposed/)
190
+ expect(src.deleteRow).not.toHaveBeenCalled()
191
+ })
192
+
193
+ it('leaves read state untouched when a mutation fails', async () => {
194
+ const src = writable()
195
+ src.createRow.mockRejectedValueOnce(new Error('boom'))
196
+ const ctl = createServerDataSource(src, { pageSize: 50, onChange: () => {} })
197
+ ctl.refresh()
198
+ await flush()
199
+ await expect(ctl.createRow({ name: 'x' })).rejects.toThrow('boom')
200
+ expect(ctl.getState().saving).toBe(false)
201
+ expect(ctl.getState().total).toBe(3) // unchanged; no refresh on failure
202
+ })
203
+ })
204
+
205
+ describe('createServerDataSource optimistic mutations', () => {
206
+ type URow = { id: number; name: string }
207
+ const opts = (onChange: (s: any) => void = () => {}) => ({
208
+ pageSize: 50,
209
+ optimistic: true,
210
+ getRowId: (r: URow) => String(r.id),
211
+ onChange,
212
+ })
213
+
214
+ function writable() {
215
+ let store: URow[] = [
216
+ { id: 0, name: 'a' },
217
+ { id: 1, name: 'b' },
218
+ { id: 2, name: 'c' },
219
+ ]
220
+ return {
221
+ getRows: vi.fn(async (req: { startRow: number; endRow: number }) => ({
222
+ rows: store.slice(req.startRow, req.endRow),
223
+ rowCount: store.length,
224
+ })),
225
+ updateRow: vi.fn(async (id: string, patch: Partial<URow>) => {
226
+ store = store.map((r) => (String(r.id) === id ? { ...r, ...patch } : r))
227
+ return store.find((r) => String(r.id) === id)!
228
+ }),
229
+ deleteRow: vi.fn(async (id: string) => {
230
+ store = store.filter((r) => String(r.id) !== id)
231
+ }),
232
+ } as any
233
+ }
234
+
235
+ it('applies an update to local rows immediately, before the server resolves', async () => {
236
+ const src = writable()
237
+ src.updateRow.mockImplementationOnce(
238
+ (id: string, patch: any) => new Promise((r) => setTimeout(() => r({ id: Number(id), ...patch }), 30)),
239
+ )
240
+ const ctl = createServerDataSource(src, opts())
241
+ ctl.refresh()
242
+ await flush()
243
+ const p = ctl.updateRow('1', { name: 'B!' })
244
+ // synchronously reflected, no refetch
245
+ expect(ctl.getState().rows.find((r) => r.id === 1)?.name).toBe('B!')
246
+ expect(ctl.getState().saving).toBe(true)
247
+ await p
248
+ expect(ctl.getState().saving).toBe(false)
249
+ // getRows only called once (the initial refresh) - no refetch for the update
250
+ expect(src.getRows).toHaveBeenCalledTimes(1)
251
+ })
252
+
253
+ it('rolls an update back on error', async () => {
254
+ const src = writable()
255
+ src.updateRow.mockRejectedValueOnce(new Error('nope'))
256
+ const ctl = createServerDataSource(src, opts())
257
+ ctl.refresh()
258
+ await flush()
259
+ await expect(ctl.updateRow('1', { name: 'B!' })).rejects.toThrow('nope')
260
+ expect(ctl.getState().rows.find((r) => r.id === 1)?.name).toBe('b') // restored
261
+ })
262
+
263
+ it('removes a row immediately on delete and decrements total, restoring on error', async () => {
264
+ const src = writable()
265
+ const ctl = createServerDataSource(src, opts())
266
+ ctl.refresh()
267
+ await flush()
268
+
269
+ const p = ctl.deleteRow('0')
270
+ expect(ctl.getState().rows.some((r) => r.id === 0)).toBe(false)
271
+ expect(ctl.getState().total).toBe(2)
272
+ await p
273
+
274
+ src.deleteRow.mockRejectedValueOnce(new Error('locked'))
275
+ await expect(ctl.deleteRow('1')).rejects.toThrow('locked')
276
+ expect(ctl.getState().rows.some((r) => r.id === 1)).toBe(true) // restored
277
+ expect(ctl.getState().total).toBe(2)
278
+ })
279
+
280
+ it('falls back to a refetch when the row is not on the current page', async () => {
281
+ const src = writable()
282
+ const ctl = createServerDataSource(src, opts())
283
+ ctl.refresh()
284
+ await flush()
285
+ await ctl.updateRow('999', { name: 'ghost' }) // not present locally
286
+ // fell back to mutate() which refreshes: getRows called again
287
+ expect(src.getRows).toHaveBeenCalledTimes(2)
288
+ })
289
+ })