@mono-lit/helper 0.0.9 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/dist/accordion-core-DaNyPWRr.js +277 -0
  2. package/dist/alert-core-DWtHbX_-.js +134 -0
  3. package/dist/breadcrumb-list-core-Dh68T8Cf.js +747 -0
  4. package/dist/{mono-button-CLkHg3WW.js → button-core-DerXg84Q.js} +3 -276
  5. package/dist/button-dropdown-core-DKYEOyjy.js +654 -0
  6. package/dist/button-utils-D5-66M02.js +63 -0
  7. package/dist/card-utils-l80qAmrN.js +312 -0
  8. package/dist/chart-core-DV7hD3ZY.js +801 -0
  9. package/dist/checkbox-utils-DKdrhAzF.js +337 -0
  10. package/dist/chip-utils-DpRrd7Zj.js +403 -0
  11. package/dist/{data-source-controller-C6C4ej_M.js → data-source-controller-DS_JLrmS.js} +1 -1
  12. package/dist/{data-source-options-fxT5QIX4.js → data-source-options-DJUNU26C.js} +6 -6
  13. package/dist/{data-source-read-BL266IW0.js → data-source-read-uKJpRAzx.js} +2 -2
  14. package/dist/date-core-CyuSyvvG.js +734 -0
  15. package/dist/drawer-core-DbFWvNln.js +759 -0
  16. package/dist/dropdown-core-BCH1pK14.js +511 -0
  17. package/dist/dropdown-table-core-SfHUJVFq.js +1342 -0
  18. package/dist/export.js +1 -1
  19. package/dist/{field-path-BabiMrrA.js → field-path-DK2VBWHl.js} +1 -1
  20. package/dist/field-tooltip.css_raw-DVblekCl.js +5 -0
  21. package/dist/{field-validation.css_raw-Cx6SGrfP.js → field-validation.css_raw-BU7C6173.js} +1 -170
  22. package/dist/file-upload-core-Cz_dBQMx.js +624 -0
  23. package/dist/filter-builder-core-DyWjbBP7.js +381 -0
  24. package/dist/{filter-builder-B6Y2mmbM.js → filter-builder-vbqIuPhX.js} +1 -1
  25. package/dist/{filter-eval-ChDSaAUH.js → filter-eval-CqeBnMYh.js} +1 -1
  26. package/dist/{filter-odata-Bi27XUpQ.js → filter-odata-GsczYKNC.js} +1 -1
  27. package/dist/{hydration-flush-BaE9gN3C.js → hydration-flush-Volt9X7Q.js} +2 -1071
  28. package/dist/index.js +11 -11
  29. package/dist/{index.node-DTqDBca7.js → index.node-Csy9zrr1.js} +4 -4
  30. package/dist/index.node.js +11 -11
  31. package/dist/input-utils-BglY-k0H.js +1145 -0
  32. package/dist/menu-core-DkAYuCZ2.js +831 -0
  33. package/dist/modal-core-CSBSPN_m.js +844 -0
  34. package/dist/mono-button-DVvZ03P-.js +280 -0
  35. package/dist/{mono-button.shadow-DqZMpcqY.js → mono-button.shadow-BANxMGln.js} +6 -594
  36. package/dist/{mono-data-chart-NKPdAgVq.js → mono-data-chart-ChF4J9RT.js} +6 -6
  37. package/dist/{mono-data-dropdown-B2iRpJuI.js → mono-data-dropdown-Bgj2PDfm.js} +1 -1
  38. package/dist/{mono-data-grid-llJCX9MC.js → mono-data-grid-DJkp96N-.js} +144 -46
  39. package/dist/{mono-event-BPku9HpS.js → mono-event-CAZf0F_k.js} +14 -9
  40. package/dist/{mono-form-controller-BrA10erm.js → mono-form-controller-DxZLvyg6.js} +2 -1
  41. package/dist/mono-table-checkbox-core-BKOMx8R5.js +5288 -0
  42. package/dist/nav-core-B5qU2dzn.js +225 -0
  43. package/dist/{normalize-error-D93O4q8g.js → normalize-error-BEcd1tmS.js} +2 -2
  44. package/dist/nuxt.js +1 -1
  45. package/dist/otp-core-DXg3vFLh.js +758 -0
  46. package/dist/radio-utils-Dk9Tl1jz.js +319 -0
  47. package/dist/rich-text-editor-core-Ddd_QaLn.js +1280 -0
  48. package/dist/select-utils-BgArYHk8.js +2140 -0
  49. package/dist/sidebar-core-CD6txDo3.js +583 -0
  50. package/dist/slider-core-vcWr321-.js +1014 -0
  51. package/dist/src/components/dropdown-table/dropdown-table-core.d.ts +2 -2
  52. package/dist/src/components/dropdown-table/dropdown-table-types.d.ts +1 -1
  53. package/dist/src/components/dropdown-table/index.d.ts +1 -1
  54. package/dist/src/components/dropdown-table/index.shadow.d.ts +1 -1
  55. package/dist/src/components/dropdown-table/mono-data-dropdown.d.ts +1 -1
  56. package/dist/src/components/form/form-types.d.ts +9 -3
  57. package/dist/src/components/input/input-core.d.ts +1 -1
  58. package/dist/src/components/input/input-types.d.ts +1 -1
  59. package/dist/src/components/otp/otp-core.d.ts +1 -1
  60. package/dist/src/components/select/select-core.d.ts +1 -1
  61. package/dist/src/components/select/select-types.d.ts +1 -1
  62. package/dist/src/components/slider/slider-core.d.ts +1 -1
  63. package/dist/src/components/slider/slider-types.d.ts +1 -1
  64. package/dist/src/components/table/filter-suggestion.d.ts +171 -0
  65. package/dist/src/components/table/index.d.ts +5 -4
  66. package/dist/src/components/table/index.shadow.d.ts +5 -4
  67. package/dist/src/components/table/mono-data-grid.d.ts +22 -3
  68. package/dist/src/components/table/mono-table-empty-core.d.ts +37 -0
  69. package/dist/src/components/table/mono-table-search-core.d.ts +22 -3
  70. package/dist/src/components/table/table-types.d.ts +74 -4
  71. package/dist/src/components/tag-input/tag-input-core.d.ts +1 -1
  72. package/dist/src/components/tag-input/tag-input-types.d.ts +1 -1
  73. package/dist/src/components/textarea/textarea-core.d.ts +1 -1
  74. package/dist/src/composables/mono-event.d.ts +7 -1
  75. package/dist/src/entries/index.d.ts +8 -6
  76. package/dist/src/entries/index.node.d.ts +8 -6
  77. package/dist/src/search/index.d.ts +1 -1
  78. package/dist/src/utils/group-count.d.ts +36 -0
  79. package/dist/switch-utils-CTZ4id2V.js +338 -0
  80. package/dist/tabs-core-r5-B8k_N.js +317 -0
  81. package/dist/tag-input-utils-D2JA90DQ.js +3060 -0
  82. package/dist/textarea-utils-VU1KeXm8.js +821 -0
  83. package/dist/ui/accordion.js +2 -274
  84. package/dist/ui/alert.js +2 -131
  85. package/dist/ui/breadcrumb.js +4 -745
  86. package/dist/ui/button-dropdown.js +4 -652
  87. package/dist/ui/button.js +2 -63
  88. package/dist/ui/card.js +2 -310
  89. package/dist/ui/chart.js +4 -800
  90. package/dist/ui/checkbox.js +4 -335
  91. package/dist/ui/chip.js +4 -402
  92. package/dist/ui/date.js +3 -732
  93. package/dist/ui/drawer.js +3 -757
  94. package/dist/ui/dropdown-table.js +5 -1341
  95. package/dist/ui/dropdown.js +4 -509
  96. package/dist/ui/file-upload.js +3 -622
  97. package/dist/ui/filter.js +5 -380
  98. package/dist/ui/form.js +1 -1
  99. package/dist/ui/index.css +118 -1
  100. package/dist/ui/input.js +3 -1141
  101. package/dist/ui/menu.js +4 -828
  102. package/dist/ui/modal.js +4 -843
  103. package/dist/ui/nav.js +4 -223
  104. package/dist/ui/otp.js +4 -756
  105. package/dist/ui/radio.js +4 -317
  106. package/dist/ui/rich-text-editor.js +4 -1278
  107. package/dist/ui/select.js +5 -2138
  108. package/dist/ui/shadow/accordion.js +4 -273
  109. package/dist/ui/shadow/alert.js +4 -130
  110. package/dist/ui/shadow/breadcrumb.js +5 -744
  111. package/dist/ui/shadow/button-dropdown.js +6 -652
  112. package/dist/ui/shadow/button.js +3 -63
  113. package/dist/ui/shadow/card.js +4 -309
  114. package/dist/ui/shadow/chart.js +6 -1576
  115. package/dist/ui/shadow/checkbox.js +4 -334
  116. package/dist/ui/shadow/chip.js +4 -396
  117. package/dist/ui/shadow/date.js +7 -731
  118. package/dist/ui/shadow/drawer.js +4 -756
  119. package/dist/ui/shadow/dropdown-table.js +8 -1762
  120. package/dist/ui/shadow/dropdown.js +4 -508
  121. package/dist/ui/shadow/file-upload.js +4 -620
  122. package/dist/ui/shadow/filter.js +7 -379
  123. package/dist/ui/shadow/input.js +6 -1140
  124. package/dist/ui/shadow/menu.js +5 -828
  125. package/dist/ui/shadow/modal.js +6 -1063
  126. package/dist/ui/shadow/nav.js +5 -223
  127. package/dist/ui/shadow/otp.js +6 -754
  128. package/dist/ui/shadow/radio.js +4 -316
  129. package/dist/ui/shadow/rich-text-editor.js +4 -1276
  130. package/dist/ui/shadow/select.js +6 -2136
  131. package/dist/ui/shadow/sidebar.js +5 -581
  132. package/dist/ui/shadow/slider.js +5 -1010
  133. package/dist/ui/shadow/switch.js +4 -335
  134. package/dist/ui/shadow/table.js +10 -4501
  135. package/dist/ui/shadow/tabs.js +6 -315
  136. package/dist/ui/shadow/tag-input.js +7 -3057
  137. package/dist/ui/shadow/textarea.js +6 -817
  138. package/dist/ui/sidebar.js +3 -581
  139. package/dist/ui/slider.js +4 -1012
  140. package/dist/ui/switch.js +4 -336
  141. package/dist/ui/table.js +9 -4502
  142. package/dist/ui/tabs.js +4 -315
  143. package/dist/ui/tag-input.js +5 -3058
  144. package/dist/ui/textarea.js +4 -819
  145. package/dist/{vite-CivRCJaf.js → vite-CfW7z2wn.js} +21 -14
  146. package/dist/vite.js +1 -1
  147. package/dist/vue.d.ts +3 -2
  148. package/package.json +10 -11
  149. package/dist/chip-strip-bNfLn52Z.js +0 -215
  150. package/dist/data-source-controller-Dx6IQ2CN.js +0 -243
  151. package/dist/data-source-options-BM4r4PRw.js +0 -558
  152. package/dist/data-source-read-HyBCRg1t.js +0 -242
  153. package/dist/excel-BXzr3uI1.js +0 -784
  154. package/dist/export-ixRdCS2y.js +0 -2388
  155. package/dist/field-tooltip-addon-CWCp19mJ.js +0 -702
  156. package/dist/field-tooltip.css_raw-U5UeTa2d.js +0 -269
  157. package/dist/filter-builder-C0T3pMx2.js +0 -208
  158. package/dist/form-control-core-BbiACMtL.js +0 -226
  159. package/dist/icon-ClxnYhBG.js +0 -19
  160. package/dist/import-Dc4eXC02.js +0 -612
  161. package/dist/light-slots-_no0r2Z-.js +0 -246
  162. package/dist/markdown-uGlE_OfJ.js +0 -20191
  163. package/dist/mono-data-grid-e9ZVBlNN.js +0 -4240
  164. package/dist/normalize-error-BmLbgANw.js +0 -1048
