@react-querybuilder/core 8.22.5 → 8.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/derivations/package.json +4 -0
- package/dist/cjs/react-querybuilder_core.cjs.development.d.ts +2078 -1665
- package/dist/cjs/react-querybuilder_core.cjs.development.js +1293 -422
- package/dist/cjs/react-querybuilder_core.cjs.development.js.map +1 -1
- package/dist/cjs/react-querybuilder_core.cjs.production.d.ts +2078 -1665
- package/dist/cjs/react-querybuilder_core.cjs.production.js +1 -1
- package/dist/cjs/react-querybuilder_core.cjs.production.js.map +1 -1
- package/dist/{convertQuery-Cw76XFIn.js → convertQuery-BLT0GIhM.js} +17 -8
- package/dist/{convertQuery-Cw76XFIn.js.map → convertQuery-BLT0GIhM.js.map} +1 -1
- package/dist/{convertQuery-D_4J7E-Z.mjs → convertQuery-DaXv3h7S.mjs} +6 -3
- package/dist/{convertQuery-D_4J7E-Z.mjs.map → convertQuery-DaXv3h7S.mjs.map} +1 -1
- package/dist/derivations.d.mts +2902 -0
- package/dist/derivations.d.ts +2902 -0
- package/dist/derivations.js +2845 -0
- package/dist/derivations.js.map +1 -0
- package/dist/derivations.mjs +2678 -0
- package/dist/derivations.mjs.map +1 -0
- package/dist/formatQuery.d.mts +2 -895
- package/dist/formatQuery.d.ts +2 -895
- package/dist/formatQuery.js +120 -140
- package/dist/formatQuery.js.map +1 -1
- package/dist/formatQuery.mjs +8 -28
- package/dist/formatQuery.mjs.map +1 -1
- package/dist/{import-DVWI7Fdh.d.mts → import-BdKPtLzo.d.mts} +2 -2
- package/dist/{import-6ExjhifL.d.ts → import-BfZTwhp2.d.ts} +2 -2
- package/dist/{index-CX9mehdL.d.mts → index-DOIE2pwx.d.mts} +274 -3
- package/dist/{index-CX9mehdL.d.ts → index-DOIE2pwx.d.ts} +274 -3
- package/dist/index-OF2Q1gTp.d.mts +895 -0
- package/dist/index-XueTy_XQ.d.ts +895 -0
- package/dist/isRuleOrGroupValid-CIMZNkt4.mjs +27 -0
- package/dist/isRuleOrGroupValid-CIMZNkt4.mjs.map +1 -0
- package/dist/isRuleOrGroupValid-CtUf4_4x.js +38 -0
- package/dist/isRuleOrGroupValid-CtUf4_4x.js.map +1 -0
- package/dist/{isRuleGroup-CqLThsbu.mjs → objectUtils-DDZ-KhV5.mjs} +25 -2
- package/dist/objectUtils-DDZ-KhV5.mjs.map +1 -0
- package/dist/{isRuleGroup-0JNiQgLg.js → objectUtils-DfnEmDSV.js} +48 -1
- package/dist/objectUtils-DfnEmDSV.js.map +1 -0
- package/dist/parseCEL.d.mts +2 -2
- package/dist/parseCEL.d.ts +2 -2
- package/dist/parseCEL.js +19 -16
- package/dist/parseCEL.js.map +1 -1
- package/dist/parseCEL.mjs +12 -9
- package/dist/parseCEL.mjs.map +1 -1
- package/dist/parseCypher.d.mts +1 -1
- package/dist/parseCypher.d.ts +1 -1
- package/dist/parseGremlin.d.mts +1 -1
- package/dist/parseGremlin.d.ts +1 -1
- package/dist/parseJSONata.d.mts +2 -2
- package/dist/parseJSONata.d.ts +2 -2
- package/dist/parseJSONata.js +17 -14
- package/dist/parseJSONata.js.map +1 -1
- package/dist/parseJSONata.mjs +11 -8
- package/dist/parseJSONata.mjs.map +1 -1
- package/dist/parseJsonLogic.d.mts +2 -2
- package/dist/parseJsonLogic.d.ts +2 -2
- package/dist/parseJsonLogic.js +38 -38
- package/dist/parseJsonLogic.js.map +1 -1
- package/dist/parseJsonLogic.mjs +5 -5
- package/dist/parseMongoDB.d.mts +2 -2
- package/dist/parseMongoDB.d.ts +2 -2
- package/dist/parseMongoDB.js +50 -47
- package/dist/parseMongoDB.js.map +1 -1
- package/dist/parseMongoDB.mjs +35 -32
- package/dist/parseMongoDB.mjs.map +1 -1
- package/dist/parseSPARQL.d.mts +1 -1
- package/dist/parseSPARQL.d.ts +1 -1
- package/dist/parseSQL.d.mts +2 -2
- package/dist/parseSQL.d.ts +2 -2
- package/dist/parseSQL.js +27 -23
- package/dist/parseSQL.js.map +1 -1
- package/dist/parseSQL.mjs +21 -17
- package/dist/parseSQL.mjs.map +1 -1
- package/dist/parseSpEL.d.mts +2 -2
- package/dist/parseSpEL.d.ts +2 -2
- package/dist/parseSpEL.js +17 -14
- package/dist/parseSpEL.js.map +1 -1
- package/dist/parseSpEL.mjs +13 -10
- package/dist/parseSpEL.mjs.map +1 -1
- package/dist/parserUtils-BKPstAaO.mjs +30 -0
- package/dist/parserUtils-BKPstAaO.mjs.map +1 -0
- package/dist/parserUtils-DbBXyAht.js +41 -0
- package/dist/parserUtils-DbBXyAht.js.map +1 -0
- package/dist/{prepareQueryObjects-BnGL91Tt.js → prepareQueryObjects-Dj9cTl9D.js} +90 -63
- package/dist/prepareQueryObjects-Dj9cTl9D.js.map +1 -0
- package/dist/{prepareQueryObjects-ChINDIap.mjs → prepareQueryObjects-Dmyf3Wi_.mjs} +56 -59
- package/dist/prepareQueryObjects-Dmyf3Wi_.mjs.map +1 -0
- package/dist/react-querybuilder_core.d.mts +2078 -1665
- package/dist/react-querybuilder_core.legacy-esm.d.ts +2078 -1665
- package/dist/react-querybuilder_core.legacy-esm.js +1445 -635
- package/dist/react-querybuilder_core.legacy-esm.js.map +1 -1
- package/dist/react-querybuilder_core.mjs +1288 -424
- package/dist/react-querybuilder_core.mjs.map +1 -1
- package/dist/react-querybuilder_core.production.d.mts +2078 -1665
- package/dist/react-querybuilder_core.production.mjs +1 -1
- package/dist/react-querybuilder_core.production.mjs.map +1 -1
- package/dist/transformQuery-B7d9AWUf.d.mts +116 -0
- package/dist/transformQuery-BXDiUBGB.d.ts +116 -0
- package/dist/transformQuery.d.mts +2 -116
- package/dist/transformQuery.d.ts +2 -116
- package/dist/transformQuery.js +3 -4
- package/dist/transformQuery.js.map +1 -1
- package/dist/transformQuery.mjs +1 -2
- package/dist/transformQuery.mjs.map +1 -1
- package/dist/utils-CP5Jqy1P.mjs +1294 -0
- package/dist/utils-CP5Jqy1P.mjs.map +1 -0
- package/dist/utils-e-7KHkjV.js +1779 -0
- package/dist/utils-e-7KHkjV.js.map +1 -0
- package/package.json +33 -22
- package/dist/isRuleGroup-0JNiQgLg.js.map +0 -1
- package/dist/isRuleGroup-CqLThsbu.mjs.map +0 -1
- package/dist/objectUtils-Cr02aGxb.js +0 -27
- package/dist/objectUtils-Cr02aGxb.js.map +0 -1
- package/dist/objectUtils-DBlmJgBf.mjs +0 -16
- package/dist/objectUtils-DBlmJgBf.mjs.map +0 -1
- package/dist/prepareQueryObjects-BnGL91Tt.js.map +0 -1
- package/dist/prepareQueryObjects-ChINDIap.mjs.map +0 -1
- package/dist/utils-4k-j5FBa.mjs +0 -537
- package/dist/utils-4k-j5FBa.mjs.map +0 -1
- package/dist/utils-C79dxwZb.js +0 -752
- package/dist/utils-C79dxwZb.js.map +0 -1
|
@@ -0,0 +1,2678 @@
|
|
|
1
|
+
import { $ as defaultCombinators, A as isFlexibleOptionGroupArray, B as uniqByName, Ct as queryBuilderFlagDefaults, D as getFirstOption, Dt as substringOperators, E as parseNumber, Et as standardClassnames, F as toFlatOptionArray, G as splitBy, H as uniqOptList, I as toFullOption, J as LogType, K as toArray, L as toFullOptionList, M as isFullOptionGroupArray, N as isOptionGroupArray, O as getOption, P as prepareOptionList, Q as defaultCombinatorLabelMap, R as toFullOptionMap, St as nullOperators, T as getParseNumberMethod, Tt as rootPath, U as joinWith, V as uniqOptGroups, W as nullFreeArray, X as betweenOperators, Y as TestID, Z as defaultCoalesceMs, _t as defaultPlaceholderValueLabel, at as defaultOperatorLabelMap, bt as groupInvalidReasons, ct as defaultPlaceholderFieldGroupLabel, dt as defaultPlaceholderLabel, et as defaultCombinatorsExtended, ft as defaultPlaceholderName, gt as defaultPlaceholderValueGroupLabel, ht as defaultPlaceholderOperatorName, it as defaultMaxHistory, j as isFullOptionArray, k as isFlexibleOptionArray, lt as defaultPlaceholderFieldLabel, mt as defaultPlaceholderOperatorLabel, nt as defaultJoinChar, ot as defaultOperatorNegationMap, pt as defaultPlaceholderOperatorGroupLabel, q as trimIfString, rt as defaultMatchModes, st as defaultOperators, tt as defaultControlClassnames, ut as defaultPlaceholderFieldName, vt as defaultPlaceholderValueName, wt as relationalOperators, xt as inOperators, yt as defaultTranslations, z as uniqByIdentifier } from "./utils-CP5Jqy1P.mjs";
|
|
2
|
+
import { a as isRuleGroupType, c as isPojo, d as numericRegex, i as isRuleGroup, l as lc, n as objectEntries, o as isRuleGroupTypeIC, r as objectKeys, s as isRuleType, t as isUnsafeKey, u as nullOrUndefinedOrEmpty } from "./objectUtils-DDZ-KhV5.mjs";
|
|
3
|
+
import { n as convertQuery, r as convertToIC, t as convertFromIC } from "./convertQuery-DaXv3h7S.mjs";
|
|
4
|
+
import { a as getValueSourcesUtil, c as filterFieldsByComparator, i as resolveCandidateQuery, n as prepareRuleGroup, o as generateID, r as prepareRuleOrGroup, s as uuidV4regex, t as prepareRule } from "./prepareQueryObjects-Dmyf3Wi_.mjs";
|
|
5
|
+
import { n as isValidationResult, t as isRuleOrGroupValid } from "./isRuleOrGroupValid-CIMZNkt4.mjs";
|
|
6
|
+
import { transformQuery } from "./transformQuery.mjs";
|
|
7
|
+
import { Immer, current, isDraft, produce, setAutoFreeze as setAutoFreeze$1 } from "immer";
|
|
8
|
+
//#region src/controlPropKeys.ts
|
|
9
|
+
/**
|
|
10
|
+
* Runtime data describing the set of query builder controls and the props each one receives.
|
|
11
|
+
*
|
|
12
|
+
* These live in `@react-querybuilder/core` (not `react-querybuilder`) so that non-React ports
|
|
13
|
+
* can consume them without a React dependency.
|
|
14
|
+
*
|
|
15
|
+
* Nothing here can drift from the React implementation unnoticed:
|
|
16
|
+
* `react-querybuilder`'s `types/controlPropKeys.test.ts` gates {@link controlKeys} against
|
|
17
|
+
* `defaultControlElements` and `ControlElementsProp`, and every entry of
|
|
18
|
+
* {@link controlPropKeys} against the corresponding props interface—both at runtime and at
|
|
19
|
+
* compile time, in both directions.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* The name of every query builder control, alphabetized.
|
|
23
|
+
*
|
|
24
|
+
* @group Props
|
|
25
|
+
*/
|
|
26
|
+
const controlKeys = [
|
|
27
|
+
"actionElement",
|
|
28
|
+
"addGroupAction",
|
|
29
|
+
"addRuleAction",
|
|
30
|
+
"cloneGroupAction",
|
|
31
|
+
"cloneRuleAction",
|
|
32
|
+
"combinatorSelector",
|
|
33
|
+
"dragHandle",
|
|
34
|
+
"fieldSelector",
|
|
35
|
+
"inlineCombinator",
|
|
36
|
+
"lockGroupAction",
|
|
37
|
+
"lockRuleAction",
|
|
38
|
+
"matchModeEditor",
|
|
39
|
+
"muteGroupAction",
|
|
40
|
+
"muteRuleAction",
|
|
41
|
+
"notToggle",
|
|
42
|
+
"operatorSelector",
|
|
43
|
+
"removeGroupAction",
|
|
44
|
+
"removeRuleAction",
|
|
45
|
+
"rule",
|
|
46
|
+
"ruleGroup",
|
|
47
|
+
"ruleGroupBodyElements",
|
|
48
|
+
"ruleGroupHeaderElements",
|
|
49
|
+
"shiftActions",
|
|
50
|
+
"undoRedoActions",
|
|
51
|
+
"valueEditor",
|
|
52
|
+
"valueSelector",
|
|
53
|
+
"valueSourceSelector"
|
|
54
|
+
];
|
|
55
|
+
/**
|
|
56
|
+
* The full set of prop names each default control receives. Ports can use this to declare
|
|
57
|
+
* every prop explicitly instead of relying on fall-through/attribute inheritance.
|
|
58
|
+
*
|
|
59
|
+
* Each array is the complete, alphabetized key set of the control's props interface in
|
|
60
|
+
* `react-querybuilder`. The lists are written out in full rather than composed from shared
|
|
61
|
+
* bases because `--isolatedDeclarations` cannot infer array types built with spreads. A
|
|
62
|
+
* compile-time gate in `react-querybuilder` (`types/controlPropKeys.test.ts`) fails the build
|
|
63
|
+
* if any list drifts from its interface in either direction.
|
|
64
|
+
*
|
|
65
|
+
* @group Props
|
|
66
|
+
*/
|
|
67
|
+
const controlPropKeys = {
|
|
68
|
+
actionElement: [
|
|
69
|
+
"className",
|
|
70
|
+
"context",
|
|
71
|
+
"disabled",
|
|
72
|
+
"disabledTranslation",
|
|
73
|
+
"handleOnClick",
|
|
74
|
+
"label",
|
|
75
|
+
"level",
|
|
76
|
+
"path",
|
|
77
|
+
"ruleOrGroup",
|
|
78
|
+
"rules",
|
|
79
|
+
"schema",
|
|
80
|
+
"testID",
|
|
81
|
+
"title",
|
|
82
|
+
"validation"
|
|
83
|
+
],
|
|
84
|
+
addGroupAction: [
|
|
85
|
+
"className",
|
|
86
|
+
"context",
|
|
87
|
+
"disabled",
|
|
88
|
+
"disabledTranslation",
|
|
89
|
+
"handleOnClick",
|
|
90
|
+
"label",
|
|
91
|
+
"level",
|
|
92
|
+
"path",
|
|
93
|
+
"ruleOrGroup",
|
|
94
|
+
"rules",
|
|
95
|
+
"schema",
|
|
96
|
+
"testID",
|
|
97
|
+
"title",
|
|
98
|
+
"validation"
|
|
99
|
+
],
|
|
100
|
+
addRuleAction: [
|
|
101
|
+
"className",
|
|
102
|
+
"context",
|
|
103
|
+
"disabled",
|
|
104
|
+
"disabledTranslation",
|
|
105
|
+
"handleOnClick",
|
|
106
|
+
"label",
|
|
107
|
+
"level",
|
|
108
|
+
"path",
|
|
109
|
+
"ruleOrGroup",
|
|
110
|
+
"rules",
|
|
111
|
+
"schema",
|
|
112
|
+
"testID",
|
|
113
|
+
"title",
|
|
114
|
+
"validation"
|
|
115
|
+
],
|
|
116
|
+
cloneGroupAction: [
|
|
117
|
+
"className",
|
|
118
|
+
"context",
|
|
119
|
+
"disabled",
|
|
120
|
+
"disabledTranslation",
|
|
121
|
+
"handleOnClick",
|
|
122
|
+
"label",
|
|
123
|
+
"level",
|
|
124
|
+
"path",
|
|
125
|
+
"ruleOrGroup",
|
|
126
|
+
"rules",
|
|
127
|
+
"schema",
|
|
128
|
+
"testID",
|
|
129
|
+
"title",
|
|
130
|
+
"validation"
|
|
131
|
+
],
|
|
132
|
+
cloneRuleAction: [
|
|
133
|
+
"className",
|
|
134
|
+
"context",
|
|
135
|
+
"disabled",
|
|
136
|
+
"disabledTranslation",
|
|
137
|
+
"handleOnClick",
|
|
138
|
+
"label",
|
|
139
|
+
"level",
|
|
140
|
+
"path",
|
|
141
|
+
"ruleOrGroup",
|
|
142
|
+
"rules",
|
|
143
|
+
"schema",
|
|
144
|
+
"testID",
|
|
145
|
+
"title",
|
|
146
|
+
"validation"
|
|
147
|
+
],
|
|
148
|
+
combinatorSelector: [
|
|
149
|
+
"className",
|
|
150
|
+
"context",
|
|
151
|
+
"disabled",
|
|
152
|
+
"handleOnChange",
|
|
153
|
+
"level",
|
|
154
|
+
"options",
|
|
155
|
+
"path",
|
|
156
|
+
"ruleGroup",
|
|
157
|
+
"rules",
|
|
158
|
+
"schema",
|
|
159
|
+
"testID",
|
|
160
|
+
"title",
|
|
161
|
+
"validation",
|
|
162
|
+
"value"
|
|
163
|
+
],
|
|
164
|
+
dragHandle: [
|
|
165
|
+
"className",
|
|
166
|
+
"context",
|
|
167
|
+
"disabled",
|
|
168
|
+
"dragHandleAttributes",
|
|
169
|
+
"label",
|
|
170
|
+
"level",
|
|
171
|
+
"path",
|
|
172
|
+
"ruleOrGroup",
|
|
173
|
+
"schema",
|
|
174
|
+
"testID",
|
|
175
|
+
"title",
|
|
176
|
+
"validation"
|
|
177
|
+
],
|
|
178
|
+
fieldSelector: [
|
|
179
|
+
"className",
|
|
180
|
+
"context",
|
|
181
|
+
"disabled",
|
|
182
|
+
"handleOnChange",
|
|
183
|
+
"level",
|
|
184
|
+
"operator",
|
|
185
|
+
"options",
|
|
186
|
+
"path",
|
|
187
|
+
"rule",
|
|
188
|
+
"schema",
|
|
189
|
+
"testID",
|
|
190
|
+
"title",
|
|
191
|
+
"validation",
|
|
192
|
+
"value"
|
|
193
|
+
],
|
|
194
|
+
inlineCombinator: [
|
|
195
|
+
"className",
|
|
196
|
+
"component",
|
|
197
|
+
"context",
|
|
198
|
+
"disabled",
|
|
199
|
+
"handleOnChange",
|
|
200
|
+
"level",
|
|
201
|
+
"options",
|
|
202
|
+
"path",
|
|
203
|
+
"ruleGroup",
|
|
204
|
+
"rules",
|
|
205
|
+
"schema",
|
|
206
|
+
"testID",
|
|
207
|
+
"title",
|
|
208
|
+
"validation",
|
|
209
|
+
"value"
|
|
210
|
+
],
|
|
211
|
+
lockGroupAction: [
|
|
212
|
+
"className",
|
|
213
|
+
"context",
|
|
214
|
+
"disabled",
|
|
215
|
+
"disabledTranslation",
|
|
216
|
+
"handleOnClick",
|
|
217
|
+
"label",
|
|
218
|
+
"level",
|
|
219
|
+
"path",
|
|
220
|
+
"ruleOrGroup",
|
|
221
|
+
"rules",
|
|
222
|
+
"schema",
|
|
223
|
+
"testID",
|
|
224
|
+
"title",
|
|
225
|
+
"validation"
|
|
226
|
+
],
|
|
227
|
+
lockRuleAction: [
|
|
228
|
+
"className",
|
|
229
|
+
"context",
|
|
230
|
+
"disabled",
|
|
231
|
+
"disabledTranslation",
|
|
232
|
+
"handleOnClick",
|
|
233
|
+
"label",
|
|
234
|
+
"level",
|
|
235
|
+
"path",
|
|
236
|
+
"ruleOrGroup",
|
|
237
|
+
"rules",
|
|
238
|
+
"schema",
|
|
239
|
+
"testID",
|
|
240
|
+
"title",
|
|
241
|
+
"validation"
|
|
242
|
+
],
|
|
243
|
+
matchModeEditor: [
|
|
244
|
+
"className",
|
|
245
|
+
"classNames",
|
|
246
|
+
"context",
|
|
247
|
+
"disabled",
|
|
248
|
+
"field",
|
|
249
|
+
"fieldData",
|
|
250
|
+
"handleOnChange",
|
|
251
|
+
"level",
|
|
252
|
+
"match",
|
|
253
|
+
"numericEditorComponent",
|
|
254
|
+
"options",
|
|
255
|
+
"path",
|
|
256
|
+
"rule",
|
|
257
|
+
"schema",
|
|
258
|
+
"selectorComponent",
|
|
259
|
+
"testID",
|
|
260
|
+
"thresholdPlaceholder",
|
|
261
|
+
"title",
|
|
262
|
+
"validation",
|
|
263
|
+
"value"
|
|
264
|
+
],
|
|
265
|
+
muteGroupAction: [
|
|
266
|
+
"className",
|
|
267
|
+
"context",
|
|
268
|
+
"disabled",
|
|
269
|
+
"disabledTranslation",
|
|
270
|
+
"handleOnClick",
|
|
271
|
+
"label",
|
|
272
|
+
"level",
|
|
273
|
+
"path",
|
|
274
|
+
"ruleOrGroup",
|
|
275
|
+
"rules",
|
|
276
|
+
"schema",
|
|
277
|
+
"testID",
|
|
278
|
+
"title",
|
|
279
|
+
"validation"
|
|
280
|
+
],
|
|
281
|
+
muteRuleAction: [
|
|
282
|
+
"className",
|
|
283
|
+
"context",
|
|
284
|
+
"disabled",
|
|
285
|
+
"disabledTranslation",
|
|
286
|
+
"handleOnClick",
|
|
287
|
+
"label",
|
|
288
|
+
"level",
|
|
289
|
+
"path",
|
|
290
|
+
"ruleOrGroup",
|
|
291
|
+
"rules",
|
|
292
|
+
"schema",
|
|
293
|
+
"testID",
|
|
294
|
+
"title",
|
|
295
|
+
"validation"
|
|
296
|
+
],
|
|
297
|
+
notToggle: [
|
|
298
|
+
"checked",
|
|
299
|
+
"className",
|
|
300
|
+
"context",
|
|
301
|
+
"disabled",
|
|
302
|
+
"handleOnChange",
|
|
303
|
+
"label",
|
|
304
|
+
"level",
|
|
305
|
+
"path",
|
|
306
|
+
"ruleGroup",
|
|
307
|
+
"schema",
|
|
308
|
+
"testID",
|
|
309
|
+
"title",
|
|
310
|
+
"validation"
|
|
311
|
+
],
|
|
312
|
+
operatorSelector: [
|
|
313
|
+
"className",
|
|
314
|
+
"context",
|
|
315
|
+
"disabled",
|
|
316
|
+
"field",
|
|
317
|
+
"fieldData",
|
|
318
|
+
"handleOnChange",
|
|
319
|
+
"level",
|
|
320
|
+
"options",
|
|
321
|
+
"path",
|
|
322
|
+
"rule",
|
|
323
|
+
"schema",
|
|
324
|
+
"testID",
|
|
325
|
+
"title",
|
|
326
|
+
"validation",
|
|
327
|
+
"value"
|
|
328
|
+
],
|
|
329
|
+
removeGroupAction: [
|
|
330
|
+
"className",
|
|
331
|
+
"context",
|
|
332
|
+
"disabled",
|
|
333
|
+
"disabledTranslation",
|
|
334
|
+
"handleOnClick",
|
|
335
|
+
"label",
|
|
336
|
+
"level",
|
|
337
|
+
"path",
|
|
338
|
+
"ruleOrGroup",
|
|
339
|
+
"rules",
|
|
340
|
+
"schema",
|
|
341
|
+
"testID",
|
|
342
|
+
"title",
|
|
343
|
+
"validation"
|
|
344
|
+
],
|
|
345
|
+
removeRuleAction: [
|
|
346
|
+
"className",
|
|
347
|
+
"context",
|
|
348
|
+
"disabled",
|
|
349
|
+
"disabledTranslation",
|
|
350
|
+
"handleOnClick",
|
|
351
|
+
"label",
|
|
352
|
+
"level",
|
|
353
|
+
"path",
|
|
354
|
+
"ruleOrGroup",
|
|
355
|
+
"rules",
|
|
356
|
+
"schema",
|
|
357
|
+
"testID",
|
|
358
|
+
"title",
|
|
359
|
+
"validation"
|
|
360
|
+
],
|
|
361
|
+
rule: [
|
|
362
|
+
"actions",
|
|
363
|
+
"context",
|
|
364
|
+
"disabled",
|
|
365
|
+
"dndRef",
|
|
366
|
+
"dragHandleAttributes",
|
|
367
|
+
"dragMonitorId",
|
|
368
|
+
"dragRef",
|
|
369
|
+
"dropEffect",
|
|
370
|
+
"dropMonitorId",
|
|
371
|
+
"dropNotAllowed",
|
|
372
|
+
"field",
|
|
373
|
+
"groupItems",
|
|
374
|
+
"id",
|
|
375
|
+
"isDragging",
|
|
376
|
+
"isOver",
|
|
377
|
+
"operator",
|
|
378
|
+
"parentDisabled",
|
|
379
|
+
"parentMuted",
|
|
380
|
+
"path",
|
|
381
|
+
"rule",
|
|
382
|
+
"schema",
|
|
383
|
+
"shiftDownDisabled",
|
|
384
|
+
"shiftUpDisabled",
|
|
385
|
+
"translations",
|
|
386
|
+
"value",
|
|
387
|
+
"valueSource"
|
|
388
|
+
],
|
|
389
|
+
ruleGroup: [
|
|
390
|
+
"actions",
|
|
391
|
+
"combinator",
|
|
392
|
+
"context",
|
|
393
|
+
"disabled",
|
|
394
|
+
"dragHandleAttributes",
|
|
395
|
+
"dragMonitorId",
|
|
396
|
+
"dragRef",
|
|
397
|
+
"dropEffect",
|
|
398
|
+
"dropMonitorId",
|
|
399
|
+
"dropNotAllowed",
|
|
400
|
+
"dropRef",
|
|
401
|
+
"groupItems",
|
|
402
|
+
"id",
|
|
403
|
+
"isDragging",
|
|
404
|
+
"isOver",
|
|
405
|
+
"not",
|
|
406
|
+
"parentDisabled",
|
|
407
|
+
"parentMuted",
|
|
408
|
+
"path",
|
|
409
|
+
"previewRef",
|
|
410
|
+
"ruleGroup",
|
|
411
|
+
"rules",
|
|
412
|
+
"schema",
|
|
413
|
+
"shiftDownDisabled",
|
|
414
|
+
"shiftUpDisabled",
|
|
415
|
+
"translations"
|
|
416
|
+
],
|
|
417
|
+
ruleGroupBodyElements: [
|
|
418
|
+
"accessibleDescription",
|
|
419
|
+
"actions",
|
|
420
|
+
"addGroup",
|
|
421
|
+
"addRule",
|
|
422
|
+
"classNames",
|
|
423
|
+
"cloneGroup",
|
|
424
|
+
"combinator",
|
|
425
|
+
"context",
|
|
426
|
+
"disabled",
|
|
427
|
+
"dragHandleAttributes",
|
|
428
|
+
"dragMonitorId",
|
|
429
|
+
"dragRef",
|
|
430
|
+
"dropEffect",
|
|
431
|
+
"dropMonitorId",
|
|
432
|
+
"dropNotAllowed",
|
|
433
|
+
"dropRef",
|
|
434
|
+
"groupItems",
|
|
435
|
+
"id",
|
|
436
|
+
"isDragging",
|
|
437
|
+
"isOver",
|
|
438
|
+
"muted",
|
|
439
|
+
"not",
|
|
440
|
+
"onCombinatorChange",
|
|
441
|
+
"onGroupAdd",
|
|
442
|
+
"onIndependentCombinatorChange",
|
|
443
|
+
"onNotToggleChange",
|
|
444
|
+
"outerClassName",
|
|
445
|
+
"parentDisabled",
|
|
446
|
+
"parentMuted",
|
|
447
|
+
"path",
|
|
448
|
+
"pathsMemo",
|
|
449
|
+
"previewRef",
|
|
450
|
+
"removeGroup",
|
|
451
|
+
"ruleGroup",
|
|
452
|
+
"rules",
|
|
453
|
+
"schema",
|
|
454
|
+
"shiftDownDisabled",
|
|
455
|
+
"shiftGroupDown",
|
|
456
|
+
"shiftGroupUp",
|
|
457
|
+
"shiftUpDisabled",
|
|
458
|
+
"toggleLockGroup",
|
|
459
|
+
"toggleMuteGroup",
|
|
460
|
+
"translations",
|
|
461
|
+
"validationClassName",
|
|
462
|
+
"validationResult"
|
|
463
|
+
],
|
|
464
|
+
ruleGroupHeaderElements: [
|
|
465
|
+
"accessibleDescription",
|
|
466
|
+
"actions",
|
|
467
|
+
"addGroup",
|
|
468
|
+
"addRule",
|
|
469
|
+
"classNames",
|
|
470
|
+
"cloneGroup",
|
|
471
|
+
"combinator",
|
|
472
|
+
"context",
|
|
473
|
+
"disabled",
|
|
474
|
+
"dragHandleAttributes",
|
|
475
|
+
"dragMonitorId",
|
|
476
|
+
"dragRef",
|
|
477
|
+
"dropEffect",
|
|
478
|
+
"dropMonitorId",
|
|
479
|
+
"dropNotAllowed",
|
|
480
|
+
"dropRef",
|
|
481
|
+
"groupItems",
|
|
482
|
+
"id",
|
|
483
|
+
"isDragging",
|
|
484
|
+
"isOver",
|
|
485
|
+
"muted",
|
|
486
|
+
"not",
|
|
487
|
+
"onCombinatorChange",
|
|
488
|
+
"onGroupAdd",
|
|
489
|
+
"onIndependentCombinatorChange",
|
|
490
|
+
"onNotToggleChange",
|
|
491
|
+
"outerClassName",
|
|
492
|
+
"parentDisabled",
|
|
493
|
+
"parentMuted",
|
|
494
|
+
"path",
|
|
495
|
+
"pathsMemo",
|
|
496
|
+
"previewRef",
|
|
497
|
+
"removeGroup",
|
|
498
|
+
"ruleGroup",
|
|
499
|
+
"rules",
|
|
500
|
+
"schema",
|
|
501
|
+
"shiftDownDisabled",
|
|
502
|
+
"shiftGroupDown",
|
|
503
|
+
"shiftGroupUp",
|
|
504
|
+
"shiftUpDisabled",
|
|
505
|
+
"toggleLockGroup",
|
|
506
|
+
"toggleMuteGroup",
|
|
507
|
+
"translations",
|
|
508
|
+
"validationClassName",
|
|
509
|
+
"validationResult"
|
|
510
|
+
],
|
|
511
|
+
shiftActions: [
|
|
512
|
+
"className",
|
|
513
|
+
"context",
|
|
514
|
+
"disabled",
|
|
515
|
+
"labels",
|
|
516
|
+
"level",
|
|
517
|
+
"path",
|
|
518
|
+
"ruleOrGroup",
|
|
519
|
+
"schema",
|
|
520
|
+
"shiftDown",
|
|
521
|
+
"shiftDownDisabled",
|
|
522
|
+
"shiftUp",
|
|
523
|
+
"shiftUpDisabled",
|
|
524
|
+
"testID",
|
|
525
|
+
"title",
|
|
526
|
+
"titles",
|
|
527
|
+
"validation"
|
|
528
|
+
],
|
|
529
|
+
undoRedoActions: [
|
|
530
|
+
"className",
|
|
531
|
+
"classNames",
|
|
532
|
+
"context",
|
|
533
|
+
"disabled",
|
|
534
|
+
"labels",
|
|
535
|
+
"level",
|
|
536
|
+
"path",
|
|
537
|
+
"ruleOrGroup",
|
|
538
|
+
"schema",
|
|
539
|
+
"testID",
|
|
540
|
+
"title",
|
|
541
|
+
"titles",
|
|
542
|
+
"validation"
|
|
543
|
+
],
|
|
544
|
+
valueEditor: [
|
|
545
|
+
"className",
|
|
546
|
+
"context",
|
|
547
|
+
"disabled",
|
|
548
|
+
"field",
|
|
549
|
+
"fieldData",
|
|
550
|
+
"handleOnChange",
|
|
551
|
+
"inputType",
|
|
552
|
+
"level",
|
|
553
|
+
"listsAsArrays",
|
|
554
|
+
"operator",
|
|
555
|
+
"parseNumbers",
|
|
556
|
+
"path",
|
|
557
|
+
"rule",
|
|
558
|
+
"schema",
|
|
559
|
+
"selectorComponent",
|
|
560
|
+
"separator",
|
|
561
|
+
"skipHook",
|
|
562
|
+
"testID",
|
|
563
|
+
"title",
|
|
564
|
+
"type",
|
|
565
|
+
"validation",
|
|
566
|
+
"value",
|
|
567
|
+
"valueSource",
|
|
568
|
+
"values"
|
|
569
|
+
],
|
|
570
|
+
valueSelector: [
|
|
571
|
+
"className",
|
|
572
|
+
"context",
|
|
573
|
+
"disabled",
|
|
574
|
+
"handleOnChange",
|
|
575
|
+
"level",
|
|
576
|
+
"listsAsArrays",
|
|
577
|
+
"multiple",
|
|
578
|
+
"options",
|
|
579
|
+
"path",
|
|
580
|
+
"schema",
|
|
581
|
+
"testID",
|
|
582
|
+
"title",
|
|
583
|
+
"validation",
|
|
584
|
+
"value"
|
|
585
|
+
],
|
|
586
|
+
valueSourceSelector: [
|
|
587
|
+
"className",
|
|
588
|
+
"context",
|
|
589
|
+
"disabled",
|
|
590
|
+
"field",
|
|
591
|
+
"fieldData",
|
|
592
|
+
"handleOnChange",
|
|
593
|
+
"level",
|
|
594
|
+
"options",
|
|
595
|
+
"path",
|
|
596
|
+
"rule",
|
|
597
|
+
"schema",
|
|
598
|
+
"testID",
|
|
599
|
+
"title",
|
|
600
|
+
"validation",
|
|
601
|
+
"value"
|
|
602
|
+
]
|
|
603
|
+
};
|
|
604
|
+
/**
|
|
605
|
+
* Which bulk override, if any, applies to each control: `"action"` for controls that a supplied
|
|
606
|
+
* `actionElement` replaces, `"selector"` for controls that a supplied `valueSelector` replaces,
|
|
607
|
+
* and `null` for controls that neither replaces.
|
|
608
|
+
*
|
|
609
|
+
* `actionElement` and `valueSelector` are themselves `null`—they are the _source_ of a bulk
|
|
610
|
+
* override, not a target of one.
|
|
611
|
+
*
|
|
612
|
+
* Replaces name-suffix sniffing (`key.endsWith('Action')`), which would silently misclassify a
|
|
613
|
+
* future control named e.g. `pathSelector`, and which disagrees between implementations about
|
|
614
|
+
* whether `shiftActions`/`undoRedoActions` (plural) match. They do not: neither receives the
|
|
615
|
+
* `actionElement` bulk override.
|
|
616
|
+
*
|
|
617
|
+
* @group Props
|
|
618
|
+
*/
|
|
619
|
+
const controlKind = {
|
|
620
|
+
actionElement: null,
|
|
621
|
+
addGroupAction: "action",
|
|
622
|
+
addRuleAction: "action",
|
|
623
|
+
cloneGroupAction: "action",
|
|
624
|
+
cloneRuleAction: "action",
|
|
625
|
+
combinatorSelector: "selector",
|
|
626
|
+
dragHandle: null,
|
|
627
|
+
fieldSelector: "selector",
|
|
628
|
+
inlineCombinator: null,
|
|
629
|
+
lockGroupAction: "action",
|
|
630
|
+
lockRuleAction: "action",
|
|
631
|
+
matchModeEditor: null,
|
|
632
|
+
muteGroupAction: "action",
|
|
633
|
+
muteRuleAction: "action",
|
|
634
|
+
notToggle: null,
|
|
635
|
+
operatorSelector: "selector",
|
|
636
|
+
removeGroupAction: "action",
|
|
637
|
+
removeRuleAction: "action",
|
|
638
|
+
rule: null,
|
|
639
|
+
ruleGroup: null,
|
|
640
|
+
ruleGroupBodyElements: null,
|
|
641
|
+
ruleGroupHeaderElements: null,
|
|
642
|
+
shiftActions: null,
|
|
643
|
+
undoRedoActions: null,
|
|
644
|
+
valueEditor: null,
|
|
645
|
+
valueSelector: null,
|
|
646
|
+
valueSourceSelector: "selector"
|
|
647
|
+
};
|
|
648
|
+
//#endregion
|
|
649
|
+
//#region src/utils/abortReasons.ts
|
|
650
|
+
/**
|
|
651
|
+
* Abort reasons that {@link QueryManager}'s `strict` mode treats as errors. The remaining
|
|
652
|
+
* reasons—`"same-location"` and `"no-change"`—describe valid operations that had nothing to do,
|
|
653
|
+
* so they are reported to `onInvalidTarget` but never throw.
|
|
654
|
+
*
|
|
655
|
+
* Declared apart from `QueryManager` so the `@react-querybuilder/core/derivations` entry point can
|
|
656
|
+
* export it without putting the manager in its module graph.
|
|
657
|
+
*
|
|
658
|
+
* @group Query Tools
|
|
659
|
+
*/
|
|
660
|
+
const strictAbortReasons = [
|
|
661
|
+
"target-not-found",
|
|
662
|
+
"parent-not-found",
|
|
663
|
+
"parent-not-a-group",
|
|
664
|
+
"destination-not-found",
|
|
665
|
+
"root-not-allowed",
|
|
666
|
+
"not-a-combinator-slot",
|
|
667
|
+
"target-disabled",
|
|
668
|
+
"parent-disabled",
|
|
669
|
+
"max-levels-exceeded"
|
|
670
|
+
];
|
|
671
|
+
new Set(strictAbortReasons);
|
|
672
|
+
//#endregion
|
|
673
|
+
//#region ../../node_modules/clsx/dist/clsx.mjs
|
|
674
|
+
function r(e) {
|
|
675
|
+
var t, f, n = "";
|
|
676
|
+
if ("string" == typeof e || "number" == typeof e) n += e;
|
|
677
|
+
else if ("object" == typeof e) if (Array.isArray(e)) {
|
|
678
|
+
var o = e.length;
|
|
679
|
+
for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
|
|
680
|
+
} else for (f in e) e[f] && (n && (n += " "), n += f);
|
|
681
|
+
return n;
|
|
682
|
+
}
|
|
683
|
+
function clsx() {
|
|
684
|
+
for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
|
|
685
|
+
return n;
|
|
686
|
+
}
|
|
687
|
+
//#endregion
|
|
688
|
+
//#region src/utils/signature.ts
|
|
689
|
+
/**
|
|
690
|
+
* Signature returned when a change alters the _shape_ of the query—rules or groups added,
|
|
691
|
+
* removed, moved, or reordered—rather than the properties of a single node. Structural changes
|
|
692
|
+
* never coalesce with each other, so each one produces its own history entry.
|
|
693
|
+
*/
|
|
694
|
+
const structuralSignature = "~structural";
|
|
695
|
+
/**
|
|
696
|
+
* Signature returned when two queries differ by reference only, with no observable difference
|
|
697
|
+
* in their properties.
|
|
698
|
+
*/
|
|
699
|
+
const unchangedSignature = "";
|
|
700
|
+
/**
|
|
701
|
+
* Returns the list of property names that differ between two nodes, ignoring `rules` (which is
|
|
702
|
+
* compared structurally by the caller).
|
|
703
|
+
*/
|
|
704
|
+
const changedProps = (prev, next) => {
|
|
705
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(prev), ...Object.keys(next)]);
|
|
706
|
+
keys.delete("rules");
|
|
707
|
+
const changed = [];
|
|
708
|
+
for (const key of keys) if (!Object.is(prev[key], next[key])) changed.push(key);
|
|
709
|
+
return changed.toSorted();
|
|
710
|
+
};
|
|
711
|
+
const signatureOfNode = (prev, next, parentId, index) => {
|
|
712
|
+
if (prev === next) return "";
|
|
713
|
+
if (typeof prev === "string" || typeof next === "string") return typeof prev === typeof next ? `${parentId}:combinator[${index}]` : structuralSignature;
|
|
714
|
+
const prevIsGroup = isRuleGroup(prev);
|
|
715
|
+
const nextIsGroup = isRuleGroup(next);
|
|
716
|
+
if (prevIsGroup !== nextIsGroup) return structuralSignature;
|
|
717
|
+
if (!prevIsGroup || !nextIsGroup) {
|
|
718
|
+
const props = changedProps(prev, next);
|
|
719
|
+
return props.length === 0 ? "" : `${next.id ?? ""}:${props.join(",")}`;
|
|
720
|
+
}
|
|
721
|
+
if (prev.rules.length !== next.rules.length) return structuralSignature;
|
|
722
|
+
const ownProps = changedProps(prev, next);
|
|
723
|
+
let changedIndex = -1;
|
|
724
|
+
for (const [i, prevRule] of prev.rules.entries()) if (prevRule !== next.rules[i]) {
|
|
725
|
+
if (changedIndex !== -1) return structuralSignature;
|
|
726
|
+
changedIndex = i;
|
|
727
|
+
}
|
|
728
|
+
if (changedIndex === -1) return ownProps.length === 0 ? "" : `${next.id ?? ""}:${ownProps.join(",")}`;
|
|
729
|
+
if (ownProps.length > 0) return structuralSignature;
|
|
730
|
+
return signatureOfNode(prev.rules[changedIndex], next.rules[changedIndex], next.id ?? "", changedIndex);
|
|
731
|
+
};
|
|
732
|
+
/**
|
|
733
|
+
* Describes _what changed_ between two versions of a query as a short string, so that
|
|
734
|
+
* consecutive edits to the same property of the same rule (e.g. typing in a value editor) can
|
|
735
|
+
* be recognized and coalesced into a single history entry.
|
|
736
|
+
*
|
|
737
|
+
* Returns {@link structuralSignature} for changes to the shape of the query, {@link unchangedSignature} when the
|
|
738
|
+
* two queries differ by reference only, and `"<id>:<props>"` otherwise.
|
|
739
|
+
*
|
|
740
|
+
* The walk prunes on reference equality, so it costs O(depth) rather than O(size) for the
|
|
741
|
+
* single-node edits that make up the overwhelming majority of changes.
|
|
742
|
+
*
|
|
743
|
+
* Note that this relies on the structural sharing that Immer—and therefore every query
|
|
744
|
+
* produced by RQB's own update functions—guarantees: nodes that did not change keep their
|
|
745
|
+
* identity. A query that has been wholly rebuilt (deep-cloned, round-tripped through JSON,
|
|
746
|
+
* re-parsed from a string) shares no identity with its predecessor, so it is reported as
|
|
747
|
+
* {@link structuralSignature} and will not coalesce. That is a graceful degradation—every change simply
|
|
748
|
+
* gets its own history entry—but controlled components that clone the query on every change
|
|
749
|
+
* will not benefit from coalescing.
|
|
750
|
+
*/
|
|
751
|
+
const signatureOf = (prev, next) => signatureOfNode(prev, next, "", -1);
|
|
752
|
+
//#endregion
|
|
753
|
+
//#region src/utils/coalesce.ts
|
|
754
|
+
/**
|
|
755
|
+
* Determines whether a change should be absorbed into the current history entry instead of
|
|
756
|
+
* pushing a new one. This is the exact rule {@link QueryManager} applies when recording history,
|
|
757
|
+
* exposed so non-React implementations can manage their own history stacks without
|
|
758
|
+
* reimplementing (and drifting from) the semantics.
|
|
759
|
+
*
|
|
760
|
+
* A change coalesces only when all three hold:
|
|
761
|
+
* - the change is not structural (rules/groups added, removed, moved, or reordered)
|
|
762
|
+
* - its signature matches the previously recorded signature
|
|
763
|
+
* - it occurred within `coalesceMs` of the previous recording
|
|
764
|
+
*
|
|
765
|
+
* Note that {@link unchangedSignature} is not handled here. A change with no observable
|
|
766
|
+
* difference is never recorded at all, which is a separate decision made before this check.
|
|
767
|
+
*
|
|
768
|
+
* @param prevSig Signature of the previously recorded change, or `undefined` if there is none.
|
|
769
|
+
* @param nextSig Signature of the change being recorded, from {@link signatureOf}.
|
|
770
|
+
* @param prevAt Timestamp of the previous recording.
|
|
771
|
+
* @param now Timestamp of the change being recorded.
|
|
772
|
+
* @param coalesceMs Coalescing window in milliseconds.
|
|
773
|
+
*/
|
|
774
|
+
const shouldCoalesce = (prevSig, nextSig, prevAt, now, coalesceMs = 500) => nextSig !== "~structural" && nextSig === prevSig && now >= prevAt && now - prevAt < coalesceMs;
|
|
775
|
+
//#endregion
|
|
776
|
+
//#region src/utils/defaultValidator.ts
|
|
777
|
+
/**
|
|
778
|
+
* This is an example validation function you can pass to {@link react-querybuilder!QueryBuilder QueryBuilder} in the
|
|
779
|
+
* `validator` prop. It assumes that you want to validate groups, and has a no-op
|
|
780
|
+
* for validating rules which you can replace with your own implementation.
|
|
781
|
+
*/
|
|
782
|
+
const defaultValidator = (query) => {
|
|
783
|
+
const result = {};
|
|
784
|
+
/**
|
|
785
|
+
* Replace this with your custom rule validator.
|
|
786
|
+
*/
|
|
787
|
+
const validateRule = (rule) => {
|
|
788
|
+
/* v8 ignore start -- @preserve */
|
|
789
|
+
if (rule.id) result[rule.id];
|
|
790
|
+
/* v8 ignore stop -- @preserve */
|
|
791
|
+
};
|
|
792
|
+
const validateGroup = (rg) => {
|
|
793
|
+
const reasons = [];
|
|
794
|
+
if (rg.rules.length === 0) reasons.push(groupInvalidReasons.empty);
|
|
795
|
+
else if (!isRuleGroupType(rg)) {
|
|
796
|
+
let invalidICs = false;
|
|
797
|
+
for (let i = 0; i < rg.rules.length && !invalidICs; i++) if (i % 2 === 0 && typeof rg.rules[i] === "string" || i % 2 === 1 && typeof rg.rules[i] !== "string" || i % 2 === 1 && typeof rg.rules[i] === "string" && !defaultCombinators.map((c) => c.name).includes(rg.rules[i])) invalidICs = true;
|
|
798
|
+
if (invalidICs) reasons.push(groupInvalidReasons.invalidIndependentCombinators);
|
|
799
|
+
}
|
|
800
|
+
if (isRuleGroupType(rg) && !defaultCombinators.map((c) => c.name).includes(rg.combinator) && rg.rules.length > 1) reasons.push(groupInvalidReasons.invalidCombinator);
|
|
801
|
+
/* v8 ignore else -- @preserve */
|
|
802
|
+
if (rg.id) result[rg.id] = reasons.length > 0 ? {
|
|
803
|
+
valid: false,
|
|
804
|
+
reasons
|
|
805
|
+
} : true;
|
|
806
|
+
for (const r of rg.rules) if (typeof r === "string") {} else if (isRuleGroup(r)) validateGroup(r);
|
|
807
|
+
else validateRule(r);
|
|
808
|
+
};
|
|
809
|
+
validateGroup(query);
|
|
810
|
+
return result;
|
|
811
|
+
};
|
|
812
|
+
//#endregion
|
|
813
|
+
//#region src/utils/deriveClassNames.ts
|
|
814
|
+
const ruleClassnameSources = {
|
|
815
|
+
shiftActions: ["shiftActions"],
|
|
816
|
+
dragHandle: ["dragHandle"],
|
|
817
|
+
fields: ["valueSelector", "fields"],
|
|
818
|
+
matchMode: ["valueSelector", "matchMode"],
|
|
819
|
+
matchThreshold: ["valueSelector", "matchThreshold"],
|
|
820
|
+
operators: ["valueSelector", "operators"],
|
|
821
|
+
valueSource: ["valueSelector", "valueSource"],
|
|
822
|
+
value: ["value"],
|
|
823
|
+
cloneRule: ["actionElement", "cloneRule"],
|
|
824
|
+
lockRule: ["actionElement", "lockRule"],
|
|
825
|
+
muteRule: ["actionElement", "muteRule"],
|
|
826
|
+
removeRule: ["actionElement", "removeRule"],
|
|
827
|
+
valueListItem: ["valueListItem"]
|
|
828
|
+
};
|
|
829
|
+
const ruleGroupClassnameSources = {
|
|
830
|
+
header: {
|
|
831
|
+
sources: ["header"],
|
|
832
|
+
conditions: [
|
|
833
|
+
{
|
|
834
|
+
key: "dndOver",
|
|
835
|
+
when: (s) => s.isOver,
|
|
836
|
+
standardOnly: true
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
key: "dndCopy",
|
|
840
|
+
when: (s) => s.isOver && s.dropEffect === "copy"
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
key: "dndDropNotAllowed",
|
|
844
|
+
when: (s) => s.dropNotAllowed
|
|
845
|
+
}
|
|
846
|
+
]
|
|
847
|
+
},
|
|
848
|
+
shiftActions: ["shiftActions"],
|
|
849
|
+
undoRedoActions: ["undoRedoActions"],
|
|
850
|
+
undoAction: ["actionElement", "undoAction"],
|
|
851
|
+
redoAction: ["actionElement", "redoAction"],
|
|
852
|
+
dragHandle: ["dragHandle"],
|
|
853
|
+
combinators: ["valueSelector", "combinators"],
|
|
854
|
+
notToggle: ["notToggle"],
|
|
855
|
+
addRule: ["actionElement", "addRule"],
|
|
856
|
+
addGroup: ["actionElement", "addGroup"],
|
|
857
|
+
cloneGroup: ["actionElement", "cloneGroup"],
|
|
858
|
+
lockGroup: ["actionElement", "lockGroup"],
|
|
859
|
+
muteGroup: ["actionElement", "muteGroup"],
|
|
860
|
+
removeGroup: ["actionElement", "removeGroup"],
|
|
861
|
+
body: ["body"]
|
|
862
|
+
};
|
|
863
|
+
/**
|
|
864
|
+
* Conditional classes applied to a rule's wrapper element.
|
|
865
|
+
*
|
|
866
|
+
* Note that this is deliberately _not_ the same set as {@link ruleGroupOuterConditions}: a rule
|
|
867
|
+
* reflects more drag-and-drop states than a group does. Declaring each set separately is what
|
|
868
|
+
* keeps an implementation from assuming they're symmetric.
|
|
869
|
+
*/
|
|
870
|
+
const ruleOuterConditions = [
|
|
871
|
+
{
|
|
872
|
+
key: "disabled",
|
|
873
|
+
when: (s) => s.disabled
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
key: "muted",
|
|
877
|
+
when: (s) => s.muted
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
key: "dndDragging",
|
|
881
|
+
when: (s) => s.isDragging
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
key: "dndOver",
|
|
885
|
+
when: (s) => s.isOver
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
key: "dndCopy",
|
|
889
|
+
when: (s) => s.isOver && s.dropEffect === "copy"
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
key: "dndGroup",
|
|
893
|
+
when: (s) => s.isOver && s.groupItems
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
key: "dndDropNotAllowed",
|
|
897
|
+
when: (s) => s.dropNotAllowed
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
key: "hasSubQuery",
|
|
901
|
+
when: (s) => s.hasSubQuery
|
|
902
|
+
}
|
|
903
|
+
];
|
|
904
|
+
/** Conditional classes applied to a rule group's wrapper element. */
|
|
905
|
+
const ruleGroupOuterConditions = [
|
|
906
|
+
{
|
|
907
|
+
key: "disabled",
|
|
908
|
+
when: (s) => s.disabled
|
|
909
|
+
},
|
|
910
|
+
{
|
|
911
|
+
key: "muted",
|
|
912
|
+
when: (s) => s.muted
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
key: "dndDragging",
|
|
916
|
+
when: (s) => s.isDragging
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
key: "dndGroup",
|
|
920
|
+
when: (s) => s.isOver && s.groupItems
|
|
921
|
+
}
|
|
922
|
+
];
|
|
923
|
+
/**
|
|
924
|
+
* Conditional classes applied to the query builder's wrapper element.
|
|
925
|
+
*
|
|
926
|
+
* Only a `boolean` validation result contributes a class; a {@link ValidationMap} describes
|
|
927
|
+
* individual rules and groups, not the query as a whole.
|
|
928
|
+
*/
|
|
929
|
+
const queryBuilderOuterConditions = [
|
|
930
|
+
{
|
|
931
|
+
key: "disabled",
|
|
932
|
+
when: (s) => s.disabled
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
key: "valid",
|
|
936
|
+
when: (s) => typeof s.validationResult === "boolean" && s.validationResult
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
key: "invalid",
|
|
940
|
+
when: (s) => typeof s.validationResult === "boolean" && !s.validationResult
|
|
941
|
+
}
|
|
942
|
+
];
|
|
943
|
+
/**
|
|
944
|
+
* Expands conditions into `clsx` arguments: every custom class first (in declaration order),
|
|
945
|
+
* then a single object of standard classes.
|
|
946
|
+
*/
|
|
947
|
+
const conditionArgs = (conditions, state, { classNames, suppressStandardClassnames }) => {
|
|
948
|
+
const evaluated = conditions.map((condition) => [condition, !!condition.when(state)]);
|
|
949
|
+
return [...evaluated.filter(([condition]) => !condition.standardOnly).map(([condition, active]) => active && classNames?.[condition.key]), suppressStandardClassnames || Object.fromEntries(evaluated.map(([condition, active]) => [standardClassnames[condition.key], active]))];
|
|
950
|
+
};
|
|
951
|
+
/** Composes the classname for a single entry of a spec map. */
|
|
952
|
+
const deriveFromSpec = (key, spec, state, options) => {
|
|
953
|
+
const { sources, conditions } = Array.isArray(spec) ? { sources: spec } : spec;
|
|
954
|
+
return clsx(options.suppressStandardClassnames || standardClassnames[key], ...sources.map((source) => options.classNames?.[source]), ...conditions ? conditionArgs(conditions, state, options) : []);
|
|
955
|
+
};
|
|
956
|
+
const deriveFromSpecs = (specs, state, options) => {
|
|
957
|
+
const result = {};
|
|
958
|
+
for (const [key, spec] of Object.entries(specs)) result[key] = deriveFromSpec(key, spec, state, options);
|
|
959
|
+
return result;
|
|
960
|
+
};
|
|
961
|
+
/**
|
|
962
|
+
* Classnames for each element rendered by a rule. This is the framework-agnostic core of the
|
|
963
|
+
* `classNames` object returned by the `useRule` hook.
|
|
964
|
+
*
|
|
965
|
+
* @group Query Tools
|
|
966
|
+
*/
|
|
967
|
+
const deriveRuleClassNames = (options) => deriveFromSpecs(ruleClassnameSources, {}, options);
|
|
968
|
+
/**
|
|
969
|
+
* The classname for a single element of a rule, composed from the same table as
|
|
970
|
+
* {@link deriveRuleClassNames}. Useful where only one is needed, such as the items of a
|
|
971
|
+
* multi-value editor.
|
|
972
|
+
*
|
|
973
|
+
* @group Query Tools
|
|
974
|
+
*/
|
|
975
|
+
const deriveRuleClassName = (key, options) => deriveFromSpec(key, ruleClassnameSources[key], {}, options);
|
|
976
|
+
/**
|
|
977
|
+
* Classnames for each element rendered by a rule group, including its conditionally-classed
|
|
978
|
+
* `header`. This is the framework-agnostic core of the `classNames` object returned by the
|
|
979
|
+
* `useRuleGroup` hook.
|
|
980
|
+
*
|
|
981
|
+
* @group Query Tools
|
|
982
|
+
*/
|
|
983
|
+
const deriveRuleGroupClassNames = (options) => deriveFromSpecs(ruleGroupClassnameSources, options, options);
|
|
984
|
+
const deriveOuterClassName = (standardKey, conditions, options) => {
|
|
985
|
+
const { classNames, suppressStandardClassnames, leadingClassNames = [], validationClassName } = options;
|
|
986
|
+
return clsx(...leadingClassNames, suppressStandardClassnames || standardClassnames[standardKey], classNames?.[standardKey], ...conditionArgs(conditions, options, options), validationClassName);
|
|
987
|
+
};
|
|
988
|
+
/**
|
|
989
|
+
* The outer (wrapper) classname for a rule, including every conditional state class.
|
|
990
|
+
*
|
|
991
|
+
* @group Query Tools
|
|
992
|
+
*/
|
|
993
|
+
const deriveRuleOuterClassName = (options) => deriveOuterClassName("rule", ruleOuterConditions, options);
|
|
994
|
+
/**
|
|
995
|
+
* The outer (wrapper) classname for a rule group, including every conditional state class.
|
|
996
|
+
*
|
|
997
|
+
* A group reflects fewer drag-and-drop states than a rule—`dndOver`, `dndCopy`,
|
|
998
|
+
* `dndDropNotAllowed`, and `hasSubQuery` do not apply—so this is not interchangeable with
|
|
999
|
+
* {@link deriveRuleOuterClassName}.
|
|
1000
|
+
*
|
|
1001
|
+
* @group Query Tools
|
|
1002
|
+
*/
|
|
1003
|
+
const deriveRuleGroupOuterClassName = (options) => deriveOuterClassName("ruleGroup", ruleGroupOuterConditions, options);
|
|
1004
|
+
/**
|
|
1005
|
+
* The outer (wrapper) classname for the query builder itself, including the conditional
|
|
1006
|
+
* `disabled`, `valid`, and `invalid` state classes.
|
|
1007
|
+
*
|
|
1008
|
+
* @group Query Tools
|
|
1009
|
+
*/
|
|
1010
|
+
const deriveQueryBuilderClassNames = (options) => deriveOuterClassName("queryBuilder", queryBuilderOuterConditions, options);
|
|
1011
|
+
//#endregion
|
|
1012
|
+
//#region src/utils/deriveRuleContext.ts
|
|
1013
|
+
/**
|
|
1014
|
+
* Resolves the field configuration for a rule's `field`, falling back to a minimal option object
|
|
1015
|
+
* when the field isn't present in the field map.
|
|
1016
|
+
*/
|
|
1017
|
+
const getFieldData = (field, fieldMap) => fieldMap?.[field] ?? {
|
|
1018
|
+
name: field,
|
|
1019
|
+
value: field,
|
|
1020
|
+
label: field
|
|
1021
|
+
};
|
|
1022
|
+
/**
|
|
1023
|
+
* The input type for a rule. A field's own `inputType` takes precedence over `getInputType`.
|
|
1024
|
+
*/
|
|
1025
|
+
const getRuleInputType = (field, operator, fieldData, getInputType) => fieldData.inputType ?? getInputType(field, operator, { fieldData });
|
|
1026
|
+
/**
|
|
1027
|
+
* Whether the value editor(s) should be hidden for an operator, based on its `arity`.
|
|
1028
|
+
*/
|
|
1029
|
+
const hideValueControlsForOperator = (operatorObject) => {
|
|
1030
|
+
const arity = operatorObject?.arity;
|
|
1031
|
+
return typeof arity === "string" && arity === "unary" || typeof arity === "number" && arity < 2;
|
|
1032
|
+
};
|
|
1033
|
+
/**
|
|
1034
|
+
* Value source options for a rule. A `valueSource` present on the rule but absent from the
|
|
1035
|
+
* configured list is appended, so the current selection is always representable.
|
|
1036
|
+
*/
|
|
1037
|
+
const getRuleValueSourceOptions = (rule, fieldData, getValueSources) => {
|
|
1038
|
+
const configuredVSs = getValueSources(rule.field, rule.operator, { fieldData });
|
|
1039
|
+
if (rule.valueSource && !getOption(configuredVSs, rule.valueSource)) return [...configuredVSs, {
|
|
1040
|
+
name: rule.valueSource,
|
|
1041
|
+
value: rule.valueSource,
|
|
1042
|
+
label: rule.valueSource
|
|
1043
|
+
}];
|
|
1044
|
+
return configuredVSs;
|
|
1045
|
+
};
|
|
1046
|
+
/**
|
|
1047
|
+
* Normalizes the result of `getParameters` to a non-empty list or `null`.
|
|
1048
|
+
*/
|
|
1049
|
+
const getParametersAsList = (parameters) => parameters && parameters.length > 0 ? parameters : null;
|
|
1050
|
+
/**
|
|
1051
|
+
* The value editor type for a rule. `valueSource: "field"` always uses a select list, and
|
|
1052
|
+
* `valueSource: "parameter"` uses a (multi)select when parameters are available.
|
|
1053
|
+
*/
|
|
1054
|
+
const getRuleValueEditorType = (rule, fieldData, parametersAsList, getValueEditorType) => rule.valueSource === "field" ? "select" : rule.valueSource === "parameter" ? parametersAsList ? lc(rule.operator) === "in" || lc(rule.operator) === "notin" ? "multiselect" : "select" : "text" : getValueEditorType(rule.field, rule.operator, { fieldData });
|
|
1055
|
+
/**
|
|
1056
|
+
* The option list presented by a rule's value editor, resolved from its `valueSource`.
|
|
1057
|
+
*/
|
|
1058
|
+
const getRuleValues = (rule, fieldData, fields, parametersAsList, getValues) => {
|
|
1059
|
+
const v = rule.valueSource === "field" ? filterFieldsByComparator(fieldData, fields, rule.operator) : rule.valueSource === "parameter" ? parametersAsList ?? [] : getValues(rule.field, rule.operator, { fieldData });
|
|
1060
|
+
return isFlexibleOptionArray(v) || isFlexibleOptionGroupArray(v) ? toFullOptionList(v) : v;
|
|
1061
|
+
};
|
|
1062
|
+
/**
|
|
1063
|
+
* The validation result for a rule: the entry from a query-level {@link ValidationMap} if present,
|
|
1064
|
+
* otherwise the field's own `validator` result, otherwise `null`.
|
|
1065
|
+
*/
|
|
1066
|
+
const getRuleValidationResult = (rule, fieldData, validationMap = {}, id = rule.id ?? "") => validationMap[id] ?? (typeof fieldData.validator === "function" ? fieldData.validator(rule) : null);
|
|
1067
|
+
/**
|
|
1068
|
+
* Resolves everything about a single rule that depends on the field/operator configuration:
|
|
1069
|
+
* its field data, operators, value editor type, value list, value sources, match modes, and
|
|
1070
|
+
* validation result.
|
|
1071
|
+
*
|
|
1072
|
+
* This is the framework-agnostic core of the `useRule` hook, shared with
|
|
1073
|
+
* {@link QueryManager.getRuleContext} so that non-React implementations derive identical results.
|
|
1074
|
+
* It performs no memoization; callers are responsible for caching as appropriate.
|
|
1075
|
+
*
|
|
1076
|
+
* @group Query Tools
|
|
1077
|
+
*/
|
|
1078
|
+
const deriveRuleContext = (rule, resolvers, options = {}) => {
|
|
1079
|
+
const { fields, fieldMap, getInputType, getMatchModes, getOperators, getParameters, getValueEditorType, getValues, getValueSources, getSubQueryBuilderProps } = resolvers;
|
|
1080
|
+
const fieldData = getFieldData(rule.field, fieldMap);
|
|
1081
|
+
const inputType = fieldData.inputType ?? getInputType(rule.field, rule.operator, { fieldData });
|
|
1082
|
+
const matchModes = getMatchModes(rule.field, { fieldData });
|
|
1083
|
+
const operators = getOperators(rule.field, { fieldData });
|
|
1084
|
+
const operatorObject = getOption(operators, rule.operator);
|
|
1085
|
+
const valueSourceOptions = getRuleValueSourceOptions(rule, fieldData, getValueSources);
|
|
1086
|
+
const parameters = getParametersAsList(getParameters(rule.field, rule.operator, { fieldData }));
|
|
1087
|
+
return {
|
|
1088
|
+
fieldData,
|
|
1089
|
+
hideValueControls: hideValueControlsForOperator(operatorObject),
|
|
1090
|
+
inputType,
|
|
1091
|
+
matchModes,
|
|
1092
|
+
operatorObject,
|
|
1093
|
+
operators,
|
|
1094
|
+
parameters,
|
|
1095
|
+
validationResult: getRuleValidationResult(rule, fieldData, options.validationMap, options.id),
|
|
1096
|
+
valueEditorType: getRuleValueEditorType(rule, fieldData, parameters, getValueEditorType),
|
|
1097
|
+
values: getRuleValues(rule, fieldData, fields, parameters, getValues),
|
|
1098
|
+
valueSourceOptions,
|
|
1099
|
+
valueSources: valueSourceOptions.map(({ value }) => value),
|
|
1100
|
+
subQueryBuilderProps: getSubQueryBuilderProps?.(rule.field, { fieldData }) ?? {}
|
|
1101
|
+
};
|
|
1102
|
+
};
|
|
1103
|
+
/**
|
|
1104
|
+
* The effective combinator for a group: its own `combinator` when it has one, otherwise the
|
|
1105
|
+
* first configured combinator (which is the case for groups with independent combinators).
|
|
1106
|
+
*
|
|
1107
|
+
* This intentionally covers only the current property-based API. The `RuleGroup` component
|
|
1108
|
+
* additionally falls back to its deprecated `combinator` prop; that fallback stays in the hook.
|
|
1109
|
+
*/
|
|
1110
|
+
const getRuleGroupCombinator = (ruleGroup, combinators) => isRuleGroupType(ruleGroup) ? ruleGroup.combinator : getFirstOption(combinators) ?? "";
|
|
1111
|
+
/**
|
|
1112
|
+
* Resolves everything about a rule group that depends on the combinator configuration, plus its
|
|
1113
|
+
* validation result.
|
|
1114
|
+
*
|
|
1115
|
+
* Note that unlike {@link deriveRuleContext}, there is no field-level validator fallback—a
|
|
1116
|
+
* group's validation result comes only from the query-level {@link ValidationMap}.
|
|
1117
|
+
*
|
|
1118
|
+
* @group Query Tools
|
|
1119
|
+
*/
|
|
1120
|
+
const deriveRuleGroupContext = (ruleGroup, combinators, options = {}) => {
|
|
1121
|
+
const independentCombinators = isRuleGroupTypeIC(ruleGroup);
|
|
1122
|
+
const combinator = getRuleGroupCombinator(ruleGroup, combinators);
|
|
1123
|
+
const combinatorObject = getOption(combinators, combinator);
|
|
1124
|
+
return {
|
|
1125
|
+
combinator,
|
|
1126
|
+
combinatorObject,
|
|
1127
|
+
combinators,
|
|
1128
|
+
combinatorBasedClassName: independentCombinators ? null : combinatorObject?.className ?? "",
|
|
1129
|
+
independentCombinators,
|
|
1130
|
+
validationResult: (options.validationMap ?? {})[options.id ?? ruleGroup.id ?? ""] ?? null
|
|
1131
|
+
};
|
|
1132
|
+
};
|
|
1133
|
+
//#endregion
|
|
1134
|
+
//#region src/utils/deriveValueEditor.ts
|
|
1135
|
+
/** Operators whose value is a list rather than a single scalar. */
|
|
1136
|
+
const multiValueOperators = /* @__PURE__ */ new Set([
|
|
1137
|
+
"between",
|
|
1138
|
+
"notBetween",
|
|
1139
|
+
"in",
|
|
1140
|
+
"notIn"
|
|
1141
|
+
]);
|
|
1142
|
+
/**
|
|
1143
|
+
* Whether an operator's value is a list of two bounds.
|
|
1144
|
+
*
|
|
1145
|
+
* @group Value Editors
|
|
1146
|
+
*/
|
|
1147
|
+
const isBetweenOperator = (operator) => operator === "between" || operator === "notBetween";
|
|
1148
|
+
/**
|
|
1149
|
+
* Determines whether a rule's `value` needs to be collapsed to a single element because it no
|
|
1150
|
+
* longer represents a list, and what it should become.
|
|
1151
|
+
*
|
|
1152
|
+
* This happens when the value is an array (or a comma-containing string in a `number` input,
|
|
1153
|
+
* which `<input type="number">` can't display) while the operator is not one of the multi-value
|
|
1154
|
+
* operators and the editor is not a multiselect—typically right after the operator changes from
|
|
1155
|
+
* `"in"` or `"between"` to something else.
|
|
1156
|
+
*
|
|
1157
|
+
* The React `useValueEditor` hook applies the result in an effect; other implementations may
|
|
1158
|
+
* apply it wherever is idiomatic.
|
|
1159
|
+
*
|
|
1160
|
+
* @group Value Editors
|
|
1161
|
+
*/
|
|
1162
|
+
const getValueEditorReset = ({ skipHook, type, operator, value, inputType }) => {
|
|
1163
|
+
return !skipHook && type !== "multiselect" && !multiValueOperators.has(operator) && (Array.isArray(value) || inputType === "number" && typeof value === "string" && value.includes(",")) ? {
|
|
1164
|
+
reset: true,
|
|
1165
|
+
value: toArray(value, { retainEmptyStrings: true })[0] ?? ""
|
|
1166
|
+
} : {
|
|
1167
|
+
reset: false,
|
|
1168
|
+
value
|
|
1169
|
+
};
|
|
1170
|
+
};
|
|
1171
|
+
/**
|
|
1172
|
+
* Produces the next value for a series of value editors when the editor at `index` changes.
|
|
1173
|
+
*
|
|
1174
|
+
* For `between`/`notBetween`, editing the first bound guarantees an array of at least two
|
|
1175
|
+
* elements, seeding the second from the first available option. The result is a comma-joined
|
|
1176
|
+
* string unless `listsAsArrays` is `true`.
|
|
1177
|
+
*
|
|
1178
|
+
* @group Value Editors
|
|
1179
|
+
*/
|
|
1180
|
+
const getMultiValueUpdate = ({ value, index, valueAsArray, operator, values, listsAsArrays, parseNumberMethod }) => {
|
|
1181
|
+
const parsedVal = parseNumber(value, { parseNumbers: parseNumberMethod });
|
|
1182
|
+
const needsBetweenFix = index === 0 && isBetweenOperator(operator) && (valueAsArray.length < 2 || valueAsArray[1] === void 0);
|
|
1183
|
+
if (valueAsArray[index] === parsedVal && !needsBetweenFix) return listsAsArrays ? valueAsArray : joinWith(valueAsArray, ",");
|
|
1184
|
+
const v = [...valueAsArray];
|
|
1185
|
+
v[index] = parsedVal;
|
|
1186
|
+
if (needsBetweenFix) v[1] = getFirstOption(values) ?? "";
|
|
1187
|
+
return listsAsArrays ? v : joinWith(v, ",");
|
|
1188
|
+
};
|
|
1189
|
+
/**
|
|
1190
|
+
* Coerces a value to a `bigint`, falling back to the parsed number when it can't be represented
|
|
1191
|
+
* as one (an empty string or a decimal, for example).
|
|
1192
|
+
*
|
|
1193
|
+
* @group Value Editors
|
|
1194
|
+
*/
|
|
1195
|
+
const coerceBigIntValue = (value, parseNumberMethod) => {
|
|
1196
|
+
const valAsMaybeNumber = parseNumber(value, {
|
|
1197
|
+
parseNumbers: parseNumberMethod,
|
|
1198
|
+
bigIntOnOverflow: true
|
|
1199
|
+
});
|
|
1200
|
+
try {
|
|
1201
|
+
return BigInt(valAsMaybeNumber);
|
|
1202
|
+
} catch {
|
|
1203
|
+
return valAsMaybeNumber;
|
|
1204
|
+
}
|
|
1205
|
+
};
|
|
1206
|
+
/**
|
|
1207
|
+
* The `type` attribute an `<input>` should use for a rule. `bigint` values and the `in`/`notIn`
|
|
1208
|
+
* operators (whose value is a comma-separated list) both require a text input.
|
|
1209
|
+
*
|
|
1210
|
+
* @group Value Editors
|
|
1211
|
+
*/
|
|
1212
|
+
const coerceInputType = (inputType, operator) => inputType === "bigint" || operator === "in" || operator === "notIn" ? "text" : inputType || "text";
|
|
1213
|
+
/**
|
|
1214
|
+
* Produces the next value for a value selector. Multiselect values are normalized to an array
|
|
1215
|
+
* first, then comma-joined unless `listsAsArrays` is `true`. Single-select values pass through.
|
|
1216
|
+
*
|
|
1217
|
+
* @group Value Editors
|
|
1218
|
+
*/
|
|
1219
|
+
const getValueSelectorUpdate = (value, { multiple, listsAsArrays } = {}) => {
|
|
1220
|
+
if (!multiple) return value;
|
|
1221
|
+
const valueAsArray = toArray(value);
|
|
1222
|
+
return listsAsArrays ? valueAsArray : joinWith(valueAsArray, ",");
|
|
1223
|
+
};
|
|
1224
|
+
/**
|
|
1225
|
+
* Normalizes a value selector's current value for display. Multiselect values become an array of
|
|
1226
|
+
* strings so they match option names, which are always strings (e.g. `[42]` becomes `["42"]`).
|
|
1227
|
+
*
|
|
1228
|
+
* @group Value Editors
|
|
1229
|
+
*/
|
|
1230
|
+
const normalizeValueSelectorValue = (value, multiple) => multiple ? toArray(value).map(String) : value;
|
|
1231
|
+
//#endregion
|
|
1232
|
+
//#region src/utils/pathUtils.ts
|
|
1233
|
+
/**
|
|
1234
|
+
* Returns the {@link RuleType} or {@link RuleGroupType}/{@link RuleGroupTypeIC}
|
|
1235
|
+
* at the given path within a query.
|
|
1236
|
+
*/
|
|
1237
|
+
const findPath = (path, query) => {
|
|
1238
|
+
let target = query;
|
|
1239
|
+
let level = 0;
|
|
1240
|
+
while (level < path.length && target && isRuleGroup(target)) {
|
|
1241
|
+
const t = target.rules[path[level]];
|
|
1242
|
+
target = typeof t === "string" ? null : t;
|
|
1243
|
+
level++;
|
|
1244
|
+
}
|
|
1245
|
+
return level < path.length ? null : target;
|
|
1246
|
+
};
|
|
1247
|
+
/**
|
|
1248
|
+
* Returns the {@link RuleType} or {@link RuleGroupType}/{@link RuleGroupTypeIC}
|
|
1249
|
+
* with the given `id` within a query.
|
|
1250
|
+
*/
|
|
1251
|
+
const findID = (id, query) => {
|
|
1252
|
+
if (query.id === id) return query;
|
|
1253
|
+
for (const rule of query.rules) {
|
|
1254
|
+
if (typeof rule === "string") continue;
|
|
1255
|
+
if (rule.id === id) return rule;
|
|
1256
|
+
else if (isRuleGroup(rule)) {
|
|
1257
|
+
const subRule = findID(id, rule);
|
|
1258
|
+
if (subRule) return subRule;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
return null;
|
|
1262
|
+
};
|
|
1263
|
+
/**
|
|
1264
|
+
* Returns the {@link Path} of the {@link RuleType} or {@link RuleGroupType}/{@link RuleGroupTypeIC}
|
|
1265
|
+
* with the given `id` within a query.
|
|
1266
|
+
*/
|
|
1267
|
+
const getPathOfID = (id, query) => {
|
|
1268
|
+
if (query.id === id) return [];
|
|
1269
|
+
const idx = query.rules.findIndex((r) => !(typeof r === "string") && r.id === id);
|
|
1270
|
+
if (idx >= 0) return [idx];
|
|
1271
|
+
for (const [i, r] of Object.entries(query.rules)) if (isRuleGroup(r)) {
|
|
1272
|
+
const subPath = getPathOfID(id, r);
|
|
1273
|
+
if (Array.isArray(subPath)) return [Number.parseInt(i), ...subPath];
|
|
1274
|
+
}
|
|
1275
|
+
return null;
|
|
1276
|
+
};
|
|
1277
|
+
/**
|
|
1278
|
+
* Truncates the last element of an array and returns the result as a new array.
|
|
1279
|
+
*/
|
|
1280
|
+
const getParentPath = (path) => path.slice(0, -1);
|
|
1281
|
+
/**
|
|
1282
|
+
* Determines if two paths (each `Path`) are equivalent.
|
|
1283
|
+
*/
|
|
1284
|
+
const pathsAreEqual = (path1, path2) => {
|
|
1285
|
+
if (path1.length !== path2.length) return false;
|
|
1286
|
+
for (let i = 0; i < path1.length; i++) if (path1[i] !== path2[i]) return false;
|
|
1287
|
+
return true;
|
|
1288
|
+
};
|
|
1289
|
+
/**
|
|
1290
|
+
* Determines if the first path is an ancestor of the second path. The first path must
|
|
1291
|
+
* be shorter and exactly match the second path up through the length of the first path.
|
|
1292
|
+
*/
|
|
1293
|
+
const isAncestor = (maybeAncestor, path) => {
|
|
1294
|
+
if (maybeAncestor.length >= path.length) return false;
|
|
1295
|
+
for (let i = 0; i < maybeAncestor.length; i++) if (maybeAncestor[i] !== path[i]) return false;
|
|
1296
|
+
return true;
|
|
1297
|
+
};
|
|
1298
|
+
/**
|
|
1299
|
+
* Finds the deepest/longest path that two paths have in common.
|
|
1300
|
+
*/
|
|
1301
|
+
const getCommonAncestorPath = (path1, path2) => {
|
|
1302
|
+
const commonAncestorPath = [];
|
|
1303
|
+
const parentPath1 = getParentPath(path1);
|
|
1304
|
+
const parentPath2 = getParentPath(path2);
|
|
1305
|
+
let i = 0;
|
|
1306
|
+
while (i < parentPath1.length && i < parentPath2.length && parentPath1[i] === parentPath2[i]) {
|
|
1307
|
+
commonAncestorPath.push(parentPath2[i]);
|
|
1308
|
+
i++;
|
|
1309
|
+
}
|
|
1310
|
+
return commonAncestorPath;
|
|
1311
|
+
};
|
|
1312
|
+
/**
|
|
1313
|
+
* Determines if the rule or group at the specified path is either disabled itself
|
|
1314
|
+
* or disabled by an ancestor group.
|
|
1315
|
+
*/
|
|
1316
|
+
const pathIsDisabled = (path, query) => {
|
|
1317
|
+
let disabled = !!query.disabled;
|
|
1318
|
+
let target = query;
|
|
1319
|
+
let level = 0;
|
|
1320
|
+
while (level < path.length && !disabled && isRuleGroup(target)) {
|
|
1321
|
+
const t = target.rules[path[level]];
|
|
1322
|
+
if (isPojo(t) && (isRuleGroup(t) || "field" in t && !!t.field)) {
|
|
1323
|
+
disabled = !!t.disabled;
|
|
1324
|
+
target = t;
|
|
1325
|
+
}
|
|
1326
|
+
level++;
|
|
1327
|
+
}
|
|
1328
|
+
return disabled;
|
|
1329
|
+
};
|
|
1330
|
+
/**
|
|
1331
|
+
* Determines if the rule or group at the specified path is disabled by `disabledPaths`—the array
|
|
1332
|
+
* form of the `QueryBuilder` `disabled` prop, which disables nodes by position rather than by a
|
|
1333
|
+
* `disabled` property on the node itself. A path is disabled if it appears in `disabledPaths` or
|
|
1334
|
+
* descends from a path that does.
|
|
1335
|
+
*
|
|
1336
|
+
* @group Paths
|
|
1337
|
+
*/
|
|
1338
|
+
const pathIsDisabledByPaths = (path, disabledPaths = []) => disabledPaths.some((p) => pathsAreEqual(p, path) || isAncestor(p, path));
|
|
1339
|
+
/**
|
|
1340
|
+
* Builds the {@link PathInfo} for each child of a group at `path`. A child is disabled if its
|
|
1341
|
+
* parent is disabled or if its own path appears in `disabledPaths`.
|
|
1342
|
+
*
|
|
1343
|
+
* @group Paths
|
|
1344
|
+
*/
|
|
1345
|
+
const derivePathInfo = (path, childCount, { disabled = false, disabledPaths = [] } = {}) => {
|
|
1346
|
+
const paths = [];
|
|
1347
|
+
for (let i = 0; i < childCount; i++) {
|
|
1348
|
+
const thisPath = [...path, i];
|
|
1349
|
+
paths[i] = {
|
|
1350
|
+
path: thisPath,
|
|
1351
|
+
disabled: disabled || disabledPaths.some((p) => pathsAreEqual(thisPath, p))
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
return paths;
|
|
1355
|
+
};
|
|
1356
|
+
//#endregion
|
|
1357
|
+
//#region src/utils/generateAccessibleDescription.ts
|
|
1358
|
+
/**
|
|
1359
|
+
* The default `accessibleDescriptionGenerator`. Produces the `aria-label` for a rule group:
|
|
1360
|
+
* `"Query builder"` for the root group, `"Rule group at path 0-1"` for any other.
|
|
1361
|
+
*
|
|
1362
|
+
* @group Accessibility
|
|
1363
|
+
*/
|
|
1364
|
+
const generateAccessibleDescription = (params) => pathsAreEqual([], params.path) ? `Query builder` : `Rule group at path ${params.path.join("-")}`;
|
|
1365
|
+
//#endregion
|
|
1366
|
+
//#region src/utils/getMatchModesUtil.ts
|
|
1367
|
+
const dummyFD = {
|
|
1368
|
+
name: "name",
|
|
1369
|
+
value: "name",
|
|
1370
|
+
matchModes: null,
|
|
1371
|
+
label: "label"
|
|
1372
|
+
};
|
|
1373
|
+
/**
|
|
1374
|
+
* Utility function to get the match modes array for the given
|
|
1375
|
+
* field. If the field definition does not define a `matchModes`
|
|
1376
|
+
* property, the `getMatchModes` prop is used. Returns
|
|
1377
|
+
* `FullOption<MatchMode>[]` of all match modes by default.
|
|
1378
|
+
*/
|
|
1379
|
+
const getMatchModesUtil = (fieldData, getMatchModes) => {
|
|
1380
|
+
const fd = fieldData ? toFullOption(fieldData) : /* v8 ignore next -- @preserve */ dummyFD;
|
|
1381
|
+
let matchModes = fd.matchModes ?? false;
|
|
1382
|
+
if (!matchModes && getMatchModes) matchModes = getMatchModes(fd.value, { fieldData: fd });
|
|
1383
|
+
if (matchModes === true) return defaultMatchModes;
|
|
1384
|
+
else if (matchModes === false) return [];
|
|
1385
|
+
if (isFlexibleOptionArray(matchModes)) return toFullOptionList(matchModes);
|
|
1386
|
+
return matchModes?.map((mm) => defaultMatchModes.find((dmm) => dmm.value === lc(mm)) ?? {
|
|
1387
|
+
name: mm,
|
|
1388
|
+
value: mm,
|
|
1389
|
+
label: mm
|
|
1390
|
+
}) ?? [];
|
|
1391
|
+
};
|
|
1392
|
+
//#endregion
|
|
1393
|
+
//#region src/utils/getRuleDefaultValue.ts
|
|
1394
|
+
const getFirstOptionsFrom = (opts, r, listsAsArrays) => {
|
|
1395
|
+
const firstOption = getFirstOption(opts);
|
|
1396
|
+
if (r.operator === "between" || r.operator === "notBetween") {
|
|
1397
|
+
const valueAsArray = [firstOption, firstOption];
|
|
1398
|
+
return listsAsArrays ? valueAsArray : joinWith(valueAsArray.map((v) => v ?? /* v8 ignore start -- @preserve */ ""), ",");
|
|
1399
|
+
}
|
|
1400
|
+
return firstOption;
|
|
1401
|
+
};
|
|
1402
|
+
/**
|
|
1403
|
+
* Computes the default `value` for a rule given its `field`/`operator`/`valueSource`, mirroring
|
|
1404
|
+
* the precedence the {@link react-querybuilder!QueryBuilder QueryBuilder} applies: a field's
|
|
1405
|
+
* `defaultValue`, then a `getDefaultValue` override, then a value derived from the field/operator's
|
|
1406
|
+
* value list and editor type (first option for `select`/`radio`, `false` for `checkbox`, paired for
|
|
1407
|
+
* `between`/`notBetween`), or a comparator-valid field when `valueSource` is `'field'`. Falls back
|
|
1408
|
+
* to `''`.
|
|
1409
|
+
*
|
|
1410
|
+
* @group Option Lists
|
|
1411
|
+
*/
|
|
1412
|
+
const getRuleDefaultValue = (rule, options) => {
|
|
1413
|
+
const { fieldData, fields, getValueEditorType, getValues, getDefaultValue, getParameters, listsAsArrays } = options;
|
|
1414
|
+
if (fieldData?.defaultValue !== void 0 && fieldData.defaultValue !== null) return fieldData.defaultValue;
|
|
1415
|
+
else if (getDefaultValue) return getDefaultValue(rule, { fieldData });
|
|
1416
|
+
let value = "";
|
|
1417
|
+
const values = getValues(rule.field, rule.operator, { fieldData });
|
|
1418
|
+
if (rule.valueSource === "field") {
|
|
1419
|
+
const filteredFields = filterFieldsByComparator(fieldData, fields, rule.operator);
|
|
1420
|
+
value = filteredFields.length > 0 ? getFirstOptionsFrom(filteredFields, rule, listsAsArrays) : "";
|
|
1421
|
+
} else if (rule.valueSource === "parameter") {
|
|
1422
|
+
const parameters = getParameters ? getParameters(rule.field, rule.operator, { fieldData }) : null;
|
|
1423
|
+
value = parameters && parameters.length > 0 ? getFirstOption(parameters) : "";
|
|
1424
|
+
} else if (values.length > 0) {
|
|
1425
|
+
const editorType = getValueEditorType(rule.field, rule.operator, { fieldData });
|
|
1426
|
+
if (editorType === "multiselect") value = listsAsArrays ? [] : "";
|
|
1427
|
+
else if (editorType === "select" || editorType === "radio") value = getFirstOptionsFrom(values, rule, listsAsArrays);
|
|
1428
|
+
} else if (getValueEditorType(rule.field, rule.operator, { fieldData }) === "checkbox") value = false;
|
|
1429
|
+
return value;
|
|
1430
|
+
};
|
|
1431
|
+
//#endregion
|
|
1432
|
+
//#region src/utils/getValidationClassNames.ts
|
|
1433
|
+
/**
|
|
1434
|
+
* Gets the standard classname for valid or invalid components
|
|
1435
|
+
* based on the given validation result.
|
|
1436
|
+
*/
|
|
1437
|
+
const getValidationClassNames = (validationResult) => {
|
|
1438
|
+
const valid = typeof validationResult === "boolean" ? validationResult : typeof validationResult === "object" && validationResult !== null ? validationResult.valid : null;
|
|
1439
|
+
return typeof valid === "boolean" ? valid ? standardClassnames.valid : standardClassnames.invalid : "";
|
|
1440
|
+
};
|
|
1441
|
+
//#endregion
|
|
1442
|
+
//#region src/utils/immerInstances.ts
|
|
1443
|
+
/**
|
|
1444
|
+
* Globally enables or disables immer's auto-freeze, i.e. the deep freeze applied to every query
|
|
1445
|
+
* returned by {@link add}, {@link update}, {@link remove}, {@link move}, {@link insert}, and
|
|
1446
|
+
* {@link group}.
|
|
1447
|
+
*
|
|
1448
|
+
* Re-exported from immer so that consumers mixing these tools with their own `produce` calls can
|
|
1449
|
+
* switch both at once. Prefer the per-call `freeze` option, or the {@link QueryManager} option of
|
|
1450
|
+
* the same name, when the change should not be process-wide. A per-call `freeze: false` wins
|
|
1451
|
+
* regardless of this setting; a per-call `freeze: true` does not re-enable freezing once this has
|
|
1452
|
+
* turned it off, since it selects immer's default instance.
|
|
1453
|
+
*
|
|
1454
|
+
* @group Query Tools
|
|
1455
|
+
*/
|
|
1456
|
+
const setAutoFreeze = setAutoFreeze$1;
|
|
1457
|
+
const unfrozenImmer = new Immer({ autoFreeze: false });
|
|
1458
|
+
/**
|
|
1459
|
+
* The `produce` implementation matching the requested freeze behavior.
|
|
1460
|
+
*
|
|
1461
|
+
* @internal
|
|
1462
|
+
*/
|
|
1463
|
+
const producerFor = (freeze = true) => freeze ? produce : unfrozenImmer.produce;
|
|
1464
|
+
//#endregion
|
|
1465
|
+
//#region src/utils/mergeAnyTranslations.ts
|
|
1466
|
+
/**
|
|
1467
|
+
* Merges any number of partial translations into a single definition.
|
|
1468
|
+
*/
|
|
1469
|
+
const mergeAnyTranslations = (base, ...otherTranslations) => {
|
|
1470
|
+
const result = { ...base };
|
|
1471
|
+
for (const translations of otherTranslations)
|
|
1472
|
+
// v8 ignore else
|
|
1473
|
+
if (translations) for (const key of objectKeys(translations)) {
|
|
1474
|
+
if (isUnsafeKey(key)) continue;
|
|
1475
|
+
if (result[key]) result[key] = {
|
|
1476
|
+
...result[key],
|
|
1477
|
+
...translations[key]
|
|
1478
|
+
};
|
|
1479
|
+
else result[key] = { ...translations[key] };
|
|
1480
|
+
}
|
|
1481
|
+
return result;
|
|
1482
|
+
};
|
|
1483
|
+
const mergeAnyTranslation = (el, keyPropContextMap, defaults) => {
|
|
1484
|
+
if (isUnsafeKey(el)) return void 0;
|
|
1485
|
+
const finalKeys = objectEntries(keyPropContextMap).map(([key, [pT, cT]]) => [key, pT ?? cT ?? defaults?.[el]?.[key]]).filter((k) => !isUnsafeKey(k[0]) && !!k[1]);
|
|
1486
|
+
if (finalKeys.length > 0 || defaults) {
|
|
1487
|
+
const defaultProperties = defaults?.[el] ?? {};
|
|
1488
|
+
const finalObject = Object.assign({}, defaultProperties, Object.fromEntries(finalKeys));
|
|
1489
|
+
return { [el]: finalObject };
|
|
1490
|
+
}
|
|
1491
|
+
};
|
|
1492
|
+
//#endregion
|
|
1493
|
+
//#region src/utils/mergeClassnames.ts
|
|
1494
|
+
const joinClassnamesByName = (name, args) => {
|
|
1495
|
+
let result = "";
|
|
1496
|
+
for (let i = 0; i < args.length; i++) {
|
|
1497
|
+
const v = args[i]?.[name];
|
|
1498
|
+
if (!v) continue;
|
|
1499
|
+
const s = typeof v === "string" ? v : clsx(v);
|
|
1500
|
+
if (s) result = result ? `${result} ${s}` : s;
|
|
1501
|
+
}
|
|
1502
|
+
return result;
|
|
1503
|
+
};
|
|
1504
|
+
/**
|
|
1505
|
+
* Merges a list of partial {@link Classnames} definitions into a single definition.
|
|
1506
|
+
*/
|
|
1507
|
+
const mergeClassnames = (...args) => ({
|
|
1508
|
+
queryBuilder: joinClassnamesByName("queryBuilder", args),
|
|
1509
|
+
ruleGroup: joinClassnamesByName("ruleGroup", args),
|
|
1510
|
+
header: joinClassnamesByName("header", args),
|
|
1511
|
+
body: joinClassnamesByName("body", args),
|
|
1512
|
+
combinators: joinClassnamesByName("combinators", args),
|
|
1513
|
+
addRule: joinClassnamesByName("addRule", args),
|
|
1514
|
+
addGroup: joinClassnamesByName("addGroup", args),
|
|
1515
|
+
cloneRule: joinClassnamesByName("cloneRule", args),
|
|
1516
|
+
cloneGroup: joinClassnamesByName("cloneGroup", args),
|
|
1517
|
+
removeGroup: joinClassnamesByName("removeGroup", args),
|
|
1518
|
+
rule: joinClassnamesByName("rule", args),
|
|
1519
|
+
fields: joinClassnamesByName("fields", args),
|
|
1520
|
+
operators: joinClassnamesByName("operators", args),
|
|
1521
|
+
value: joinClassnamesByName("value", args),
|
|
1522
|
+
removeRule: joinClassnamesByName("removeRule", args),
|
|
1523
|
+
notToggle: joinClassnamesByName("notToggle", args),
|
|
1524
|
+
shiftActions: joinClassnamesByName("shiftActions", args),
|
|
1525
|
+
undoRedoActions: joinClassnamesByName("undoRedoActions", args),
|
|
1526
|
+
undoAction: joinClassnamesByName("undoAction", args),
|
|
1527
|
+
redoAction: joinClassnamesByName("redoAction", args),
|
|
1528
|
+
dragHandle: joinClassnamesByName("dragHandle", args),
|
|
1529
|
+
lockRule: joinClassnamesByName("lockRule", args),
|
|
1530
|
+
lockGroup: joinClassnamesByName("lockGroup", args),
|
|
1531
|
+
muteRule: joinClassnamesByName("muteRule", args),
|
|
1532
|
+
muteGroup: joinClassnamesByName("muteGroup", args),
|
|
1533
|
+
muted: joinClassnamesByName("muted", args),
|
|
1534
|
+
valueSource: joinClassnamesByName("valueSource", args),
|
|
1535
|
+
actionElement: joinClassnamesByName("actionElement", args),
|
|
1536
|
+
valueSelector: joinClassnamesByName("valueSelector", args),
|
|
1537
|
+
betweenRules: joinClassnamesByName("betweenRules", args),
|
|
1538
|
+
valid: joinClassnamesByName("valid", args),
|
|
1539
|
+
invalid: joinClassnamesByName("invalid", args),
|
|
1540
|
+
dndDragging: joinClassnamesByName("dndDragging", args),
|
|
1541
|
+
dndOver: joinClassnamesByName("dndOver", args),
|
|
1542
|
+
dndCopy: joinClassnamesByName("dndCopy", args),
|
|
1543
|
+
dndGroup: joinClassnamesByName("dndGroup", args),
|
|
1544
|
+
dndDropNotAllowed: joinClassnamesByName("dndDropNotAllowed", args),
|
|
1545
|
+
dndPreviewPosition: joinClassnamesByName("dndPreviewPosition", args),
|
|
1546
|
+
dndHidden: joinClassnamesByName("dndHidden", args),
|
|
1547
|
+
disabled: joinClassnamesByName("disabled", args),
|
|
1548
|
+
valueListItem: joinClassnamesByName("valueListItem", args),
|
|
1549
|
+
matchMode: joinClassnamesByName("matchMode", args),
|
|
1550
|
+
matchThreshold: joinClassnamesByName("matchThreshold", args),
|
|
1551
|
+
branches: joinClassnamesByName("branches", args),
|
|
1552
|
+
hasSubQuery: joinClassnamesByName("hasSubQuery", args),
|
|
1553
|
+
loading: joinClassnamesByName("loading", args),
|
|
1554
|
+
valueDateTimeRelative: joinClassnamesByName("valueDateTimeRelative", args)
|
|
1555
|
+
});
|
|
1556
|
+
//#endregion
|
|
1557
|
+
//#region src/utils/optionResolvers.ts
|
|
1558
|
+
/**
|
|
1559
|
+
* Resolves the operator list for a field, applying the same precedence as the `QueryBuilder`
|
|
1560
|
+
* component: the field's own `operators`, then the `getOperators` callback, then the
|
|
1561
|
+
* query-level operator list.
|
|
1562
|
+
*
|
|
1563
|
+
* @group Option Lists
|
|
1564
|
+
*/
|
|
1565
|
+
const resolveOperatorList = ({ field, fieldData, getOperators, operators, placeholder, baseOption, autoSelectOption }) => prepareOptionList({
|
|
1566
|
+
optionList: fieldData?.operators ?? getOperators?.(field, { fieldData }) ?? operators,
|
|
1567
|
+
placeholder,
|
|
1568
|
+
baseOption,
|
|
1569
|
+
labelMap: defaultOperatorLabelMap,
|
|
1570
|
+
autoSelectOption
|
|
1571
|
+
}).optionList;
|
|
1572
|
+
/**
|
|
1573
|
+
* Resolves the default operator for a field, applying the same precedence as the `QueryBuilder`
|
|
1574
|
+
* component: the field's own `defaultOperator`, then the `getDefaultOperator` option (a string or
|
|
1575
|
+
* a function), then the first available operator.
|
|
1576
|
+
*
|
|
1577
|
+
* @group Option Lists
|
|
1578
|
+
*/
|
|
1579
|
+
const resolveDefaultOperator = ({ field, fieldData, getDefaultOperator, getOperators }) => {
|
|
1580
|
+
if (fieldData?.defaultOperator) return fieldData.defaultOperator;
|
|
1581
|
+
if (getDefaultOperator) return typeof getDefaultOperator === "function" ? getDefaultOperator(field, { fieldData }) : getDefaultOperator;
|
|
1582
|
+
return getFirstOption(getOperators(field, { fieldData })) ?? "";
|
|
1583
|
+
};
|
|
1584
|
+
/**
|
|
1585
|
+
* Resolves the value editor type for a field/operator pair, applying the same precedence as the
|
|
1586
|
+
* `QueryBuilder` component: the field's own `valueEditorType` (a string or a function of the
|
|
1587
|
+
* operator), then the `getValueEditorType` callback, then `"text"`.
|
|
1588
|
+
*
|
|
1589
|
+
* @group Option Lists
|
|
1590
|
+
*/
|
|
1591
|
+
const resolveValueEditorType = ({ field, operator, fieldData, getValueEditorType }) => {
|
|
1592
|
+
if (fieldData?.valueEditorType) return typeof fieldData.valueEditorType === "function" ? fieldData.valueEditorType(operator) : fieldData.valueEditorType;
|
|
1593
|
+
return getValueEditorType?.(field, operator, { fieldData }) ?? "text";
|
|
1594
|
+
};
|
|
1595
|
+
/**
|
|
1596
|
+
* Resolves the value option list for a field/operator pair, applying the same precedence as the
|
|
1597
|
+
* `QueryBuilder` component: the field's own `values`, then the `getValues` callback, then an
|
|
1598
|
+
* empty list.
|
|
1599
|
+
*
|
|
1600
|
+
* @group Option Lists
|
|
1601
|
+
*/
|
|
1602
|
+
const resolveValueList = ({ field, operator, fieldData, getValues, placeholder, baseOption, autoSelectOption }) => prepareOptionList({
|
|
1603
|
+
optionList: fieldData?.values ?? getValues?.(field, operator, { fieldData }) ?? [],
|
|
1604
|
+
placeholder,
|
|
1605
|
+
baseOption,
|
|
1606
|
+
autoSelectOption
|
|
1607
|
+
}).optionList;
|
|
1608
|
+
//#endregion
|
|
1609
|
+
//#region src/utils/optionsEqual.ts
|
|
1610
|
+
const isPlainObject = (x) => {
|
|
1611
|
+
const proto = Object.getPrototypeOf(x);
|
|
1612
|
+
return proto === null || proto === Object.prototype;
|
|
1613
|
+
};
|
|
1614
|
+
/**
|
|
1615
|
+
* Structural equality for {@link QueryManagerOptions} values. Arrays and plain objects are
|
|
1616
|
+
* compared by value; everything else — functions, class instances, `Map`s, `Date`s — by
|
|
1617
|
+
* identity.
|
|
1618
|
+
*
|
|
1619
|
+
* That split is what makes an options object rebuilt on every render compare equal as long as
|
|
1620
|
+
* its _data_ did not change, which is the entire point: a caller that passes object literals
|
|
1621
|
+
* (`fields={[...]}`, `translations={{...}}`) hands a fresh identity to every reconfigure, so an
|
|
1622
|
+
* identity-only comparison would report a change every time.
|
|
1623
|
+
*
|
|
1624
|
+
* Comparing functions by identity is deliberate rather than a limitation. Two functions cannot
|
|
1625
|
+
* be proven equivalent, so a caller that rebuilds `getDefaultValue` per render must either
|
|
1626
|
+
* memoize it or accept the reconfigure.
|
|
1627
|
+
*/
|
|
1628
|
+
const valuesEqual = (a, b) => {
|
|
1629
|
+
if (Object.is(a, b)) return true;
|
|
1630
|
+
if (Array.isArray(a) || Array.isArray(b)) return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((v, i) => valuesEqual(v, b[i]));
|
|
1631
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
1632
|
+
if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b) || !isPlainObject(a)) return false;
|
|
1633
|
+
const aKeys = Object.keys(a);
|
|
1634
|
+
return aKeys.length === Object.keys(b).length && aKeys.every((k) => valuesEqual(a[k], b[k]));
|
|
1635
|
+
};
|
|
1636
|
+
/**
|
|
1637
|
+
* Whether two {@link QueryManagerOptions} objects describe the same configuration, per
|
|
1638
|
+
* {@link valuesEqual}: data by value, functions by identity.
|
|
1639
|
+
*
|
|
1640
|
+
* {@link QueryManager.reconfigure} uses this to gate itself, so most callers never need it
|
|
1641
|
+
* directly. It is exported for framework adapters that decide whether to _call_ `reconfigure`
|
|
1642
|
+
* at all — skipping the call avoids building the merged options object in the first place.
|
|
1643
|
+
*
|
|
1644
|
+
* Nested option objects (`history`, `translations`, …) are descended into rather than compared
|
|
1645
|
+
* by identity, so `{ history: { maxHistory: 10 } }` rebuilt per render compares equal.
|
|
1646
|
+
*/
|
|
1647
|
+
const optionsEqual = (a, b) => valuesEqual(a, b);
|
|
1648
|
+
//#endregion
|
|
1649
|
+
//#region src/utils/preferProp.ts
|
|
1650
|
+
const preferPropDefaultTrue = (prop, context) => prop === false ? false : prop ? true : !(context === false);
|
|
1651
|
+
const preferPropDefaultFalse = (prop, context) => prop ? true : prop === false ? false : !!context;
|
|
1652
|
+
/**
|
|
1653
|
+
* For given default, prop, and context values, return the first provided of prop,
|
|
1654
|
+
* context, and default, in that order.
|
|
1655
|
+
*/
|
|
1656
|
+
const preferProp = (def, prop, context, doNotFinalize) => !doNotFinalize ? def ? preferPropDefaultTrue(prop, context) : preferPropDefaultFalse(prop, context) : prop ?? context;
|
|
1657
|
+
/**
|
|
1658
|
+
* For given default, prop, and context values, return the first provided of prop,
|
|
1659
|
+
* context, and default, in that order.
|
|
1660
|
+
*/
|
|
1661
|
+
const preferAnyProp = (def, prop, context) => prop !== void 0 && prop != null ? prop : context !== void 0 && context != null ? context : def;
|
|
1662
|
+
/**
|
|
1663
|
+
* For a given set of defaults, props, and context values, return the first provided of prop,
|
|
1664
|
+
* context, and default—in that order—for each property in the defaults object.
|
|
1665
|
+
*/
|
|
1666
|
+
const preferFlagProps = (props = {}, contextVals = {}, finalize) => objectEntries(queryBuilderFlagDefaults).reduce((acc, [key, def]) => {
|
|
1667
|
+
acc[key] = preferProp(def, props[key], contextVals[key], !finalize);
|
|
1668
|
+
return acc;
|
|
1669
|
+
}, {});
|
|
1670
|
+
//#endregion
|
|
1671
|
+
//#region src/utils/regenerateIDs.ts
|
|
1672
|
+
/**
|
|
1673
|
+
* Generates a new `id` property for a rule.
|
|
1674
|
+
*/
|
|
1675
|
+
const regenerateID = (rule, { idGenerator = generateID } = {}) => structuredClone({
|
|
1676
|
+
...rule,
|
|
1677
|
+
id: idGenerator()
|
|
1678
|
+
});
|
|
1679
|
+
/**
|
|
1680
|
+
* Recursively generates new `id` properties for a rule group and all its rules and subgroups.
|
|
1681
|
+
*/
|
|
1682
|
+
const regenerateIDs = (subject, { idGenerator = generateID } = {}) => {
|
|
1683
|
+
if (!isPojo(subject)) return subject;
|
|
1684
|
+
if (!isRuleGroup(subject)) return structuredClone({
|
|
1685
|
+
...subject,
|
|
1686
|
+
id: idGenerator()
|
|
1687
|
+
});
|
|
1688
|
+
const newGroup = {
|
|
1689
|
+
...subject,
|
|
1690
|
+
id: idGenerator()
|
|
1691
|
+
};
|
|
1692
|
+
// v8 ignore else
|
|
1693
|
+
if (Array.isArray(newGroup.rules)) newGroup.rules = subject.rules.map((r) => typeof r === "string" ? r : isRuleGroup(r) ? regenerateIDs(r, { idGenerator }) : regenerateID(r, { idGenerator }));
|
|
1694
|
+
return newGroup;
|
|
1695
|
+
};
|
|
1696
|
+
//#endregion
|
|
1697
|
+
//#region src/utils/queryTools.ts
|
|
1698
|
+
/**
|
|
1699
|
+
* Whether a mutation targeting `pathOrID` is blocked by the given guards, and why.
|
|
1700
|
+
* Returns `null` when the mutation may proceed.
|
|
1701
|
+
*
|
|
1702
|
+
* Exported so that callers which run their own logic before mutating—such as a UI layer that
|
|
1703
|
+
* invokes a confirmation callback—can apply the same rules without duplicating them.
|
|
1704
|
+
*
|
|
1705
|
+
* @group Query Tools
|
|
1706
|
+
*/
|
|
1707
|
+
const getGuardAbortReason = (query, pathOrID, guards = {}, { asParent = false } = {}) => {
|
|
1708
|
+
if (guards.queryDisabled) return asParent ? "parent-disabled" : "target-disabled";
|
|
1709
|
+
if (!guards.respectDisabled || pathOrID === void 0) return null;
|
|
1710
|
+
const path = Array.isArray(pathOrID) ? pathOrID : getPathOfID(pathOrID, query);
|
|
1711
|
+
if (path && (pathIsDisabled(path, query) || pathIsDisabledByPaths(path, guards.disabledPaths))) return asParent ? "parent-disabled" : "target-disabled";
|
|
1712
|
+
return null;
|
|
1713
|
+
};
|
|
1714
|
+
/**
|
|
1715
|
+
* Whether adding a group beneath `parentPath` would exceed `maxLevels`.
|
|
1716
|
+
*
|
|
1717
|
+
* @group Query Tools
|
|
1718
|
+
*/
|
|
1719
|
+
const exceedsMaxLevels = (parentPath, { maxLevels = Infinity } = {}) => !!parentPath && parentPath.length >= maxLevels;
|
|
1720
|
+
/**
|
|
1721
|
+
* Adds a rule or group to a query without mutating the original query.
|
|
1722
|
+
*
|
|
1723
|
+
* @returns A new query with the rule or group added.
|
|
1724
|
+
*
|
|
1725
|
+
* @group Query Tools
|
|
1726
|
+
*/
|
|
1727
|
+
const add = (query, ruleOrGroup, parentPathOrID, options = {}) => producerFor(options.freeze)(query, (q) => addInPlace(q, ruleOrGroup, parentPathOrID, options));
|
|
1728
|
+
/**
|
|
1729
|
+
* Adds a rule or group to a query in place.
|
|
1730
|
+
*
|
|
1731
|
+
* @returns The query (mutated in place) with the rule or group added.
|
|
1732
|
+
*
|
|
1733
|
+
* @group Query Tools
|
|
1734
|
+
*/
|
|
1735
|
+
const addInPlace = (query, ruleOrGroup, parentPathOrID, options = {}) => {
|
|
1736
|
+
const { combinators = defaultCombinators, combinatorPreceding, idGenerator = generateID, onAbort } = options;
|
|
1737
|
+
const parent = Array.isArray(parentPathOrID) ? findPath(parentPathOrID, query) : findID(parentPathOrID, query);
|
|
1738
|
+
if (!parent) {
|
|
1739
|
+
onAbort?.({
|
|
1740
|
+
reason: "parent-not-found",
|
|
1741
|
+
operation: "add",
|
|
1742
|
+
pathOrID: parentPathOrID
|
|
1743
|
+
});
|
|
1744
|
+
return query;
|
|
1745
|
+
}
|
|
1746
|
+
if (!isRuleGroup(parent)) {
|
|
1747
|
+
onAbort?.({
|
|
1748
|
+
reason: "parent-not-a-group",
|
|
1749
|
+
operation: "add",
|
|
1750
|
+
pathOrID: parentPathOrID
|
|
1751
|
+
});
|
|
1752
|
+
return query;
|
|
1753
|
+
}
|
|
1754
|
+
const parentPath = Array.isArray(parentPathOrID) ? parentPathOrID : getPathOfID(parentPathOrID, query);
|
|
1755
|
+
const addGuardReason = getGuardAbortReason(query, parentPath, options, { asParent: true });
|
|
1756
|
+
if (addGuardReason) {
|
|
1757
|
+
onAbort?.({
|
|
1758
|
+
reason: addGuardReason,
|
|
1759
|
+
operation: "add",
|
|
1760
|
+
pathOrID: parentPathOrID
|
|
1761
|
+
});
|
|
1762
|
+
return query;
|
|
1763
|
+
}
|
|
1764
|
+
if (isRuleGroup(ruleOrGroup) && exceedsMaxLevels(parentPath, options)) {
|
|
1765
|
+
onAbort?.({
|
|
1766
|
+
reason: "max-levels-exceeded",
|
|
1767
|
+
operation: "add",
|
|
1768
|
+
pathOrID: parentPathOrID
|
|
1769
|
+
});
|
|
1770
|
+
return query;
|
|
1771
|
+
}
|
|
1772
|
+
if (isRuleGroupTypeIC(parent) && parent.rules.length > 0) {
|
|
1773
|
+
const prevCombinator = parent.rules.at(-2);
|
|
1774
|
+
parent.rules.push(combinatorPreceding ?? (typeof prevCombinator === "string" ? prevCombinator : getFirstOption(combinators)));
|
|
1775
|
+
}
|
|
1776
|
+
parent.rules.push(prepareRuleOrGroup(ruleOrGroup, { idGenerator }));
|
|
1777
|
+
return query;
|
|
1778
|
+
};
|
|
1779
|
+
/**
|
|
1780
|
+
* Relative order in which properties are applied during a multi-property update.
|
|
1781
|
+
* Lower ranks apply first; `value` applies last so an explicitly-provided value
|
|
1782
|
+
* is never clobbered by a reset triggered by a `field`/`operator`/`valueSource`
|
|
1783
|
+
* change. Unranked properties default to `3` (between `valueSource` and `value`).
|
|
1784
|
+
*/
|
|
1785
|
+
const updatePropRank = {
|
|
1786
|
+
field: 0,
|
|
1787
|
+
operator: 1,
|
|
1788
|
+
valueSource: 2,
|
|
1789
|
+
value: 4
|
|
1790
|
+
};
|
|
1791
|
+
/** Stable-sorts `[prop, value]` entries by {@link updatePropRank}. */
|
|
1792
|
+
const orderUpdateEntries = (entries) => entries.toSorted((x, y) => (updatePropRank[x[0]] ?? 3) - (updatePropRank[y[0]] ?? 3));
|
|
1793
|
+
/**
|
|
1794
|
+
* Normalizes the variadic {@link update}/{@link updateInPlace} arguments (single
|
|
1795
|
+
* prop+value, parallel arrays, or property map) into a canonically-ordered list
|
|
1796
|
+
* of `[prop, value]` entries plus the target path/ID and options.
|
|
1797
|
+
*/
|
|
1798
|
+
const normalizeUpdateArgs = (a, b, c, d) => {
|
|
1799
|
+
if (typeof a === "string") return {
|
|
1800
|
+
entries: [[a, b]],
|
|
1801
|
+
pathOrID: c,
|
|
1802
|
+
options: d ?? {}
|
|
1803
|
+
};
|
|
1804
|
+
if (Array.isArray(a)) {
|
|
1805
|
+
const values = b;
|
|
1806
|
+
return {
|
|
1807
|
+
entries: orderUpdateEntries(a.map((prop, i) => [prop, values[i]])),
|
|
1808
|
+
pathOrID: c,
|
|
1809
|
+
options: d ?? {}
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
return {
|
|
1813
|
+
entries: orderUpdateEntries(Object.entries(a)),
|
|
1814
|
+
pathOrID: b,
|
|
1815
|
+
options: c ?? {}
|
|
1816
|
+
};
|
|
1817
|
+
};
|
|
1818
|
+
/**
|
|
1819
|
+
* The options argument of an {@link update} call, whose position depends on the argument form.
|
|
1820
|
+
* Cheaper than {@link normalizeUpdateArgs} when only the options are needed.
|
|
1821
|
+
*/
|
|
1822
|
+
const updateOptionsOf = (a, b, c, d) => (typeof a === "string" || Array.isArray(a) ? d : c) ?? {};
|
|
1823
|
+
/**
|
|
1824
|
+
* Applies one or more property updates to a query in place. Shared by
|
|
1825
|
+
* {@link update} and {@link updateInPlace}.
|
|
1826
|
+
*/
|
|
1827
|
+
const applyUpdatesInPlace = (query, a, b, c, d) => {
|
|
1828
|
+
const { entries, pathOrID, options } = normalizeUpdateArgs(a, b, c, d);
|
|
1829
|
+
for (const [prop, value] of entries) updateInPlaceSingle(query, prop, value, pathOrID, options);
|
|
1830
|
+
return query;
|
|
1831
|
+
};
|
|
1832
|
+
/**
|
|
1833
|
+
* Updates one or more properties of a rule or group within a query without
|
|
1834
|
+
* mutating the original query. Properties may be supplied individually
|
|
1835
|
+
* (`prop`, `value`), as parallel arrays (`props`, `values`), or as a
|
|
1836
|
+
* property-to-value map. For multi-property updates, `field`, `operator`, and
|
|
1837
|
+
* `valueSource` are applied before `value`, so an explicit `value` is never
|
|
1838
|
+
* reset by a change to one of those properties.
|
|
1839
|
+
*
|
|
1840
|
+
* @returns A new query with the rule or group properties updated.
|
|
1841
|
+
*
|
|
1842
|
+
* @group Query Tools
|
|
1843
|
+
*/
|
|
1844
|
+
const update = ((query, a, b, c, d) => producerFor(updateOptionsOf(a, b, c, d).freeze)(query, (q) => {
|
|
1845
|
+
applyUpdatesInPlace(q, a, b, c, d);
|
|
1846
|
+
}));
|
|
1847
|
+
/**
|
|
1848
|
+
* Updates one or more properties of a rule or group within a query in place.
|
|
1849
|
+
* See {@link update} for the supported argument forms and ordering semantics.
|
|
1850
|
+
*
|
|
1851
|
+
* @returns The query (mutated in place) with the rule or group properties updated.
|
|
1852
|
+
*
|
|
1853
|
+
* @group Query Tools
|
|
1854
|
+
*/
|
|
1855
|
+
const updateInPlace = ((query, a, b, c, d) => applyUpdatesInPlace(query, a, b, c, d));
|
|
1856
|
+
/**
|
|
1857
|
+
* Updates a single property of a rule or group within a query in place.
|
|
1858
|
+
*/
|
|
1859
|
+
const updateInPlaceSingle = (query, prop, value, pathOrID, options = {}) => {
|
|
1860
|
+
const { resetOnFieldChange: _resetOnFieldChange = true, resetOnOperatorChange = false, getRuleDefaultOperator = () => "=", getValueSources = () => ["value"], getRuleDefaultValue = () => "", getMatchModes = () => [], onAbort } = options;
|
|
1861
|
+
let resetOnFieldChange = _resetOnFieldChange;
|
|
1862
|
+
const path = Array.isArray(pathOrID) ? pathOrID : getPathOfID(pathOrID, query);
|
|
1863
|
+
if (!path) {
|
|
1864
|
+
onAbort?.({
|
|
1865
|
+
reason: "target-not-found",
|
|
1866
|
+
operation: "update",
|
|
1867
|
+
pathOrID
|
|
1868
|
+
});
|
|
1869
|
+
return query;
|
|
1870
|
+
}
|
|
1871
|
+
const updateGuards = prop === "disabled" ? { queryDisabled: options.queryDisabled } : options;
|
|
1872
|
+
const updateGuardReason = getGuardAbortReason(query, path, updateGuards);
|
|
1873
|
+
if (updateGuardReason) {
|
|
1874
|
+
onAbort?.({
|
|
1875
|
+
reason: updateGuardReason,
|
|
1876
|
+
operation: "update",
|
|
1877
|
+
pathOrID
|
|
1878
|
+
});
|
|
1879
|
+
return query;
|
|
1880
|
+
}
|
|
1881
|
+
if (prop === "combinator" && !isRuleGroupType(query)) {
|
|
1882
|
+
const parent = findPath(getParentPath(path), query);
|
|
1883
|
+
if (!parent || !isRuleGroup(parent)) {
|
|
1884
|
+
onAbort?.({
|
|
1885
|
+
reason: "target-not-found",
|
|
1886
|
+
operation: "update",
|
|
1887
|
+
pathOrID
|
|
1888
|
+
});
|
|
1889
|
+
return query;
|
|
1890
|
+
}
|
|
1891
|
+
const parentRules = parent.rules;
|
|
1892
|
+
if (path.at(-1) % 2 === 1) {
|
|
1893
|
+
if (parentRules[path.at(-1)] === value) onAbort?.({
|
|
1894
|
+
reason: "no-change",
|
|
1895
|
+
operation: "update",
|
|
1896
|
+
pathOrID
|
|
1897
|
+
});
|
|
1898
|
+
else parentRules[path.at(-1)] = value;
|
|
1899
|
+
} else onAbort?.({
|
|
1900
|
+
reason: "not-a-combinator-slot",
|
|
1901
|
+
operation: "update",
|
|
1902
|
+
pathOrID
|
|
1903
|
+
});
|
|
1904
|
+
return query;
|
|
1905
|
+
}
|
|
1906
|
+
const ruleOrGroup = findPath(path, query);
|
|
1907
|
+
if (!ruleOrGroup) {
|
|
1908
|
+
onAbort?.({
|
|
1909
|
+
reason: "target-not-found",
|
|
1910
|
+
operation: "update",
|
|
1911
|
+
pathOrID
|
|
1912
|
+
});
|
|
1913
|
+
return query;
|
|
1914
|
+
}
|
|
1915
|
+
const isGroup = isRuleGroup(ruleOrGroup);
|
|
1916
|
+
if (ruleOrGroup[prop] === value) {
|
|
1917
|
+
onAbort?.({
|
|
1918
|
+
reason: "no-change",
|
|
1919
|
+
operation: "update",
|
|
1920
|
+
pathOrID
|
|
1921
|
+
});
|
|
1922
|
+
return query;
|
|
1923
|
+
}
|
|
1924
|
+
if (prop !== "valueSource") ruleOrGroup[prop] = value;
|
|
1925
|
+
if (isGroup) return query;
|
|
1926
|
+
let resetValueSource = false;
|
|
1927
|
+
let resetValue = false;
|
|
1928
|
+
if (prop === "field") {
|
|
1929
|
+
const fromFieldMatchModes = getMatchModes(ruleOrGroup.field);
|
|
1930
|
+
const toFieldMatchModes = getMatchModes(value);
|
|
1931
|
+
if (toFieldMatchModes.length === 0) delete ruleOrGroup.match;
|
|
1932
|
+
else {
|
|
1933
|
+
const nextMatchMode = ruleOrGroup.match?.mode && getOption(toFieldMatchModes, ruleOrGroup.match.mode) ? null : getFirstOption(toFieldMatchModes);
|
|
1934
|
+
if (nextMatchMode) ruleOrGroup.match = {
|
|
1935
|
+
mode: nextMatchMode,
|
|
1936
|
+
threshold: 1
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
if (fromFieldMatchModes.length > 0 || toFieldMatchModes.length > 0) resetOnFieldChange = true;
|
|
1940
|
+
}
|
|
1941
|
+
if (resetOnFieldChange && prop === "field") {
|
|
1942
|
+
ruleOrGroup.operator = getRuleDefaultOperator(value);
|
|
1943
|
+
resetValueSource = true;
|
|
1944
|
+
resetValue = true;
|
|
1945
|
+
}
|
|
1946
|
+
if (resetOnOperatorChange && prop === "operator") {
|
|
1947
|
+
resetValueSource = true;
|
|
1948
|
+
resetValue = true;
|
|
1949
|
+
}
|
|
1950
|
+
const valueSources = getValueSourcesUtil({
|
|
1951
|
+
name: ruleOrGroup.field,
|
|
1952
|
+
value: ruleOrGroup.field,
|
|
1953
|
+
label: ""
|
|
1954
|
+
}, ruleOrGroup.operator, getValueSources);
|
|
1955
|
+
const defaultValueSource = getFirstOption(valueSources);
|
|
1956
|
+
if (resetValueSource && ruleOrGroup.valueSource && defaultValueSource !== ruleOrGroup.valueSource || prop === "valueSource" && value !== ruleOrGroup.valueSource) {
|
|
1957
|
+
resetValue = !!ruleOrGroup.valueSource || !ruleOrGroup.valueSource && value !== defaultValueSource;
|
|
1958
|
+
ruleOrGroup.valueSource = resetValueSource ? defaultValueSource : value;
|
|
1959
|
+
}
|
|
1960
|
+
if (resetValue) ruleOrGroup.value = getRuleDefaultValue(ruleOrGroup);
|
|
1961
|
+
return query;
|
|
1962
|
+
};
|
|
1963
|
+
/**
|
|
1964
|
+
* Removes a rule or group from a query without mutating the original query.
|
|
1965
|
+
*
|
|
1966
|
+
* @returns A new query with the rule or group removed.
|
|
1967
|
+
*
|
|
1968
|
+
* @group Query Tools
|
|
1969
|
+
*/
|
|
1970
|
+
const remove = (query, pathOrID, options = {}) => producerFor(options.freeze)(query, (q) => removeInPlace(q, pathOrID, options));
|
|
1971
|
+
/**
|
|
1972
|
+
* Removes a rule or group from a query in place.
|
|
1973
|
+
*
|
|
1974
|
+
* @returns The query (mutated in place) with the rule or group removed.
|
|
1975
|
+
*
|
|
1976
|
+
* @group Query Tools
|
|
1977
|
+
*/
|
|
1978
|
+
const removeInPlace = (query, pathOrID, options = {}) => {
|
|
1979
|
+
const { onAbort } = options;
|
|
1980
|
+
const path = Array.isArray(pathOrID) ? pathOrID : getPathOfID(pathOrID, query);
|
|
1981
|
+
if (!path) {
|
|
1982
|
+
onAbort?.({
|
|
1983
|
+
reason: "target-not-found",
|
|
1984
|
+
operation: "remove",
|
|
1985
|
+
pathOrID
|
|
1986
|
+
});
|
|
1987
|
+
return query;
|
|
1988
|
+
}
|
|
1989
|
+
if (path.length === 0) {
|
|
1990
|
+
onAbort?.({
|
|
1991
|
+
reason: "root-not-allowed",
|
|
1992
|
+
operation: "remove",
|
|
1993
|
+
pathOrID
|
|
1994
|
+
});
|
|
1995
|
+
return query;
|
|
1996
|
+
}
|
|
1997
|
+
const removeGuardReason = getGuardAbortReason(query, path, options);
|
|
1998
|
+
if (removeGuardReason) {
|
|
1999
|
+
onAbort?.({
|
|
2000
|
+
reason: removeGuardReason,
|
|
2001
|
+
operation: "remove",
|
|
2002
|
+
pathOrID
|
|
2003
|
+
});
|
|
2004
|
+
return query;
|
|
2005
|
+
}
|
|
2006
|
+
if (!findPath(path, query)) {
|
|
2007
|
+
onAbort?.({
|
|
2008
|
+
reason: "target-not-found",
|
|
2009
|
+
operation: "remove",
|
|
2010
|
+
pathOrID
|
|
2011
|
+
});
|
|
2012
|
+
return query;
|
|
2013
|
+
}
|
|
2014
|
+
const index = path.at(-1);
|
|
2015
|
+
const parent = findPath(getParentPath(path), query);
|
|
2016
|
+
if (!isRuleGroupType(parent) && parent.rules.length > 1) {
|
|
2017
|
+
const idxStartDelete = index === 0 ? 0 : index - 1;
|
|
2018
|
+
parent.rules.splice(idxStartDelete, 2);
|
|
2019
|
+
} else parent.rules.splice(index, 1);
|
|
2020
|
+
return query;
|
|
2021
|
+
};
|
|
2022
|
+
const getNextPath = (query, currentPath, newPathOrShiftDirection) => {
|
|
2023
|
+
if (Array.isArray(newPathOrShiftDirection)) return newPathOrShiftDirection;
|
|
2024
|
+
const ic = isRuleGroupTypeIC(query);
|
|
2025
|
+
if (newPathOrShiftDirection === "up") {
|
|
2026
|
+
if (pathsAreEqual(currentPath, [0])) return currentPath;
|
|
2027
|
+
else if (currentPath.at(-1) === 0) {
|
|
2028
|
+
const parentPath = getParentPath(currentPath);
|
|
2029
|
+
return [...getParentPath(parentPath), Math.max(0, parentPath.at(-1) - (ic ? 1 : 0))];
|
|
2030
|
+
} else {
|
|
2031
|
+
const evaluationPath = [...getParentPath(currentPath), Math.max(0, currentPath.at(-1) - (ic ? 2 : 1))];
|
|
2032
|
+
const entityAtTarget = findPath(evaluationPath, query);
|
|
2033
|
+
if (isRuleGroup(entityAtTarget)) return [...evaluationPath, entityAtTarget.rules.length];
|
|
2034
|
+
else return [...getParentPath(currentPath), Math.max(0, currentPath.at(-1) - (ic ? 3 : 1))];
|
|
2035
|
+
}
|
|
2036
|
+
} else if (newPathOrShiftDirection === "down") {
|
|
2037
|
+
if (pathsAreEqual([query.rules.length - 1], currentPath)) return currentPath;
|
|
2038
|
+
else if (currentPath.at(-1) === findPath(getParentPath(currentPath), query).rules.length - 1) {
|
|
2039
|
+
const parentPath = getParentPath(currentPath);
|
|
2040
|
+
return [...getParentPath(parentPath), parentPath.at(-1) + 1];
|
|
2041
|
+
} else {
|
|
2042
|
+
const evaluationPath = [...getParentPath(currentPath), currentPath.at(-1) + (ic ? 2 : 1)];
|
|
2043
|
+
const entityToEvaluate = findPath(evaluationPath, query);
|
|
2044
|
+
if (isRuleGroup(entityToEvaluate)) return [...evaluationPath, 0];
|
|
2045
|
+
else return [...getParentPath(currentPath), currentPath.at(-1) + (ic ? 3 : 2)];
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
return currentPath;
|
|
2049
|
+
};
|
|
2050
|
+
/**
|
|
2051
|
+
* Moves a rule or group from one path to another without mutating the original query.
|
|
2052
|
+
* In the options parameter, pass `{ clone: true }` to copy instead of move.
|
|
2053
|
+
*
|
|
2054
|
+
* @returns A new query with the rule or group moved or cloned.
|
|
2055
|
+
*
|
|
2056
|
+
* @group Query Tools
|
|
2057
|
+
*/
|
|
2058
|
+
const move = (query, oldPathOrID, newPath, options = {}) => producerFor(options.freeze)(query, (q) => moveInPlace(q, oldPathOrID, newPath, options));
|
|
2059
|
+
/**
|
|
2060
|
+
* Moves a rule or group from one path to another in place.
|
|
2061
|
+
* In the options parameter, pass `{ clone: true }` to copy instead of move.
|
|
2062
|
+
*
|
|
2063
|
+
* @returns The query (mutated in place) with the rule or group moved or cloned.
|
|
2064
|
+
*
|
|
2065
|
+
* @group Query Tools
|
|
2066
|
+
*/
|
|
2067
|
+
const moveInPlace = (query, oldPathOrID, newPath, options = {}) => {
|
|
2068
|
+
const { clone = false, combinators = defaultCombinators, idGenerator = generateID, onAbort } = options;
|
|
2069
|
+
const oldPath = Array.isArray(oldPathOrID) ? oldPathOrID : getPathOfID(oldPathOrID, query);
|
|
2070
|
+
if (!oldPath) {
|
|
2071
|
+
onAbort?.({
|
|
2072
|
+
reason: "target-not-found",
|
|
2073
|
+
operation: "move",
|
|
2074
|
+
pathOrID: oldPathOrID
|
|
2075
|
+
});
|
|
2076
|
+
return query;
|
|
2077
|
+
}
|
|
2078
|
+
const ruleOrGroupOriginal = findPath(oldPath, query);
|
|
2079
|
+
if (!ruleOrGroupOriginal) {
|
|
2080
|
+
onAbort?.({
|
|
2081
|
+
reason: "target-not-found",
|
|
2082
|
+
operation: "move",
|
|
2083
|
+
pathOrID: oldPathOrID
|
|
2084
|
+
});
|
|
2085
|
+
return query;
|
|
2086
|
+
}
|
|
2087
|
+
const nextPath = getNextPath(query, oldPath, newPath);
|
|
2088
|
+
if (oldPath.length === 0) {
|
|
2089
|
+
onAbort?.({
|
|
2090
|
+
reason: "root-not-allowed",
|
|
2091
|
+
operation: "move",
|
|
2092
|
+
pathOrID: oldPathOrID
|
|
2093
|
+
});
|
|
2094
|
+
return query;
|
|
2095
|
+
}
|
|
2096
|
+
const moveGuardReason = getGuardAbortReason(query, oldPath, options);
|
|
2097
|
+
if (moveGuardReason) {
|
|
2098
|
+
onAbort?.({
|
|
2099
|
+
reason: moveGuardReason,
|
|
2100
|
+
operation: "move",
|
|
2101
|
+
pathOrID: oldPathOrID
|
|
2102
|
+
});
|
|
2103
|
+
return query;
|
|
2104
|
+
}
|
|
2105
|
+
if (pathsAreEqual(oldPath, nextPath)) {
|
|
2106
|
+
onAbort?.({
|
|
2107
|
+
reason: "same-location",
|
|
2108
|
+
operation: "move",
|
|
2109
|
+
pathOrID: oldPathOrID
|
|
2110
|
+
});
|
|
2111
|
+
return query;
|
|
2112
|
+
}
|
|
2113
|
+
if (!findPath(getParentPath(nextPath), query)) {
|
|
2114
|
+
onAbort?.({
|
|
2115
|
+
reason: "destination-not-found",
|
|
2116
|
+
operation: "move",
|
|
2117
|
+
pathOrID: newPath
|
|
2118
|
+
});
|
|
2119
|
+
return query;
|
|
2120
|
+
}
|
|
2121
|
+
if (!clone && isAncestor(oldPath, nextPath)) {
|
|
2122
|
+
onAbort?.({
|
|
2123
|
+
reason: "destination-not-found",
|
|
2124
|
+
operation: "move",
|
|
2125
|
+
pathOrID: newPath
|
|
2126
|
+
});
|
|
2127
|
+
return query;
|
|
2128
|
+
}
|
|
2129
|
+
const ruleOrGroup = clone ? regenerateIDs(isDraft(ruleOrGroupOriginal) ? current(ruleOrGroupOriginal) : ruleOrGroupOriginal, { idGenerator }) : ruleOrGroupOriginal;
|
|
2130
|
+
const independentCombinators = isRuleGroupTypeIC(query);
|
|
2131
|
+
const parentOfRuleToRemove = findPath(getParentPath(oldPath), query);
|
|
2132
|
+
const ruleToRemoveIndex = oldPath.at(-1);
|
|
2133
|
+
const oldPrevCombinator = independentCombinators && ruleToRemoveIndex > 0 ? parentOfRuleToRemove.rules[ruleToRemoveIndex - 1] : null;
|
|
2134
|
+
const oldNextCombinator = independentCombinators && ruleToRemoveIndex < parentOfRuleToRemove.rules.length - 1 ? parentOfRuleToRemove.rules[ruleToRemoveIndex + 1] : null;
|
|
2135
|
+
if (!clone) {
|
|
2136
|
+
const idxStartDelete = independentCombinators ? Math.max(0, ruleToRemoveIndex - 1) : ruleToRemoveIndex;
|
|
2137
|
+
const deleteLength = independentCombinators ? 2 : 1;
|
|
2138
|
+
parentOfRuleToRemove.rules.splice(idxStartDelete, deleteLength);
|
|
2139
|
+
}
|
|
2140
|
+
const newNewPath = [...nextPath];
|
|
2141
|
+
if (independentCombinators && newNewPath.at(-1) % 2 === 1) newNewPath[newNewPath.length - 1] += 1;
|
|
2142
|
+
const commonAncestorPath = getCommonAncestorPath(oldPath, nextPath);
|
|
2143
|
+
if (!clone && oldPath.length === commonAncestorPath.length + 1 && newNewPath[commonAncestorPath.length] > oldPath[commonAncestorPath.length]) newNewPath[commonAncestorPath.length] -= independentCombinators ? 2 : 1;
|
|
2144
|
+
const newNewParentPath = getParentPath(newNewPath);
|
|
2145
|
+
const parentToInsertInto = findPath(newNewParentPath, query);
|
|
2146
|
+
const newIndex = newNewPath.at(-1);
|
|
2147
|
+
const spliceIndex = independentCombinators && newIndex > 0 ? newIndex - 1 : newIndex;
|
|
2148
|
+
/**
|
|
2149
|
+
* This function 1) glosses over the need for type assertions to splice directly
|
|
2150
|
+
* into `parentToInsertInto.rules`, and 2) shortens the actual insertion code.
|
|
2151
|
+
*/
|
|
2152
|
+
const insertRuleOrGroup = (...args) => parentToInsertInto.rules.splice(spliceIndex, 0, ...args);
|
|
2153
|
+
if (parentToInsertInto.rules.length === 0 || !independentCombinators) insertRuleOrGroup(ruleOrGroup);
|
|
2154
|
+
else if (newIndex === 0) {
|
|
2155
|
+
if (ruleToRemoveIndex === 0 && oldNextCombinator) insertRuleOrGroup(ruleOrGroup, oldNextCombinator);
|
|
2156
|
+
else insertRuleOrGroup(ruleOrGroup, parentToInsertInto.rules[1] ?? oldPrevCombinator ?? getFirstOption(combinators));
|
|
2157
|
+
} else if (oldPrevCombinator) insertRuleOrGroup(oldPrevCombinator, ruleOrGroup);
|
|
2158
|
+
else insertRuleOrGroup(parentToInsertInto.rules[spliceIndex - 2] ?? oldNextCombinator ?? getFirstOption(combinators), ruleOrGroup);
|
|
2159
|
+
return query;
|
|
2160
|
+
};
|
|
2161
|
+
/**
|
|
2162
|
+
* Inserts a rule or group into a query without mutating the original query.
|
|
2163
|
+
*
|
|
2164
|
+
* @returns A new query with the rule or group inserted.
|
|
2165
|
+
*
|
|
2166
|
+
* @group Query Tools
|
|
2167
|
+
*/
|
|
2168
|
+
const insert = (query, ruleOrGroup, path, options = {}) => producerFor(options.freeze)(query, (q) => insertInPlace(q, ruleOrGroup, path, options));
|
|
2169
|
+
/**
|
|
2170
|
+
* Inserts a rule or group into a query in place.
|
|
2171
|
+
*
|
|
2172
|
+
* @returns The query (mutated in place) with the rule or group inserted.
|
|
2173
|
+
*
|
|
2174
|
+
* @group Query Tools
|
|
2175
|
+
*/
|
|
2176
|
+
const insertInPlace = (query, ruleOrGroup, path, options = {}) => {
|
|
2177
|
+
const { combinators = defaultCombinators, combinatorPreceding, combinatorSucceeding, idGenerator = generateID, replace = false, onAbort } = options;
|
|
2178
|
+
const parentToInsertInto = findPath(getParentPath(path), query);
|
|
2179
|
+
if (!parentToInsertInto) {
|
|
2180
|
+
onAbort?.({
|
|
2181
|
+
reason: "parent-not-found",
|
|
2182
|
+
operation: "insert",
|
|
2183
|
+
pathOrID: path
|
|
2184
|
+
});
|
|
2185
|
+
return query;
|
|
2186
|
+
}
|
|
2187
|
+
if (!isRuleGroup(parentToInsertInto)) {
|
|
2188
|
+
onAbort?.({
|
|
2189
|
+
reason: "parent-not-a-group",
|
|
2190
|
+
operation: "insert",
|
|
2191
|
+
pathOrID: path
|
|
2192
|
+
});
|
|
2193
|
+
return query;
|
|
2194
|
+
}
|
|
2195
|
+
const parentPath = getParentPath(path);
|
|
2196
|
+
const insertGuardReason = getGuardAbortReason(query, parentPath, options, { asParent: true });
|
|
2197
|
+
if (insertGuardReason) {
|
|
2198
|
+
onAbort?.({
|
|
2199
|
+
reason: insertGuardReason,
|
|
2200
|
+
operation: "insert",
|
|
2201
|
+
pathOrID: path
|
|
2202
|
+
});
|
|
2203
|
+
return query;
|
|
2204
|
+
}
|
|
2205
|
+
if (isRuleGroup(ruleOrGroup) && exceedsMaxLevels(parentPath, options)) {
|
|
2206
|
+
onAbort?.({
|
|
2207
|
+
reason: "max-levels-exceeded",
|
|
2208
|
+
operation: "insert",
|
|
2209
|
+
pathOrID: path
|
|
2210
|
+
});
|
|
2211
|
+
return query;
|
|
2212
|
+
}
|
|
2213
|
+
const rorg = regenerateIDs(ruleOrGroup, { idGenerator });
|
|
2214
|
+
const independentCombinators = isRuleGroupTypeIC(query);
|
|
2215
|
+
const newIndex = path.at(-1);
|
|
2216
|
+
/**
|
|
2217
|
+
* This function 1) glosses over the need for type assertions to splice directly
|
|
2218
|
+
* into `parentToInsertInto.rules`, and 2) shortens the actual insertion code.
|
|
2219
|
+
*/
|
|
2220
|
+
const insertRuleOrGroup = (idx, ...args) => parentToInsertInto.rules.splice(idx, replace ? args.length : 0, ...args);
|
|
2221
|
+
if (parentToInsertInto.rules.length === 0 || !independentCombinators) insertRuleOrGroup(newIndex, rorg);
|
|
2222
|
+
else if (replace && independentCombinators) insertRuleOrGroup(newIndex + newIndex % 2, rorg);
|
|
2223
|
+
else if (newIndex === 0) {
|
|
2224
|
+
if (rorg.path?.at(-1) === 0 && combinatorSucceeding) insertRuleOrGroup(newIndex, rorg, combinatorSucceeding);
|
|
2225
|
+
else insertRuleOrGroup(newIndex, rorg, parentToInsertInto.rules[1] ?? combinatorPreceding ?? getFirstOption(combinators));
|
|
2226
|
+
} else {
|
|
2227
|
+
const normalizedNewIndex = newIndex % 2 === 0 ? newIndex - 1 : newIndex;
|
|
2228
|
+
if (combinatorPreceding) insertRuleOrGroup(normalizedNewIndex, combinatorPreceding, rorg);
|
|
2229
|
+
else insertRuleOrGroup(normalizedNewIndex, parentToInsertInto.rules[normalizedNewIndex - 2] ?? combinatorSucceeding ?? getFirstOption(combinators), rorg);
|
|
2230
|
+
}
|
|
2231
|
+
return query;
|
|
2232
|
+
};
|
|
2233
|
+
/**
|
|
2234
|
+
* Creates a new group at a target path with its `rules` array containing the current
|
|
2235
|
+
* objects at the target path and the source path without mutating the original query.
|
|
2236
|
+
* In the options parameter, pass `{ clone: true }` to copy the source rule/group instead of move.
|
|
2237
|
+
*
|
|
2238
|
+
* @returns A new query with the rules or groups grouped.
|
|
2239
|
+
*
|
|
2240
|
+
* @group Query Tools
|
|
2241
|
+
*/
|
|
2242
|
+
const group = (query, sourcePathOrID, targetPathOrID, options = {}) => producerFor(options.freeze)(query, (q) => groupInPlace(q, sourcePathOrID, targetPathOrID, options));
|
|
2243
|
+
/**
|
|
2244
|
+
* Creates a new group at a target path with its `rules` array containing the current
|
|
2245
|
+
* objects at the target path and the source path in place.
|
|
2246
|
+
* In the options parameter, pass `{ clone: true }` to copy the source rule/group instead of move.
|
|
2247
|
+
*
|
|
2248
|
+
* @returns The query (mutated in place) with the rules or groups grouped.
|
|
2249
|
+
*
|
|
2250
|
+
* @group Query Tools
|
|
2251
|
+
*/
|
|
2252
|
+
const groupInPlace = (query, sourcePathOrID, targetPathOrID, options = {}) => {
|
|
2253
|
+
const { clone = false, combinators = defaultCombinators, idGenerator = generateID, onAbort } = options;
|
|
2254
|
+
const sourcePath = Array.isArray(sourcePathOrID) ? sourcePathOrID : getPathOfID(sourcePathOrID, query);
|
|
2255
|
+
const targetPath = Array.isArray(targetPathOrID) ? targetPathOrID : getPathOfID(targetPathOrID, query);
|
|
2256
|
+
if (!sourcePath) {
|
|
2257
|
+
onAbort?.({
|
|
2258
|
+
reason: "target-not-found",
|
|
2259
|
+
operation: "group",
|
|
2260
|
+
pathOrID: sourcePathOrID
|
|
2261
|
+
});
|
|
2262
|
+
return query;
|
|
2263
|
+
}
|
|
2264
|
+
if (!targetPath) {
|
|
2265
|
+
onAbort?.({
|
|
2266
|
+
reason: "target-not-found",
|
|
2267
|
+
operation: "group",
|
|
2268
|
+
pathOrID: targetPathOrID
|
|
2269
|
+
});
|
|
2270
|
+
return query;
|
|
2271
|
+
}
|
|
2272
|
+
const nextPath = getNextPath(query, sourcePath, targetPath);
|
|
2273
|
+
if (sourcePath.length === 0) {
|
|
2274
|
+
onAbort?.({
|
|
2275
|
+
reason: "root-not-allowed",
|
|
2276
|
+
operation: "group",
|
|
2277
|
+
pathOrID: sourcePathOrID
|
|
2278
|
+
});
|
|
2279
|
+
return query;
|
|
2280
|
+
}
|
|
2281
|
+
const groupGuardReason = getGuardAbortReason(query, sourcePath, options);
|
|
2282
|
+
if (groupGuardReason) {
|
|
2283
|
+
onAbort?.({
|
|
2284
|
+
reason: groupGuardReason,
|
|
2285
|
+
operation: "group",
|
|
2286
|
+
pathOrID: sourcePathOrID
|
|
2287
|
+
});
|
|
2288
|
+
return query;
|
|
2289
|
+
}
|
|
2290
|
+
if (pathsAreEqual(sourcePath, nextPath)) {
|
|
2291
|
+
onAbort?.({
|
|
2292
|
+
reason: "same-location",
|
|
2293
|
+
operation: "group",
|
|
2294
|
+
pathOrID: sourcePathOrID
|
|
2295
|
+
});
|
|
2296
|
+
return query;
|
|
2297
|
+
}
|
|
2298
|
+
if (!findPath(getParentPath(nextPath), query)) {
|
|
2299
|
+
onAbort?.({
|
|
2300
|
+
reason: "destination-not-found",
|
|
2301
|
+
operation: "group",
|
|
2302
|
+
pathOrID: targetPathOrID
|
|
2303
|
+
});
|
|
2304
|
+
return query;
|
|
2305
|
+
}
|
|
2306
|
+
if (!clone && isAncestor(sourcePath, nextPath)) {
|
|
2307
|
+
onAbort?.({
|
|
2308
|
+
reason: "destination-not-found",
|
|
2309
|
+
operation: "group",
|
|
2310
|
+
pathOrID: targetPathOrID
|
|
2311
|
+
});
|
|
2312
|
+
return query;
|
|
2313
|
+
}
|
|
2314
|
+
const sourceRuleOrGroupOriginal = findPath(sourcePath, query);
|
|
2315
|
+
const targetRuleOrGroup = findPath(targetPath, query);
|
|
2316
|
+
if (!sourceRuleOrGroupOriginal) {
|
|
2317
|
+
onAbort?.({
|
|
2318
|
+
reason: "target-not-found",
|
|
2319
|
+
operation: "group",
|
|
2320
|
+
pathOrID: sourcePathOrID
|
|
2321
|
+
});
|
|
2322
|
+
return query;
|
|
2323
|
+
}
|
|
2324
|
+
if (!targetRuleOrGroup) {
|
|
2325
|
+
onAbort?.({
|
|
2326
|
+
reason: "target-not-found",
|
|
2327
|
+
operation: "group",
|
|
2328
|
+
pathOrID: targetPathOrID
|
|
2329
|
+
});
|
|
2330
|
+
return query;
|
|
2331
|
+
}
|
|
2332
|
+
const sourceRuleOrGroup = clone ? regenerateIDs(isDraft(sourceRuleOrGroupOriginal) ? current(sourceRuleOrGroupOriginal) : sourceRuleOrGroupOriginal, { idGenerator }) : sourceRuleOrGroupOriginal;
|
|
2333
|
+
const independentCombinators = isRuleGroupTypeIC(query);
|
|
2334
|
+
const parentOfRuleToRemove = findPath(getParentPath(sourcePath), query);
|
|
2335
|
+
const ruleToRemoveIndex = sourcePath.at(-1);
|
|
2336
|
+
if (!clone) {
|
|
2337
|
+
const idxStartDelete = independentCombinators ? Math.max(0, ruleToRemoveIndex - 1) : ruleToRemoveIndex;
|
|
2338
|
+
const deleteLength = independentCombinators ? 2 : 1;
|
|
2339
|
+
parentOfRuleToRemove.rules.splice(idxStartDelete, deleteLength);
|
|
2340
|
+
}
|
|
2341
|
+
const newNewPath = [...nextPath];
|
|
2342
|
+
const commonAncestorPath = getCommonAncestorPath(sourcePath, nextPath);
|
|
2343
|
+
if (!clone && sourcePath.length === commonAncestorPath.length + 1 && nextPath[commonAncestorPath.length] > sourcePath[commonAncestorPath.length]) newNewPath[commonAncestorPath.length] -= independentCombinators ? 2 : 1;
|
|
2344
|
+
const newNewParentPath = getParentPath(newNewPath);
|
|
2345
|
+
const parentOfTargetPath = findPath(newNewParentPath, query);
|
|
2346
|
+
const targetPathIndex = newNewPath.at(-1);
|
|
2347
|
+
parentOfTargetPath.rules.splice(targetPathIndex, 1, prepareRuleOrGroup(independentCombinators ? { rules: [
|
|
2348
|
+
targetRuleOrGroup,
|
|
2349
|
+
getFirstOption(combinators),
|
|
2350
|
+
sourceRuleOrGroup
|
|
2351
|
+
] } : {
|
|
2352
|
+
combinator: getFirstOption(combinators),
|
|
2353
|
+
rules: [targetRuleOrGroup, sourceRuleOrGroup]
|
|
2354
|
+
}, { idGenerator }));
|
|
2355
|
+
return query;
|
|
2356
|
+
};
|
|
2357
|
+
//#endregion
|
|
2358
|
+
//#region src/utils/queryActions.ts
|
|
2359
|
+
/**
|
|
2360
|
+
* Builds the query builder's six mutation handlers as pure functions of the current query.
|
|
2361
|
+
*
|
|
2362
|
+
* This is the framework-agnostic core of the action handlers in `useQueryBuilderSchema`. It owns
|
|
2363
|
+
* the policy that surrounds the query tools—disabled gating, the confirmation callback protocol,
|
|
2364
|
+
* `maxLevels`, and debug logging—so an implementation only has to supply its own storage: read
|
|
2365
|
+
* the current query, call the action, and apply a non-`undefined` result.
|
|
2366
|
+
*
|
|
2367
|
+
* @group Query Tools
|
|
2368
|
+
*/
|
|
2369
|
+
const createQueryActions = (config = {}) => {
|
|
2370
|
+
const { qbId, combinators, idGenerator, maxLevels = Infinity, queryDisabled, respectDisabled = true, disabledPaths, resetOnFieldChange, resetOnOperatorChange, getRuleDefaultOperator, getValueSources, getRuleDefaultValue, getMatchModes, freeze, onAddRule, onAddGroup, onRemove, onMoveRule, onMoveGroup, onGroupRule, onGroupGroup, onLog } = config;
|
|
2371
|
+
const log = (payload) => onLog?.({
|
|
2372
|
+
qbId,
|
|
2373
|
+
...payload
|
|
2374
|
+
});
|
|
2375
|
+
const guards = {
|
|
2376
|
+
respectDisabled,
|
|
2377
|
+
queryDisabled,
|
|
2378
|
+
disabledPaths
|
|
2379
|
+
};
|
|
2380
|
+
/** Whether the mutation is blocked, using the same rules the query tools apply internally. */
|
|
2381
|
+
const blocked = (query, path, asParent = false) => !!getGuardAbortReason(query, path, guards, { asParent });
|
|
2382
|
+
return {
|
|
2383
|
+
addRule: (query, rule, parentPath, context) => {
|
|
2384
|
+
if (blocked(query, parentPath, true)) {
|
|
2385
|
+
log({
|
|
2386
|
+
type: LogType.parentPathDisabled,
|
|
2387
|
+
rule,
|
|
2388
|
+
parentPath,
|
|
2389
|
+
query
|
|
2390
|
+
});
|
|
2391
|
+
return;
|
|
2392
|
+
}
|
|
2393
|
+
const nextRule = onAddRule ? onAddRule(rule, parentPath, query, context) : true;
|
|
2394
|
+
if (!nextRule) {
|
|
2395
|
+
log({
|
|
2396
|
+
type: LogType.onAddRuleFalse,
|
|
2397
|
+
rule,
|
|
2398
|
+
parentPath,
|
|
2399
|
+
query
|
|
2400
|
+
});
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
const newRule = nextRule === true ? rule : nextRule;
|
|
2404
|
+
const newQuery = add(query, newRule, parentPath, {
|
|
2405
|
+
combinators,
|
|
2406
|
+
combinatorPreceding: newRule.combinatorPreceding,
|
|
2407
|
+
idGenerator,
|
|
2408
|
+
freeze
|
|
2409
|
+
});
|
|
2410
|
+
log({
|
|
2411
|
+
type: LogType.add,
|
|
2412
|
+
query,
|
|
2413
|
+
newQuery,
|
|
2414
|
+
newRule,
|
|
2415
|
+
parentPath
|
|
2416
|
+
});
|
|
2417
|
+
return newQuery;
|
|
2418
|
+
},
|
|
2419
|
+
addGroup: (query, ruleGroup, parentPath, context) => {
|
|
2420
|
+
if (parentPath.length >= maxLevels) return void 0;
|
|
2421
|
+
if (blocked(query, parentPath, true)) {
|
|
2422
|
+
log({
|
|
2423
|
+
type: LogType.parentPathDisabled,
|
|
2424
|
+
ruleGroup,
|
|
2425
|
+
parentPath,
|
|
2426
|
+
query
|
|
2427
|
+
});
|
|
2428
|
+
return;
|
|
2429
|
+
}
|
|
2430
|
+
const nextGroup = onAddGroup ? onAddGroup(ruleGroup, parentPath, query, context) : true;
|
|
2431
|
+
if (!nextGroup) {
|
|
2432
|
+
log({
|
|
2433
|
+
type: LogType.onAddGroupFalse,
|
|
2434
|
+
ruleGroup,
|
|
2435
|
+
parentPath,
|
|
2436
|
+
query
|
|
2437
|
+
});
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
const newGroup = nextGroup === true ? ruleGroup : nextGroup;
|
|
2441
|
+
const newQuery = add(query, newGroup, parentPath, {
|
|
2442
|
+
combinators,
|
|
2443
|
+
combinatorPreceding: newGroup.combinatorPreceding ?? void 0,
|
|
2444
|
+
idGenerator,
|
|
2445
|
+
freeze
|
|
2446
|
+
});
|
|
2447
|
+
log({
|
|
2448
|
+
type: LogType.add,
|
|
2449
|
+
query,
|
|
2450
|
+
newQuery,
|
|
2451
|
+
newGroup,
|
|
2452
|
+
parentPath
|
|
2453
|
+
});
|
|
2454
|
+
return newQuery;
|
|
2455
|
+
},
|
|
2456
|
+
propChange: (query, prop, value, path) => {
|
|
2457
|
+
if (getGuardAbortReason(query, path, prop === "disabled" ? { queryDisabled } : guards)) {
|
|
2458
|
+
log({
|
|
2459
|
+
type: LogType.pathDisabled,
|
|
2460
|
+
path,
|
|
2461
|
+
prop,
|
|
2462
|
+
value,
|
|
2463
|
+
query
|
|
2464
|
+
});
|
|
2465
|
+
return;
|
|
2466
|
+
}
|
|
2467
|
+
const newQuery = update(query, prop, value, path, {
|
|
2468
|
+
resetOnFieldChange,
|
|
2469
|
+
resetOnOperatorChange,
|
|
2470
|
+
getRuleDefaultOperator,
|
|
2471
|
+
getValueSources,
|
|
2472
|
+
getRuleDefaultValue,
|
|
2473
|
+
getMatchModes,
|
|
2474
|
+
freeze
|
|
2475
|
+
});
|
|
2476
|
+
log({
|
|
2477
|
+
type: LogType.update,
|
|
2478
|
+
query,
|
|
2479
|
+
newQuery,
|
|
2480
|
+
prop,
|
|
2481
|
+
value,
|
|
2482
|
+
path
|
|
2483
|
+
});
|
|
2484
|
+
return newQuery;
|
|
2485
|
+
},
|
|
2486
|
+
removeRuleOrGroup: (query, path, context) => {
|
|
2487
|
+
if (blocked(query, path)) {
|
|
2488
|
+
log({
|
|
2489
|
+
type: LogType.pathDisabled,
|
|
2490
|
+
path,
|
|
2491
|
+
query
|
|
2492
|
+
});
|
|
2493
|
+
return;
|
|
2494
|
+
}
|
|
2495
|
+
const ruleOrGroup = findPath(path, query);
|
|
2496
|
+
/* v8 ignore next -- @preserve */
|
|
2497
|
+
if (!ruleOrGroup) return void 0;
|
|
2498
|
+
if (onRemove && !onRemove(ruleOrGroup, path, query, context)) {
|
|
2499
|
+
log({
|
|
2500
|
+
type: LogType.onRemoveFalse,
|
|
2501
|
+
ruleOrGroup,
|
|
2502
|
+
path,
|
|
2503
|
+
query
|
|
2504
|
+
});
|
|
2505
|
+
return;
|
|
2506
|
+
}
|
|
2507
|
+
const newQuery = remove(query, path, { freeze });
|
|
2508
|
+
log({
|
|
2509
|
+
type: LogType.remove,
|
|
2510
|
+
query,
|
|
2511
|
+
newQuery,
|
|
2512
|
+
path,
|
|
2513
|
+
ruleOrGroup
|
|
2514
|
+
});
|
|
2515
|
+
return newQuery;
|
|
2516
|
+
},
|
|
2517
|
+
moveRule: (query, oldPath, newPath, clone, context) => {
|
|
2518
|
+
if (blocked(query, oldPath)) {
|
|
2519
|
+
log({
|
|
2520
|
+
type: LogType.pathDisabled,
|
|
2521
|
+
oldPath,
|
|
2522
|
+
newPath,
|
|
2523
|
+
query
|
|
2524
|
+
});
|
|
2525
|
+
return;
|
|
2526
|
+
}
|
|
2527
|
+
const ruleOrGroup = findPath(oldPath, query);
|
|
2528
|
+
if (!ruleOrGroup) return void 0;
|
|
2529
|
+
const nextQuery = move(query, oldPath, newPath, {
|
|
2530
|
+
clone,
|
|
2531
|
+
combinators,
|
|
2532
|
+
idGenerator,
|
|
2533
|
+
freeze
|
|
2534
|
+
});
|
|
2535
|
+
const isGroup = isRuleGroup(ruleOrGroup);
|
|
2536
|
+
const callback = isGroup ? onMoveGroup : onMoveRule;
|
|
2537
|
+
const callbackResult = callback ? callback(ruleOrGroup, oldPath, newPath, query, nextQuery, {
|
|
2538
|
+
clone,
|
|
2539
|
+
combinators
|
|
2540
|
+
}, context) : true;
|
|
2541
|
+
if (!callbackResult) {
|
|
2542
|
+
log({
|
|
2543
|
+
type: isGroup ? LogType.onMoveGroupFalse : LogType.onMoveRuleFalse,
|
|
2544
|
+
ruleOrGroup,
|
|
2545
|
+
oldPath,
|
|
2546
|
+
newPath,
|
|
2547
|
+
clone,
|
|
2548
|
+
query,
|
|
2549
|
+
nextQuery
|
|
2550
|
+
});
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
const newQuery = isRuleGroup(callbackResult) ? callbackResult : nextQuery;
|
|
2554
|
+
log({
|
|
2555
|
+
type: LogType.move,
|
|
2556
|
+
query,
|
|
2557
|
+
newQuery,
|
|
2558
|
+
oldPath,
|
|
2559
|
+
newPath,
|
|
2560
|
+
clone
|
|
2561
|
+
});
|
|
2562
|
+
return newQuery;
|
|
2563
|
+
},
|
|
2564
|
+
groupRule: (query, sourcePath, targetPath, clone, context) => {
|
|
2565
|
+
if (blocked(query, sourcePath)) {
|
|
2566
|
+
log({
|
|
2567
|
+
type: LogType.pathDisabled,
|
|
2568
|
+
sourcePath,
|
|
2569
|
+
targetPath,
|
|
2570
|
+
query
|
|
2571
|
+
});
|
|
2572
|
+
return;
|
|
2573
|
+
}
|
|
2574
|
+
const ruleOrGroup = findPath(sourcePath, query);
|
|
2575
|
+
if (!ruleOrGroup) return void 0;
|
|
2576
|
+
const nextQuery = group(query, sourcePath, targetPath, {
|
|
2577
|
+
clone,
|
|
2578
|
+
combinators,
|
|
2579
|
+
idGenerator,
|
|
2580
|
+
freeze
|
|
2581
|
+
});
|
|
2582
|
+
const isGroup = isRuleGroup(ruleOrGroup);
|
|
2583
|
+
const callback = isGroup ? onGroupGroup : onGroupRule;
|
|
2584
|
+
const callbackResult = callback ? callback(ruleOrGroup, sourcePath, targetPath, query, nextQuery, {
|
|
2585
|
+
clone,
|
|
2586
|
+
combinators
|
|
2587
|
+
}, context) : true;
|
|
2588
|
+
if (!callbackResult) {
|
|
2589
|
+
log({
|
|
2590
|
+
type: isGroup ? LogType.onGroupGroupFalse : LogType.onGroupRuleFalse,
|
|
2591
|
+
ruleOrGroup,
|
|
2592
|
+
sourcePath,
|
|
2593
|
+
targetPath,
|
|
2594
|
+
clone,
|
|
2595
|
+
query,
|
|
2596
|
+
nextQuery
|
|
2597
|
+
});
|
|
2598
|
+
return;
|
|
2599
|
+
}
|
|
2600
|
+
const newQuery = isRuleGroup(callbackResult) ? callbackResult : nextQuery;
|
|
2601
|
+
log({
|
|
2602
|
+
type: LogType.group,
|
|
2603
|
+
query,
|
|
2604
|
+
newQuery,
|
|
2605
|
+
sourcePath,
|
|
2606
|
+
targetPath,
|
|
2607
|
+
clone
|
|
2608
|
+
});
|
|
2609
|
+
return newQuery;
|
|
2610
|
+
}
|
|
2611
|
+
};
|
|
2612
|
+
};
|
|
2613
|
+
//#endregion
|
|
2614
|
+
//#region src/utils/ruleFactory.ts
|
|
2615
|
+
/**
|
|
2616
|
+
* Creates a rule from the given configuration, applying the same precedence as the
|
|
2617
|
+
* `QueryBuilder` component. The `value` is computed in a second pass, once `field`, `operator`,
|
|
2618
|
+
* and `valueSource` are known, since the default value depends on all three.
|
|
2619
|
+
*
|
|
2620
|
+
* @group Query Tools
|
|
2621
|
+
*/
|
|
2622
|
+
const createRule = ({ fields, getDefaultField, getRuleDefaultOperator, getValueSources, getMatchModes, getRuleDefaultValue, idGenerator = generateID }) => {
|
|
2623
|
+
let field = getFirstOption(fields) ?? "";
|
|
2624
|
+
if (getDefaultField) {
|
|
2625
|
+
if (typeof getDefaultField === "function") {
|
|
2626
|
+
const defaultField = getDefaultField(fields);
|
|
2627
|
+
if (defaultField) field = defaultField;
|
|
2628
|
+
} else field = getDefaultField;
|
|
2629
|
+
}
|
|
2630
|
+
const fieldData = getOption(fields, field);
|
|
2631
|
+
const operator = getRuleDefaultOperator(field);
|
|
2632
|
+
const valueSource = getFirstOption(getValueSources(field, operator, { fieldData })) ?? "value";
|
|
2633
|
+
const matchMode = getFirstOption(getMatchModes(field, { fieldData }));
|
|
2634
|
+
const newRule = {
|
|
2635
|
+
id: idGenerator(),
|
|
2636
|
+
field,
|
|
2637
|
+
operator,
|
|
2638
|
+
valueSource,
|
|
2639
|
+
value: "",
|
|
2640
|
+
...matchMode ? { match: {
|
|
2641
|
+
mode: matchMode,
|
|
2642
|
+
threshold: 1
|
|
2643
|
+
} } : null
|
|
2644
|
+
};
|
|
2645
|
+
return {
|
|
2646
|
+
...newRule,
|
|
2647
|
+
value: getRuleDefaultValue(newRule)
|
|
2648
|
+
};
|
|
2649
|
+
};
|
|
2650
|
+
/**
|
|
2651
|
+
* Creates a group from the given configuration. Pass `true` for `independentCombinators` to omit
|
|
2652
|
+
* the `combinator` property.
|
|
2653
|
+
*
|
|
2654
|
+
* The group's own `id` is generated _before_ any contained rule's, matching the order the
|
|
2655
|
+
* `QueryBuilder` component has always used. This is observable when `idGenerator` is
|
|
2656
|
+
* deterministic, as it often is in tests.
|
|
2657
|
+
*
|
|
2658
|
+
* @group Query Tools
|
|
2659
|
+
*/
|
|
2660
|
+
const createRuleGroup = ({ combinators, addRuleToNewGroups, createRule: createRuleMain, idGenerator = generateID }, independentCombinators) => {
|
|
2661
|
+
const id = idGenerator();
|
|
2662
|
+
const rules = addRuleToNewGroups ? [createRuleMain()] : [];
|
|
2663
|
+
if (independentCombinators) return {
|
|
2664
|
+
id,
|
|
2665
|
+
rules,
|
|
2666
|
+
not: false
|
|
2667
|
+
};
|
|
2668
|
+
return {
|
|
2669
|
+
id,
|
|
2670
|
+
rules,
|
|
2671
|
+
combinator: getFirstOption(combinators) ?? "",
|
|
2672
|
+
not: false
|
|
2673
|
+
};
|
|
2674
|
+
};
|
|
2675
|
+
//#endregion
|
|
2676
|
+
export { LogType, TestID, add, addInPlace, betweenOperators, clsx, coerceBigIntValue, coerceInputType, controlKeys, controlKind, controlPropKeys, convertFromIC, convertQuery, convertToIC, createQueryActions, createRule, createRuleGroup, defaultCoalesceMs, defaultCombinatorLabelMap, defaultCombinators, defaultCombinatorsExtended, defaultControlClassnames, defaultJoinChar, defaultMatchModes, defaultMaxHistory, defaultOperatorLabelMap, defaultOperatorNegationMap, defaultOperators, defaultPlaceholderFieldGroupLabel, defaultPlaceholderFieldLabel, defaultPlaceholderFieldName, defaultPlaceholderLabel, defaultPlaceholderName, defaultPlaceholderOperatorGroupLabel, defaultPlaceholderOperatorLabel, defaultPlaceholderOperatorName, defaultPlaceholderValueGroupLabel, defaultPlaceholderValueLabel, defaultPlaceholderValueName, defaultTranslations, defaultValidator, derivePathInfo, deriveQueryBuilderClassNames, deriveRuleClassName, deriveRuleClassNames, deriveRuleContext, deriveRuleGroupClassNames, deriveRuleGroupContext, deriveRuleGroupOuterClassName, deriveRuleOuterClassName, exceedsMaxLevels, filterFieldsByComparator, findID, findPath, generateAccessibleDescription, generateID, getCommonAncestorPath, getFieldData, getFirstOption, getGuardAbortReason, getMatchModesUtil, getMultiValueUpdate, getOption, getParametersAsList, getParentPath, getParseNumberMethod, getPathOfID, getRuleDefaultValue, getRuleGroupCombinator, getRuleInputType, getRuleValidationResult, getRuleValueEditorType, getRuleValueSourceOptions, getRuleValues, getValidationClassNames, getValueEditorReset, getValueSelectorUpdate, getValueSourcesUtil, group, groupInPlace, groupInvalidReasons, hideValueControlsForOperator, inOperators, insert, insertInPlace, isAncestor, isBetweenOperator, isFlexibleOptionArray, isFlexibleOptionGroupArray, isFullOptionArray, isFullOptionGroupArray, isOptionGroupArray, isPojo, isRuleGroup, isRuleGroupType, isRuleGroupTypeIC, isRuleOrGroupValid, isRuleType, isUnsafeKey, isValidationResult, joinWith, lc, mergeAnyTranslation, mergeAnyTranslations, mergeClassnames, move, moveInPlace, normalizeValueSelectorValue, nullFreeArray, nullOperators, nullOrUndefinedOrEmpty, numericRegex, objectEntries, objectKeys, optionsEqual, parseNumber, pathIsDisabled, pathIsDisabledByPaths, pathsAreEqual, preferAnyProp, preferFlagProps, preferProp, prepareOptionList, prepareRule, prepareRuleGroup, prepareRuleOrGroup, queryBuilderFlagDefaults, regenerateID, regenerateIDs, relationalOperators, remove, removeInPlace, resolveCandidateQuery, resolveDefaultOperator, resolveOperatorList, resolveValueEditorType, resolveValueList, rootPath, setAutoFreeze, shouldCoalesce, signatureOf, splitBy, standardClassnames, strictAbortReasons, structuralSignature, substringOperators, toArray, toFlatOptionArray, toFullOption, toFullOptionList, toFullOptionMap, transformQuery, trimIfString, unchangedSignature, uniqByIdentifier, uniqByName, uniqOptGroups, uniqOptList, update, updateInPlace, uuidV4regex, valuesEqual };
|
|
2677
|
+
|
|
2678
|
+
//# sourceMappingURL=derivations.mjs.map
|