@mono-lit/helper 0.0.0-stage → 0.0.2

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