@react-querybuilder/core 8.21.2 → 8.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/react-querybuilder_core.cjs.development.d.ts +1399 -44
  3. package/dist/cjs/react-querybuilder_core.cjs.development.js +2244 -87
  4. package/dist/cjs/react-querybuilder_core.cjs.development.js.map +1 -1
  5. package/dist/cjs/react-querybuilder_core.cjs.production.d.ts +1399 -44
  6. package/dist/cjs/react-querybuilder_core.cjs.production.js +1 -1
  7. package/dist/cjs/react-querybuilder_core.cjs.production.js.map +1 -1
  8. package/dist/formatQuery.js +47 -38
  9. package/dist/formatQuery.js.map +1 -1
  10. package/dist/formatQuery.mjs +47 -38
  11. package/dist/formatQuery.mjs.map +1 -1
  12. package/dist/parseCEL.js +6 -8
  13. package/dist/parseCEL.js.map +1 -1
  14. package/dist/parseCEL.mjs +6 -8
  15. package/dist/parseCEL.mjs.map +1 -1
  16. package/dist/parseCypher.js.map +1 -1
  17. package/dist/parseJSONata.js +2 -2
  18. package/dist/parseJSONata.js.map +1 -1
  19. package/dist/parseJSONata.mjs +1 -1
  20. package/dist/parseJsonLogic.js +3 -3
  21. package/dist/parseJsonLogic.js.map +1 -1
  22. package/dist/parseJsonLogic.mjs +3 -3
  23. package/dist/parseJsonLogic.mjs.map +1 -1
  24. package/dist/parseMongoDB.js +2 -2
  25. package/dist/parseMongoDB.mjs +2 -2
  26. package/dist/parseSPARQL.js +0 -1
  27. package/dist/parseSPARQL.js.map +1 -1
  28. package/dist/parseSPARQL.mjs +0 -1
  29. package/dist/parseSPARQL.mjs.map +1 -1
  30. package/dist/parseSQL.js +54 -62
  31. package/dist/parseSQL.js.map +1 -1
  32. package/dist/parseSQL.mjs +54 -62
  33. package/dist/parseSQL.mjs.map +1 -1
  34. package/dist/parseSpEL.js +3 -3
  35. package/dist/parseSpEL.js.map +1 -1
  36. package/dist/parseSpEL.mjs +3 -3
  37. package/dist/parseSpEL.mjs.map +1 -1
  38. package/dist/{prepareQueryObjects-C5dHLhnC.js → prepareQueryObjects-BnGL91Tt.js} +9 -5
  39. package/dist/{prepareQueryObjects-C5dHLhnC.js.map → prepareQueryObjects-BnGL91Tt.js.map} +1 -1
  40. package/dist/{prepareQueryObjects-9emVAGYG.mjs → prepareQueryObjects-ChINDIap.mjs} +9 -5
  41. package/dist/{prepareQueryObjects-9emVAGYG.mjs.map → prepareQueryObjects-ChINDIap.mjs.map} +1 -1
  42. package/dist/query-builder-layout.css +1 -1
  43. package/dist/query-builder-layout.css.map +1 -1
  44. package/dist/query-builder.css +1 -1
  45. package/dist/query-builder.css.map +1 -1
  46. package/dist/react-querybuilder_core.d.mts +1399 -44
  47. package/dist/react-querybuilder_core.legacy-esm.d.ts +1399 -44
  48. package/dist/react-querybuilder_core.legacy-esm.js +2266 -81
  49. package/dist/react-querybuilder_core.legacy-esm.js.map +1 -1
  50. package/dist/react-querybuilder_core.mjs +2202 -89
  51. package/dist/react-querybuilder_core.mjs.map +1 -1
  52. package/dist/react-querybuilder_core.production.d.mts +1399 -44
  53. package/dist/react-querybuilder_core.production.mjs +1 -1
  54. package/dist/react-querybuilder_core.production.mjs.map +1 -1
  55. package/dist/styles/_layout.scss +5 -2
  56. package/dist/{utils-CxWs-N47.mjs → utils-4k-j5FBa.mjs} +4 -2
  57. package/dist/utils-4k-j5FBa.mjs.map +1 -0
  58. package/dist/{utils-SchkzSCN.js → utils-C79dxwZb.js} +4 -2
  59. package/dist/utils-C79dxwZb.js.map +1 -0
  60. package/package.json +9 -8
  61. package/dist/utils-CxWs-N47.mjs.map +0 -1
  62. package/dist/utils-SchkzSCN.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { numericQuantity, numericRegex as numericRegex$1 } from "numeric-quantity";
2
- import { current, isDraft, produce } from "immer";
2
+ import { current, freeze, isDraft, produce } from "immer";
3
3
  //#region src/defaults.ts
4
4
  /**
5
5
  * @group Defaults
@@ -130,6 +130,14 @@ const defaultTranslations = {
130
130
  label: "˅",
131
131
  title: "Shift down"
132
132
  },
133
+ undo: {
134
+ label: "↶",
135
+ title: "Undo"
136
+ },
137
+ redo: {
138
+ label: "↷",
139
+ title: "Redo"
140
+ },
133
141
  dragHandle: {
134
142
  label: "⁞⁞",
135
143
  title: "Drag handle"
@@ -454,6 +462,9 @@ const standardClassnames = {
454
462
  valid: "queryBuilder-valid",
455
463
  invalid: "queryBuilder-invalid",
456
464
  shiftActions: "shiftActions",
465
+ undoRedoActions: "undoRedoActions",
466
+ undoAction: "undoRedoActions-undo",
467
+ redoAction: "undoRedoActions-redo",
457
468
  dndDragging: "dndDragging",
458
469
  dndOver: "dndOver",
459
470
  dndCopy: "dndCopy",
@@ -502,6 +513,9 @@ const defaultControlClassnames = {
502
513
  value: "",
503
514
  removeRule: "",
504
515
  shiftActions: "",
516
+ undoRedoActions: "",
517
+ undoAction: "",
518
+ redoAction: "",
505
519
  dragHandle: "",
506
520
  lockRule: "",
507
521
  lockGroup: "",
@@ -559,6 +573,9 @@ const TestID = {
559
573
  valueEditor: "value-editor",
560
574
  notToggle: "not-toggle",
561
575
  shiftActions: "shift-actions",
576
+ undoRedoActions: "undo-redo-actions",
577
+ undoAction: "undo-action",
578
+ redoAction: "redo-action",
562
579
  dragHandle: "drag-handle",
563
580
  lockRule: "lock-rule",
564
581
  lockGroup: "lock-group",
@@ -605,6 +622,7 @@ const queryBuilderFlagDefaults = {
605
622
  enableDragAndDrop: false,
606
623
  enableMountQueryChange: true,
607
624
  listsAsArrays: false,
625
+ preserveQueryStateOnUnmount: false,
608
626
  resetOnFieldChange: true,
609
627
  resetOnOperatorChange: false,
610
628
  showCloneButtons: false,
@@ -613,8 +631,22 @@ const queryBuilderFlagDefaults = {
613
631
  showMuteButtons: false,
614
632
  showNotToggle: false,
615
633
  showShiftActions: false,
634
+ showUndoRedo: false,
616
635
  suppressStandardClassnames: false
617
636
  };
637
+ /**
638
+ * Default maximum number of undo steps retained by query history.
639
+ *
640
+ * @group Defaults
641
+ */
642
+ const defaultMaxHistory = 50;
643
+ /**
644
+ * Default time window (in milliseconds) within which consecutive changes to the same
645
+ * property of the same rule are coalesced into a single undo step.
646
+ *
647
+ * @group Defaults
648
+ */
649
+ const defaultCoalesceMs = 500;
618
650
  //#endregion
619
651
  //#region src/utils/arrayUtils.ts
620
652
  /**
@@ -800,6 +832,205 @@ function convertQuery(query) {
800
832
  return isRuleGroupTypeIC(query) ? convertFromIC(query) : convertToIC(query);
801
833
  }
802
834
  //#endregion
835
+ //#region src/utils/deriveClassNames.ts
836
+ const ruleClassnameSources = {
837
+ shiftActions: ["shiftActions"],
838
+ dragHandle: ["dragHandle"],
839
+ fields: ["valueSelector", "fields"],
840
+ matchMode: ["valueSelector", "matchMode"],
841
+ matchThreshold: ["valueSelector", "matchThreshold"],
842
+ operators: ["valueSelector", "operators"],
843
+ valueSource: ["valueSelector", "valueSource"],
844
+ value: ["value"],
845
+ cloneRule: ["actionElement", "cloneRule"],
846
+ lockRule: ["actionElement", "lockRule"],
847
+ muteRule: ["actionElement", "muteRule"],
848
+ removeRule: ["actionElement", "removeRule"],
849
+ valueListItem: ["valueListItem"]
850
+ };
851
+ const ruleGroupClassnameSources = {
852
+ header: {
853
+ sources: ["header"],
854
+ conditions: [
855
+ {
856
+ key: "dndOver",
857
+ when: (s) => s.isOver,
858
+ standardOnly: true
859
+ },
860
+ {
861
+ key: "dndCopy",
862
+ when: (s) => s.isOver && s.dropEffect === "copy"
863
+ },
864
+ {
865
+ key: "dndDropNotAllowed",
866
+ when: (s) => s.dropNotAllowed
867
+ }
868
+ ]
869
+ },
870
+ shiftActions: ["shiftActions"],
871
+ undoRedoActions: ["undoRedoActions"],
872
+ undoAction: ["actionElement", "undoAction"],
873
+ redoAction: ["actionElement", "redoAction"],
874
+ dragHandle: ["dragHandle"],
875
+ combinators: ["valueSelector", "combinators"],
876
+ notToggle: ["notToggle"],
877
+ addRule: ["actionElement", "addRule"],
878
+ addGroup: ["actionElement", "addGroup"],
879
+ cloneGroup: ["actionElement", "cloneGroup"],
880
+ lockGroup: ["actionElement", "lockGroup"],
881
+ muteGroup: ["actionElement", "muteGroup"],
882
+ removeGroup: ["actionElement", "removeGroup"],
883
+ body: ["body"]
884
+ };
885
+ /**
886
+ * Conditional classes applied to a rule's wrapper element.
887
+ *
888
+ * Note that this is deliberately _not_ the same set as {@link ruleGroupOuterConditions}: a rule
889
+ * reflects more drag-and-drop states than a group does. Declaring each set separately is what
890
+ * keeps an implementation from assuming they're symmetric.
891
+ */
892
+ const ruleOuterConditions = [
893
+ {
894
+ key: "disabled",
895
+ when: (s) => s.disabled
896
+ },
897
+ {
898
+ key: "muted",
899
+ when: (s) => s.muted
900
+ },
901
+ {
902
+ key: "dndDragging",
903
+ when: (s) => s.isDragging
904
+ },
905
+ {
906
+ key: "dndOver",
907
+ when: (s) => s.isOver
908
+ },
909
+ {
910
+ key: "dndCopy",
911
+ when: (s) => s.isOver && s.dropEffect === "copy"
912
+ },
913
+ {
914
+ key: "dndGroup",
915
+ when: (s) => s.isOver && s.groupItems
916
+ },
917
+ {
918
+ key: "dndDropNotAllowed",
919
+ when: (s) => s.dropNotAllowed
920
+ },
921
+ {
922
+ key: "hasSubQuery",
923
+ when: (s) => s.hasSubQuery
924
+ }
925
+ ];
926
+ /** Conditional classes applied to a rule group's wrapper element. */
927
+ const ruleGroupOuterConditions = [
928
+ {
929
+ key: "disabled",
930
+ when: (s) => s.disabled
931
+ },
932
+ {
933
+ key: "muted",
934
+ when: (s) => s.muted
935
+ },
936
+ {
937
+ key: "dndDragging",
938
+ when: (s) => s.isDragging
939
+ },
940
+ {
941
+ key: "dndGroup",
942
+ when: (s) => s.isOver && s.groupItems
943
+ }
944
+ ];
945
+ /**
946
+ * Conditional classes applied to the query builder's wrapper element.
947
+ *
948
+ * Only a `boolean` validation result contributes a class; a {@link ValidationMap} describes
949
+ * individual rules and groups, not the query as a whole.
950
+ */
951
+ const queryBuilderOuterConditions = [
952
+ {
953
+ key: "disabled",
954
+ when: (s) => s.disabled
955
+ },
956
+ {
957
+ key: "valid",
958
+ when: (s) => typeof s.validationResult === "boolean" && s.validationResult
959
+ },
960
+ {
961
+ key: "invalid",
962
+ when: (s) => typeof s.validationResult === "boolean" && !s.validationResult
963
+ }
964
+ ];
965
+ /**
966
+ * Expands conditions into `clsx` arguments: every custom class first (in declaration order),
967
+ * then a single object of standard classes.
968
+ */
969
+ const conditionArgs = (conditions, state, { classNames, suppressStandardClassnames }) => {
970
+ const evaluated = conditions.map((condition) => [condition, !!condition.when(state)]);
971
+ return [...evaluated.filter(([condition]) => !condition.standardOnly).map(([condition, active]) => active && classNames?.[condition.key]), suppressStandardClassnames || Object.fromEntries(evaluated.map(([condition, active]) => [standardClassnames[condition.key], active]))];
972
+ };
973
+ /** Composes the classname for a single entry of a spec map. */
974
+ const deriveFromSpec = (key, spec, state, options) => {
975
+ const { sources, conditions } = Array.isArray(spec) ? { sources: spec } : spec;
976
+ return clsx(options.suppressStandardClassnames || standardClassnames[key], ...sources.map((source) => options.classNames?.[source]), ...conditions ? conditionArgs(conditions, state, options) : []);
977
+ };
978
+ const deriveFromSpecs = (specs, state, options) => {
979
+ const result = {};
980
+ for (const [key, spec] of Object.entries(specs)) result[key] = deriveFromSpec(key, spec, state, options);
981
+ return result;
982
+ };
983
+ /**
984
+ * Classnames for each element rendered by a rule. This is the framework-agnostic core of the
985
+ * `classNames` object returned by the `useRule` hook.
986
+ *
987
+ * @group Query Tools
988
+ */
989
+ const deriveRuleClassNames = (options) => deriveFromSpecs(ruleClassnameSources, {}, options);
990
+ /**
991
+ * The classname for a single element of a rule, composed from the same table as
992
+ * {@link deriveRuleClassNames}. Useful where only one is needed, such as the items of a
993
+ * multi-value editor.
994
+ *
995
+ * @group Query Tools
996
+ */
997
+ const deriveRuleClassName = (key, options) => deriveFromSpec(key, ruleClassnameSources[key], {}, options);
998
+ /**
999
+ * Classnames for each element rendered by a rule group, including its conditionally-classed
1000
+ * `header`. This is the framework-agnostic core of the `classNames` object returned by the
1001
+ * `useRuleGroup` hook.
1002
+ *
1003
+ * @group Query Tools
1004
+ */
1005
+ const deriveRuleGroupClassNames = (options) => deriveFromSpecs(ruleGroupClassnameSources, options, options);
1006
+ const deriveOuterClassName = (standardKey, conditions, options) => {
1007
+ const { classNames, suppressStandardClassnames, leadingClassNames = [], validationClassName } = options;
1008
+ return clsx(...leadingClassNames, suppressStandardClassnames || standardClassnames[standardKey], classNames?.[standardKey], ...conditionArgs(conditions, options, options), validationClassName);
1009
+ };
1010
+ /**
1011
+ * The outer (wrapper) classname for a rule, including every conditional state class.
1012
+ *
1013
+ * @group Query Tools
1014
+ */
1015
+ const deriveRuleOuterClassName = (options) => deriveOuterClassName("rule", ruleOuterConditions, options);
1016
+ /**
1017
+ * The outer (wrapper) classname for a rule group, including every conditional state class.
1018
+ *
1019
+ * A group reflects fewer drag-and-drop states than a rule—`dndOver`, `dndCopy`,
1020
+ * `dndDropNotAllowed`, and `hasSubQuery` do not apply—so this is not interchangeable with
1021
+ * {@link deriveRuleOuterClassName}.
1022
+ *
1023
+ * @group Query Tools
1024
+ */
1025
+ const deriveRuleGroupOuterClassName = (options) => deriveOuterClassName("ruleGroup", ruleGroupOuterConditions, options);
1026
+ /**
1027
+ * The outer (wrapper) classname for the query builder itself, including the conditional
1028
+ * `disabled`, `valid`, and `invalid` state classes.
1029
+ *
1030
+ * @group Query Tools
1031
+ */
1032
+ const deriveQueryBuilderClassNames = (options) => deriveOuterClassName("queryBuilder", queryBuilderOuterConditions, options);
1033
+ //#endregion
803
1034
  //#region src/utils/defaultValidator.ts
