@mono-lit/helper 0.0.8 → 0.0.10

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 (161) 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-CLqE8F8P.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-DUXo_jxd.js → mono-data-dropdown-Bgj2PDfm.js} +1 -1
  38. package/dist/{mono-data-grid-Bb00QM72.js → mono-data-grid-DJkp96N-.js} +346 -51
  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/otp-core-DXg3vFLh.js +758 -0
  45. package/dist/radio-utils-Dk9Tl1jz.js +319 -0
  46. package/dist/rich-text-editor-core-Ddd_QaLn.js +1280 -0
  47. package/dist/select-utils-BgArYHk8.js +2140 -0
  48. package/dist/sidebar-core-CD6txDo3.js +583 -0
  49. package/dist/slider-core-vcWr321-.js +1014 -0
  50. package/dist/src/components/dropdown-table/dropdown-table-core.d.ts +2 -2
  51. package/dist/src/components/dropdown-table/dropdown-table-types.d.ts +1 -1
  52. package/dist/src/components/dropdown-table/index.d.ts +1 -1
  53. package/dist/src/components/dropdown-table/index.shadow.d.ts +1 -1
  54. package/dist/src/components/dropdown-table/mono-data-dropdown.d.ts +1 -1
  55. package/dist/src/components/form/form-types.d.ts +9 -3
  56. package/dist/src/components/input/input-core.d.ts +1 -1
  57. package/dist/src/components/input/input-types.d.ts +1 -1
  58. package/dist/src/components/otp/otp-core.d.ts +1 -1
  59. package/dist/src/components/select/select-core.d.ts +1 -1
  60. package/dist/src/components/select/select-types.d.ts +1 -1
  61. package/dist/src/components/slider/slider-core.d.ts +1 -1
  62. package/dist/src/components/slider/slider-types.d.ts +1 -1
  63. package/dist/src/components/table/filter-suggestion.d.ts +171 -0
  64. package/dist/src/components/table/index.d.ts +5 -4
  65. package/dist/src/components/table/index.shadow.d.ts +5 -4
  66. package/dist/src/components/table/mono-data-grid.d.ts +58 -3
  67. package/dist/src/components/table/mono-table-empty-core.d.ts +37 -0
  68. package/dist/src/components/table/mono-table-search-core.d.ts +22 -3
  69. package/dist/src/components/table/table-types.d.ts +74 -4
  70. package/dist/src/components/tag-input/tag-input-core.d.ts +1 -1
  71. package/dist/src/components/tag-input/tag-input-types.d.ts +1 -1
  72. package/dist/src/components/textarea/textarea-core.d.ts +1 -1
  73. package/dist/src/composables/mono-event.d.ts +7 -1
  74. package/dist/src/entries/index.d.ts +8 -6
  75. package/dist/src/entries/index.node.d.ts +8 -6
  76. package/dist/src/search/index.d.ts +1 -1
  77. package/dist/src/utils/group-count.d.ts +36 -0
  78. package/dist/switch-utils-CTZ4id2V.js +338 -0
  79. package/dist/tabs-core-r5-B8k_N.js +317 -0
  80. package/dist/tag-input-utils-D2JA90DQ.js +3060 -0
  81. package/dist/textarea-utils-VU1KeXm8.js +821 -0
  82. package/dist/ui/accordion.js +2 -274
  83. package/dist/ui/alert.js +2 -131
  84. package/dist/ui/breadcrumb.js +4 -745
  85. package/dist/ui/button-dropdown.js +4 -652
  86. package/dist/ui/button.js +2 -63
  87. package/dist/ui/card.js +2 -310
  88. package/dist/ui/chart.js +4 -800
  89. package/dist/ui/checkbox.js +4 -335
  90. package/dist/ui/chip.js +4 -402
  91. package/dist/ui/date.js +3 -732
  92. package/dist/ui/drawer.js +3 -757
  93. package/dist/ui/dropdown-table.js +5 -1341
  94. package/dist/ui/dropdown.js +4 -509
  95. package/dist/ui/file-upload.js +3 -622
  96. package/dist/ui/filter.js +5 -380
  97. package/dist/ui/form.js +1 -1
  98. package/dist/ui/index.css +130 -5
  99. package/dist/ui/input.js +3 -1141
  100. package/dist/ui/menu.js +4 -828
  101. package/dist/ui/modal.js +4 -843
  102. package/dist/ui/nav.js +4 -223
  103. package/dist/ui/otp.js +4 -756
  104. package/dist/ui/radio.js +4 -317
  105. package/dist/ui/rich-text-editor.js +4 -1278
  106. package/dist/ui/select.js +5 -2138
  107. package/dist/ui/shadow/accordion.js +4 -273
  108. package/dist/ui/shadow/alert.js +4 -130
  109. package/dist/ui/shadow/breadcrumb.js +5 -744
  110. package/dist/ui/shadow/button-dropdown.js +6 -652
  111. package/dist/ui/shadow/button.js +3 -63
  112. package/dist/ui/shadow/card.js +4 -309
  113. package/dist/ui/shadow/chart.js +6 -1576
  114. package/dist/ui/shadow/checkbox.js +4 -334
  115. package/dist/ui/shadow/chip.js +4 -396
  116. package/dist/ui/shadow/date.js +7 -731
  117. package/dist/ui/shadow/drawer.js +4 -756
  118. package/dist/ui/shadow/dropdown-table.js +8 -1762
  119. package/dist/ui/shadow/dropdown.js +4 -508
  120. package/dist/ui/shadow/file-upload.js +4 -620
  121. package/dist/ui/shadow/filter.js +7 -379
  122. package/dist/ui/shadow/input.js +6 -1140
  123. package/dist/ui/shadow/menu.js +5 -828
  124. package/dist/ui/shadow/modal.js +6 -1063
  125. package/dist/ui/shadow/nav.js +5 -223
  126. package/dist/ui/shadow/otp.js +6 -754
  127. package/dist/ui/shadow/radio.js +4 -316
  128. package/dist/ui/shadow/rich-text-editor.js +4 -1276
  129. package/dist/ui/shadow/select.js +6 -2136
  130. package/dist/ui/shadow/sidebar.js +5 -581
  131. package/dist/ui/shadow/slider.js +5 -1010
  132. package/dist/ui/shadow/switch.js +4 -335
  133. package/dist/ui/shadow/table.js +10 -4501
  134. package/dist/ui/shadow/tabs.js +6 -315
  135. package/dist/ui/shadow/tag-input.js +7 -3057
  136. package/dist/ui/shadow/textarea.js +6 -817
  137. package/dist/ui/sidebar.js +3 -581
  138. package/dist/ui/slider.js +4 -1012
  139. package/dist/ui/switch.js +4 -336
  140. package/dist/ui/table.js +9 -4502
  141. package/dist/ui/tabs.js +4 -315
  142. package/dist/ui/tag-input.js +5 -3058
  143. package/dist/ui/textarea.js +4 -819
  144. package/dist/vue.d.ts +3 -2
  145. package/package.json +3 -3
  146. package/dist/chip-strip-bNfLn52Z.js +0 -215
  147. package/dist/data-source-controller-Dx6IQ2CN.js +0 -243
  148. package/dist/data-source-options-BM4r4PRw.js +0 -558
  149. package/dist/data-source-read-HyBCRg1t.js +0 -242
  150. package/dist/excel-BXzr3uI1.js +0 -784
  151. package/dist/export-ixRdCS2y.js +0 -2388
  152. package/dist/field-tooltip-addon-CWCp19mJ.js +0 -702
  153. package/dist/field-tooltip.css_raw-U5UeTa2d.js +0 -269
  154. package/dist/filter-builder-C0T3pMx2.js +0 -208
  155. package/dist/form-control-core-BbiACMtL.js +0 -226
  156. package/dist/icon-ClxnYhBG.js +0 -19
  157. package/dist/import-Dc4eXC02.js +0 -612
  158. package/dist/light-slots-_no0r2Z-.js +0 -246
  159. package/dist/markdown-uGlE_OfJ.js +0 -20191
  160. package/dist/mono-data-grid-Bkr-e1-i.js +0 -4043
  161. 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 };