@@ -1,558 +0,0 @@
1
- import { n as isPath, r as mergePatch, t as getFieldValue } from "./field-path-BabiMrrA.js";
2
- import { D as compileFilterPredicate, E as andPredicates, T as andFilters, f as mergeSearchFields } from "./normalize-error-BmLbgANw.js";
3
- import { i as searchFilterOf, s as flattenLeaves, t as isDataSourceLike } from "./data-source-read-HyBCRg1t.js";
4
- import { t as resolveMaybeReactive } from "./reactive-Dtd-esHO.js";
5
- import { s as odataStringToArray, t as arrayToODataString } from "./filter-odata-Bi27XUpQ.js";
6
-
7
- //#region src/components/table/array-source.ts
8
- /** Normalise a possibly-grouped input array to flat leaf rows when grouping. */
9
- function normalizeInput(data, group) {
10
- return (Array.isArray(group) ? group.length > 0 : !!group) ? flattenLeaves(data) : [...data];
11
- }
12
- /**
13
- * Wrap a plain array in the structural {@link MonoGridSource} shape so
14
- * {@link monoDataGrid} can drive it with no devextreme DataSource — paging,
15
- * search and filtering all run client-side, in memory.
16
- *
17
- * @example
18
- * const source = monoArraySource(rows, { pageSize: 10, searchValue: ['name'] })
19
- * const table = monoDataGrid(source)
20
- * await table.load()
21
- * // later: source.setData(nextRows) // swap the underlying array
22
- */
23
- function monoArraySource(data = [], opts = {}) {
24
- const handlers = {
25
- changed: [],
26
- loadingChanged: [],
27
- loadError: []
28
- };
29
- let all = normalizeInput(data, opts.group);
30
- let pageIndex = 0;
31
- let pageSize = opts.pageSize ?? 10;
32
- let paginate = true;
33
- let search = null;
34
- let searchExpr = mergeSearchFields(opts)?.filter((e) => typeof e === "string") ?? null;
35
- let searchOperation = "contains";
36
- let filter = null;
37
- let sortList = [];
38
- let page = [];
39
- /**
40
- * `applied()` as of the last `load()`, or null once a query setter has written since.
41
- *
42
- * `totalCount()` / `isLastPage()` re-ran the whole filter + search + SORT per call, and the
43
- * grid asks them on every sync — every virtual-scroll window shift, ~3 times per chunk of a
44
- * chunked "all" load. `load()` always recomputes (so an in-place edit of the data shows on the
45
- * next load) and stores it here; every setter that WRITES clears it.
46
- */
47
- let appliedCache = null;
48
- const matched = () => appliedCache ??= applied();
49
- /** Pass a setter's written value through, dropping the cached match set. */
50
- const moved = (v) => {
51
- appliedCache = null;
52
- return v;
53
- };
54
- const emit = (event, arg) => (handlers[event] ?? []).slice().forEach((h) => h(arg));
55
- /** Normalise a single devextreme sort entry into `{ selector, desc }`. */
56
- function normalizeSortEntry(value) {
57
- if (!value) return null;
58
- if (typeof value === "string") return {
59
- selector: value,
60
- desc: false
61
- };
62
- const o = value;
63
- return o.selector ? {
64
- selector: o.selector,
65
- desc: !!o.desc
66
- } : null;
67
- }
68
- /** Normalise the devextreme sort forms into an ordered multi-key list. */
69
- function normalizeSortList(value) {
70
- return (Array.isArray(value) ? value : value == null ? [] : [value]).map(normalizeSortEntry).filter((e) => !!e);
71
- }
72
- /** Read a sort/compare value; a wildcard path sorts best-effort by its first match. */
73
- function sortValue(row, selector) {
74
- if (!isPath(selector)) return row[selector];
75
- const v = getFieldValue(row, selector);
76
- return Array.isArray(v) ? v[0] : v;
77
- }
78
- /** Compare two rows by one selector (numeric when both sides parse). */
79
- function compareBy(a, b, selector, desc) {
80
- const av = sortValue(a, selector);
81
- const bv = sortValue(b, selector);
82
- const dir = desc ? -1 : 1;
83
- if (av == null && bv == null) return 0;
84
- if (av == null) return -dir;
85
- if (bv == null) return dir;
86
- const an = Number(av);
87
- const bn = Number(bv);
88
- const cmp = !Number.isNaN(an) && !Number.isNaN(bn) ? an - bn : String(av).localeCompare(String(bv));
89
- return cmp === 0 ? 0 : (cmp < 0 ? -1 : 1) * dir;
90
- }
91
- /** Apply filter + search + sort, returning the matching rows. */
92
- function applied() {
93
- let rows = all;
94
- if (typeof filter === "function") rows = rows.filter(filter);
95
- const cols = searchExpr == null ? [] : Array.isArray(searchExpr) ? searchExpr : [searchExpr];
96
- if (search && cols.length) {
97
- const needle = String(search).toLowerCase();
98
- const matches = (row, c) => {
99
- const raw = isPath(c) ? getFieldValue(row, c) : row[c];
100
- return (Array.isArray(raw) ? raw : [raw]).some((v) => String(v ?? "").toLowerCase().includes(needle));
101
- };
102
- rows = rows.filter((row) => cols.some((c) => matches(row, c)));
103
- }
104
- if (sortList.length) rows = [...rows].sort((a, b) => {
105
- for (const { selector, desc } of sortList) {
106
- const cmp = compareBy(a, b, selector, desc);
107
- if (cmp !== 0) return cmp;
108
- }
109
- return 0;
110
- });
111
- return rows;
112
- }
113
- const source = {
114
- on: (event, handler) => void (handlers[event] ??= []).push(handler),
115
- off: (event, handler) => {
116
- handlers[event] = (handlers[event] ?? []).filter((h) => h !== handler);
117
- },
118
- items: () => page,
119
- /** Full backing array (unfiltered) — lets the grid derive header-filter values. */
120
- data: () => [...all],
121
- isLoading: () => false,
122
- totalCount: () => matched().length,
123
- isLastPage: () => (pageIndex + 1) * pageSize >= matched().length,
124
- paginate: (value) => value === void 0 ? paginate : paginate = value,
125
- pageSize: (value) => value === void 0 ? pageSize : pageSize = value,
126
- pageIndex: (value) => value === void 0 ? pageIndex : pageIndex = value,
127
- searchValue: (value) => value === void 0 ? search : moved(search = value || null),
128
- searchExpr: (value) => value === void 0 ? searchExpr : moved(searchExpr = value ?? null),
129
- searchOperation: (op) => op === void 0 ? searchOperation : moved(searchOperation = op),
130
- filter: (value) => value === void 0 ? filter : moved(filter = typeof value === "function" ? value : null),
131
- sort: (value) => value === void 0 ? sortList : moved(sortList = normalizeSortList(value)),
132
- load: () => {
133
- emit("loadingChanged", true);
134
- const rows = appliedCache = applied();
135
- const start = pageIndex * pageSize;
136
- page = paginate ? rows.slice(start, start + pageSize) : rows.slice();
137
- emit("changed");
138
- emit("loadingChanged", false);
139
- return Promise.resolve(page);
140
- },
141
- reload: () => source.load(),
142
- /** Replace the backing array and reload from page 0. */
143
- setData: (next) => {
144
- all = normalizeInput(next, opts.group);
145
- appliedCache = null;
146
- pageIndex = 0;
147
- return Promise.resolve(source.load());
148
- },
149
- /**
150
- * Merge `values` into the row whose `keyExpr` field equals `key`, then reload
151
- * — the per-row write path used by `monoDataGrid.saveChanges()` for an
152
- * array-backed table (correct across pages, unlike a whole-array `setData`).
153
- */
154
- update: (key, values) => {
155
- const keyExpr = opts.keyExpr ?? "Id";
156
- const idx = all.findIndex((row) => String(row?.[keyExpr]) === String(key));
157
- if (idx >= 0) all[idx] = mergePatch(all[idx], values);
158
- appliedCache = null;
159
- return Promise.resolve(source.load());
160
- }
161
- };
162
- return source;
163
- }
164
-
165
- //#endregion
166
- //#region src/utils/data-source-apply.ts
167
- /** The store's OData version, or `null` for anything that is not an OData store. */
168
- function odataStoreVersion(store) {
169
- const s = store;
170
- if (!s || typeof s.version !== "function") return null;
171
- const v = Number(s.version());
172
- return Number.isFinite(v) ? v : null;
173
- }
174
- /** The store's base url, or `null` when it cannot be read. */
175
- function odataStoreUrl(store) {
176
- const s = store;
177
- const url = s?._requestDispatcher?.url ?? s?._url;
178
- return typeof url === "string" && url ? url : null;
179
- }
180
- /** `<url>?$apply=<encoded clause>` — `&` when the url already has a query string. */
181
- function applyRequestUrl(url, apply) {
182
- return `${url}${url.includes("?") ? "&" : "?"}$apply=${encodeURIComponent(apply)}`;
183
- }
184
- /**
185
- * The `store.load()` options that carry `apply`, or `null` when this store
186
- * cannot carry one (no request should be made).
187
- *
188
- * `requireTotalCount: false` keeps `$count` off — a rolled-up result has no
189
- * meaningful total, and `$count` beside `$apply` is rejected by some servers.
190
- */
191
- function applyLoadOptions(store, apply, options = {}) {
192
- if (odataStoreVersion(store) == null) return null;
193
- const url = odataStoreUrl(store);
194
- if (!url) return null;
195
- const hasTop = typeof options.top === "number" && options.top >= 0;
196
- return {
197
- urlOverride: applyRequestUrl(url, apply),
198
- requireTotalCount: false,
199
- ...hasTop ? { take: options.top } : {}
200
- };
201
- }
202
- /** Normalise a devextreme store `load` result into a plain rows array. */
203
- function storeRows(res) {
204
- return Array.isArray(res) ? res : res?.data ?? [];
205
- }
206
- /**
207
- * Send `apply` through `store` and return the rolled-up rows, or `null` when
208
- * the store cannot carry an `$apply` (nothing was requested). A request that
209
- * FAILS throws — the caller decides whether that disables the path for good
210
- * (see {@link createApplyGate}).
211
- */
212
- async function loadApply(store, apply, options = {}) {
213
- const loadOptions = applyLoadOptions(store, apply, options);
214
- if (!loadOptions) return null;
215
- return storeRows(await store.load(loadOptions));
216
- }
217
- /**
218
- * Whether `rows` are what an honoured `$apply` returns — each row carrying
219
- * ONLY the grouping key(s) and the aggregate aliases in `keys` (plus OData
220
- * `@odata.*` annotations) — as opposed to plain entities from a server that
221
- * ignored the clause. The distinction matters because an alias is usually the
222
- * field's own name: an entity `{ Id, Dept, Total }` has the alias `Total` too,
223
- * and would be mistaken for a bucket by a presence check. The extra `Id` is
224
- * what gives it away. `keys` are matched on their ROOT segment (`Job/Title`
225
- * comes back as `{ Job: { Title } }`).
226
- */
227
- function isRolledUp(rows, keys) {
228
- const allowed = new Set(keys.map((k) => k.split(/[./]/, 1)[0]));
229
- return rows.every((row) => !!row && typeof row === "object" && Object.keys(row).every((k) => k.startsWith("@") || allowed.has(k)));
230
- }
231
- /**
232
- * Whether a failed `$apply` request means the BACKEND does not do `$apply` —
233
- * a 4xx (`400 Bad Request`, `404`) or `501 Not Implemented` — as opposed to a
234
- * transient fault (network down: `httpStatus` 0 / absent, a 5xx). devextreme
235
- * puts the status on the error as `httpStatus`.
236
- */
237
- function isApplyRejected(err) {
238
- const status = Number(err?.httpStatus);
239
- if (!Number.isFinite(status)) return false;
240
- return status >= 400 && status < 500 || status === 501;
241
- }
242
- /**
243
- * Per-controller "is the `$apply` path still worth trying?" switch.
244
- *
245
- * A backend without `$apply` answers every attempt with the same 4xx, and a
246
- * grid that retried on every panel open / recompute would pay one failed
247
- * request each time before falling back. So the FIRST definite rejection is
248
- * remembered for the controller's lifetime; a transient failure is not, and
249
- * the next attempt goes out normally.
250
- */
251
- function createApplyGate(enabled) {
252
- let rejected = false;
253
- return {
254
- get skip() {
255
- return !enabled || rejected;
256
- },
257
- reject(err) {
258
- if (isApplyRejected(err)) rejected = true;
259
- }
260
- };
261
- }
262
-
263
- //#endregion
264
- //#region src/utils/data-source-summary.ts
265
- /**
266
- * Server-side column aggregates: the pieces a consumer needs to answer
267
- * "what are the totals?" without the grid draining every row to add them up.
268
- *
269
- * ── Resolver → store → drain ──
270
- *
271
- * A registered summary covers the FULL filtered set, not the current page, so
272
- * something has to see every matching row. Left to itself the grid drains the
273
- * bound source in `take: 100` chunks — free over an array, and pathological over
274
- * a server-paged table, where it re-fetches through the pager exactly the rows
275
- * the pager exists to avoid.
276
- *
277
- * The fix is to ask the server for the totals, and there are two ways in. The
278
- * grid's own: ONE `$apply=filter(…)/aggregate(…)` through the bound source's
279
- * STORE (`utils/data-source-apply`), which rides its url, `beforeSend` and auth.
280
- * That needs a devextreme `ODataStore` — a CustomStore has nothing to carry the
281
- * clause on, and **devextreme itself has no aggregate support**: `totalSummary`,
282
- * `groupSummary` and `$apply` appear nowhere in it, an unknown load option is
283
- * dropped in silence rather than refused (a `store.load({ totalSummary })` comes
284
- * back as ordinary rows), and `customQueryParams` are service-operation
285
- * parameters — quoted as literals, and on OData v4 a FUNCTION-INVOCATION url,
286
- * `Entity($apply='…')` — so the clause has to go into the url (`urlOverride`),
287
- * which is what `loadApply` does.
288
- *
289
- * The consumer's: a resolver. It is asked FIRST and covers what the store path
290
- * cannot — a fetcher of the consumer's own, a custom dialect, a cached answer.
291
- * The library builds the clause and hands it over; the consumer performs the
292
- * request with whatever it already uses. Declining (`null`) falls through to the
293
- * store path, and that declining falls through to the drain.
294
- */
295
- /**
296
- * mono's aggregate kinds → the OData v4 aggregation keyword.
297
- *
298
- * `countDistinct` maps to `countdistinct`, which OData does define — unlike the
299
- * devextreme summary descriptor, which has no equivalent at all. Building the
300
- * clause is free; whether a given backend implements it is the consumer's
301
- * problem, and declining is always safe.
302
- */
303
- const ODATA_AGG = {
304
- sum: "sum",
305
- avg: "average",
306
- min: "min",
307
- max: "max",
308
- count: "$count",
309
- countDistinct: "countdistinct"
310
- };
311
- /** A dotted path (`Job.Budget`) addresses a nav property as `Job/Budget` in OData. */
312
- const toODataPath = (field) => field.replaceAll(".", "/");
313
- /**
314
- * A field a server can aggregate.
315
- *
316
- * A WILDCARD or INDEX path (`Lines.[*].Total`) is rejected: `getFieldValue`
317
- * resolves those by walking a collection on a loaded row, and no `$apply`
318
- * expression means the same thing — the nearest OData construct aggregates the
319
- * collection per parent rather than across all of them.
320
- */
321
- const isAggregatableField = (field) => !!field && !field.includes("[") && !field.includes("*");
322
- /**
323
- * The response key each spec's value comes back under.
324
- *
325
- * Plain `field` while that field carries ONE aggregate, which is the ordinary
326
- * case and keeps the response readable; `field_type` once the same field has
327
- * two (a `sum` and an `avg` of `Price`), because an `$apply` cannot alias two
328
- * values to the same name. An explicit `name` always wins, and a bare `count`
329
- * has no field to name it after.
330
- */
331
- function summaryAliases(specs) {
332
- const seen = /* @__PURE__ */ new Map();
333
- for (const s of specs) {
334
- if (!s.field) continue;
335
- seen.set(s.field, (seen.get(s.field) ?? 0) + 1);
336
- }
337
- return specs.map((s, i) => {
338
- if (s.name) return s.name;
339
- if (!s.field) return `count_${i}`;
340
- return (seen.get(s.field) ?? 0) > 1 ? `${s.field}_${s.type ?? "sum"}` : s.field;
341
- });
342
- }
343
- /**
344
- * Build `aggregate(<field> with <agg> as <alias>, …)` for a set of specs.
345
- *
346
- * Returns `null` when no spec can be expressed — a caller with nothing to ask
347
- * for should not make a request.
348
- */
349
- function buildSummaryApply(specs) {
350
- const aliases = summaryAliases(specs);
351
- const parts = specs.map((spec, i) => {
352
- const agg = ODATA_AGG[spec.type ?? "sum"];
353
- if (!agg) return null;
354
- if (agg === "$count") return `$count as ${aliases[i]}`;
355
- if (!spec.field || !isAggregatableField(spec.field)) return null;
356
- return `${toODataPath(spec.field)} with ${agg} as ${aliases[i]}`;
357
- }).filter((p) => !!p);
358
- return parts.length ? `aggregate(${parts.join(",")})` : null;
359
- }
360
- /**
361
- * Compose a filter with an aggregate: `filter(<expr>)/aggregate(…)`.
362
- *
363
- * The filter goes INSIDE `$apply` rather than travelling as a sibling `$filter`
364
- * — that is what makes the two compose, since the filter runs first and the
365
- * aggregation sees only matching rows.
366
- */
367
- function composeSummaryApply(apply, filter) {
368
- const expr = arrayToODataString(filter);
369
- return expr ? `filter(${expr})/${apply}` : apply;
370
- }
371
- /**
372
- * The predicate a summary must aggregate over — IDENTICAL to what the drain uses.
373
- *
374
- * `readAllRows` reads a DataSource as its own `filter()` AND its reconstructed
375
- * `searchValue`, which is a DataSource-level option the store knows nothing
376
- * about. Anything less here and a total would quietly count rows the user had
377
- * searched away, which is worse than being slow.
378
- */
379
- function summaryFilterOf(source) {
380
- if (!isDataSourceLike(source)) return null;
381
- const ds = source;
382
- return andFilters(ds.filter?.() ?? null, searchFilterOf(ds) ?? null);
383
- }
384
-
385
- //#endregion
386
- //#region src/utils/data-source-options.ts
387
- /** The knobs with a meaning of their own; anything else is passed through as a load option. */
388
- const DATA_SOURCE_KNOBS = /* @__PURE__ */ new Set([
389
- "filter",
390
- "select",
391
- "expand",
392
- "sort",
393
- "customQueryParams",
394
- "paginate",
395
- "pageSize",
396
- "requireTotalCount",
397
- "searchExpr",
398
- "searchOperation"
399
- ]);
400
- /** A comma string or an array → a trimmed string list; `null`/`undefined` → `undefined`. */
401
- function toList(v) {
402
- if (v == null) return void 0;
403
- if (Array.isArray(v)) return v.map(String);
404
- return String(v).split(",").map((x) => x.trim()).filter(Boolean);
405
- }
406
- /** devextreme's sort forms (string / object / array / mixed) → an ordered entry list. */
407
- function normalizeSortList(value) {
408
- const arr = Array.isArray(value) ? value : value == null ? [] : [value];
409
- const out = [];
410
- for (const v of arr) {
411
- if (!v) continue;
412
- if (typeof v === "string") out.push({
413
- selector: v,
414
- desc: false
415
- });
416
- else if (typeof v === "object" && v.selector) out.push({
417
- selector: v.selector,
418
- desc: !!v.desc
419
- });
420
- }
421
- return out;
422
- }
423
- /** `"A desc, B"` → `[{A, desc}, {B}]`. */
424
- function parseOrderby(text) {
425
- return text.split(",").map((part) => part.trim()).filter(Boolean).map((part) => {
426
- const [selector, dir] = part.split(/\s+/);
427
- return {
428
- selector,
429
- desc: (dir ?? "").toLowerCase() === "desc"
430
- };
431
- });
432
- }
433
- /**
434
- * The parser emits OData keyword operators (`eq`, `ge`) — the filter builder's
435
- * tree reads either — but devextreme's ODataStore compiles ONLY the symbol forms
436
- * and throws `E4003` on a keyword. Rewritten recursively.
437
- */
438
- function toSymbolOps(expr) {
439
- if (!Array.isArray(expr)) return expr;
440
- const SYM = {
441
- eq: "=",
442
- ne: "<>",
443
- gt: ">",
444
- ge: ">=",
445
- lt: "<",
446
- le: "<="
447
- };
448
- if (expr.length === 3 && typeof expr[0] === "string" && typeof expr[1] === "string" && !Array.isArray(expr[2])) {
449
- const op = SYM[expr[1].toLowerCase()];
450
- return op ? [
451
- expr[0],
452
- op,
453
- expr[2]
454
- ] : expr;
455
- }
456
- return expr.map((part) => Array.isArray(part) ? toSymbolOps(part) : part);
457
- }
458
- /**
459
- * A raw `$filter` → devextreme's array form, so the store compiles it TOGETHER
460
- * with whatever the component adds. The parser throws on syntax it does not know
461
- * (lambdas, ISO date literals); those go through as a one-element raw clause,
462
- * which devextreme emits as `(expr) eq true` — valid OData, with the caveat that
463
- * it rewrites `.` to `/` inside the text.
464
- */
465
- function parseOdataFilter(raw) {
466
- const text = raw.trim();
467
- if (!text) return null;
468
- try {
469
- return toSymbolOps(odataStringToArray(text) ?? null);
470
- } catch {
471
- return [`(${text})`];
472
- }
473
- }
474
- /** `{ $select, $expand, $orderby, $filter, …custom }` → the DataSource shape. */
475
- function normalizeOdataOptions(o) {
476
- const out = {};
477
- const custom = {};
478
- for (const [k, v] of Object.entries(o)) {
479
- if (v === void 0) continue;
480
- if (k === "$select") out.select = toList(v);
481
- else if (k === "$expand") out.expand = Array.isArray(v) ? v.map(String) : String(v);
482
- else if (k === "$orderby") out.sort = parseOrderby(String(v));
483
- else if (k === "$filter") {
484
- const f = parseOdataFilter(String(v));
485
- if (f != null) out.filter = f;
486
- } else custom[k] = v;
487
- }
488
- if (Object.keys(custom).length) out.customQueryParams = custom;
489
- return out;
490
- }
491
- /**
492
- * `a` ∧ `b`: filters AND-ed (predicates and arrays each on their own terms),
493
- * `select`/`expand` unioned, sort de-duplicated by selector with `a` leading,
494
- * `customQueryParams` merged with `b` winning, scalars `b` if defined else `a`.
495
- */
496
- function mergeDataSourceOptions(a, b) {
497
- const out = {
498
- ...a,
499
- ...b
500
- };
501
- if (a.filter != null && b.filter != null) out.filter = typeof a.filter === "function" || typeof b.filter === "function" ? andPredicates(typeof a.filter === "function" ? a.filter : compileFilterPredicate(a.filter), typeof b.filter === "function" ? b.filter : compileFilterPredicate(b.filter)) : andFilters(a.filter, b.filter);
502
- else out.filter = a.filter ?? b.filter;
503
- const union = (x, y) => {
504
- const xs = toList(x), ys = toList(y);
505
- if (!xs && !ys) return void 0;
506
- return [.../* @__PURE__ */ new Set([...xs ?? [], ...ys ?? []])];
507
- };
508
- if (a.select !== void 0 || b.select !== void 0) out.select = union(a.select, b.select);
509
- if (a.expand !== void 0 || b.expand !== void 0) {
510
- const nested = (v) => typeof v === "string" && v.includes("(");
511
- out.expand = nested(a.expand) || nested(b.expand) ? b.expand ?? a.expand : union(a.expand, b.expand);
512
- }
513
- if (a.sort !== void 0 || b.sort !== void 0) {
514
- const as = normalizeSortList(a.sort);
515
- const bs = normalizeSortList(b.sort).filter((e) => !as.some((x) => x.selector === e.selector));
516
- out.sort = [...as, ...bs];
517
- }
518
- if (a.customQueryParams || b.customQueryParams) out.customQueryParams = {
519
- ...a.customQueryParams ?? {},
520
- ...b.customQueryParams ?? {}
521
- };
522
- return out;
523
- }
524
- /**
525
- * Read both option bags fresh and merge them — the one call a controller makes
526
- * at the top of every query. Returns a NEW object every time (a getter hands
527
- * back fresh state), so callers that need change detection compare by content:
528
- * see {@link dataSourceOptionsKey}.
529
- */
530
- function resolveDataSourceOptions(dataSourceOptions, odataOptions) {
531
- const ds = resolveMaybeReactive(dataSourceOptions) ?? {};
532
- const od = resolveMaybeReactive(odataOptions);
533
- return od ? mergeDataSourceOptions(ds, normalizeOdataOptions(od)) : { ...ds };
534
- }
535
- /**
536
- * A content key for change detection. Functions (an array-source predicate) do
537
- * not stringify, so they are marked and must be compared by reference by the
538
- * caller; a circular object yields a fresh random key and so always counts as
539
- * changed.
540
- */
541
- function dataSourceOptionsKey(o) {
542
- try {
543
- return JSON.stringify(o, (_k, v) => typeof v === "function" ? "[fn]" : v);
544
- } catch {
545
- return String(Math.random());
546
- }
547
- }
548
- /** `expand` / `customQueryParams` / unknown keys — what goes onto a `store.load()` beside the knobs. */
549
- function extraLoadOptions(o) {
550
- const out = {};
551
- for (const [k, v] of Object.entries(o)) if (!DATA_SOURCE_KNOBS.has(k) && v !== void 0) out[k] = v;
552
- if (o.expand !== void 0) out.expand = o.expand;
553
- if (o.customQueryParams !== void 0) out.customQueryParams = o.customQueryParams;
554
- return out;
555
- }
556
-
557
- //#endregion
558
- export { resolveDataSourceOptions as a, composeSummaryApply as c, createApplyGate as d, isRolledUp as f, monoArraySource as h, normalizeSortList as i, summaryAliases as l, storeRows as m, extraLoadOptions as n, toList as o, loadApply as p, mergeDataSourceOptions as r, buildSummaryApply as s, dataSourceOptionsKey as t, summaryFilterOf as u };