@mono-lit/helper 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (538) hide show
  1. package/LICENSE.md +55 -0
  2. package/README.md +34 -2
  3. package/dist/breakpoints-CAbhDUht.js +84 -0
  4. package/dist/checkbox-Cqpie9Ap.js +4 -0
  5. package/dist/chip-DBUUa2eB.js +4 -0
  6. package/dist/chip-limits-BMoO31JM.js +213 -0
  7. package/dist/chip-strip-Cw3gyz0q.js +213 -0
  8. package/dist/color-DHYrfXsX.js +253 -0
  9. package/dist/css-class-BRKRzHx-.js +60 -0
  10. package/dist/css-size-DhHSVZJK.js +37 -0
  11. package/dist/data/theme/presets.json.d.ts +140 -0
  12. package/dist/data-search-Bwo5bg8K.js +573 -0
  13. package/dist/data-source-controller-C50Qyfzh.js +178 -0
  14. package/dist/data-source-options-BTpfrOZ_.js +535 -0
  15. package/dist/data-source-options-CQSTZHPE.js +537 -0
  16. package/dist/data-source-read-CUX-lgrQ.js +216 -0
  17. package/dist/data-source-read-DL88aH8t.js +217 -0
  18. package/dist/decorate-DEXtNagw.js +141 -0
  19. package/dist/element-props-CLB6yvbm.js +114 -0
  20. package/dist/esm-Ptok4SQZ.js +2026 -0
  21. package/dist/excel-CY6zGkcx.js +608 -0
  22. package/dist/excel-DopRbEMK.js +608 -0
  23. package/dist/export-BvlBx_GZ.js +2302 -0
  24. package/dist/export.d.ts +1 -0
  25. package/dist/export.js +2302 -0
  26. package/dist/field-icons-BoOG6KrL.js +51 -0
  27. package/dist/field-path-C92eGLg3.js +251 -0
  28. package/dist/filter-builder-Bke3qOgJ.js +205 -0
  29. package/dist/filter-builder-CE4zE0V5.js +205 -0
  30. package/dist/filter-eval-DIfU2EAn.js +173 -0
  31. package/dist/filter-odata-C1vSGZaY.js +591 -0
  32. package/dist/form-control-core-B8d7k6vk.js +223 -0
  33. package/dist/form-control-core-eeRr0zLU.js +223 -0
  34. package/dist/hydration-flush-RB5Tz5nl.js +1001 -0
  35. package/dist/icon-CJkTlmTb.js +55 -0
  36. package/dist/icon-dVhYJUIH.js +17 -0
  37. package/dist/import-BgJdkwBI.js +606 -0
  38. package/dist/import.d.ts +1 -0
  39. package/dist/import.js +606 -0
  40. package/dist/index.d.ts +1 -0
  41. package/dist/index.js +16 -0
  42. package/dist/index.node-DjvYOL-J.js +395 -0
  43. package/dist/index.node.d.ts +1 -0
  44. package/dist/index.node.js +16 -0
  45. package/dist/input-zGOULADt.js +4 -0
  46. package/dist/layout-var-CZnzMC0s.js +73 -0
  47. package/dist/light-slots-BVJwyg_2.js +244 -0
  48. package/dist/light-slots-DW1WgfgT.js +254 -0
  49. package/dist/markdown-DfqrfOJN.js +20146 -0
  50. package/dist/mono-button-CoVnBLav.js +873 -0
  51. package/dist/mono-button.shadow-FfP_hMH4.js +784 -0
  52. package/dist/mono-data-chart-ChU4Ii3p.js +738 -0
  53. package/dist/mono-data-dropdown-BAUl58kW.js +357 -0
  54. package/dist/mono-data-grid-CLBK8clE.js +4140 -0
  55. package/dist/mono-data-grid-Db80FzdZ.js +4142 -0
  56. package/dist/mono-element-B0kP_96P.js +232 -0
  57. package/dist/mono-event-Bi1qP9uN.js +129 -0
  58. package/dist/mono-form-controller-5PfsU4qa.js +409 -0
  59. package/dist/mono-modal-controller-BMO3n3rP.js +223 -0
  60. package/dist/mono-ui-CPV7rrdo.js +358 -0
  61. package/dist/notifier-CE4yxMUQ.js +88 -0
  62. package/dist/nuxt.d.ts +1 -0
  63. package/dist/nuxt.js +130 -0
  64. package/dist/popup-portal-BziRX1yG.js +465 -0
  65. package/dist/popup-stack-CEEMib__.js +189 -0
  66. package/dist/rate-limit-BBa2PO79.js +521 -0
  67. package/dist/reactive-BHsqVjRh.js +789 -0
  68. package/dist/reactive-D3EERqgO.js +47 -0
  69. package/dist/search.d.ts +1 -0
  70. package/dist/search.js +97 -0
  71. package/dist/src/components/accordion/accordion-core.d.ts +62 -0
  72. package/dist/src/components/accordion/accordion-types.d.ts +62 -0
  73. package/dist/src/components/accordion/index.d.ts +3 -0
  74. package/dist/src/components/accordion/index.shadow.d.ts +5 -0
  75. package/dist/src/components/accordion/mono-accordion.d.ts +55 -0
  76. package/dist/src/components/accordion/mono-accordion.shadow.d.ts +56 -0
  77. package/dist/src/components/alert/alert-core.d.ts +46 -0
  78. package/dist/src/components/alert/alert-types.d.ts +44 -0
  79. package/dist/src/components/alert/index.d.ts +3 -0
  80. package/dist/src/components/alert/index.shadow.d.ts +5 -0
  81. package/dist/src/components/alert/mono-alert.d.ts +44 -0
  82. package/dist/src/components/alert/mono-alert.shadow.d.ts +34 -0
  83. package/dist/src/components/breadcrumb/breadcrumb-core.d.ts +61 -0
  84. package/dist/src/components/breadcrumb/breadcrumb-list-core.d.ts +49 -0
  85. package/dist/src/components/breadcrumb/breadcrumb-render.d.ts +31 -0
  86. package/dist/src/components/breadcrumb/breadcrumb-types.d.ts +117 -0
  87. package/dist/src/components/breadcrumb/breadcrumb-utils.d.ts +43 -0
  88. package/dist/src/components/breadcrumb/index.d.ts +6 -0
  89. package/dist/src/components/breadcrumb/index.shadow.d.ts +8 -0
  90. package/dist/src/components/breadcrumb/mono-breadcrumb-list.d.ts +29 -0
  91. package/dist/src/components/breadcrumb/mono-breadcrumb-list.shadow.d.ts +48 -0
  92. package/dist/src/components/breadcrumb/mono-breadcrumb.d.ts +38 -0
  93. package/dist/src/components/breadcrumb/mono-breadcrumb.shadow.d.ts +73 -0
  94. package/dist/src/components/button/button-core.d.ts +90 -0
  95. package/dist/src/components/button/button-dropdown-core.d.ts +57 -0
  96. package/dist/src/components/button/button-dropdown-types.d.ts +129 -0
  97. package/dist/src/components/button/button-types.d.ts +219 -0
  98. package/dist/src/components/button/button-utils.d.ts +18 -0
  99. package/dist/src/components/button/index.button-dropdown.d.ts +4 -0
  100. package/dist/src/components/button/index.button-dropdown.shadow.d.ts +4 -0
  101. package/dist/src/components/button/index.d.ts +8 -0
  102. package/dist/src/components/button/index.shadow.d.ts +5 -0
  103. package/dist/src/components/button/mono-button-dropdown.d.ts +35 -0
  104. package/dist/src/components/button/mono-button-dropdown.shadow.d.ts +45 -0
  105. package/dist/src/components/button/mono-button.d.ts +123 -0
  106. package/dist/src/components/button/mono-button.shadow.d.ts +56 -0
  107. package/dist/src/components/card/card-core.d.ts +74 -0
  108. package/dist/src/components/card/card-types.d.ts +137 -0
  109. package/dist/src/components/card/card-utils.d.ts +23 -0
  110. package/dist/src/components/card/index.d.ts +4 -0
  111. package/dist/src/components/card/index.shadow.d.ts +6 -0
  112. package/dist/src/components/card/mono-card.d.ts +56 -0
  113. package/dist/src/components/card/mono-card.shadow.d.ts +52 -0
  114. package/dist/src/components/chart/chart-core.d.ts +43 -0
  115. package/dist/src/components/chart/chart-odata.d.ts +52 -0
  116. package/dist/src/components/chart/chart-types.d.ts +337 -0
  117. package/dist/src/components/chart/index.d.ts +8 -0
  118. package/dist/src/components/chart/index.shadow.d.ts +7 -0
  119. package/dist/src/components/chart/mono-chart-presets.d.ts +43 -0
  120. package/dist/src/components/chart/mono-chart-presets.shadow.d.ts +43 -0
  121. package/dist/src/components/chart/mono-chart.d.ts +30 -0
  122. package/dist/src/components/chart/mono-chart.shadow.d.ts +22 -0
  123. package/dist/src/components/chart/mono-data-chart.d.ts +27 -0
  124. package/dist/src/components/checkbox/checkbox-core.d.ts +53 -0
  125. package/dist/src/components/checkbox/checkbox-types.d.ts +69 -0
  126. package/dist/src/components/checkbox/checkbox-utils.d.ts +35 -0
  127. package/dist/src/components/checkbox/index.d.ts +4 -0
  128. package/dist/src/components/checkbox/index.shadow.d.ts +5 -0
  129. package/dist/src/components/checkbox/mono-checkbox.d.ts +34 -0
  130. package/dist/src/components/checkbox/mono-checkbox.shadow.d.ts +45 -0
  131. package/dist/src/components/chip/chip-core.d.ts +58 -0
  132. package/dist/src/components/chip/chip-types.d.ts +142 -0
  133. package/dist/src/components/chip/chip-utils.d.ts +24 -0
  134. package/dist/src/components/chip/index.d.ts +8 -0
  135. package/dist/src/components/chip/index.shadow.d.ts +6 -0
  136. package/dist/src/components/chip/mono-chip.d.ts +48 -0
  137. package/dist/src/components/chip/mono-chip.shadow.d.ts +33 -0
  138. package/dist/src/components/chip/mono-status-dot.d.ts +34 -0
  139. package/dist/src/components/date/date-core.d.ts +80 -0
  140. package/dist/src/components/date/date-types.d.ts +163 -0
  141. package/dist/src/components/date/index.d.ts +3 -0
  142. package/dist/src/components/date/index.shadow.d.ts +4 -0
  143. package/dist/src/components/date/mono-date.d.ts +22 -0
  144. package/dist/src/components/date/mono-date.shadow.d.ts +31 -0
  145. package/dist/src/components/drawer/drawer-core.d.ts +99 -0
  146. package/dist/src/components/drawer/drawer-types.d.ts +153 -0
  147. package/dist/src/components/drawer/index.d.ts +3 -0
  148. package/dist/src/components/drawer/index.shadow.d.ts +4 -0
  149. package/dist/src/components/drawer/mono-drawer.d.ts +45 -0
  150. package/dist/src/components/drawer/mono-drawer.shadow.d.ts +46 -0
  151. package/dist/src/components/dropdown/dropdown-core.d.ts +33 -0
  152. package/dist/src/components/dropdown/dropdown-types.d.ts +88 -0
  153. package/dist/src/components/dropdown/index.d.ts +3 -0
  154. package/dist/src/components/dropdown/index.shadow.d.ts +4 -0
  155. package/dist/src/components/dropdown/mono-dropdown.d.ts +34 -0
  156. package/dist/src/components/dropdown/mono-dropdown.shadow.d.ts +28 -0
  157. package/dist/src/components/dropdown-table/dropdown-table-core.d.ts +65 -0
  158. package/dist/src/components/dropdown-table/dropdown-table-types.d.ts +278 -0
  159. package/dist/src/components/dropdown-table/index.d.ts +5 -0
  160. package/dist/src/components/dropdown-table/index.shadow.d.ts +6 -0
  161. package/dist/src/components/dropdown-table/mono-data-dropdown.d.ts +152 -0
  162. package/dist/src/components/dropdown-table/mono-dropdown-table.d.ts +46 -0
  163. package/dist/src/components/dropdown-table/mono-dropdown-table.shadow.d.ts +22 -0
  164. package/dist/src/components/file-upload/file-upload-core.d.ts +60 -0
  165. package/dist/src/components/file-upload/file-upload-types.d.ts +145 -0
  166. package/dist/src/components/file-upload/file-upload-utils.d.ts +16 -0
  167. package/dist/src/components/file-upload/index.d.ts +4 -0
  168. package/dist/src/components/file-upload/index.shadow.d.ts +5 -0
  169. package/dist/src/components/file-upload/mono-file-upload.d.ts +33 -0
  170. package/dist/src/components/file-upload/mono-file-upload.shadow.d.ts +50 -0
  171. package/dist/src/components/filter/filter-builder-core.d.ts +36 -0
  172. package/dist/src/components/filter/filter-builder.d.ts +23 -0
  173. package/dist/src/components/filter/filter-odata.d.ts +27 -0
  174. package/dist/src/components/filter/filter-operators.d.ts +13 -0
  175. package/dist/src/components/filter/filter-types.d.ts +253 -0
  176. package/dist/src/components/filter/index.d.ts +6 -0
  177. package/dist/src/components/filter/index.shadow.d.ts +7 -0
  178. package/dist/src/components/filter/mono-filter-builder.d.ts +21 -0
  179. package/dist/src/components/filter/mono-filter-builder.shadow.d.ts +17 -0
  180. package/dist/src/components/form/form-control-core.d.ts +37 -0
  181. package/dist/src/components/form/form-rules.d.ts +25 -0
  182. package/dist/src/components/form/form-types.d.ts +307 -0
  183. package/dist/src/components/form/index.d.ts +6 -0
  184. package/dist/src/components/form/mono-form-controller.d.ts +16 -0
  185. package/dist/src/components/input/index.d.ts +6 -0
  186. package/dist/src/components/input/index.shadow.d.ts +8 -0
  187. package/dist/src/components/input/input-core.d.ts +91 -0
  188. package/dist/src/components/input/input-format.d.ts +118 -0
  189. package/dist/src/components/input/input-types.d.ts +179 -0
  190. package/dist/src/components/input/input-utils.d.ts +10 -0
  191. package/dist/src/components/input/mono-input.d.ts +46 -0
  192. package/dist/src/components/input/mono-input.shadow.d.ts +34 -0
  193. package/dist/src/components/menu/index.d.ts +6 -0
  194. package/dist/src/components/menu/index.shadow.d.ts +7 -0
  195. package/dist/src/components/menu/menu-core.d.ts +65 -0
  196. package/dist/src/components/menu/menu-render.d.ts +55 -0
  197. package/dist/src/components/menu/menu-types.d.ts +195 -0
  198. package/dist/src/components/menu/menu-utils.d.ts +53 -0
  199. package/dist/src/components/menu/mono-menu-list.d.ts +118 -0
  200. package/dist/src/components/menu/mono-menu.d.ts +39 -0
  201. package/dist/src/components/menu/mono-menu.shadow.d.ts +40 -0
  202. package/dist/src/components/modal/index.d.ts +4 -0
  203. package/dist/src/components/modal/index.shadow.d.ts +5 -0
  204. package/dist/src/components/modal/modal-core.d.ts +110 -0
  205. package/dist/src/components/modal/modal-types.d.ts +304 -0
  206. package/dist/src/components/modal/mono-modal-controller.d.ts +4 -0
  207. package/dist/src/components/modal/mono-modal.d.ts +45 -0
  208. package/dist/src/components/modal/mono-modal.shadow.d.ts +50 -0
  209. package/dist/src/components/nav/index.d.ts +4 -0
  210. package/dist/src/components/nav/index.shadow.d.ts +6 -0
  211. package/dist/src/components/nav/mono-nav.d.ts +41 -0
  212. package/dist/src/components/nav/mono-nav.shadow.d.ts +21 -0
  213. package/dist/src/components/nav/nav-core.d.ts +34 -0
  214. package/dist/src/components/nav/nav-types.d.ts +48 -0
  215. package/dist/src/components/nav/nav-utils.d.ts +12 -0
  216. package/dist/src/components/radio/index.d.ts +4 -0
  217. package/dist/src/components/radio/index.shadow.d.ts +5 -0
  218. package/dist/src/components/radio/mono-radio.d.ts +31 -0
  219. package/dist/src/components/radio/mono-radio.shadow.d.ts +35 -0
  220. package/dist/src/components/radio/radio-core.d.ts +45 -0
  221. package/dist/src/components/radio/radio-types.d.ts +58 -0
  222. package/dist/src/components/radio/radio-utils.d.ts +12 -0
  223. package/dist/src/components/rich-text-editor/index.d.ts +5 -0
  224. package/dist/src/components/rich-text-editor/index.shadow.d.ts +6 -0
  225. package/dist/src/components/rich-text-editor/mono-rich-text-editor.d.ts +31 -0
  226. package/dist/src/components/rich-text-editor/mono-rich-text-editor.shadow.d.ts +34 -0
  227. package/dist/src/components/rich-text-editor/rich-text-editor-core.d.ts +100 -0
  228. package/dist/src/components/rich-text-editor/rich-text-editor-loader.d.ts +17 -0
  229. package/dist/src/components/rich-text-editor/rich-text-editor-toolbar.d.ts +65 -0
  230. package/dist/src/components/rich-text-editor/rich-text-editor-types.d.ts +231 -0
  231. package/dist/src/components/select/index.d.ts +4 -0
  232. package/dist/src/components/select/index.shadow.d.ts +5 -0
  233. package/dist/src/components/select/mono-select.d.ts +69 -0
  234. package/dist/src/components/select/mono-select.shadow.d.ts +48 -0
  235. package/dist/src/components/select/select-core.d.ts +98 -0
  236. package/dist/src/components/select/select-types.d.ts +258 -0
  237. package/dist/src/components/select/select-utils.d.ts +7 -0
  238. package/dist/src/components/sidebar/index.d.ts +4 -0
  239. package/dist/src/components/sidebar/index.shadow.d.ts +6 -0
  240. package/dist/src/components/sidebar/mono-sidebar.d.ts +34 -0
  241. package/dist/src/components/sidebar/mono-sidebar.shadow.d.ts +27 -0
  242. package/dist/src/components/sidebar/sidebar-core.d.ts +51 -0
  243. package/dist/src/components/sidebar/sidebar-types.d.ts +119 -0
  244. package/dist/src/components/sidebar/sidebar-utils.d.ts +55 -0
  245. package/dist/src/components/switch/index.d.ts +4 -0
  246. package/dist/src/components/switch/index.shadow.d.ts +5 -0
  247. package/dist/src/components/switch/mono-switch.d.ts +31 -0
  248. package/dist/src/components/switch/mono-switch.shadow.d.ts +36 -0
  249. package/dist/src/components/switch/switch-core.d.ts +49 -0
  250. package/dist/src/components/switch/switch-types.d.ts +64 -0
  251. package/dist/src/components/switch/switch-utils.d.ts +12 -0
  252. package/dist/src/components/table/array-source.d.ts +44 -0
  253. package/dist/src/components/table/date-filter-tree.d.ts +93 -0
  254. package/dist/src/components/table/grouping.d.ts +55 -0
  255. package/dist/src/components/table/index.d.ts +33 -0
  256. package/dist/src/components/table/index.shadow.d.ts +47 -0
  257. package/dist/src/components/table/mono-data-grid.d.ts +1756 -0
  258. package/dist/src/components/table/mono-table-checkbox-core.d.ts +76 -0
  259. package/dist/src/components/table/mono-table-checkbox.d.ts +34 -0
  260. package/dist/src/components/table/mono-table-checkbox.shadow.d.ts +26 -0
  261. package/dist/src/components/table/mono-table-detail-core.d.ts +87 -0
  262. package/dist/src/components/table/mono-table-detail.d.ts +32 -0
  263. package/dist/src/components/table/mono-table-detail.shadow.d.ts +31 -0
  264. package/dist/src/components/table/mono-table-empty-core.d.ts +60 -0
  265. package/dist/src/components/table/mono-table-empty.d.ts +35 -0
  266. package/dist/src/components/table/mono-table-empty.shadow.d.ts +40 -0
  267. package/dist/src/components/table/mono-table-error-core.d.ts +84 -0
  268. package/dist/src/components/table/mono-table-error.d.ts +28 -0
  269. package/dist/src/components/table/mono-table-error.shadow.d.ts +21 -0
  270. package/dist/src/components/table/mono-table-info-core.d.ts +13 -0
  271. package/dist/src/components/table/mono-table-info.d.ts +19 -0
  272. package/dist/src/components/table/mono-table-info.shadow.d.ts +14 -0
  273. package/dist/src/components/table/mono-table-loading-core.d.ts +41 -0
  274. package/dist/src/components/table/mono-table-loading.d.ts +50 -0
  275. package/dist/src/components/table/mono-table-loading.shadow.d.ts +14 -0
  276. package/dist/src/components/table/mono-table-page-size-core.d.ts +14 -0
  277. package/dist/src/components/table/mono-table-page-size.d.ts +16 -0
  278. package/dist/src/components/table/mono-table-page-size.shadow.d.ts +11 -0
  279. package/dist/src/components/table/mono-table-paging-core.d.ts +22 -0
  280. package/dist/src/components/table/mono-table-paging-group-core.d.ts +18 -0
  281. package/dist/src/components/table/mono-table-paging-group.d.ts +20 -0
  282. package/dist/src/components/table/mono-table-paging-group.shadow.d.ts +13 -0
  283. package/dist/src/components/table/mono-table-paging.d.ts +16 -0
  284. package/dist/src/components/table/mono-table-paging.shadow.d.ts +11 -0
  285. package/dist/src/components/table/mono-table-search-core.d.ts +74 -0
  286. package/dist/src/components/table/mono-table-search.d.ts +34 -0
  287. package/dist/src/components/table/mono-table-search.shadow.d.ts +21 -0
  288. package/dist/src/components/table/mono-table-sort-core.d.ts +35 -0
  289. package/dist/src/components/table/mono-table-sort.d.ts +38 -0
  290. package/dist/src/components/table/mono-table-sort.shadow.d.ts +20 -0
  291. package/dist/src/components/table/mono-table-summary-core.d.ts +26 -0
  292. package/dist/src/components/table/mono-table-summary.d.ts +24 -0
  293. package/dist/src/components/table/mono-table-summary.shadow.d.ts +14 -0
  294. package/dist/src/components/table/mono-table-th-core.d.ts +56 -0
  295. package/dist/src/components/table/mono-table-th.d.ts +41 -0
  296. package/dist/src/components/table/mono-table-th.shadow.d.ts +19 -0
  297. package/dist/src/components/table/store-group-source.d.ts +24 -0
  298. package/dist/src/components/table/table-controller-core.d.ts +40 -0
  299. package/dist/src/components/table/table-filter-icons.d.ts +3 -0
  300. package/dist/src/components/table/table-menu-core.d.ts +65 -0
  301. package/dist/src/components/table/table-overlay.d.ts +165 -0
  302. package/dist/src/components/table/table-sort-icons.d.ts +4 -0
  303. package/dist/src/components/table/table-sort-utils.d.ts +20 -0
  304. package/dist/src/components/table/table-types.d.ts +475 -0
  305. package/dist/src/components/tabs/index.d.ts +3 -0
  306. package/dist/src/components/tabs/index.shadow.d.ts +4 -0
  307. package/dist/src/components/tabs/mono-tabs.d.ts +33 -0
  308. package/dist/src/components/tabs/mono-tabs.shadow.d.ts +37 -0
  309. package/dist/src/components/tabs/tabs-core.d.ts +57 -0
  310. package/dist/src/components/tabs/tabs-types.d.ts +65 -0
  311. package/dist/src/components/tag-input/index.d.ts +4 -0
  312. package/dist/src/components/tag-input/index.shadow.d.ts +5 -0
  313. package/dist/src/components/tag-input/mono-tag-input.d.ts +66 -0
  314. package/dist/src/components/tag-input/mono-tag-input.shadow.d.ts +40 -0
  315. package/dist/src/components/tag-input/tag-input-core.d.ts +108 -0
  316. package/dist/src/components/tag-input/tag-input-types.d.ts +394 -0
  317. package/dist/src/components/tag-input/tag-input-utils.d.ts +8 -0
  318. package/dist/src/components/textarea/index.d.ts +4 -0
  319. package/dist/src/components/textarea/index.shadow.d.ts +5 -0
  320. package/dist/src/components/textarea/mono-textarea.d.ts +29 -0
  321. package/dist/src/components/textarea/mono-textarea.shadow.d.ts +31 -0
  322. package/dist/src/components/textarea/textarea-core.d.ts +60 -0
  323. package/dist/src/components/textarea/textarea-types.d.ts +131 -0
  324. package/dist/src/components/textarea/textarea-utils.d.ts +7 -0
  325. package/dist/src/components/tooltip/control-tooltip.d.ts +36 -0
  326. package/dist/src/components/tooltip/index.d.ts +6 -0
  327. package/dist/src/components/tooltip/tooltip-config.d.ts +27 -0
  328. package/dist/src/components/tooltip/tooltip-declarative.d.ts +41 -0
  329. package/dist/src/components/tooltip/tooltip-loader.d.ts +1 -0
  330. package/dist/src/components/tooltip/tooltip-types.d.ts +110 -0
  331. package/dist/src/composables/breakpoints.d.ts +59 -0
  332. package/dist/src/composables/chip-limits.d.ts +48 -0
  333. package/dist/src/composables/chip-strip.d.ts +93 -0
  334. package/dist/src/composables/color.d.ts +103 -0
  335. package/dist/src/composables/css-class.d.ts +27 -0
  336. package/dist/src/composables/css-size.d.ts +40 -0
  337. package/dist/src/composables/data-source-controller.d.ts +84 -0
  338. package/dist/src/composables/element-props.d.ts +46 -0
  339. package/dist/src/composables/field-icons.d.ts +7 -0
  340. package/dist/src/composables/hybird-prop.d.ts +79 -0
  341. package/dist/src/composables/hydration-flush.d.ts +34 -0
  342. package/dist/src/composables/icon.d.ts +17 -0
  343. package/dist/src/composables/layout-var.d.ts +34 -0
  344. package/dist/src/composables/light-slots.d.ts +133 -0
  345. package/dist/src/composables/mdi-glyphs.d.ts +4 -0
  346. package/dist/src/composables/mono-element.d.ts +9 -0
  347. package/dist/src/composables/mono-event.d.ts +55 -0
  348. package/dist/src/composables/mono-pending.d.ts +8 -0
  349. package/dist/src/composables/mono-skeleton.d.ts +193 -0
  350. package/dist/src/composables/mono-ui.d.ts +58 -0
  351. package/dist/src/composables/notifier.d.ts +46 -0
  352. package/dist/src/composables/popup-portal.d.ts +226 -0
  353. package/dist/src/composables/popup-stack.d.ts +70 -0
  354. package/dist/src/composables/rate-limit.d.ts +108 -0
  355. package/dist/src/composables/reactive.d.ts +53 -0
  356. package/dist/src/composables/shadow-css.d.ts +42 -0
  357. package/dist/src/composables/visibility.d.ts +36 -0
  358. package/dist/src/data/theme/index.d.ts +30 -0
  359. package/dist/src/data/theme/presets.d.ts +94 -0
  360. package/dist/src/entries/index.d.ts +2 -0
  361. package/dist/src/entries/index.node.d.ts +42 -0
  362. package/dist/src/entries/search.d.ts +3 -0
  363. package/dist/src/export/detail.d.ts +29 -0
  364. package/dist/src/export/download.d.ts +9 -0
  365. package/dist/src/export/excel/index.d.ts +27 -0
  366. package/dist/src/export/excel/nodes.d.ts +123 -0
  367. package/dist/src/export/expression.d.ts +36 -0
  368. package/dist/src/export/helpers.d.ts +6 -0
  369. package/dist/src/export/index.d.ts +13 -0
  370. package/dist/src/export/markdown.d.ts +52 -0
  371. package/dist/src/export/resolve-data.d.ts +31 -0
  372. package/dist/src/export/sentinel.d.ts +97 -0
  373. package/dist/src/export/sheet-name.d.ts +45 -0
  374. package/dist/src/export/styles.d.ts +33 -0
  375. package/dist/src/export/template.d.ts +10 -0
  376. package/dist/src/export/types.d.ts +259 -0
  377. package/dist/src/import/coerce.d.ts +38 -0
  378. package/dist/src/import/index.d.ts +16 -0
  379. package/dist/src/import/match.d.ts +54 -0
  380. package/dist/src/import/parse.d.ts +15 -0
  381. package/dist/src/import/types.d.ts +153 -0
  382. package/dist/src/nuxt/index.d.ts +94 -0
  383. package/dist/src/search/data-search.d.ts +207 -0
  384. package/dist/src/search/field-path.d.ts +107 -0
  385. package/dist/src/search/filter-eval.d.ts +30 -0
  386. package/dist/src/search/index.d.ts +22 -0
  387. package/dist/src/search/odata-search.d.ts +18 -0
  388. package/dist/src/search/search-expr.d.ts +160 -0
  389. package/dist/src/utils/data-source-apply.d.ts +97 -0
  390. package/dist/src/utils/data-source-options.d.ts +116 -0
  391. package/dist/src/utils/data-source-read.d.ts +85 -0
  392. package/dist/src/utils/data-source-summary.d.ts +55 -0
  393. package/dist/src/utils/normalize-error.d.ts +60 -0
  394. package/dist/src/vite/index.d.ts +48 -0
  395. package/dist/src/vite/mono-client-only.d.ts +35 -0
  396. package/dist/src/vite/mono-ssr-stub.d.ts +20 -0
  397. package/dist/src/vite/shadow-manifest.d.ts +8 -0
  398. package/dist/styles-BIJ31TM4.js +20146 -0
  399. package/dist/tooltip-CysgRgBi.js +760 -0
  400. package/dist/tooltip.d.ts +1 -0
  401. package/dist/tooltip.js +2 -0
  402. package/dist/ui/accordion.d.ts +1 -0
  403. package/dist/ui/accordion.js +466 -0
  404. package/dist/ui/alert.d.ts +1 -0
  405. package/dist/ui/alert.js +221 -0
  406. package/dist/ui/breadcrumb.d.ts +1 -0
  407. package/dist/ui/breadcrumb.js +902 -0
  408. package/dist/ui/button-dropdown.d.ts +1 -0
  409. package/dist/ui/button-dropdown.js +679 -0
  410. package/dist/ui/button.d.ts +1 -0
  411. package/dist/ui/button.js +63 -0
  412. package/dist/ui/card.d.ts +1 -0
  413. package/dist/ui/card.js +511 -0
  414. package/dist/ui/chart.d.ts +1 -0
  415. package/dist/ui/chart.js +808 -0
  416. package/dist/ui/checkbox.d.ts +1 -0
  417. package/dist/ui/checkbox.js +413 -0
  418. package/dist/ui/chip.d.ts +1 -0
  419. package/dist/ui/chip.js +558 -0
  420. package/dist/ui/date.d.ts +1 -0
  421. package/dist/ui/date.js +686 -0
  422. package/dist/ui/drawer.d.ts +1 -0
  423. package/dist/ui/drawer.js +901 -0
  424. package/dist/ui/dropdown-table.d.ts +1 -0
  425. package/dist/ui/dropdown-table.js +1291 -0
  426. package/dist/ui/dropdown.d.ts +1 -0
  427. package/dist/ui/dropdown.js +586 -0
  428. package/dist/ui/file-upload.d.ts +1 -0
  429. package/dist/ui/file-upload.js +688 -0
  430. package/dist/ui/filter.d.ts +1 -0
  431. package/dist/ui/filter.js +400 -0
  432. package/dist/ui/font/files/geist-47c091c9.woff2 +0 -0
  433. package/dist/ui/font/files/geist-8dd77544.woff2 +0 -0
  434. package/dist/ui/font/files/geist-b9248b1e.woff2 +0 -0
  435. package/dist/ui/font/files/geist-d40d74b4.woff2 +0 -0
  436. package/dist/ui/font/files/geist-f493429a.woff2 +0 -0
  437. package/dist/ui/font/files/geistmono-03737868.woff2 +0 -0
  438. package/dist/ui/font/files/geistmono-421a7e38.woff2 +0 -0
  439. package/dist/ui/font/files/geistmono-48ef518d.woff2 +0 -0
  440. package/dist/ui/font/files/geistmono-5df35d39.woff2 +0 -0
  441. package/dist/ui/font/files/geistmono-7de289a4.woff2 +0 -0
  442. package/dist/ui/font/files/geistmono-e976128f.woff2 +0 -0
  443. package/dist/ui/font/files/inter-05cf802c.woff2 +0 -0
  444. package/dist/ui/font/files/inter-2b2f25a4.woff2 +0 -0
  445. package/dist/ui/font/files/inter-467acc7a.woff2 +0 -0
  446. package/dist/ui/font/files/inter-6e85163c.woff2 +0 -0
  447. package/dist/ui/font/files/inter-7048add3.woff2 +0 -0
  448. package/dist/ui/font/files/inter-9be0371e.woff2 +0 -0
  449. package/dist/ui/font/files/inter-a28b7e31.woff2 +0 -0
  450. package/dist/ui/font/files/poppins-054df55a.woff2 +0 -0
  451. package/dist/ui/font/files/poppins-0ee5dc9a.woff2 +0 -0
  452. package/dist/ui/font/files/poppins-0f44cb18.woff2 +0 -0
  453. package/dist/ui/font/files/poppins-0ff0d30c.woff2 +0 -0
  454. package/dist/ui/font/files/poppins-13045147.woff2 +0 -0
  455. package/dist/ui/font/files/poppins-1db57cd9.woff2 +0 -0
  456. package/dist/ui/font/files/poppins-2cf2a902.woff2 +0 -0
  457. package/dist/ui/font/files/poppins-39e7f079.woff2 +0 -0
  458. package/dist/ui/font/files/poppins-9607042d.woff2 +0 -0
  459. package/dist/ui/font/files/poppins-9a88fc1f.woff2 +0 -0
  460. package/dist/ui/font/files/poppins-b8f4f752.woff2 +0 -0
  461. package/dist/ui/font/files/poppins-f5e57db4.woff2 +0 -0
  462. package/dist/ui/font/geist-mono.css +104 -0
  463. package/dist/ui/font/geist.css +168 -0
  464. package/dist/ui/font/inter.css +232 -0
  465. package/dist/ui/font/poppins.css +104 -0
  466. package/dist/ui/form.d.ts +1 -0
  467. package/dist/ui/form.js +3 -0
  468. package/dist/ui/index.css +21826 -0
  469. package/dist/ui/input.d.ts +1 -0
  470. package/dist/ui/input.js +1176 -0
  471. package/dist/ui/menu.d.ts +1 -0
  472. package/dist/ui/menu.js +1289 -0
  473. package/dist/ui/modal.d.ts +1 -0
  474. package/dist/ui/modal.js +995 -0
  475. package/dist/ui/nav.d.ts +1 -0
  476. package/dist/ui/nav.js +295 -0
  477. package/dist/ui/radio.d.ts +1 -0
  478. package/dist/ui/radio.js +379 -0
  479. package/dist/ui/rich-text-editor.d.ts +1 -0
  480. package/dist/ui/rich-text-editor.js +1338 -0
  481. package/dist/ui/select.d.ts +1 -0
  482. package/dist/ui/select.js +2066 -0
  483. package/dist/ui/shadow/accordion.js +442 -0
  484. package/dist/ui/shadow/alert.js +218 -0
  485. package/dist/ui/shadow/breadcrumb.js +919 -0
  486. package/dist/ui/shadow/button-dropdown.js +696 -0
  487. package/dist/ui/shadow/button.js +63 -0
  488. package/dist/ui/shadow/card.js +524 -0
  489. package/dist/ui/shadow/chart.js +1569 -0
  490. package/dist/ui/shadow/checkbox.js +457 -0
  491. package/dist/ui/shadow/chip.js +559 -0
  492. package/dist/ui/shadow/date.js +712 -0
  493. package/dist/ui/shadow/drawer.js +869 -0
  494. package/dist/ui/shadow/dropdown-table.js +1620 -0
  495. package/dist/ui/shadow/dropdown.js +547 -0
  496. package/dist/ui/shadow/file-upload.js +731 -0
  497. package/dist/ui/shadow/filter.js +402 -0
  498. package/dist/ui/shadow/input.js +1150 -0
  499. package/dist/ui/shadow/menu.js +899 -0
  500. package/dist/ui/shadow/modal.js +1193 -0
  501. package/dist/ui/shadow/nav.js +240 -0
  502. package/dist/ui/shadow/radio.js +408 -0
  503. package/dist/ui/shadow/rich-text-editor.js +1344 -0
  504. package/dist/ui/shadow/select.js +2033 -0
  505. package/dist/ui/shadow/sidebar.js +606 -0
  506. package/dist/ui/shadow/switch.js +428 -0
  507. package/dist/ui/shadow/table.js +4755 -0
  508. package/dist/ui/shadow/tabs.js +372 -0
  509. package/dist/ui/shadow/tag-input.js +2846 -0
  510. package/dist/ui/shadow/textarea.js +816 -0
  511. package/dist/ui/sidebar.d.ts +1 -0
  512. package/dist/ui/sidebar.js +659 -0
  513. package/dist/ui/switch.d.ts +1 -0
  514. package/dist/ui/switch.js +398 -0
  515. package/dist/ui/table.d.ts +1 -0
  516. package/dist/ui/table.js +4654 -0
  517. package/dist/ui/tabs.d.ts +1 -0
  518. package/dist/ui/tabs.js +379 -0
  519. package/dist/ui/tag-input.d.ts +1 -0
  520. package/dist/ui/tag-input.js +2819 -0
  521. package/dist/ui/textarea.d.ts +1 -0
  522. package/dist/ui/textarea.js +819 -0
  523. package/dist/ui/theme/legacy.css +293 -0
  524. package/dist/ui/theme/luma.css +743 -0
  525. package/dist/ui/theme/lyra.css +754 -0
  526. package/dist/ui/theme/maia.css +673 -0
  527. package/dist/ui/theme/mira.css +802 -0
  528. package/dist/ui/theme/nova.css +662 -0
  529. package/dist/ui/theme/one.css +699 -0
  530. package/dist/ui/theme/rhea.css +742 -0
  531. package/dist/ui/theme/sera.css +845 -0
  532. package/dist/ui/theme/tokens.css +261 -0
  533. package/dist/ui/theme/vega.css +467 -0
  534. package/dist/vite-C5iUK7so.js +329 -0
  535. package/dist/vite.d.ts +1 -0
  536. package/dist/vite.js +2 -0
  537. package/dist/vue.d.ts +282 -0
  538. package/package.json +411 -3