804
1035
  /**
805
1036
  * This is an example validation function you can pass to {@link react-querybuilder!QueryBuilder QueryBuilder} in the
@@ -1060,8 +1291,10 @@ const prepareOptionList = (props) => {
1060
1291
  ...op,
1061
1292
  [placeholderName]: defaultOption
1062
1293
  };
1063
- } else if (isFlexibleOptionGroupArray(optionList)) for (const og of optionList) for (const opt of og.options) optionsMap[opt.value ?? opt.name] = toFullOption(opt, baseOption);
1064
- else for (const opt of optionList) optionsMap[opt.value ?? opt.name] = toFullOption(opt, baseOption);
1294
+ } else if (isFlexibleOptionGroupArray(optionList)) for (const og of optionList) for (const opt of og.options) optionsMap[opt.value ??
1295
+ /* v8 ignore start -- @preserve */ opt.name] = toFullOption(opt, baseOption);
1296
+ else for (const opt of optionList) optionsMap[opt.value ??
1297
+ /* v8 ignore start -- @preserve */ opt.name] = toFullOption(opt, baseOption);
1065
1298
  return {
1066
1299
  defaultOption,
1067
1300
  optionList,
@@ -1075,7 +1308,8 @@ const filterByComparator = (field, operator, fieldToCompare) => {
1075
1308
  const fullFieldToCompare = toFullOption(fieldToCompare);
1076
1309
  if (fullField.value === fullFieldToCompare.value) return false;
1077
1310
  if (typeof fullField.comparator === "string") return fullField[fullField.comparator] === fullFieldToCompare[fullField.comparator];
1078
- return fullField.comparator?.(fullFieldToCompare, operator) ?? false;
1311
+ return fullField.comparator?.(fullFieldToCompare, operator) ??
1312
+ /* v8 ignore start -- @preserve */ false;
1079
1313
  };
1080
1314
  /**
1081
1315
  * For a given {@link FullField}, returns the `fields` list filtered for
@@ -1090,7 +1324,8 @@ const filterByComparator = (field, operator, fieldToCompare) => {
1090
1324
  */
1091
1325
  const filterFieldsByComparator = (field, fields, operator) => {
1092
1326
  if (!field.comparator) {
1093
- const filterOutSameField = (f) => (f.value ?? f.name) !== (field.value ?? field.name);
1327
+ const filterOutSameField = (f) => (f.value ?? /* v8 ignore start -- @preserve */ f.name) !== (field.value ??
1328
+ /* v8 ignore start -- @preserve */ field.name);
1094
1329
  if (isFlexibleOptionGroupArray(fields)) return fields.map((og) => ({
1095
1330
  ...og,
1096
1331
  options: og.options.filter((v) => filterOutSameField(v))
@@ -1104,6 +1339,128 @@ const filterFieldsByComparator = (field, fields, operator) => {
1104
1339
  return fields.filter((f) => filterByComparator(field, operator, f));
1105
1340
  };
1106
1341
  //#endregion
1342
+ //#region src/utils/deriveRuleContext.ts
1343
+ /**
1344
+ * Resolves the field configuration for a rule's `field`, falling back to a minimal option object
1345
+ * when the field isn't present in the field map.
1346
+ */
1347
+ const getFieldData = (field, fieldMap) => fieldMap?.[field] ?? {
1348
+ name: field,
1349
+ value: field,
1350
+ label: field
1351
+ };
1352
+ /**
1353
+ * The input type for a rule. A field's own `inputType` takes precedence over `getInputType`.
1354
+ */
1355
+ const getRuleInputType = (field, operator, fieldData, getInputType) => fieldData.inputType ?? getInputType(field, operator, { fieldData });
1356
+ /**
1357
+ * Whether the value editor(s) should be hidden for an operator, based on its `arity`.
1358
+ */
1359
+ const hideValueControlsForOperator = (operatorObject) => {
1360
+ const arity = operatorObject?.arity;
1361
+ return typeof arity === "string" && arity === "unary" || typeof arity === "number" && arity < 2;
1362
+ };
1363
+ /**
1364
+ * Value source options for a rule. A `valueSource` present on the rule but absent from the
1365
+ * configured list is appended, so the current selection is always representable.
1366
+ */
1367
+ const getRuleValueSourceOptions = (rule, fieldData, getValueSources) => {
1368
+ const configuredVSs = getValueSources(rule.field, rule.operator, { fieldData });
1369
+ if (rule.valueSource && !getOption(configuredVSs, rule.valueSource)) return [...configuredVSs, {
1370
+ name: rule.valueSource,
1371
+ value: rule.valueSource,
1372
+ label: rule.valueSource
1373
+ }];
1374
+ return configuredVSs;
1375
+ };
1376
+ /**
1377
+ * Normalizes the result of `getParameters` to a non-empty list or `null`.
1378
+ */
1379
+ const getParametersAsList = (parameters) => parameters && parameters.length > 0 ? parameters : null;
1380
+ /**
1381
+ * The value editor type for a rule. `valueSource: "field"` always uses a select list, and
1382
+ * `valueSource: "parameter"` uses a (multi)select when parameters are available.
1383
+ */
1384
+ 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 });
1385
+ /**
1386
+ * The option list presented by a rule's value editor, resolved from its `valueSource`.
1387
+ */
1388
+ const getRuleValues = (rule, fieldData, fields, parametersAsList, getValues) => {
1389
+ const v = rule.valueSource === "field" ? filterFieldsByComparator(fieldData, fields, rule.operator) : rule.valueSource === "parameter" ? parametersAsList ?? [] : getValues(rule.field, rule.operator, { fieldData });
1390
+ return isFlexibleOptionArray(v) || isFlexibleOptionGroupArray(v) ? toFullOptionList(v) : v;
1391
+ };
1392
+ /**
1393
+ * The validation result for a rule: the entry from a query-level {@link ValidationMap} if present,
1394
+ * otherwise the field's own `validator` result, otherwise `null`.
1395
+ */
1396
+ const getRuleValidationResult = (rule, fieldData, validationMap = {}, id = rule.id ?? "") => validationMap[id] ?? (typeof fieldData.validator === "function" ? fieldData.validator(rule) : null);
1397
+ /**
1398
+ * Resolves everything about a single rule that depends on the field/operator configuration:
1399
+ * its field data, operators, value editor type, value list, value sources, match modes, and
1400
+ * validation result.
1401
+ *
1402
+ * This is the framework-agnostic core of the `useRule` hook, shared with
1403
+ * {@link QueryManager.getRuleContext} so that non-React implementations derive identical results.
1404
+ * It performs no memoization; callers are responsible for caching as appropriate.
1405
+ *
1406
+ * @group Query Tools
1407
+ */
1408
+ const deriveRuleContext = (rule, resolvers, options = {}) => {
1409
+ const { fields, fieldMap, getInputType, getMatchModes, getOperators, getParameters, getValueEditorType, getValues, getValueSources, getSubQueryBuilderProps } = resolvers;
1410
+ const fieldData = getFieldData(rule.field, fieldMap);
1411
+ const inputType = fieldData.inputType ?? getInputType(rule.field, rule.operator, { fieldData });
1412
+ const matchModes = getMatchModes(rule.field, { fieldData });
1413
+ const operators = getOperators(rule.field, { fieldData });
1414
+ const operatorObject = getOption(operators, rule.operator);
1415
+ const valueSourceOptions = getRuleValueSourceOptions(rule, fieldData, getValueSources);
1416
+ const parameters = getParametersAsList(getParameters(rule.field, rule.operator, { fieldData }));
1417
+ return {
1418
+ fieldData,
1419
+ hideValueControls: hideValueControlsForOperator(operatorObject),
1420
+ inputType,
1421
+ matchModes,
1422
+ operatorObject,
1423
+ operators,
1424
+ parameters,
1425
+ validationResult: getRuleValidationResult(rule, fieldData, options.validationMap, options.id),
1426
+ valueEditorType: getRuleValueEditorType(rule, fieldData, parameters, getValueEditorType),
1427
+ values: getRuleValues(rule, fieldData, fields, parameters, getValues),
1428
+ valueSourceOptions,
1429
+ valueSources: valueSourceOptions.map(({ value }) => value),
1430
+ subQueryBuilderProps: getSubQueryBuilderProps?.(rule.field, { fieldData }) ?? {}
1431
+ };
1432
+ };
1433
+ /**
1434
+ * The effective combinator for a group: its own `combinator` when it has one, otherwise the
1435
+ * first configured combinator (which is the case for groups with independent combinators).
1436
+ *
1437
+ * This intentionally covers only the current property-based API. The `RuleGroup` component
1438
+ * additionally falls back to its deprecated `combinator` prop; that fallback stays in the hook.
1439
+ */
1440
+ const getRuleGroupCombinator = (ruleGroup, combinators) => isRuleGroupType(ruleGroup) ? ruleGroup.combinator : getFirstOption(combinators) ?? "";
1441
+ /**
1442
+ * Resolves everything about a rule group that depends on the combinator configuration, plus its
1443
+ * validation result.
1444
+ *
1445
+ * Note that unlike {@link deriveRuleContext}, there is no field-level validator fallback—a
1446
+ * group's validation result comes only from the query-level {@link ValidationMap}.
1447
+ *
1448
+ * @group Query Tools
1449
+ */
1450
+ const deriveRuleGroupContext = (ruleGroup, combinators, options = {}) => {
1451
+ const independentCombinators = isRuleGroupTypeIC(ruleGroup);
1452
+ const combinator = getRuleGroupCombinator(ruleGroup, combinators);
1453
+ const combinatorObject = getOption(combinators, combinator);
1454
+ return {
1455
+ combinator,
1456
+ combinatorObject,
1457
+ combinators,
1458
+ combinatorBasedClassName: independentCombinators ? null : combinatorObject?.className ?? "",
1459
+ independentCombinators,
1460
+ validationResult: (options.validationMap ?? {})[options.id ?? ruleGroup.id ?? ""] ?? null
1461
+ };
1462
+ };
1463
+ //#endregion
1107
1464
  //#region src/utils/parseNumber.ts
1108
1465
  /**
1109
1466
  * Converts a string to a number. Uses native `parseFloat` if `parseNumbers` is "native",
@@ -1123,6 +1480,104 @@ const parseNumber = (val, { parseNumbers, bigIntOnOverflow } = {}) => {
1123
1480
  return typeof valAsNum === "bigint" || !Number.isNaN(valAsNum) ? valAsNum : val;
1124
1481
  };
1125
1482
  //#endregion
1483
+ //#region src/utils/deriveValueEditor.ts
1484
+ /** Operators whose value is a list rather than a single scalar. */
1485
+ const multiValueOperators = /* @__PURE__ */ new Set([
1486
+ "between",
1487
+ "notBetween",
1488
+ "in",
1489
+ "notIn"
1490
+ ]);
1491
+ /**
1492
+ * Whether an operator's value is a list of two bounds.
1493
+ *
1494
+ * @group Value Editors
1495
+ */
1496
+ const isBetweenOperator = (operator) => operator === "between" || operator === "notBetween";
1497
+ /**
1498
+ * Determines whether a rule's `value` needs to be collapsed to a single element because it no
1499
+ * longer represents a list, and what it should become.
1500
+ *
1501
+ * This happens when the value is an array (or a comma-containing string in a `number` input,
1502
+ * which `<input type="number">` can't display) while the operator is not one of the multi-value
1503
+ * operators and the editor is not a multiselect—typically right after the operator changes from
1504
+ * `"in"` or `"between"` to something else.
1505
+ *
1506
+ * The React `useValueEditor` hook applies the result in an effect; other implementations may
1507
+ * apply it wherever is idiomatic.
1508
+ *
1509
+ * @group Value Editors
1510
+ */
1511
+ const getValueEditorReset = ({ skipHook, type, operator, value, inputType }) => {
1512
+ return !skipHook && type !== "multiselect" && !multiValueOperators.has(operator) && (Array.isArray(value) || inputType === "number" && typeof value === "string" && value.includes(",")) ? {
1513
+ reset: true,
1514
+ value: toArray(value, { retainEmptyStrings: true })[0] ?? ""
1515
+ } : {
1516
+ reset: false,
1517
+ value
1518
+ };
1519
+ };
1520
+ /**
1521
+ * Produces the next value for a series of value editors when the editor at `index` changes.
1522
+ *
1523
+ * For `between`/`notBetween`, editing the first bound guarantees an array of at least two
1524
+ * elements, seeding the second from the first available option. The result is a comma-joined
1525
+ * string unless `listsAsArrays` is `true`.
1526
+ *
1527
+ * @group Value Editors
1528
+ */
1529
+ const getMultiValueUpdate = ({ value, index, valueAsArray, operator, values, listsAsArrays, parseNumberMethod }) => {
1530
+ const parsedVal = parseNumber(value, { parseNumbers: parseNumberMethod });
1531
+ const needsBetweenFix = index === 0 && isBetweenOperator(operator) && (valueAsArray.length < 2 || valueAsArray[1] === void 0);
1532
+ if (valueAsArray[index] === parsedVal && !needsBetweenFix) return listsAsArrays ? valueAsArray : joinWith(valueAsArray, ",");
1533
+ const v = [...valueAsArray];
1534
+ v[index] = parsedVal;
1535
+ if (needsBetweenFix) v[1] = getFirstOption(values) ?? "";
1536
+ return listsAsArrays ? v : joinWith(v, ",");
1537
+ };
1538
+ /**
1539
+ * Coerces a value to a `bigint`, falling back to the parsed number when it can't be represented
1540
+ * as one (an empty string or a decimal, for example).
1541
+ *
1542
+ * @group Value Editors
1543
+ */
1544
+ const coerceBigIntValue = (value, parseNumberMethod) => {
1545
+ const valAsMaybeNumber = parseNumber(value, {
1546
+ parseNumbers: parseNumberMethod,
1547
+ bigIntOnOverflow: true
1548
+ });
1549
+ try {
1550
+ return BigInt(valAsMaybeNumber);
1551
+ } catch {
1552
+ return valAsMaybeNumber;
1553
+ }
1554
+ };
1555
+ /**
1556
+ * The `type` attribute an `<input>` should use for a rule. `bigint` values and the `in`/`notIn`
1557
+ * operators (whose value is a comma-separated list) both require a text input.
1558
+ *
1559
+ * @group Value Editors
1560
+ */
1561
+ const coerceInputType = (inputType, operator) => inputType === "bigint" || operator === "in" || operator === "notIn" ? "text" : inputType || "text";
1562
+ /**
1563
+ * Produces the next value for a value selector. Multiselect values are normalized to an array
1564
+ * first, then comma-joined unless `listsAsArrays` is `true`. Single-select values pass through.
1565
+ *
1566
+ * @group Value Editors
1567
+ */
1568
+ const getValueSelectorUpdate = (value, { multiple, listsAsArrays } = {}) => {
1569
+ if (!multiple) return value;
1570
+ const valueAsArray = toArray(value);
1571
+ return listsAsArrays ? valueAsArray : joinWith(valueAsArray, ",");
1572
+ };
1573
+ /**
1574
+ * Normalizes a value selector's current value for display. Multiselect values become an array of
1575
+ * strings so they match option names, which are always strings (e.g. `[42]` becomes `["42"]`).
1576
+ *
1577
+ * @group Value Editors
1578
+ */
1579
+ const normalizeValueSelectorValue = (value, multiple) => multiple ? toArray(value).map(String) : value;
1580
+ //#endregion
1126
1581
  //#region src/utils/transformQuery.ts
1127
1582
  const remapProperties = (obj, propertyMap, deleteRemappedProperties) => {
1128
1583
  const result = {};
@@ -1469,7 +1924,9 @@ const translationMatchFilter = (key, keyToTest, conditions) => keyToTest.startsW
1469
1924
  *
1470
1925
  * @group Export
1471
1926
  */
1472
- const getNLTranslataion = (key, translations, conditions = []) => conditions.length === 0 ? translations[key] ?? defaultNLTranslations[key] ?? "" : Object.entries(translations).find(([keyToTest]) => translationMatchFilter(key, keyToTest, conditions))?.[1] ?? Object.entries(defaultNLTranslations).find(([keyToTest]) => translationMatchFilter(key, keyToTest, conditions))?.[1] ?? defaultNLTranslations[key] ?? "";
1927
+ const getNLTranslataion = (key, translations, conditions = []) => conditions.length === 0 ? translations[key] ?? defaultNLTranslations[key] ??
1928
+ /* v8 ignore start -- @preserve */ "" : Object.entries(translations).find(([keyToTest]) => translationMatchFilter(key, keyToTest, conditions))?.[1] ?? Object.entries(defaultNLTranslations).find(([keyToTest]) => translationMatchFilter(key, keyToTest, conditions))?.[1] ?? defaultNLTranslations[key] ??
1929
+ /* v8 ignore next -- @preserve */ "";
1473
1930
  /**
1474
1931
  * Transforms
1475
1932
  * - `match: { mode: "atLeast", threshold: 1 }` to `match: { mode: "some" }`
@@ -1538,7 +1995,7 @@ const bigIntJsonParseReviver = (_key, value) => isPojo(value) && Object.keys(val
1538
1995
  const defaultRuleGroupProcessorCEL = (ruleGroup, options) => {
1539
1996
  const { fields, fallbackExpression, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
1540
1997
  const processRuleGroup = (rg, outermost) => {
1541
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermost ? fallbackExpression : "";
1998
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermost ? fallbackExpression : "";
1542
1999
  const processedRules = [];
1543
2000
  let precedingCombinator = "";
1544
2001
  let firstRule = true;
@@ -1604,10 +2061,11 @@ const defaultRuleProcessorCEL = (rule, opts = {}) => {
1604
2061
  const { mode, threshold } = matchEval;
1605
2062
  const subqueryDepth = opts.subqueryDepth ?? 0;
1606
2063
  const arrayElementAlias = getSubqueryElementAlias(subqueryDepth);
1607
- const nestedArrayFilter = defaultRuleGroupProcessorCEL(transformQuery(rule.value, { ruleProcessor: (r) => ({
2064
+ const celQuery = transformQuery(rule.value, { ruleProcessor: (r) => ({
1608
2065
  ...r,
1609
2066
  field: `${arrayElementAlias}${r.field ? `.${r.field}` : ""}`
1610
- }) }), {
2067
+ }) });
2068
+ const nestedArrayFilter = defaultRuleGroupProcessorCEL(celQuery, {
1611
2069
  ...opts,
1612
2070
  subqueryDepth: subqueryDepth + 1
1613
2071
  });
@@ -1684,7 +2142,7 @@ const defaultRuleGroupProcessorMongoDBQuery = (ruleGroup, options, meta) => {
1684
2142
  const { context, fields, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
1685
2143
  const { inExpressionContext } = context ?? {};
1686
2144
  const processRuleGroup = (rg, outermost) => {
1687
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermost ? mongoDbFallback : false;
2145
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermost ? mongoDbFallback : false;
1688
2146
  const combinator = `$${lc(rg.combinator)}`;
1689
2147
  let hasChildRules = false;
1690
2148
  const expressions = rg.rules.map((rule) => {
@@ -1862,7 +2320,7 @@ const defaultRuleProcessorMongoDB = (rule, options) => {
1862
2320
  const defaultRuleGroupProcessorSpEL = (ruleGroup, options) => {
1863
2321
  const { fields, fallbackExpression, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
1864
2322
  const processRuleGroup = (rg, outermost) => {
1865
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermost ? fallbackExpression : "";
2323
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermost ? fallbackExpression : "";
1866
2324
  const processedRules = [];
1867
2325
  let precedingCombinator = "";
1868
2326
  let firstRule = true;
@@ -2103,7 +2561,7 @@ const cypherCombinatorMap = {
2103
2561
  const defaultRuleGroupProcessorCypher = (ruleGroup, options) => {
2104
2562
  const { fields, fallbackExpression, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
2105
2563
  const processRuleGroup = (rg, outermost) => {
2106
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermost ? fallbackExpression : "";
2564
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermost ? fallbackExpression : "";
2107
2565
  const processedRules = [];
2108
2566
  let precedingCombinator = "";
2109
2567
  let firstRule = true;
@@ -2167,7 +2625,7 @@ const defaultRuleGroupProcessorDrizzle = (ruleGroup, options, _meta) => (columns
2167
2625
  if (!columns || !drizzleOperators) return void 0;
2168
2626
  const { and, not, or } = drizzleOperators;
2169
2627
  const processRuleGroup = (rg, _outermost) => {
2170
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return;
2628
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return;
2171
2629
  const processedRules = rg.rules.map((rule) => {
2172
2630
  if (isRuleGroup(rule)) return processRuleGroup(rule);
2173
2631
  const [validationResult, fieldValidator] = validateRule(rule);
@@ -2200,7 +2658,7 @@ const defaultRuleGroupProcessorDrizzle = (ruleGroup, options, _meta) => (columns
2200
2658
  const defaultRuleGroupProcessorElasticSearch = (ruleGroup, options) => {
2201
2659
  const { fields, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
2202
2660
  const processRuleGroup = (rg) => {
2203
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return false;
2661
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return false;
2204
2662
  const processedRules = rg.rules.map((rule) => {
2205
2663
  if (isRuleGroup(rule)) return processRuleGroup(rule);
2206
2664
  const [validationResult, fieldValidator] = validateRule(rule);
@@ -2235,7 +2693,9 @@ const defaultRuleGroupProcessorGremlin = (ruleGroup, options) => {
2235
2693
  // v8 ignore next -- @preserve
2236
2694
  if (typeof rule === "string" || isRuleGroup(rule)) return void 0;
2237
2695
  const [validationResult, fieldValidator] = validateRule(rule);
2238
- if (!isRuleOrGroupValid(rule, validationResult, fieldValidator) || rule.field === placeholderFieldName || rule.operator === placeholderOperatorName || placeholderValueName !== void 0 && rule.value === placeholderValueName) return;
2696
+ if (!isRuleOrGroupValid(rule, validationResult, fieldValidator) ||
2697
+ /* v8 ignore next 2 -- @preserve */
2698
+ rule.field === placeholderFieldName || rule.operator === placeholderOperatorName || placeholderValueName !== void 0 && rule.value === placeholderValueName) return;
2239
2699
  const fieldData = getOption(fields, rule.field);
2240
2700
  return ruleProcessor(rule, {
2241
2701
  ...options,
@@ -2246,7 +2706,7 @@ const defaultRuleGroupProcessorGremlin = (ruleGroup, options) => {
2246
2706
  };
2247
2707
  /** Recursively processes a nested group into `.and()`/`.or()`/`.not()` form. */
2248
2708
  const processNested = (rg) => {
2249
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return "";
2709
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return "";
2250
2710
  const predicates = [];
2251
2711
  for (const rule of rg.rules) {
2252
2712
  if (typeof rule === "string") continue;
@@ -2264,7 +2724,7 @@ const defaultRuleGroupProcessorGremlin = (ruleGroup, options) => {
2264
2724
  if (predicates.length === 1 && !rg.not) return predicates[0];
2265
2725
  return `.${prefix}(${predicates.map((p) => p.startsWith(".") ? `__${p}` : p).join(", ")})`;
2266
2726
  };
2267
- if (!isRuleOrGroupValid(ruleGroup, validationMap[ruleGroup.id ?? ""])) return fallbackExpression;
2727
+ if (!isRuleOrGroupValid(ruleGroup, validationMap[ruleGroup.id ?? /* v8 ignore start -- @preserve */ ""])) return fallbackExpression;
2268
2728
  const steps = [];
2269
2729
  for (const rule of ruleGroup.rules) {
2270
2730
  if (typeof rule === "string") continue;
@@ -2289,7 +2749,7 @@ const defaultRuleGroupProcessorGremlin = (ruleGroup, options) => {
2289
2749
  const defaultRuleGroupProcessorJSONata = (ruleGroup, options) => {
2290
2750
  const { fields, fallbackExpression, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
2291
2751
  const processRuleGroup = (rg, outermost) => {
2292
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermost ? fallbackExpression : "";
2752
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermost ? fallbackExpression : "";
2293
2753
  const processedRules = [];
2294
2754
  let precedingCombinator = "";
2295
2755
  let firstRule = true;
@@ -2345,7 +2805,7 @@ const defaultRuleGroupProcessorJsonLogic = (ruleGroup, options) => {
2345
2805
  const { fields, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
2346
2806
  const query = convertFromIC(ruleGroup);
2347
2807
  const processRuleGroup = (rg, _outermost) => {
2348
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return false;
2808
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return false;
2349
2809
  const processedRules = rg.rules.map((rule) => {
2350
2810
  if (isRuleGroup(rule)) return processRuleGroup(rule);
2351
2811
  const [validationResult, fieldValidator] = validateRule(rule);
@@ -2373,7 +2833,7 @@ const defaultRuleGroupProcessorJsonLogic = (ruleGroup, options) => {
2373
2833
  const defaultRuleGroupProcessorLDAP = (ruleGroup, options) => {
2374
2834
  const { fields, fallbackExpression, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
2375
2835
  const processRuleGroup = (rg, outermost) => {
2376
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermost ? fallbackExpression : "";
2836
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermost ? fallbackExpression : "";
2377
2837
  const rules = rg.rules.map((rule) => {
2378
2838
  if (isRuleGroup(rule)) return processRuleGroup(rule);
2379
2839
  const [validationResult, fieldValidator] = validateRule(rule);
@@ -2406,7 +2866,7 @@ const isBracketed = (str) => str.startsWith("{") && str.endsWith("}");
2406
2866
  const defaultRuleGroupProcessorMongoDB = (ruleGroup, options, meta) => {
2407
2867
  const { fields, fallbackExpression, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
2408
2868
  const processRuleGroup = (rg, outermost) => {
2409
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermost ? fallbackExpression : "";
2869
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermost ? fallbackExpression : "";
2410
2870
  const combinator = `"$${lc(rg.combinator)}"`;
2411
2871
  let hasChildRules = false;
2412
2872
  const expressions = rg.rules.map((rule) => {
@@ -2443,7 +2903,7 @@ const defaultRuleGroupProcessorMongoDB = (ruleGroup, options, meta) => {
2443
2903
  const defaultRuleGroupProcessorNL = (ruleGroup, options) => {
2444
2904
  const { fields, fallbackExpression, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, translations, validateRule, validationMap } = options;
2445
2905
  const processRuleGroup = (rg, outermostOrLonelyInGroup) => {
2446
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermostOrLonelyInGroup ? fallbackExpression : "";
2906
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermostOrLonelyInGroup ? fallbackExpression : "";
2447
2907
  const rg2 = isRuleGroupTypeIC(rg) && rg.rules.some((r) => typeof r === "string" && lc(r) === "xor") ? convertFromIC(rg) : rg;
2448
2908
  const processedRules = [];
2449
2909
  let precedingCombinator = "";
@@ -2454,7 +2914,8 @@ const defaultRuleGroupProcessorNL = (ruleGroup, options) => {
2454
2914
  continue;
2455
2915
  }
2456
2916
  if (isRuleGroup(rule)) {
2457
- const processedGroup = processRuleGroup(rule, rg2.rules.length === 1 && !(rg2.not || /^xor$/i.test(rg2.combinator ?? "")));
2917
+ const processedGroup = processRuleGroup(rule, rg2.rules.length === 1 && !(rg2.not || /^xor$/i.test(rg2.combinator ??
2918
+ /* v8 ignore start -- @preserve */ "")));
2458
2919
  // v8 ignore else
2459
2920
  if (processedGroup) {
2460
2921
  if (!firstRule && precedingCombinator) {
@@ -2541,7 +3002,7 @@ const defaultRuleGroupProcessorParameterized = (ruleGroup, options) => {
2541
3002
  return sql;
2542
3003
  };
2543
3004
  const processRuleGroup = (rg, outermostOrLonelyInGroup) => {
2544
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermostOrLonelyInGroup ? fallbackExpression : "";
3005
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermostOrLonelyInGroup ? fallbackExpression : "";
2545
3006
  const processedRules = [];
2546
3007
  let precedingCombinator = "";
2547
3008
  let firstRule = true;
@@ -2601,7 +3062,7 @@ const prismaFallback = {};
2601
3062
  const defaultRuleGroupProcessorPrisma = (ruleGroup, options) => {
2602
3063
  const { fields, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
2603
3064
  const processRuleGroup = (rg, outermost) => {
2604
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermost ? prismaFallback : void 0;
3065
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermost ? prismaFallback : void 0;
2605
3066
  const combinator = rg.combinator.toUpperCase();
2606
3067
  let hasChildRules = false;
2607
3068
  const expressions = rg.rules.map((rule) => {
@@ -2640,7 +3101,7 @@ const defaultRuleGroupProcessorSequelize = (ruleGroup, options) => {
2640
3101
  const { sequelizeOperators: Op } = context;
2641
3102
  if (!Op) return void 0;
2642
3103
  const processRuleGroup = (rg, _outermost) => {
2643
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return;
3104
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return;
2644
3105
  const combinator = rg.combinator.toUpperCase();
2645
3106
  let hasChildRules = false;
2646
3107
  const expressions = rg.rules.map((rule) => {
@@ -2679,7 +3140,7 @@ const defaultRuleGroupProcessorSequelize = (ruleGroup, options) => {
2679
3140
  const defaultRuleGroupProcessorSPARQL = (ruleGroup, options) => {
2680
3141
  const { fields, fallbackExpression, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
2681
3142
  const processRuleGroup = (rg, outermost) => {
2682
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermost ? fallbackExpression : "";
3143
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermost ? fallbackExpression : "";
2683
3144
  const processedRules = [];
2684
3145
  let precedingCombinator = "";
2685
3146
  let firstRule = true;
@@ -2734,7 +3195,7 @@ const defaultRuleGroupProcessorSPARQL = (ruleGroup, options) => {
2734
3195
  const defaultRuleGroupProcessorSQL = (ruleGroup, options) => {
2735
3196
  const { fields, fallbackExpression, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
2736
3197
  const processRuleGroup = (rg, outermostOrLonelyInGroup) => {
2737
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermostOrLonelyInGroup ? fallbackExpression : "";
3198
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermostOrLonelyInGroup ? fallbackExpression : "";
2738
3199
  const processedRules = [];
2739
3200
  let precedingCombinator = "";
2740
3201
  let firstRule = true;
@@ -2802,7 +3263,7 @@ const defaultRuleGroupProcessorTanStackDB = (ruleGroup, options) => (refs) => {
2802
3263
  /* v8 ignore next -- @preserve */
2803
3264
  if (refKeys.length === 0) return fallback;
2804
3265
  const processRuleGroup = (rg) => {
2805
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return;
3266
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return;
2806
3267
  const processedRules = rg.rules.map((rule) => {
2807
3268
  if (isRuleGroup(rule)) return processRuleGroup(rule);
2808
3269
  const [validationResult, fieldValidator] = validateRule(rule);
@@ -3205,7 +3666,7 @@ const defaultRuleProcessorCypher = (rule, opts = {}) => {
3205
3666
  * @group Export
3206
3667
  */
3207
3668
  const defaultRuleProcessorDrizzle = (rule, _options) => {
3208
- const opts = _options ?? ( /* v8 ignore start -- @preserve */ {});
3669
+ const opts = _options ?? /* v8 ignore start -- @preserve */ {};
3209
3670
  // v8 ignore next
3210
3671
  const { parseNumbers, preserveValueOrder, context = {} } = opts;
3211
3672
  const { columns, drizzleOperators, useRawFields } = context;
@@ -3228,10 +3689,11 @@ const defaultRuleProcessorDrizzle = (rule, _options) => {
3228
3689
  const { mode, threshold } = matchEval;
3229
3690
  const subqueryDepth = opts.subqueryDepth ?? 0;
3230
3691
  const arrayElementAlias = getSubqueryElementAlias(subqueryDepth);
3231
- const nestedArrayFilter = defaultRuleGroupProcessorDrizzle(transformQuery(rule.value, { ruleProcessor: (r) => ({
3692
+ const sqlQuery = transformQuery(rule.value, { ruleProcessor: (r) => ({
3232
3693
  ...r,
3233
3694
  field: arrayElementAlias
3234
- }) }), {
3695
+ }) });
3696
+ const nestedArrayFilter = defaultRuleGroupProcessorDrizzle(sqlQuery, {
3235
3697
  ...opts,
3236
3698
  context: {
3237
3699
  ...opts.context,
@@ -3770,7 +4232,7 @@ const defaultValueProcessorNL = (rule, opts = {}) => {
3770
4232
  const { escapeQuotes, fields, parseNumbers, quoteFieldNamesWith, quoteValuesWith, fieldIdentifierSeparator, translations } = opts;
3771
4233
  const valueIsField = rule.valueSource === "field";
3772
4234
  const operatorLowerCase = lc(rule.operator);
3773
- const quoteChar = quoteValuesWith || "'";
4235
+ const quoteChar = quoteValuesWith || /* v8 ignore start -- @preserve */ "'";
3774
4236
  const quoteValue = (v) => `${quoteChar}${v}${quoteChar}`;
3775
4237
  const escapeValue = (v) => escapeStringValueQuotes(v, quoteChar, escapeQuotes);
3776
4238
  const wrapAndEscape = (v) => quoteValue(escapeValue(v));
@@ -3778,7 +4240,7 @@ const defaultValueProcessorNL = (rule, opts = {}) => {
3778
4240
  quoteFieldNamesWith,
3779
4241
  fieldIdentifierSeparator
3780
4242
  });
3781
- const t = translations ?? ( /* v8 ignore start -- @preserve */ {});
4243
+ const t = translations ?? /* v8 ignore start -- @preserve */ {};
3782
4244
  const orTL = t.or ?? "or";
3783
4245
  const trueTL = t.true ?? "true";
3784
4246
  const falseTL = t.false ?? "false";
@@ -3790,7 +4252,8 @@ const defaultValueProcessorNL = (rule, opts = {}) => {
3790
4252
  case "between":
3791
4253
  case "notbetween": {
3792
4254
  if (!valueIsField) return defaultValueProcessorByRule(rule, opts);
3793
- const valueAsArray = toArray(rule.value, { retainEmptyStrings: true }).slice(0, 2).map((v) => wrapFieldName(getOption(fields ?? [], v)?.label ?? v));
4255
+ const valueAsArray = toArray(rule.value, { retainEmptyStrings: true }).slice(0, 2).map((v) => wrapFieldName(getOption(fields ??
4256
+ /* v8 ignore start -- @preserve */ [], v)?.label ?? v));
3794
4257
  if (valueAsArray.length < 2 || !isValidValue(valueAsArray[0]) || !isValidValue(valueAsArray[1])) return "";
3795
4258
  return defaultValueProcessorByRule({
3796
4259
  ...rule,
@@ -3801,14 +4264,16 @@ const defaultValueProcessorNL = (rule, opts = {}) => {
3801
4264
  case "notin": {
3802
4265
  const valueAsArray = toArray(rule.value);
3803
4266
  if (valueAsArray.length === 0) return "";
3804
- const valStringArray = valueAsArray.map((v) => valueIsField ? wrapFieldName(getOption(fields ?? [], v)?.label ?? v) : shouldRenderAsNumber(v, parseNumbers) ? `${trimIfString(v)}` : `${wrapAndEscape(v)}`);
4267
+ const valStringArray = valueAsArray.map((v) => valueIsField ? wrapFieldName(getOption(fields ??
4268
+ /* v8 ignore start -- @preserve */ [], v)?.label ?? v) : shouldRenderAsNumber(v, parseNumbers) ? `${trimIfString(v)}` : `${wrapAndEscape(v)}`);
3805
4269
  if (valStringArray.length === 1) return valStringArray[0];
3806
4270
  const oxfordComma = useOxfordComma && valStringArray.length > 2 ? "," : "";
3807
4271
  return `(${`${valStringArray.slice(0, -1).join(listSep)}${oxfordComma} ${orTL} ${valStringArray.at(-1)}`})`;
3808
4272
  }
3809
4273
  }
3810
4274
  if (typeof rule.value === "boolean") return rule.value ? trueTL : falseTL;
3811
- return valueIsField ? wrapFieldName(getOption(fields ?? [], rule.value)?.label ?? rule.value) : shouldRenderAsNumber(rule.value, parseNumbers) ? `${trimIfString(rule.value)}` : `${wrapAndEscape(rule.value)}`;
4275
+ return valueIsField ? wrapFieldName(getOption(fields ??
4276
+ /* v8 ignore start -- @preserve */ [], rule.value)?.label ?? rule.value) : shouldRenderAsNumber(rule.value, parseNumbers) ? `${trimIfString(rule.value)}` : `${wrapAndEscape(rule.value)}`;
3812
4277
  };
3813
4278
  //#endregion
3814
4279
  //#region src/utils/formatQuery/defaultRuleProcessorNL.ts
@@ -3860,7 +4325,7 @@ const defaultOperatorProcessorNL = (rule, opts = {}) => {
3860
4325
  name: field,
3861
4326
  value: field,
3862
4327
  label: field
3863
- } }) ?? []), normalizedOperator) ?? {
4328
+ } }) ?? /* v8 ignore start -- @preserve */ []), normalizedOperator) ?? {
3864
4329
  name: normalizedOperator,
3865
4330
  value: normalizedOperator,
3866
4331
  label: normalizedOperator
@@ -3876,7 +4341,7 @@ const defaultOperatorProcessorNL = (rule, opts = {}) => {
3876
4341
  const defaultRuleProcessorNL = (rule, opts) => {
3877
4342
  const { field, operator } = rule;
3878
4343
  // v8 ignore next
3879
- const { fieldData, quoteFieldNamesWith = ["", ""], fieldIdentifierSeparator = "", quoteValuesWith = `'`, operatorProcessor = defaultOperatorProcessorNL, valueProcessor = defaultValueProcessorNL, concatOperator = "||", wordOrder = "SVO" } = opts ?? ( /* v8 ignore start -- @preserve */ {});
4344
+ const { fieldData, quoteFieldNamesWith = ["", ""], fieldIdentifierSeparator = "", quoteValuesWith = `'`, operatorProcessor = defaultOperatorProcessorNL, valueProcessor = defaultValueProcessorNL, concatOperator = "||", wordOrder = "SVO" } = opts ?? /* v8 ignore start -- @preserve */ {};
3880
4345
  const processedField = getQuotedFieldName(fieldData?.label ?? field, {
3881
4346
  quoteFieldNamesWith,
3882
4347
  fieldIdentifierSeparator
@@ -4097,7 +4562,7 @@ const defaultRuleProcessorParameterized = (rule, opts, meta) => {
4097
4562
  }
4098
4563
  let paramValue = rule.value;
4099
4564
  if (typeof rule.value === "string") if (shouldRenderAsNumber(rule.value, parseNumbers)) paramValue = parseNumber(rule.value, { parseNumbers });
4100
- else paramValue = /^'.*'$/g.test(value) ? value.replaceAll(/(^'|'$)/g, "") : /* v8 ignore next -- @preserve */ value;
4565
+ else paramValue = /^'.*'$/g.test(value) ? value.replaceAll(/(^'|'$)/g, "") : /* v8 ignore next -- @preserve */ value;
4101
4566
  let paramName = "";
4102
4567
  if (parameterized) params.push(paramValue);
4103
4568
  else {
@@ -4314,7 +4779,7 @@ const defaultRuleProcessorSPARQL = (rule, opts = {}) => {
4314
4779
  * @group Export
4315
4780
  */
4316
4781
  const defaultRuleProcessorTanStackDB = (rule, _options) => {
4317
- const { parseNumbers, preserveValueOrder, context = {} } = _options ?? ( /* v8 ignore start -- @preserve */ {});
4782
+ const { parseNumbers, preserveValueOrder, context = {} } = _options ?? /* v8 ignore start -- @preserve */ {};
4318
4783
  const ops = context.tanStackDbOperators;
4319
4784
  const refs = context._tanstackDbRefs;
4320
4785
  const primaryRef = context._tanstackDbPrimaryRef;
@@ -4550,7 +5015,8 @@ function formatQuery(ruleGroup, optionParam = {}) {
4550
5015
  const ruleProcessor = (typeof ruleProcessor_option === "function" ? ruleProcessor_option : null) ?? (valueProcessorCanActAsRuleProcessor.has(format) && typeof ruleProcessor_option !== "function" && valueProcessor_option ? valueProcessor : null) ?? defaultRuleProcessors[format] ?? defaultRuleProcessorSQL;
4551
5016
  const quoteFieldNamesWith = getQuoteFieldNamesWithArray(quoteFieldNamesWith_option);
4552
5017
  const fields = toFullOptionList(optObj.fields);
4553
- const getOperators = (f, m) => toFullOptionList(getOperators_option(f, m) ?? []);
5018
+ const getOperators = (f, m) => toFullOptionList(getOperators_option(f, m) ??
5019
+ /* v8 ignore start -- @preserve */ []);
4554
5020
  const fallbackExpression = fallbackExpression_option ?? defaultFallbackExpressions[format] ?? defaultFallbackExpressions.sql;
4555
5021
  let validationMap = {};
4556
5022
  // v8 ignore else
@@ -4573,7 +5039,7 @@ function formatQuery(ruleGroup, optionParam = {}) {
4573
5039
  const uniqueFields = toFlatOptionArray(fields);
4574
5040
  for (const f of uniqueFields)
4575
5041
  // v8 ignore else
4576
- if (typeof f.validator === "function") validatorMap[f.value ?? f.name] = f.validator;
5042
+ if (typeof f.validator === "function") validatorMap[f.value ?? /* v8 ignore start -- @preserve */ f.name] = f.validator;
4577
5043
  const validateRule = (rule) => {
4578
5044
  let validationResult;
4579
5045
  let fieldValidator;
@@ -4744,12 +5210,20 @@ const getParentPath = (path) => path.slice(0, -1);
4744
5210
  /**
4745
5211
  * Determines if two paths (each `Path`) are equivalent.
4746
5212
  */
4747
- const pathsAreEqual = (path1, path2) => path1.length === path2.length && path1.every((val, idx) => val === path2[idx]);
5213
+ const pathsAreEqual = (path1, path2) => {
5214
+ if (path1.length !== path2.length) return false;
5215
+ for (let i = 0; i < path1.length; i++) if (path1[i] !== path2[i]) return false;
5216
+ return true;
5217
+ };
4748
5218
  /**
4749
5219
  * Determines if the first path is an ancestor of the second path. The first path must
4750
5220
  * be shorter and exactly match the second path up through the length of the first path.
4751
5221
  */
4752
- const isAncestor = (maybeAncestor, path) => maybeAncestor.length < path.length && new RegExp(`^${maybeAncestor.join("-")}`).test(path.join("-"));
5222
+ const isAncestor = (maybeAncestor, path) => {
5223
+ if (maybeAncestor.length >= path.length) return false;
5224
+ for (let i = 0; i < maybeAncestor.length; i++) if (maybeAncestor[i] !== path[i]) return false;
5225
+ return true;
5226
+ };
4753
5227
  /**
4754
5228
  * Finds the deepest/longest path that two paths have in common.
4755
5229
  */
@@ -4782,11 +5256,38 @@ const pathIsDisabled = (path, query) => {
4782
5256
  }
4783
5257
  return disabled;
4784
5258
  };
5259
+ /**
5260
+ * Determines if the rule or group at the specified path is disabled by `disabledPaths`—the array
5261
+ * form of the `QueryBuilder` `disabled` prop, which disables nodes by position rather than by a
5262
+ * `disabled` property on the node itself. A path is disabled if it appears in `disabledPaths` or
5263
+ * descends from a path that does.
5264
+ *
5265
+ * @group Paths
5266
+ */
5267
+ const pathIsDisabledByPaths = (path, disabledPaths = []) => disabledPaths.some((p) => pathsAreEqual(p, path) || isAncestor(p, path));
5268
+ /**
5269
+ * Builds the {@link PathInfo} for each child of a group at `path`. A child is disabled if its
5270
+ * parent is disabled or if its own path appears in `disabledPaths`.
5271
+ *
5272
+ * @group Paths
5273
+ */
5274
+ const derivePathInfo = (path, childCount, { disabled = false, disabledPaths = [] } = {}) => {
5275
+ const paths = [];
5276
+ for (let i = 0; i < childCount; i++) {
5277
+ const thisPath = [...path, i];
5278
+ paths[i] = {
5279
+ path: thisPath,
5280
+ disabled: disabled || disabledPaths.some((p) => pathsAreEqual(thisPath, p))
5281
+ };
5282
+ }
5283
+ return paths;
5284
+ };
4785
5285
  //#endregion
4786
5286
  //#region src/utils/generateAccessibleDescription.ts
4787
5287
  const generateAccessibleDescription = (params) => pathsAreEqual([], params.path) ? `Query builder` : `Rule group at path ${params.path.join("-")}`;
4788
5288
  //#endregion
4789
5289
  //#region src/utils/generateID.ts
5290
+ /* v8 ignore file -- this is fine */
4790
5291
  const cryptoModule = globalThis.crypto;
4791
5292
  const uuidV4regex = /^[\da-f]{8}-[\da-f]{4}-4[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/i;
4792
5293
  /**
@@ -4835,7 +5336,7 @@ const dummyFD$1 = {
4835
5336
  * `FullOption<MatchMode>[]` of all match modes by default.
4836
5337
  */
4837
5338
  const getMatchModesUtil = (fieldData, getMatchModes) => {
4838
- const fd = fieldData ? toFullOption(fieldData) : /* v8 ignore next -- @preserve */ dummyFD$1;
5339
+ const fd = fieldData ? toFullOption(fieldData) : /* v8 ignore next -- @preserve */ dummyFD$1;
4839
5340
  let matchModes = fd.matchModes ?? false;
4840
5341
  if (!matchModes && getMatchModes) matchModes = getMatchModes(fd.value, { fieldData: fd });
4841
5342
  if (matchModes === true) return defaultMatchModes;
@@ -4853,7 +5354,7 @@ const getFirstOptionsFrom = (opts, r, listsAsArrays) => {
4853
5354
  const firstOption = getFirstOption(opts);
4854
5355
  if (r.operator === "between" || r.operator === "notBetween") {
4855
5356
  const valueAsArray = [firstOption, firstOption];
4856
- return listsAsArrays ? valueAsArray : joinWith(valueAsArray.map((v) => v ?? ""), ",");
5357
+ return listsAsArrays ? valueAsArray : joinWith(valueAsArray.map((v) => v ?? /* v8 ignore start -- @preserve */ ""), ",");
4857
5358
  }
4858
5359
  return firstOption;
4859
5360
  };
@@ -4989,6 +5490,9 @@ const mergeClassnames = (...args) => ({
4989
5490
  removeRule: joinClassnamesByName("removeRule", args),
4990
5491
  notToggle: joinClassnamesByName("notToggle", args),
4991
5492
  shiftActions: joinClassnamesByName("shiftActions", args),
5493
+ undoRedoActions: joinClassnamesByName("undoRedoActions", args),
5494
+ undoAction: joinClassnamesByName("undoAction", args),
5495
+ redoAction: joinClassnamesByName("redoAction", args),
4992
5496
  dragHandle: joinClassnamesByName("dragHandle", args),
4993
5497
  lockRule: joinClassnamesByName("lockRule", args),
4994
5498
  lockGroup: joinClassnamesByName("lockGroup", args),
@@ -5018,6 +5522,58 @@ const mergeClassnames = (...args) => ({
5018
5522
  valueDateTimeRelative: joinClassnamesByName("valueDateTimeRelative", args)
5019
5523
  });
5020
5524
  //#endregion
5525
+ //#region src/utils/optionResolvers.ts
5526
+ /**
5527
+ * Resolves the operator list for a field, applying the same precedence as the `QueryBuilder`
5528
+ * component: the field's own `operators`, then the `getOperators` callback, then the
5529
+ * query-level operator list.
5530
+ *
5531
+ * @group Option Lists
5532
+ */
5533
+ const resolveOperatorList = ({ field, fieldData, getOperators, operators, placeholder, baseOption, autoSelectOption }) => prepareOptionList({
5534
+ optionList: fieldData?.operators ?? getOperators?.(field, { fieldData }) ?? operators,
5535
+ placeholder,
5536
+ baseOption,
5537
+ labelMap: defaultOperatorLabelMap,
5538
+ autoSelectOption
5539
+ }).optionList;
5540
+ /**
5541
+ * Resolves the default operator for a field, applying the same precedence as the `QueryBuilder`
5542
+ * component: the field's own `defaultOperator`, then the `getDefaultOperator` option (a string or
5543
+ * a function), then the first available operator.
5544
+ *
5545
+ * @group Option Lists
5546
+ */
5547
+ const resolveDefaultOperator = ({ field, fieldData, getDefaultOperator, getOperators }) => {
5548
+ if (fieldData?.defaultOperator) return fieldData.defaultOperator;
5549
+ if (getDefaultOperator) return typeof getDefaultOperator === "function" ? getDefaultOperator(field, { fieldData }) : getDefaultOperator;
5550
+ return getFirstOption(getOperators(field, { fieldData })) ?? "";
5551
+ };
5552
+ /**
5553
+ * Resolves the value editor type for a field/operator pair, applying the same precedence as the
5554
+ * `QueryBuilder` component: the field's own `valueEditorType` (a string or a function of the
5555
+ * operator), then the `getValueEditorType` callback, then `"text"`.
5556
+ *
5557
+ * @group Option Lists
5558
+ */
5559
+ const resolveValueEditorType = ({ field, operator, fieldData, getValueEditorType }) => {
5560
+ if (fieldData?.valueEditorType) return typeof fieldData.valueEditorType === "function" ? fieldData.valueEditorType(operator) : fieldData.valueEditorType;
5561
+ return getValueEditorType?.(field, operator, { fieldData }) ?? "text";
5562
+ };
5563
+ /**
5564
+ * Resolves the value option list for a field/operator pair, applying the same precedence as the
5565
+ * `QueryBuilder` component: the field's own `values`, then the `getValues` callback, then an
5566
+ * empty list.
5567
+ *
5568
+ * @group Option Lists
5569
+ */
5570
+ const resolveValueList = ({ field, operator, fieldData, getValues, placeholder, baseOption, autoSelectOption }) => prepareOptionList({
5571
+ optionList: fieldData?.values ?? getValues?.(field, operator, { fieldData }) ?? [],
5572
+ placeholder,
5573
+ baseOption,
5574
+ autoSelectOption
5575
+ }).optionList;
5576
+ //#endregion
5021
5577
  //#region src/utils/preferProp.ts
5022
5578
  const preferPropDefaultTrue = (prop, context) => prop === false ? false : prop ? true : !(context === false);
5023
5579
  const preferPropDefaultFalse = (prop, context) => prop ? true : prop === false ? false : !!context;
@@ -5082,6 +5638,21 @@ const prepareRuleGroup = (queryObject, { idGenerator = generateID } = {}) => {
5082
5638
  * Ensures that a rule or group is valid. See {@link prepareRule} and {@link prepareRuleGroup}.
5083
5639
  */
5084
5640
  const prepareRuleOrGroup = (rg, { idGenerator = generateID } = {}) => isRuleGroup(rg) ? prepareRuleGroup(rg, { idGenerator }) : prepareRule(rg, { idGenerator });
5641
+ /**
5642
+ * Resolves the query a query builder should render from the available sources, in precedence
5643
+ * order: the controlled `query`, then whatever is already in the store, then the uncontrolled
5644
+ * `defaultQuery`, then a freshly created empty group.
5645
+ *
5646
+ * The result is prepared with {@link prepareRuleGroup} unless it already has an `id`, which is
5647
+ * taken to mean it has been prepared before—most often because the caller is passing back the
5648
+ * object it received from `onQueryChange`.
5649
+ *
5650
+ * @group Query Tools
5651
+ */
5652
+ const resolveCandidateQuery = (sources, options) => {
5653
+ const candidateQuery = sources.query ?? sources.storeQuery ?? sources.defaultQuery ?? sources.fallbackQuery;
5654
+ return candidateQuery.id ? candidateQuery : prepareRuleGroup(candidateQuery, options);
5655
+ };
5085
5656
  //#endregion
5086
5657
  //#region src/utils/regenerateIDs.ts
5087
5658
  /**
@@ -5111,6 +5682,28 @@ const regenerateIDs = (subject, { idGenerator = generateID } = {}) => {
5111
5682
  //#endregion
5112
5683
  //#region src/utils/queryTools.ts
5113
5684
  /**
5685
+ * Whether a mutation targeting `pathOrID` is blocked by the given guards, and why.
5686
+ * Returns `null` when the mutation may proceed.
5687
+ *
5688
+ * Exported so that callers which run their own logic before mutating—such as a UI layer that
5689
+ * invokes a confirmation callback—can apply the same rules without duplicating them.
5690
+ *
5691
+ * @group Query Tools
5692
+ */
5693
+ const getGuardAbortReason = (query, pathOrID, guards = {}, { asParent = false } = {}) => {
5694
+ if (guards.queryDisabled) return asParent ? "parent-disabled" : "target-disabled";
5695
+ if (!guards.respectDisabled || pathOrID === void 0) return null;
5696
+ const path = Array.isArray(pathOrID) ? pathOrID : getPathOfID(pathOrID, query);
5697
+ if (path && (pathIsDisabled(path, query) || pathIsDisabledByPaths(path, guards.disabledPaths))) return asParent ? "parent-disabled" : "target-disabled";
5698
+ return null;
5699
+ };
5700
+ /**
5701
+ * Whether adding a group beneath `parentPath` would exceed `maxLevels`.
5702
+ *
5703
+ * @group Query Tools
5704
+ */
5705
+ const exceedsMaxLevels = (parentPath, { maxLevels = Infinity } = {}) => !!parentPath && parentPath.length >= maxLevels;
5706
+ /**
5114
5707
  * Adds a rule or group to a query without mutating the original query.
5115
5708
  *
5116
5709
  * @returns A new query with the rule or group added.
@@ -5126,9 +5719,42 @@ const add = (query, ruleOrGroup, parentPathOrID, options = {}) => produce(query,
5126
5719
  * @group Query Tools
5127
5720
  */
5128
5721
  const addInPlace = (query, ruleOrGroup, parentPathOrID, options = {}) => {
5129
- const { combinators = defaultCombinators, combinatorPreceding, idGenerator = generateID } = options;
5722
+ const { combinators = defaultCombinators, combinatorPreceding, idGenerator = generateID, onAbort } = options;
5130
5723
  const parent = Array.isArray(parentPathOrID) ? findPath(parentPathOrID, query) : findID(parentPathOrID, query);
5131
- if (!parent || !isRuleGroup(parent)) return query;
5724
+ if (!parent) {
5725
+ onAbort?.({
5726
+ reason: "parent-not-found",
5727
+ operation: "add",
5728
+ pathOrID: parentPathOrID
5729
+ });
5730
+ return query;
5731
+ }
5732
+ if (!isRuleGroup(parent)) {
5733
+ onAbort?.({
5734
+ reason: "parent-not-a-group",
5735
+ operation: "add",
5736
+ pathOrID: parentPathOrID
5737
+ });
5738
+ return query;
5739
+ }
5740
+ const parentPath = Array.isArray(parentPathOrID) ? parentPathOrID : getPathOfID(parentPathOrID, query);
5741
+ const addGuardReason = getGuardAbortReason(query, parentPath, options, { asParent: true });
5742
+ if (addGuardReason) {
5743
+ onAbort?.({
5744
+ reason: addGuardReason,
5745
+ operation: "add",
5746
+ pathOrID: parentPathOrID
5747
+ });
5748
+ return query;
5749
+ }
5750
+ if (isRuleGroup(ruleOrGroup) && exceedsMaxLevels(parentPath, options)) {
5751
+ onAbort?.({
5752
+ reason: "max-levels-exceeded",
5753
+ operation: "add",
5754
+ pathOrID: parentPathOrID
5755
+ });
5756
+ return query;
5757
+ }
5132
5758
  if (isRuleGroupTypeIC(parent) && parent.rules.length > 0) {
5133
5759
  const prevCombinator = parent.rules.at(-2);
5134
5760
  parent.rules.push(combinatorPreceding ?? (typeof prevCombinator === "string" ? prevCombinator : getFirstOption(combinators)));
@@ -5149,7 +5775,7 @@ const updatePropRank = {
5149
5775
  value: 4
5150
5776
  };
5151
5777
  /** Stable-sorts `[prop, value]` entries by {@link updatePropRank}. */
5152
- const orderUpdateEntries = (entries) => entries.sort((x, y) => (updatePropRank[x[0]] ?? 3) - (updatePropRank[y[0]] ?? 3));
5778
+ const orderUpdateEntries = (entries) => entries.toSorted((x, y) => (updatePropRank[x[0]] ?? 3) - (updatePropRank[y[0]] ?? 3));
5153
5779
  /**
5154
5780
  * Normalizes the variadic {@link update}/{@link updateInPlace} arguments (single
5155
5781
  * prop+value, parallel arrays, or property map) into a canonically-ordered list
@@ -5212,19 +5838,69 @@ const updateInPlace = ((query, a, b, c, d) => applyUpdatesInPlace(query, a, b, c
5212
5838
  * Updates a single property of a rule or group within a query in place.
5213
5839
  */
5214
5840
  const updateInPlaceSingle = (query, prop, value, pathOrID, options = {}) => {
5215
- const { resetOnFieldChange: _resetOnFieldChange = true, resetOnOperatorChange = false, getRuleDefaultOperator = () => "=", getValueSources = () => ["value"], getRuleDefaultValue = () => "", getMatchModes = () => [] } = options;
5841
+ const { resetOnFieldChange: _resetOnFieldChange = true, resetOnOperatorChange = false, getRuleDefaultOperator = () => "=", getValueSources = () => ["value"], getRuleDefaultValue = () => "", getMatchModes = () => [], onAbort } = options;
5216
5842
  let resetOnFieldChange = _resetOnFieldChange;
5217
5843
  const path = Array.isArray(pathOrID) ? pathOrID : getPathOfID(pathOrID, query);
5218
- if (!path) return query;
5844
+ if (!path) {
5845
+ onAbort?.({
5846
+ reason: "target-not-found",
5847
+ operation: "update",
5848
+ pathOrID
5849
+ });
5850
+ return query;
5851
+ }
5852
+ const updateGuards = prop === "disabled" ? { queryDisabled: options.queryDisabled } : options;
5853
+ const updateGuardReason = getGuardAbortReason(query, path, updateGuards);
5854
+ if (updateGuardReason) {
5855
+ onAbort?.({
5856
+ reason: updateGuardReason,
5857
+ operation: "update",
5858
+ pathOrID
5859
+ });
5860
+ return query;
5861
+ }
5219
5862
  if (prop === "combinator" && !isRuleGroupType(query)) {
5220
- const parentRules = findPath(getParentPath(path), query).rules;
5221
- if (path.at(-1) % 2 === 1) parentRules[path.at(-1)] = value;
5863
+ const parent = findPath(getParentPath(path), query);
5864
+ if (!parent || !isRuleGroup(parent)) {
5865
+ onAbort?.({
5866
+ reason: "target-not-found",
5867
+ operation: "update",
5868
+ pathOrID
5869
+ });
5870
+ return query;
5871
+ }
5872
+ const parentRules = parent.rules;
5873
+ if (path.at(-1) % 2 === 1) if (parentRules[path.at(-1)] === value) onAbort?.({
5874
+ reason: "no-change",
5875
+ operation: "update",
5876
+ pathOrID
5877
+ });
5878
+ else parentRules[path.at(-1)] = value;
5879
+ else onAbort?.({
5880
+ reason: "not-a-combinator-slot",
5881
+ operation: "update",
5882
+ pathOrID
5883
+ });
5222
5884
  return query;
5223
5885
  }
5224
5886
  const ruleOrGroup = findPath(path, query);
5225
- if (!ruleOrGroup) return query;
5887
+ if (!ruleOrGroup) {
5888
+ onAbort?.({
5889
+ reason: "target-not-found",
5890
+ operation: "update",
5891
+ pathOrID
5892
+ });
5893
+ return query;
5894
+ }
5226
5895
  const isGroup = isRuleGroup(ruleOrGroup);
5227
- if (ruleOrGroup[prop] === value) return query;
5896
+ if (ruleOrGroup[prop] === value) {
5897
+ onAbort?.({
5898
+ reason: "no-change",
5899
+ operation: "update",
5900
+ pathOrID
5901
+ });
5902
+ return query;
5903
+ }
5228
5904
  if (prop !== "valueSource") ruleOrGroup[prop] = value;
5229
5905
  if (isGroup) return query;
5230
5906
  let resetValueSource = false;
@@ -5270,7 +5946,7 @@ const updateInPlaceSingle = (query, prop, value, pathOrID, options = {}) => {
5270
5946
  *
5271
5947
  * @group Query Tools
5272
5948
  */
5273
- const remove = (query, pathOrID) => produce(query, (q) => removeInPlace(q, pathOrID));
5949
+ const remove = (query, pathOrID, options = {}) => produce(query, (q) => removeInPlace(q, pathOrID, options));
5274
5950
  /**
5275
5951
  * Removes a rule or group from a query in place.
5276
5952
  *
@@ -5278,21 +5954,54 @@ const remove = (query, pathOrID) => produce(query, (q) => removeInPlace(q, pathO
5278
5954
  *
5279
5955
  * @group Query Tools
5280
5956
  */
5281
- const removeInPlace = (query, pathOrID) => {
5957
+ const removeInPlace = (query, pathOrID, options = {}) => {
5958
+ const { onAbort } = options;
5282
5959
  const path = Array.isArray(pathOrID) ? pathOrID : getPathOfID(pathOrID, query);
5283
- if (!path || path.length === 0 || !isRuleGroupType(query) && !findPath(path, query)) return query;
5284
- const index = path.at(-1);
5285
- const parent = findPath(getParentPath(path), query);
5286
- if (parent && isRuleGroup(parent)) if (!isRuleGroupType(parent) && parent.rules.length > 1) {
5287
- const idxStartDelete = index === 0 ? 0 : index - 1;
5288
- parent.rules.splice(idxStartDelete, 2);
5289
- } else parent.rules.splice(index, 1);
5290
- return query;
5291
- };
5292
- const getNextPath = (query, currentPath, newPathOrShiftDirection) => {
5293
- if (Array.isArray(newPathOrShiftDirection)) return newPathOrShiftDirection;
5294
- const ic = isRuleGroupTypeIC(query);
5295
- if (newPathOrShiftDirection === "up") if (pathsAreEqual(currentPath, [0])) return currentPath;
5960
+ if (!path) {
5961
+ onAbort?.({
5962
+ reason: "target-not-found",
5963
+ operation: "remove",
5964
+ pathOrID
5965
+ });
5966
+ return query;
5967
+ }
5968
+ if (path.length === 0) {
5969
+ onAbort?.({
5970
+ reason: "root-not-allowed",
5971
+ operation: "remove",
5972
+ pathOrID
5973
+ });
5974
+ return query;
5975
+ }
5976
+ const removeGuardReason = getGuardAbortReason(query, path, options);
5977
+ if (removeGuardReason) {
5978
+ onAbort?.({
5979
+ reason: removeGuardReason,
5980
+ operation: "remove",
5981
+ pathOrID
5982
+ });
5983
+ return query;
5984
+ }
5985
+ if (!findPath(path, query)) {
5986
+ onAbort?.({
5987
+ reason: "target-not-found",
5988
+ operation: "remove",
5989
+ pathOrID
5990
+ });
5991
+ return query;
5992
+ }
5993
+ const index = path.at(-1);
5994
+ const parent = findPath(getParentPath(path), query);
5995
+ if (!isRuleGroupType(parent) && parent.rules.length > 1) {
5996
+ const idxStartDelete = index === 0 ? 0 : index - 1;
5997
+ parent.rules.splice(idxStartDelete, 2);
5998
+ } else parent.rules.splice(index, 1);
5999
+ return query;
6000
+ };
6001
+ const getNextPath = (query, currentPath, newPathOrShiftDirection) => {
6002
+ if (Array.isArray(newPathOrShiftDirection)) return newPathOrShiftDirection;
6003
+ const ic = isRuleGroupTypeIC(query);
6004
+ if (newPathOrShiftDirection === "up") if (pathsAreEqual(currentPath, [0])) return currentPath;
5296
6005
  else if (currentPath.at(-1) === 0) {
5297
6006
  const parentPath = getParentPath(currentPath);
5298
6007
  return [...getParentPath(parentPath), Math.max(0, parentPath.at(-1) - (ic ? 1 : 0))];
@@ -5308,7 +6017,8 @@ const getNextPath = (query, currentPath, newPathOrShiftDirection) => {
5308
6017
  return [...getParentPath(parentPath), parentPath.at(-1) + 1];
5309
6018
  } else {
5310
6019
  const evaluationPath = [...getParentPath(currentPath), currentPath.at(-1) + (ic ? 2 : 1)];
5311
- if (isRuleGroup(findPath(evaluationPath, query))) return [...evaluationPath, 0];
6020
+ const entityToEvaluate = findPath(evaluationPath, query);
6021
+ if (isRuleGroup(entityToEvaluate)) return [...evaluationPath, 0];
5312
6022
  else return [...getParentPath(currentPath), currentPath.at(-1) + (ic ? 3 : 2)];
5313
6023
  }
5314
6024
  return currentPath;
@@ -5331,13 +6041,67 @@ const move = (query, oldPathOrID, newPath, options = {}) => produce(query, (q) =
5331
6041
  * @group Query Tools
5332
6042
  */
5333
6043
  const moveInPlace = (query, oldPathOrID, newPath, options = {}) => {
5334
- const { clone = false, combinators = defaultCombinators, idGenerator = generateID } = options;
6044
+ const { clone = false, combinators = defaultCombinators, idGenerator = generateID, onAbort } = options;
5335
6045
  const oldPath = Array.isArray(oldPathOrID) ? oldPathOrID : getPathOfID(oldPathOrID, query);
5336
- if (!oldPath) return query;
5337
- const nextPath = getNextPath(query, oldPath, newPath);
5338
- if (oldPath.length === 0 || pathsAreEqual(oldPath, nextPath) || !findPath(getParentPath(nextPath), query)) return query;
6046
+ if (!oldPath) {
6047
+ onAbort?.({
6048
+ reason: "target-not-found",
6049
+ operation: "move",
6050
+ pathOrID: oldPathOrID
6051
+ });
6052
+ return query;
6053
+ }
5339
6054
  const ruleOrGroupOriginal = findPath(oldPath, query);
5340
- if (!ruleOrGroupOriginal) return query;
6055
+ if (!ruleOrGroupOriginal) {
6056
+ onAbort?.({
6057
+ reason: "target-not-found",
6058
+ operation: "move",
6059
+ pathOrID: oldPathOrID
6060
+ });
6061
+ return query;
6062
+ }
6063
+ const nextPath = getNextPath(query, oldPath, newPath);
6064
+ if (oldPath.length === 0) {
6065
+ onAbort?.({
6066
+ reason: "root-not-allowed",
6067
+ operation: "move",
6068
+ pathOrID: oldPathOrID
6069
+ });
6070
+ return query;
6071
+ }
6072
+ const moveGuardReason = getGuardAbortReason(query, oldPath, options);
6073
+ if (moveGuardReason) {
6074
+ onAbort?.({
6075
+ reason: moveGuardReason,
6076
+ operation: "move",
6077
+ pathOrID: oldPathOrID
6078
+ });
6079
+ return query;
6080
+ }
6081
+ if (pathsAreEqual(oldPath, nextPath)) {
6082
+ onAbort?.({
6083
+ reason: "same-location",
6084
+ operation: "move",
6085
+ pathOrID: oldPathOrID
6086
+ });
6087
+ return query;
6088
+ }
6089
+ if (!findPath(getParentPath(nextPath), query)) {
6090
+ onAbort?.({
6091
+ reason: "destination-not-found",
6092
+ operation: "move",
6093
+ pathOrID: newPath
6094
+ });
6095
+ return query;
6096
+ }
6097
+ if (!clone && isAncestor(oldPath, nextPath)) {
6098
+ onAbort?.({
6099
+ reason: "destination-not-found",
6100
+ operation: "move",
6101
+ pathOrID: newPath
6102
+ });
6103
+ return query;
6104
+ }
5341
6105
  const ruleOrGroup = clone ? regenerateIDs(isDraft(ruleOrGroupOriginal) ? current(ruleOrGroupOriginal) : ruleOrGroupOriginal, { idGenerator }) : ruleOrGroupOriginal;
5342
6106
  const independentCombinators = isRuleGroupTypeIC(query);
5343
6107
  const parentOfRuleToRemove = findPath(getParentPath(oldPath), query);
@@ -5350,20 +6114,23 @@ const moveInPlace = (query, oldPathOrID, newPath, options = {}) => {
5350
6114
  parentOfRuleToRemove.rules.splice(idxStartDelete, deleteLength);
5351
6115
  }
5352
6116
  const newNewPath = [...nextPath];
6117
+ if (independentCombinators && newNewPath.at(-1) % 2 === 1) newNewPath[newNewPath.length - 1] += 1;
5353
6118
  const commonAncestorPath = getCommonAncestorPath(oldPath, nextPath);
5354
- if (!clone && oldPath.length === commonAncestorPath.length + 1 && nextPath[commonAncestorPath.length] > oldPath[commonAncestorPath.length]) newNewPath[commonAncestorPath.length] -= independentCombinators ? 2 : 1;
5355
- const parentToInsertInto = findPath(getParentPath(newNewPath), query);
6119
+ if (!clone && oldPath.length === commonAncestorPath.length + 1 && newNewPath[commonAncestorPath.length] > oldPath[commonAncestorPath.length]) newNewPath[commonAncestorPath.length] -= independentCombinators ? 2 : 1;
6120
+ const newNewParentPath = getParentPath(newNewPath);
6121
+ const parentToInsertInto = findPath(newNewParentPath, query);
5356
6122
  const newIndex = newNewPath.at(-1);
6123
+ const spliceIndex = independentCombinators && newIndex > 0 ? newIndex - 1 : newIndex;
5357
6124
  /**
5358
6125
  * This function 1) glosses over the need for type assertions to splice directly
5359
6126
  * into `parentToInsertInto.rules`, and 2) shortens the actual insertion code.
5360
6127
  */
5361
- const insertRuleOrGroup = (...args) => parentToInsertInto.rules.splice(newIndex, 0, ...args);
6128
+ const insertRuleOrGroup = (...args) => parentToInsertInto.rules.splice(spliceIndex, 0, ...args);
5362
6129
  if (parentToInsertInto.rules.length === 0 || !independentCombinators) insertRuleOrGroup(ruleOrGroup);
5363
6130
  else if (newIndex === 0) if (ruleToRemoveIndex === 0 && oldNextCombinator) insertRuleOrGroup(ruleOrGroup, oldNextCombinator);
5364
6131
  else insertRuleOrGroup(ruleOrGroup, parentToInsertInto.rules[1] ?? oldPrevCombinator ?? getFirstOption(combinators));
5365
6132
  else if (oldPrevCombinator) insertRuleOrGroup(oldPrevCombinator, ruleOrGroup);
5366
- else insertRuleOrGroup(parentToInsertInto.rules[newIndex - 2] ?? oldNextCombinator ?? getFirstOption(combinators), ruleOrGroup);
6133
+ else insertRuleOrGroup(parentToInsertInto.rules[spliceIndex - 2] ?? oldNextCombinator ?? getFirstOption(combinators), ruleOrGroup);
5367
6134
  return query;
5368
6135
  };
5369
6136
  /**
@@ -5382,9 +6149,42 @@ const insert = (query, ruleOrGroup, path, options = {}) => produce(query, (q) =>
5382
6149
  * @group Query Tools
5383
6150
  */
5384
6151
  const insertInPlace = (query, ruleOrGroup, path, options = {}) => {
5385
- const { combinators = defaultCombinators, combinatorPreceding, combinatorSucceeding, idGenerator = generateID, replace = false } = options;
6152
+ const { combinators = defaultCombinators, combinatorPreceding, combinatorSucceeding, idGenerator = generateID, replace = false, onAbort } = options;
5386
6153
  const parentToInsertInto = findPath(getParentPath(path), query);
5387
- if (!parentToInsertInto || !isRuleGroup(parentToInsertInto)) return query;
6154
+ if (!parentToInsertInto) {
6155
+ onAbort?.({
6156
+ reason: "parent-not-found",
6157
+ operation: "insert",
6158
+ pathOrID: path
6159
+ });
6160
+ return query;
6161
+ }
6162
+ if (!isRuleGroup(parentToInsertInto)) {
6163
+ onAbort?.({
6164
+ reason: "parent-not-a-group",
6165
+ operation: "insert",
6166
+ pathOrID: path
6167
+ });
6168
+ return query;
6169
+ }
6170
+ const parentPath = getParentPath(path);
6171
+ const insertGuardReason = getGuardAbortReason(query, parentPath, options, { asParent: true });
6172
+ if (insertGuardReason) {
6173
+ onAbort?.({
6174
+ reason: insertGuardReason,
6175
+ operation: "insert",
6176
+ pathOrID: path
6177
+ });
6178
+ return query;
6179
+ }
6180
+ if (isRuleGroup(ruleOrGroup) && exceedsMaxLevels(parentPath, options)) {
6181
+ onAbort?.({
6182
+ reason: "max-levels-exceeded",
6183
+ operation: "insert",
6184
+ pathOrID: path
6185
+ });
6186
+ return query;
6187
+ }
5388
6188
  const rorg = regenerateIDs(ruleOrGroup, { idGenerator });
5389
6189
  const independentCombinators = isRuleGroupTypeIC(query);
5390
6190
  const newIndex = path.at(-1);
@@ -5424,15 +6224,85 @@ const group = (query, sourcePathOrID, targetPathOrID, options = {}) => produce(q
5424
6224
  * @group Query Tools
5425
6225
  */
5426
6226
  const groupInPlace = (query, sourcePathOrID, targetPathOrID, options = {}) => {
5427
- const { clone = false, combinators = defaultCombinators, idGenerator = generateID } = options;
6227
+ const { clone = false, combinators = defaultCombinators, idGenerator = generateID, onAbort } = options;
5428
6228
  const sourcePath = Array.isArray(sourcePathOrID) ? sourcePathOrID : getPathOfID(sourcePathOrID, query);
5429
6229
  const targetPath = Array.isArray(targetPathOrID) ? targetPathOrID : getPathOfID(targetPathOrID, query);
5430
- if (!sourcePath || !targetPath) return query;
6230
+ if (!sourcePath) {
6231
+ onAbort?.({
6232
+ reason: "target-not-found",
6233
+ operation: "group",
6234
+ pathOrID: sourcePathOrID
6235
+ });
6236
+ return query;
6237
+ }
6238
+ if (!targetPath) {
6239
+ onAbort?.({
6240
+ reason: "target-not-found",
6241
+ operation: "group",
6242
+ pathOrID: targetPathOrID
6243
+ });
6244
+ return query;
6245
+ }
5431
6246
  const nextPath = getNextPath(query, sourcePath, targetPath);
5432
- if (sourcePath.length === 0 || pathsAreEqual(sourcePath, nextPath) || !findPath(getParentPath(nextPath), query)) return query;
6247
+ if (sourcePath.length === 0) {
6248
+ onAbort?.({
6249
+ reason: "root-not-allowed",
6250
+ operation: "group",
6251
+ pathOrID: sourcePathOrID
6252
+ });
6253
+ return query;
6254
+ }
6255
+ const groupGuardReason = getGuardAbortReason(query, sourcePath, options);
6256
+ if (groupGuardReason) {
6257
+ onAbort?.({
6258
+ reason: groupGuardReason,
6259
+ operation: "group",
6260
+ pathOrID: sourcePathOrID
6261
+ });
6262
+ return query;
6263
+ }
6264
+ if (pathsAreEqual(sourcePath, nextPath)) {
6265
+ onAbort?.({
6266
+ reason: "same-location",
6267
+ operation: "group",
6268
+ pathOrID: sourcePathOrID
6269
+ });
6270
+ return query;
6271
+ }
6272
+ if (!findPath(getParentPath(nextPath), query)) {
6273
+ onAbort?.({
6274
+ reason: "destination-not-found",
6275
+ operation: "group",
6276
+ pathOrID: targetPathOrID
6277
+ });
6278
+ return query;
6279
+ }
6280
+ if (!clone && isAncestor(sourcePath, nextPath)) {
6281
+ onAbort?.({
6282
+ reason: "destination-not-found",
6283
+ operation: "group",
6284
+ pathOrID: targetPathOrID
6285
+ });
6286
+ return query;
6287
+ }
5433
6288
  const sourceRuleOrGroupOriginal = findPath(sourcePath, query);
5434
6289
  const targetRuleOrGroup = findPath(targetPath, query);
5435
- if (!sourceRuleOrGroupOriginal || !targetRuleOrGroup) return query;
6290
+ if (!sourceRuleOrGroupOriginal) {
6291
+ onAbort?.({
6292
+ reason: "target-not-found",
6293
+ operation: "group",
6294
+ pathOrID: sourcePathOrID
6295
+ });
6296
+ return query;
6297
+ }
6298
+ if (!targetRuleOrGroup) {
6299
+ onAbort?.({
6300
+ reason: "target-not-found",
6301
+ operation: "group",
6302
+ pathOrID: targetPathOrID
6303
+ });
6304
+ return query;
6305
+ }
5436
6306
  const sourceRuleOrGroup = clone ? regenerateIDs(isDraft(sourceRuleOrGroupOriginal) ? current(sourceRuleOrGroupOriginal) : sourceRuleOrGroupOriginal, { idGenerator }) : sourceRuleOrGroupOriginal;
5437
6307
  const independentCombinators = isRuleGroupTypeIC(query);
5438
6308
  const parentOfRuleToRemove = findPath(getParentPath(sourcePath), query);
@@ -5445,7 +6315,8 @@ const groupInPlace = (query, sourcePathOrID, targetPathOrID, options = {}) => {
5445
6315
  const newNewPath = [...nextPath];
5446
6316
  const commonAncestorPath = getCommonAncestorPath(sourcePath, nextPath);
5447
6317
  if (!clone && sourcePath.length === commonAncestorPath.length + 1 && nextPath[commonAncestorPath.length] > sourcePath[commonAncestorPath.length]) newNewPath[commonAncestorPath.length] -= independentCombinators ? 2 : 1;
5448
- const parentOfTargetPath = findPath(getParentPath(newNewPath), query);
6318
+ const newNewParentPath = getParentPath(newNewPath);
6319
+ const parentOfTargetPath = findPath(newNewParentPath, query);
5449
6320
  const targetPathIndex = newNewPath.at(-1);
5450
6321
  parentOfTargetPath.rules.splice(targetPathIndex, 1, prepareRuleOrGroup(independentCombinators ? { rules: [
5451
6322
  targetRuleOrGroup,
@@ -5458,6 +6329,1248 @@ const groupInPlace = (query, sourcePathOrID, targetPathOrID, options = {}) => {
5458
6329
  return query;
5459
6330
  };
5460
6331
  //#endregion
5461
- export { LogType, TestID, add, addInPlace, betweenOperators, bigIntJsonParseReviver, bigIntJsonStringifyReplacer, celCombinatorMap, clsx, convertFromIC, convertQuery, convertToIC, cypherCombinatorMap, defaultCELValueProcessor, defaultCombinatorLabelMap, defaultCombinators, defaultCombinatorsExtended, defaultControlClassnames, defaultExportOperatorMap, defaultJoinChar, defaultMatchModes, defaultMongoDBValueProcessor, defaultNLTranslations, defaultOperatorLabelMap, defaultOperatorNegationMap, defaultOperatorProcessorNL, defaultOperatorProcessorSQL, defaultOperators, defaultPlaceholderFieldGroupLabel, defaultPlaceholderFieldLabel, defaultPlaceholderFieldName, defaultPlaceholderLabel, defaultPlaceholderName, defaultPlaceholderOperatorGroupLabel, defaultPlaceholderOperatorLabel, defaultPlaceholderOperatorName, defaultPlaceholderValueGroupLabel, defaultPlaceholderValueLabel, defaultPlaceholderValueName, defaultRuleGroupProcessorCEL, defaultRuleGroupProcessorCypher, defaultRuleGroupProcessorDiagnostics, defaultRuleGroupProcessorDrizzle, defaultRuleGroupProcessorElasticSearch, defaultRuleGroupProcessorGremlin, defaultRuleGroupProcessorJSONata, defaultRuleGroupProcessorJsonLogic, defaultRuleGroupProcessorLDAP, defaultRuleGroupProcessorMongoDB, defaultRuleGroupProcessorMongoDBQuery, defaultRuleGroupProcessorNL, defaultRuleGroupProcessorParameterized, defaultRuleGroupProcessorPrisma, defaultRuleGroupProcessorSPARQL, defaultRuleGroupProcessorSQL, defaultRuleGroupProcessorSequelize, defaultRuleGroupProcessorSpEL, defaultRuleGroupProcessorTanStackDB, defaultRuleProcessorCEL, defaultRuleProcessorCypher, defaultRuleProcessorDrizzle, defaultRuleProcessorElasticSearch, defaultRuleProcessorGremlin, defaultRuleProcessorJSONata, defaultRuleProcessorJsonLogic, defaultRuleProcessorLDAP, defaultRuleProcessorMongoDB, defaultRuleProcessorMongoDBQuery, defaultRuleProcessorNL, defaultRuleProcessorParameterized, defaultRuleProcessorPrisma, defaultRuleProcessorSPARQL, defaultRuleProcessorSQL, defaultRuleProcessorSequelize, defaultRuleProcessorSpEL, defaultRuleProcessorTanStackDB, defaultSpELValueProcessor, defaultTranslations, defaultValidator, defaultValueProcessor, defaultValueProcessorByRule, defaultValueProcessorCELByRule, defaultValueProcessorMongoDBByRule, defaultValueProcessorNL, defaultValueProcessorSpELByRule, filterFieldsByComparator, findID, findPath, formatQuery, formatQueryOptionPresets, generateAccessibleDescription, generateID, getCommonAncestorPath, getFirstOption, getLikeWildcards, getMatchModesUtil, getNLTranslataion, getOption, getParentPath, getParseNumberMethod, getPathOfID, getQuoteFieldNamesWithArray, getQuotedFieldName, getRuleDefaultValue, getSQLConcat, getSubqueryElementAlias, getValidationClassNames, getValueSourcesUtil, group, groupInPlace, groupInvalidReasons, inOperators, insert, insertInPlace, isAncestor, isFlexibleOptionArray, isFlexibleOptionGroupArray, isFullOptionArray, isFullOptionGroupArray, isOptionGroupArray, isPojo, isRuleGroup, isRuleGroupType, isRuleGroupTypeIC, isRuleOrGroupValid, isRuleType, isUnsafeKey, isValidValue, isValidationResult, isValueProcessorLegacy, joinWith, jsonLogicAdditionalOperators, lc, mapSQLOperator, mergeAnyTranslation, mergeAnyTranslations, mergeClassnames, mongoDbFallback, mongoOperators, move, moveInPlace, normalizeConstituentWordOrder, nullFreeArray, nullOperators, nullOrUndefinedOrEmpty, numericRegex, numerifyValues, objectEntries, objectKeys, parseNumber, pathIsDisabled, pathsAreEqual, preferAnyProp, preferFlagProps, preferProp, prepareOptionList, prepareRule, prepareRuleGroup, prepareRuleOrGroup, prismaFallback, prismaOperators, processMatchMode, queryBuilderFlagDefaults, regenerateID, regenerateIDs, relationalOperators, remove, removeInPlace, rootPath, shouldRenderAsNumber, sparqlVar, splitBy, sqlDialectPresets, standardClassnames, stripParamPrefix, subqueryElementAliasBase, substringOperators, toArray, toFlatOptionArray, toFullOption, toFullOptionList, toFullOptionMap, transformQuery, trimIfString, uniqByIdentifier, uniqByName, uniqOptGroups, uniqOptList, update, updateInPlace, uuidV4regex, withParamPrefix, wrapLikeFragment };
6332
+ //#region src/utils/queryActions.ts
6333
+ /**
6334
+ * Builds the query builder's six mutation handlers as pure functions of the current query.
6335
+ *
6336
+ * This is the framework-agnostic core of the action handlers in `useQueryBuilderSchema`. It owns
6337
+ * the policy that surrounds the query tools—disabled gating, the confirmation callback protocol,
6338
+ * `maxLevels`, and debug logging—so an implementation only has to supply its own storage: read
6339
+ * the current query, call the action, and apply a non-`undefined` result.
6340
+ *
6341
+ * @group Query Tools
6342
+ */
6343
+ const createQueryActions = (config = {}) => {
6344
+ const { qbId, combinators, idGenerator, maxLevels = Infinity, queryDisabled, respectDisabled = true, disabledPaths, resetOnFieldChange, resetOnOperatorChange, getRuleDefaultOperator, getValueSources, getRuleDefaultValue, getMatchModes, onAddRule, onAddGroup, onRemove, onMoveRule, onMoveGroup, onGroupRule, onGroupGroup, onLog } = config;
6345
+ const log = (payload) => onLog?.({
6346
+ qbId,
6347
+ ...payload
6348
+ });
6349
+ const guards = {
6350
+ respectDisabled,
6351
+ queryDisabled,
6352
+ disabledPaths
6353
+ };
6354
+ /** Whether the mutation is blocked, using the same rules the query tools apply internally. */
6355
+ const blocked = (query, path, asParent = false) => !!getGuardAbortReason(query, path, guards, { asParent });
6356
+ return {
6357
+ addRule: (query, rule, parentPath, context) => {
6358
+ if (blocked(query, parentPath, true)) {
6359
+ log({
6360
+ type: LogType.parentPathDisabled,
6361
+ rule,
6362
+ parentPath,
6363
+ query
6364
+ });
6365
+ return;
6366
+ }
6367
+ const nextRule = onAddRule ? onAddRule(rule, parentPath, query, context) : true;
6368
+ if (!nextRule) {
6369
+ log({
6370
+ type: LogType.onAddRuleFalse,
6371
+ rule,
6372
+ parentPath,
6373
+ query
6374
+ });
6375
+ return;
6376
+ }
6377
+ const newRule = nextRule === true ? rule : nextRule;
6378
+ const newQuery = add(query, newRule, parentPath, {
6379
+ combinators,
6380
+ combinatorPreceding: newRule.combinatorPreceding,
6381
+ idGenerator
6382
+ });
6383
+ log({
6384
+ type: LogType.add,
6385
+ query,
6386
+ newQuery,
6387
+ newRule,
6388
+ parentPath
6389
+ });
6390
+ return newQuery;
6391
+ },
6392
+ addGroup: (query, ruleGroup, parentPath, context) => {
6393
+ if (parentPath.length >= maxLevels) return void 0;
6394
+ if (blocked(query, parentPath, true)) {
6395
+ log({
6396
+ type: LogType.parentPathDisabled,
6397
+ ruleGroup,
6398
+ parentPath,
6399
+ query
6400
+ });
6401
+ return;
6402
+ }
6403
+ const nextGroup = onAddGroup ? onAddGroup(ruleGroup, parentPath, query, context) : true;
6404
+ if (!nextGroup) {
6405
+ log({
6406
+ type: LogType.onAddGroupFalse,
6407
+ ruleGroup,
6408
+ parentPath,
6409
+ query
6410
+ });
6411
+ return;
6412
+ }
6413
+ const newGroup = nextGroup === true ? ruleGroup : nextGroup;
6414
+ const newQuery = add(query, newGroup, parentPath, {
6415
+ combinators,
6416
+ combinatorPreceding: newGroup.combinatorPreceding ?? void 0,
6417
+ idGenerator
6418
+ });
6419
+ log({
6420
+ type: LogType.add,
6421
+ query,
6422
+ newQuery,
6423
+ newGroup,
6424
+ parentPath
6425
+ });
6426
+ return newQuery;
6427
+ },
6428
+ propChange: (query, prop, value, path) => {
6429
+ if (getGuardAbortReason(query, path, prop === "disabled" ? { queryDisabled } : guards)) {
6430
+ log({
6431
+ type: LogType.pathDisabled,
6432
+ path,
6433
+ prop,
6434
+ value,
6435
+ query
6436
+ });
6437
+ return;
6438
+ }
6439
+ const newQuery = update(query, prop, value, path, {
6440
+ resetOnFieldChange,
6441
+ resetOnOperatorChange,
6442
+ getRuleDefaultOperator,
6443
+ getValueSources,
6444
+ getRuleDefaultValue,
6445
+ getMatchModes
6446
+ });
6447
+ log({
6448
+ type: LogType.update,
6449
+ query,
6450
+ newQuery,
6451
+ prop,
6452
+ value,
6453
+ path
6454
+ });
6455
+ return newQuery;
6456
+ },
6457
+ removeRuleOrGroup: (query, path, context) => {
6458
+ if (blocked(query, path)) {
6459
+ log({
6460
+ type: LogType.pathDisabled,
6461
+ path,
6462
+ query
6463
+ });
6464
+ return;
6465
+ }
6466
+ const ruleOrGroup = findPath(path, query);
6467
+ /* v8 ignore next -- @preserve */
6468
+ if (!ruleOrGroup) return void 0;
6469
+ if (onRemove && !onRemove(ruleOrGroup, path, query, context)) {
6470
+ log({
6471
+ type: LogType.onRemoveFalse,
6472
+ ruleOrGroup,
6473
+ path,
6474
+ query
6475
+ });
6476
+ return;
6477
+ }
6478
+ const newQuery = remove(query, path);
6479
+ log({
6480
+ type: LogType.remove,
6481
+ query,
6482
+ newQuery,
6483
+ path,
6484
+ ruleOrGroup
6485
+ });
6486
+ return newQuery;
6487
+ },
6488
+ moveRule: (query, oldPath, newPath, clone, context) => {
6489
+ if (blocked(query, oldPath)) {
6490
+ log({
6491
+ type: LogType.pathDisabled,
6492
+ oldPath,
6493
+ newPath,
6494
+ query
6495
+ });
6496
+ return;
6497
+ }
6498
+ const ruleOrGroup = findPath(oldPath, query);
6499
+ if (!ruleOrGroup) return void 0;
6500
+ const nextQuery = move(query, oldPath, newPath, {
6501
+ clone,
6502
+ combinators,
6503
+ idGenerator
6504
+ });
6505
+ const isGroup = isRuleGroup(ruleOrGroup);
6506
+ const callback = isGroup ? onMoveGroup : onMoveRule;
6507
+ const callbackResult = callback ? callback(ruleOrGroup, oldPath, newPath, query, nextQuery, {
6508
+ clone,
6509
+ combinators
6510
+ }, context) : true;
6511
+ if (!callbackResult) {
6512
+ log({
6513
+ type: isGroup ? LogType.onMoveGroupFalse : LogType.onMoveRuleFalse,
6514
+ ruleOrGroup,
6515
+ oldPath,
6516
+ newPath,
6517
+ clone,
6518
+ query,
6519
+ nextQuery
6520
+ });
6521
+ return;
6522
+ }
6523
+ const newQuery = isRuleGroup(callbackResult) ? callbackResult : nextQuery;
6524
+ log({
6525
+ type: LogType.move,
6526
+ query,
6527
+ newQuery,
6528
+ oldPath,
6529
+ newPath,
6530
+ clone
6531
+ });
6532
+ return newQuery;
6533
+ },
6534
+ groupRule: (query, sourcePath, targetPath, clone, context) => {
6535
+ if (blocked(query, sourcePath)) {
6536
+ log({
6537
+ type: LogType.pathDisabled,
6538
+ sourcePath,
6539
+ targetPath,
6540
+ query
6541
+ });
6542
+ return;
6543
+ }
6544
+ const ruleOrGroup = findPath(sourcePath, query);
6545
+ if (!ruleOrGroup) return void 0;
6546
+ const nextQuery = group(query, sourcePath, targetPath, {
6547
+ clone,
6548
+ combinators,
6549
+ idGenerator
6550
+ });
6551
+ const isGroup = isRuleGroup(ruleOrGroup);
6552
+ const callback = isGroup ? onGroupGroup : onGroupRule;
6553
+ const callbackResult = callback ? callback(ruleOrGroup, sourcePath, targetPath, query, nextQuery, {
6554
+ clone,
6555
+ combinators
6556
+ }, context) : true;
6557
+ if (!callbackResult) {
6558
+ log({
6559
+ type: isGroup ? LogType.onGroupGroupFalse : LogType.onGroupRuleFalse,
6560
+ ruleOrGroup,
6561
+ sourcePath,
6562
+ targetPath,
6563
+ clone,
6564
+ query,
6565
+ nextQuery
6566
+ });
6567
+ return;
6568
+ }
6569
+ const newQuery = isRuleGroup(callbackResult) ? callbackResult : nextQuery;
6570
+ log({
6571
+ type: LogType.group,
6572
+ query,
6573
+ newQuery,
6574
+ sourcePath,
6575
+ targetPath,
6576
+ clone
6577
+ });
6578
+ return newQuery;
6579
+ }
6580
+ };
6581
+ };
6582
+ //#endregion
6583
+ //#region src/utils/ruleFactory.ts
6584
+ /**
6585
+ * Creates a rule from the given configuration, applying the same precedence as the
6586
+ * `QueryBuilder` component. The `value` is computed in a second pass, once `field`, `operator`,
6587
+ * and `valueSource` are known, since the default value depends on all three.
6588
+ *
6589
+ * @group Query Tools
6590
+ */
6591
+ const createRule = ({ fields, getDefaultField, getRuleDefaultOperator, getValueSources, getMatchModes, getRuleDefaultValue, idGenerator = generateID }) => {
6592
+ let field = getFirstOption(fields) ?? "";
6593
+ if (getDefaultField) if (typeof getDefaultField === "function") {
6594
+ const defaultField = getDefaultField(fields);
6595
+ if (defaultField) field = defaultField;
6596
+ } else field = getDefaultField;
6597
+ const fieldData = getOption(fields, field);
6598
+ const operator = getRuleDefaultOperator(field);
6599
+ const valueSource = getFirstOption(getValueSources(field, operator, { fieldData })) ?? "value";
6600
+ const matchMode = getFirstOption(getMatchModes(field, { fieldData }));
6601
+ const newRule = {
6602
+ id: idGenerator(),
6603
+ field,
6604
+ operator,
6605
+ valueSource,
6606
+ value: "",
6607
+ ...matchMode ? { match: {
6608
+ mode: matchMode,
6609
+ threshold: 1
6610
+ } } : null
6611
+ };
6612
+ return {
6613
+ ...newRule,
6614
+ value: getRuleDefaultValue(newRule)
6615
+ };
6616
+ };
6617
+ /**
6618
+ * Creates a group from the given configuration. Pass `true` for `independentCombinators` to omit
6619
+ * the `combinator` property.
6620
+ *
6621
+ * The group's own `id` is generated _before_ any contained rule's, matching the order the
6622
+ * `QueryBuilder` component has always used. This is observable when `idGenerator` is
6623
+ * deterministic, as it often is in tests.
6624
+ *
6625
+ * @group Query Tools
6626
+ */
6627
+ const createRuleGroup = ({ combinators, addRuleToNewGroups, createRule: createRuleMain, idGenerator = generateID }, independentCombinators) => {
6628
+ const id = idGenerator();
6629
+ const rules = addRuleToNewGroups ? [createRuleMain()] : [];
6630
+ if (independentCombinators) return {
6631
+ id,
6632
+ rules,
6633
+ not: false
6634
+ };
6635
+ return {
6636
+ id,
6637
+ rules,
6638
+ combinator: getFirstOption(combinators) ?? "",
6639
+ not: false
6640
+ };
6641
+ };
6642
+ //#endregion
6643
+ //#region src/utils/signature.ts
6644
+ /**
6645
+ * Signature returned when a change alters the _shape_ of the query—rules or groups added,
6646
+ * removed, moved, or reordered—rather than the properties of a single node. Structural changes
6647
+ * never coalesce with each other, so each one produces its own history entry.
6648
+ */
6649
+ const structuralSignature = "~structural";
6650
+ /**
6651
+ * Signature returned when two queries differ by reference only, with no observable difference
6652
+ * in their properties.
6653
+ */
6654
+ const unchangedSignature = "";
6655
+ /**
6656
+ * Returns the list of property names that differ between two nodes, ignoring `rules` (which is
6657
+ * compared structurally by the caller).
6658
+ */
6659
+ const changedProps = (prev, next) => {
6660
+ const keys = /* @__PURE__ */ new Set([...Object.keys(prev), ...Object.keys(next)]);
6661
+ keys.delete("rules");
6662
+ const changed = [];
6663
+ for (const key of keys) if (!Object.is(prev[key], next[key])) changed.push(key);
6664
+ return changed.toSorted();
6665
+ };
6666
+ const signatureOfNode = (prev, next, parentId, index) => {
6667
+ if (prev === next) return "";
6668
+ if (typeof prev === "string" || typeof next === "string") return typeof prev === typeof next ? `${parentId}:combinator[${index}]` : structuralSignature;
6669
+ const prevIsGroup = isRuleGroup(prev);
6670
+ const nextIsGroup = isRuleGroup(next);
6671
+ if (prevIsGroup !== nextIsGroup) return structuralSignature;
6672
+ if (!prevIsGroup || !nextIsGroup) {
6673
+ const props = changedProps(prev, next);
6674
+ return props.length === 0 ? "" : `${next.id ?? ""}:${props.join(",")}`;
6675
+ }
6676
+ if (prev.rules.length !== next.rules.length) return structuralSignature;
6677
+ const ownProps = changedProps(prev, next);
6678
+ let changedIndex = -1;
6679
+ for (const [i, prevRule] of prev.rules.entries()) if (prevRule !== next.rules[i]) {
6680
+ if (changedIndex !== -1) return structuralSignature;
6681
+ changedIndex = i;
6682
+ }
6683
+ if (changedIndex === -1) return ownProps.length === 0 ? "" : `${next.id ?? ""}:${ownProps.join(",")}`;
6684
+ if (ownProps.length > 0) return structuralSignature;
6685
+ return signatureOfNode(prev.rules[changedIndex], next.rules[changedIndex], next.id ?? "", changedIndex);
6686
+ };
6687
+ /**
6688
+ * Describes _what changed_ between two versions of a query as a short string, so that
6689
+ * consecutive edits to the same property of the same rule (e.g. typing in a value editor) can
6690
+ * be recognized and coalesced into a single history entry.
6691
+ *
6692
+ * Returns {@link structuralSignature} for changes to the shape of the query, {@link unchangedSignature} when the
6693
+ * two queries differ by reference only, and `"<id>:<props>"` otherwise.
6694
+ *
6695
+ * The walk prunes on reference equality, so it costs O(depth) rather than O(size) for the
6696
+ * single-node edits that make up the overwhelming majority of changes.
6697
+ *
6698
+ * Note that this relies on the structural sharing that Immer—and therefore every query
6699
+ * produced by RQB's own update functions—guarantees: nodes that did not change keep their
6700
+ * identity. A query that has been wholly rebuilt (deep-cloned, round-tripped through JSON,
6701
+ * re-parsed from a string) shares no identity with its predecessor, so it is reported as
6702
+ * {@link structuralSignature} and will not coalesce. That is a graceful degradation—every change simply
6703
+ * gets its own history entry—but controlled components that clone the query on every change
6704
+ * will not benefit from coalescing.
6705
+ */
6706
+ const signatureOf = (prev, next) => signatureOfNode(prev, next, "", -1);
6707
+ //#endregion
6708
+ //#region src/utils/QueryManager.ts
6709
+ /**
6710
+ * Abort reasons that {@link QueryManager}'s `strict` mode treats as errors. The remaining
6711
+ * reasons—`"same-location"` and `"no-change"`—describe valid operations that had nothing to do,
6712
+ * so they are reported to `onInvalidTarget` but never throw.
6713
+ *
6714
+ * @group Query Tools
6715
+ */
6716
+ const strictAbortReasons = [
6717
+ "target-not-found",
6718
+ "parent-not-found",
6719
+ "parent-not-a-group",
6720
+ "destination-not-found",
6721
+ "root-not-allowed",
6722
+ "not-a-combinator-slot",
6723
+ "target-disabled",
6724
+ "parent-disabled",
6725
+ "max-levels-exceeded"
6726
+ ];
6727
+ const strictAbortReasonSet = new Set(strictAbortReasons);
6728
+ /**
6729
+ * Thrown by {@link QueryManager} methods in `strict` mode when an operation cannot be carried
6730
+ * out because its target could not be used.
6731
+ *
6732
+ * @group Query Tools
6733
+ */
6734
+ var QueryManagerError = class QueryManagerError extends Error {
6735
+ /** Why the operation was aborted. */
6736
+ code;
6737
+ /** Full details about the aborted operation. */
6738
+ info;
6739
+ constructor(info) {
6740
+ super(`QueryManager: "${info.operation}" aborted (${info.reason})${info.pathOrID === void 0 ? "" : ` for target ${JSON.stringify(info.pathOrID)}`}.`);
6741
+ this.name = "QueryManagerError";
6742
+ this.code = info.reason;
6743
+ this.info = info;
6744
+ Object.setPrototypeOf(this, QueryManagerError.prototype);
6745
+ }
6746
+ };
6747
+ /**
6748
+ * Stateful wrapper around the {@link add}/{@link remove}/{@link update}/{@link move}/
6749
+ * {@link insert}/{@link group} query tools, plus rule/group factories, {@link defaultValidator
6750
+ * validation}, and {@link formatQuery formatting}.
6751
+ *
6752
+ * The query is held internally, so each method takes the same arguments as its `queryTools`
6753
+ * counterpart minus the leading `query` parameter, and returns the manager itself for chaining.
6754
+ * Mutations use the non-`InPlace` tools, so a query previously handed out by
6755
+ * {@link QueryManager.getQuery} is never modified.
6756
+ *
6757
+ * ```ts
6758
+ * const q = new QueryManager(undefined, { fields });
6759
+ * q.add(q.createRule()).add({ field: 'firstName', operator: '=', value: 'Steve' });
6760
+ * q.format('sql');
6761
+ * ```
6762
+ *
6763
+ * Like the underlying query tools, methods are a no-op when the target path or `id` can't be
6764
+ * resolved (including attempts to remove the root group). By default nothing is thrown; pass
6765
+ * `strict: true` to raise a {@link QueryManagerError} instead, or `onInvalidTarget` to observe
6766
+ * aborted operations without changing control flow.
6767
+ *
6768
+ * @group Query Tools
6769
+ */
6770
+ var QueryManager = class QueryManager {
6771
+ #query;
6772
+ #options;
6773
+ #fields;
6774
+ #fieldMap;
6775
+ #operators;
6776
+ #combinators;
6777
+ #idGenerator;
6778
+ #validator;
6779
+ #strict;
6780
+ #respectDisabled;
6781
+ #onInvalidTarget;
6782
+ #listeners = /* @__PURE__ */ new Set();
6783
+ #historyEnabled;
6784
+ #maxHistory;
6785
+ #coalesceMs;
6786
+ #now;
6787
+ #past = [];
6788
+ #future = [];
6789
+ #lastSig;
6790
+ #lastAt = 0;
6791
+ #batchDepth = 0;
6792
+ #batchSnapshot;
6793
+ /** Whether a history-stack method ran inside the batch currently in progress. */
6794
+ #historyBypassed = false;
6795
+ /**
6796
+ * The query the cached fields below were derived from. Caches are keyed on query _identity_
6797
+ * rather than invalidated from {@link QueryManager.#commit} because `undo`, `redo`, and
6798
+ * `batch`'s rollback all assign `#query` directly.
6799
+ */
6800
+ #cacheFor;
6801
+ #idPathIndex;
6802
+ #validation;
6803
+ constructor(query, options = {}) {
6804
+ this.#options = options;
6805
+ this.#idGenerator = options.idGenerator ?? generateID;
6806
+ this.#validator = options.validator ?? defaultValidator;
6807
+ this.#strict = options.strict ?? false;
6808
+ this.#respectDisabled = options.respectDisabled ?? true;
6809
+ this.#onInvalidTarget = options.onInvalidTarget;
6810
+ const history = options.history ?? false;
6811
+ const historyOptions = typeof history === "object" ? history : {};
6812
+ this.#historyEnabled = history !== false;
6813
+ this.#maxHistory = historyOptions.maxHistory ?? 50;
6814
+ this.#coalesceMs = historyOptions.coalesceMs ?? 500;
6815
+ this.#now = options.now ?? Date.now;
6816
+ const { optionList: fields, optionsMap: fieldMap } = prepareOptionList({
6817
+ optionList: options.fields,
6818
+ baseOption: options.baseField,
6819
+ autoSelectOption: options.autoSelectField
6820
+ });
6821
+ this.#fields = freeze(fields, true);
6822
+ this.#fieldMap = freeze(fieldMap, true);
6823
+ this.#operators = prepareOptionList({
6824
+ optionList: options.operators ?? defaultOperators,
6825
+ baseOption: options.baseOperator,
6826
+ labelMap: defaultOperatorLabelMap,
6827
+ autoSelectOption: options.autoSelectOperator
6828
+ }).optionList;
6829
+ this.#combinators = freeze(prepareOptionList({
6830
+ optionList: options.combinators ?? defaultCombinators,
6831
+ baseOption: options.baseCombinator
6832
+ }).optionList, true);
6833
+ this.#query = freeze(query ? prepareRuleGroup(query, { idGenerator: this.#idGenerator }) : this.createRuleGroup(), true);
6834
+ }
6835
+ /** Resolves the field configuration for a field name. */
6836
+ #fieldData(field) {
6837
+ return this.#fieldMap[field] ?? {};
6838
+ }
6839
+ /** Resolves the operator list for a field, mirroring `QueryBuilder`'s precedence. */
6840
+ #operatorsFor(field) {
6841
+ return resolveOperatorList({
6842
+ field,
6843
+ fieldData: this.#fieldData(field),
6844
+ getOperators: this.#options.getOperators,
6845
+ operators: this.#operators,
6846
+ baseOption: this.#options.baseOperator,
6847
+ autoSelectOption: this.#options.autoSelectOperator
6848
+ });
6849
+ }
6850
+ /** Resolves the default operator for a field, mirroring `QueryBuilder`'s precedence. */
6851
+ #defaultOperator(field) {
6852
+ return resolveDefaultOperator({
6853
+ field,
6854
+ fieldData: this.#fieldData(field),
6855
+ getDefaultOperator: this.#options.getDefaultOperator,
6856
+ getOperators: (f) => this.#operatorsFor(f)
6857
+ });
6858
+ }
6859
+ #valueSourcesFor(field, operator) {
6860
+ return getValueSourcesUtil(this.#fieldData(field), operator, this.#options.getValueSources);
6861
+ }
6862
+ #matchModesFor(field) {
6863
+ return getMatchModesUtil(this.#fieldData(field), this.#options.getMatchModes);
6864
+ }
6865
+ #valuesFor(field, operator) {
6866
+ return resolveValueList({
6867
+ field,
6868
+ operator,
6869
+ fieldData: this.#fieldData(field),
6870
+ getValues: this.#options.getValues,
6871
+ autoSelectOption: this.#options.autoSelectValue
6872
+ });
6873
+ }
6874
+ #valueEditorTypeFor(field, operator) {
6875
+ return resolveValueEditorType({
6876
+ field,
6877
+ operator,
6878
+ fieldData: this.#fieldData(field),
6879
+ getValueEditorType: this.#options.getValueEditorType
6880
+ });
6881
+ }
6882
+ /** Computes the default `value` for a rule, mirroring `QueryBuilder`'s precedence. */
6883
+ #defaultValue(rule) {
6884
+ const { getDefaultValue, getParameters, listsAsArrays } = this.#options;
6885
+ return getRuleDefaultValue(rule, {
6886
+ fieldData: this.#fieldData(rule.field),
6887
+ fields: this.#fields,
6888
+ listsAsArrays,
6889
+ getValueEditorType: (f, o) => this.#valueEditorTypeFor(f, o),
6890
+ getValues: (f, o) => this.#valuesFor(f, o),
6891
+ getDefaultValue: getDefaultValue && ((r, misc) => getDefaultValue(r, misc)),
6892
+ getParameters: getParameters && ((f, o, misc) => this.#parametersFor(f, o, misc))
6893
+ });
6894
+ }
6895
+ /**
6896
+ * Resolves the parameter list for a field/operator pair, normalized the same way as every
6897
+ * other option list. Shared by {@link QueryManager.#defaultValue} and
6898
+ * {@link QueryManager.getRuleContext} so both see the same shape.
6899
+ */
6900
+ #parametersFor(field, operator, misc) {
6901
+ return prepareOptionList({
6902
+ optionList: this.#options.getParameters?.(field, operator, misc) ?? [],
6903
+ autoSelectOption: this.#options.autoSelectValue
6904
+ }).optionList;
6905
+ }
6906
+ /** Defaults shared by every mutating method, overridable per call. */
6907
+ #guardOptions() {
6908
+ const { maxLevels } = this.#options;
6909
+ return {
6910
+ maxLevels: (maxLevels ?? 0) > 0 ? Number(maxLevels) : Infinity,
6911
+ respectDisabled: this.#respectDisabled,
6912
+ disabledPaths: this.#options.disabledPaths,
6913
+ queryDisabled: this.#options.queryDisabled
6914
+ };
6915
+ }
6916
+ #toolOptions() {
6917
+ return {
6918
+ combinators: this.#combinators,
6919
+ idGenerator: this.#idGenerator,
6920
+ ...this.#guardOptions()
6921
+ };
6922
+ }
6923
+ /**
6924
+ * Builds the `onAbort` handler passed to the query tools, applying the per-call overrides on
6925
+ * top of the manager's own options.
6926
+ */
6927
+ #onAbort({ strict, onInvalidTarget }) {
6928
+ const strictMain = strict ?? this.#strict;
6929
+ const handler = onInvalidTarget ?? this.#onInvalidTarget;
6930
+ return (info) => {
6931
+ handler?.(info);
6932
+ if (strictMain && strictAbortReasonSet.has(info.reason)) throw new QueryManagerError(info);
6933
+ };
6934
+ }
6935
+ /** Defaults for {@link update}, so resets mirror `QueryBuilder`'s behavior. */
6936
+ #updateOptions() {
6937
+ return {
6938
+ getRuleDefaultOperator: (f) => this.#defaultOperator(f),
6939
+ getRuleDefaultValue: (r) => this.#defaultValue(r),
6940
+ getValueSources: (f, o) => this.#valueSourcesFor(f, o),
6941
+ getMatchModes: (f) => this.#matchModesFor(f),
6942
+ resetOnFieldChange: this.#options.resetOnFieldChange,
6943
+ resetOnOperatorChange: this.#options.resetOnOperatorChange,
6944
+ ...this.#guardOptions()
6945
+ };
6946
+ }
6947
+ /**
6948
+ * Applies a new query, recording history and notifying subscribers as appropriate. Every
6949
+ * mutation funnels through here. A tool that could not resolve its target returns the same
6950
+ * query object, which is treated as a no-op.
6951
+ */
6952
+ #commit(next) {
6953
+ const prev = this.#query;
6954
+ if (prev === next) return;
6955
+ this.#query = next;
6956
+ if (this.#batchDepth > 0) return;
6957
+ this.#record(prev, next);
6958
+ this.#notify();
6959
+ }
6960
+ /**
6961
+ * Records a change, either as a new history entry or by absorbing it into the current one.
6962
+ * Mirrors the recording semantics of the `react-querybuilder/history` entry point.
6963
+ */
6964
+ #record(prev, next) {
6965
+ if (!this.#historyEnabled) return;
6966
+ const sig = signatureOf(prev, next);
6967
+ if (sig === "") return;
6968
+ const now = this.#now();
6969
+ if (!(sig !== "~structural" && sig === this.#lastSig && now - this.#lastAt < this.#coalesceMs)) {
6970
+ this.#past.push(prev);
6971
+ if (this.#past.length > this.#maxHistory) this.#past.shift();
6972
+ this.#future = [];
6973
+ }
6974
+ this.#lastSig = sig;
6975
+ this.#lastAt = now;
6976
+ }
6977
+ #notify() {
6978
+ for (const listener of this.#listeners) listener();
6979
+ }
6980
+ /**
6981
+ * Records that a history-stack method ran inside the current batch. Those methods manage
6982
+ * `#past`/`#future` themselves, so the batch must not also record an entry on completion —
6983
+ * doing so would push a duplicate onto `#past` and clear the redo stack that `undo` just
6984
+ * populated.
6985
+ */
6986
+ #markHistoryBypassed() {
6987
+ if (this.#batchDepth > 0) this.#historyBypassed = true;
6988
+ }
6989
+ /**
6990
+ * Discards every cached derivation when the query has been replaced since they were computed.
6991
+ * Called at the top of each cached reader.
6992
+ */
6993
+ #ensureCache() {
6994
+ if (this.#cacheFor === this.#query) return;
6995
+ this.#cacheFor = this.#query;
6996
+ this.#idPathIndex = void 0;
6997
+ this.#validation = void 0;
6998
+ }
6999
+ /**
7000
+ * The single traversal implementation. Combinator strings in independent-combinator groups
7001
+ * are skipped.
7002
+ *
7003
+ * @yields The subtree rooted at `node`, depth-first in pre-order, starting with `node` itself.
7004
+ */
7005
+ *#walkFrom(node, path, parent) {
7006
+ yield {
7007
+ node,
7008
+ path,
7009
+ parent
7010
+ };
7011
+ if (!isRuleGroup(node)) return;
7012
+ const startGroup = node;
7013
+ for (const [index, child] of startGroup.rules.entries()) {
7014
+ if (typeof child === "string") continue;
7015
+ yield* this.#walkFrom(child, [...path, index], startGroup);
7016
+ }
7017
+ }
7018
+ /** Builds (once per query) the `id` to {@link Path} index backing `findID`/`getPathOfID`. */
7019
+ #index() {
7020
+ this.#ensureCache();
7021
+ if (!this.#idPathIndex) {
7022
+ const index = /* @__PURE__ */ new Map();
7023
+ for (const { node, path } of this.#walkFrom(this.#query, [], null)) if (node.id !== void 0 && !index.has(node.id)) index.set(node.id, path);
7024
+ this.#idPathIndex = index;
7025
+ }
7026
+ return this.#idPathIndex;
7027
+ }
7028
+ /** Resolves a path or `id` to a path, or `null` when the `id` isn't present. */
7029
+ #toPath(pathOrID) {
7030
+ return typeof pathOrID === "string" ? this.#index().get(pathOrID) ?? null : pathOrID;
7031
+ }
7032
+ /**
7033
+ * The current query. The returned object is frozen and structurally shared, so it is safe to
7034
+ * retain and compare by reference to detect changes.
7035
+ *
7036
+ * Like {@link QueryManager.subscribe}, this method is bound to the instance, so it can be
7037
+ * passed as a bare reference (e.g. as the `getSnapshot` argument to `useSyncExternalStore`).
7038
+ */
7039
+ getQuery = () => this.#query;
7040
+ /** Replaces the current query, ensuring every rule and group has an `id`. */
7041
+ setQuery(query) {
7042
+ this.#commit(freeze(prepareRuleGroup(query, { idGenerator: this.#idGenerator }), true));
7043
+ return this;
7044
+ }
7045
+ /**
7046
+ * Creates a rule using the configured fields, operators, and defaults. The rule is not added
7047
+ * to the query—pass it to {@link QueryManager.add} or {@link QueryManager.insert}.
7048
+ */
7049
+ createRule() {
7050
+ return createRule({
7051
+ fields: this.#fields,
7052
+ getDefaultField: this.#options.getDefaultField,
7053
+ getRuleDefaultOperator: (f) => this.#defaultOperator(f),
7054
+ getValueSources: (f, o) => this.#valueSourcesFor(f, o),
7055
+ getMatchModes: (f) => this.#matchModesFor(f),
7056
+ getRuleDefaultValue: (r) => this.#defaultValue(r),
7057
+ idGenerator: this.#idGenerator
7058
+ });
7059
+ }
7060
+ /**
7061
+ * Creates a group. Pass `true` for a group with independent combinators. The group is not
7062
+ * added to the query—pass it to {@link QueryManager.add} or {@link QueryManager.insert}.
7063
+ */
7064
+ createRuleGroup(independentCombinators) {
7065
+ return createRuleGroup({
7066
+ combinators: this.#combinators,
7067
+ addRuleToNewGroups: this.#options.addRuleToNewGroups,
7068
+ createRule: () => this.createRule(),
7069
+ idGenerator: this.#idGenerator
7070
+ }, independentCombinators);
7071
+ }
7072
+ /**
7073
+ * Adds a rule or group to the end of the group at `parentPathOrID`, which defaults to the
7074
+ * root group.
7075
+ */
7076
+ add(ruleOrGroup, parentPathOrID = [], options = {}) {
7077
+ const { strict, onInvalidTarget, ...toolOptions } = options;
7078
+ this.#commit(add(this.#query, ruleOrGroup, parentPathOrID, {
7079
+ ...this.#toolOptions(),
7080
+ ...toolOptions,
7081
+ onAbort: this.#onAbort({
7082
+ strict,
7083
+ onInvalidTarget
7084
+ })
7085
+ }));
7086
+ return this;
7087
+ }
7088
+ /** Removes the rule or group at the given path or `id`. The root group cannot be removed. */
7089
+ remove(pathOrID, options = {}) {
7090
+ const { strict, onInvalidTarget, ...toolOptions } = options;
7091
+ this.#commit(remove(this.#query, pathOrID, {
7092
+ ...this.#guardOptions(),
7093
+ ...toolOptions,
7094
+ onAbort: this.#onAbort({
7095
+ strict,
7096
+ onInvalidTarget
7097
+ })
7098
+ }));
7099
+ return this;
7100
+ }
7101
+ update(a, b, c, d) {
7102
+ const optionsIndex = typeof a === "string" || Array.isArray(a) ? 3 : 2;
7103
+ const args = [
7104
+ a,
7105
+ b,
7106
+ c,
7107
+ d
7108
+ ];
7109
+ const { strict, onInvalidTarget, ...toolOptions } = args[optionsIndex] ?? {};
7110
+ args[optionsIndex] = {
7111
+ ...this.#updateOptions(),
7112
+ ...toolOptions,
7113
+ onAbort: this.#onAbort({
7114
+ strict,
7115
+ onInvalidTarget
7116
+ })
7117
+ };
7118
+ this.#commit(update(this.#query, ...args.slice(0, optionsIndex + 1)));
7119
+ return this;
7120
+ }
7121
+ /** Moves the rule or group at `oldPathOrID` to `newPath`, or shifts it `'up'`/`'down'`. */
7122
+ move(oldPathOrID, newPath, options = {}) {
7123
+ const { strict, onInvalidTarget, ...toolOptions } = options;
7124
+ this.#commit(move(this.#query, oldPathOrID, newPath, {
7125
+ ...this.#toolOptions(),
7126
+ ...toolOptions,
7127
+ onAbort: this.#onAbort({
7128
+ strict,
7129
+ onInvalidTarget
7130
+ })
7131
+ }));
7132
+ return this;
7133
+ }
7134
+ /**
7135
+ * Inserts a rule or group at the given path. Unlike the other methods, this accepts a path
7136
+ * only—inserting _at_ an `id` would be ambiguous.
7137
+ */
7138
+ insert(ruleOrGroup, path, options = {}) {
7139
+ const { strict, onInvalidTarget, ...toolOptions } = options;
7140
+ this.#commit(insert(this.#query, ruleOrGroup, path, {
7141
+ ...this.#toolOptions(),
7142
+ ...toolOptions,
7143
+ onAbort: this.#onAbort({
7144
+ strict,
7145
+ onInvalidTarget
7146
+ })
7147
+ }));
7148
+ return this;
7149
+ }
7150
+ /**
7151
+ * Creates a new group at `targetPathOrID` containing the rules/groups currently at
7152
+ * `targetPathOrID` and `sourcePathOrID`.
7153
+ */
7154
+ group(sourcePathOrID, targetPathOrID, options = {}) {
7155
+ const { strict, onInvalidTarget, ...toolOptions } = options;
7156
+ this.#commit(group(this.#query, sourcePathOrID, targetPathOrID, {
7157
+ ...this.#toolOptions(),
7158
+ ...toolOptions,
7159
+ onAbort: this.#onAbort({
7160
+ strict,
7161
+ onInvalidTarget
7162
+ })
7163
+ }));
7164
+ return this;
7165
+ }
7166
+ /**
7167
+ * Creates an independent manager with the same configuration and the current query.
7168
+ *
7169
+ * Subscribers and history are _not_ carried over: the clone starts with no listeners and an
7170
+ * empty undo stack. Because every mutation produces a new query object, the two managers
7171
+ * share the initial query safely and diverge from the first change.
7172
+ *
7173
+ * Pass `{ regenerateIDs: true }` to give every rule and group in the clone a new `id`, which
7174
+ * is useful when both queries will be used together (e.g. inserted into the same tree).
7175
+ */
7176
+ clone(options) {
7177
+ const query = options?.regenerateIDs ? regenerateIDs(this.#query, { idGenerator: this.#idGenerator }) : this.#query;
7178
+ return new QueryManager(query, this.#options);
7179
+ }
7180
+ /**
7181
+ * Registers a listener called after every change to the query, and returns a function that
7182
+ * unregisters it. Mutations that resolve to a no-op do not notify, and a
7183
+ * {@link QueryManager.batch batch} notifies once no matter how many changes it contains.
7184
+ *
7185
+ * Together with {@link QueryManager.getQuery}, this satisfies React's `useSyncExternalStore`
7186
+ * contract. Both methods are bound to the instance, so they are stable references across
7187
+ * renders and can be passed directly:
7188
+ *
7189
+ * ```ts
7190
+ * const query = useSyncExternalStore(q.subscribe, q.getQuery);
7191
+ * ```
7192
+ *
7193
+ * In React, prefer the `useQueryManager` hook from `react-querybuilder`, which wraps this.
7194
+ */
7195
+ subscribe = (listener) => {
7196
+ this.#listeners.add(listener);
7197
+ return () => {
7198
+ this.#listeners.delete(listener);
7199
+ };
7200
+ };
7201
+ /**
7202
+ * Runs `fn`, deferring history recording and subscriber notification until it returns. The
7203
+ * whole batch becomes a single undo step and triggers a single notification, or neither if
7204
+ * the query ends up unchanged.
7205
+ *
7206
+ * Batches may be nested; only the outermost one commits. If `fn` throws, the query and its
7207
+ * history are restored to their pre-batch state and the error propagates, so a batch either
7208
+ * applies completely or not at all.
7209
+ *
7210
+ * {@link QueryManager.undo}, {@link QueryManager.redo}, and {@link QueryManager.clearHistory}
7211
+ * may be called inside a batch; their notifications are deferred like everything else. Because
7212
+ * they manage the history stacks themselves, a batch containing one of them records no entry of
7213
+ * its own, leaving the stacks exactly as those methods left them.
7214
+ */
7215
+ batch(fn) {
7216
+ this.#batchDepth++;
7217
+ if (this.#batchDepth === 1) this.#batchSnapshot = {
7218
+ query: this.#query,
7219
+ past: [...this.#past],
7220
+ future: [...this.#future],
7221
+ lastSig: this.#lastSig,
7222
+ lastAt: this.#lastAt
7223
+ };
7224
+ try {
7225
+ fn();
7226
+ } catch (error) {
7227
+ if (this.#batchDepth === 1) {
7228
+ const snapshot = this.#batchSnapshot;
7229
+ this.#query = snapshot.query;
7230
+ this.#past = snapshot.past;
7231
+ this.#future = snapshot.future;
7232
+ this.#lastSig = snapshot.lastSig;
7233
+ this.#lastAt = snapshot.lastAt;
7234
+ }
7235
+ throw error;
7236
+ } finally {
7237
+ this.#batchDepth--;
7238
+ if (this.#batchDepth === 0) {
7239
+ const { query: base } = this.#batchSnapshot;
7240
+ this.#batchSnapshot = void 0;
7241
+ if (base !== this.#query) {
7242
+ if (!this.#historyBypassed) this.#record(base, this.#query);
7243
+ this.#notify();
7244
+ }
7245
+ this.#historyBypassed = false;
7246
+ }
7247
+ }
7248
+ return this;
7249
+ }
7250
+ /** Whether there is a previous query to restore. Always `false` unless `history` is enabled. */
7251
+ canUndo() {
7252
+ return this.#past.length > 0;
7253
+ }
7254
+ /** Whether there is an undone query to restore. Always `false` unless `history` is enabled. */
7255
+ canRedo() {
7256
+ return this.#future.length > 0;
7257
+ }
7258
+ /** Restores the previous query. No-op when {@link QueryManager.canUndo} is `false`. */
7259
+ undo() {
7260
+ if (this.#past.length === 0) return this;
7261
+ this.#future.unshift(this.#query);
7262
+ this.#query = this.#past.pop();
7263
+ this.#lastSig = void 0;
7264
+ this.#markHistoryBypassed();
7265
+ if (this.#batchDepth === 0) this.#notify();
7266
+ return this;
7267
+ }
7268
+ /** Restores the most recently undone query. No-op when {@link QueryManager.canRedo} is `false`. */
7269
+ redo() {
7270
+ if (this.#future.length === 0) return this;
7271
+ this.#past.push(this.#query);
7272
+ this.#query = this.#future.shift();
7273
+ this.#lastSig = void 0;
7274
+ this.#markHistoryBypassed();
7275
+ if (this.#batchDepth === 0) this.#notify();
7276
+ return this;
7277
+ }
7278
+ /** Discards all undo/redo history without changing the current query. */
7279
+ clearHistory() {
7280
+ this.#markHistoryBypassed();
7281
+ this.#past = [];
7282
+ this.#future = [];
7283
+ this.#lastSig = void 0;
7284
+ return this;
7285
+ }
7286
+ /**
7287
+ * The recorded history: `past` oldest first, `future` newest first. Both are copies, so
7288
+ * mutating them does not affect the manager.
7289
+ */
7290
+ getHistory() {
7291
+ return {
7292
+ past: [...this.#past],
7293
+ future: [...this.#future]
7294
+ };
7295
+ }
7296
+ /**
7297
+ * Validates the current query with the configured validator.
7298
+ *
7299
+ * The result is cached until the query changes, so a custom `validator` with side effects (or
7300
+ * one that depends on anything other than the query) may run fewer times than expected.
7301
+ */
7302
+ validate() {
7303
+ this.#ensureCache();
7304
+ this.#validation ??= this.#validator(this.#query);
7305
+ return this.#validation;
7306
+ }
7307
+ format(options) {
7308
+ return formatQuery(this.#query, options);
7309
+ }
7310
+ /**
7311
+ * Yields every rule and group in the query, depth-first in pre-order, starting with the root
7312
+ * group itself. Combinator strings in independent-combinator groups are skipped.
7313
+ *
7314
+ * ```ts
7315
+ * for (const { node, path, parent } of qm.walk({ rulesOnly: true })) {
7316
+ * console.log(path, node.field);
7317
+ * }
7318
+ * ```
7319
+ *
7320
+ * Traversal operates on the query as it was when iteration began, so mutating the manager
7321
+ * mid-iteration does not affect a walk already in progress. Because generators are lazy, that
7322
+ * happens on the first iteration step rather than when `walk` is called.
7323
+ *
7324
+ * @yields Every rule and group in the query, subject to `options`.
7325
+ */
7326
+ *walk(options = {}) {
7327
+ const { from, rulesOnly, groupsOnly } = options;
7328
+ let start = this.#query;
7329
+ let startPath = [];
7330
+ let startParent = null;
7331
+ if (from !== void 0) {
7332
+ const path = this.#toPath(from);
7333
+ const node = path && findPath(path, this.#query);
7334
+ if (!path || !node) return;
7335
+ start = node;
7336
+ startPath = path;
7337
+ startParent = path.length === 0 ? null : findPath(getParentPath(path), this.#query);
7338
+ }
7339
+ for (const entry of this.#walkFrom(start, startPath, startParent)) {
7340
+ if (rulesOnly && isRuleGroup(entry.node)) continue;
7341
+ if (groupsOnly && !isRuleGroup(entry.node)) continue;
7342
+ yield entry;
7343
+ }
7344
+ }
7345
+ /** Yields every rule in the query. Shorthand for `walk({ ...options, rulesOnly: true })`. */
7346
+ rules(options = {}) {
7347
+ return this.walk({
7348
+ ...options,
7349
+ rulesOnly: true
7350
+ });
7351
+ }
7352
+ /**
7353
+ * Yields every group in the query, including the root group. Shorthand for
7354
+ * `walk({ ...options, groupsOnly: true })`.
7355
+ */
7356
+ groups(options = {}) {
7357
+ return this.walk({
7358
+ ...options,
7359
+ groupsOnly: true
7360
+ });
7361
+ }
7362
+ /** Returns the first node matching `predicate`, or `null` if there is none. */
7363
+ find(predicate, options = {}) {
7364
+ for (const entry of this.walk(options)) if (predicate(entry)) return entry;
7365
+ return null;
7366
+ }
7367
+ /** Returns every node matching `predicate`. */
7368
+ filter(predicate, options = {}) {
7369
+ const results = [];
7370
+ for (const entry of this.walk(options)) if (predicate(entry)) results.push(entry);
7371
+ return results;
7372
+ }
7373
+ /** Equivalent to {@link QueryManager.walk} with no options, enabling `for...of` and spread. */
7374
+ [Symbol.iterator]() {
7375
+ return this.walk();
7376
+ }
7377
+ /**
7378
+ * Returns the rule or group at the given path, or `null` if the path can't be resolved.
7379
+ *
7380
+ * Unlike the standalone {@link findPath}, which can return `undefined` for an out-of-range
7381
+ * index, unresolvable paths are always normalized to `null` here.
7382
+ */
7383
+ findPath(path) {
7384
+ return findPath(path, this.#query) ?? null;
7385
+ }
7386
+ /**
7387
+ * Returns the rule or group with the given `id`, or `null` if there is none. Backed by an
7388
+ * index built once per query, so repeated lookups are constant time.
7389
+ */
7390
+ findID(id) {
7391
+ const path = this.#index().get(id);
7392
+ return path === void 0 ? null : findPath(path, this.#query);
7393
+ }
7394
+ /**
7395
+ * Returns the {@link Path} of the rule or group with the given `id`, or `null` if there is
7396
+ * none. Backed by an index built once per query, so repeated lookups are constant time.
7397
+ */
7398
+ getPathOfID(id) {
7399
+ return this.#index().get(id) ?? null;
7400
+ }
7401
+ /**
7402
+ * Determines whether the rule or group at the given path is disabled, either itself or by an
7403
+ * ancestor group.
7404
+ */
7405
+ pathIsDisabled(path) {
7406
+ return pathIsDisabled(path, this.#query);
7407
+ }
7408
+ /** Returns the rule or group at the given path or `id`, or `null` if it can't be resolved. */
7409
+ getNode(pathOrID) {
7410
+ const path = this.#toPath(pathOrID);
7411
+ return path === null ? null : findPath(path, this.#query) ?? null;
7412
+ }
7413
+ /**
7414
+ * Returns the rule at the given path or `id`, or `null` if it can't be resolved _or_ resolves
7415
+ * to a group.
7416
+ */
7417
+ getRule(pathOrID) {
7418
+ const node = this.getNode(pathOrID);
7419
+ return node && !isRuleGroup(node) ? node : null;
7420
+ }
7421
+ /**
7422
+ * Returns the group at the given path or `id`, or `null` if it can't be resolved _or_
7423
+ * resolves to a rule.
7424
+ */
7425
+ getGroup(pathOrID) {
7426
+ const node = this.getNode(pathOrID);
7427
+ return node && isRuleGroup(node) ? node : null;
7428
+ }
7429
+ /**
7430
+ * Returns the group containing the rule or group at the given path or `id`. Returns `null`
7431
+ * for the root group, which has no parent, and when the target can't be resolved.
7432
+ */
7433
+ getParent(pathOrID) {
7434
+ const path = this.#toPath(pathOrID);
7435
+ if (path === null || path.length === 0) return null;
7436
+ if (!findPath(path, this.#query)) return null;
7437
+ return findPath(getParentPath(path), this.#query);
7438
+ }
7439
+ /**
7440
+ * The normalized field list, as the `QueryBuilder` component would render it. Needed to
7441
+ * populate a field selector.
7442
+ */
7443
+ getFields() {
7444
+ return this.#fields;
7445
+ }
7446
+ /**
7447
+ * The normalized combinator list, as the `QueryBuilder` component would render it. Needed to
7448
+ * populate a combinator selector.
7449
+ */
7450
+ getCombinators() {
7451
+ return this.#combinators;
7452
+ }
7453
+ /**
7454
+ * The field configuration for a field name. When the field isn't configured, returns the same
7455
+ * minimal fallback (`{ name, value, label }`, all set to the field name) that
7456
+ * {@link QueryManager.getRuleContext} reports as `fieldData`, so both access paths agree.
7457
+ */
7458
+ getFieldData(field) {
7459
+ return getFieldData(field, this.#fieldMap);
7460
+ }
7461
+ /** The operator list for a field, mirroring `QueryBuilder`'s precedence. */
7462
+ getOperators(field) {
7463
+ return this.#operatorsFor(field);
7464
+ }
7465
+ /** The value sources available for a field/operator pair. */
7466
+ getValueSources(field, operator) {
7467
+ return this.#valueSourcesFor(field, operator);
7468
+ }
7469
+ /** The match modes available for a field. */
7470
+ getMatchModes(field) {
7471
+ return this.#matchModesFor(field);
7472
+ }
7473
+ /** The value option list for a field/operator pair. */
7474
+ getValues(field, operator) {
7475
+ return this.#valuesFor(field, operator);
7476
+ }
7477
+ /** The value editor type for a field/operator pair. */
7478
+ getValueEditorType(field, operator) {
7479
+ return this.#valueEditorTypeFor(field, operator);
7480
+ }
7481
+ /**
7482
+ * Resolves everything about a rule that depends on the field/operator configuration—field data,
7483
+ * operators, value editor type, value list, value sources, match modes, and validation result.
7484
+ * Returns `null` when the target can't be resolved or isn't a rule.
7485
+ *
7486
+ * This is the same derivation the `useRule` hook performs, so a non-React implementation can
7487
+ * render a rule without reimplementing the configuration precedence rules.
7488
+ */
7489
+ getRuleContext(pathOrID) {
7490
+ const rule = this.getRule(pathOrID);
7491
+ if (!rule) return null;
7492
+ const validation = this.validate();
7493
+ return deriveRuleContext(rule, {
7494
+ fields: this.#fields,
7495
+ fieldMap: this.#fieldMap,
7496
+ getInputType: (f, o, misc) => this.#options.getInputType?.(f, o, misc) ?? null,
7497
+ getMatchModes: (f) => this.#matchModesFor(f),
7498
+ getOperators: (f) => this.#operatorsFor(f),
7499
+ getParameters: (f, o, misc) => this.#parametersFor(f, o, misc),
7500
+ getValueEditorType: (f, o) => this.#valueEditorTypeFor(f, o),
7501
+ getValues: (f, o) => this.#valuesFor(f, o),
7502
+ getValueSources: (f, o) => this.getValueSources(f, o),
7503
+ getSubQueryBuilderProps: (f, misc) => this.#options.getSubQueryBuilderProps?.(f, misc) ?? {}
7504
+ }, {
7505
+ validationMap: typeof validation === "boolean" ? {} : validation,
7506
+ id: rule.id
7507
+ });
7508
+ }
7509
+ /**
7510
+ * Resolves everything about a rule group that depends on the combinator configuration, plus its
7511
+ * validation result. Returns `null` when the target can't be resolved or isn't a group.
7512
+ *
7513
+ * This is the same derivation the `useRuleGroup` hook performs.
7514
+ */
7515
+ getRuleGroupContext(pathOrID = []) {
7516
+ const ruleGroup = this.getGroup(pathOrID);
7517
+ if (!ruleGroup) return null;
7518
+ const validation = this.validate();
7519
+ return deriveRuleGroupContext(ruleGroup, this.#combinators, {
7520
+ validationMap: typeof validation === "boolean" ? {} : validation,
7521
+ id: ruleGroup.id
7522
+ });
7523
+ }
7524
+ /** Whether the current query uses independent combinators. */
7525
+ isIC() {
7526
+ return isRuleGroupTypeIC(this.#query);
7527
+ }
7528
+ /**
7529
+ * Returns the signature describing how the current query differs from `other`, as used by
7530
+ * this manager's history coalescing.
7531
+ */
7532
+ signatureOf(other) {
7533
+ return signatureOf(this.#query, other);
7534
+ }
7535
+ /** Generates a {@link DiagnosticsResult}. Shorthand for `format('diagnostics')`. */
7536
+ diagnostics() {
7537
+ return formatQuery(this.#query, "diagnostics");
7538
+ }
7539
+ /**
7540
+ * Returns the current query, so `JSON.stringify(queryManager)` produces the same output as
7541
+ * `JSON.stringify(queryManager.getQuery())`.
7542
+ */
7543
+ toJSON() {
7544
+ return this.#query;
7545
+ }
7546
+ /**
7547
+ * Returns a new manager with the same configuration and the current query converted to use
7548
+ * independent combinators. Idempotent, and never modifies this manager. As with
7549
+ * {@link QueryManager.clone}, subscribers and history are not carried over.
7550
+ */
7551
+ toIC() {
7552
+ return new QueryManager(convertToIC(this.#query), this.#options);
7553
+ }
7554
+ /**
7555
+ * Returns a new manager with the same configuration and the current query converted to use a
7556
+ * single combinator per group. Idempotent, and never modifies this manager. As with
7557
+ * {@link QueryManager.clone}, subscribers and history are not carried over.
7558
+ */
7559
+ fromIC() {
7560
+ return new QueryManager(convertFromIC(this.#query), this.#options);
7561
+ }
7562
+ /**
7563
+ * Runs {@link transformQuery} against the current query and returns its result.
7564
+ *
7565
+ * Unlike {@link QueryManager.toIC}/{@link QueryManager.fromIC}, this returns the raw
7566
+ * transformed value rather than a new manager, since `transformQuery` can produce arbitrary
7567
+ * shapes that are no longer valid queries. This manager is never modified.
7568
+ */
7569
+ transform(options) {
7570
+ return transformQuery(this.#query, options);
7571
+ }
7572
+ };
7573
+ //#endregion
7574
+ export { LogType, QueryManager, QueryManagerError, TestID, add, addInPlace, betweenOperators, bigIntJsonParseReviver, bigIntJsonStringifyReplacer, celCombinatorMap, clsx, coerceBigIntValue, coerceInputType, convertFromIC, convertQuery, convertToIC, createQueryActions, createRule, createRuleGroup, cypherCombinatorMap, defaultCELValueProcessor, defaultCoalesceMs, defaultCombinatorLabelMap, defaultCombinators, defaultCombinatorsExtended, defaultControlClassnames, defaultExportOperatorMap, defaultJoinChar, defaultMatchModes, defaultMaxHistory, defaultMongoDBValueProcessor, defaultNLTranslations, defaultOperatorLabelMap, defaultOperatorNegationMap, defaultOperatorProcessorNL, defaultOperatorProcessorSQL, defaultOperators, defaultPlaceholderFieldGroupLabel, defaultPlaceholderFieldLabel, defaultPlaceholderFieldName, defaultPlaceholderLabel, defaultPlaceholderName, defaultPlaceholderOperatorGroupLabel, defaultPlaceholderOperatorLabel, defaultPlaceholderOperatorName, defaultPlaceholderValueGroupLabel, defaultPlaceholderValueLabel, defaultPlaceholderValueName, defaultRuleGroupProcessorCEL, defaultRuleGroupProcessorCypher, defaultRuleGroupProcessorDiagnostics, defaultRuleGroupProcessorDrizzle, defaultRuleGroupProcessorElasticSearch, defaultRuleGroupProcessorGremlin, defaultRuleGroupProcessorJSONata, defaultRuleGroupProcessorJsonLogic, defaultRuleGroupProcessorLDAP, defaultRuleGroupProcessorMongoDB, defaultRuleGroupProcessorMongoDBQuery, defaultRuleGroupProcessorNL, defaultRuleGroupProcessorParameterized, defaultRuleGroupProcessorPrisma, defaultRuleGroupProcessorSPARQL, defaultRuleGroupProcessorSQL, defaultRuleGroupProcessorSequelize, defaultRuleGroupProcessorSpEL, defaultRuleGroupProcessorTanStackDB, defaultRuleProcessorCEL, defaultRuleProcessorCypher, defaultRuleProcessorDrizzle, defaultRuleProcessorElasticSearch, defaultRuleProcessorGremlin, defaultRuleProcessorJSONata, defaultRuleProcessorJsonLogic, defaultRuleProcessorLDAP, defaultRuleProcessorMongoDB, defaultRuleProcessorMongoDBQuery, defaultRuleProcessorNL, defaultRuleProcessorParameterized, defaultRuleProcessorPrisma, defaultRuleProcessorSPARQL, defaultRuleProcessorSQL, defaultRuleProcessorSequelize, defaultRuleProcessorSpEL, defaultRuleProcessorTanStackDB, defaultSpELValueProcessor, defaultTranslations, defaultValidator, defaultValueProcessor, defaultValueProcessorByRule, defaultValueProcessorCELByRule, defaultValueProcessorMongoDBByRule, defaultValueProcessorNL, defaultValueProcessorSpELByRule, derivePathInfo, deriveQueryBuilderClassNames, deriveRuleClassName, deriveRuleClassNames, deriveRuleContext, deriveRuleGroupClassNames, deriveRuleGroupContext, deriveRuleGroupOuterClassName, deriveRuleOuterClassName, exceedsMaxLevels, filterFieldsByComparator, findID, findPath, formatQuery, formatQueryOptionPresets, generateAccessibleDescription, generateID, getCommonAncestorPath, getFieldData, getFirstOption, getGuardAbortReason, getLikeWildcards, getMatchModesUtil, getMultiValueUpdate, getNLTranslataion, getOption, getParametersAsList, getParentPath, getParseNumberMethod, getPathOfID, getQuoteFieldNamesWithArray, getQuotedFieldName, getRuleDefaultValue, getRuleGroupCombinator, getRuleInputType, getRuleValidationResult, getRuleValueEditorType, getRuleValueSourceOptions, getRuleValues, getSQLConcat, getSubqueryElementAlias, 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, isValidValue, isValidationResult, isValueProcessorLegacy, joinWith, jsonLogicAdditionalOperators, lc, mapSQLOperator, mergeAnyTranslation, mergeAnyTranslations, mergeClassnames, mongoDbFallback, mongoOperators, move, moveInPlace, normalizeConstituentWordOrder, normalizeValueSelectorValue, nullFreeArray, nullOperators, nullOrUndefinedOrEmpty, numericRegex, numerifyValues, objectEntries, objectKeys, parseNumber, pathIsDisabled, pathIsDisabledByPaths, pathsAreEqual, preferAnyProp, preferFlagProps, preferProp, prepareOptionList, prepareRule, prepareRuleGroup, prepareRuleOrGroup, prismaFallback, prismaOperators, processMatchMode, queryBuilderFlagDefaults, regenerateID, regenerateIDs, relationalOperators, remove, removeInPlace, resolveCandidateQuery, resolveDefaultOperator, resolveOperatorList, resolveValueEditorType, resolveValueList, rootPath, shouldRenderAsNumber, signatureOf, sparqlVar, splitBy, sqlDialectPresets, standardClassnames, strictAbortReasons, stripParamPrefix, structuralSignature, subqueryElementAliasBase, substringOperators, toArray, toFlatOptionArray, toFullOption, toFullOptionList, toFullOptionMap, transformQuery, trimIfString, unchangedSignature, uniqByIdentifier, uniqByName, uniqOptGroups, uniqOptList, update, updateInPlace, uuidV4regex, withParamPrefix, wrapLikeFragment };
5462
7575
 
5463
7576
  //# sourceMappingURL=react-querybuilder_core.mjs.map