@svgrid/enterprise 3.0.2 → 3.1.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 (364) hide show
  1. package/LICENSE +94 -24
  2. package/README.md +36 -4
  3. package/dist/SvAdvancedFilter.svelte +1 -1
  4. package/dist/SvAlertsManager.svelte +2 -1
  5. package/dist/SvBoard.svelte +2 -1
  6. package/dist/SvExportMenu.svelte +4 -1
  7. package/dist/SvExportMenu.svelte.d.ts +0 -13
  8. package/dist/SvExpressionEditor.svelte +3 -3
  9. package/dist/SvFormulaBar.svelte +94 -20
  10. package/dist/SvFormulaBar.svelte.d.ts +7 -0
  11. package/dist/SvGridAlerts.svelte +49 -9
  12. package/dist/SvGridBoard.svelte +13 -8
  13. package/dist/SvGridBulkEditDrawer.svelte +26 -2
  14. package/dist/SvGridEditPanel.svelte +3 -0
  15. package/dist/SvGridScheduler.svelte +69 -40
  16. package/dist/SvGridSelectionBar.svelte +10 -3
  17. package/dist/SvPivotDesigner.svelte +198 -40
  18. package/dist/SvPivotDesigner.svelte.d.ts +27 -20
  19. package/dist/SvSheet.svelte +4043 -243
  20. package/dist/SvSheet.svelte.d.ts +76 -3
  21. package/dist/SvSheetChartSetup.svelte +151 -0
  22. package/dist/SvSheetChartSetup.svelte.d.ts +18 -0
  23. package/dist/SvSheetComment.svelte +216 -31
  24. package/dist/SvSheetComment.svelte.d.ts +11 -11
  25. package/dist/SvSheetConditionalFormat.svelte +28 -31
  26. package/dist/SvSheetDataValidation.svelte +66 -42
  27. package/dist/SvSheetEditRanges.svelte +217 -0
  28. package/dist/SvSheetEditRanges.svelte.d.ts +15 -0
  29. package/dist/SvSheetErrors.svelte +125 -0
  30. package/dist/SvSheetErrors.svelte.d.ts +20 -0
  31. package/dist/SvSheetEvaluate.svelte +133 -0
  32. package/dist/SvSheetEvaluate.svelte.d.ts +15 -0
  33. package/dist/SvSheetFilterMenu.svelte +137 -34
  34. package/dist/SvSheetFilterMenu.svelte.d.ts +5 -0
  35. package/dist/SvSheetFindReplace.svelte +19 -19
  36. package/dist/SvSheetFormatCells.svelte +125 -81
  37. package/dist/SvSheetGoalSeek.svelte +17 -16
  38. package/dist/SvSheetInsertFunction.svelte +15 -13
  39. package/dist/SvSheetIteration.svelte +81 -0
  40. package/dist/SvSheetIteration.svelte.d.ts +10 -0
  41. package/dist/SvSheetLink.svelte +99 -0
  42. package/dist/SvSheetLink.svelte.d.ts +17 -0
  43. package/dist/SvSheetListPicker.svelte +4 -2
  44. package/dist/SvSheetListPicker.svelte.d.ts +0 -6
  45. package/dist/SvSheetManageRules.svelte +20 -18
  46. package/dist/SvSheetNameManager.svelte +17 -15
  47. package/dist/SvSheetPageSetup.svelte +159 -0
  48. package/dist/SvSheetPageSetup.svelte.d.ts +13 -0
  49. package/dist/SvSheetPasteSpecial.svelte +22 -20
  50. package/dist/SvSheetPivot.svelte +220 -0
  51. package/dist/SvSheetPivot.svelte.d.ts +21 -0
  52. package/dist/SvSheetProtectSheet.svelte +81 -0
  53. package/dist/SvSheetProtectSheet.svelte.d.ts +11 -0
  54. package/dist/SvSheetRemoveDuplicates.svelte +13 -10
  55. package/dist/SvSheetRibbon.svelte +104 -51
  56. package/dist/SvSheetSizeDialog.svelte +4 -2
  57. package/dist/SvSheetSort.svelte +218 -0
  58. package/dist/SvSheetSort.svelte.d.ts +28 -0
  59. package/dist/SvSheetSparklines.svelte +135 -0
  60. package/dist/SvSheetSparklines.svelte.d.ts +14 -0
  61. package/dist/SvSheetTable.svelte +160 -0
  62. package/dist/SvSheetTable.svelte.d.ts +24 -0
  63. package/dist/SvSheetTabs.svelte +161 -31
  64. package/dist/SvSheetTabs.svelte.d.ts +22 -8
  65. package/dist/SvSheetTextToColumns.svelte +20 -20
  66. package/dist/SvSheetValidationAlert.svelte +8 -6
  67. package/dist/advanced-filter-enable.js +1 -1
  68. package/dist/alerts/alert-engine.d.ts +6 -0
  69. package/dist/alerts/alert-engine.js +21 -4
  70. package/dist/alerts/alert-formats.js +2 -0
  71. package/dist/alerts/alert-types.d.ts +1 -1
  72. package/dist/alerts.d.ts +1 -1
  73. package/dist/alerts.js +2 -2
  74. package/dist/board.js +2 -2
  75. package/dist/bulk-edit.d.ts +3 -1
  76. package/dist/bulk-edit.js +1 -1
  77. package/dist/cdn/svgrid-enterprise.svelte-external.js +39827 -16429
  78. package/dist/designer/assets/GridMenus-8gTNssgV.js +8 -0
  79. package/dist/designer/assets/{SvChartMenu-D9hEeHar.js → SvChartMenu-BDoeqGz6.js} +1 -1
  80. package/dist/designer/assets/{SvDateTimePicker-DARvKUL2.js → SvDateTimePicker-DCPg7WGN.js} +1 -1
  81. package/dist/designer/assets/SvGridCellEditor-DMoWJjEI.js +4 -0
  82. package/dist/designer/assets/{SvGridChart-B8j1SSy3.js → SvGridChart-Cy1ATrWh.js} +2 -2
  83. package/dist/designer/assets/SvGridChartBuilder-eQ3mn0L9.js +1 -0
  84. package/dist/designer/assets/{SvGridChartPanel-pT6ITc2P.js → SvGridChartPanel-CATdVc9X.js} +3 -3
  85. package/dist/designer/assets/{SvGridChartView-bg281UA1.js → SvGridChartView-CmsZtCED.js} +1 -1
  86. package/dist/designer/assets/SvGridDropdown-Cjz0H-fI.js +1 -0
  87. package/dist/designer/assets/{SvTabs-UFCMTHXS.js → SvTabs-2mHC6oaz.js} +1 -1
  88. package/dist/designer/assets/cell-formatting-BbubJTVz.js +1 -0
  89. package/dist/designer/assets/{chart-cIPUsbM5.js → chart-Cn7MnNqA.js} +1 -1
  90. package/dist/designer/assets/{chart-panel-messages-DtTxpBUU.js → chart-panel-messages-Dz7PTIqT.js} +1 -1
  91. package/dist/designer/assets/{chart-summary-Czu_5epb.js → chart-summary-BD1qYB2R.js} +1 -1
  92. package/dist/designer/assets/{chart-trend-D5I5VEe8.js → chart-trend-B8xr5LuD.js} +1 -1
  93. package/dist/designer/assets/{dismissable-BO547MH9.js → dismissable-ChWRPkoa.js} +1 -1
  94. package/dist/designer/assets/{export-format-D-eP4b2l.js → export-format-DGckmgKr.js} +1 -1
  95. package/dist/designer/assets/{focus-trap-CAreAhlU.js → focus-trap-V2-6Fy9I.js} +1 -1
  96. package/dist/designer/assets/{index-DwU7CAMm.js → index-BSjv6Qec.js} +317 -315
  97. package/dist/designer/assets/index-_JVFxt02.css +1 -0
  98. package/dist/designer/assets/row-resize-BInFWCpr.js +1 -0
  99. package/dist/designer/assets/server-block-cache-CXJKkJpy.js +1 -0
  100. package/dist/designer/assets/src-DUQ4xE3x.js +58 -0
  101. package/dist/designer/assets/validate-C3B8QKsM.js +1 -0
  102. package/dist/designer/index.html +17 -16
  103. package/dist/export-pdf.d.ts +75 -0
  104. package/dist/export-pdf.js +216 -15
  105. package/dist/export.js +10 -2
  106. package/dist/gantt/SvGridGantt.svelte +2890 -0
  107. package/dist/gantt/SvGridGantt.svelte.d.ts +30 -0
  108. package/dist/gantt/gantt-config.d.ts +73 -0
  109. package/dist/gantt/gantt-config.js +1 -0
  110. package/dist/gantt/gantt-critical-path.d.ts +67 -0
  111. package/dist/gantt/gantt-critical-path.js +207 -0
  112. package/dist/gantt/gantt-model.d.ts +257 -0
  113. package/dist/gantt/gantt-model.js +651 -0
  114. package/dist/gantt/gantt-resources.d.ts +59 -0
  115. package/dist/gantt/gantt-resources.js +71 -0
  116. package/dist/gantt/gantt.d.ts +7 -0
  117. package/dist/gantt/gantt.js +36 -0
  118. package/dist/gantt/index.d.ts +15 -0
  119. package/dist/gantt/index.js +22 -0
  120. package/dist/gantt/timeline-arrows.d.ts +102 -0
  121. package/dist/gantt/timeline-arrows.js +181 -0
  122. package/dist/import.d.ts +2 -2
  123. package/dist/import.js +1 -1
  124. package/dist/index.d.ts +34 -7
  125. package/dist/index.js +44 -7
  126. package/dist/install.js +8 -1
  127. package/dist/license-core.d.ts +17 -1
  128. package/dist/license-core.js +16 -5
  129. package/dist/license.d.ts +18 -2
  130. package/dist/license.js +71 -6
  131. package/dist/node/studio.js +140 -8
  132. package/dist/pivot-enable.js +2 -2
  133. package/dist/scheduler-dependencies.d.ts +35 -0
  134. package/dist/scheduler-dependencies.js +36 -1
  135. package/dist/scheduler.js +2 -2
  136. package/dist/selection-bar.js +2 -2
  137. package/dist/server/SvGroupCell.svelte +213 -0
  138. package/dist/server/SvGroupCell.svelte.d.ts +62 -0
  139. package/dist/server/SvRowGroupPanel.svelte +322 -0
  140. package/dist/server/SvRowGroupPanel.svelte.d.ts +37 -0
  141. package/dist/server/enable.d.ts +17 -0
  142. package/dist/server/enable.js +49 -0
  143. package/dist/server/index.d.ts +22 -0
  144. package/dist/server/index.js +24 -0
  145. package/dist/server/messages.d.ts +45 -0
  146. package/dist/server/messages.js +35 -0
  147. package/dist/server/server-group-model.d.ts +104 -0
  148. package/dist/server/server-group-model.js +272 -0
  149. package/dist/server/server-pivot.d.ts +46 -0
  150. package/dist/server/server-pivot.js +57 -0
  151. package/dist/server/server-row-model.d.ts +547 -0
  152. package/dist/server/server-row-model.js +1551 -0
  153. package/dist/server/server-selection.d.ts +99 -0
  154. package/dist/server/server-selection.js +215 -0
  155. package/dist/server/svgrid-adapter.d.ts +155 -0
  156. package/dist/server/svgrid-adapter.js +202 -0
  157. package/dist/sheet/ast.d.ts +45 -1
  158. package/dist/sheet/ast.js +14 -0
  159. package/dist/sheet/auto-filter.d.ts +58 -4
  160. package/dist/sheet/auto-filter.js +159 -11
  161. package/dist/sheet/autocomplete.d.ts +9 -0
  162. package/dist/sheet/autocomplete.js +90 -4
  163. package/dist/sheet/cell-images.d.ts +39 -0
  164. package/dist/sheet/cell-images.js +90 -0
  165. package/dist/sheet/cell-types.d.ts +58 -0
  166. package/dist/sheet/cell-types.js +96 -0
  167. package/dist/sheet/coerce.d.ts +40 -0
  168. package/dist/sheet/coerce.js +170 -3
  169. package/dist/sheet/comments.d.ts +43 -9
  170. package/dist/sheet/comments.js +85 -27
  171. package/dist/sheet/conditional-formats.d.ts +29 -6
  172. package/dist/sheet/conditional-formats.js +37 -12
  173. package/dist/sheet/csv.d.ts +32 -0
  174. package/dist/sheet/csv.js +158 -0
  175. package/dist/sheet/culture.d.ts +87 -0
  176. package/dist/sheet/culture.js +247 -0
  177. package/dist/sheet/delta.d.ts +102 -0
  178. package/dist/sheet/delta.js +203 -0
  179. package/dist/sheet/deps.d.ts +45 -1
  180. package/dist/sheet/deps.js +77 -2
  181. package/dist/sheet/document.d.ts +106 -4
  182. package/dist/sheet/document.js +204 -3
  183. package/dist/sheet/engine.d.ts +67 -0
  184. package/dist/sheet/engine.js +19 -0
  185. package/dist/sheet/entry.d.ts +8 -4
  186. package/dist/sheet/entry.js +171 -21
  187. package/dist/sheet/error-check.d.ts +49 -0
  188. package/dist/sheet/error-check.js +83 -0
  189. package/dist/sheet/evaluate-steps.d.ts +48 -0
  190. package/dist/sheet/evaluate-steps.js +196 -0
  191. package/dist/sheet/evaluate.d.ts +67 -0
  192. package/dist/sheet/evaluate.js +725 -13
  193. package/dist/sheet/filter-files.d.ts +37 -0
  194. package/dist/sheet/filter-files.js +465 -0
  195. package/dist/sheet/format-store.d.ts +15 -0
  196. package/dist/sheet/format-store.js +11 -1
  197. package/dist/sheet/from-rows.d.ts +14 -0
  198. package/dist/sheet/from-rows.js +50 -0
  199. package/dist/sheet/function-catalog.d.ts +1 -1
  200. package/dist/sheet/function-catalog.js +296 -5
  201. package/dist/sheet/functions.js +363 -91
  202. package/dist/sheet/index.d.ts +35 -8
  203. package/dist/sheet/index.js +35 -8
  204. package/dist/sheet/links.d.ts +57 -0
  205. package/dist/sheet/links.js +174 -0
  206. package/dist/sheet/messages.d.ts +778 -0
  207. package/dist/sheet/messages.js +806 -0
  208. package/dist/sheet/number-format.d.ts +52 -5
  209. package/dist/sheet/number-format.js +465 -68
  210. package/dist/sheet/objects.d.ts +106 -0
  211. package/dist/sheet/objects.js +149 -0
  212. package/dist/sheet/ods-document.d.ts +44 -0
  213. package/dist/sheet/ods-document.js +1275 -0
  214. package/dist/sheet/open-file.d.ts +26 -0
  215. package/dist/sheet/open-file.js +56 -0
  216. package/dist/sheet/outline.d.ts +82 -0
  217. package/dist/sheet/outline.js +228 -0
  218. package/dist/sheet/packs/array.d.ts +16 -0
  219. package/dist/sheet/packs/array.js +172 -0
  220. package/dist/sheet/packs/database.d.ts +42 -0
  221. package/dist/sheet/packs/database.js +142 -0
  222. package/dist/sheet/packs/engineering.d.ts +2 -0
  223. package/dist/sheet/packs/engineering.js +296 -0
  224. package/dist/sheet/packs/financial.d.ts +5 -0
  225. package/dist/sheet/packs/financial.js +220 -0
  226. package/dist/sheet/packs/math-stats.d.ts +2 -0
  227. package/dist/sheet/packs/math-stats.js +372 -0
  228. package/dist/sheet/packs/special.d.ts +44 -0
  229. package/dist/sheet/packs/special.js +187 -0
  230. package/dist/sheet/packs/stats-dist.d.ts +2 -0
  231. package/dist/sheet/packs/stats-dist.js +785 -0
  232. package/dist/sheet/packs/text-date.d.ts +2 -0
  233. package/dist/sheet/packs/text-date.js +332 -0
  234. package/dist/sheet/packs/trig.d.ts +2 -0
  235. package/dist/sheet/packs/trig.js +73 -0
  236. package/dist/sheet/page-setup.d.ts +73 -0
  237. package/dist/sheet/page-setup.js +73 -0
  238. package/dist/sheet/parse.js +42 -10
  239. package/dist/sheet/paste-special.d.ts +22 -0
  240. package/dist/sheet/paste-special.js +14 -5
  241. package/dist/sheet/pivot-range.d.ts +160 -0
  242. package/dist/sheet/pivot-range.js +333 -0
  243. package/dist/sheet/presence.d.ts +73 -0
  244. package/dist/sheet/presence.js +79 -0
  245. package/dist/sheet/print.d.ts +104 -0
  246. package/dist/sheet/print.js +169 -0
  247. package/dist/sheet/protection.d.ts +55 -1
  248. package/dist/sheet/protection.js +55 -2
  249. package/dist/sheet/rects.d.ts +1 -1
  250. package/dist/sheet/refs.d.ts +48 -0
  251. package/dist/sheet/refs.js +155 -8
  252. package/dist/sheet/ribbon-icons.d.ts +1 -1
  253. package/dist/sheet/ribbon-icons.js +186 -0
  254. package/dist/sheet/ribbon.d.ts +1 -1
  255. package/dist/sheet/ribbon.js +324 -23
  256. package/dist/sheet/sheet-kinds.d.ts +74 -0
  257. package/dist/sheet/sheet-kinds.js +91 -0
  258. package/dist/sheet/shortcuts.d.ts +13 -3
  259. package/dist/sheet/shortcuts.js +63 -12
  260. package/dist/sheet/sort.d.ts +51 -0
  261. package/dist/sheet/sort.js +107 -0
  262. package/dist/sheet/sparklines.d.ts +111 -0
  263. package/dist/sheet/sparklines.js +204 -0
  264. package/dist/sheet/table-styles.d.ts +74 -0
  265. package/dist/sheet/table-styles.js +152 -0
  266. package/dist/sheet/tables.d.ts +19 -0
  267. package/dist/sheet/tables.js +31 -0
  268. package/dist/sheet/tokenize.d.ts +18 -1
  269. package/dist/sheet/tokenize.js +60 -9
  270. package/dist/sheet/validation.d.ts +16 -0
  271. package/dist/sheet/validation.js +37 -0
  272. package/dist/sheet/workbook.d.ts +148 -3
  273. package/dist/sheet/workbook.js +742 -31
  274. package/dist/sheet/xls-cfb.d.ts +32 -0
  275. package/dist/sheet/xls-cfb.js +290 -0
  276. package/dist/sheet/xls-document.d.ts +10 -0
  277. package/dist/sheet/xls-document.js +1243 -0
  278. package/dist/sheet/xls-formula.d.ts +40 -0
  279. package/dist/sheet/xls-formula.js +569 -0
  280. package/dist/sheet/xlsx-document.d.ts +108 -0
  281. package/dist/sheet/xlsx-document.js +1849 -0
  282. package/dist/sheet/xlsx-drawing.d.ts +99 -0
  283. package/dist/sheet/xlsx-drawing.js +420 -0
  284. package/dist/sheet-enable.js +6 -7
  285. package/dist/sheet-text.d.ts +7 -0
  286. package/dist/sheet-text.js +29 -0
  287. package/dist/sources/auth-supabase.js +2 -2
  288. package/dist/sources/realtime-supabase.js +2 -2
  289. package/dist/sources/rest.d.ts +6 -2
  290. package/dist/sources/rest.js +42 -7
  291. package/dist/sources/supabase.d.ts +10 -1
  292. package/dist/sources/supabase.js +73 -40
  293. package/dist/studio/emit-project.js +14 -1
  294. package/dist/studio/index.js +1 -1
  295. package/dist/studio/project.d.ts +11 -2
  296. package/dist/studio/project.js +5 -2
  297. package/dist/studio/ui-components.generated.js +107 -2
  298. package/dist/sveltekit/in-memory.d.ts +2 -2
  299. package/dist/sveltekit/in-memory.js +125 -4
  300. package/dist/sveltekit/index.d.ts +1 -1
  301. package/dist/sveltekit/index.js +1 -1
  302. package/dist/sveltekit/query-plan.d.ts +50 -4
  303. package/dist/sveltekit/query-plan.js +25 -3
  304. package/dist/sveltekit/sql-source.d.ts +6 -0
  305. package/dist/sveltekit/sql-source.js +34 -6
  306. package/dist/sveltekit/sql.d.ts +31 -0
  307. package/dist/sveltekit/sql.js +135 -25
  308. package/dist/sveltekit/transport.d.ts +10 -2
  309. package/dist/sveltekit/transport.js +30 -6
  310. package/dist/upgrade-prompt.d.ts +1 -1
  311. package/dist/version.d.ts +1 -1
  312. package/dist/version.js +1 -1
  313. package/dist/watermark.js +35 -2
  314. package/dist/wc/GridMenus-By9vPuq0.js +1088 -0
  315. package/dist/wc/SvChartMenu-CQNrQGT2.js +308 -0
  316. package/dist/wc/SvDateRangeInput-DR6m7bSM.js +199 -0
  317. package/dist/wc/SvDateTimePicker-D-vRnaMo.js +771 -0
  318. package/dist/wc/SvField-B_kurx_P.js +149 -0
  319. package/dist/wc/SvGrid.helpers-BdVDtjUm.js +135 -0
  320. package/dist/wc/SvGridCellEditor-Bnf1Kr5C.js +560 -0
  321. package/dist/wc/SvGridChart-BqUJh2pG.js +3572 -0
  322. package/dist/wc/SvGridChartBuilder-CGycC5cn.js +988 -0
  323. package/dist/wc/SvGridChartPanel-CRnizsHD.js +1746 -0
  324. package/dist/wc/SvGridChartView-D1pa_xMy.js +47 -0
  325. package/dist/wc/SvGridDropdown-45WsObLL.js +259 -0
  326. package/dist/wc/SvModal-DrfOYYyX.js +189 -0
  327. package/dist/wc/cell-formatting-CBfgahv1.js +86 -0
  328. package/dist/wc/chart-6OXlybVT.js +4341 -0
  329. package/dist/wc/chart-export-CeSADGfi.js +135 -0
  330. package/dist/wc/chart-export-pdf-BGko7MER.js +104 -0
  331. package/dist/wc/chart-summary-2m3ia1-n.js +137 -0
  332. package/dist/wc/chart-trend-CQOQtTMl.js +50 -0
  333. package/dist/wc/chart-validate-CD6ptiwg.js +180 -0
  334. package/dist/wc/client-DQIzjDBG.js +3064 -0
  335. package/dist/wc/column-resize-DUPgynvc.js +101 -0
  336. package/dist/wc/date-format-DS-z_h9v.js +1130 -0
  337. package/dist/wc/dismissable-CQ6XFXsk.js +45 -0
  338. package/dist/wc/editor-contract-peBDxir_.js +22 -0
  339. package/dist/wc/editor-registry-DxGKqcvx.js +24 -0
  340. package/dist/wc/focus-trap-Dbb2tJxb.js +65 -0
  341. package/dist/wc/list-option-DrRyivWp.js +82 -0
  342. package/dist/wc/popover-WsGlvtQv.js +69 -0
  343. package/dist/wc/positioning-BiKLfu1E.js +112 -0
  344. package/dist/wc/react.d.ts +41 -0
  345. package/dist/wc/react.js +90 -0
  346. package/dist/wc/rolldown-runtime-Dy4uBu1J.js +11 -0
  347. package/dist/wc/row-drag-touch-CycRR65n.js +93 -0
  348. package/dist/wc/row-resize-limG_N1z.js +111 -0
  349. package/dist/wc/sv-sheet-element.d.ts +97 -0
  350. package/dist/wc/sv-sheet-element.js +44213 -0
  351. package/dist/wc/svelte-virtualizer.svelte-CXtQy2TQ.js +530 -0
  352. package/dist/wc/validate-BqDthkNr.js +88 -0
  353. package/dist/wc/vue.d.ts +31 -0
  354. package/dist/wc/vue.js +75 -0
  355. package/package.json +30 -5
  356. package/dist/designer/assets/GridMenus-BlsvL_aU.js +0 -8
  357. package/dist/designer/assets/SvGridCellEditor-C7uQXYds.js +0 -4
  358. package/dist/designer/assets/SvGridChartBuilder-kvyIU_Rl.js +0 -1
  359. package/dist/designer/assets/SvGridDropdown-Bb3HH2Aj.js +0 -1
  360. package/dist/designer/assets/cell-formatting-wr5wm7US.js +0 -1
  361. package/dist/designer/assets/index-CMFgAoVV.css +0 -1
  362. package/dist/designer/assets/row-resize-DLktcqUK.js +0 -1
  363. package/dist/designer/assets/src-EdI3Xqds.js +0 -58
  364. package/dist/designer/assets/validate-h-aslRV9.js +0 -1
