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