@@ -0,0 +1,4142 @@
1
+ import { a as parseFieldPath, c as toODataClause, l as toODataSelector, n as isPath, o as projectFields, r as mergePatch, s as setFieldValue, t as getFieldValue } from "./field-path-C92eGLg3.js";
2
+ import { a as buildGroups, c as isGroupNode, i as searchFilterOf, o as collectGroupPaths, r as readAllRows, s as flattenLeaves } from "./data-source-read-DL88aH8t.js";
3
+ import { i as joinFilters, n as andPredicates, r as compileFilterPredicate, t as andFilters } from "./filter-eval-DIfU2EAn.js";
4
+ import { S as searchEntryFor, _ as hasWildcardSearch, b as plainSearchColumns, g as hasCustomSearch, m as customRemoteClause, o as mergeSearchFields, p as customPredicate, t as DEFAULT_SEARCH_FIELDS, x as resolveSearchEntries } from "./data-search-Bwo5bg8K.js";
5
+ import { a as resolveDataSourceOptions, c as composeSummaryApply, d as createApplyGate, f as isRolledUp, h as monoArraySource, i as normalizeSortList, l as summaryAliases, m as storeRows, n as extraLoadOptions, o as toList, p as loadApply, s as buildSummaryApply, t as dataSourceOptionsKey, u as summaryFilterOf } from "./data-source-options-CQSTZHPE.js";
6
+ import { n as unwrapReactive } from "./reactive-D3EERqgO.js";
7
+ import { t as arrayToODataString } from "./filter-odata-C1vSGZaY.js";
8
+ import { t as createNotifier } from "./notifier-CE4yxMUQ.js";
9
+ import { t as monoFilterBuilder } from "./filter-builder-Bke3qOgJ.js";
10
+ //#region src/utils/normalize-error.ts
11
+ /** Fields worth reading off an object that is not an `Error`. */
12
+ var MESSAGE_KEYS = [
13
+ "message",
14
+ "statusText",
15
+ "errorMessage",
16
+ "error",
17
+ "detail"
18
+ ];
19
+ var text = (value) => typeof value === "string" && value.trim() ? value.trim() : "";
20
+ var isObject = (value) => value !== null && typeof value === "object";
21
+ /**
22
+ * A readable message for `raw`, or `fallback` when nothing legible can be found.
23
+ *
24
+ * Never throws, and never returns an empty string — a caller rendering this into
25
+ * an error bar has to have something to render, and a blank bar is worse than a
26
+ * generic one.
27
+ */
28
+ function normalizeError(raw, fallback = "Request failed") {
29
+ if (raw == null) return fallback;
30
+ if (raw instanceof Error) {
31
+ const own = text(raw.message);
32
+ if (own) return own;
33
+ } else {
34
+ if (typeof raw === "string") return text(raw) || fallback;
35
+ if (typeof raw === "number" || typeof raw === "boolean") return String(raw);
36
+ }
37
+ if (isObject(raw)) {
38
+ const obj = raw;
39
+ for (const key of MESSAGE_KEYS) {
40
+ const hit = text(obj[key]);
41
+ if (hit) return hit;
42
+ }
43
+ const response = obj.response;
44
+ if (isObject(response)) {
45
+ const nested = text(response.statusText) || text(response.message);
46
+ if (nested) return nested;
47
+ }
48
+ const status = errorStatus(obj);
49
+ if (status !== void 0 && status > 0) return `HTTP ${status}`;
50
+ if (raw instanceof Error) return text(raw.name) || fallback;
51
+ return fallback;
52
+ }
53
+ return text(String(raw)) || fallback;
54
+ }
55
+ /**
56
+ * What the bar says for a status. English; an app puts its own language in
57
+ * ONCE with `setErrorMessages()` rather than on every table.
58
+ */
59
+ var DEFAULT_ERROR_MESSAGES = Object.freeze({
60
+ network: "Could not reach the server. Check your connection and try again.",
61
+ 400: "The server rejected the request.",
62
+ 401: "Your session has expired. Please sign in again.",
63
+ 403: "You do not have permission to view this data.",
64
+ 404: "The requested data could not be found.",
65
+ 408: "The server took too long to respond. Please try again.",
66
+ 409: "The request conflicts with the current state of the data.",
67
+ 422: "The server could not process the request.",
68
+ 429: "Too many requests. Please wait a moment and try again.",
69
+ 500: "Something went wrong on the server. Please try again later.",
70
+ 502: "The server is temporarily unavailable. Please try again later.",
71
+ 503: "The server is temporarily unavailable. Please try again later.",
72
+ 504: "The server took too long to respond. Please try again later.",
73
+ default: "Request failed"
74
+ });
75
+ var globalMessages = null;
76
+ /**
77
+ * App-wide wording, merged over the defaults; `null` restores them. A grid's own
78
+ * `errorMessages` merges over this in turn, so a single table can still differ.
79
+ */
80
+ function setErrorMessages(messages) {
81
+ globalMessages = messages ? { ...messages } : null;
82
+ }
83
+ /** Defaults ← app-wide ← `local`, same shape as the filter builder's `resolveTexts`. */
84
+ function resolveErrorMessages(local) {
85
+ const merged = { ...DEFAULT_ERROR_MESSAGES };
86
+ for (const layer of [globalMessages, local]) {
87
+ if (!layer) continue;
88
+ for (const key of Object.keys(layer)) {
89
+ const value = layer[key];
90
+ if (typeof value === "string" && value.trim()) merged[key] = value;
91
+ }
92
+ }
93
+ return merged;
94
+ }
95
+ /**
96
+ * The standard reason phrases. Not shown to anyone — they are what `describeError`
97
+ * uses to recognise that an error's text is ONLY the phrase for its status
98
+ * (`Error('Forbidden')` + `httpStatus: 403`), which adds nothing to the preset and
99
+ * so is not a detail worth appending.
100
+ */
101
+ var REASON_PHRASES = {
102
+ 400: "Bad Request",
103
+ 401: "Unauthorized",
104
+ 402: "Payment Required",
105
+ 403: "Forbidden",
106
+ 404: "Not Found",
107
+ 405: "Method Not Allowed",
108
+ 408: "Request Timeout",
109
+ 409: "Conflict",
110
+ 410: "Gone",
111
+ 412: "Precondition Failed",
112
+ 413: "Payload Too Large",
113
+ 415: "Unsupported Media Type",
114
+ 422: "Unprocessable Entity",
115
+ 429: "Too Many Requests",
116
+ 500: "Internal Server Error",
117
+ 501: "Not Implemented",
118
+ 502: "Bad Gateway",
119
+ 503: "Service Unavailable",
120
+ 504: "Gateway Timeout"
121
+ };
122
+ /** Texts a transport mints for "no answer at all" — a status of 0 in words. */
123
+ var NETWORK_TEXTS = [
124
+ "failed to fetch",
125
+ "networkerror when attempting to fetch resource",
126
+ "load failed",
127
+ "network error",
128
+ "unspecified network error",
129
+ "network connection timeout",
130
+ "network request failed"
131
+ ];
132
+ var toStatus = (value) => {
133
+ if (value === null || value === void 0 || value === "") return void 0;
134
+ const n = Number(value);
135
+ return Number.isInteger(n) && n >= 0 && n < 1e3 ? n : void 0;
136
+ };
137
+ /**
138
+ * The HTTP status an error carries, `0` for a network failure, `undefined` when
139
+ * it has none.
140
+ *
141
+ * Reads the fields the common transports use — devextreme's `httpStatus`, a
142
+ * fetch `Response` / axios error's `status` and `response.status`, ofetch's
143
+ * `statusCode`, an OData error body's numeric `code` — on an `Error` instance as
144
+ * readily as on a plain object, since devextreme `extend()`s them onto an
145
+ * `Error`.
146
+ */
147
+ function errorStatus(raw) {
148
+ if (!isObject(raw)) return void 0;
149
+ const response = isObject(raw.response) ? raw.response : void 0;
150
+ const direct = toStatus(raw.httpStatus) ?? toStatus(raw.status) ?? toStatus(raw.statusCode) ?? toStatus(response?.status) ?? toStatus(response?.statusCode);
151
+ if (direct !== void 0) return direct;
152
+ const code = toStatus((isObject(raw.errorDetails) ? raw.errorDetails : void 0)?.code);
153
+ if (code !== void 0 && code >= 400) return code;
154
+ const own = (raw instanceof Error ? text(raw.message) : text(raw.message)).toLowerCase();
155
+ if (own && NETWORK_TEXTS.some((t) => own.includes(t))) return 0;
156
+ }
157
+ /**
158
+ * The text a SERVER wrote about the failure, if the error carries one — an OData
159
+ * error body's message, a JSON body's `message` / `error`, an axios
160
+ * `response.data`. `''` when there is nothing beyond the transport's own words.
161
+ */
162
+ function serverDetail(raw) {
163
+ if (!isObject(raw)) return "";
164
+ const details = raw.errorDetails;
165
+ if (isObject(details)) {
166
+ const m = details.message;
167
+ const hit = text(m) || (isObject(m) ? text(m.value) : "");
168
+ if (hit) return hit;
169
+ }
170
+ const response = isObject(raw.response) ? raw.response : void 0;
171
+ const data = response?.data ?? response?._data ?? raw.data;
172
+ if (isObject(data)) {
173
+ const err = data.error;
174
+ const hit = text(data.message) || text(data.detail) || text(data.title) || text(err) || (isObject(err) ? text(err.message) : "");
175
+ if (hit) return hit;
176
+ } else if (text(data)) return text(data);
177
+ if (!(raw instanceof Error)) {
178
+ const err = raw.error;
179
+ const hit = text(raw.errorMessage) || text(raw.detail) || (isObject(err) ? text(err.message) : "");
180
+ if (hit) return hit;
181
+ }
182
+ return "";
183
+ }
184
+ /** Is `value` nothing more than the reason phrase / a generic label for `status`? */
185
+ function isBoilerplate(value, status) {
186
+ const v = value.toLowerCase();
187
+ if (!v || v === "error" || v === "unknown error" || v === "request failed") return true;
188
+ if (/^http\s*\d{3}$/.test(v)) return true;
189
+ if (NETWORK_TEXTS.some((t) => v.includes(t))) return true;
190
+ if (status !== void 0) {
191
+ const phrase = REASON_PHRASES[status]?.toLowerCase();
192
+ if (phrase && (v === phrase || v === `${status} ${phrase}` || v === `${status}`)) return true;
193
+ if (v === String(status)) return true;
194
+ }
195
+ return Object.values(REASON_PHRASES).some((p) => p.toLowerCase() === v);
196
+ }
197
+ /**
198
+ * The line a person should read for `raw`.
199
+ *
200
+ * Order: a preset for the status (`'network'` for 0) → the error's own text →
201
+ * `messages.default`. When a preset is the headline, whatever the server said
202
+ * on top of the status becomes `detail`; without a preset, the raw text IS the
203
+ * headline and only a distinct server body is a detail. Never throws, never
204
+ * returns an empty `message`.
205
+ */
206
+ function describeError(raw, messages) {
207
+ const texts = resolveErrorMessages(messages);
208
+ const status = errorStatus(raw);
209
+ const fallback = texts.default || DEFAULT_ERROR_MESSAGES.default;
210
+ const rawText = normalizeError(raw, fallback);
211
+ const preset = status === void 0 ? void 0 : status === 0 ? texts.network : texts[status];
212
+ if (preset) {
213
+ const candidate = serverDetail(raw) || (rawText === fallback ? "" : rawText);
214
+ const detail = candidate && !isBoilerplate(candidate, status) && candidate !== preset ? candidate : "";
215
+ return detail ? {
216
+ message: preset,
217
+ status,
218
+ detail
219
+ } : {
220
+ message: preset,
221
+ status
222
+ };
223
+ }
224
+ const message = rawText || fallback;
225
+ const fromServer = serverDetail(raw);
226
+ const detail = fromServer && fromServer !== message && !isBoilerplate(fromServer, status) ? fromServer : "";
227
+ const out = { message };
228
+ if (status !== void 0) out.status = status;
229
+ if (detail) out.detail = detail;
230
+ return out;
231
+ }
232
+ //#endregion
233
+ //#region src/components/table/store-group-source.ts
234
+ /**
235
+ * Build a {@link MonoServerGroupSource} that drives **server-side** group paging
236
+ * straight from a devextreme `DataSource`'s store — no extra fetcher, no custom
237
+ * callbacks. `loadGroups` issues one cheap grouped query (group keys + counts +
238
+ * optional summaries, no rows); `loadRows` fetches a single group's page with
239
+ * `$filter` + `$skip`/`$top`. Returns `null` if the source has no usable store
240
+ * (e.g. a plain in-memory array), so the caller can fall back to client grouping.
241
+ */
242
+ function storeGroupSource(source, config) {
243
+ const store = source.store?.();
244
+ if (!store || typeof store.load !== "function") return null;
245
+ const { groupField, select, searchExpr, searchOperation, groupSummary } = config;
246
+ const summaryDesc = groupSummary ? Object.entries(groupSummary).map(([selector, summaryType]) => ({
247
+ selector,
248
+ summaryType
249
+ })) : void 0;
250
+ /**
251
+ * Build a devextreme filter expression for the active search (or undefined).
252
+ * Delegates to the shared builder, which takes the search state off a source;
253
+ * here the search arrives per-call via `ctx`, so a minimal stand-in carries it.
254
+ */
255
+ function searchFilter(search) {
256
+ if (!search || !searchExpr) return void 0;
257
+ return searchFilterOf({
258
+ load: () => [],
259
+ items: () => [],
260
+ searchValue: () => search,
261
+ searchExpr: () => searchExpr,
262
+ searchOperation: () => searchOperation ?? "contains"
263
+ });
264
+ }
265
+ /** devextreme `store.load` returns either an array or `{ data, … }`. */
266
+ const rows = (res) => Array.isArray(res) ? res : res?.data ?? [];
267
+ return {
268
+ async loadGroups(ctx) {
269
+ const filter = joinFilters([ctx.filter, searchFilter(ctx.search)], "and");
270
+ return rows(await store.load({
271
+ group: [{
272
+ selector: groupField,
273
+ isExpanded: false
274
+ }],
275
+ ...summaryDesc ? { groupSummary: summaryDesc } : {},
276
+ requireGroupCount: true,
277
+ ...filter ? { filter } : {},
278
+ sort: ctx.sort ?? [{ selector: groupField }],
279
+ ...ctx.loadOptions ?? {}
280
+ })).map((g) => ({
281
+ key: g.key,
282
+ count: Number(g.count ?? (Array.isArray(g.items) ? g.items.length : 0)) || 0,
283
+ aggregates: summaryDesc && Array.isArray(g.summary) ? Object.fromEntries(summaryDesc.map((s, i) => [s.selector, Number(g.summary[i]) || 0])) : void 0
284
+ }));
285
+ },
286
+ async loadRows(key, ctx) {
287
+ const groupFilter = [
288
+ groupField,
289
+ "=",
290
+ key
291
+ ];
292
+ const rowSelect = select ?? ctx.select;
293
+ return rows(await store.load({
294
+ filter: joinFilters([
295
+ groupFilter,
296
+ ctx.filter,
297
+ searchFilter(ctx.search)
298
+ ], "and"),
299
+ ...rowSelect ? { select: rowSelect } : {},
300
+ ...ctx.sort ? { sort: ctx.sort } : {},
301
+ skip: ctx.skip,
302
+ take: ctx.take,
303
+ requireTotalCount: false,
304
+ ...ctx.loadOptions ?? {}
305
+ }));
306
+ }
307
+ };
308
+ }
309
+ //#endregion
310
+ //#region src/components/table/date-filter-tree.ts
311
+ var DATE_LEVELS = [
312
+ "year",
313
+ "month",
314
+ "day",
315
+ "hour",
316
+ "minute",
317
+ "second"
318
+ ];
319
+ /** A `{ from, to }` pair of Dates (or ISO strings, which `setColumnFilter` also accepts). */
320
+ function isDateRange(v) {
321
+ if (!v || typeof v !== "object") return false;
322
+ const o = v;
323
+ const ok = (x) => x instanceof Date || typeof x === "string";
324
+ return ok(o.from) && ok(o.to);
325
+ }
326
+ var BLANK_KEY = "blank";
327
+ /**
328
+ * A column value as a Date, or `null` for a blank / unparsable one. Accepts a
329
+ * `Date`, a number (epoch ms), an ISO string, and a bare `YYYY-MM-DD` — the
330
+ * last is parsed as a LOCAL date on purpose (`new Date('2026-03-05')` would be
331
+ * UTC midnight and shift a day west of Greenwich).
332
+ */
333
+ function parseDateValue(v) {
334
+ if (v == null || v === "") return null;
335
+ if (v instanceof Date) return Number.isNaN(v.getTime()) ? null : v;
336
+ if (typeof v === "number") {
337
+ const d = new Date(v);
338
+ return Number.isNaN(d.getTime()) ? null : d;
339
+ }
340
+ if (typeof v !== "string") return null;
341
+ const s = v.trim();
342
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
343
+ if (m) return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
344
+ const d = new Date(s);
345
+ return Number.isNaN(d.getTime()) ? null : d;
346
+ }
347
+ /** `[year, month(1-12), day, hour, minute, second]` of a Date, local or UTC. */
348
+ function datePartsOf(d, utc = false) {
349
+ return utc ? [
350
+ d.getUTCFullYear(),
351
+ d.getUTCMonth() + 1,
352
+ d.getUTCDate(),
353
+ d.getUTCHours(),
354
+ d.getUTCMinutes(),
355
+ d.getUTCSeconds()
356
+ ] : [
357
+ d.getFullYear(),
358
+ d.getMonth() + 1,
359
+ d.getDate(),
360
+ d.getHours(),
361
+ d.getMinutes(),
362
+ d.getSeconds()
363
+ ];
364
+ }
365
+ /** The Date at the START of the period the parts describe (missing parts = their minimum). */
366
+ function dateFromParts(parts, utc) {
367
+ const [y, mo = 1, d = 1, h = 0, mi = 0, s = 0] = parts;
368
+ return utc ? new Date(Date.UTC(y, mo - 1, d, h, mi, s)) : new Date(y, mo - 1, d, h, mi, s);
369
+ }
370
+ /** The period `[from, to)` of a node at `level` with these parts. */
371
+ function periodOf(parts, level, utc = false) {
372
+ const i = DATE_LEVELS.indexOf(level);
373
+ const own = parts.slice(0, i + 1);
374
+ const next = [...own];
375
+ next[i] += 1;
376
+ return {
377
+ from: dateFromParts(own, utc),
378
+ to: dateFromParts(next, utc)
379
+ };
380
+ }
381
+ function labelFor(parts, level, utc, locale) {
382
+ const d = dateFromParts(parts, utc);
383
+ const tz = utc ? { timeZone: "UTC" } : {};
384
+ const two = (n) => String(n).padStart(2, "0");
385
+ switch (level) {
386
+ case "year": return String(parts[0]);
387
+ case "month": return new Intl.DateTimeFormat(locale, {
388
+ month: "long",
389
+ ...tz
390
+ }).format(d);
391
+ case "day": return `${two(parts[2])} (${new Intl.DateTimeFormat(locale, {
392
+ weekday: "short",
393
+ ...tz
394
+ }).format(d)})`;
395
+ case "hour": return `${two(parts[3])}:00`;
396
+ case "minute": return `${two(parts[3])}:${two(parts[4])}`;
397
+ case "second": return `${two(parts[3])}:${two(parts[4])}:${two(parts[5])}`;
398
+ }
399
+ }
400
+ /**
401
+ * Fold distinct timestamps into the tree. Only periods that occur are built,
402
+ * counts are summed up the branches, siblings are in chronological order, and
403
+ * a blank value becomes one `(Blanks)` node at the top (never expandable).
404
+ */
405
+ function buildDateTree(values, options = {}) {
406
+ const depth = DATE_LEVELS.indexOf(options.depth ?? "month");
407
+ const utc = options.utc ?? false;
408
+ const locale = options.locale;
409
+ const roots = /* @__PURE__ */ new Map();
410
+ let blanks = 0;
411
+ for (const v of values) {
412
+ const d = parseDateValue(v.value);
413
+ const count = Number(v.count) || 0;
414
+ if (!d) {
415
+ blanks += count;
416
+ continue;
417
+ }
418
+ const parts = datePartsOf(d, utc);
419
+ let siblings = roots;
420
+ for (let i = 0; i <= depth; i++) {
421
+ const level = DATE_LEVELS[i];
422
+ const own = parts.slice(0, i + 1);
423
+ let n = siblings.get(parts[i]);
424
+ if (!n) {
425
+ const { from, to } = periodOf(own, level, utc);
426
+ n = {
427
+ key: own.join("-"),
428
+ level,
429
+ parts: own,
430
+ label: labelFor(own, level, utc, locale),
431
+ count: 0,
432
+ from,
433
+ to,
434
+ children: []
435
+ };
436
+ siblings.set(parts[i], n);
437
+ }
438
+ n.count += count;
439
+ siblings = childMap(n);
440
+ }
441
+ }
442
+ const finish = (map) => [...map.entries()].sort(([a], [b]) => a - b).map(([, n]) => {
443
+ n.children = finish(childMap(n));
444
+ childMaps.delete(n);
445
+ return n;
446
+ });
447
+ const out = finish(roots);
448
+ if (blanks > 0) out.unshift({
449
+ key: BLANK_KEY,
450
+ level: "year",
451
+ parts: [],
452
+ label: "(Blanks)",
453
+ count: blanks,
454
+ from: null,
455
+ to: null,
456
+ children: []
457
+ });
458
+ return out;
459
+ }
460
+ var childMaps = /* @__PURE__ */ new WeakMap();
461
+ function childMap(n) {
462
+ let m = childMaps.get(n);
463
+ if (!m) {
464
+ m = /* @__PURE__ */ new Map();
465
+ childMaps.set(n, m);
466
+ }
467
+ return m;
468
+ }
469
+ /**
470
+ * A node's tick state from the set of ticked keys. A key in the set means the
471
+ * WHOLE node is ticked (so every descendant is on too); otherwise the children
472
+ * decide — all on → on, none → off, some (or a partly-ticked child) → mixed.
473
+ *
474
+ * Pass `roots` to have a ticked ANCESTOR count (the node is then on) — the
475
+ * renderer walks top-down and passes `inheritedOn` instead, which is the same
476
+ * answer without a path lookup per row.
477
+ */
478
+ function checkState(node, checked, roots, inheritedOn = false) {
479
+ if (inheritedOn || checked.has(node.key)) return "on";
480
+ if (roots && pathTo(node.key, roots).slice(0, -1).some((a) => checked.has(a.key))) return "on";
481
+ if (!node.children.length) return "off";
482
+ let on = 0;
483
+ let mixed = false;
484
+ for (const c of node.children) {
485
+ const s = checkState(c, checked);
486
+ if (s === "on") on++;
487
+ else if (s === "mixed") mixed = true;
488
+ }
489
+ if (on === node.children.length) return "on";
490
+ return on > 0 || mixed ? "mixed" : "off";
491
+ }
492
+ /**
493
+ * Tick or untick a node: the node's own key goes in (and every descendant key
494
+ * comes out — the node covers them) or the node and every descendant come out.
495
+ * Ticking a child of a fully-ticked parent first expands the parent's tick to its
496
+ * siblings, so unticking one day of a ticked month leaves the other days ticked.
497
+ */
498
+ function setChecked(node, on, checked, roots) {
499
+ const next = new Set(checked);
500
+ const path = pathTo(node.key, roots);
501
+ for (let i = 0; i < path.length - 1; i++) {
502
+ const anc = path[i];
503
+ if (next.has(anc.key)) {
504
+ next.delete(anc.key);
505
+ for (const c of anc.children) next.add(c.key);
506
+ }
507
+ }
508
+ const strip = (n) => {
509
+ next.delete(n.key);
510
+ n.children.forEach(strip);
511
+ };
512
+ strip(node);
513
+ if (on) next.add(node.key);
514
+ for (let i = path.length - 2; i >= 0; i--) {
515
+ const anc = path[i];
516
+ if (anc.children.length && anc.children.every((c) => next.has(c.key))) {
517
+ anc.children.forEach((c) => next.delete(c.key));
518
+ next.add(anc.key);
519
+ }
520
+ }
521
+ return next;
522
+ }
523
+ function pathTo(key, roots) {
524
+ const walk = (nodes, trail) => {
525
+ for (const n of nodes) {
526
+ if (n.key === key) return [...trail, n];
527
+ const found = walk(n.children, [...trail, n]);
528
+ if (found) return found;
529
+ }
530
+ return null;
531
+ };
532
+ return walk(roots, []) ?? [];
533
+ }
534
+ /** Tick everything (every root) or nothing. */
535
+ function setAllChecked(roots, on) {
536
+ return on ? new Set(roots.map((r) => r.key)) : /* @__PURE__ */ new Set();
537
+ }
538
+ /**
539
+ * The ticked periods as filter values — one `{ from, to }` per fully-ticked node,
540
+ * walking down only where a node is partly ticked. `(Blanks)` ticked adds a
541
+ * `null` value (the column filter's "is blank"), matching what the plain header
542
+ * filter sends for a blank.
543
+ */
544
+ function minimize(roots, checked) {
545
+ const out = [];
546
+ const walk = (n) => {
547
+ if (checked.has(n.key)) {
548
+ out.push(n.from && n.to ? {
549
+ from: n.from,
550
+ to: n.to
551
+ } : null);
552
+ return;
553
+ }
554
+ n.children.forEach(walk);
555
+ };
556
+ roots.forEach(walk);
557
+ return out;
558
+ }
559
+ /**
560
+ * The tick set that reproduces an applied filter (`table.columnFilter(field)`):
561
+ * a node is ticked when one of the ranges covers its whole period; a blank value
562
+ * ticks `(Blanks)`. A range that only partly covers a node ticks the children it
563
+ * covers instead.
564
+ */
565
+ function seedFromRanges(roots, values) {
566
+ const ranges = [];
567
+ let blank = false;
568
+ for (const v of values) if (v == null || v === "") blank = true;
569
+ else if (isDateRange(v)) {
570
+ const from = v.from instanceof Date ? v.from : new Date(v.from);
571
+ const to = v.to instanceof Date ? v.to : new Date(v.to);
572
+ if (!Number.isNaN(from.getTime()) && !Number.isNaN(to.getTime())) ranges.push({
573
+ from,
574
+ to
575
+ });
576
+ }
577
+ const out = /* @__PURE__ */ new Set();
578
+ const covers = (n) => !!n.from && !!n.to && ranges.some((r) => r.from.getTime() <= n.from.getTime() && r.to.getTime() >= n.to.getTime());
579
+ const walk = (n) => {
580
+ if (n.key === "blank") {
581
+ if (blank) out.add(n.key);
582
+ return;
583
+ }
584
+ if (covers(n)) {
585
+ out.add(n.key);
586
+ return;
587
+ }
588
+ n.children.forEach(walk);
589
+ };
590
+ roots.forEach(walk);
591
+ return out;
592
+ }
593
+ /** Whether a Date falls in `[from, to)`. */
594
+ function inRange(d, r) {
595
+ const t = d.getTime();
596
+ return t >= r.from.getTime() && t < r.to.getTime();
597
+ }
598
+ //#endregion
599
+ //#region src/components/table/mono-data-grid.ts
600
+ function isCanceled(err) {
601
+ if (!err) return false;
602
+ return err === "canceled" || err.name === "canceled" || err.message === "canceled" || err.__id === "canceled";
603
+ }
604
+ function monoDataGrid(ds = null, opts = {}) {
605
+ let bound = null;
606
+ let searchFieldsOverride;
607
+ let searchFieldsCache = null;
608
+ /**
609
+ * The configured search fields, or `undefined` when none of the four spellings
610
+ * was given. `undefined` is load-bearing: it means "not configured", which lets
611
+ * a data source keep using its own `searchExpr` instead of being handed `[]`.
612
+ *
613
+ * An override set through {@link setSearchExpr} (i.e. by `<mono-table-search>`)
614
+ * wins over the options — the element is the more specific declaration.
615
+ */
616
+ function searchFields() {
617
+ if (searchFieldsOverride !== void 0) return searchFieldsOverride;
618
+ const keys = [
619
+ opts.searchExpr,
620
+ opts["search-expr"],
621
+ opts.searchValue,
622
+ opts["search-value"]
623
+ ];
624
+ if (searchFieldsCache && searchFieldsCache.keys.length === keys.length && searchFieldsCache.keys.every((k, i) => k === keys[i])) return searchFieldsCache.entries;
625
+ const entries = mergeSearchFields(opts);
626
+ searchFieldsCache = {
627
+ keys,
628
+ entries
629
+ };
630
+ return entries;
631
+ }
632
+ /** Whether the CONSUMER declared fields (any spelling, or via `setSearchValue`). */
633
+ function searchFieldsConfigured() {
634
+ return searchFields() !== void 0;
635
+ }
636
+ /**
637
+ * Whether the bound SOURCE carries its own column list.
638
+ *
639
+ * A devextreme DataSource can be built with `searchExpr` of its own, and a grid
640
+ * that declares nothing relies on that source folding the search itself. The
641
+ * `'*'` default must not take that over — it would replace a correct server-side
642
+ * search with one derived from the loaded page.
643
+ */
644
+ function sourceDeclaresSearchFields() {
645
+ const e = bound?.searchExpr?.();
646
+ return Array.isArray(e) ? e.length > 0 : !!e;
647
+ }
648
+ /**
649
+ * The fields actually searched: what the consumer configured, else what the
650
+ * source declares (left to the source — see above), else the **default `'*'`**,
651
+ * meaning every top-level field.
652
+ *
653
+ * `'*'` is resolved against real rows by `searchEntries()` and never leaves the
654
+ * client as a literal — it expands to concrete column names first.
655
+ */
656
+ function effectiveSearchFields() {
657
+ const configured = searchFields();
658
+ if (configured !== void 0) return configured;
659
+ if (sourceDeclaresSearchFields()) return void 0;
660
+ return [...DEFAULT_SEARCH_FIELDS];
661
+ }
662
+ let groupFields = toFields(opts.group);
663
+ const collapsed = /* @__PURE__ */ new Set();
664
+ let groupPageSize = Math.max(1, opts.pageSize ?? 10);
665
+ let groupPageIndex = 0;
666
+ let pageSizeAll = false;
667
+ const chunkSize = Math.max(1, opts.chunkSize ?? 100);
668
+ let suppressSync = false;
669
+ let allRows = [];
670
+ let scrollMode = "off";
671
+ let accumulated = [];
672
+ let scrollNextPage = 0;
673
+ let scrollHasMore = false;
674
+ let scrollLoading = false;
675
+ let scrollToken = 0;
676
+ let preferredPageSize = null;
677
+ let sourcePageSize = null;
678
+ let vStart = 0;
679
+ let vEnd = 0;
680
+ let vPadTop = 0;
681
+ let vPadBottom = 0;
682
+ const groupRowPaging = /* @__PURE__ */ new Map();
683
+ const defaultGroupRowPageSize = opts.groupRowPageSize && opts.groupRowPageSize > 0 ? Math.floor(opts.groupRowPageSize) : 0;
684
+ /** Effective row paging for a group: explicit registration, else the default. */
685
+ function groupRowPagingFor(path) {
686
+ const explicit = groupRowPaging.get(path);
687
+ if (explicit) return explicit;
688
+ return defaultGroupRowPageSize ? {
689
+ pageIndex: 0,
690
+ pageSize: defaultGroupRowPageSize
691
+ } : null;
692
+ }
693
+ const pathIndex = /* @__PURE__ */ new Map();
694
+ let serverGroup = opts.serverGroup ?? null;
695
+ let serverGroups = [];
696
+ const groupRows = /* @__PURE__ */ new Map();
697
+ const groupRowsKey = /* @__PURE__ */ new Map();
698
+ const groupRowLoading = /* @__PURE__ */ new Set();
699
+ let searchValue = null;
700
+ let groupSeq = 0;
701
+ const isGrouped = () => groupFields.length > 0;
702
+ /** Server mode requires a single group field + a `serverGroup` provider. */
703
+ const serverMode = () => !!serverGroup && groupFields.length === 1;
704
+ function clearRowCaches() {
705
+ groupRows.clear();
706
+ groupRowsKey.clear();
707
+ groupRowLoading.clear();
708
+ }
709
+ const formOps = [];
710
+ let formKey = null;
711
+ let formShowForm = false;
712
+ let formTempSeq = 0;
713
+ let formHandle = null;
714
+ const MONO_ROW_KEY = Symbol("monoFormRowKey");
715
+ const EDITABLE_MARK = "Editable";
716
+ const ctrl = {
717
+ items: [],
718
+ mapped: [],
719
+ displayRows: [],
720
+ loading: false,
721
+ hasLoaded: false,
722
+ error: null,
723
+ totalCount: 0,
724
+ pageIndex: 0,
725
+ pageSize: 0,
726
+ pageSizeAll: false,
727
+ pageCount: 0,
728
+ searchTerms: [],
729
+ sorts: [],
730
+ sortField: null,
731
+ sortOrder: null,
732
+ grouped: groupFields.length > 0,
733
+ scrollMode: "off",
734
+ hasMore: false,
735
+ loadedCount: 0,
736
+ virtualStart: 0,
737
+ virtualEnd: 0,
738
+ virtualPadTop: 0,
739
+ virtualPadBottom: 0,
740
+ dataSource: null,
741
+ editingKey: null,
742
+ editableTrigger: opts.editableTrigger ?? "click",
743
+ editorNavKeys: opts.editorNavKeys ?? "tab-arrows",
744
+ onCellChange: null,
745
+ pendingData: formOps,
746
+ load,
747
+ reload,
748
+ clear,
749
+ setPage,
750
+ setScrollPaging,
751
+ loadNext,
752
+ setVirtualWindow,
753
+ setPreferredPageSize,
754
+ setPageSize,
755
+ setSearch,
756
+ setSearchTerms,
757
+ setSearchValue: setSearchExpr,
758
+ setSearchExpr,
759
+ searchValue: effectiveSearchFields,
760
+ searchExpr: effectiveSearchFields,
761
+ setFilter,
762
+ setDataSourceOptions,
763
+ setOdataOptions,
764
+ refresh,
765
+ resolvedDataSourceOptions,
766
+ setColumnFilter,
767
+ columnFilterCombining,
768
+ errorBehaviour,
769
+ setErrorBehaviour,
770
+ columnFilter,
771
+ filteredColumns,
772
+ distinctValues,
773
+ setSort,
774
+ sortCombining,
775
+ sortOf,
776
+ sortIndex,
777
+ clearSort,
778
+ columnMap,
779
+ setData,
780
+ setGroup,
781
+ toggleGroup,
782
+ isGroupCollapsed,
783
+ expandAllGroups,
784
+ collapseAllGroups,
785
+ setGroupPageSize,
786
+ setGroupPage,
787
+ clearGroupPaging,
788
+ groupPageInfo,
789
+ groupNode,
790
+ getData,
791
+ summary,
792
+ summaryText,
793
+ setSummaryResolver,
794
+ registerSummary,
795
+ unregisterSummary,
796
+ summaries,
797
+ buildGroups: buildGroupTree,
798
+ export: exportReport,
799
+ import: importSheet,
800
+ isCellImported,
801
+ isRowImported,
802
+ bind,
803
+ subscribe,
804
+ dispose,
805
+ registerColumn,
806
+ unregisterColumn,
807
+ detail,
808
+ check,
809
+ registerDetail,
810
+ unregisterDetail,
811
+ columns,
812
+ props,
813
+ registeredColumns,
814
+ editableColumns,
815
+ moveEditor,
816
+ bindRowTrigger,
817
+ beginEditRow,
818
+ cancelEdit,
819
+ isEditingRow,
820
+ isEditingCell,
821
+ commitCell,
822
+ editorKeydown,
823
+ stageCell,
824
+ cellValue,
825
+ isCellDirty,
826
+ isRowDirty,
827
+ pendingCount,
828
+ hasChanges,
829
+ changes,
830
+ discardChanges,
831
+ saveChanges,
832
+ form,
833
+ rowKey
834
+ };
835
+ if (opts.filterBuilder) ctrl.filterBuilder = monoFilterBuilder({
836
+ ...opts.filterBuilder,
837
+ dataGrid: ctrl
838
+ });
839
+ const columnEls = /* @__PURE__ */ new Set();
840
+ const pending = /* @__PURE__ */ new Map();
841
+ function normalizeSummary(cfg) {
842
+ if (!cfg) return {
843
+ specs: [],
844
+ recalcSearching: false,
845
+ recalcChangedData: true,
846
+ resolve: null
847
+ };
848
+ if (Array.isArray(cfg)) return {
849
+ specs: [...cfg],
850
+ recalcSearching: false,
851
+ recalcChangedData: true,
852
+ resolve: null
853
+ };
854
+ const specs = [];
855
+ for (const [field, spec] of Object.entries(cfg.fields ?? {})) {
856
+ const list = Array.isArray(spec) ? spec : [spec];
857
+ for (const s of list) specs.push({
858
+ field,
859
+ ...s
860
+ });
861
+ }
862
+ return {
863
+ specs,
864
+ recalcSearching: cfg.recalculate?.searching ?? false,
865
+ recalcChangedData: cfg.recalculate?.changedData ?? true,
866
+ resolve: cfg.resolve ?? null
867
+ };
868
+ }
869
+ const _summaryCfg = normalizeSummary(opts.summary);
870
+ const summarySpecs = _summaryCfg.specs;
871
+ for (const col of opts.props?.th ?? []) {
872
+ if (!col?.field || !col.summary) continue;
873
+ const spec = {
874
+ field: col.field,
875
+ ...col.summary
876
+ };
877
+ if (!summarySpecs.some((s) => s.field === spec.field && (s.type ?? "sum") === (spec.type ?? "sum") && s.name === spec.name)) summarySpecs.push(spec);
878
+ }
879
+ const recalcSearching = _summaryCfg.recalcSearching;
880
+ const recalcChangedData = _summaryCfg.recalcChangedData;
881
+ let summaryResolve = _summaryCfg.resolve;
882
+ const summaryCache = /* @__PURE__ */ new Map();
883
+ /**
884
+ * The `$apply`-through-the-store switch, shared by the summary footer and the
885
+ * header filter: off by option, or off for good once the backend rejected one.
886
+ */
887
+ const applyGate = createApplyGate(opts.serverApply !== false);
888
+ let errorMode = opts.onError === "keep-list" ? "keep-list" : "clear-list";
889
+ function errorBehaviour() {
890
+ return errorMode;
891
+ }
892
+ function setErrorBehaviour(next) {
893
+ errorMode = next === "keep-list" ? "keep-list" : "clear-list";
894
+ }
895
+ const summaryKey = (spec) => `${spec.field ?? "*"}|${spec.type ?? "sum"}|${spec.name ?? ""}`;
896
+ /** Find the spec that a `(field, type)` read refers to. Type is matched only when given. */
897
+ function findSummarySpec(field, type) {
898
+ return summarySpecs.find((s) => (s.field ?? "") === field && (type === void 0 || (s.type ?? "sum") === type));
899
+ }
900
+ /**
901
+ * How many elements currently want each spec.
902
+ *
903
+ * Registration is REFCOUNTED because several `<mono-table-summary>` cells can
904
+ * declare the same field (a footer total and a toolbar total, say). Only the
905
+ * first pushes the spec; previously the first to unmount removed the shared spec
906
+ * out from under the others, and since nothing re-runs their registration they
907
+ * rendered an empty value forever.
908
+ */
909
+ const summaryRefs = /* @__PURE__ */ new Map();
910
+ function registerSummary(spec) {
911
+ const key = summaryKey(spec);
912
+ summaryRefs.set(key, (summaryRefs.get(key) ?? 0) + 1);
913
+ if (summarySpecs.some((s) => summaryKey(s) === key)) return;
914
+ summarySpecs.push(spec);
915
+ recomputeSummaries(true);
916
+ }
917
+ function unregisterSummary(spec) {
918
+ const key = summaryKey(spec);
919
+ const refs = (summaryRefs.get(key) ?? 1) - 1;
920
+ if (refs > 0) {
921
+ summaryRefs.set(key, refs);
922
+ return;
923
+ }
924
+ summaryRefs.delete(key);
925
+ const i = summarySpecs.findIndex((s) => summaryKey(s) === key);
926
+ if (i >= 0) summarySpecs.splice(i, 1);
927
+ }
928
+ function summaries() {
929
+ return summarySpecs;
930
+ }
931
+ /** Build the public result object for one spec from the cache. */
932
+ function summaryResult(spec) {
933
+ const value = summaryCache.get(summaryKey(spec)) ?? null;
934
+ return {
935
+ field: spec.field,
936
+ type: spec.type ?? "sum",
937
+ name: spec.name,
938
+ value,
939
+ text: formatSummary(value, spec)
940
+ };
941
+ }
942
+ const summaryHandle = {
943
+ get(field, type) {
944
+ const spec = findSummarySpec(field, type);
945
+ return spec ? summaryResult(spec) : null;
946
+ },
947
+ getAll() {
948
+ return summarySpecs.map(summaryResult);
949
+ }
950
+ };
951
+ function summary(field, type) {
952
+ if (field === void 0) return summaryHandle;
953
+ let spec = findSummarySpec(field, type);
954
+ if (!spec) {
955
+ spec = {
956
+ field,
957
+ type: type ?? "sum"
958
+ };
959
+ registerSummary(spec);
960
+ }
961
+ return summaryCache.get(summaryKey(spec)) ?? null;
962
+ }
963
+ let summaryVersion = 0;
964
+ let summaryComputed = -1;
965
+ /**
966
+ * Bumped on EVERY row-set change, unconditionally — `summaryVersion` is gated
967
+ * by the `recalculate` config, so it can't be used to answer "is the row set
968
+ * still the one I last read?". `check().selectAll()` uses this to know whether
969
+ * its cached drain is still valid.
970
+ */
971
+ let rowSetVersion = 0;
972
+ const bumpSummary = (reason) => {
973
+ rowSetVersion++;
974
+ if (reason === "search" && !recalcSearching) return;
975
+ if (reason === "data" && !recalcChangedData && summaryComputed >= 0) return;
976
+ summaryVersion++;
977
+ };
978
+ function summaryText(field, type) {
979
+ const spec = findSummarySpec(field, type) ?? {
980
+ field,
981
+ type: type ?? "sum"
982
+ };
983
+ return formatSummary(summary(field, type), spec);
984
+ }
985
+ function formatSummary(value, spec) {
986
+ if (spec.format) return spec.format(value, ctrl.items);
987
+ if (value === null || Number.isNaN(value)) return spec.emptyText ?? "—";
988
+ const body = spec.precision != null ? value.toLocaleString(void 0, {
989
+ minimumFractionDigits: spec.precision,
990
+ maximumFractionDigits: spec.precision
991
+ }) : value.toLocaleString();
992
+ return `${spec.prefix ?? ""}${body}${spec.suffix ?? ""}`;
993
+ }
994
+ /** Aggregate one spec over `rows`. Non-numeric values are skipped. */
995
+ function aggregate(spec, rows) {
996
+ const type = spec.type ?? "sum";
997
+ if (type === "count") return rows.length;
998
+ const field = spec.field;
999
+ if (!field) return null;
1000
+ const valuesOf = (row) => {
1001
+ const raw = getFieldValue(row, field);
1002
+ return Array.isArray(raw) ? raw : [raw];
1003
+ };
1004
+ if (type === "countDistinct") {
1005
+ const seen = /* @__PURE__ */ new Set();
1006
+ for (const row of rows) for (const v of valuesOf(row)) if (v != null && v !== "") seen.add(v);
1007
+ return seen.size;
1008
+ }
1009
+ const nums = [];
1010
+ for (const row of rows) for (const raw of valuesOf(row)) {
1011
+ if (raw == null || raw === "") continue;
1012
+ const n = Number(raw);
1013
+ if (Number.isFinite(n)) nums.push(n);
1014
+ }
1015
+ if (!nums.length) return null;
1016
+ switch (type) {
1017
+ case "sum": return nums.reduce((a, b) => a + b, 0);
1018
+ case "avg": return nums.reduce((a, b) => a + b, 0) / nums.length;
1019
+ case "min": return Math.min(...nums);
1020
+ case "max": return Math.max(...nums);
1021
+ default: return null;
1022
+ }
1023
+ }
1024
+ /**
1025
+ * Run the consumer's resolver, normalised to one value per spec.
1026
+ *
1027
+ * Declines (`null`) for every case the caller should not have to think about:
1028
+ * no resolver, no bound source, an ARRAY source (already in memory — a network
1029
+ * round trip for rows we hold would be absurd), a spec set no `$apply` can
1030
+ * express, or a resolver that returned the wrong shape.
1031
+ *
1032
+ * A THROW is caught and reported once rather than propagated: a failed totals
1033
+ * request should degrade to a slower total, not break the grid. It is warned
1034
+ * about exactly once per grid so a persistent fault is visible without
1035
+ * flooding the console on every reload.
1036
+ */
1037
+ let _summaryResolveWarned = false;
1038
+ /**
1039
+ * Swap the resolver after construction.
1040
+ *
1041
+ * The option form covers a grid you build yourself. This covers the other
1042
+ * case, which is at least as common: a grid built by a shared factory or
1043
+ * composable that does not forward `summary` — the consumer still owns the
1044
+ * fetcher, and needs somewhere to hand it over.
1045
+ *
1046
+ * Forces a recompute rather than waiting for the next data change, so a footer
1047
+ * already on screen with a locally-drained total corrects itself, and clears
1048
+ * the one-shot warning so a newly attached resolver gets a fair hearing.
1049
+ */
1050
+ function setSummaryResolver(resolve) {
1051
+ summaryResolve = resolve ?? null;
1052
+ _summaryResolveWarned = false;
1053
+ recomputeSummaries(true);
1054
+ }
1055
+ /**
1056
+ * A resolver's (or the server's) answer → one value per spec, in spec order.
1057
+ * Positional, or keyed by alias — falling back to the plain field name, which
1058
+ * is what an `$apply` aliases to whenever a field carries one aggregate (the
1059
+ * ordinary case). `null` when the shape cannot be read.
1060
+ */
1061
+ function summaryOutToValues(out) {
1062
+ const num = (v) => {
1063
+ const n = Number(v);
1064
+ return Number.isFinite(n) ? n : null;
1065
+ };
1066
+ if (Array.isArray(out)) return out.length === summarySpecs.length ? out.map(num) : null;
1067
+ if (!out || typeof out !== "object") return null;
1068
+ const row = out;
1069
+ const aliases = summaryAliases(summarySpecs);
1070
+ return summarySpecs.map((spec, i) => {
1071
+ const key = aliases[i];
1072
+ if (key in row) return num(row[key]);
1073
+ if (spec.field && spec.field in row) return num(row[spec.field]);
1074
+ return null;
1075
+ });
1076
+ }
1077
+ async function resolveSummaries() {
1078
+ if (!summaryResolve || !bound || Array.isArray(bound)) return null;
1079
+ const apply = buildSummaryApply(summarySpecs);
1080
+ if (!apply) return null;
1081
+ const filter = summaryFilterOf(bound);
1082
+ const aliases = summaryAliases(summarySpecs);
1083
+ try {
1084
+ const out = await summaryResolve({
1085
+ specs: summarySpecs,
1086
+ aliases,
1087
+ filter,
1088
+ odataFilter: arrayToODataString(filter) ?? "",
1089
+ apply,
1090
+ applyWithFilter: composeSummaryApply(apply, filter),
1091
+ source: bound
1092
+ });
1093
+ if (!out) return null;
1094
+ return summaryOutToValues(out);
1095
+ } catch (err) {
1096
+ if (!_summaryResolveWarned) {
1097
+ _summaryResolveWarned = true;
1098
+ console.warn("[mono-table] summary resolver failed; falling back to reading every row. Return \"null\" from the resolver to fall back without this warning.", err);
1099
+ }
1100
+ return null;
1101
+ }
1102
+ }
1103
+ /**
1104
+ * The totals as ONE `$apply=filter(…)/aggregate(…)` request through the bound
1105
+ * source's own store — the same clause the consumer resolver is offered, sent
1106
+ * by the grid itself when there is no resolver (or it declined).
1107
+ *
1108
+ * `null` is the decline: no store, the path switched off, a store that cannot
1109
+ * carry an `$apply` (`loadApply`), a response that is not one aggregated row
1110
+ * (a server that ignored the clause hands back entities, which carry none of
1111
+ * the aliases), or a failed request — which also reports to the gate, so a
1112
+ * backend that rejects `$apply` is asked exactly once.
1113
+ */
1114
+ async function resolveSummariesViaStore() {
1115
+ if (applyGate.skip || !bound || Array.isArray(bound) || !hasStore()) return null;
1116
+ const apply = buildSummaryApply(summarySpecs);
1117
+ if (!apply) return null;
1118
+ let rows;
1119
+ try {
1120
+ rows = await loadApply(bound.store(), composeSummaryApply(apply, summaryFilterOf(bound)));
1121
+ } catch (err) {
1122
+ applyGate.reject(err);
1123
+ return null;
1124
+ }
1125
+ if (!rows || rows.length !== 1 || !isRolledUp(rows, summaryAliases(summarySpecs))) return null;
1126
+ return summaryOutToValues(rows[0]);
1127
+ }
1128
+ let _summaryRun = 0;
1129
+ /**
1130
+ * Recompute every registered summary over the full filtered set and notify.
1131
+ * No-op when nothing is registered (an unused grid never fetches) or — unless
1132
+ * `force`d — when the set hasn't changed since the last compute (so sort/paging
1133
+ * don't refetch). A run token guards against overlapping async reads landing
1134
+ * out of order.
1135
+ *
1136
+ * BURST COALESCING: a footer mounts many `<mono-table-summary>` elements and each
1137
+ * registration forces a recompute — Lit elements upgrade across separate tasks, so a
1138
+ * 48-column footer used to fire one resolver request PER element, each carrying only
1139
+ * the specs registered so far (the `$apply` aggregate list visibly grew 1 → 2 → N).
1140
+ * While one recompute is in flight, later ones only mark `_summaryQueued` and return;
1141
+ * a single trailing recompute then runs with the FULL spec set. A mount burst is
1142
+ * capped at leading + trailing requests whatever N is, and the trailing run's cache
1143
+ * write is what the footer finally shows.
1144
+ */
1145
+ let _summaryInFlight = false;
1146
+ let _summaryQueued = false;
1147
+ async function recomputeSummaries(force = false) {
1148
+ if (!summarySpecs.length) return;
1149
+ if (!force && summaryComputed === summaryVersion) return;
1150
+ if (_summaryInFlight) {
1151
+ _summaryQueued = true;
1152
+ return;
1153
+ }
1154
+ _summaryInFlight = true;
1155
+ const run = ++_summaryRun;
1156
+ const captured = summaryVersion;
1157
+ try {
1158
+ let resolved = await resolveSummaries();
1159
+ if (run !== _summaryRun) return;
1160
+ if (!resolved) {
1161
+ resolved = await resolveSummariesViaStore();
1162
+ if (run !== _summaryRun) return;
1163
+ }
1164
+ if (resolved) {
1165
+ summarySpecs.forEach((spec, i) => {
1166
+ summaryCache.set(summaryKey(spec), resolved[i] ?? null);
1167
+ });
1168
+ summaryComputed = captured;
1169
+ notify();
1170
+ return;
1171
+ }
1172
+ const rows = await getData();
1173
+ if (run !== _summaryRun) return;
1174
+ for (const spec of summarySpecs) summaryCache.set(summaryKey(spec), aggregate(spec, rows));
1175
+ summaryComputed = captured;
1176
+ notify();
1177
+ } finally {
1178
+ _summaryInFlight = false;
1179
+ if (_summaryQueued) {
1180
+ _summaryQueued = false;
1181
+ recomputeSummaries(true);
1182
+ }
1183
+ }
1184
+ }
1185
+ function registerColumn(el) {
1186
+ columnEls.add(el);
1187
+ }
1188
+ function unregisterColumn(el) {
1189
+ columnEls.delete(el);
1190
+ if (columnEls.size === 0) unbindRowTrigger();
1191
+ }
1192
+ const detailEls = /* @__PURE__ */ new Set();
1193
+ function registerDetail(el) {
1194
+ detailEls.add(el);
1195
+ }
1196
+ function unregisterDetail(el) {
1197
+ detailEls.delete(el);
1198
+ }
1199
+ const detailHandle = {
1200
+ expandAll() {
1201
+ for (const el of detailEls) el.open = true;
1202
+ notify();
1203
+ },
1204
+ collapseAll() {
1205
+ for (const el of detailEls) if (!el.stayOpen) el.open = false;
1206
+ notify();
1207
+ },
1208
+ collapseOthers(except) {
1209
+ let changed = false;
1210
+ for (const el of detailEls) {
1211
+ if (el === except || el.stayOpen || !el.open) continue;
1212
+ el.open = false;
1213
+ changed = true;
1214
+ }
1215
+ if (changed) notify();
1216
+ },
1217
+ openCount() {
1218
+ let open = 0;
1219
+ for (const el of detailEls) if (el.open) open++;
1220
+ return open;
1221
+ },
1222
+ getAll: () => domOrdered(Array.from(detailEls))
1223
+ };
1224
+ function detail() {
1225
+ return detailHandle;
1226
+ }
1227
+ const checkedRows = /* @__PURE__ */ new Map();
1228
+ let checkConfig = {};
1229
+ let checkPending = false;
1230
+ /** `keyValue` as a list (empty = project nothing, i.e. keep the whole row). */
1231
+ function checkKeys() {
1232
+ const k = checkConfig.keyValue;
1233
+ if (!k) return [];
1234
+ return (Array.isArray(k) ? k : [k]).filter(Boolean);
1235
+ }
1236
+ /**
1237
+ * Columns to fetch while draining, or `null` for "everything".
1238
+ *
1239
+ * `keyExpr` is normally included, even when the consumer only asked for other
1240
+ * fields: `rowKeyOf` reads `row[keyExpr]` and falls back to the array INDEX
1241
+ * when it's missing, so a drain without it would key every row by its position
1242
+ * and collide with the loaded page. A `[*]` path can't be expressed as
1243
+ * `$select` at all (it needs an expand), so any wildcard drops the optimisation.
1244
+ *
1245
+ * EXCEPT when `keyExpr` names a field the rows do not have — a misconfigured
1246
+ * (or defaulted) key would otherwise put a non-existent column on the wire and
1247
+ * the server rejects the whole drain: OData answers `$select=Id` on an entity
1248
+ * without `Id` with a 400. A wrong key should cost a wider fetch, not a failed
1249
+ * one, so drop the optimisation and read whole rows instead.
1250
+ */
1251
+ function checkSelect() {
1252
+ const keys = checkKeys();
1253
+ if (!keys.length) return null;
1254
+ const cols = [];
1255
+ for (const key of keys) {
1256
+ if (parseFieldPath(key).hasWildcard) return null;
1257
+ const selector = toODataSelector(key);
1258
+ if (!selector) return null;
1259
+ cols.push(selector);
1260
+ }
1261
+ if (!cols.includes(keyExpr)) {
1262
+ if (!rowsHaveKeyExpr()) {
1263
+ warnMissingKeyExpr();
1264
+ return null;
1265
+ }
1266
+ cols.push(keyExpr);
1267
+ }
1268
+ return cols;
1269
+ }
1270
+ /** A positive integer limit, or undefined when the config has none. */
1271
+ function limitOf(value) {
1272
+ if (value == null) return void 0;
1273
+ const n = Math.floor(Number(value));
1274
+ return Number.isFinite(n) && n >= 0 ? n : void 0;
1275
+ }
1276
+ function checkLimits() {
1277
+ const max = limitOf(checkConfig.max);
1278
+ const min = limitOf(checkConfig.min);
1279
+ return {
1280
+ ...max !== void 0 ? { max } : {},
1281
+ ...min !== void 0 ? { min } : {}
1282
+ };
1283
+ }
1284
+ /**
1285
+ * Add rows up to the cap. Rows already selected never count against the room
1286
+ * left — re-selecting a drained set the user has partly unticked must still be
1287
+ * a no-op for what is there. When `capped` is false (`replace`) the cap is
1288
+ * ignored: a value the developer pushes in is theirs.
1289
+ */
1290
+ function addChecked(rows, capped = true) {
1291
+ const max = capped ? checkLimits().max : void 0;
1292
+ rows.forEach((row, i) => {
1293
+ const key = rowKeyOf(row, i);
1294
+ if (!checkedRows.has(key) && max !== void 0 && checkedRows.size >= max) return;
1295
+ checkedRows.set(key, row);
1296
+ });
1297
+ }
1298
+ /**
1299
+ * The last completed drain, so "select all → untick one → select all again"
1300
+ * costs nothing instead of walking the whole source a second time.
1301
+ *
1302
+ * Deliberately NOT an OData `not in (…)` query over the already-selected keys:
1303
+ * the exclusion list is largest exactly when the selection is largest, so
1304
+ * re-selecting after unticking one of 1,085 rows would mean putting 1,084 ids
1305
+ * in a URL — past every practical URL limit, and slower than the paged drain it
1306
+ * replaces. Remembering the drain is the cheap direction.
1307
+ *
1308
+ * One entry, invalidated whenever the ROW SET could differ: a different
1309
+ * filter/search (compared by reference, since the controller builds a fresh
1310
+ * expression for every real change), a different `$select` projection, or any
1311
+ * `bumpSummary` (bind / reload / setData / search / filter). Sorting and paging
1312
+ * deliberately do NOT invalidate — they reorder or window the same set.
1313
+ */
1314
+ let drainedRows = null;
1315
+ let drainedFilter = null;
1316
+ let drainedSearch = null;
1317
+ let drainedTerms = "";
1318
+ let drainedSelect = "";
1319
+ let drainedVersion = -1;
1320
+ function drainIsReusable() {
1321
+ if (!drainedRows) return false;
1322
+ return Object.is(drainedFilter, bound?.filter?.() ?? null) && Object.is(drainedSearch, bound?.searchValue?.() ?? null) && drainedTerms === JSON.stringify(ctrl.searchTerms) && drainedSelect === JSON.stringify(checkSelect()) && drainedVersion === rowSetVersion;
1323
+ }
1324
+ function rememberDrain(rows) {
1325
+ drainedRows = rows;
1326
+ drainedFilter = bound?.filter?.() ?? null;
1327
+ drainedSearch = bound?.searchValue?.() ?? null;
1328
+ drainedTerms = JSON.stringify(ctrl.searchTerms);
1329
+ drainedSelect = JSON.stringify(checkSelect());
1330
+ drainedVersion = rowSetVersion;
1331
+ }
1332
+ const checkHandle = {
1333
+ getAll() {
1334
+ const keys = checkKeys();
1335
+ return [...checkedRows.values()].map((row) => projectFields(row, keys));
1336
+ },
1337
+ count: () => checkedRows.size,
1338
+ rows: () => [...checkedRows.values()],
1339
+ isChecked(row) {
1340
+ return checkedRows.has(typeof row === "string" ? row : rowKey(row));
1341
+ },
1342
+ toggle(item, checked) {
1343
+ if (item == null) return;
1344
+ const key = rowKey(item);
1345
+ const has = checkedRows.has(key);
1346
+ const next = checked === void 0 ? !has : checked;
1347
+ const { max, min } = checkLimits();
1348
+ if (!(next && !has && max !== void 0 && checkedRows.size >= max || !next && has && min !== void 0 && checkedRows.size <= min)) if (next) checkedRows.set(key, item);
1349
+ else checkedRows.delete(key);
1350
+ notify();
1351
+ },
1352
+ selectPage() {
1353
+ addChecked(ctrl.items);
1354
+ notify();
1355
+ },
1356
+ replace(rows) {
1357
+ checkedRows.clear();
1358
+ addChecked(rows, false);
1359
+ notify();
1360
+ },
1361
+ async selectAll() {
1362
+ if (checkPending) return;
1363
+ if (drainIsReusable()) {
1364
+ addChecked(drainedRows);
1365
+ notify();
1366
+ return;
1367
+ }
1368
+ checkPending = true;
1369
+ notify();
1370
+ try {
1371
+ const select = checkSelect();
1372
+ const rows = await readAllRows(bound, {
1373
+ chunkSize: Math.max(1, Math.floor(checkConfig.chunk ?? 100)),
1374
+ ...select ? { select } : {},
1375
+ sort: mergedSort()
1376
+ });
1377
+ rememberDrain(rows);
1378
+ addChecked(rows);
1379
+ clearError();
1380
+ } catch (err) {
1381
+ captureError(err, "selectAll");
1382
+ throw err;
1383
+ } finally {
1384
+ checkPending = false;
1385
+ notify();
1386
+ }
1387
+ },
1388
+ clear() {
1389
+ if (!checkedRows.size) return;
1390
+ const min = checkLimits().min ?? 0;
1391
+ if (min > 0) {
1392
+ if (checkedRows.size <= min) {
1393
+ notify();
1394
+ return;
1395
+ }
1396
+ const keep = [...checkedRows.entries()].slice(0, min);
1397
+ checkedRows.clear();
1398
+ for (const [key, row] of keep) checkedRows.set(key, row);
1399
+ } else checkedRows.clear();
1400
+ notify();
1401
+ },
1402
+ limits: checkLimits,
1403
+ get atMax() {
1404
+ const max = checkLimits().max;
1405
+ return max !== void 0 && checkedRows.size >= max;
1406
+ },
1407
+ get atMin() {
1408
+ const min = checkLimits().min;
1409
+ return min !== void 0 && checkedRows.size <= min;
1410
+ },
1411
+ get pending() {
1412
+ return checkPending;
1413
+ },
1414
+ get allChecked() {
1415
+ const rows = ctrl.items;
1416
+ return rows.length > 0 && rows.every((row, i) => checkedRows.has(rowKeyOf(row, i)));
1417
+ },
1418
+ get someChecked() {
1419
+ const rows = ctrl.items;
1420
+ if (!rows.length || !checkedRows.size) return false;
1421
+ return !rows.every((row, i) => checkedRows.has(rowKeyOf(row, i)));
1422
+ },
1423
+ configure(config) {
1424
+ for (const [k, v] of Object.entries(config)) if (v === null) delete checkConfig[k];
1425
+ else if (v !== void 0) checkConfig[k] = v;
1426
+ }
1427
+ };
1428
+ function check() {
1429
+ return checkHandle;
1430
+ }
1431
+ /**
1432
+ * Sort elements by document order so a handle reads left→right / top→bottom as
1433
+ * displayed. Falls back to insertion order where `compareDocumentPosition` is
1434
+ * unavailable (SSR / no DOM).
1435
+ */
1436
+ function domOrdered(els) {
1437
+ return els.sort((a, b) => {
1438
+ if (typeof a.compareDocumentPosition !== "function") return 0;
1439
+ const pos = a.compareDocumentPosition(b);
1440
+ if (pos & 4) return -1;
1441
+ if (pos & 2) return 1;
1442
+ return 0;
1443
+ });
1444
+ }
1445
+ function orderedColumnEls() {
1446
+ return Array.from(columnEls).sort((a, b) => {
1447
+ if (typeof a.compareDocumentPosition !== "function") return 0;
1448
+ const pos = a.compareDocumentPosition(b);
1449
+ if (pos & 4) return -1;
1450
+ if (pos & 2) return 1;
1451
+ return 0;
1452
+ });
1453
+ }
1454
+ function registeredColumns() {
1455
+ return orderedColumnEls().map((el) => ({
1456
+ field: el.field,
1457
+ caption: el.caption,
1458
+ sort: el.sort,
1459
+ editable: el.editable,
1460
+ headerFilter: el.headerFilter,
1461
+ dateFilter: el.dateFilter
1462
+ }));
1463
+ }
1464
+ function editableColumns() {
1465
+ return registeredColumns().filter((c) => !!c.editable && !!c.field);
1466
+ }
1467
+ const columnDefs = [...opts.columns ?? []];
1468
+ const columnsHandle = {
1469
+ get: (field) => columnDefs.find((c) => c.field === field) ?? null,
1470
+ getAll: () => columnDefs
1471
+ };
1472
+ const elementProps = { ...opts.props ?? {} };
1473
+ {
1474
+ const declared = opts.props?.th ?? [];
1475
+ const byField = /* @__PURE__ */ new Map();
1476
+ for (const c of columnDefs) if (c?.field) byField.set(c.field, { ...c });
1477
+ for (const c of declared) {
1478
+ if (!c?.field) continue;
1479
+ byField.set(c.field, {
1480
+ ...byField.get(c.field) ?? {},
1481
+ ...c
1482
+ });
1483
+ }
1484
+ const ordered = [];
1485
+ for (const c of declared) if (c?.field && byField.has(c.field)) ordered.push(byField.get(c.field));
1486
+ for (const [field, c] of byField) if (!ordered.some((o) => o.field === field)) ordered.push(c);
1487
+ elementProps.th = ordered;
1488
+ }
1489
+ function props() {
1490
+ return elementProps;
1491
+ }
1492
+ /** Snapshot written into `opts.state` on every notify (see `notify`). */
1493
+ function propsSnapshot() {
1494
+ return {
1495
+ ...elementProps,
1496
+ th: (elementProps.th ?? []).map((c) => ({ ...c }))
1497
+ };
1498
+ }
1499
+ function columns() {
1500
+ return columnsHandle;
1501
+ }
1502
+ function indexOfRowKey(rowKey) {
1503
+ return ctrl.items.findIndex((row, i) => rowKeyOf(row, i) === rowKey);
1504
+ }
1505
+ function rowByKey(rowKey) {
1506
+ const i = indexOfRowKey(rowKey);
1507
+ return i >= 0 ? ctrl.items[i] : void 0;
1508
+ }
1509
+ function _cssEscape(value) {
1510
+ return typeof CSS !== "undefined" && CSS.escape ? CSS.escape(value) : value;
1511
+ }
1512
+ /**
1513
+ * Is this pointer event a press on `el`'s scrollbar rather than on its content?
1514
+ *
1515
+ * Overlay scrollbars are painted over the content box, so the event target is
1516
+ * the element underneath and only the coordinates can tell the two apart. A
1517
+ * band is live only on an axis that actually overflows, which keeps a genuine
1518
+ * click on the last row from being mistaken for a scrollbar grab.
1519
+ */
1520
+ function _inScrollbarBand(el, e) {
1521
+ const { clientX: x, clientY: y } = e;
1522
+ if (typeof x !== "number" || typeof y !== "number") return false;
1523
+ const BAND = 17;
1524
+ const r = el.getBoundingClientRect();
1525
+ const inX = x >= r.left && x <= r.right;
1526
+ const inY = y >= r.top && y <= r.bottom;
1527
+ if (el.scrollWidth > el.clientWidth + 1 && inX && y >= r.bottom - BAND && y <= r.bottom) return true;
1528
+ if (el.scrollHeight > el.clientHeight + 1 && inY && x >= r.right - BAND && x <= r.right) return true;
1529
+ return false;
1530
+ }
1531
+ let _outsidePointer = null;
1532
+ function installOutsideExit() {
1533
+ if (typeof document === "undefined" || _outsidePointer) return;
1534
+ _outsidePointer = (e) => {
1535
+ const key = ctrl.editingKey;
1536
+ if (key == null) return;
1537
+ const target = e.target;
1538
+ if (!target?.closest) return;
1539
+ if (target.closest(`[data-row-key="${_cssEscape(key)}"]`) || target.closest("[data-mono-popup-portal], .flatpickr-calendar")) return;
1540
+ let node = target;
1541
+ while (node) {
1542
+ const s = getComputedStyle(node);
1543
+ if (/(auto|scroll)/.test(s.overflowX + s.overflowY) && _inScrollbarBand(node, e)) return;
1544
+ node = node.parentElement;
1545
+ }
1546
+ setTimeout(() => {
1547
+ if (ctrl.editingKey === key) cancelEdit();
1548
+ }, 0);
1549
+ };
1550
+ document.addEventListener("pointerdown", _outsidePointer, true);
1551
+ }
1552
+ function removeOutsideExit() {
1553
+ if (!_outsidePointer || typeof document === "undefined") return;
1554
+ document.removeEventListener("pointerdown", _outsidePointer, true);
1555
+ _outsidePointer = null;
1556
+ }
1557
+ let _triggerEl = null;
1558
+ let _triggerHandler = null;
1559
+ let _triggerType = null;
1560
+ const _onRowTrigger = (e) => {
1561
+ const target = e.target;
1562
+ if (!target?.closest) return;
1563
+ if (target.closest("[data-edit-cell]")) return;
1564
+ if (target.closest("button, a, input, select, textarea, label, [contenteditable=\"true\"]")) return;
1565
+ const rowEl = target.closest("[data-row-key]");
1566
+ if (!rowEl) return;
1567
+ const key = rowEl.getAttribute("data-row-key");
1568
+ if (key == null || ctrl.editingKey === key) return;
1569
+ if (!editableColumns().length) return;
1570
+ if (isEditableRow(rowByKey(key))) return;
1571
+ beginEditRow(key, e);
1572
+ };
1573
+ /**
1574
+ * Point the row trigger at the `<table>` that owns this grid's rows. Called by
1575
+ * every `mono-table-th` on update; idempotent, so only a changed table element
1576
+ * or a changed `editableTrigger` actually re-binds.
1577
+ */
1578
+ function bindRowTrigger(tableEl) {
1579
+ if (typeof document === "undefined") return;
1580
+ const type = ctrl.editableTrigger === "double-click" ? "dblclick" : "click";
1581
+ if (tableEl === _triggerEl && type === _triggerType) return;
1582
+ unbindRowTrigger();
1583
+ if (!tableEl) return;
1584
+ _triggerHandler = _onRowTrigger;
1585
+ _triggerType = type;
1586
+ _triggerEl = tableEl;
1587
+ tableEl.addEventListener(type, _triggerHandler);
1588
+ }
1589
+ function unbindRowTrigger() {
1590
+ if (_triggerEl && _triggerHandler && _triggerType) _triggerEl.removeEventListener(_triggerType, _triggerHandler);
1591
+ _triggerEl = null;
1592
+ _triggerHandler = null;
1593
+ _triggerType = null;
1594
+ }
1595
+ function beginEditRow(rowKey, event) {
1596
+ if (ctrl.editingKey === rowKey) return;
1597
+ ctrl.editingKey = rowKey;
1598
+ installOutsideExit();
1599
+ installEditorKeys();
1600
+ notify();
1601
+ if (event) focusRowCell(event.target, rowKey, "first", { preferClicked: true });
1602
+ }
1603
+ function cancelEdit() {
1604
+ removeOutsideExit();
1605
+ removeEditorKeys();
1606
+ if (ctrl.editingKey === null) return;
1607
+ ctrl.editingKey = null;
1608
+ notify();
1609
+ }
1610
+ function isEditingRow(rowKey) {
1611
+ return ctrl.editingKey === rowKey;
1612
+ }
1613
+ /** A `form({ showForm: true })` row inserted for inline data entry. */
1614
+ function isEditableRow(row) {
1615
+ return !!row && row._Type === EDITABLE_MARK;
1616
+ }
1617
+ function isEditingCell(rowKeyStr, field) {
1618
+ if (!registeredColumns().some((c) => c.field === field && !!c.editable)) return false;
1619
+ return ctrl.editingKey === rowKeyStr || isEditableRow(rowByKey(rowKeyStr));
1620
+ }
1621
+ function commitCell(rowKey, field, value) {
1622
+ ctrl.onCellChange?.({
1623
+ rowKey,
1624
+ field,
1625
+ value,
1626
+ row: rowByKey(rowKey)
1627
+ });
1628
+ }
1629
+ function serverKeyOf(rowKey) {
1630
+ const row = rowByKey(rowKey);
1631
+ return row ? row[keyExpr] ?? rowKey : rowKey;
1632
+ }
1633
+ function stageCell(rowKey, field, value) {
1634
+ const row = rowByKey(rowKey);
1635
+ if (isEditableRow(row)) {
1636
+ if (isPath(field)) setFieldValue(row, field, value);
1637
+ else row[field] = value;
1638
+ notify();
1639
+ return;
1640
+ }
1641
+ const patch = pending.get(rowKey) ?? {};
1642
+ patch[field] = value;
1643
+ pending.set(rowKey, patch);
1644
+ clearImported(rowKey, field);
1645
+ notify();
1646
+ }
1647
+ const importedCells = /* @__PURE__ */ new Map();
1648
+ function markImported(rowKey, fields) {
1649
+ const set = importedCells.get(rowKey) ?? /* @__PURE__ */ new Set();
1650
+ for (const f of fields) set.add(f);
1651
+ importedCells.set(rowKey, set);
1652
+ }
1653
+ function clearImported(rowKey, field) {
1654
+ const set = importedCells.get(rowKey);
1655
+ if (!set) return;
1656
+ set.delete(field);
1657
+ if (!set.size) importedCells.delete(rowKey);
1658
+ }
1659
+ function isCellImported(rowKey, field) {
1660
+ return !!importedCells.get(rowKey)?.has(field);
1661
+ }
1662
+ function isRowImported(rowKey) {
1663
+ return !!importedCells.get(rowKey)?.size;
1664
+ }
1665
+ /** Staged value for a cell, or `fallback` when the cell isn't staged. */
1666
+ function cellValue(rowKey, field, fallback) {
1667
+ const row = rowByKey(rowKey);
1668
+ if (isEditableRow(row)) {
1669
+ const v = isPath(field) ? getFieldValue(row, field) : row[field];
1670
+ return v === void 0 ? fallback : v;
1671
+ }
1672
+ const patch = pending.get(rowKey);
1673
+ return patch && field in patch ? patch[field] : fallback;
1674
+ }
1675
+ function isCellDirty(rowKey, field) {
1676
+ const patch = pending.get(rowKey);
1677
+ return !!patch && field in patch;
1678
+ }
1679
+ function isRowDirty(rowKey) {
1680
+ return pending.has(rowKey);
1681
+ }
1682
+ function pendingCount() {
1683
+ return pending.size;
1684
+ }
1685
+ function hasChanges() {
1686
+ return pending.size > 0;
1687
+ }
1688
+ /** The staged change set — for a consumer that sends its own (bulk) request. */
1689
+ function changes() {
1690
+ return Array.from(pending.entries()).map(([rowKey, patch]) => ({
1691
+ rowKey,
1692
+ key: serverKeyOf(rowKey),
1693
+ patch: { ...patch },
1694
+ row: rowByKey(rowKey)
1695
+ }));
1696
+ }
1697
+ /** Drop the staged buffer and the import marks that shadow it. */
1698
+ function clearPending() {
1699
+ pending.clear();
1700
+ importedCells.clear();
1701
+ }
1702
+ function discardChanges() {
1703
+ if (pending.size === 0) return;
1704
+ clearPending();
1705
+ notify();
1706
+ }
1707
+ /**
1708
+ * Flush every staged row to the bound source, then clear + reflect. Remote
1709
+ * DataSource → `store.update(key, patch)` per row + `reload()`; array source →
1710
+ * per-row `update`; else a `setData` merge of the current page. On error the
1711
+ * buffer is KEPT so the save can be retried.
1712
+ */
1713
+ async function saveChanges() {
1714
+ if (pending.size === 0) return;
1715
+ const entries = Array.from(pending.entries());
1716
+ const s = bound;
1717
+ const store = s?.store?.();
1718
+ if (store && typeof store.update === "function") {
1719
+ for (const [rowKey, patch] of entries) {
1720
+ if (Object.keys(patch).some(isPath)) console.warn("[monoDataGrid] saveChanges: a remote store.update patch contains a path key; nested PATCH bodies are not auto-built — send the nested shape your backend expects.");
1721
+ await store.update(serverKeyOf(rowKey), patch);
1722
+ }
1723
+ clearPending();
1724
+ await reload();
1725
+ return;
1726
+ }
1727
+ if (s && typeof s.update === "function") {
1728
+ for (const [rowKey, patch] of entries) await s.update(serverKeyOf(rowKey), patch);
1729
+ clearPending();
1730
+ notify();
1731
+ return;
1732
+ }
1733
+ if (s && typeof s.setData === "function") {
1734
+ const next = (s.data?.() ?? ctrl.items ?? []).map((row, i) => {
1735
+ const patch = pending.get(rowKeyOf(row, i));
1736
+ return patch ? mergePatch(row, patch) : row;
1737
+ });
1738
+ clearPending();
1739
+ await setData(next);
1740
+ return;
1741
+ }
1742
+ clearPending();
1743
+ notify();
1744
+ }
1745
+ /** First existing row to infer an empty-row shape from (array backing → page → items). */
1746
+ function formSample() {
1747
+ const fromData = (bound?.data?.())?.[0];
1748
+ const fromItems = (bound?.items?.())?.[0];
1749
+ return fromData ?? fromItems ?? ctrl.items[0];
1750
+ }
1751
+ /** Build a typed-empty row from a sample: number→0, string→'', bool→false, array→[], Date→null, object→{}. */
1752
+ function emptyLike(sample) {
1753
+ const out = {};
1754
+ for (const k in sample) {
1755
+ const v = sample[k];
1756
+ if (v == null) out[k] = null;
1757
+ else if (typeof v === "number") out[k] = 0;
1758
+ else if (typeof v === "string") out[k] = "";
1759
+ else if (typeof v === "boolean") out[k] = false;
1760
+ else if (Array.isArray(v)) out[k] = [];
1761
+ else if (v instanceof Date) out[k] = null;
1762
+ else if (typeof v === "object") out[k] = {};
1763
+ else out[k] = null;
1764
+ }
1765
+ return out;
1766
+ }
1767
+ /** Strip OData response metadata off a staged row before it enters the data. */
1768
+ function cleanRow(row) {
1769
+ if (row && typeof row === "object") {
1770
+ const r = row;
1771
+ if ("@odata.context" in r) delete r["@odata.context"];
1772
+ if ("@odata.url" in r) delete r["@odata.url"];
1773
+ }
1774
+ return row;
1775
+ }
1776
+ /**
1777
+ * The row to actually commit: OData metadata stripped, and — for a `showForm`
1778
+ * row — the `_Type` marker removed (the non-enumerable identity symbol is dropped
1779
+ * by the spread). Returns a clean object; never carries the editable marker.
1780
+ */
1781
+ function commitRow(row) {
1782
+ cleanRow(row);
1783
+ if (isEditableRow(row)) {
1784
+ const { _Type, ...rest } = row;
1785
+ return rest;
1786
+ }
1787
+ return row;
1788
+ }
1789
+ const looseEq = (a, b) => a == b;
1790
+ /** Replay one op into a full array (array source path). */
1791
+ function replayArray(arr, op, key) {
1792
+ const idxOf = (k) => arr.findIndex((e) => looseEq(e?.[key], k));
1793
+ if (op.op === "add") for (const src of op.rows) {
1794
+ const row = commitRow(src);
1795
+ const i = idxOf(row?.[key]);
1796
+ if (i >= 0) arr.splice(i, 1, row);
1797
+ else arr.unshift(row);
1798
+ }
1799
+ else if (op.op === "edit") {
1800
+ const i = idxOf(op.key);
1801
+ if (i >= 0) arr.splice(i, 1, {
1802
+ ...arr[i],
1803
+ ...op.row
1804
+ });
1805
+ } else {
1806
+ const i = idxOf(op.key);
1807
+ if (i >= 0) arr.splice(i, 1);
1808
+ }
1809
+ }
1810
+ /** Replay one op into the live page collection (remote source path). Returns the totalCount delta. */
1811
+ function replayDs(coll, op, key, pushes) {
1812
+ const idxOf = (k) => coll.findIndex((e) => looseEq(e?.[key], k));
1813
+ if (op.op === "add") {
1814
+ let delta = 0;
1815
+ for (const src of op.rows) {
1816
+ const row = commitRow(src);
1817
+ const k = row?.[key];
1818
+ const i = idxOf(k);
1819
+ if (i >= 0) Object.assign(coll[i], row);
1820
+ else {
1821
+ coll.unshift(row);
1822
+ pushes.push({
1823
+ type: "insert",
1824
+ data: row,
1825
+ key: k
1826
+ });
1827
+ delta++;
1828
+ }
1829
+ }
1830
+ return delta;
1831
+ }
1832
+ if (op.op === "edit") {
1833
+ const i = idxOf(op.key);
1834
+ if (i >= 0) {
1835
+ Object.assign(coll[i], op.row);
1836
+ pushes.push({
1837
+ type: "update",
1838
+ key: op.key,
1839
+ data: op.row
1840
+ });
1841
+ }
1842
+ return 0;
1843
+ }
1844
+ const i = idxOf(op.key);
1845
+ if (i >= 0) {
1846
+ coll.splice(i, 1);
1847
+ pushes.push({
1848
+ type: "remove",
1849
+ key: op.key
1850
+ });
1851
+ return -1;
1852
+ }
1853
+ return 0;
1854
+ }
1855
+ /** Commit all staged form ops into the rendered data. Local only — no server call. Final: no undo. */
1856
+ async function applyForm() {
1857
+ if (formOps.length === 0) return;
1858
+ const key = formKey ?? keyExpr;
1859
+ const ops = formOps.splice(0, formOps.length);
1860
+ const s = bound;
1861
+ if (s && typeof s.setData === "function") {
1862
+ const next = [...s.data?.() ?? ctrl.items.filter((r) => !isEditableRow(r)) ?? []];
1863
+ for (const op of ops) replayArray(next, op, key);
1864
+ await setData(next);
1865
+ return;
1866
+ }
1867
+ const store = s?.store?.();
1868
+ if (s && store) {
1869
+ if (isGrouped() || serverMode()) console.warn("[monoDataGrid] form().apply() on a grouped/server grid updates only the flat page; reload to refresh groups.");
1870
+ const coll = s.items?.() ?? [];
1871
+ const pushes = [];
1872
+ let delta = 0;
1873
+ for (const op of ops) delta += replayDs(coll, op, key, pushes);
1874
+ if (typeof store.push === "function" && pushes.length) store.push(pushes);
1875
+ ctrl.items = [...coll];
1876
+ ctrl.totalCount = Math.max(0, ctrl.totalCount + delta);
1877
+ render();
1878
+ return;
1879
+ }
1880
+ notify();
1881
+ }
1882
+ /** Tag a `showForm` row: visible marker + stable non-enumerable identity. */
1883
+ function tagEditable(row) {
1884
+ row._Type = EDITABLE_MARK;
1885
+ Object.defineProperty(row, MONO_ROW_KEY, {
1886
+ value: `__form_${++formTempSeq}`,
1887
+ enumerable: false,
1888
+ configurable: true,
1889
+ writable: true
1890
+ });
1891
+ return row;
1892
+ }
1893
+ /** {@link MonoTableController.form} — memoized, chainable handle over one shared buffer. */
1894
+ function form(config) {
1895
+ if (config?.key) formKey = config.key;
1896
+ formShowForm = !!config?.showForm;
1897
+ if (formHandle) return formHandle;
1898
+ const handle = {
1899
+ add(row) {
1900
+ let rows;
1901
+ if (row === void 0) {
1902
+ const sample = formSample();
1903
+ rows = [sample ? emptyLike(sample) : {}];
1904
+ } else rows = Array.isArray(row) ? row : [row];
1905
+ if (formShowForm) {
1906
+ rows.forEach(tagEditable);
1907
+ ctrl.items.unshift(...rows);
1908
+ ctrl.totalCount += rows.length;
1909
+ render();
1910
+ }
1911
+ formOps.push({
1912
+ op: "add",
1913
+ rows
1914
+ });
1915
+ return handle;
1916
+ },
1917
+ edit(k, row) {
1918
+ formOps.push({
1919
+ op: "edit",
1920
+ key: k,
1921
+ row
1922
+ });
1923
+ return handle;
1924
+ },
1925
+ delete(k) {
1926
+ formOps.push({
1927
+ op: "delete",
1928
+ key: k
1929
+ });
1930
+ return handle;
1931
+ },
1932
+ apply: applyForm,
1933
+ revert(key) {
1934
+ const k = String(key);
1935
+ const items = ctrl.items;
1936
+ const idx = items.findIndex((r, i) => rowKeyOf(r, i) === k);
1937
+ if (idx >= 0 && isEditableRow(items[idx])) {
1938
+ items.splice(idx, 1);
1939
+ ctrl.totalCount = Math.max(0, ctrl.totalCount - 1);
1940
+ }
1941
+ for (let i = formOps.length - 1; i >= 0; i--) {
1942
+ const op = formOps[i];
1943
+ if (op.op !== "add") continue;
1944
+ op.rows = op.rows.filter((r) => rowKeyOf(r, -1) !== k);
1945
+ if (op.rows.length === 0) formOps.splice(i, 1);
1946
+ }
1947
+ render();
1948
+ return handle;
1949
+ },
1950
+ revertAll() {
1951
+ const shown = ctrl.items.filter((r) => isEditableRow(r)).length;
1952
+ if (shown) {
1953
+ ctrl.items = ctrl.items.filter((r) => !isEditableRow(r));
1954
+ ctrl.totalCount = Math.max(0, ctrl.totalCount - shown);
1955
+ }
1956
+ for (let i = formOps.length - 1; i >= 0; i--) {
1957
+ const op = formOps[i];
1958
+ if (op.op !== "add") continue;
1959
+ op.rows = op.rows.filter((r) => !isEditableRow(r));
1960
+ if (op.rows.length === 0) formOps.splice(i, 1);
1961
+ }
1962
+ render();
1963
+ return handle;
1964
+ },
1965
+ changes: () => formOps.map((o) => ({ ...o })),
1966
+ discard(key) {
1967
+ if (key === void 0) {
1968
+ formOps.length = 0;
1969
+ const shown = ctrl.items.filter((r) => isEditableRow(r)).length;
1970
+ if (shown) {
1971
+ ctrl.items = ctrl.items.filter((r) => !isEditableRow(r));
1972
+ ctrl.totalCount = Math.max(0, ctrl.totalCount - shown);
1973
+ render();
1974
+ }
1975
+ return handle;
1976
+ }
1977
+ const k = String(key);
1978
+ const items = ctrl.items;
1979
+ const idx = items.findIndex((r, i) => rowKeyOf(r, i) === k);
1980
+ if (idx >= 0 && isEditableRow(items[idx])) {
1981
+ items.splice(idx, 1);
1982
+ ctrl.totalCount = Math.max(0, ctrl.totalCount - 1);
1983
+ }
1984
+ for (let i = formOps.length - 1; i >= 0; i--) {
1985
+ const op = formOps[i];
1986
+ if (op.op === "add") {
1987
+ op.rows = op.rows.filter((r) => rowKeyOf(r, -1) !== k);
1988
+ if (op.rows.length === 0) formOps.splice(i, 1);
1989
+ } else if (String(op.key) === k) formOps.splice(i, 1);
1990
+ }
1991
+ render();
1992
+ return handle;
1993
+ },
1994
+ discardAll() {
1995
+ handle.discard();
1996
+ clearPending();
1997
+ cancelEdit();
1998
+ render();
1999
+ return handle;
2000
+ }
2001
+ };
2002
+ formHandle = handle;
2003
+ return handle;
2004
+ }
2005
+ /**
2006
+ * Focus an editor in `rowKey`.
2007
+ *
2008
+ * `preventScroll` is the important part: a bare `.focus()` scrolls the element
2009
+ * into view, and this runs inside a `requestAnimationFrame` — one frame AFTER
2010
+ * the row re-rendered — so the browser scrolls `.mono-table-scroll`
2011
+ * (`overflow-x: auto`) or the page a moment after the click lands. That
2012
+ * delayed nudge is what reads as the editor "jumping".
2013
+ *
2014
+ * `reveal` opts back into a MINIMAL scroll for the cross-row Tab, where the
2015
+ * next row genuinely may be off screen. `block: 'nearest'` moves the least
2016
+ * possible amount and does nothing when the row is already visible — unlike
2017
+ * the default focus scroll, which can centre it.
2018
+ *
2019
+ * `preferClicked` focuses the editor in the cell the user actually clicked
2020
+ * (falling back to `edge` when that cell has none — an ID or actions column,
2021
+ * or a click on the row's padding). Without it, opening a row by clicking its
2022
+ * Job cell would drop the caret in Name, and the user has to Tab back across
2023
+ * the row to reach the field they aimed at.
2024
+ */
2025
+ function focusRowCell(fromEl, rowKey, edge, opts = {}) {
2026
+ const from = fromEl;
2027
+ const table = from?.closest?.("table");
2028
+ if (!table || typeof requestAnimationFrame === "undefined") return;
2029
+ const clickedCell = opts.preferClicked ? from?.closest?.("td") : null;
2030
+ requestAnimationFrame(() => {
2031
+ const escaped = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(rowKey) : rowKey;
2032
+ const cells = table.querySelectorAll(`[data-row-key="${escaped}"] [data-edit-cell]`);
2033
+ const clicked = clickedCell?.closest(`[data-row-key="${escaped}"]`) && table.contains(clickedCell) ? clickedCell.querySelector("[data-edit-cell]") : null;
2034
+ const target = (opts.field ? table.querySelector(`[data-row-key="${escaped}"] [data-edit-cell="${_cssEscape(opts.field)}"]`) : null) ?? clicked ?? (edge === "first" ? cells[0] : cells[cells.length - 1]);
2035
+ const definesOwnFocus = typeof target?.focus === "function" && typeof HTMLElement !== "undefined" && target.focus !== HTMLElement.prototype.focus;
2036
+ const focusable = target?.matches?.("input, textarea, select, button") ? target : definesOwnFocus ? target : target?.querySelector?.("input, textarea, select, button") ?? target;
2037
+ if (opts.reveal) revealCell(target);
2038
+ focusable?.focus?.({ preventScroll: true });
2039
+ });
2040
+ }
2041
+ /** The nearest scrollable ancestor of `el` — the table's own scroll region first. */
2042
+ function scrollParentOf(el) {
2043
+ const region = el.closest(".mono-table-scroll, [mono-table-scroll]");
2044
+ if (region) return region;
2045
+ let node = el.parentElement;
2046
+ while (node) {
2047
+ const s = getComputedStyle(node);
2048
+ if (/(auto|scroll)/.test(s.overflowX + s.overflowY)) return node;
2049
+ node = node.parentElement;
2050
+ }
2051
+ return null;
2052
+ }
2053
+ /**
2054
+ * Scroll `cell` into view by the SMALLEST delta that works — and no further.
2055
+ *
2056
+ * `scrollIntoView({ block: 'nearest', inline: 'nearest' })` looks like the
2057
+ * answer but isn't: it knows nothing about pinned columns, so on a wide grid
2058
+ * it happily parks the cell *underneath* a `.mono-table-sticky-left` column or
2059
+ * the sticky header, leaving the user typing into something they can't see.
2060
+ * So compute the genuinely visible band from the pinned elements' own rects
2061
+ * and scroll only as far as that band requires. Each axis is handled
2062
+ * independently, so stepping sideways never nudges the page vertically.
2063
+ *
2064
+ * This has to be explicit now: the keyboard path focuses with
2065
+ * `preventScroll: true`, which is what stopped the browser from revealing the
2066
+ * editor for us.
2067
+ */
2068
+ function revealCell(cell) {
2069
+ if (!cell || typeof getComputedStyle === "undefined") return;
2070
+ const scroller = scrollParentOf(cell);
2071
+ if (!scroller) return;
2072
+ const cellBox = cell.getBoundingClientRect();
2073
+ const view = scroller.getBoundingClientRect();
2074
+ const row = cell.closest("tr");
2075
+ const table = cell.closest("table");
2076
+ const MARGIN = 8;
2077
+ const edgeOf = (sel, side) => {
2078
+ const rects = Array.from((row ?? table)?.querySelectorAll(sel) ?? []).filter((n) => !n.contains(cell)).map((n) => n.getBoundingClientRect());
2079
+ if (!rects.length) return null;
2080
+ if (side === "left") return Math.max(...rects.map((r) => r.right));
2081
+ if (side === "right") return Math.min(...rects.map((r) => r.left));
2082
+ if (side === "top") return Math.max(...rects.map((r) => r.bottom));
2083
+ return Math.min(...rects.map((r) => r.top));
2084
+ };
2085
+ const left = Math.max(view.left, edgeOf(".mono-table-sticky-left, [mono-sticky-left]", "left") ?? -Infinity);
2086
+ const right = Math.min(view.right, edgeOf(".mono-table-sticky-right, [mono-sticky-right]", "right") ?? Infinity);
2087
+ if (cellBox.left < left + MARGIN) scroller.scrollLeft -= left + MARGIN - cellBox.left;
2088
+ else if (cellBox.right > right - MARGIN) scroller.scrollLeft += cellBox.right - (right - MARGIN);
2089
+ if (scroller.scrollHeight <= scroller.clientHeight + 1) return;
2090
+ const heads = Array.from(table?.querySelectorAll(".mono-table-sticky-head thead th, [mono-sticky-head] thead th, thead th") ?? []).map((n) => n.getBoundingClientRect());
2091
+ const foots = Array.from(table?.querySelectorAll(".mono-table-sticky-foot tfoot th, .mono-table-sticky-foot tfoot td, [mono-sticky-foot] tfoot th, [mono-sticky-foot] tfoot td") ?? []).map((n) => n.getBoundingClientRect());
2092
+ const top = Math.max(view.top, ...heads.map((r) => r.bottom).filter((v) => v <= view.bottom));
2093
+ const bottom = Math.min(view.bottom, ...foots.map((r) => r.top).filter((v) => v >= view.top));
2094
+ if (cellBox.top < top + MARGIN) scroller.scrollTop -= top + MARGIN - cellBox.top;
2095
+ else if (cellBox.bottom > bottom - MARGIN) scroller.scrollTop += cellBox.bottom - (bottom - MARGIN);
2096
+ }
2097
+ /** Which `[data-edit-cell]` holds the caret right now. */
2098
+ function activeEditorCell(fromEl) {
2099
+ const byEvent = fromEl?.closest?.("[data-edit-cell]");
2100
+ if (byEvent) return byEvent;
2101
+ return (typeof document !== "undefined" ? document.activeElement : null)?.closest?.("[data-edit-cell]") ?? null;
2102
+ }
2103
+ /**
2104
+ * Move the editor one cell and focus it. Clamps on both axes and returns
2105
+ * `false` when the move is blocked, so the caller can swallow the key without
2106
+ * anything happening — arrow navigation must NOT wrap around an edge. Only the
2107
+ * plain Tab tap passes `wrap`, which lets left/right cross into the
2108
+ * previous/next row in reading order (the long-standing Tab behaviour).
2109
+ */
2110
+ function moveEditor(dir, fromEl, opts = {}) {
2111
+ const rowKey = ctrl.editingKey;
2112
+ if (rowKey == null) return false;
2113
+ const cell = activeEditorCell(fromEl);
2114
+ const field = cell?.getAttribute("data-edit-cell");
2115
+ const cols = editableColumns();
2116
+ const colIdx = field ? cols.findIndex((c) => c.field === field) : -1;
2117
+ const rowIdx = indexOfRowKey(rowKey);
2118
+ if (colIdx === -1 || rowIdx === -1 || !cols.length) return false;
2119
+ const rows = ctrl.items;
2120
+ const anchor = cell ?? fromEl;
2121
+ if (dir === "up" || dir === "down") {
2122
+ const nextIdx = rowIdx + (dir === "down" ? 1 : -1);
2123
+ const next = rows[nextIdx];
2124
+ if (!next) return false;
2125
+ const nextKey = rowKeyOf(next, nextIdx);
2126
+ beginEditRow(nextKey);
2127
+ focusRowCell(anchor, nextKey, "first", {
2128
+ reveal: true,
2129
+ field: cols[colIdx].field
2130
+ });
2131
+ return true;
2132
+ }
2133
+ const nextCol = colIdx + (dir === "right" ? 1 : -1);
2134
+ if (nextCol >= 0 && nextCol < cols.length) {
2135
+ focusRowCell(anchor, rowKey, "first", {
2136
+ reveal: true,
2137
+ field: cols[nextCol].field
2138
+ });
2139
+ return true;
2140
+ }
2141
+ if (!opts.wrap) return false;
2142
+ const nextIdx = rowIdx + (dir === "right" ? 1 : -1);
2143
+ const next = rows[nextIdx];
2144
+ if (!next) return false;
2145
+ const nextKey = rowKeyOf(next, nextIdx);
2146
+ beginEditRow(nextKey);
2147
+ focusRowCell(anchor, nextKey, dir === "right" ? "first" : "last", { reveal: true });
2148
+ return true;
2149
+ }
2150
+ const ARROW_DIRS = {
2151
+ ArrowLeft: "left",
2152
+ ArrowRight: "right",
2153
+ ArrowUp: "up",
2154
+ ArrowDown: "down"
2155
+ };
2156
+ let _editorKeys = null;
2157
+ let _tabHeld = false;
2158
+ let _tabConsumed = false;
2159
+ let _tabShift = false;
2160
+ /**
2161
+ * The control inside `cell` that owns a popup. `data-edit-cell` usually sits on
2162
+ * the control itself, but allow a wrapper. Custom elements only, and `open`
2163
+ * must be a METHOD — `<details>` and `<dialog>` both carry a boolean `open`
2164
+ * property that isn't callable.
2165
+ */
2166
+ function popupEditorOf(cell) {
2167
+ if (!cell) return null;
2168
+ return [cell, ...Array.from(cell.querySelectorAll("*"))].find((el) => el.tagName.includes("-") && typeof el.open === "function") ?? null;
2169
+ }
2170
+ /**
2171
+ * `Enter` / `Escape`, shared by the document-level machine and the per-editor
2172
+ * `editorKeydown` so the two modes behave identically. Returns whether the key
2173
+ * belonged to us.
2174
+ */
2175
+ function handleEditorActionKey(e, cell) {
2176
+ if (e.key === "Escape") {
2177
+ const host = popupEditorOf(cell);
2178
+ if (host?.isOpen && typeof host.close === "function") {
2179
+ e.preventDefault();
2180
+ e.stopPropagation();
2181
+ host.close();
2182
+ return true;
2183
+ }
2184
+ cancelEdit();
2185
+ return true;
2186
+ }
2187
+ if (e.key !== "Enter") return false;
2188
+ const active = e.target;
2189
+ if (active?.tagName === "TEXTAREA" || active?.isContentEditable) return false;
2190
+ const host = popupEditorOf(cell);
2191
+ if (host?.isOpen) return false;
2192
+ e.preventDefault();
2193
+ if (host) {
2194
+ e.stopPropagation();
2195
+ host.open();
2196
+ }
2197
+ return true;
2198
+ }
2199
+ /**
2200
+ * The very last cell in the grid (or the very first, going back). Tab is left
2201
+ * alone there so a keyboard user can still leave the table — trapping focus in
2202
+ * the grid would be worse than losing Tab+Arrow from that one cell.
2203
+ */
2204
+ function atGridEdge(cell, back) {
2205
+ const cols = editableColumns();
2206
+ const colIdx = cols.findIndex((c) => c.field === cell.getAttribute("data-edit-cell"));
2207
+ const rowIdx = ctrl.editingKey != null ? indexOfRowKey(ctrl.editingKey) : -1;
2208
+ if (colIdx === -1 || rowIdx === -1) return true;
2209
+ return back ? colIdx === 0 && rowIdx === 0 : colIdx === cols.length - 1 && rowIdx === ctrl.items.length - 1;
2210
+ }
2211
+ function installEditorKeys() {
2212
+ if (typeof document === "undefined" || _editorKeys) return;
2213
+ if (ctrl.editorNavKeys !== "tab-arrows") return;
2214
+ const editingCell = (e) => {
2215
+ const key = ctrl.editingKey;
2216
+ if (key == null) return null;
2217
+ const cell = e.target?.closest?.("[data-edit-cell]");
2218
+ return cell?.closest(`[data-row-key="${_cssEscape(key)}"]`) ? cell : null;
2219
+ };
2220
+ const down = (evt) => {
2221
+ const e = evt;
2222
+ const cell = editingCell(e);
2223
+ if (!cell) return;
2224
+ if (handleEditorActionKey(e, cell)) return;
2225
+ if (e.key === "Tab") {
2226
+ if (atGridEdge(cell, e.shiftKey)) {
2227
+ _tabHeld = false;
2228
+ return;
2229
+ }
2230
+ e.preventDefault();
2231
+ if (!e.repeat) {
2232
+ _tabHeld = true;
2233
+ _tabConsumed = false;
2234
+ _tabShift = e.shiftKey;
2235
+ }
2236
+ return;
2237
+ }
2238
+ if (!_tabHeld) return;
2239
+ const dir = ARROW_DIRS[e.key];
2240
+ if (!dir) return;
2241
+ e.preventDefault();
2242
+ _tabConsumed = true;
2243
+ moveEditor(dir, e.target);
2244
+ };
2245
+ const up = (evt) => {
2246
+ const e = evt;
2247
+ if (e.key !== "Tab" || !_tabHeld) return;
2248
+ _tabHeld = false;
2249
+ if (_tabConsumed) return;
2250
+ moveEditor(_tabShift ? "left" : "right", e.target, { wrap: true });
2251
+ };
2252
+ document.addEventListener("keydown", down, true);
2253
+ document.addEventListener("keyup", up, true);
2254
+ _editorKeys = {
2255
+ down,
2256
+ up
2257
+ };
2258
+ }
2259
+ function removeEditorKeys() {
2260
+ _tabHeld = false;
2261
+ _tabConsumed = false;
2262
+ if (!_editorKeys || typeof document === "undefined") return;
2263
+ document.removeEventListener("keydown", _editorKeys.down, true);
2264
+ document.removeEventListener("keyup", _editorKeys.up, true);
2265
+ _editorKeys = null;
2266
+ }
2267
+ function editorKeydown(event, rowKey, field) {
2268
+ if (ctrl.editorNavKeys === "tab-arrows") return;
2269
+ if (event.key === "Escape" || event.key === "Enter") {
2270
+ const cell = event.target?.closest?.("[data-edit-cell]");
2271
+ handleEditorActionKey(event, cell);
2272
+ return;
2273
+ }
2274
+ if (event.key !== "Tab") return;
2275
+ const cols = editableColumns();
2276
+ const idx = cols.findIndex((c) => c.field === field);
2277
+ if (idx === -1) return;
2278
+ const rowIdx = indexOfRowKey(rowKey);
2279
+ if (rowIdx === -1) return;
2280
+ if (event.shiftKey) {
2281
+ if (idx > 0) return;
2282
+ const prev = ctrl.items[rowIdx - 1];
2283
+ if (!prev) return;
2284
+ event.preventDefault();
2285
+ const prevKey = rowKeyOf(prev, rowIdx - 1);
2286
+ beginEditRow(prevKey);
2287
+ focusRowCell(event.target, prevKey, "last", { reveal: true });
2288
+ } else {
2289
+ if (idx < cols.length - 1) return;
2290
+ const nextRow = ctrl.items[rowIdx + 1];
2291
+ if (!nextRow) return;
2292
+ event.preventDefault();
2293
+ const nextKey = rowKeyOf(nextRow, rowIdx + 1);
2294
+ beginEditRow(nextKey);
2295
+ focusRowCell(event.target, nextKey, "first", { reveal: true });
2296
+ }
2297
+ }
2298
+ function toFields(group) {
2299
+ if (!group) return [];
2300
+ return (Array.isArray(group) ? group : [group]).filter(Boolean);
2301
+ }
2302
+ /**
2303
+ * Sort descriptor sent to the source. When grouping, the group fields lead
2304
+ * (so each fetched/sliced page holds contiguous, ordered groups); the active
2305
+ * column sort follows — applied to the group field itself if it is one.
2306
+ */
2307
+ function effectiveSort() {
2308
+ const keys = ctrl.sorts.map((s) => ({
2309
+ selector: s.field,
2310
+ desc: s.order === "desc"
2311
+ }));
2312
+ if (!groupFields.length) return keys.length ? keys : null;
2313
+ const list = groupFields.map((f) => ({
2314
+ selector: f,
2315
+ desc: keys.find((k) => k.selector === f)?.desc ?? false
2316
+ }));
2317
+ for (const k of keys) if (!groupFields.includes(k.selector)) list.push(k);
2318
+ return list;
2319
+ }
2320
+ /**
2321
+ * Push the merged sort onto the bound source — the column sort plus the base
2322
+ * sort as tiebreakers (see `mergedSort`). Remembers what it wrote so a sort the
2323
+ * consumer sets on the source afterwards can be told apart and adopted.
2324
+ */
2325
+ function applySourceSort() {
2326
+ const s = bound;
2327
+ if (!s || typeof s.sort !== "function") return;
2328
+ s.sort(mergedSort());
2329
+ lastWrittenSort = s.sort() ?? null;
2330
+ }
2331
+ const notifier = createNotifier({ onFlush: () => {
2332
+ syncMapped();
2333
+ if (opts.state) opts.state.value = propsSnapshot();
2334
+ } });
2335
+ const notify = notifier.notify;
2336
+ const keyExpr = opts.keyExpr ?? "Id";
2337
+ const rowKeyOf = (row, i) => String(row?.[MONO_ROW_KEY] ?? row?.[keyExpr] ?? i);
2338
+ /**
2339
+ * Last-resort identity for a row we cannot place: stable per object, and never
2340
+ * equal to another row's. See `rowKey` for why "-1 for everyone" is not an option.
2341
+ */
2342
+ const orphanKeys = /* @__PURE__ */ new WeakMap();
2343
+ let orphanSeq = 0;
2344
+ /**
2345
+ * Does the loaded row set actually carry `keyExpr`? Drives the drain + the warning.
2346
+ *
2347
+ * `ctrl.items` is not always rows. Once grouping is on it holds group NODES
2348
+ * (`{ key, items, count, level, path, … }`), and a node has none of the row's fields — so
2349
+ * sampling it would report every correctly-configured grouped table as misconfigured. Dig one
2350
+ * level down to the first group that actually has rows loaded instead; in server-group mode a
2351
+ * group's rows arrive only when it is on screen, so "no rows yet" means not-yet-known, not
2352
+ * missing, and takes the same benefit of the doubt as an empty ungrouped table.
2353
+ */
2354
+ function rowsHaveKeyExpr() {
2355
+ /** A group node, not a row — the shape `syncGrouped` / `renderServer` put in `ctrl.items`. */
2356
+ const isNode = (x) => !!x && typeof x === "object" && Array.isArray(x.items) && "path" in x;
2357
+ /** First real row under `list`, descending as many group levels as there are. */
2358
+ const firstRow = (list, depth = 0) => {
2359
+ if (depth > 8) return void 0;
2360
+ for (const entry of list) {
2361
+ if (!isNode(entry)) return entry;
2362
+ const found = firstRow(entry.items ?? [], depth + 1);
2363
+ if (found != null) return found;
2364
+ }
2365
+ };
2366
+ const sample = firstRow(ctrl.items);
2367
+ if (sample == null || typeof sample !== "object") return true;
2368
+ return keyExpr in sample;
2369
+ }
2370
+ let keyExprWarned = false;
2371
+ /** Dev-only, once per controller: a wrong `keyExpr` otherwise fails completely silently. */
2372
+ function warnMissingKeyExpr() {
2373
+ if (keyExprWarned || ctrl.items.length === 0 || rowsHaveKeyExpr()) return;
2374
+ keyExprWarned = true;
2375
+ console.warn(`[mono-table] keyExpr "${keyExpr}" is not a property of the loaded rows${opts.keyExpr ? "" : " (nothing set one, so it defaulted to \"Id\")"}. Row keys fall back to identity, and a remote drain cannot \$select it. Set \`keyExpr\` to the row key field.`);
2376
+ }
2377
+ /**
2378
+ * Public stable row key — prefers the `showForm` identity, else `keyExpr`.
2379
+ *
2380
+ * `unwrapReactive` first: Vue hands components a reactive PROXY of the row while
2381
+ * `ctrl.items` holds the raw object, so a bare `indexOf(proxy)` is always `-1`.
2382
+ * With `keyExpr` also missing that made EVERY row key `"-1"` — tick one row and
2383
+ * the whole grid reads as ticked. A row we still cannot place gets its own
2384
+ * WeakMap id rather than a shared sentinel, so two rows can never collide
2385
+ * whatever the config. A row with a real index keeps its index-derived key, so
2386
+ * array grids with no `keyExpr` are untouched.
2387
+ */
2388
+ function rowKey(row) {
2389
+ const raw = unwrapReactive(row);
2390
+ const i = ctrl.items.indexOf(raw);
2391
+ if (i >= 0) return rowKeyOf(raw, i);
2392
+ const direct = rowKeyOf(raw, NaN);
2393
+ if (direct !== "NaN") return direct;
2394
+ warnMissingKeyExpr();
2395
+ const obj = raw;
2396
+ if (obj == null || typeof obj !== "object") return String(raw);
2397
+ let id = orphanKeys.get(obj);
2398
+ if (!id) {
2399
+ id = `mono-orphan:${orphanSeq += 1}`;
2400
+ orphanKeys.set(obj, id);
2401
+ }
2402
+ return id;
2403
+ }
2404
+ /** Columns that declare a per-column `map`, resolved once per mapping pass. */
2405
+ function mapColumns() {
2406
+ const out = [];
2407
+ for (const c of elementProps.th ?? []) if (c?.field && typeof c.map === "function") out.push({
2408
+ field: c.field,
2409
+ map: c.map
2410
+ });
2411
+ return out;
2412
+ }
2413
+ /** True when anything at all would transform a row (lets the fast path skip). */
2414
+ function hasMap() {
2415
+ return typeof opts.map === "function" || mapColumns().length > 0;
2416
+ }
2417
+ /** One column's `map`, for the header filter's value labels. */
2418
+ function columnMap(field) {
2419
+ return mapColumns().find((c) => c.field === field)?.map;
2420
+ }
2421
+ /**
2422
+ * Raw row → presentation row. The grid-level `map` builds the row, then each
2423
+ * column's `map` overwrites its own field — reading the RAW value, deliberately
2424
+ * not the grid map's output, so a column map behaves identically here and in the
2425
+ * header-filter list (which can only hand it a raw distinct value).
2426
+ *
2427
+ * Returns the SAME object when nothing is configured, so no-map tables allocate
2428
+ * nothing and `mapped === items` holds by reference.
2429
+ */
2430
+ function mapRow(row, index, cols = mapColumns()) {
2431
+ const rowMap = typeof opts.map === "function" ? opts.map : null;
2432
+ if (!rowMap && cols.length === 0) return row;
2433
+ let out = rowMap ? rowMap(row, index) : { ...row };
2434
+ if (cols.length && out && typeof out === "object") for (const c of cols) out[c.field] = c.map(row?.[c.field], row, index);
2435
+ return out;
2436
+ }
2437
+ /** Recompute `ctrl.mapped` from `ctrl.items`. Cheap no-op without a map. */
2438
+ function syncMapped() {
2439
+ if (!hasMap()) {
2440
+ ctrl.mapped = ctrl.items;
2441
+ return;
2442
+ }
2443
+ const cols = mapColumns();
2444
+ ctrl.mapped = ctrl.items.map((row, i) => mapRow(row, i, cols));
2445
+ }
2446
+ /** Flatten `ctrl.items` into the ready-to-render, keyed `displayRows` list. */
2447
+ function buildDisplayRows() {
2448
+ const out = [];
2449
+ const cols = mapColumns();
2450
+ if (!isGrouped()) {
2451
+ ctrl.items.forEach((row, i) => out.push({
2452
+ key: `r:${rowKeyOf(row, i)}`,
2453
+ kind: "row",
2454
+ level: 0,
2455
+ row,
2456
+ mapped: mapRow(row, i, cols)
2457
+ }));
2458
+ return out;
2459
+ }
2460
+ const footerOn = !pageSizeAll && (serverMode() || defaultGroupRowPageSize > 0 || groupRowPaging.size > 0);
2461
+ const walk = (nodes) => {
2462
+ for (const node of nodes) {
2463
+ out.push({
2464
+ key: `g:${node.path}`,
2465
+ kind: "group",
2466
+ level: node.level,
2467
+ node
2468
+ });
2469
+ if (node.collapsed) continue;
2470
+ const children = node.items;
2471
+ if (children.length > 0 && isGroupNode(children[0])) walk(children);
2472
+ else {
2473
+ children.forEach((row, i) => out.push({
2474
+ key: `r:${node.path}:${rowKeyOf(row, i)}`,
2475
+ kind: "row",
2476
+ level: node.level + 1,
2477
+ row,
2478
+ mapped: mapRow(row, i, cols)
2479
+ }));
2480
+ if (footerOn) out.push({
2481
+ key: `f:${node.path}`,
2482
+ kind: "footer",
2483
+ level: node.level,
2484
+ node
2485
+ });
2486
+ }
2487
+ }
2488
+ };
2489
+ walk(ctrl.items);
2490
+ return out;
2491
+ }
2492
+ /** Rebuild `displayRows` from the current `items`, then notify subscribers. */
2493
+ function render() {
2494
+ ctrl.displayRows = buildDisplayRows();
2495
+ notify();
2496
+ }
2497
+ /** Build the full group tree from every matched row the source holds. */
2498
+ function fullGroups() {
2499
+ return buildGroups(flattenLeaves(bound ? [...bound.items?.() ?? []] : []), groupFields, collapsed);
2500
+ }
2501
+ /** Index every group node by path (full, unsliced) for `groupPageInfo`. */
2502
+ function indexPaths(nodes) {
2503
+ for (const node of nodes) {
2504
+ pathIndex.set(node.path, node);
2505
+ if (node.items.length && isGroupNode(node.items[0])) indexPaths(node.items);
2506
+ }
2507
+ }
2508
+ /** Clone nodes, slicing each group's items to its registered group-page. */
2509
+ function toDisplay(nodes) {
2510
+ return nodes.map((node) => {
2511
+ let items = node.items.length > 0 && isGroupNode(node.items[0]) ? toDisplay(node.items) : node.items;
2512
+ const pg = pageSizeAll ? null : groupRowPagingFor(node.path);
2513
+ if (pg) {
2514
+ const start = pg.pageIndex * pg.pageSize;
2515
+ items = items.slice(start, start + pg.pageSize);
2516
+ }
2517
+ return {
2518
+ ...node,
2519
+ items
2520
+ };
2521
+ });
2522
+ }
2523
+ function syncGrouped() {
2524
+ const all = fullGroups();
2525
+ pathIndex.clear();
2526
+ indexPaths(all);
2527
+ const total = all.length;
2528
+ const size = pageSizeAll ? Math.max(1, total) : Math.max(1, groupPageSize);
2529
+ const pageCount = Math.max(1, Math.ceil(total / size));
2530
+ if (groupPageIndex > pageCount - 1) groupPageIndex = pageCount - 1;
2531
+ if (groupPageIndex < 0) groupPageIndex = 0;
2532
+ const start = groupPageIndex * size;
2533
+ ctrl.grouped = true;
2534
+ ctrl.items = toDisplay(all.slice(start, start + size));
2535
+ ctrl.totalCount = total;
2536
+ ctrl.pageSize = size;
2537
+ ctrl.pageSizeAll = pageSizeAll;
2538
+ ctrl.pageIndex = groupPageIndex;
2539
+ ctrl.pageCount = pageCount;
2540
+ render();
2541
+ }
2542
+ /** Effective row paging for a group in server mode (always has a size). */
2543
+ function serverRowPaging(path) {
2544
+ return groupRowPagingFor(path) ?? {
2545
+ pageIndex: 0,
2546
+ pageSize: defaultGroupRowPageSize || groupPageSize
2547
+ };
2548
+ }
2549
+ /** Re-render from the in-memory group list + loaded row pages (no fetch). */
2550
+ function renderServer() {
2551
+ pathIndex.clear();
2552
+ const total = serverGroups.length;
2553
+ const size = pageSizeAll ? Math.max(1, total) : Math.max(1, groupPageSize);
2554
+ const pageCount = Math.max(1, Math.ceil(total / size));
2555
+ if (groupPageIndex > pageCount - 1) groupPageIndex = pageCount - 1;
2556
+ if (groupPageIndex < 0) groupPageIndex = 0;
2557
+ const start = groupPageIndex * size;
2558
+ const nodes = serverGroups.slice(start, start + size).map((meta) => {
2559
+ const path = String(meta.key);
2560
+ const node = {
2561
+ key: meta.key,
2562
+ items: collapsed.has(path) ? [] : groupRows.get(path) ?? [],
2563
+ count: meta.count,
2564
+ level: 0,
2565
+ path,
2566
+ collapsed: collapsed.has(path),
2567
+ meta
2568
+ };
2569
+ pathIndex.set(path, node);
2570
+ return node;
2571
+ });
2572
+ ctrl.grouped = true;
2573
+ ctrl.items = nodes;
2574
+ ctrl.totalCount = total;
2575
+ ctrl.pageSize = size;
2576
+ ctrl.pageSizeAll = pageSizeAll;
2577
+ ctrl.pageIndex = groupPageIndex;
2578
+ ctrl.pageCount = pageCount;
2579
+ ctrl.loading = groupRowLoading.size > 0;
2580
+ render();
2581
+ }
2582
+ /** Fetch a group's current page — or, in "all" mode, every row (chunked). */
2583
+ async function loadGroupRows(meta, pg, want) {
2584
+ if (!serverGroup) return;
2585
+ const path = String(meta.key);
2586
+ groupRowLoading.add(path);
2587
+ renderServer();
2588
+ try {
2589
+ if (pageSizeAll) {
2590
+ const acc = [];
2591
+ for (let skip = 0;; skip += chunkSize) {
2592
+ const chunk = await serverGroup.loadRows(meta.key, {
2593
+ skip,
2594
+ take: chunkSize,
2595
+ search: searchValue,
2596
+ ...serverCtx()
2597
+ });
2598
+ acc.push(...chunk ?? []);
2599
+ if (!chunk || chunk.length < chunkSize || acc.length >= meta.count) break;
2600
+ if (skip > 1e7) break;
2601
+ }
2602
+ groupRows.set(path, acc);
2603
+ } else {
2604
+ const rows = await serverGroup.loadRows(meta.key, {
2605
+ skip: pg.pageIndex * pg.pageSize,
2606
+ take: pg.pageSize,
2607
+ search: searchValue,
2608
+ ...serverCtx()
2609
+ });
2610
+ groupRows.set(path, rows ?? []);
2611
+ }
2612
+ groupRowsKey.set(path, want);
2613
+ } catch (err) {
2614
+ captureError(err, "groupRows");
2615
+ } finally {
2616
+ groupRowLoading.delete(path);
2617
+ renderServer();
2618
+ }
2619
+ }
2620
+ /** Fetch rows for every visible, expanded group missing its current page. */
2621
+ async function fetchVisibleRows() {
2622
+ if (!serverGroup) return;
2623
+ const size = pageSizeAll ? Math.max(1, serverGroups.length) : Math.max(1, groupPageSize);
2624
+ const start = groupPageIndex * size;
2625
+ const visible = serverGroups.slice(start, start + size);
2626
+ const jobs = [];
2627
+ for (const meta of visible) {
2628
+ const path = String(meta.key);
2629
+ if (collapsed.has(path)) continue;
2630
+ const pg = serverRowPaging(path);
2631
+ const want = pageSizeAll ? "all" : `${pg.pageIndex}:${pg.pageSize}`;
2632
+ if (groupRowsKey.get(path) === want || groupRowLoading.has(path)) continue;
2633
+ jobs.push(loadGroupRows(meta, pg, want));
2634
+ }
2635
+ if (jobs.length) await Promise.all(jobs);
2636
+ }
2637
+ /** Reload the group list (cheap groupby), then the visible groups' rows. */
2638
+ async function loadGroupsServer() {
2639
+ if (!serverGroup) return;
2640
+ if (bound) applyBase(bound);
2641
+ const seq = ++groupSeq;
2642
+ ctrl.loading = true;
2643
+ notify();
2644
+ let metas = [];
2645
+ try {
2646
+ metas = await serverGroup.loadGroups({
2647
+ search: searchValue,
2648
+ ...serverCtx()
2649
+ }) ?? [];
2650
+ clearError();
2651
+ } catch (err) {
2652
+ captureError(err, "group");
2653
+ metas = [];
2654
+ }
2655
+ if (seq !== groupSeq) return;
2656
+ serverGroups = metas;
2657
+ clearRowCaches();
2658
+ renderServer();
2659
+ await fetchVisibleRows();
2660
+ }
2661
+ /**
2662
+ * Republish `ctrl` from the bound source.
2663
+ *
2664
+ * `initial` marks the one call `bind()` makes for itself. Every OTHER call
2665
+ * happens because the source produced a result — a load, a search, a filter, a
2666
+ * sort, a page — so this is the funnel where `hasLoaded` latches, rather than
2667
+ * the `changed` handler: a source that resolves `load()` without emitting still
2668
+ * reaches here, and `bind()`'s own pass must NOT count, because reflecting
2669
+ * whatever a source already holds is not the same as having asked it for
2670
+ * anything.
2671
+ */
2672
+ function sync(initial = false) {
2673
+ if (!initial) ctrl.hasLoaded = true;
2674
+ warnMissingKeyExpr();
2675
+ if (serverMode()) {
2676
+ renderServer();
2677
+ return;
2678
+ }
2679
+ if (isGrouped()) {
2680
+ syncGrouped();
2681
+ return;
2682
+ }
2683
+ if (scrollMode !== "off") {
2684
+ syncScroll();
2685
+ return;
2686
+ }
2687
+ if (pageSizeAll) {
2688
+ ctrl.grouped = false;
2689
+ ctrl.items = allRows;
2690
+ ctrl.pageIndex = 0;
2691
+ ctrl.pageSize = allRows.length || 1;
2692
+ ctrl.pageSizeAll = true;
2693
+ ctrl.totalCount = allRows.length;
2694
+ ctrl.pageCount = 1;
2695
+ render();
2696
+ return;
2697
+ }
2698
+ const s = bound;
2699
+ const rawItems = s ? [...s.items?.() ?? []] : [];
2700
+ const total = s?.totalCount?.();
2701
+ const pageSize = s?.pageSize?.() ?? rawItems.length;
2702
+ const pageIndex = s?.pageIndex?.() ?? 0;
2703
+ const hasTotal = typeof total === "number" && total >= 0;
2704
+ ctrl.grouped = false;
2705
+ ctrl.items = rawItems;
2706
+ ctrl.pageIndex = pageIndex;
2707
+ ctrl.pageSize = pageSize;
2708
+ ctrl.pageSizeAll = false;
2709
+ ctrl.totalCount = hasTotal ? total : rawItems.length;
2710
+ if (hasTotal && pageSize > 0) ctrl.pageCount = Math.max(1, Math.ceil(total / pageSize));
2711
+ else if (s?.isLastPage?.()) ctrl.pageCount = pageIndex + 1;
2712
+ else ctrl.pageCount = pageIndex + 2;
2713
+ render();
2714
+ }
2715
+ function syncLoading() {
2716
+ ctrl.loading = bound?.isLoading?.() ?? false;
2717
+ notify();
2718
+ }
2719
+ const onChanged = () => {
2720
+ if (suppressSync) return;
2721
+ sync();
2722
+ recomputeSummaries();
2723
+ };
2724
+ const onLoadingChanged = () => syncLoading();
2725
+ function detach() {
2726
+ if (!bound) return;
2727
+ bound.off("changed", onChanged);
2728
+ bound.off("loadingChanged", onLoadingChanged);
2729
+ bound.off("loadError", onLoadingChanged);
2730
+ bound = null;
2731
+ ctrl.hasLoaded = false;
2732
+ ctrl.error = null;
2733
+ }
2734
+ function bind(next) {
2735
+ const source = Array.isArray(next) ? monoArraySource(next, {
2736
+ pageSize: opts.pageSize,
2737
+ keyExpr: opts.keyExpr,
2738
+ searchExpr: plainSearchColumns(searchFields()),
2739
+ group: groupFields
2740
+ }) : next;
2741
+ if (bound === source) return;
2742
+ detach();
2743
+ bound = source;
2744
+ ctrl.dataSource = source;
2745
+ bumpSummary("data");
2746
+ externalBase = source?.filter?.() ?? null;
2747
+ lastWrittenFilter = externalBase;
2748
+ lastComposed = void 0;
2749
+ composeMemo = null;
2750
+ lastWrittenSort = source?.sort?.() ?? null;
2751
+ externalSort = normalizeSortList(lastWrittenSort);
2752
+ resolvedKey = null;
2753
+ appliedKnobs.clear();
2754
+ if (!source) {
2755
+ sync(true);
2756
+ return;
2757
+ }
2758
+ rebuildGridExpr();
2759
+ if (!opts.serverGroup && groupFields.length === 1 && typeof source.store === "function") serverGroup = storeGroupSource(source, {
2760
+ groupField: groupFields[0],
2761
+ select: opts.select,
2762
+ searchExpr: plainSearchColumns(searchFields()),
2763
+ searchOperation: opts.searchOperation,
2764
+ groupSummary: opts.groupSummary
2765
+ });
2766
+ source.on("changed", onChanged);
2767
+ source.on("loadingChanged", onLoadingChanged);
2768
+ source.on("loadError", onLoadingChanged);
2769
+ if (preferredPageSize != null) {
2770
+ const cur = source.pageSize?.();
2771
+ if (typeof cur === "number" && cur > 0) sourcePageSize = cur;
2772
+ source.pageSize?.(preferredPageSize);
2773
+ }
2774
+ if (groupFields.length && !serverMode()) applySourceSort();
2775
+ sync(true);
2776
+ }
2777
+ /**
2778
+ * Record a failed operation so something can render it.
2779
+ *
2780
+ * A FRESH object every time, never a reused one. `<mono-table-error>` dismisses
2781
+ * per failure rather than per message — it remembers the object it dismissed
2782
+ * and re-shows when a different one arrives — so two identical failures have to
2783
+ * be distinguishable, and object identity is the only thing that reliably is
2784
+ * (a store rejecting with the plain string `'Network error'` gives `===` values
2785
+ * for two entirely unrelated attempts).
2786
+ */
2787
+ function captureError(raw, source) {
2788
+ const { message, status, detail } = describeError(raw, opts.errorMessages);
2789
+ ctrl.error = {
2790
+ raw,
2791
+ message,
2792
+ status,
2793
+ detail,
2794
+ source,
2795
+ at: Date.now()
2796
+ };
2797
+ if (errorMode === "clear-list" && source !== "selectAll") {
2798
+ clear();
2799
+ return;
2800
+ }
2801
+ notify();
2802
+ }
2803
+ /**
2804
+ * Drop a recorded error because an operation just succeeded.
2805
+ *
2806
+ * Called at each operation's own success point rather than from the render
2807
+ * funnels (`sync` / `renderServer`), and that is not a stylistic choice:
2808
+ * `loadGroupsServer` calls `renderServer()` immediately AFTER its catch, so a
2809
+ * clear there would wipe the error it had just captured, every time.
2810
+ *
2811
+ * Guarded, so a table that has never failed does not publish a notify on every
2812
+ * successful load for a field nobody changed.
2813
+ */
2814
+ function clearError() {
2815
+ if (!ctrl.error) return;
2816
+ ctrl.error = null;
2817
+ notify();
2818
+ }
2819
+ async function runLoad(fn, source = "load") {
2820
+ if (!bound) return;
2821
+ try {
2822
+ await Promise.resolve(fn());
2823
+ clearError();
2824
+ } catch (err) {
2825
+ if (isCanceled(err)) return;
2826
+ captureError(err, source);
2827
+ throw err;
2828
+ }
2829
+ }
2830
+ /**
2831
+ * Whether the source pages. Client group mode pages the groups in memory, so
2832
+ * it loads everything; otherwise `dataSourceOptions.paginate` decides, and the
2833
+ * default is to page — a flat table always did.
2834
+ */
2835
+ function sourcePaginate() {
2836
+ if (isGrouped()) return false;
2837
+ return resolvedOpts.paginate ?? true;
2838
+ }
2839
+ async function load() {
2840
+ const s = bound;
2841
+ if (s) applyBase(s);
2842
+ if (serverMode()) {
2843
+ await loadGroupsServer();
2844
+ return;
2845
+ }
2846
+ if (scrollActive()) {
2847
+ await loadScrollPage(true);
2848
+ return;
2849
+ }
2850
+ if (pageSizeAll && !isGrouped()) {
2851
+ await loadAllChunked();
2852
+ return;
2853
+ }
2854
+ if (!s) return;
2855
+ s.paginate?.(sourcePaginate());
2856
+ await runLoad(() => s.load());
2857
+ }
2858
+ async function reload() {
2859
+ bumpSummary("data");
2860
+ const s = bound;
2861
+ if (s) applyBase(s);
2862
+ if (serverMode()) {
2863
+ clearRowCaches();
2864
+ await loadGroupsServer();
2865
+ return;
2866
+ }
2867
+ if (scrollActive()) {
2868
+ await loadScrollPage(true);
2869
+ return;
2870
+ }
2871
+ if (pageSizeAll && !isGrouped()) {
2872
+ await loadAllChunked();
2873
+ return;
2874
+ }
2875
+ if (!s) return;
2876
+ s.paginate?.(sourcePaginate());
2877
+ await runLoad(() => s.reload ? s.reload() : s.load(), "reload");
2878
+ }
2879
+ function clear() {
2880
+ scrollToken++;
2881
+ suppressSync = false;
2882
+ accumulated = [];
2883
+ allRows = [];
2884
+ scrollNextPage = 0;
2885
+ scrollHasMore = false;
2886
+ vStart = vEnd = vPadTop = vPadBottom = 0;
2887
+ serverGroups = [];
2888
+ clearRowCaches();
2889
+ ctrl.items = [];
2890
+ ctrl.loading = false;
2891
+ ctrl.totalCount = 0;
2892
+ ctrl.pageIndex = 0;
2893
+ ctrl.pageCount = 1;
2894
+ ctrl.loadedCount = 0;
2895
+ ctrl.hasMore = false;
2896
+ ctrl.virtualStart = ctrl.virtualEnd = 0;
2897
+ ctrl.virtualPadTop = ctrl.virtualPadBottom = 0;
2898
+ render();
2899
+ }
2900
+ async function setPage(pageIndex) {
2901
+ if (isGrouped()) {
2902
+ groupPageIndex = Math.max(0, pageIndex);
2903
+ sync();
2904
+ if (serverMode()) await fetchVisibleRows();
2905
+ return;
2906
+ }
2907
+ const s = bound;
2908
+ if (!s) return;
2909
+ s.pageIndex?.(Math.max(0, pageIndex));
2910
+ await load();
2911
+ }
2912
+ async function setPageSize(pageSize) {
2913
+ const all = pageSize === "all";
2914
+ pageSizeAll = all;
2915
+ if (isGrouped()) {
2916
+ if (!all) groupPageSize = Math.max(1, pageSize);
2917
+ groupPageIndex = 0;
2918
+ sync();
2919
+ if (serverMode()) await fetchVisibleRows();
2920
+ return;
2921
+ }
2922
+ const s = bound;
2923
+ if (!s) return;
2924
+ if (all) {
2925
+ await loadAllChunked();
2926
+ return;
2927
+ }
2928
+ s.pageSize?.(pageSize);
2929
+ s.pageIndex?.(0);
2930
+ await load();
2931
+ }
2932
+ /**
2933
+ * Ungrouped "all": pull every row, in `chunkSize` requests, accumulate, and
2934
+ * show on one page. Works with a capped backend (e.g. 100 rows/request).
2935
+ */
2936
+ async function loadAllChunked() {
2937
+ const s = bound;
2938
+ if (!s) {
2939
+ allRows = [];
2940
+ sync();
2941
+ return;
2942
+ }
2943
+ suppressSync = true;
2944
+ ctrl.loading = true;
2945
+ notify();
2946
+ const acc = [];
2947
+ try {
2948
+ applyBase(s);
2949
+ s.paginate?.(true);
2950
+ s.pageSize?.(chunkSize);
2951
+ let page = 0;
2952
+ for (;;) {
2953
+ s.pageIndex?.(page);
2954
+ await runLoad(() => s.load());
2955
+ const items = [...s.items?.() ?? []];
2956
+ acc.push(...items);
2957
+ const total = s.totalCount?.();
2958
+ const reachedTotal = typeof total === "number" && total >= 0 && acc.length >= total;
2959
+ if ((s.isLastPage?.() ?? items.length < chunkSize) || reachedTotal || items.length === 0) break;
2960
+ page += 1;
2961
+ if (page > 1e5) break;
2962
+ }
2963
+ } finally {
2964
+ suppressSync = false;
2965
+ }
2966
+ allRows = acc;
2967
+ ctrl.loading = bound?.isLoading?.() ?? false;
2968
+ sync();
2969
+ }
2970
+ /** Scroll modes only apply to a flat (ungrouped, non-server) grid. */
2971
+ function scrollActive() {
2972
+ return scrollMode !== "off" && !isGrouped() && !serverMode();
2973
+ }
2974
+ /** Map the accumulator (+ virtual window) onto `ctrl` and render. */
2975
+ function syncScroll() {
2976
+ const s = bound;
2977
+ const total = s?.totalCount?.();
2978
+ const hasTotal = typeof total === "number" && total >= 0;
2979
+ ctrl.grouped = false;
2980
+ ctrl.pageSizeAll = false;
2981
+ ctrl.scrollMode = scrollMode;
2982
+ ctrl.loading = s?.isLoading?.() ?? false;
2983
+ ctrl.loadedCount = accumulated.length;
2984
+ ctrl.hasMore = scrollHasMore;
2985
+ ctrl.totalCount = hasTotal ? total : accumulated.length;
2986
+ ctrl.pageIndex = 0;
2987
+ ctrl.pageSize = accumulated.length || 1;
2988
+ ctrl.pageCount = 1;
2989
+ if (scrollMode === "virtual") {
2990
+ const end = Math.min(Math.max(vStart, vEnd), accumulated.length);
2991
+ const start = Math.min(Math.max(0, vStart), end);
2992
+ ctrl.items = accumulated.slice(start, end);
2993
+ ctrl.virtualStart = start;
2994
+ ctrl.virtualEnd = end;
2995
+ ctrl.virtualPadTop = Math.max(0, vPadTop);
2996
+ ctrl.virtualPadBottom = Math.max(0, vPadBottom);
2997
+ } else {
2998
+ ctrl.items = accumulated;
2999
+ ctrl.virtualStart = 0;
3000
+ ctrl.virtualEnd = accumulated.length;
3001
+ ctrl.virtualPadTop = 0;
3002
+ ctrl.virtualPadBottom = 0;
3003
+ }
3004
+ render();
3005
+ }
3006
+ /**
3007
+ * Load one source page and append it to the accumulator. `reset` clears the
3008
+ * accumulator and starts from page 0 (a fresh query / mode enable).
3009
+ *
3010
+ * Concurrency: every entry claims a `scrollToken`. Only the holder of the newest one may
3011
+ * append, advance the page counter or release the shared load flags -- a reset (filter change,
3012
+ * search, rebind) routinely lands while a `loadNext()` is still awaiting its page, and
3013
+ * `runLoad` swallows the cancellation that would otherwise stop the loser.
3014
+ */
3015
+ async function loadScrollPage(reset) {
3016
+ const s = bound;
3017
+ if (!s) {
3018
+ accumulated = [];
3019
+ scrollNextPage = 0;
3020
+ scrollHasMore = false;
3021
+ syncScroll();
3022
+ return;
3023
+ }
3024
+ const token = ++scrollToken;
3025
+ const mine = () => token === scrollToken;
3026
+ if (reset) {
3027
+ applyBase(s);
3028
+ accumulated = [];
3029
+ scrollNextPage = 0;
3030
+ vStart = vEnd = vPadTop = vPadBottom = 0;
3031
+ ctrl.items = [];
3032
+ ctrl.loadedCount = 0;
3033
+ ctrl.hasMore = false;
3034
+ ctrl.virtualStart = ctrl.virtualEnd = 0;
3035
+ ctrl.virtualPadTop = ctrl.virtualPadBottom = 0;
3036
+ }
3037
+ suppressSync = true;
3038
+ ctrl.loading = true;
3039
+ notify();
3040
+ try {
3041
+ s.paginate?.(true);
3042
+ const page = scrollNextPage;
3043
+ s.pageIndex?.(page);
3044
+ await runLoad(() => s.load());
3045
+ if (!mine()) return;
3046
+ const rows = [...s.items?.() ?? []];
3047
+ accumulated.push(...rows);
3048
+ const total = s.totalCount?.();
3049
+ const size = s.pageSize?.() ?? rows.length;
3050
+ const reachedTotal = typeof total === "number" && total >= 0 && accumulated.length >= total;
3051
+ const isLast = s.isLastPage?.() ?? rows.length < (size || 1);
3052
+ scrollHasMore = !(reachedTotal || isLast || rows.length === 0);
3053
+ scrollNextPage = page + 1;
3054
+ } finally {
3055
+ if (mine()) suppressSync = false;
3056
+ }
3057
+ if (!mine()) return;
3058
+ ctrl.loading = bound?.isLoading?.() ?? false;
3059
+ syncScroll();
3060
+ }
3061
+ async function setScrollPaging(mode, opts = {}) {
3062
+ const reload = opts.reload !== false;
3063
+ if (mode !== "off" && (isGrouped() || serverMode())) {
3064
+ if (typeof console !== "undefined") console.warn(`[mono-table] scroll paging ("${mode}") requires a flat (ungrouped) table; keeping standard paging.`);
3065
+ if (scrollMode !== "off") {
3066
+ scrollMode = "off";
3067
+ ctrl.scrollMode = "off";
3068
+ }
3069
+ return;
3070
+ }
3071
+ if (scrollMode === mode) return;
3072
+ scrollMode = mode;
3073
+ ctrl.scrollMode = mode;
3074
+ if (mode === "off") {
3075
+ if (!reload) {
3076
+ syncScroll();
3077
+ return;
3078
+ }
3079
+ accumulated = [];
3080
+ await load();
3081
+ return;
3082
+ }
3083
+ if (!bound) {
3084
+ syncScroll();
3085
+ return;
3086
+ }
3087
+ if (accumulated.length) {
3088
+ syncScroll();
3089
+ return;
3090
+ }
3091
+ await loadScrollPage(true);
3092
+ }
3093
+ async function loadNext() {
3094
+ if (!scrollActive() || !scrollHasMore || scrollLoading) return;
3095
+ scrollLoading = true;
3096
+ try {
3097
+ await loadScrollPage(false);
3098
+ } finally {
3099
+ scrollLoading = false;
3100
+ }
3101
+ }
3102
+ function setVirtualWindow(start, end, padTop, padBottom) {
3103
+ if (scrollMode !== "virtual") return;
3104
+ const ns = Math.max(0, Math.floor(start));
3105
+ const ne = Math.max(ns, Math.floor(end));
3106
+ const nt = Math.max(0, Math.round(padTop));
3107
+ const nb = Math.max(0, Math.round(padBottom));
3108
+ if (ns === vStart && ne === vEnd && nt === vPadTop && nb === vPadBottom) return;
3109
+ vStart = ns;
3110
+ vEnd = ne;
3111
+ vPadTop = nt;
3112
+ vPadBottom = nb;
3113
+ syncScroll();
3114
+ }
3115
+ async function setPreferredPageSize(pageSize) {
3116
+ const v = typeof pageSize === "number" && pageSize > 0 ? Math.floor(pageSize) : null;
3117
+ if (v === preferredPageSize) return;
3118
+ const wasOverriding = preferredPageSize != null;
3119
+ preferredPageSize = v;
3120
+ const s = bound;
3121
+ if (!s) return;
3122
+ if (v != null) {
3123
+ if (!wasOverriding) {
3124
+ const cur = s.pageSize?.();
3125
+ sourcePageSize = typeof cur === "number" && cur > 0 ? cur : null;
3126
+ }
3127
+ s.pageSize?.(v);
3128
+ } else {
3129
+ const base = resolvedOpts.pageSize;
3130
+ if (typeof base === "number" && base > 0) s.pageSize?.(base);
3131
+ else if (sourcePageSize != null) s.pageSize?.(sourcePageSize);
3132
+ sourcePageSize = null;
3133
+ }
3134
+ s.pageIndex?.(0);
3135
+ await load();
3136
+ }
3137
+ /**
3138
+ * Rows a `*` pattern resolves against. The full backing array when the source
3139
+ * has one (an in-memory source exposes `data()`), else the loaded page — which
3140
+ * is all a remote source can offer, and is enough to read the shape.
3141
+ */
3142
+ function searchSampleRows() {
3143
+ return bound?.data?.() ?? ctrl.items ?? [];
3144
+ }
3145
+ /**
3146
+ * searchExpr as a list of entries — plain column names, `{ field, custom }` and
3147
+ * `*` patterns resolved against the data. `textOnly` for a remote source, whose
3148
+ * `$filter` can only `contains` a string column.
3149
+ */
3150
+ function searchEntries() {
3151
+ return resolveSearchEntries(effectiveSearchFields(), {
3152
+ rows: searchSampleRows(),
3153
+ textOnly: hasStore()
3154
+ });
3155
+ }
3156
+ /**
3157
+ * The entries an unbound term fans out over, or the single entry a
3158
+ * column-bound term targets. A bound field with no `searchExpr` entry keeps the
3159
+ * old behaviour and is treated as a plain column.
3160
+ */
3161
+ function entriesForTerm(field, all) {
3162
+ if (!field) return all;
3163
+ return [searchEntryFor(searchFields(), field) ?? field];
3164
+ }
3165
+ /** Whether a remote search must bypass devextreme folding (a path/wildcard column). */
3166
+ function searchIsPath() {
3167
+ return hasStore() && plainSearchColumns(searchFields()).some(isPath);
3168
+ }
3169
+ /** Current live search string, held when the controller owns the search filter (path mode). */
3170
+ let searchTerm = null;
3171
+ /**
3172
+ * Remote OData search filter expr, built by the controller when a searchExpr
3173
+ * column is a path/wildcard (devextreme's own search folding can't emit `Job/Name`
3174
+ * navigation or `Nav/any(...)` lambdas). Returns `null` when folding handles it.
3175
+ */
3176
+ /** Join expressions with `and` / `or`, returning the lone one unwrapped. */
3177
+ function joinExprs(parts, join) {
3178
+ const kept = parts.filter((p) => p != null);
3179
+ if (!kept.length) return null;
3180
+ if (kept.length === 1) return kept[0];
3181
+ const out = [];
3182
+ kept.forEach((p, i) => {
3183
+ if (i) out.push(join);
3184
+ out.push(p);
3185
+ });
3186
+ return out;
3187
+ }
3188
+ /**
3189
+ * Group the live terms by `field` — a term with no field covers every
3190
+ * `searchExpr` column. Preserves first-seen order so the emitted filter is
3191
+ * stable and diffable.
3192
+ */
3193
+ function groupedSearchTerms() {
3194
+ const order = [];
3195
+ const byField = /* @__PURE__ */ new Map();
3196
+ for (const t of ctrl.searchTerms) {
3197
+ const v = String(t?.value ?? "");
3198
+ if (!v) continue;
3199
+ const key = t.field || void 0;
3200
+ if (!byField.has(key)) {
3201
+ byField.set(key, []);
3202
+ order.push(key);
3203
+ }
3204
+ byField.get(key).push(v);
3205
+ }
3206
+ return order.map((field) => ({
3207
+ field,
3208
+ values: byField.get(field)
3209
+ }));
3210
+ }
3211
+ /**
3212
+ * Remote OData search filter built by the controller rather than devextreme's
3213
+ * own `searchValue` folding. Used when folding CAN'T express the search:
3214
+ * - a `searchExpr` column is a path/wildcard (`Job/Name`, `Nav/any(...)`), or
3215
+ * - there is more than one term, or any term is bound to a single column —
3216
+ * devextreme's `searchValue` holds exactly one string across one column set.
3217
+ *
3218
+ * Shape: OR within a field, AND across fields.
3219
+ */
3220
+ function remoteSearchExpr() {
3221
+ const groups = groupedSearchTerms();
3222
+ if (!groups.length || !controllerOwnsSearch()) return null;
3223
+ const op = opts.searchOperation ?? "contains";
3224
+ const all = searchEntries();
3225
+ return joinExprs(groups.map((g) => {
3226
+ const entries = entriesForTerm(g.field, all);
3227
+ const clauses = [];
3228
+ for (const value of g.values) {
3229
+ const expr = joinExprs(entries.map((e) => typeof e === "string" ? toODataClause(e, op, value) : customRemoteClause(e, value, op)).filter((c) => c != null), "or");
3230
+ if (expr != null) clauses.push(expr);
3231
+ }
3232
+ return joinExprs(clauses, "or");
3233
+ }), "and");
3234
+ }
3235
+ /**
3236
+ * Whether the controller (not devextreme) must build the search filter. One
3237
+ * plain unbound term over plain columns can still ride devextreme's folding.
3238
+ */
3239
+ function controllerOwnsSearch() {
3240
+ const groups = groupedSearchTerms();
3241
+ if (!groups.length) return false;
3242
+ if (!hasStore()) return false;
3243
+ if (searchIsPath()) return true;
3244
+ if (hasCustomSearch(effectiveSearchFields()) || hasWildcardSearch(effectiveSearchFields())) return true;
3245
+ return !(groups.length === 1 && groups[0].values.length === 1 && !groups[0].field);
3246
+ }
3247
+ /** Client-side equivalent: OR within a field, AND across fields. */
3248
+ function searchPredicate() {
3249
+ const groups = groupedSearchTerms();
3250
+ if (!groups.length) return null;
3251
+ const op = opts.searchOperation ?? "contains";
3252
+ const matches = (row, col, needle) => {
3253
+ const raw = isPath(col) ? getFieldValue(row, col) : row?.[col];
3254
+ return (Array.isArray(raw) ? raw : [raw]).some((v) => {
3255
+ const s = String(v ?? "").toLowerCase();
3256
+ const n = needle.toLowerCase();
3257
+ if (op === "startswith") return s.startsWith(n);
3258
+ if (op === "endswith") return s.endsWith(n);
3259
+ if (op === "=" || op === "equals") return s === n;
3260
+ return s.includes(n);
3261
+ });
3262
+ };
3263
+ const all = searchEntries();
3264
+ const perGroup = groups.map((g) => entriesForTerm(g.field, all));
3265
+ return (row) => groups.every((g, i) => {
3266
+ const entries = perGroup[i];
3267
+ if (!entries.length) return true;
3268
+ return g.values.some((needle) => entries.some((e) => {
3269
+ if (typeof e === "string") return matches(row, e, needle);
3270
+ const pred = customPredicate(e, needle, op);
3271
+ return pred ? pred(row) : false;
3272
+ }));
3273
+ });
3274
+ }
3275
+ /**
3276
+ * Whether an ARRAY source needs the controller's predicate. One plain unbound
3277
+ * term can stay on the source's own `searchValue`/`searchExpr` — which matters
3278
+ * when `searchExpr` was configured on `monoArraySource` and not on the grid.
3279
+ */
3280
+ function controllerOwnsArraySearch() {
3281
+ const groups = groupedSearchTerms();
3282
+ if (!groups.length) return false;
3283
+ if (hasCustomSearch(effectiveSearchFields()) || hasWildcardSearch(effectiveSearchFields())) return true;
3284
+ if (groups.length === 1 && groups[0].values.length === 1 && !groups[0].field) return false;
3285
+ return true;
3286
+ }
3287
+ /** Single-term shorthand — the classic search box. */
3288
+ async function setSearch(value) {
3289
+ await setSearchTerms(value ? [{ value }] : []);
3290
+ }
3291
+ /**
3292
+ * Replace the searched fields. `undefined`/`null` drops the override so the
3293
+ * `searchExpr` / `search-value` option takes over again.
3294
+ *
3295
+ * Only re-runs the query when a search is actually live — changing the fields
3296
+ * with an empty box has nothing to re-filter, and a needless `load()` would
3297
+ * make `<mono-table-search>` refetch on every connect.
3298
+ */
3299
+ async function setSearchExpr(expr) {
3300
+ searchFieldsOverride = expr == null ? void 0 : mergeSearchFields({ searchExpr: expr });
3301
+ if (!ctrl.searchTerms.length) {
3302
+ notify();
3303
+ return;
3304
+ }
3305
+ await setSearchTerms(ctrl.searchTerms);
3306
+ }
3307
+ async function setSearchTerms(terms) {
3308
+ ctrl.searchTerms = (terms ?? []).filter((t) => t && String(t.value ?? "") !== "");
3309
+ groupPageIndex = 0;
3310
+ bumpSummary("search");
3311
+ if (serverMode()) {
3312
+ searchValue = ctrl.searchTerms.map((t) => t.value).join(" ") || null;
3313
+ notify();
3314
+ await loadGroupsServer();
3315
+ return;
3316
+ }
3317
+ const s = bound;
3318
+ if (!s) return;
3319
+ searchTerm = ctrl.searchTerms[0]?.value ?? null;
3320
+ if (!hasStore()) {
3321
+ if (controllerOwnsArraySearch()) s.searchValue?.(null);
3322
+ else {
3323
+ s.searchOperation?.(opts.searchOperation ?? "contains");
3324
+ if (searchFieldsConfigured()) s.searchExpr?.(plainSearchColumns(searchFields()));
3325
+ s.searchValue?.(searchTerm);
3326
+ }
3327
+ applyFilter();
3328
+ } else if (controllerOwnsSearch()) {
3329
+ s.searchValue?.(null);
3330
+ applyFilter();
3331
+ } else {
3332
+ s.searchOperation?.(opts.searchOperation ?? "contains");
3333
+ if (searchFieldsConfigured()) s.searchExpr?.(plainSearchColumns(searchFields()));
3334
+ s.searchValue?.(searchTerm);
3335
+ applyFilter();
3336
+ }
3337
+ s.pageIndex?.(0);
3338
+ notify();
3339
+ await load();
3340
+ }
3341
+ /**
3342
+ * The grid's explicit filter — ONE layer, not a replacement.
3343
+ *
3344
+ * This is what the slotted filter builder writes, and it used to be a bare
3345
+ * `s.filter(x)`: it replaced the consumer's own source filter, the column
3346
+ * filters and the search in one go, and the next keystroke then replaced IT
3347
+ * with the search alone. It is now composed like everything else — under the
3348
+ * base, beside the columns and the search — and `null` clears only this layer.
3349
+ * That is also why it is NOT the adopted external base: the builder's *Clear*
3350
+ * would otherwise wipe the scope the consumer set on the source.
3351
+ */
3352
+ async function setFilter(filter) {
3353
+ if (!bound) return;
3354
+ bumpSummary("search");
3355
+ explicitFilter = hasStore() ? filter ?? null : compileOrPredicate(filter);
3356
+ applyFilter();
3357
+ groupPageIndex = 0;
3358
+ await load();
3359
+ }
3360
+ const columnFilters = /* @__PURE__ */ new Map();
3361
+ /** The combine gesture, remembered — see `MonoSetColumnFilterOptions.sticky`. */
3362
+ let filterCombining = false;
3363
+ /** Whether the bound source is a remote DataSource (has a devextreme store). */
3364
+ function hasStore() {
3365
+ return typeof bound?.store === "function" && !!bound.store();
3366
+ }
3367
+ /**
3368
+ * OR-join a column's value clauses into a devextreme filter expression (path-aware).
3369
+ *
3370
+ * A value is normally a plain equality; a `{ from, to }` range (what the DATE
3371
+ * filter stores) becomes `[[f,'>=',from],'and',[f,'<',to]]` — half-open, so
3372
+ * a "March" range never touches April 1st 00:00:00.
3373
+ */
3374
+ function orClauses(field, values) {
3375
+ const clauses = values.map((v) => {
3376
+ if (isDateRange(v)) {
3377
+ const ge = toODataClause(field, ">=", v.from);
3378
+ const lt = toODataClause(field, "<", v.to);
3379
+ return ge && lt ? [
3380
+ ge,
3381
+ "and",
3382
+ lt
3383
+ ] : null;
3384
+ }
3385
+ return toODataClause(field, "=", v);
3386
+ }).filter((c) => c != null);
3387
+ if (!clauses.length) return null;
3388
+ if (clauses.length === 1) return clauses[0];
3389
+ const out = [];
3390
+ clauses.forEach((c, i) => {
3391
+ if (i) out.push("or");
3392
+ out.push(c);
3393
+ });
3394
+ return out;
3395
+ }
3396
+ /**
3397
+ * Rebuild the grid's OWN filter layers from the column filters and the
3398
+ * controller-owned search — `columnExpr` (columns only, what a server-group
3399
+ * source gets) and `gridExpr` (columns AND search, what the source gets).
3400
+ *
3401
+ * Only builds; it writes nothing. Until this was split out, the write was here
3402
+ * too, and it wrote THIS expression alone — so a filter the consumer had put on
3403
+ * the source was gone on the first search keystroke and "restored" to nothing
3404
+ * on clear. The write now goes through `writeFilter()`, which composes these on
3405
+ * top of the base.
3406
+ */
3407
+ /** The active column filters, optionally leaving one column out (its own panel). */
3408
+ function activeColumnFilters(except) {
3409
+ return [...columnFilters.entries()].filter(([f, v]) => v.length && f !== except);
3410
+ }
3411
+ /** Remote: the column filters as ONE devextreme expression — AND across columns, OR within. */
3412
+ function columnFilterExpr(except) {
3413
+ const cols = activeColumnFilters(except).map(([f, v]) => orClauses(f, v)).filter((c) => c != null);
3414
+ if (!cols.length) return null;
3415
+ if (cols.length === 1) return cols[0];
3416
+ const out = [];
3417
+ cols.forEach((c, i) => {
3418
+ if (i) out.push("and");
3419
+ out.push(c);
3420
+ });
3421
+ return out;
3422
+ }
3423
+ /**
3424
+ * Array / client: the same as a predicate. Path fields resolve nested/wildcard
3425
+ * values (wildcard = match any element).
3426
+ */
3427
+ function columnFilterPredicate(except) {
3428
+ const active = activeColumnFilters(except);
3429
+ if (!active.length) return null;
3430
+ return (row) => active.every(([f, vals]) => {
3431
+ const raw = isPath(f) ? getFieldValue(row, f) : row?.[f];
3432
+ return (Array.isArray(raw) ? raw : [raw]).some((rv) => vals.some((v) => {
3433
+ if (isDateRange(v)) {
3434
+ const d = parseDateValue(rv);
3435
+ if (!d) return false;
3436
+ return inRange(d, {
3437
+ from: v.from instanceof Date ? v.from : new Date(v.from),
3438
+ to: v.to instanceof Date ? v.to : new Date(v.to)
3439
+ });
3440
+ }
3441
+ return String(rv) === String(v);
3442
+ }));
3443
+ });
3444
+ }
3445
+ function rebuildGridExpr() {
3446
+ if (hasStore()) {
3447
+ const expr = columnFilterExpr();
3448
+ columnExpr = expr;
3449
+ gridExpr = andFilters(expr, remoteSearchExpr());
3450
+ } else {
3451
+ const colPred = columnFilterPredicate();
3452
+ const sPred = controllerOwnsArraySearch() ? searchPredicate() : null;
3453
+ columnExpr = colPred;
3454
+ gridExpr = andPredicates(colPred, sPred);
3455
+ }
3456
+ }
3457
+ /**
3458
+ * A grid-driven filter change: search, column filter, `setFilter`. Adopts
3459
+ * anything the consumer changed on the source in the meantime, rebuilds the
3460
+ * grid's layers, writes the composed filter and goes back to page 0.
3461
+ */
3462
+ function applyFilter() {
3463
+ const s = bound;
3464
+ if (!s) return;
3465
+ syncExternalFilter(s);
3466
+ rebuildGridExpr();
3467
+ writeFilter(s);
3468
+ s.pageIndex?.(0);
3469
+ }
3470
+ async function setColumnFilter(field, values, options) {
3471
+ if (!field) return;
3472
+ const multi = options?.multi === true;
3473
+ if (!multi) columnFilters.clear();
3474
+ if (!values || !values.length) columnFilters.delete(field);
3475
+ else columnFilters.set(field, [...values]);
3476
+ filterCombining = columnFilters.size > 0 && multi && (options?.sticky === true || filterCombining);
3477
+ groupPageIndex = 0;
3478
+ bumpSummary("search");
3479
+ notify();
3480
+ applyFilter();
3481
+ await load();
3482
+ }
3483
+ function columnFilter(field) {
3484
+ return [...columnFilters.get(field) ?? []];
3485
+ }
3486
+ function columnFilterCombining() {
3487
+ return filterCombining;
3488
+ }
3489
+ function filteredColumns() {
3490
+ return [...columnFilters.entries()].filter(([, v]) => v.length).map(([f]) => f);
3491
+ }
3492
+ let dsOptionsSource = opts.dataSourceOptions;
3493
+ let odataOptionsSource = opts.odataOptions;
3494
+ /** The merged, normalised options as of the last resolve. */
3495
+ let resolvedOpts = {};
3496
+ /** Change key of `resolvedOpts` — `null` until the first resolve, which always counts. */
3497
+ let resolvedKey = null;
3498
+ /** A function-valued `filter` cannot go through the key; compared by reference instead. */
3499
+ let optionsFilterRef = void 0;
3500
+ let optionsFilter = null;
3501
+ let optionsSort = [];
3502
+ /** Knobs `applyBaseOptions` has written, so a knob that DISAPPEARS is cleared, not left behind. */
3503
+ const appliedKnobs = /* @__PURE__ */ new Set();
3504
+ let externalBase = null;
3505
+ let explicitFilter = null;
3506
+ let columnExpr = null;
3507
+ let gridExpr = null;
3508
+ /** What the source HELD after the grid's last write (read back — a source may normalise). */
3509
+ let lastWrittenFilter = null;
3510
+ /** What the grid last composed and handed to the source. */
3511
+ let lastComposed = void 0;
3512
+ let composeMemo = null;
3513
+ let externalSort = [];
3514
+ let lastWrittenSort = null;
3515
+ let rawFilterWarned = false;
3516
+ /** Array-source filter layer: a predicate as given, an array compiled, a raw OData string dropped. */
3517
+ function compileOrPredicate(filter) {
3518
+ if (filter == null) return null;
3519
+ if (typeof filter === "function") return filter;
3520
+ if (Array.isArray(filter) && filter.length === 1 && typeof filter[0] === "string") {
3521
+ if (!rawFilterWarned) {
3522
+ rawFilterWarned = true;
3523
+ console.warn("[monoDataGrid] a raw OData $filter string cannot filter an array source — ignored.");
3524
+ }
3525
+ return null;
3526
+ }
3527
+ return compileFilterPredicate(filter);
3528
+ }
3529
+ /**
3530
+ * Re-read the two option bags. Returns whether anything changed since the last
3531
+ * resolve — the getters hand back a fresh object every time, so the comparison
3532
+ * is by content, not identity.
3533
+ */
3534
+ function resolveDsOptions() {
3535
+ const merged = resolveDataSourceOptions(dsOptionsSource, odataOptionsSource);
3536
+ const fnFilter = typeof merged.filter === "function" ? merged.filter : void 0;
3537
+ const key = dataSourceOptionsKey(merged);
3538
+ if (key === resolvedKey && fnFilter === optionsFilterRef) return false;
3539
+ resolvedKey = key;
3540
+ optionsFilterRef = fnFilter;
3541
+ resolvedOpts = merged;
3542
+ optionsFilter = hasStore() ? merged.filter ?? null : compileOrPredicate(merged.filter);
3543
+ optionsSort = normalizeSortList(merged.sort);
3544
+ return true;
3545
+ }
3546
+ /**
3547
+ * Adopt a filter the consumer set on the source directly: anything the source
3548
+ * holds that is not what the grid last put there. One comparison is enough
3549
+ * because `writeFilter` records what the source HOLDS after a write, not what
3550
+ * was passed — so a source that ignores writes (a getter-only fake) reads back
3551
+ * as its own last write and is never adopted, and a source that normalises
3552
+ * compares against its normalised value.
3553
+ */
3554
+ function syncExternalFilter(s) {
3555
+ if (typeof s.filter !== "function") return false;
3556
+ const live = s.filter() ?? null;
3557
+ if (live === lastWrittenFilter) return false;
3558
+ externalBase = live;
3559
+ return true;
3560
+ }
3561
+ /** The same for `ds.sort(...)`. */
3562
+ function syncExternalSort(s) {
3563
+ if (typeof s.sort !== "function") return false;
3564
+ const live = s.sort() ?? null;
3565
+ if (live === lastWrittenSort) return false;
3566
+ lastWrittenSort = live;
3567
+ externalSort = normalizeSortList(live);
3568
+ return true;
3569
+ }
3570
+ /** options ∧ external ∧ explicit — everything under the grid's own layers. */
3571
+ function baseFilter() {
3572
+ return hasStore() ? andFilters(andFilters(optionsFilter, externalBase), explicitFilter) : andPredicates(optionsFilter, externalBase, explicitFilter);
3573
+ }
3574
+ /** What a server-group source gets: the base plus the column filters, NOT the search (it folds that itself). */
3575
+ function baseFilterWithColumns() {
3576
+ return hasStore() ? andFilters(baseFilter(), columnExpr) : andPredicates(baseFilter(), columnExpr);
3577
+ }
3578
+ function composeFilter() {
3579
+ const m = composeMemo;
3580
+ if (m && m.o === optionsFilter && m.e === externalBase && m.x === explicitFilter && m.g === gridExpr) return m.out;
3581
+ const out = hasStore() ? andFilters(baseFilter(), gridExpr) : andPredicates(baseFilter(), gridExpr);
3582
+ composeMemo = {
3583
+ o: optionsFilter,
3584
+ e: externalBase,
3585
+ x: explicitFilter,
3586
+ g: gridExpr,
3587
+ out
3588
+ };
3589
+ return out;
3590
+ }
3591
+ /** Write the composed filter — only when it is not what the source already holds. */
3592
+ function writeFilter(s) {
3593
+ if (typeof s.filter !== "function") return false;
3594
+ const next = composeFilter();
3595
+ if (next === lastComposed && (s.filter() ?? null) === lastWrittenFilter) return false;
3596
+ s.filter(next);
3597
+ lastComposed = next;
3598
+ lastWrittenFilter = s.filter() ?? null;
3599
+ return true;
3600
+ }
3601
+ /**
3602
+ * The sort actually sent: the user's column sort first (it wins), then the
3603
+ * options' sort, then whatever the consumer set on the source — later entries
3604
+ * only for selectors not already sorted, so they act as tiebreakers. Clearing
3605
+ * the column sort therefore falls back to the base instead of to nothing.
3606
+ */
3607
+ function mergedSort() {
3608
+ let keys = effectiveSort() ?? [];
3609
+ if (hasStore()) keys = keys.map((s) => {
3610
+ if (!isPath(s.selector)) return s;
3611
+ const selector = toODataSelector(s.selector);
3612
+ if (selector) return {
3613
+ ...s,
3614
+ selector
3615
+ };
3616
+ console.warn(`[monoDataGrid] sort: path "${s.selector}" is not an OData selector — skipped.`);
3617
+ return null;
3618
+ }).filter((s) => !!s);
3619
+ const out = [...keys];
3620
+ for (const e of [...optionsSort, ...externalSort]) if (!out.some((k) => k.selector === e.selector)) out.push({
3621
+ selector: e.selector,
3622
+ desc: !!e.desc
3623
+ });
3624
+ return out.length ? out : null;
3625
+ }
3626
+ /** Push the non-filter knobs of `resolvedOpts` onto the source. Only called when they changed. */
3627
+ function applyBaseOptions(s) {
3628
+ const o = resolvedOpts;
3629
+ const knob = (name, present, write) => {
3630
+ if (present) {
3631
+ write(false);
3632
+ appliedKnobs.add(name);
3633
+ } else if (appliedKnobs.has(name)) {
3634
+ write(true);
3635
+ appliedKnobs.delete(name);
3636
+ }
3637
+ };
3638
+ knob("select", o.select !== void 0, (clear) => s.select?.(clear ? null : toList(o.select) ?? null));
3639
+ knob("requireTotalCount", typeof o.requireTotalCount === "boolean", (clear) => s.requireTotalCount?.(clear ? false : !!o.requireTotalCount));
3640
+ if (!searchFieldsConfigured()) {
3641
+ knob("searchExpr", o.searchExpr !== void 0, (clear) => s.searchExpr?.(clear ? null : o.searchExpr));
3642
+ knob("searchOperation", typeof o.searchOperation === "string", (clear) => s.searchOperation?.(clear ? "contains" : String(o.searchOperation)));
3643
+ }
3644
+ if (preferredPageSize == null && typeof o.pageSize === "number" && o.pageSize > 0) s.pageSize?.(o.pageSize);
3645
+ const lo = typeof s.loadOptions === "function" ? s.loadOptions() : null;
3646
+ if (lo && typeof lo === "object") {
3647
+ const extras = extraLoadOptions(o);
3648
+ for (const k of Object.keys(extras)) knob(`lo:${k}`, extras[k] !== void 0, (clear) => {
3649
+ if (clear) delete lo[k];
3650
+ else lo[k] = extras[k];
3651
+ });
3652
+ for (const name of [...appliedKnobs]) if (name.startsWith("lo:") && !(name.slice(3) in extras)) {
3653
+ delete lo[name.slice(3)];
3654
+ appliedKnobs.delete(name);
3655
+ }
3656
+ }
3657
+ if (optionsSort.length || appliedKnobs.has("sort")) {
3658
+ applySourceSort();
3659
+ if (optionsSort.length) appliedKnobs.add("sort");
3660
+ else appliedKnobs.delete("sort");
3661
+ }
3662
+ }
3663
+ /**
3664
+ * The one call every load funnel makes first: re-read the options, adopt what
3665
+ * the consumer changed on the source, push the knobs, write the composed
3666
+ * filter. Never loads — the funnel does that — and writes nothing when nothing
3667
+ * moved, so a plain `setPage(n)` costs no filter write and keeps its page.
3668
+ */
3669
+ function applyBase(s) {
3670
+ const optChanged = resolveDsOptions();
3671
+ const extChanged = syncExternalFilter(s);
3672
+ const sortAdopted = syncExternalSort(s);
3673
+ if (optChanged) applyBaseOptions(s);
3674
+ else if (sortAdopted && (ctrl.sorts.length || optionsSort.length)) applySourceSort();
3675
+ if (optChanged || extChanged) {
3676
+ bumpSummary("search");
3677
+ groupPageIndex = 0;
3678
+ }
3679
+ writeFilter(s);
3680
+ return optChanged || extChanged;
3681
+ }
3682
+ /** The `MonoServerGroupCtx` for the current query. */
3683
+ function serverCtx() {
3684
+ const loadOptions = {};
3685
+ if (resolvedOpts.expand !== void 0) loadOptions.expand = resolvedOpts.expand;
3686
+ if (resolvedOpts.customQueryParams !== void 0) loadOptions.customQueryParams = resolvedOpts.customQueryParams;
3687
+ const filter = baseFilterWithColumns();
3688
+ const select = opts.select ?? toList(resolvedOpts.select);
3689
+ return {
3690
+ sort: mergedSort(),
3691
+ ...filter != null ? { filter } : {},
3692
+ ...select ? { select } : {},
3693
+ ...Object.keys(loadOptions).length ? { loadOptions } : {}
3694
+ };
3695
+ }
3696
+ async function setDataSourceOptions(next) {
3697
+ dsOptionsSource = next;
3698
+ await refresh();
3699
+ }
3700
+ async function setOdataOptions(next) {
3701
+ odataOptionsSource = next;
3702
+ await refresh();
3703
+ }
3704
+ async function refresh() {
3705
+ const s = bound;
3706
+ if (!s) return;
3707
+ if (applyBase(s)) s.pageIndex?.(0);
3708
+ await load();
3709
+ }
3710
+ function resolvedDataSourceOptions() {
3711
+ return resolvedOpts;
3712
+ }
3713
+ /** The panel's order: nulls first, numeric before lexical. Shared by both distinct paths. */
3714
+ function sortColumnValues(values) {
3715
+ return values.sort((a, b) => {
3716
+ const av = a.value, bv = b.value;
3717
+ if (av == null) return bv == null ? 0 : -1;
3718
+ if (bv == null) return 1;
3719
+ const an = Number(av), bn = Number(bv);
3720
+ if (!Number.isNaN(an) && !Number.isNaN(bn)) return an - bn;
3721
+ return String(av).localeCompare(String(bv));
3722
+ });
3723
+ }
3724
+ /**
3725
+ * Distinct values as ONE `$apply=groupby((field),aggregate($count as count))`
3726
+ * request through the store, or `null` when that is not on.
3727
+ *
3728
+ * `null` — not `[]` — is the fallback signal, and it comes from four places:
3729
+ * the path switched off (`serverApply: false`, or a backend that already
3730
+ * rejected an `$apply` — the gate remembers), a wildcard path (there is no
3731
+ * selector to group by), a store that cannot carry an `$apply` at all (a
3732
+ * CustomStore — `loadApply` sends nothing) or a request that failed, and a
3733
+ * response that is not grouped (a server that ignored the clause hands back
3734
+ * plain entities, which have no `count`). An EMPTY grouped result is a real
3735
+ * answer (nothing in scope) and is returned as such.
3736
+ *
3737
+ * The request carries `$apply` and nothing else: `$select` beside `$apply` is
3738
+ * rejected by most servers, a sibling `$filter` would be applied a second time
3739
+ * (the base folds INSIDE the clause), and `$top` only if the column asked for
3740
+ * a cap. `loadApply` puts the clause INTO the url (`urlOverride` —
3741
+ * `customQueryParams` is quoted as a literal by devextreme and becomes a
3742
+ * function call on v4), so this rides the store's URL, `beforeSend` and auth
3743
+ * exactly as every other request does.
3744
+ */
3745
+ async function distinctViaApply(store, field, options) {
3746
+ if (applyGate.skip) return null;
3747
+ if (parseFieldPath(field).hasWildcard) return null;
3748
+ const selector = isPath(field) ? toODataSelector(field) : field;
3749
+ if (!selector) return null;
3750
+ const apply = `groupby((${selector}),aggregate($count as count))`;
3751
+ const odataFilter = options?.filter != null ? arrayToODataString(options.filter) : "";
3752
+ const clause = odataFilter ? `filter(${odataFilter})/${apply}` : apply;
3753
+ const top = typeof options?.take === "number" && options.take >= 0 ? options.take : void 0;
3754
+ let rows;
3755
+ try {
3756
+ rows = await loadApply(store, clause, { top });
3757
+ } catch (err) {
3758
+ applyGate.reject(err);
3759
+ return null;
3760
+ }
3761
+ if (!rows) return null;
3762
+ if (!rows.length) return [];
3763
+ const first = rows[0];
3764
+ if (!first || typeof first !== "object" || typeof first.count !== "number") return null;
3765
+ if (!isRolledUp(rows, [field, "count"])) return null;
3766
+ const path = isPath(field);
3767
+ return sortColumnValues(rows.map((r) => ({
3768
+ value: path ? getFieldValue(r, field) : r[field],
3769
+ count: Number(r.count) || 0
3770
+ })));
3771
+ }
3772
+ /** Count occurrences of `field` across `rows` into a sorted `{value,count}[]`. */
3773
+ function distinctFromRows(rows, field) {
3774
+ const counts = /* @__PURE__ */ new Map();
3775
+ const bump = (v) => {
3776
+ counts.set(v, (counts.get(v) ?? 0) + 1);
3777
+ };
3778
+ const path = isPath(field);
3779
+ for (const r of rows) if (path) {
3780
+ const v = getFieldValue(r, field);
3781
+ if (Array.isArray(v)) v.forEach(bump);
3782
+ else bump(v);
3783
+ } else bump(r?.[field]);
3784
+ return sortColumnValues([...counts.entries()].map(([value, count]) => ({
3785
+ value,
3786
+ count
3787
+ })));
3788
+ }
3789
+ async function distinctValues(field, options) {
3790
+ if (!field) return [];
3791
+ resolveDsOptions();
3792
+ if (bound) syncExternalFilter(bound);
3793
+ const base = baseFilter();
3794
+ const cascade = opts.headerFilterCascade !== false;
3795
+ let filter;
3796
+ if (hasStore()) {
3797
+ filter = andFilters(base, options?.filter ?? null);
3798
+ if (cascade) {
3799
+ const search = remoteSearchExpr() ?? searchFilterOf(bound) ?? null;
3800
+ filter = andFilters(filter, andFilters(columnFilterExpr(field), search));
3801
+ }
3802
+ } else {
3803
+ filter = andPredicates(base, typeof options?.filter === "function" ? options.filter : null);
3804
+ if (cascade) filter = andPredicates(andPredicates(filter, columnFilterPredicate(field)), searchPredicate());
3805
+ }
3806
+ const scoped = filter == null ? options : {
3807
+ ...options ?? {},
3808
+ filter
3809
+ };
3810
+ if (typeof opts.distinctValues === "function") {
3811
+ const filter = hasStore() ? scoped?.filter ?? null : null;
3812
+ const odataFilter = filter != null ? arrayToODataString(filter) : "";
3813
+ const apply = `groupby((${isPath(field) ? toODataSelector(field) ?? field : field}),aggregate($count as count))`;
3814
+ const ctx = {
3815
+ filter,
3816
+ odataFilter,
3817
+ apply,
3818
+ applyWithFilter: odataFilter ? `filter(${odataFilter})/${apply}` : apply,
3819
+ source: bound
3820
+ };
3821
+ return (await opts.distinctValues(field, scoped, ctx) ?? []).map((r) => {
3822
+ const o = r;
3823
+ return o && typeof o === "object" && "value" in o ? {
3824
+ value: o.value,
3825
+ count: Number(o.count ?? 0) || 0
3826
+ } : {
3827
+ value: r,
3828
+ count: 0
3829
+ };
3830
+ });
3831
+ }
3832
+ const s = bound;
3833
+ if (!hasStore()) {
3834
+ let rows = s?.data?.() ?? ctrl.items;
3835
+ if (typeof scoped?.filter === "function") rows = rows.filter(scoped.filter);
3836
+ if (typeof scoped?.take === "number" && scoped.take >= 0) rows = rows.slice(0, scoped.take);
3837
+ return distinctFromRows(rows, field);
3838
+ }
3839
+ const store = s.store();
3840
+ const grouped = await distinctViaApply(store, field, scoped);
3841
+ if (grouped) return grouped;
3842
+ const loadOpts = {};
3843
+ if (!isPath(field)) loadOpts.select = [field];
3844
+ for (const [key, value] of Object.entries(scoped ?? {})) if (value == null) delete loadOpts[key];
3845
+ else loadOpts[key] = value;
3846
+ return distinctFromRows(storeRows(await store.load(loadOpts)), field);
3847
+ }
3848
+ async function setData(next) {
3849
+ const s = bound;
3850
+ if (typeof s?.setData !== "function") return;
3851
+ bumpSummary("data");
3852
+ await runLoad(() => s.setData(next));
3853
+ if (scrollActive()) await loadScrollPage(true);
3854
+ }
3855
+ /** The combine gesture, remembered — see `MonoSetSortOptions.sticky`. */
3856
+ let sortCombineMode = false;
3857
+ function sortCombining() {
3858
+ return sortCombineMode;
3859
+ }
3860
+ /** This column's direction, or null when it isn't part of the sort. */
3861
+ function sortOf(field) {
3862
+ return ctrl.sorts.find((s) => s.field === field)?.order ?? null;
3863
+ }
3864
+ /** 1-based precedence in `sorts`; `0` when the column isn't sorted. */
3865
+ function sortIndex(field) {
3866
+ return ctrl.sorts.findIndex((s) => s.field === field) + 1;
3867
+ }
3868
+ /**
3869
+ * The 1-based precedence a column DECLARED via `sort.index`, if any. Central
3870
+ * `props.th` config wins over the element's own `:sort.prop`, matching how every
3871
+ * other key resolves.
3872
+ */
3873
+ function declaredSortIndex(field) {
3874
+ const fromProps = (elementProps.th ?? []).find((c) => c?.field === field)?.sort;
3875
+ if (typeof fromProps === "object" && typeof fromProps?.index === "number") return fromProps.index;
3876
+ for (const el of columnEls) if (el.field === field && typeof el.sort === "object" && typeof el.sort?.index === "number") return el.sort.index;
3877
+ }
3878
+ /**
3879
+ * Re-order `sorts` so any column declaring `sort.index` sits at that slot.
3880
+ * Unindexed columns keep their click order and fill the remaining slots — the
3881
+ * comparator falls back to the current position, so the sort is stable.
3882
+ * No-ops (and allocates nothing) when no column declares an index.
3883
+ */
3884
+ function applyDeclaredSortOrder() {
3885
+ if (ctrl.sorts.length < 2) return;
3886
+ const marked = ctrl.sorts.map((s, i) => ({
3887
+ s,
3888
+ i,
3889
+ hint: declaredSortIndex(s.field)
3890
+ }));
3891
+ if (!marked.some((m) => typeof m.hint === "number")) return;
3892
+ marked.sort((a, b) => {
3893
+ const ah = a.hint ?? Number.POSITIVE_INFINITY;
3894
+ const bh = b.hint ?? Number.POSITIVE_INFINITY;
3895
+ return ah === bh ? a.i - b.i : ah - bh;
3896
+ });
3897
+ ctrl.sorts = marked.map((m) => m.s);
3898
+ }
3899
+ /** Keep the single-value mirrors pointing at the primary key. */
3900
+ function syncPrimarySort() {
3901
+ ctrl.sortField = ctrl.sorts[0]?.field ?? null;
3902
+ ctrl.sortOrder = ctrl.sorts[0]?.order ?? null;
3903
+ }
3904
+ function clearSort() {
3905
+ return setSort(null);
3906
+ }
3907
+ async function setSort(field, order, options) {
3908
+ if (!field) {
3909
+ ctrl.sorts = [];
3910
+ sortCombineMode = false;
3911
+ } else {
3912
+ const multi = options?.multi === true;
3913
+ const current = sortOf(field);
3914
+ const next = order !== void 0 ? order : current === "asc" ? "desc" : current === "desc" ? null : "asc";
3915
+ if (!next) ctrl.sorts = multi ? ctrl.sorts.filter((s) => s.field !== field) : [];
3916
+ else if (multi) ctrl.sorts = ctrl.sorts.findIndex((s) => s.field === field) >= 0 ? ctrl.sorts.map((s) => s.field === field ? {
3917
+ field,
3918
+ order: next
3919
+ } : s) : [...ctrl.sorts, {
3920
+ field,
3921
+ order: next
3922
+ }];
3923
+ else ctrl.sorts = [{
3924
+ field,
3925
+ order: next
3926
+ }];
3927
+ sortCombineMode = ctrl.sorts.length > 0 && multi && (options?.sticky === true || sortCombineMode);
3928
+ }
3929
+ applyDeclaredSortOrder();
3930
+ syncPrimarySort();
3931
+ notify();
3932
+ groupPageIndex = 0;
3933
+ if (serverMode()) {
3934
+ await loadGroupsServer();
3935
+ return;
3936
+ }
3937
+ const s = bound;
3938
+ if (!s) return;
3939
+ applySourceSort();
3940
+ s.pageIndex?.(0);
3941
+ await load();
3942
+ }
3943
+ async function setGroup(group) {
3944
+ groupFields = toFields(group);
3945
+ ctrl.grouped = groupFields.length > 0;
3946
+ collapsed.clear();
3947
+ groupRowPaging.clear();
3948
+ groupPageIndex = 0;
3949
+ if (serverMode()) {
3950
+ clearRowCaches();
3951
+ await loadGroupsServer();
3952
+ return;
3953
+ }
3954
+ const s = bound;
3955
+ if (!s) {
3956
+ sync();
3957
+ return;
3958
+ }
3959
+ applySourceSort();
3960
+ s.pageIndex?.(0);
3961
+ await load();
3962
+ }
3963
+ function toggleGroup(target) {
3964
+ const path = typeof target === "string" ? target : target?.path;
3965
+ if (!path) return;
3966
+ if (collapsed.has(path)) collapsed.delete(path);
3967
+ else collapsed.add(path);
3968
+ sync();
3969
+ if (serverMode()) fetchVisibleRows();
3970
+ }
3971
+ function isGroupCollapsed(path) {
3972
+ return collapsed.has(path);
3973
+ }
3974
+ function expandAllGroups() {
3975
+ if (!collapsed.size) return;
3976
+ collapsed.clear();
3977
+ sync();
3978
+ if (serverMode()) fetchVisibleRows();
3979
+ }
3980
+ function collapseAllGroups() {
3981
+ if (!groupFields.length) return;
3982
+ if (serverMode()) {
3983
+ for (const meta of serverGroups) collapsed.add(String(meta.key));
3984
+ sync();
3985
+ return;
3986
+ }
3987
+ const tree = buildGroups(flattenLeaves(bound ? [...bound.items?.() ?? []] : []), groupFields);
3988
+ for (const path of collectGroupPaths(tree)) collapsed.add(path);
3989
+ sync();
3990
+ }
3991
+ function setGroupPageSize(path, pageSize) {
3992
+ if (!path) return;
3993
+ const size = Math.max(1, Math.floor(pageSize) || 1);
3994
+ const cur = groupRowPaging.get(path);
3995
+ if (cur && cur.pageSize === size) return;
3996
+ groupRowPaging.set(path, {
3997
+ pageIndex: 0,
3998
+ pageSize: size
3999
+ });
4000
+ if (serverMode()) {
4001
+ groupRowsKey.delete(path);
4002
+ sync();
4003
+ fetchVisibleRows();
4004
+ return;
4005
+ }
4006
+ sync();
4007
+ }
4008
+ function setGroupPage(path, pageIndex) {
4009
+ if (!path) return;
4010
+ const pageSize = groupRowPaging.get(path)?.pageSize ?? (defaultGroupRowPageSize || groupPageSize);
4011
+ groupRowPaging.set(path, {
4012
+ pageIndex: Math.max(0, pageIndex),
4013
+ pageSize
4014
+ });
4015
+ if (serverMode()) {
4016
+ groupRowsKey.delete(path);
4017
+ sync();
4018
+ fetchVisibleRows();
4019
+ return;
4020
+ }
4021
+ sync();
4022
+ }
4023
+ function clearGroupPaging(path) {
4024
+ if (groupRowPaging.delete(path)) if (serverMode()) {
4025
+ groupRowsKey.delete(path);
4026
+ sync();
4027
+ fetchVisibleRows();
4028
+ } else sync();
4029
+ }
4030
+ function groupPageInfo(path) {
4031
+ if (pageSizeAll) {
4032
+ const node = pathIndex.get(path);
4033
+ const total = serverMode() ? serverGroups.find((g) => String(g.key) === path)?.count ?? 0 : node?.items.length ?? 0;
4034
+ return {
4035
+ pageIndex: 0,
4036
+ pageCount: 1,
4037
+ pageSize: Math.max(1, total),
4038
+ total
4039
+ };
4040
+ }
4041
+ if (serverMode()) {
4042
+ const total = serverGroups.find((g) => String(g.key) === path)?.count ?? 0;
4043
+ const pg = groupRowPagingFor(path);
4044
+ const pageSize = Math.max(1, pg?.pageSize ?? (defaultGroupRowPageSize || total || 1));
4045
+ const pageCount = Math.max(1, Math.ceil(total / pageSize));
4046
+ return {
4047
+ pageIndex: Math.min(Math.max(0, pg?.pageIndex ?? 0), pageCount - 1),
4048
+ pageCount,
4049
+ pageSize,
4050
+ total
4051
+ };
4052
+ }
4053
+ const node = pathIndex.get(path);
4054
+ const total = node ? node.items.length : 0;
4055
+ const pg = groupRowPagingFor(path);
4056
+ const pageSize = Math.max(1, pg?.pageSize ?? total ?? 1);
4057
+ const pageCount = Math.max(1, Math.ceil(total / pageSize));
4058
+ return {
4059
+ pageIndex: Math.min(Math.max(0, pg?.pageIndex ?? 0), pageCount - 1),
4060
+ pageCount,
4061
+ pageSize,
4062
+ total
4063
+ };
4064
+ }
4065
+ function groupNode(path) {
4066
+ return pathIndex.get(path);
4067
+ }
4068
+ /**
4069
+ * Every row matching the current query, without disturbing what's on screen.
4070
+ *
4071
+ * The chunked read itself lives in `readAllRows` (shared with the report
4072
+ * engine's `data` resolver), which also reconstructs the DataSource's active
4073
+ * **search** — `searchValue` is a DataSource-level option the store knows
4074
+ * nothing about, so reading the store directly would otherwise hand back rows
4075
+ * the user filtered away.
4076
+ *
4077
+ * `suppressSync` wraps the call because an array-backed source has to be
4078
+ * re-paged to be read: the detour fires `changed`, and subscribers must not
4079
+ * see the intermediate full-list state.
4080
+ */
4081
+ async function getData() {
4082
+ const s = bound;
4083
+ if (!s) return [];
4084
+ suppressSync = true;
4085
+ try {
4086
+ return await readAllRows(s, {
4087
+ chunkSize,
4088
+ select: opts.select ?? toList(resolvedOpts.select),
4089
+ sort: mergedSort()
4090
+ });
4091
+ } finally {
4092
+ suppressSync = false;
4093
+ }
4094
+ }
4095
+ /**
4096
+ * Group `rows` (or everything {@link getData} returns) by the grid's own
4097
+ * group fields. Named `buildGroupTree` internally so it doesn't shadow the
4098
+ * imported `buildGroups` helper that `syncGrouped` and friends rely on.
4099
+ */
4100
+ async function buildGroupTree(rows) {
4101
+ if (!groupFields.length) return [];
4102
+ return buildGroups(rows ?? await getData(), groupFields);
4103
+ }
4104
+ async function importSheet(options) {
4105
+ const { importTable } = await import("./import.js");
4106
+ return importTable(options, {
4107
+ getData,
4108
+ rowKeyOf,
4109
+ serverKeyOf: (row) => row?.[keyExpr] ?? rowKeyOf(row, -1),
4110
+ stageImported: (changes) => {
4111
+ for (const change of changes) {
4112
+ const patch = pending.get(change.rowKey) ?? {};
4113
+ Object.assign(patch, change.patch);
4114
+ pending.set(change.rowKey, patch);
4115
+ markImported(change.rowKey, Object.keys(change.patch));
4116
+ }
4117
+ notify();
4118
+ }
4119
+ });
4120
+ }
4121
+ async function exportReport(options) {
4122
+ const { exportTable } = await import("./export.js");
4123
+ return exportTable(options);
4124
+ }
4125
+ function subscribe(cb) {
4126
+ return notifier.subscribe(cb);
4127
+ }
4128
+ function dispose() {
4129
+ detach();
4130
+ removeOutsideExit();
4131
+ unbindRowTrigger();
4132
+ clearRowCaches();
4133
+ formOps.length = 0;
4134
+ formHandle = null;
4135
+ ctrl.filterBuilder?.dispose();
4136
+ notifier.clear();
4137
+ }
4138
+ if (ds) bind(ds);
4139
+ return ctrl;
4140
+ }
4141
+ //#endregion
4142
+ export { seedFromRanges as a, DEFAULT_ERROR_MESSAGES as c, normalizeError as d, resolveErrorMessages as f, minimize as i, describeError as l, buildDateTree as n, setAllChecked as o, setErrorMessages as p, checkState as r, setChecked as s, monoDataGrid as t, errorStatus as u };