@@ -0,0 +1,1551 @@
1
+ /**
2
+ * The Server-Side Row Model.
3
+ *
4
+ * One `getRows` contract, any depth of grouping or tree, and every level
5
+ * scrolls: the top level is one block cache, each expanded group is another,
6
+ * and the rows the user has not scrolled to are placeholders that cost
7
+ * nothing to hold. Sorting, filtering, expanding, refreshing, transactions
8
+ * (Phase 4) and selection across rows that were never loaded (Phase 5) all
9
+ * hang off this one object, and `<SvGrid rowModel={ctl} />` wires the lot.
10
+ *
11
+ * What the free grid gives it: the datasource contract (`ServerRequest` /
12
+ * `ServerResult`), the block cache (`createBlockCache`), the placeholder mark
13
+ * (`createRowPlaceholder`) and the four seams `rowModel` plugs into. What this
14
+ * file adds is the TREE: a store per route, a flatten that splices expanded
15
+ * groups' children into one display list, and the rules for which stores a
16
+ * sort, a filter or a refresh actually touches.
17
+ *
18
+ * Headless and framework-agnostic on purpose. Nothing here imports Svelte;
19
+ * `onChange` and `subscribe` are how the outside learns anything changed.
20
+ *
21
+ * `createServerGroupModel` (the previous model, block-append with a "Load N
22
+ * more" button) is still exported for anyone on it; this one supersedes it.
23
+ */
24
+ import { createBlockCache, createRowPlaceholder, rowPlaceholderState, toServerFilterColumns,
25
+ // The subpath, not the root: the root re-exports SvGrid.svelte, which
26
+ // would land in every bundle and test that touches this model.
27
+ } from '@svgrid/grid/server';
28
+ import { nudgeServerRowModel } from './enable.js';
29
+ import { buildPivotResultColumns } from './server-pivot.js';
30
+ import { fillMessage, resolveServerGroupMessages } from './messages.js';
31
+ import { createServerSelectionModel, } from './server-selection.js';
32
+ import { fromCallbackSelectionState, isCallbackSelectionState, } from './svgrid-adapter.js';
33
+ const routeKey = (route) => JSON.stringify(route);
34
+ const LOADING_ROW = createRowPlaceholder('loading', {
35
+ kind: 'placeholder',
36
+ state: 'loading',
37
+ });
38
+ const FAILED_ROW = createRowPlaceholder('failed', {
39
+ kind: 'placeholder',
40
+ state: 'failed',
41
+ });
42
+ /** The fixed id of the grand-total display row, so a transaction can address it. */
43
+ export const GRAND_TOTAL_ROW_ID = 'sv-grand-total';
44
+ /** The prefix of a group footer's id; the rest is the group route. */
45
+ export const GROUP_TOTAL_ROW_ID_PREFIX = 'sv-group-total:';
46
+ /** Plain type-aware ordering for `clientSideSort`. */
47
+ function compareValues(a, b) {
48
+ if (a == null && b == null)
49
+ return 0;
50
+ if (a == null)
51
+ return 1;
52
+ if (b == null)
53
+ return -1;
54
+ if (typeof a === 'number' && typeof b === 'number')
55
+ return a - b;
56
+ if (a instanceof Date && b instanceof Date)
57
+ return a.getTime() - b.getTime();
58
+ return String(a).localeCompare(String(b), undefined, { numeric: true });
59
+ }
60
+ /**
61
+ * Build the Server-Side Row Model over a `ServerDataSource`: lazy grouping
62
+ * and tree data one level at a time, a block cache per level, aggregates,
63
+ * child counts, grand totals, pivot, paging, transactions and a selection
64
+ * that reaches rows the grid never loaded. Hand the result to
65
+ * `<SvGrid rowModel>`. Enterprise: nudges when no licence key is set.
66
+ */
67
+ export function createServerRowModel(source, options) {
68
+ // Runs without a key; shows the watermark + one console notice.
69
+ nudgeServerRowModel();
70
+ let groupBy = [...(options.groupBy ?? [])];
71
+ let aggregations = [...(options.aggregations ?? [])];
72
+ let pivotBy = [...(options.pivotBy ?? [])];
73
+ let pivotMode = !!options.pivotMode;
74
+ let pivotRowTotals = options.pivotRowTotals ?? false;
75
+ const pivotSeparator = options.pivotFieldSeparator ?? '_';
76
+ /** The union of every `pivotResultFields` seen since the last reset, in order. */
77
+ let pivotFields = [];
78
+ /** Verbatim column defs from the backend, when it sent them instead of fields. */
79
+ let pivotColumnsFromSource = null;
80
+ const treeData = !!options.treeData;
81
+ if (treeData && (!options.getRowId || !options.hasChildren)) {
82
+ throw new Error('createServerRowModel: treeData requires getRowId and hasChildren');
83
+ }
84
+ const getRowId = options.getRowId;
85
+ const hasChildren = options.hasChildren;
86
+ const childCountOf = options.childCount;
87
+ const skeletonRows = Math.max(1, options.skeletonRows ?? 3);
88
+ const paging = options.pagination ?? null;
89
+ let pageIndex = 0;
90
+ let pageSize = Math.max(1, paging?.pageSize ?? 100);
91
+ /** Rows the pager counts, as of the last flatten. */
92
+ let pagerRowCount = 0;
93
+ const groupFooters = !!options.groupFooters;
94
+ const grandTotalRow = options.grandTotalRow;
95
+ const debug = !!options.debug;
96
+ let sortModel = [];
97
+ let filterModel = {};
98
+ const stores = new Map();
99
+ const expanded = new Set();
100
+ /** Group routes that have been through `isGroupOpenByDefault` once. */
101
+ const seenGroups = new Set();
102
+ let autoExpand = false;
103
+ let grandTotal = undefined;
104
+ /**
105
+ * The grand total on hand is from before an in-place refresh of the top
106
+ * level or a bulk edit, so the next root block asks for a fresh one. The
107
+ * old one stays on screen until it lands rather than blinking out.
108
+ */
109
+ let grandTotalStale = false;
110
+ /** Leaves whose detail panel is open, by the id `getRowId` gives them. */
111
+ const openDetails = new Set();
112
+ let rootError = null;
113
+ let disposed = false;
114
+ /** Writes the server has not answered yet; `saving` is "any". */
115
+ let writesInFlight = 0;
116
+ // Generation guard: bumped on any reset so responses for the previous shape
117
+ // are discarded when they land.
118
+ let generation = 0;
119
+ // A single in-flight budget across every level. The block cache caps
120
+ // concurrency per instance; with one instance per route that would let ten
121
+ // open groups fire twenty requests, so the gate lives here.
122
+ const maxConcurrent = Math.max(1, options.maxConcurrentRequests ?? 2);
123
+ let inFlight = 0;
124
+ const waiting = [];
125
+ const acquire = () => {
126
+ if (inFlight < maxConcurrent) {
127
+ inFlight += 1;
128
+ return Promise.resolve();
129
+ }
130
+ return new Promise((resolve) => waiting.push(() => { inFlight += 1; resolve(); }));
131
+ };
132
+ const release = () => {
133
+ inFlight -= 1;
134
+ waiting.shift()?.();
135
+ };
136
+ const subscribers = new Set();
137
+ let lastViewport = [0, 0];
138
+ let segments = [];
139
+ let emitScheduled = false;
140
+ // ----------------------------------------------------------- stores
141
+ function levelOf(route) {
142
+ return route.length;
143
+ }
144
+ function isLeafLevel(level) {
145
+ return !treeData && level >= groupBy.length;
146
+ }
147
+ /** The group row `route` names, if its parent level has it loaded. */
148
+ function parentRowOf(route) {
149
+ if (route.length === 0)
150
+ return undefined;
151
+ const parent = stores.get(routeKey(route.slice(0, -1)));
152
+ if (!parent)
153
+ return undefined;
154
+ const key = route[route.length - 1];
155
+ const rows = parent.cache.rows();
156
+ for (const child of rows) {
157
+ if (rowPlaceholderState(child))
158
+ continue;
159
+ if (child.kind === 'group' && child.key === key)
160
+ return child.data;
161
+ }
162
+ return undefined;
163
+ }
164
+ function toChildren(route, startRow, rows) {
165
+ const level = levelOf(route);
166
+ const groupField = groupBy[level];
167
+ const storeKey = routeKey(route);
168
+ return rows.map((data, i) => {
169
+ const isGroup = treeData ? hasChildren(data) : level < groupBy.length;
170
+ if (isGroup) {
171
+ const key = treeData
172
+ ? getRowId(data)
173
+ : ((v) => (v == null ? '' : String(v)))(data[groupField]);
174
+ const agg = {};
175
+ for (const a of aggregations)
176
+ agg[a.col] = data[a.col];
177
+ return {
178
+ kind: 'group',
179
+ key,
180
+ field: treeData ? '' : groupField,
181
+ aggregates: agg,
182
+ childCount: childCountOf?.(data),
183
+ data,
184
+ };
185
+ }
186
+ const id = getRowId ? `${storeKey}:${getRowId(data)}` : `${storeKey}:${startRow + i}`;
187
+ return { kind: 'leaf', id, data };
188
+ });
189
+ }
190
+ function createStore(route) {
191
+ const level = levelOf(route);
192
+ const params = options.levelParams?.(level, route) ?? {};
193
+ const blockSize = Math.max(1, params.blockSize ?? options.blockSize ?? 100);
194
+ const loadMore = !!params.loadMore;
195
+ const infinite = !loadMore && params.infinite !== false;
196
+ const key = routeKey(route);
197
+ const gen = generation;
198
+ const store = {
199
+ route,
200
+ key,
201
+ level,
202
+ infinite,
203
+ loadMore,
204
+ blockSize,
205
+ refreshPending: false,
206
+ seenBlocks: new Map(),
207
+ cache: null,
208
+ };
209
+ store.cache = createBlockCache({
210
+ blockSize,
211
+ maxBlocksInCache: params.maxBlocksInCache ?? options.maxBlocksInCache,
212
+ // The model gates concurrency itself, across levels.
213
+ maxConcurrentRequests: Number.MAX_SAFE_INTEGER,
214
+ blockLoadDebounceMs: options.blockLoadDebounceMs,
215
+ initialRowCount: Math.max(1, params.initialRowCount ?? skeletonRows),
216
+ fetch: async (startRow, endRow, signal) => {
217
+ await acquire();
218
+ try {
219
+ if (disposed || gen !== generation || signal.aborted) {
220
+ throw new Error('stale');
221
+ }
222
+ const needsGrandTotal = route.length === 0 && grandTotalRow != null && (grandTotal === undefined || grandTotalStale);
223
+ const request = {
224
+ startRow,
225
+ endRow,
226
+ pageIndex: Math.floor(startRow / blockSize),
227
+ pageSize: blockSize,
228
+ sortModel,
229
+ filterModel,
230
+ groupBy,
231
+ groupKeys: route,
232
+ aggregations,
233
+ ...(pivotMode ? { pivotBy, pivotMode: true } : {}),
234
+ ...(needsGrandTotal ? { needsGrandTotal: true } : {}),
235
+ ...(route.length ? { parentRow: parentRowOf(route) } : {}),
236
+ ...(options.context !== undefined ? { context: options.context } : {}),
237
+ signal,
238
+ };
239
+ let result;
240
+ try {
241
+ result = await source.getRows(request);
242
+ }
243
+ catch (err) {
244
+ if (!disposed && gen === generation) {
245
+ if (route.length === 0)
246
+ rootError = err;
247
+ options.onLoadError?.(route, err);
248
+ }
249
+ throw err;
250
+ }
251
+ if (disposed || gen !== generation)
252
+ throw new Error('stale');
253
+ if (route.length === 0) {
254
+ rootError = null;
255
+ if ('grandTotal' in result) {
256
+ grandTotal = result.grandTotal ?? null;
257
+ grandTotalStale = false;
258
+ }
259
+ }
260
+ if (pivotMode) {
261
+ if (result.pivotResultColumns)
262
+ pivotColumnsFromSource = result.pivotResultColumns;
263
+ for (const f of result.pivotResultFields ?? []) {
264
+ if (!pivotFields.includes(f))
265
+ pivotFields.push(f);
266
+ }
267
+ }
268
+ return {
269
+ rows: toChildren(route, startRow, result.rows),
270
+ rowCount: result.rowCount >= 0 ? result.rowCount : undefined,
271
+ };
272
+ }
273
+ finally {
274
+ release();
275
+ }
276
+ },
277
+ onChange: (s) => {
278
+ if (disposed || gen !== generation)
279
+ return;
280
+ // Runs once per block that just landed: open-by-default, and the
281
+ // debug log. Scanning only those blocks keeps this O(blockSize)
282
+ // rather than O(rows) on every change.
283
+ for (const index of diffBlocks(store))
284
+ openByDefaultIn(store, index);
285
+ if (store.refreshPending && !s.loading) {
286
+ store.refreshPending = false;
287
+ options.onStoreRefreshed?.(route);
288
+ }
289
+ // A level that is not infinite loads itself completely once it knows
290
+ // how big it is.
291
+ if (!infinite && !loadMore && s.lastRowKnown && s.rowCount != null) {
292
+ store.cache.setViewport(0, Math.max(0, s.rowCount - 1));
293
+ }
294
+ scheduleEmit();
295
+ },
296
+ });
297
+ stores.set(key, store);
298
+ return store;
299
+ }
300
+ function ensureStore(route) {
301
+ return stores.get(routeKey(route)) ?? createStore(route);
302
+ }
303
+ function dropSubtree(route) {
304
+ const prefix = routeKey(route);
305
+ for (const [key, store] of [...stores]) {
306
+ if (key === prefix || isUnder(key, route)) {
307
+ store.cache.dispose();
308
+ stores.delete(key);
309
+ }
310
+ }
311
+ }
312
+ function isUnder(key, route) {
313
+ // Routes are JSON arrays; a descendant's key starts with the parent's
314
+ // elements. Compare structurally rather than by string prefix so that
315
+ // ["a"] is not mistaken for a parent of ["ab"].
316
+ const parsed = JSON.parse(key);
317
+ if (parsed.length <= route.length)
318
+ return false;
319
+ return route.every((seg, i) => parsed[i] === seg);
320
+ }
321
+ /** Block indices that became `loaded` since the last call. Logs when `debug`. */
322
+ function diffBlocks(store) {
323
+ const now = new Map(store.cache.getCacheState().map((b) => [b.blockIndex, b.status]));
324
+ const landed = [];
325
+ for (const [index, status] of now) {
326
+ const before = store.seenBlocks.get(index);
327
+ if (before === status)
328
+ continue;
329
+ if (status === 'loaded')
330
+ landed.push(index);
331
+ // eslint-disable-next-line no-console -- opt-in diagnostics, see the debug option
332
+ if (debug)
333
+ console.debug(`[svgrid ssrm] ${routeKey(store.route)} block ${index}: ${status}`);
334
+ }
335
+ if (debug) {
336
+ for (const index of store.seenBlocks.keys()) {
337
+ // eslint-disable-next-line no-console -- opt-in diagnostics, see the debug option
338
+ if (!now.has(index))
339
+ console.debug(`[svgrid ssrm] ${routeKey(store.route)} block ${index}: evicted`);
340
+ }
341
+ }
342
+ store.seenBlocks = now;
343
+ return landed;
344
+ }
345
+ /**
346
+ * Open the groups in a freshly landed block that should start open: by
347
+ * `isGroupOpenByDefault`, by an `expandAll({ includeUnloaded })` in force,
348
+ * or because they are unbalanced. Once per group, so a block that is
349
+ * re-fetched does not re-open something the user closed.
350
+ */
351
+ function openByDefaultIn(store, blockIndex) {
352
+ const start = blockIndex * store.blockSize;
353
+ for (let i = start; i < start + store.blockSize; i += 1) {
354
+ const raw = store.cache.getRow(i);
355
+ if (rowPlaceholderState(raw))
356
+ continue;
357
+ const child = raw;
358
+ if (child.kind !== 'group')
359
+ continue;
360
+ const childRoute = [...store.route, child.key];
361
+ const ck = routeKey(childRoute);
362
+ if (seenGroups.has(ck))
363
+ continue;
364
+ seenGroups.add(ck);
365
+ const unbalanced = !!options.allowUnbalancedGroups && child.key === '';
366
+ if (unbalanced || autoExpand || options.isGroupOpenByDefault?.(childRoute, child.data)) {
367
+ expandGroup(childRoute);
368
+ }
369
+ }
370
+ }
371
+ // ---------------------------------------------------------- flatten
372
+ function flatten() {
373
+ const display = [];
374
+ const grid = [];
375
+ segments = [];
376
+ const pushRow = (d, data) => {
377
+ display.push(d);
378
+ grid.push(toGridRow(d, data));
379
+ };
380
+ if (grandTotalRow === 'top' && grandTotal)
381
+ pushRow(grandTotalDisplay(grandTotal), grandTotal);
382
+ const bodyStart = display.length;
383
+ /**
384
+ * A `loadMore` level shows its loaded prefix and a "more" row; rows
385
+ * past the first placeholder are not the grid's to scroll into.
386
+ */
387
+ const loadedPrefix = (rows) => {
388
+ let n = 0;
389
+ while (n < rows.length && !rowPlaceholderState(rows[n]))
390
+ n += 1;
391
+ return n;
392
+ };
393
+ const walk = (store, from = 0, to = Number.POSITIVE_INFINITY) => {
394
+ const rows = store.cache.rows();
395
+ const end = Math.min(rows.length, to, store.loadMore ? loadedPrefix(rows) : Number.POSITIVE_INFINITY);
396
+ let segStart = from;
397
+ let dispStart = display.length;
398
+ const flushSegment = (upTo) => {
399
+ if (upTo > segStart) {
400
+ segments.push({ store, displayStart: dispStart, storeStart: segStart, length: upTo - segStart });
401
+ }
402
+ };
403
+ for (let i = from; i < end; i += 1) {
404
+ const child = rows[i];
405
+ const ph = rowPlaceholderState(child);
406
+ if (ph) {
407
+ pushRow(ph === 'failed' ? FAILED_ROW : LOADING_ROW);
408
+ continue;
409
+ }
410
+ if (child.kind === 'leaf') {
411
+ pushRow({ kind: 'leaf', id: child.id, level: store.level, route: store.route, data: child.data }, child.data);
412
+ if (openDetails.size) {
413
+ const masterId = childId(child);
414
+ if (openDetails.has(masterId)) {
415
+ pushRow({ kind: 'detail', id: `${child.id}:detail`, level: store.level, route: store.route, masterId, master: child.data }, child.data);
416
+ }
417
+ }
418
+ continue;
419
+ }
420
+ const childRoute = [...store.route, child.key];
421
+ const ck = routeKey(childRoute);
422
+ const childStore = stores.get(ck);
423
+ const isExp = expanded.has(ck);
424
+ const unbalanced = !!options.allowUnbalancedGroups && child.key === '';
425
+ const hidden = isExp && (unbalanced || !!options.hideOpenParents);
426
+ if (!hidden) {
427
+ pushRow({
428
+ kind: 'group',
429
+ id: ck,
430
+ path: childRoute,
431
+ field: child.field,
432
+ key: child.key,
433
+ level: store.level,
434
+ expanded: isExp,
435
+ ...(pivotMode && !treeData && childRoute.length >= groupBy.length ? { expandable: false } : {}),
436
+ // The expander shows a spinner for the level opening, not for
437
+ // a block far down an open level that a deep scroll asked for.
438
+ loading: !!childStore && childStore.cache.getCacheState().some((b) => b.blockIndex === 0 && b.status === 'loading'),
439
+ aggregates: child.aggregates,
440
+ ...(child.childCount != null ? { childCount: child.childCount } : {}),
441
+ data: child.data,
442
+ }, child.data);
443
+ }
444
+ if (isExp && childStore) {
445
+ // This store's run ends here; the child's rows follow, then a new
446
+ // run of this store begins.
447
+ flushSegment(i + 1);
448
+ walk(childStore);
449
+ if (groupFooters && !hidden) {
450
+ pushRow({
451
+ kind: 'footer',
452
+ id: GROUP_TOTAL_ROW_ID_PREFIX + ck,
453
+ level: store.level + 1,
454
+ path: childRoute,
455
+ key: child.key,
456
+ aggregates: child.aggregates,
457
+ data: child.data,
458
+ }, child.data);
459
+ }
460
+ segStart = i + 1;
461
+ dispStart = display.length;
462
+ }
463
+ }
464
+ flushSegment(end);
465
+ if (store.loadMore) {
466
+ const count = store.cache.rowCount();
467
+ const loaded = end;
468
+ if (count == null || loaded < count) {
469
+ pushRow({
470
+ kind: 'more',
471
+ id: `sv-more:${store.key}`,
472
+ level: store.level,
473
+ path: store.route,
474
+ remaining: count == null ? store.blockSize : count - loaded,
475
+ loading: store.cache.getCacheState().some((b) => b.status === 'loading'),
476
+ });
477
+ }
478
+ }
479
+ };
480
+ const root = stores.get(routeKey([]));
481
+ if (root) {
482
+ if (paging && !paging.paginateChildRows) {
483
+ // A page of top-level rows. The root rows array is already sized to
484
+ // the count (placeholders for what has not loaded), so the page
485
+ // exists before its block does.
486
+ const count = root.cache.rowCount() ?? root.cache.rows().length;
487
+ pagerRowCount = count;
488
+ const start = Math.min(pageIndex * pageSize, Math.max(0, count - 1));
489
+ walk(root, start, start + pageSize);
490
+ }
491
+ else {
492
+ walk(root);
493
+ }
494
+ }
495
+ if (paging?.paginateChildRows) {
496
+ // A page of the flattened tree. Cut the body and re-base the segments
497
+ // that cross into the page; the grand total stays on every page.
498
+ const bodyLength = display.length - bodyStart;
499
+ pagerRowCount = bodyLength;
500
+ const start = Math.min(pageIndex * pageSize, Math.max(0, bodyLength - 1));
501
+ const stop = Math.min(bodyLength, start + pageSize);
502
+ display.splice(bodyStart + stop);
503
+ display.splice(bodyStart, start);
504
+ grid.splice(bodyStart + stop);
505
+ grid.splice(bodyStart, start);
506
+ const cut = [];
507
+ for (const seg of segments) {
508
+ const a = Math.max(seg.displayStart, bodyStart + start);
509
+ const b = Math.min(seg.displayStart + seg.length, bodyStart + stop);
510
+ if (b <= a)
511
+ continue;
512
+ cut.push({
513
+ store: seg.store,
514
+ displayStart: a - start,
515
+ storeStart: seg.storeStart + (a - seg.displayStart),
516
+ length: b - a,
517
+ });
518
+ }
519
+ segments = cut;
520
+ }
521
+ if (grandTotalRow === 'bottom' && grandTotal)
522
+ pushRow(grandTotalDisplay(grandTotal), grandTotal);
523
+ return { display, grid };
524
+ }
525
+ function pagination() {
526
+ if (!paging)
527
+ return null;
528
+ return {
529
+ pageIndex,
530
+ pageSize,
531
+ rowCount: pagerRowCount,
532
+ pageCount: Math.max(1, Math.ceil(pagerRowCount / pageSize)),
533
+ };
534
+ }
535
+ /**
536
+ * A display row as the grid sees it: the row's own fields at the top
537
+ * level plus the `__group` marker. Footers and the grand total also carry
538
+ * the flags the grid keys its `sv-grid-group-footer-row` /
539
+ * `sv-grid-grand-total-row` styling on, so they look like the client
540
+ * model's totals rather than like ordinary rows.
541
+ */
542
+ function toGridRow(d, data) {
543
+ if (d.kind === 'placeholder')
544
+ return d;
545
+ const row = { ...data, __group: d };
546
+ if (d.kind === 'footer')
547
+ row.__groupFooter = true;
548
+ if (d.kind === 'grandTotal') {
549
+ row.__groupFooter = true;
550
+ row.__grandTotal = true;
551
+ }
552
+ return row;
553
+ }
554
+ function grandTotalDisplay(data) {
555
+ const agg = {};
556
+ for (const a of aggregations)
557
+ agg[a.col] = data[a.col];
558
+ return { kind: 'grandTotal', id: GRAND_TOTAL_ROW_ID, level: 0, aggregates: agg, data };
559
+ }
560
+ function pinnedGrandTotal() {
561
+ if (!grandTotal)
562
+ return undefined;
563
+ return [toGridRow(grandTotalDisplay(grandTotal), grandTotal)];
564
+ }
565
+ // ------------------------------------------------------------- state
566
+ let current = {
567
+ displayRows: [],
568
+ gridRows: [],
569
+ groupBy: [...groupBy],
570
+ aggregations: [...aggregations],
571
+ rowCount: null,
572
+ loading: true,
573
+ saving: false,
574
+ error: null,
575
+ sortModel,
576
+ filterModel,
577
+ expandedGroups: [],
578
+ openDetails: [],
579
+ grandTotal: null,
580
+ pivotBy: [...pivotBy],
581
+ pivotMode,
582
+ pivotResultFields: [],
583
+ pagination: pagination(),
584
+ };
585
+ function buildState() {
586
+ const root = stores.get(routeKey([]));
587
+ const { display, grid } = flatten();
588
+ const rootLoaded = !!root && root.cache.getCacheState().some((b) => b.status === 'loaded');
589
+ return {
590
+ displayRows: display,
591
+ gridRows: grid,
592
+ groupBy: [...groupBy],
593
+ aggregations: [...aggregations],
594
+ rowCount: root?.cache.rowCount() ?? null,
595
+ loading: !!root && !rootLoaded && root.cache.getCacheState().some((b) => b.status === 'loading'),
596
+ saving: writesInFlight > 0,
597
+ error: rootError,
598
+ sortModel,
599
+ filterModel,
600
+ expandedGroups: [...expanded],
601
+ openDetails: [...openDetails],
602
+ grandTotal: grandTotal ?? null,
603
+ pivotBy: [...pivotBy],
604
+ pivotMode,
605
+ pivotResultFields: [...pivotFields],
606
+ pagination: pagination(),
607
+ };
608
+ }
609
+ function scheduleEmit() {
610
+ if (disposed || emitScheduled)
611
+ return;
612
+ emitScheduled = true;
613
+ queueMicrotask(() => {
614
+ emitScheduled = false;
615
+ if (disposed)
616
+ return;
617
+ current = buildState();
618
+ options.onChange?.(current);
619
+ for (const notify of subscribers)
620
+ notify();
621
+ });
622
+ }
623
+ // ------------------------------------------------------- operations
624
+ function applyViewport() {
625
+ const [start, end] = lastViewport;
626
+ for (const seg of segments) {
627
+ if (seg.store.loadMore)
628
+ continue;
629
+ const segEnd = seg.displayStart + seg.length - 1;
630
+ if (segEnd < start || seg.displayStart > end)
631
+ continue;
632
+ const from = Math.max(start, seg.displayStart) - seg.displayStart + seg.storeStart;
633
+ const to = Math.min(end, segEnd) - seg.displayStart + seg.storeStart;
634
+ seg.store.cache.setViewport(from, to);
635
+ }
636
+ }
637
+ /** Ask a level for its first block, so it shows something before any scroll. */
638
+ function primeStore(store) {
639
+ if (store.loadMore) {
640
+ store.cache.setViewport(0, store.blockSize - 1);
641
+ return;
642
+ }
643
+ const from = store.level === 0 && paging && !paging.paginateChildRows ? pageIndex * pageSize : 0;
644
+ store.cache.setViewport(from, from + Math.min(store.blockSize, skeletonRows) - 1);
645
+ }
646
+ function expandGroup(route) {
647
+ if (disposed)
648
+ return;
649
+ // Under pivot the innermost group rows ARE the result; there is nothing
650
+ // beneath them to fetch.
651
+ if (pivotMode && !treeData && route.length >= groupBy.length)
652
+ return;
653
+ const key = routeKey(route);
654
+ if (expanded.has(key))
655
+ return;
656
+ expanded.add(key);
657
+ const store = ensureStore(route);
658
+ // A known child count sizes the scrollbar (and the skeletons) right away.
659
+ const parent = parentRowOf(route);
660
+ const known = parent !== undefined ? childCountOf?.(parent) : undefined;
661
+ if (known != null && store.cache.rowCount() == null)
662
+ store.cache.setRowCount(known, true);
663
+ primeStore(store);
664
+ options.onGroupOpened?.(route);
665
+ scheduleEmit();
666
+ }
667
+ function collapseGroup(route) {
668
+ const key = routeKey(route);
669
+ if (!expanded.delete(key))
670
+ return;
671
+ if (options.purgeClosedGroups)
672
+ dropSubtree(route);
673
+ scheduleEmit();
674
+ }
675
+ /** Start over below `route` (or everywhere), keeping the expansion set. */
676
+ function rebuild(route = []) {
677
+ if (route.length === 0) {
678
+ // A full reset. Disposing every cache aborts every request, and the
679
+ // generation bump discards any that slipped past the abort.
680
+ generation += 1;
681
+ for (const s of stores.values())
682
+ s.cache.dispose();
683
+ stores.clear();
684
+ grandTotal = undefined;
685
+ grandTotalStale = false;
686
+ pivotFields = [];
687
+ pivotColumnsFromSource = null;
688
+ }
689
+ else {
690
+ dropSubtree(route);
691
+ }
692
+ const target = ensureStore(route);
693
+ primeStore(target);
694
+ // Expanded descendants come back too, each priming its own first block.
695
+ for (const key of expanded) {
696
+ const r = JSON.parse(key);
697
+ if (r.length > route.length && route.every((seg, i) => r[i] === seg))
698
+ primeStore(ensureStore(r));
699
+ }
700
+ scheduleEmit();
701
+ }
702
+ /** Which stores a sort touches, under the selective rules. */
703
+ function storesAffectedBySort(next) {
704
+ const all = [...stores.values()];
705
+ if (options.sortAllLevels || treeData)
706
+ return all;
707
+ const aggCols = new Set(aggregations.map((a) => a.col));
708
+ const levels = new Set();
709
+ let everything = false;
710
+ let leaves = false;
711
+ for (const s of next) {
712
+ const gi = groupBy.indexOf(s.id);
713
+ if (aggCols.has(s.id))
714
+ everything = true;
715
+ else if (gi >= 0)
716
+ levels.add(gi);
717
+ else
718
+ leaves = true;
719
+ }
720
+ // Clearing the sort has to put every level back to server order.
721
+ if (next.length === 0)
722
+ everything = true;
723
+ if (everything)
724
+ return all;
725
+ return all.filter((s) => levels.has(s.level) || (leaves && isLeafLevel(s.level)));
726
+ }
727
+ function sortLocally(store) {
728
+ const st = store.cache.getCacheState();
729
+ const count = store.cache.rowCount();
730
+ if (!store.cache.lastRowKnown() || count == null)
731
+ return false;
732
+ const loaded = st.filter((b) => b.status === 'loaded').length;
733
+ if (loaded * store.blockSize < count)
734
+ return false;
735
+ const rows = store.cache.rows().filter((r) => !rowPlaceholderState(r));
736
+ if (rows.length !== count)
737
+ return false;
738
+ const valueOf = (c, col) => c.data[col];
739
+ rows.sort((a, b) => {
740
+ for (const s of sortModel) {
741
+ const cmp = compareValues(valueOf(a, s.id), valueOf(b, s.id));
742
+ if (cmp !== 0)
743
+ return s.desc ? -cmp : cmp;
744
+ }
745
+ return 0;
746
+ });
747
+ store.cache.applyRows(0, rows, count);
748
+ return true;
749
+ }
750
+ function setSort(next) {
751
+ sortModel = next;
752
+ for (const store of storesAffectedBySort(next)) {
753
+ if (options.clientSideSort && sortLocally(store))
754
+ continue;
755
+ store.refreshPending = true;
756
+ store.cache.refresh();
757
+ }
758
+ scheduleEmit();
759
+ }
760
+ function setFilter(next) {
761
+ const previous = filterModel;
762
+ filterModel = Array.isArray(next.columns)
763
+ ? { global: next.global, columns: toServerFilterColumns(next) }
764
+ : next;
765
+ if (!options.onlyRefreshFilteredGroups)
766
+ return rebuild();
767
+ // Selective: the levels whose column changed. Everything else keeps its
768
+ // stale counts, which is the documented trade.
769
+ const changed = new Set();
770
+ const prevCols = previous.columns ?? {};
771
+ const nextCols = filterModel.columns ?? {};
772
+ for (const c of new Set([...Object.keys(prevCols), ...Object.keys(nextCols)])) {
773
+ if (JSON.stringify(prevCols[c]) !== JSON.stringify(nextCols[c]))
774
+ changed.add(c);
775
+ }
776
+ const globalChanged = (previous.global ?? '') !== (filterModel.global ?? '');
777
+ const exprChanged = JSON.stringify(previous.expression) !== JSON.stringify(filterModel.expression);
778
+ const aggCols = new Set(aggregations.map((a) => a.col));
779
+ if (globalChanged || exprChanged || [...changed].some((c) => aggCols.has(c)))
780
+ return rebuild();
781
+ const levels = new Set();
782
+ let leaves = false;
783
+ for (const c of changed) {
784
+ const gi = groupBy.indexOf(c);
785
+ if (gi >= 0)
786
+ levels.add(gi);
787
+ else
788
+ leaves = true;
789
+ }
790
+ for (const store of stores.values()) {
791
+ if (levels.has(store.level) || (leaves && isLeafLevel(store.level))) {
792
+ store.refreshPending = true;
793
+ store.cache.refresh();
794
+ }
795
+ }
796
+ scheduleEmit();
797
+ }
798
+ function levelState(store) {
799
+ const blocks = store.cache.getCacheState();
800
+ return {
801
+ route: store.route,
802
+ level: store.level,
803
+ rowCount: store.cache.rowCount(),
804
+ lastRowKnown: store.cache.lastRowKnown(),
805
+ loading: blocks.some((b) => b.status === 'loading'),
806
+ failedBlocks: blocks.filter((b) => b.status === 'failed').map((b) => b.blockIndex),
807
+ blocks,
808
+ };
809
+ }
810
+ // ------------------------------------------------------ transactions
811
+ /** The id a transaction row goes by at a level: key at a group level, row id at a leaf level. */
812
+ function transactionId(store, row) {
813
+ if (typeof row === 'string')
814
+ return row;
815
+ if (getRowId) {
816
+ const id = getRowId(row);
817
+ if (id === GRAND_TOTAL_ROW_ID)
818
+ return id;
819
+ }
820
+ if (isLeafLevel(store.level) || treeData) {
821
+ if (!getRowId)
822
+ throw new Error('createServerRowModel: transactions need getRowId');
823
+ return getRowId(row);
824
+ }
825
+ const field = groupBy[store.level];
826
+ const v = row[field];
827
+ return v == null ? '' : String(v);
828
+ }
829
+ /** The id a CACHED child goes by, in the same terms. */
830
+ function childId(child) {
831
+ if (child.kind === 'group')
832
+ return child.key;
833
+ // Leaf ids are `${storeKey}:${rowId}`; the row id is the tail.
834
+ return getRowId ? getRowId(child.data) : child.id.slice(child.id.lastIndexOf(':') + 1);
835
+ }
836
+ function storeStatus(store) {
837
+ const blocks = store.cache.getCacheState();
838
+ if (blocks.length === 0)
839
+ return 'storeWaitingToLoad';
840
+ if (blocks.some((b) => b.status === 'loaded'))
841
+ return 'applied';
842
+ if (blocks.some((b) => b.status === 'loading'))
843
+ return 'storeLoading';
844
+ return 'storeLoadingFailed';
845
+ }
846
+ function applyTransaction(tx) {
847
+ const result = { status: 'applied', add: [], update: [], remove: [] };
848
+ if (disposed)
849
+ return { ...result, status: 'storeNotFound' };
850
+ if (options.isApplyTransaction && !options.isApplyTransaction(tx)) {
851
+ return { ...result, status: 'cancelled' };
852
+ }
853
+ const route = tx.route ?? [];
854
+ const store = stores.get(routeKey(route));
855
+ if (!store)
856
+ return { ...result, status: 'storeNotFound' };
857
+ const status = storeStatus(store);
858
+ if (status !== 'applied')
859
+ return { ...result, status };
860
+ // The grand total answers to its fixed id, on the root route only.
861
+ const isTotal = (row) => route.length === 0 && typeof row !== 'string' && !!getRowId && getRowId(row) === GRAND_TOTAL_ROW_ID;
862
+ for (const row of tx.update ?? []) {
863
+ if (isTotal(row)) {
864
+ grandTotal = row;
865
+ result.update.push(GRAND_TOTAL_ROW_ID);
866
+ continue;
867
+ }
868
+ const id = transactionId(store, row);
869
+ const index = store.cache.findIndex((c) => childId(c) === id);
870
+ if (index < 0)
871
+ continue;
872
+ const [child] = toChildren(route, index, [row]);
873
+ store.cache.patch(index, child);
874
+ result.update.push(id);
875
+ }
876
+ for (const row of tx.remove ?? []) {
877
+ if (isTotal(row)) {
878
+ grandTotal = null;
879
+ result.remove.push(GRAND_TOTAL_ROW_ID);
880
+ continue;
881
+ }
882
+ const id = transactionId(store, row);
883
+ const index = store.cache.findIndex((c) => childId(c) === id);
884
+ // Outside the cache means outside what we can show; when the count is
885
+ // known the row is simply not here, and the count stands until the
886
+ // caller says otherwise with `rowCount`.
887
+ if (index < 0)
888
+ continue;
889
+ const wasGroup = store.cache.getRow(index).kind === 'group';
890
+ if (store.cache.remove(index, 1) > 0) {
891
+ result.remove.push(id);
892
+ // A removed group takes its expansion and its cached children with it.
893
+ if (wasGroup) {
894
+ const childRoute = [...route, id];
895
+ expanded.delete(routeKey(childRoute));
896
+ dropSubtree(childRoute);
897
+ }
898
+ }
899
+ }
900
+ if (tx.add && tx.add.length) {
901
+ const total = store.cache.rowCount();
902
+ const at = tx.addIndex ?? (total ?? store.cache.rows().length);
903
+ const children = toChildren(route, at, tx.add);
904
+ const placed = store.cache.insert(at, children);
905
+ if (placed)
906
+ for (const c of children)
907
+ result.add.push(childId(c));
908
+ // Adding the grand total means setting it.
909
+ for (const row of tx.add)
910
+ if (isTotal(row))
911
+ grandTotal = row;
912
+ }
913
+ if (typeof tx.rowCount === 'number')
914
+ store.cache.setRowCount(tx.rowCount, true);
915
+ // The parent group row shows this level's count beside its key; adds
916
+ // and removes move it by the net change. Aggregates stay as they were:
917
+ // a refresh of the parent recomputes them, as documented.
918
+ const delta = result.add.length - result.remove.length;
919
+ if (delta !== 0 && route.length > 0)
920
+ adjustChildCount(route, delta);
921
+ scheduleEmit();
922
+ return result;
923
+ }
924
+ function adjustChildCount(route, delta) {
925
+ const parent = stores.get(routeKey(route.slice(0, -1)));
926
+ if (!parent)
927
+ return;
928
+ const key = route[route.length - 1];
929
+ const index = parent.cache.findIndex((c) => c.kind === 'group' && c.key === key);
930
+ if (index < 0)
931
+ return;
932
+ const child = parent.cache.getRow(index);
933
+ if (child.kind !== 'group' || child.childCount == null)
934
+ return;
935
+ parent.cache.patch(index, { ...child, childCount: Math.max(0, child.childCount + delta) });
936
+ }
937
+ const asyncQueue = [];
938
+ let asyncTimer = null;
939
+ function flushAsyncTransactions() {
940
+ if (asyncTimer)
941
+ clearTimeout(asyncTimer);
942
+ asyncTimer = null;
943
+ if (asyncQueue.length === 0)
944
+ return;
945
+ const batch = asyncQueue.splice(0);
946
+ const results = [];
947
+ for (const item of batch) {
948
+ const result = applyTransaction(item.tx);
949
+ // A level still loading gets its transaction back in the queue for
950
+ // the next flush; nothing else is retried.
951
+ if (result.status === 'storeLoading' || result.status === 'storeWaitingToLoad') {
952
+ asyncQueue.push(item);
953
+ continue;
954
+ }
955
+ results.push(result);
956
+ item.callback?.(result);
957
+ }
958
+ if (results.length)
959
+ options.onAsyncTransactionsFlushed?.(results);
960
+ if (asyncQueue.length)
961
+ scheduleAsyncFlush();
962
+ }
963
+ function scheduleAsyncFlush() {
964
+ if (asyncTimer || disposed)
965
+ return;
966
+ asyncTimer = setTimeout(() => {
967
+ asyncTimer = null;
968
+ flushAsyncTransactions();
969
+ }, options.asyncTransactionWaitMs ?? 50);
970
+ }
971
+ /** Run one write with `saving` raised around it; the flag is "any write in flight". */
972
+ async function saving(write) {
973
+ writesInFlight += 1;
974
+ scheduleEmit();
975
+ try {
976
+ return await write();
977
+ }
978
+ finally {
979
+ writesInFlight -= 1;
980
+ scheduleEmit();
981
+ }
982
+ }
983
+ /** The level and index holding a leaf row with this id, searching loaded rows only. */
984
+ function locate(id) {
985
+ for (const store of stores.values()) {
986
+ const index = store.cache.findIndex((c) => childId(c) === id);
987
+ if (index >= 0)
988
+ return { store, index };
989
+ }
990
+ return null;
991
+ }
992
+ function updateRowData(id, patch, opts) {
993
+ const hit = locate(id);
994
+ if (!hit)
995
+ return false;
996
+ const current = hit.store.cache.getRow(hit.index);
997
+ const next = (opts?.replace ? patch : { ...current.data, ...patch });
998
+ const [child] = toChildren(hit.store.route, hit.index, [next]);
999
+ hit.store.cache.patch(hit.index, child);
1000
+ scheduleEmit();
1001
+ return true;
1002
+ }
1003
+ // --------------------------------------------------------- selection
1004
+ const selectionModel = options.selection
1005
+ ? createServerSelectionModel({
1006
+ groupSelects: options.selection.groupSelects,
1007
+ // The grid re-derives the header checkbox and the row ticks from a
1008
+ // row-model notification, so a selection change is one.
1009
+ onChange: scheduleEmit,
1010
+ leafCount: groupLeafCount,
1011
+ })
1012
+ : null;
1013
+ /**
1014
+ * Leaves under a loaded group row, from its `count` aggregate (the same
1015
+ * one `leafTotal` reads on the grand total); null without one, or when
1016
+ * the group row is not loaded.
1017
+ */
1018
+ function groupLeafCount(route) {
1019
+ const counter = aggregations.find((a) => a.fn === 'count');
1020
+ if (!counter || route.length === 0)
1021
+ return null;
1022
+ const parent = stores.get(routeKey(route.slice(0, -1)));
1023
+ if (!parent)
1024
+ return null;
1025
+ const key = route[route.length - 1];
1026
+ const index = parent.cache.findIndex((c) => c.kind === 'group' && c.key === key);
1027
+ if (index < 0)
1028
+ return null;
1029
+ const child = parent.cache.getRow(index);
1030
+ if (child.kind !== 'group')
1031
+ return null;
1032
+ const n = Number(child.aggregates[counter.col]);
1033
+ return Number.isFinite(n) ? n : null;
1034
+ }
1035
+ /** Where a grid row lives in selection terms: its parent route and its own id. */
1036
+ function locateForSelection(row) {
1037
+ const d = row.__group;
1038
+ if (!d)
1039
+ return null;
1040
+ if (d.kind === 'group')
1041
+ return { route: d.path.slice(0, -1), id: d.key, isGroup: true };
1042
+ if (d.kind === 'leaf') {
1043
+ return { route: d.route ?? [], id: getRowId ? getRowId(d.data) : d.id, isGroup: false };
1044
+ }
1045
+ return null;
1046
+ }
1047
+ /**
1048
+ * How many leaves the whole result holds, for an honest select-all count:
1049
+ * the root count when nothing is grouped, else the `count` aggregation
1050
+ * on the grand total (the only place the server says it), else unknown.
1051
+ */
1052
+ function leafTotal() {
1053
+ if (treeData || groupBy.length === 0)
1054
+ return current.rowCount;
1055
+ const counter = aggregations.find((a) => a.fn === 'count');
1056
+ if (!counter || !grandTotal)
1057
+ return null;
1058
+ const n = Number(grandTotal[counter.col]);
1059
+ return Number.isFinite(n) ? n : null;
1060
+ }
1061
+ const selectionAdapter = selectionModel
1062
+ ? {
1063
+ isSelected: (_rowId, row) => {
1064
+ const at = locateForSelection(row);
1065
+ return at ? selectionModel.isSelected(at.route, at.id) : false;
1066
+ },
1067
+ headerState: () => selectionModel.headerState(),
1068
+ toggle: (_rowId, row, next) => {
1069
+ const at = locateForSelection(row);
1070
+ if (at)
1071
+ selectionModel.toggle(at.route, at.id, next, at.isGroup);
1072
+ },
1073
+ toggleAll: (next) => selectionModel.setAll(next),
1074
+ selectedCount: () => selectionModel.selectedCount(leafTotal()),
1075
+ bulkUpdate: (patch) => bulkUpdate(patch),
1076
+ }
1077
+ : undefined;
1078
+ async function bulkUpdate(patch) {
1079
+ if (!selectionModel)
1080
+ throw new Error('createServerRowModel: bulkUpdate needs the `selection` option');
1081
+ if (!source.updateWhere) {
1082
+ throw new Error('createServerRowModel: the datasource does not implement updateWhere(), so a bulk edit ' +
1083
+ 'cannot reach rows the grid has not loaded');
1084
+ }
1085
+ const rule = selectionModel.getState();
1086
+ // A nested rule is keyed level by level by the group columns; say which.
1087
+ const changed = await source.updateWhere(filterModel, patch, 'selectAllChildren' in rule ? { ...rule, groupBy: [...groupBy] } : rule);
1088
+ if (disposed)
1089
+ return changed;
1090
+ // Every level may have changed rows AND aggregates; reload them in place.
1091
+ for (const store of stores.values()) {
1092
+ store.refreshPending = true;
1093
+ store.cache.refresh();
1094
+ }
1095
+ grandTotalStale = true;
1096
+ scheduleEmit();
1097
+ return changed;
1098
+ }
1099
+ // ------------------------------------------------------------ pivot
1100
+ /**
1101
+ * The columns that replace the grid's own while pivoting: the leading
1102
+ * columns the app named, then one header group per pivot key with a value
1103
+ * column per aggregation under it. Null outside pivot mode, or before the
1104
+ * first pivoted response has said what fields exist.
1105
+ */
1106
+ function pivotResultColumns() {
1107
+ if (!pivotMode)
1108
+ return null;
1109
+ const leading = options.pivotLeadingColumns ?? [];
1110
+ if (pivotColumnsFromSource)
1111
+ return [...leading, ...pivotColumnsFromSource];
1112
+ if (pivotFields.length === 0)
1113
+ return null;
1114
+ return [
1115
+ ...leading,
1116
+ ...buildPivotResultColumns(pivotFields, aggregations, {
1117
+ separator: pivotSeparator,
1118
+ pivotResultColumn: options.pivotResultColumn,
1119
+ rowTotals: pivotRowTotals,
1120
+ }),
1121
+ ];
1122
+ }
1123
+ // ---------------------------------------------------- the controller
1124
+ // The id a leaf's detail is kept under: the app's row id, else the leaf's own.
1125
+ const leafDetailId = (g) => (getRowId ? getRowId(g.data) : g.id.slice(g.id.lastIndexOf(':') + 1));
1126
+ const nav = {
1127
+ isGroup: (row) => row.__group?.kind === 'group',
1128
+ level: (row) => row.__group && 'level' in row.__group ? row.__group.level : 0,
1129
+ expanded: (row) => row.__group?.kind === 'group' ? row.__group.expanded : false,
1130
+ onToggle: (row) => {
1131
+ if (row.__group?.kind === 'group')
1132
+ model.toggleGroup(row.__group);
1133
+ else if (row.__group?.kind === 'more')
1134
+ model.loadMoreChildren(row.__group.path);
1135
+ },
1136
+ // A leaf's detail, by the id the app gave the row; a detail row toggles its master.
1137
+ toggleDetail: (row) => {
1138
+ const g = row.__group;
1139
+ if (g?.kind === 'leaf')
1140
+ model.toggleDetail(leafDetailId(g));
1141
+ else if (g?.kind === 'detail')
1142
+ model.toggleDetail(g.masterId);
1143
+ },
1144
+ // Only a leaf has a detail to open; a group's expander is in the group column.
1145
+ hasDetail: (row) => row.__group?.kind === 'leaf',
1146
+ detailOpen: (row) => {
1147
+ const g = row.__group;
1148
+ return g?.kind === 'leaf' ? model.isDetailOpen(leafDetailId(g)) : false;
1149
+ },
1150
+ };
1151
+ const model = {
1152
+ refresh(opts) {
1153
+ if (disposed)
1154
+ return;
1155
+ const route = opts?.route ?? [];
1156
+ const store = stores.get(routeKey(route));
1157
+ if (opts?.purge || !store)
1158
+ return rebuild(route);
1159
+ store.refreshPending = true;
1160
+ // The top level re-read in place brings its aggregates back fresh;
1161
+ // the grand total is one of them.
1162
+ if (route.length === 0)
1163
+ grandTotalStale = true;
1164
+ store.cache.refresh();
1165
+ scheduleEmit();
1166
+ },
1167
+ retryLoads() {
1168
+ for (const store of stores.values())
1169
+ store.cache.retryFailed();
1170
+ },
1171
+ setSort,
1172
+ setFilter,
1173
+ setGroupBy(next) {
1174
+ model.setLayout({ groupBy: next });
1175
+ },
1176
+ setAggregations(next) {
1177
+ model.setLayout({ aggregations: next });
1178
+ },
1179
+ setPivot(next) {
1180
+ model.setLayout(next);
1181
+ },
1182
+ setLayout(next) {
1183
+ if (disposed)
1184
+ return;
1185
+ const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
1186
+ const groupChanged = next.groupBy !== undefined && !same(next.groupBy, groupBy);
1187
+ const aggChanged = next.aggregations !== undefined && !same(next.aggregations, aggregations);
1188
+ const pivotChanged = (next.pivotBy !== undefined && !same(next.pivotBy, pivotBy)) ||
1189
+ (next.pivotMode !== undefined && next.pivotMode !== pivotMode);
1190
+ // The totals group is built from what is already loaded: no request.
1191
+ if (next.pivotRowTotals !== undefined && next.pivotRowTotals !== pivotRowTotals) {
1192
+ pivotRowTotals = next.pivotRowTotals;
1193
+ if (!groupChanged && !aggChanged && !pivotChanged) {
1194
+ scheduleEmit();
1195
+ return;
1196
+ }
1197
+ }
1198
+ if (!groupChanged && !aggChanged && !pivotChanged)
1199
+ return;
1200
+ if (next.groupBy)
1201
+ groupBy = [...next.groupBy];
1202
+ if (next.aggregations)
1203
+ aggregations = [...next.aggregations];
1204
+ if (next.pivotBy)
1205
+ pivotBy = [...next.pivotBy];
1206
+ if (next.pivotMode !== undefined)
1207
+ pivotMode = next.pivotMode;
1208
+ // New group columns make new rows, and pivoted group rows and plain
1209
+ // group rows are different rows: neither keeps its open state. A
1210
+ // change of aggregations alone keeps the tree open and refetches it.
1211
+ if (groupChanged || pivotChanged) {
1212
+ expanded.clear();
1213
+ seenGroups.clear();
1214
+ }
1215
+ rebuild();
1216
+ },
1217
+ toggleGroup(row) {
1218
+ if (expanded.has(row.id))
1219
+ collapseGroup(row.path);
1220
+ else
1221
+ expandGroup(row.path);
1222
+ },
1223
+ expandGroup,
1224
+ collapseGroup,
1225
+ toggleDetail(id, open) {
1226
+ const next = open ?? !openDetails.has(id);
1227
+ if (next === openDetails.has(id))
1228
+ return;
1229
+ if (next)
1230
+ openDetails.add(id);
1231
+ else
1232
+ openDetails.delete(id);
1233
+ scheduleEmit();
1234
+ },
1235
+ isDetailOpen(id) {
1236
+ return openDetails.has(id);
1237
+ },
1238
+ closeAllDetails() {
1239
+ if (!openDetails.size)
1240
+ return;
1241
+ openDetails.clear();
1242
+ scheduleEmit();
1243
+ },
1244
+ expandAll(opts) {
1245
+ autoExpand = !!opts?.includeUnloaded;
1246
+ for (const store of [...stores.values()]) {
1247
+ for (const child of store.cache.rows()) {
1248
+ if (rowPlaceholderState(child) || child.kind !== 'group')
1249
+ continue;
1250
+ expandGroup([...store.route, child.key]);
1251
+ }
1252
+ }
1253
+ },
1254
+ collapseAll() {
1255
+ autoExpand = false;
1256
+ const open = [...expanded].map((k) => JSON.parse(k));
1257
+ expanded.clear();
1258
+ if (options.purgeClosedGroups)
1259
+ for (const r of open)
1260
+ dropSubtree(r);
1261
+ scheduleEmit();
1262
+ },
1263
+ isExpanded: (route) => expanded.has(routeKey(route)),
1264
+ applyRowData({ route = [], rows, rowCount, startRow = 0 }) {
1265
+ if (disposed)
1266
+ return;
1267
+ const store = ensureStore(route);
1268
+ store.cache.applyRows(startRow, toChildren(route, startRow, rows), rowCount);
1269
+ scheduleEmit();
1270
+ },
1271
+ applyTransaction,
1272
+ applyTransactionAsync(tx, callback) {
1273
+ asyncQueue.push({ tx, callback });
1274
+ scheduleAsyncFlush();
1275
+ },
1276
+ flushAsyncTransactions,
1277
+ updateRowData,
1278
+ async createRow(input, route = [], addIndex) {
1279
+ if (!source.createRow)
1280
+ throw new Error('createServerRowModel: the datasource does not implement createRow()');
1281
+ const saved = await saving(() => source.createRow(input));
1282
+ if (disposed)
1283
+ return saved;
1284
+ applyTransaction({ route, add: [saved], ...(addIndex !== undefined ? { addIndex } : {}) });
1285
+ return saved;
1286
+ },
1287
+ async updateRow(id, patch) {
1288
+ if (!source.updateRow)
1289
+ throw new Error('createServerRowModel: the datasource does not implement updateRow()');
1290
+ const before = options.optimistic ? locate(id) : null;
1291
+ const previous = before ? before.store.cache.getRow(before.index).data : null;
1292
+ // Show the patch now; the server answer replaces it, a rejection restores it.
1293
+ if (previous)
1294
+ updateRowData(id, patch);
1295
+ let saved;
1296
+ try {
1297
+ saved = await saving(() => source.updateRow(id, patch));
1298
+ }
1299
+ catch (err) {
1300
+ if (previous && !disposed)
1301
+ updateRowData(id, previous, { replace: true });
1302
+ throw err;
1303
+ }
1304
+ if (disposed)
1305
+ return saved;
1306
+ const hit = locate(id);
1307
+ if (hit)
1308
+ applyTransaction({ route: hit.store.route, update: [saved] });
1309
+ return saved;
1310
+ },
1311
+ async moveRow(id, toRoute, opts = {}) {
1312
+ const from = locate(id);
1313
+ if (!from)
1314
+ throw new Error(`createServerRowModel: moveRow: row ${id} is not loaded`);
1315
+ const child = from.store.cache.getRow(from.index);
1316
+ let row = child.data;
1317
+ if (opts.patch) {
1318
+ if (!source.updateRow)
1319
+ throw new Error('createServerRowModel: the datasource does not implement updateRow()');
1320
+ row = await saving(() => source.updateRow(id, opts.patch));
1321
+ if (disposed)
1322
+ return { row, status: 'cancelled' };
1323
+ }
1324
+ const fromRoute = [...from.store.route];
1325
+ // A moved group's children were cached under its old route.
1326
+ if (child.kind === 'group')
1327
+ dropSubtree([...fromRoute, child.key]);
1328
+ applyTransaction({ route: fromRoute, remove: [id] });
1329
+ const res = applyTransaction({
1330
+ route: [...toRoute],
1331
+ add: [row],
1332
+ ...(opts.addIndex !== undefined ? { addIndex: opts.addIndex } : {}),
1333
+ });
1334
+ // A target level nothing has opened cannot take the row, but its group
1335
+ // row is on screen and the row did go there: the badge follows now
1336
+ // rather than at the next refresh.
1337
+ if (res.status === 'storeNotFound' && toRoute.length)
1338
+ adjustChildCount([...toRoute], 1);
1339
+ return { row, status: res.status };
1340
+ },
1341
+ async deleteRow(id) {
1342
+ if (!source.deleteRow)
1343
+ throw new Error('createServerRowModel: the datasource does not implement deleteRow()');
1344
+ const before = options.optimistic ? locate(id) : null;
1345
+ const removed = before ? before.store.cache.getRow(before.index).data : null;
1346
+ const route = before ? [...before.store.route] : [];
1347
+ const index = before?.index ?? 0;
1348
+ // Take the row out now; a rejection puts it back where it was.
1349
+ if (removed)
1350
+ applyTransaction({ route, remove: [id] });
1351
+ try {
1352
+ await saving(() => source.deleteRow(id));
1353
+ }
1354
+ catch (err) {
1355
+ if (removed && !disposed)
1356
+ applyTransaction({ route, add: [removed], addIndex: index });
1357
+ throw err;
1358
+ }
1359
+ if (disposed || removed)
1360
+ return;
1361
+ const hit = locate(id);
1362
+ if (hit)
1363
+ applyTransaction({ route: hit.store.route, remove: [id] });
1364
+ },
1365
+ setViewport(startIndex, endIndex) {
1366
+ lastViewport = [Math.max(0, startIndex), Math.max(startIndex, endIndex)];
1367
+ applyViewport();
1368
+ },
1369
+ setPage(next) {
1370
+ if (!paging || disposed)
1371
+ return;
1372
+ const last = Math.max(0, Math.ceil(pagerRowCount / pageSize) - 1);
1373
+ const clamped = Math.max(0, Math.min(Math.floor(next), last));
1374
+ if (clamped === pageIndex)
1375
+ return;
1376
+ pageIndex = clamped;
1377
+ // The grid keeps reporting the same on-screen indices from page to
1378
+ // page, so the new page asks for its own blocks here.
1379
+ current = buildState();
1380
+ applyViewport();
1381
+ const root = stores.get(routeKey([]));
1382
+ if (root)
1383
+ primeStore(root);
1384
+ scheduleEmit();
1385
+ },
1386
+ setPageSize(next) {
1387
+ if (!paging || disposed)
1388
+ return;
1389
+ const size = Math.max(1, Math.floor(next));
1390
+ if (size === pageSize)
1391
+ return;
1392
+ const firstRow = pageIndex * pageSize;
1393
+ pageSize = size;
1394
+ pageIndex = Math.floor(firstRow / size);
1395
+ current = buildState();
1396
+ applyViewport();
1397
+ const root = stores.get(routeKey([]));
1398
+ if (root)
1399
+ primeStore(root);
1400
+ scheduleEmit();
1401
+ },
1402
+ loadMoreChildren(route) {
1403
+ const store = stores.get(routeKey(route));
1404
+ if (!store || disposed)
1405
+ return;
1406
+ const rows = store.cache.rows();
1407
+ let loaded = 0;
1408
+ while (loaded < rows.length && !rowPlaceholderState(rows[loaded]))
1409
+ loaded += 1;
1410
+ store.cache.setViewport(loaded, loaded + store.blockSize - 1);
1411
+ scheduleEmit();
1412
+ },
1413
+ selectionModel,
1414
+ getSelectionState: () => selectionModel?.getState() ?? null,
1415
+ setSelectionState(state) {
1416
+ selectionModel?.setState(isCallbackSelectionState(state) ? fromCallbackSelectionState(state) : state);
1417
+ },
1418
+ bulkUpdate,
1419
+ getLevelState(route = []) {
1420
+ const store = stores.get(routeKey(route));
1421
+ return store ? levelState(store) : null;
1422
+ },
1423
+ levelStates: () => [...stores.values()].map(levelState),
1424
+ getCacheState() {
1425
+ const out = [];
1426
+ for (const store of stores.values()) {
1427
+ for (const b of store.cache.getCacheState())
1428
+ out.push({ ...b, route: store.route });
1429
+ }
1430
+ return out;
1431
+ },
1432
+ // Fresh, not the last emitted snapshot: a caller reading state straight
1433
+ // after `collapseAll()` expects to see it collapsed, and the emit is a
1434
+ // microtask away. `getRows()` stays on the snapshot - it is the hot path.
1435
+ getState: () => (current = buildState()),
1436
+ dispose() {
1437
+ disposed = true;
1438
+ generation += 1;
1439
+ if (asyncTimer)
1440
+ clearTimeout(asyncTimer);
1441
+ asyncTimer = null;
1442
+ asyncQueue.length = 0;
1443
+ for (const s of stores.values())
1444
+ s.cache.dispose();
1445
+ stores.clear();
1446
+ subscribers.clear();
1447
+ source.destroy?.();
1448
+ },
1449
+ get pinnedTopRows() {
1450
+ return grandTotalRow === 'pinnedTop' ? pinnedGrandTotal() : undefined;
1451
+ },
1452
+ get pinnedBottomRows() {
1453
+ return grandTotalRow === 'pinnedBottom' ? pinnedGrandTotal() : undefined;
1454
+ },
1455
+ get pivotResultColumns() {
1456
+ return pivotResultColumns();
1457
+ },
1458
+ // A getter: the grid re-reads it after every change notification.
1459
+ get pagination() {
1460
+ if (!paging)
1461
+ return undefined;
1462
+ return {
1463
+ pageIndex,
1464
+ pageSize,
1465
+ rowCount: pagerRowCount,
1466
+ setPage: (i) => model.setPage(i),
1467
+ setPageSize: (n) => model.setPageSize(n),
1468
+ ...(paging.pageSizes ? { pageSizes: paging.pageSizes } : {}),
1469
+ ...(paging.autoPageSize ? { autoPageSize: true } : {}),
1470
+ };
1471
+ },
1472
+ // --- GridRowModel ---------------------------------------------------
1473
+ subscribe(onChange) {
1474
+ subscribers.add(onChange);
1475
+ return () => {
1476
+ subscribers.delete(onChange);
1477
+ };
1478
+ },
1479
+ getRows: () => current.gridRows,
1480
+ isLoading: () => current.loading,
1481
+ getRowId: (row, index) => row.__group && 'id' in row.__group ? row.__group.id : `sv-ph:${index}`,
1482
+ rowPlaceholder: (row) => rowPlaceholderState(row),
1483
+ retryRow(_row, rowIndex) {
1484
+ // The block under the clicked row; every failed block only when the
1485
+ // index maps to no level (a stale index after a layout change).
1486
+ for (const seg of segments) {
1487
+ if (rowIndex < seg.displayStart || rowIndex >= seg.displayStart + seg.length)
1488
+ continue;
1489
+ seg.store.cache.retryFailedAt(rowIndex - seg.displayStart + seg.storeStart);
1490
+ return;
1491
+ }
1492
+ for (const store of stores.values())
1493
+ store.cache.retryFailed();
1494
+ },
1495
+ group: nav,
1496
+ selection: selectionAdapter,
1497
+ filterValues: options.filterValues,
1498
+ };
1499
+ return model;
1500
+ }
1501
+ /**
1502
+ * Map a state's display rows to grid rows. `state.gridRows` already holds
1503
+ * this; the function exists for parity with `serverGroupRows` and for code
1504
+ * that keeps its own display list.
1505
+ */
1506
+ export function serverRowModelRows(state) {
1507
+ return state?.gridRows ?? [];
1508
+ }
1509
+ /** The `serverGroup` prop for a grid driven by hand rather than by `rowModel`. */
1510
+ export function serverRowModelNav(ctl) {
1511
+ return ctl.group;
1512
+ }
1513
+ /**
1514
+ * The group column's text for one grid row: a group's key (or its
1515
+ * `leafField` under `treeData`, where a node's key is its id), `Total` for a
1516
+ * footer, `Grand total` for the grand total, the `leafField` value of a leaf,
1517
+ * and nothing for the rows that carry no data (placeholders, skeletons, the
1518
+ * "load more" row). `SvGroupCell` draws the expander, the count and the
1519
+ * spinner; this is the value behind it, so give the group column
1520
+ * `fieldFn: (row) => serverGroupText(row, leafField)` and copy, export, the
1521
+ * clipboard and a `pinnedTop` / `pinnedBottom` grand total (which the grid
1522
+ * formats from the accessor, not from the cell renderer) all read the same
1523
+ * text. `messages` is the map `SvGroupCell` takes, so a localized footer
1524
+ * and grand total read the same in the cell and in a copy.
1525
+ */
1526
+ export function serverGroupText(row, leafField, messages) {
1527
+ const meta = row.__group;
1528
+ if (!meta)
1529
+ return '';
1530
+ const m = resolveServerGroupMessages(messages);
1531
+ switch (meta.kind) {
1532
+ case 'group': {
1533
+ if (meta.field !== '' || !leafField)
1534
+ return meta.key;
1535
+ const v = row[leafField];
1536
+ return v == null ? meta.key : String(v);
1537
+ }
1538
+ case 'footer':
1539
+ return fillMessage(m.total, { label: meta.key });
1540
+ case 'grandTotal':
1541
+ return m.grandTotal;
1542
+ case 'leaf': {
1543
+ if (!leafField)
1544
+ return '';
1545
+ const v = row[leafField];
1546
+ return v == null ? '' : String(v);
1547
+ }
1548
+ default:
1549
+ return '';
1550
+ }
1551
+ }