@react-querybuilder/core 8.21.1 → 8.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/react-querybuilder_core.cjs.development.d.ts +1426 -56
  3. package/dist/cjs/react-querybuilder_core.cjs.development.js +2241 -83
  4. package/dist/cjs/react-querybuilder_core.cjs.development.js.map +1 -1
  5. package/dist/cjs/react-querybuilder_core.cjs.production.d.ts +1426 -56
  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.d.mts +17 -2
  9. package/dist/formatQuery.d.ts +17 -2
  10. package/dist/formatQuery.js +68 -45
  11. package/dist/formatQuery.js.map +1 -1
  12. package/dist/formatQuery.mjs +67 -46
  13. package/dist/formatQuery.mjs.map +1 -1
  14. package/dist/{import-Ddc3d_8q.d.ts → import-6ExjhifL.d.ts} +2 -2
  15. package/dist/{import-BVdxeIBO.d.mts → import-DVWI7Fdh.d.mts} +2 -2
  16. package/dist/{index-BSwFvkeI.d.mts → index-CX9mehdL.d.mts} +15 -1
  17. package/dist/{index-BSwFvkeI.d.ts → index-CX9mehdL.d.ts} +15 -1
  18. package/dist/parseCEL.d.mts +2 -2
  19. package/dist/parseCEL.d.ts +2 -2
  20. package/dist/parseCEL.js +6 -8
  21. package/dist/parseCEL.js.map +1 -1
  22. package/dist/parseCEL.mjs +6 -8
  23. package/dist/parseCEL.mjs.map +1 -1
  24. package/dist/parseCypher.d.mts +1 -1
  25. package/dist/parseCypher.d.ts +1 -1
  26. package/dist/parseCypher.js.map +1 -1
  27. package/dist/parseGremlin.d.mts +1 -1
  28. package/dist/parseGremlin.d.ts +1 -1
  29. package/dist/parseJSONata.d.mts +2 -2
  30. package/dist/parseJSONata.d.ts +2 -2
  31. package/dist/parseJSONata.js +2 -2
  32. package/dist/parseJSONata.js.map +1 -1
  33. package/dist/parseJSONata.mjs +1 -1
  34. package/dist/parseJsonLogic.d.mts +2 -2
  35. package/dist/parseJsonLogic.d.ts +2 -2
  36. package/dist/parseJsonLogic.js +3 -3
  37. package/dist/parseJsonLogic.js.map +1 -1
  38. package/dist/parseJsonLogic.mjs +3 -3
  39. package/dist/parseJsonLogic.mjs.map +1 -1
  40. package/dist/parseMongoDB.d.mts +2 -2
  41. package/dist/parseMongoDB.d.ts +2 -2
  42. package/dist/parseMongoDB.js +2 -2
  43. package/dist/parseMongoDB.mjs +2 -2
  44. package/dist/parseSPARQL.d.mts +1 -1
  45. package/dist/parseSPARQL.d.ts +1 -1
  46. package/dist/parseSPARQL.js +0 -1
  47. package/dist/parseSPARQL.js.map +1 -1
  48. package/dist/parseSPARQL.mjs +0 -1
  49. package/dist/parseSPARQL.mjs.map +1 -1
  50. package/dist/parseSQL.d.mts +2 -2
  51. package/dist/parseSQL.d.ts +2 -2
  52. package/dist/parseSQL.js +54 -62
  53. package/dist/parseSQL.js.map +1 -1
  54. package/dist/parseSQL.mjs +54 -62
  55. package/dist/parseSQL.mjs.map +1 -1
  56. package/dist/parseSpEL.d.mts +2 -2
  57. package/dist/parseSpEL.d.ts +2 -2
  58. package/dist/parseSpEL.js +3 -3
  59. package/dist/parseSpEL.js.map +1 -1
  60. package/dist/parseSpEL.mjs +3 -3
  61. package/dist/parseSpEL.mjs.map +1 -1
  62. package/dist/{prepareQueryObjects-C2-kTZvR.js → prepareQueryObjects-BnGL91Tt.js} +9 -5
  63. package/dist/{prepareQueryObjects-C2-kTZvR.js.map → prepareQueryObjects-BnGL91Tt.js.map} +1 -1
  64. package/dist/{prepareQueryObjects-Q18lHCKl.mjs → prepareQueryObjects-ChINDIap.mjs} +9 -5
  65. package/dist/{prepareQueryObjects-Q18lHCKl.mjs.map → prepareQueryObjects-ChINDIap.mjs.map} +1 -1
  66. package/dist/query-builder-layout.css +1 -1
  67. package/dist/query-builder-layout.css.map +1 -1
  68. package/dist/query-builder.css +1 -1
  69. package/dist/query-builder.css.map +1 -1
  70. package/dist/react-querybuilder_core.d.mts +1426 -56
  71. package/dist/react-querybuilder_core.legacy-esm.d.ts +1426 -56
  72. package/dist/react-querybuilder_core.legacy-esm.js +2279 -92
  73. package/dist/react-querybuilder_core.legacy-esm.js.map +1 -1
  74. package/dist/react-querybuilder_core.mjs +2198 -85
  75. package/dist/react-querybuilder_core.mjs.map +1 -1
  76. package/dist/react-querybuilder_core.production.d.mts +1426 -56
  77. package/dist/react-querybuilder_core.production.mjs +1 -1
  78. package/dist/react-querybuilder_core.production.mjs.map +1 -1
  79. package/dist/styles/_layout.scss +5 -2
  80. package/dist/transformQuery.d.mts +1 -1
  81. package/dist/transformQuery.d.ts +1 -1
  82. package/dist/{utils-Dt0D_DMV.mjs → utils-4k-j5FBa.mjs} +20 -3
  83. package/dist/utils-4k-j5FBa.mjs.map +1 -0
  84. package/dist/{utils-DEwV7F6B.js → utils-C79dxwZb.js} +31 -2
  85. package/dist/utils-C79dxwZb.js.map +1 -0
  86. package/package.json +9 -8
  87. package/dist/utils-DEwV7F6B.js.map +0 -1
  88. package/dist/utils-Dt0D_DMV.mjs.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,178 @@ 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
+ * Expands conditions into `clsx` arguments: every custom class first (in declaration order),
947
+ * then a single object of standard classes.
948
+ */
949
+ const conditionArgs = (conditions, state, { classNames, suppressStandardClassnames }) => {
950
+ const evaluated = conditions.map((condition) => [condition, !!condition.when(state)]);
951
+ return [...evaluated.filter(([condition]) => !condition.standardOnly).map(([condition, active]) => active && classNames?.[condition.key]), suppressStandardClassnames || Object.fromEntries(evaluated.map(([condition, active]) => [standardClassnames[condition.key], active]))];
952
+ };
953
+ /** Composes the classname for a single entry of a spec map. */
954
+ const deriveFromSpec = (key, spec, state, options) => {
955
+ const { sources, conditions } = Array.isArray(spec) ? { sources: spec } : spec;
956
+ return clsx(options.suppressStandardClassnames || standardClassnames[key], ...sources.map((source) => options.classNames?.[source]), ...conditions ? conditionArgs(conditions, state, options) : []);
957
+ };
958
+ const deriveFromSpecs = (specs, state, options) => {
959
+ const result = {};
960
+ for (const [key, spec] of Object.entries(specs)) result[key] = deriveFromSpec(key, spec, state, options);
961
+ return result;
962
+ };
963
+ /**
964
+ * Classnames for each element rendered by a rule. This is the framework-agnostic core of the
965
+ * `classNames` object returned by the `useRule` hook.
966
+ *
967
+ * @group Query Tools
968
+ */
969
+ const deriveRuleClassNames = (options) => deriveFromSpecs(ruleClassnameSources, {}, options);
970
+ /**
971
+ * The classname for a single element of a rule, composed from the same table as
972
+ * {@link deriveRuleClassNames}. Useful where only one is needed, such as the items of a
973
+ * multi-value editor.
974
+ *
975
+ * @group Query Tools
976
+ */
977
+ const deriveRuleClassName = (key, options) => deriveFromSpec(key, ruleClassnameSources[key], {}, options);
978
+ /**
979
+ * Classnames for each element rendered by a rule group, including its conditionally-classed
980
+ * `header`. This is the framework-agnostic core of the `classNames` object returned by the
981
+ * `useRuleGroup` hook.
982
+ *
983
+ * @group Query Tools
984
+ */
985
+ const deriveRuleGroupClassNames = (options) => deriveFromSpecs(ruleGroupClassnameSources, options, options);
986
+ const deriveOuterClassName = (standardKey, conditions, options) => {
987
+ const { classNames, suppressStandardClassnames, leadingClassNames = [], validationClassName } = options;
988
+ return clsx(...leadingClassNames, suppressStandardClassnames || standardClassnames[standardKey], classNames?.[standardKey], ...conditionArgs(conditions, options, options), validationClassName);
989
+ };
990
+ /**
991
+ * The outer (wrapper) classname for a rule, including every conditional state class.
992
+ *
993
+ * @group Query Tools
994
+ */
995
+ const deriveRuleOuterClassName = (options) => deriveOuterClassName("rule", ruleOuterConditions, options);
996
+ /**
997
+ * The outer (wrapper) classname for a rule group, including every conditional state class.
998
+ *
999
+ * A group reflects fewer drag-and-drop states than a rule—`dndOver`, `dndCopy`,
1000
+ * `dndDropNotAllowed`, and `hasSubQuery` do not apply—so this is not interchangeable with
1001
+ * {@link deriveRuleOuterClassName}.
1002
+ *
1003
+ * @group Query Tools
1004
+ */
1005
+ const deriveRuleGroupOuterClassName = (options) => deriveOuterClassName("ruleGroup", ruleGroupOuterConditions, options);
1006
+ //#endregion
803
1007
  //#region src/utils/defaultValidator.ts
804
1008
  /**
805
1009
  * This is an example validation function you can pass to {@link react-querybuilder!QueryBuilder QueryBuilder} in the
@@ -1060,8 +1264,10 @@ const prepareOptionList = (props) => {
1060
1264
  ...op,
1061
1265
  [placeholderName]: defaultOption
1062
1266
  };
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);
1267
+ } else if (isFlexibleOptionGroupArray(optionList)) for (const og of optionList) for (const opt of og.options) optionsMap[opt.value ??
1268
+ /* v8 ignore start -- @preserve */ opt.name] = toFullOption(opt, baseOption);
1269
+ else for (const opt of optionList) optionsMap[opt.value ??
1270
+ /* v8 ignore start -- @preserve */ opt.name] = toFullOption(opt, baseOption);
1065
1271
  return {
1066
1272
  defaultOption,
1067
1273
  optionList,
@@ -1075,7 +1281,8 @@ const filterByComparator = (field, operator, fieldToCompare) => {
1075
1281
  const fullFieldToCompare = toFullOption(fieldToCompare);
1076
1282
  if (fullField.value === fullFieldToCompare.value) return false;
1077
1283
  if (typeof fullField.comparator === "string") return fullField[fullField.comparator] === fullFieldToCompare[fullField.comparator];
1078
- return fullField.comparator?.(fullFieldToCompare, operator) ?? false;
1284
+ return fullField.comparator?.(fullFieldToCompare, operator) ??
1285
+ /* v8 ignore start -- @preserve */ false;
1079
1286
  };
1080
1287
  /**
1081
1288
  * For a given {@link FullField}, returns the `fields` list filtered for
@@ -1090,7 +1297,8 @@ const filterByComparator = (field, operator, fieldToCompare) => {
1090
1297
  */
1091
1298
  const filterFieldsByComparator = (field, fields, operator) => {
1092
1299
  if (!field.comparator) {
1093
- const filterOutSameField = (f) => (f.value ?? f.name) !== (field.value ?? field.name);
1300
+ const filterOutSameField = (f) => (f.value ?? /* v8 ignore start -- @preserve */ f.name) !== (field.value ??
1301
+ /* v8 ignore start -- @preserve */ field.name);
1094
1302
  if (isFlexibleOptionGroupArray(fields)) return fields.map((og) => ({
1095
1303
  ...og,
1096
1304
  options: og.options.filter((v) => filterOutSameField(v))
@@ -1104,6 +1312,128 @@ const filterFieldsByComparator = (field, fields, operator) => {
1104
1312
  return fields.filter((f) => filterByComparator(field, operator, f));
1105
1313
  };
1106
1314
  //#endregion
1315
+ //#region src/utils/deriveRuleContext.ts
1316
+ /**
1317
+ * Resolves the field configuration for a rule's `field`, falling back to a minimal option object
1318
+ * when the field isn't present in the field map.
1319
+ */
1320
+ const getFieldData = (field, fieldMap) => fieldMap?.[field] ?? {
1321
+ name: field,
1322
+ value: field,
1323
+ label: field
1324
+ };
1325
+ /**
1326
+ * The input type for a rule. A field's own `inputType` takes precedence over `getInputType`.
1327
+ */
1328
+ const getRuleInputType = (field, operator, fieldData, getInputType) => fieldData.inputType ?? getInputType(field, operator, { fieldData });
1329
+ /**
1330
+ * Whether the value editor(s) should be hidden for an operator, based on its `arity`.
1331
+ */
1332
+ const hideValueControlsForOperator = (operatorObject) => {
1333
+ const arity = operatorObject?.arity;
1334
+ return typeof arity === "string" && arity === "unary" || typeof arity === "number" && arity < 2;
1335
+ };
1336
+ /**
1337
+ * Value source options for a rule. A `valueSource` present on the rule but absent from the
1338
+ * configured list is appended, so the current selection is always representable.
1339
+ */
1340
+ const getRuleValueSourceOptions = (rule, fieldData, getValueSources) => {
1341
+ const configuredVSs = getValueSources(rule.field, rule.operator, { fieldData });
1342
+ if (rule.valueSource && !getOption(configuredVSs, rule.valueSource)) return [...configuredVSs, {
1343
+ name: rule.valueSource,
1344
+ value: rule.valueSource,
1345
+ label: rule.valueSource
1346
+ }];
1347
+ return configuredVSs;
1348
+ };
1349
+ /**
1350
+ * Normalizes the result of `getParameters` to a non-empty list or `null`.
1351
+ */
1352
+ const getParametersAsList = (parameters) => parameters && parameters.length > 0 ? parameters : null;
1353
+ /**
1354
+ * The value editor type for a rule. `valueSource: "field"` always uses a select list, and
1355
+ * `valueSource: "parameter"` uses a (multi)select when parameters are available.
1356
+ */
1357
+ 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 });
1358
+ /**
1359
+ * The option list presented by a rule's value editor, resolved from its `valueSource`.
1360
+ */
1361
+ const getRuleValues = (rule, fieldData, fields, parametersAsList, getValues) => {
1362
+ const v = rule.valueSource === "field" ? filterFieldsByComparator(fieldData, fields, rule.operator) : rule.valueSource === "parameter" ? parametersAsList ?? [] : getValues(rule.field, rule.operator, { fieldData });
1363
+ return isFlexibleOptionArray(v) || isFlexibleOptionGroupArray(v) ? toFullOptionList(v) : v;
1364
+ };
1365
+ /**
1366
+ * The validation result for a rule: the entry from a query-level {@link ValidationMap} if present,
1367
+ * otherwise the field's own `validator` result, otherwise `null`.
1368
+ */
1369
+ const getRuleValidationResult = (rule, fieldData, validationMap = {}, id = rule.id ?? "") => validationMap[id] ?? (typeof fieldData.validator === "function" ? fieldData.validator(rule) : null);
1370
+ /**
1371
+ * Resolves everything about a single rule that depends on the field/operator configuration:
1372
+ * its field data, operators, value editor type, value list, value sources, match modes, and
1373
+ * validation result.
1374
+ *
1375
+ * This is the framework-agnostic core of the `useRule` hook, shared with
1376
+ * {@link QueryManager.getRuleContext} so that non-React implementations derive identical results.
1377
+ * It performs no memoization; callers are responsible for caching as appropriate.
1378
+ *
1379
+ * @group Query Tools
1380
+ */
1381
+ const deriveRuleContext = (rule, resolvers, options = {}) => {
1382
+ const { fields, fieldMap, getInputType, getMatchModes, getOperators, getParameters, getValueEditorType, getValues, getValueSources, getSubQueryBuilderProps } = resolvers;
1383
+ const fieldData = getFieldData(rule.field, fieldMap);
1384
+ const inputType = fieldData.inputType ?? getInputType(rule.field, rule.operator, { fieldData });
1385
+ const matchModes = getMatchModes(rule.field, { fieldData });
1386
+ const operators = getOperators(rule.field, { fieldData });
1387
+ const operatorObject = getOption(operators, rule.operator);
1388
+ const valueSourceOptions = getRuleValueSourceOptions(rule, fieldData, getValueSources);
1389
+ const parameters = getParametersAsList(getParameters(rule.field, rule.operator, { fieldData }));
1390
+ return {
1391
+ fieldData,
1392
+ hideValueControls: hideValueControlsForOperator(operatorObject),
1393
+ inputType,
1394
+ matchModes,
1395
+ operatorObject,
1396
+ operators,
1397
+ parameters,
1398
+ validationResult: getRuleValidationResult(rule, fieldData, options.validationMap, options.id),
1399
+ valueEditorType: getRuleValueEditorType(rule, fieldData, parameters, getValueEditorType),
1400
+ values: getRuleValues(rule, fieldData, fields, parameters, getValues),
1401
+ valueSourceOptions,
1402
+ valueSources: valueSourceOptions.map(({ value }) => value),
1403
+ subQueryBuilderProps: getSubQueryBuilderProps?.(rule.field, { fieldData }) ?? {}
1404
+ };
1405
+ };
1406
+ /**
1407
+ * The effective combinator for a group: its own `combinator` when it has one, otherwise the
1408
+ * first configured combinator (which is the case for groups with independent combinators).
1409
+ *
1410
+ * This intentionally covers only the current property-based API. The `RuleGroup` component
1411
+ * additionally falls back to its deprecated `combinator` prop; that fallback stays in the hook.
1412
+ */
1413
+ const getRuleGroupCombinator = (ruleGroup, combinators) => isRuleGroupType(ruleGroup) ? ruleGroup.combinator : getFirstOption(combinators) ?? "";
1414
+ /**
1415
+ * Resolves everything about a rule group that depends on the combinator configuration, plus its
1416
+ * validation result.
1417
+ *
1418
+ * Note that unlike {@link deriveRuleContext}, there is no field-level validator fallback—a
1419
+ * group's validation result comes only from the query-level {@link ValidationMap}.
1420
+ *
1421
+ * @group Query Tools
1422
+ */
1423
+ const deriveRuleGroupContext = (ruleGroup, combinators, options = {}) => {
1424
+ const independentCombinators = isRuleGroupTypeIC(ruleGroup);
1425
+ const combinator = getRuleGroupCombinator(ruleGroup, combinators);
1426
+ const combinatorObject = getOption(combinators, combinator);
1427
+ return {
1428
+ combinator,
1429
+ combinatorObject,
1430
+ combinators,
1431
+ combinatorBasedClassName: independentCombinators ? null : combinatorObject?.className ?? "",
1432
+ independentCombinators,
1433
+ validationResult: (options.validationMap ?? {})[options.id ?? ruleGroup.id ?? ""] ?? null
1434
+ };
1435
+ };
1436
+ //#endregion
1107
1437
  //#region src/utils/parseNumber.ts
1108
1438
  /**
1109
1439
  * Converts a string to a number. Uses native `parseFloat` if `parseNumbers` is "native",
@@ -1123,6 +1453,104 @@ const parseNumber = (val, { parseNumbers, bigIntOnOverflow } = {}) => {
1123
1453
  return typeof valAsNum === "bigint" || !Number.isNaN(valAsNum) ? valAsNum : val;
1124
1454
  };
1125
1455
  //#endregion
1456
+ //#region src/utils/deriveValueEditor.ts
1457
+ /** Operators whose value is a list rather than a single scalar. */
1458
+ const multiValueOperators = /* @__PURE__ */ new Set([
1459
+ "between",
1460
+ "notBetween",
1461
+ "in",
1462
+ "notIn"
1463
+ ]);
1464
+ /**
1465
+ * Whether an operator's value is a list of two bounds.
1466
+ *
1467
+ * @group Value Editors
1468
+ */
1469
+ const isBetweenOperator = (operator) => operator === "between" || operator === "notBetween";
1470
+ /**
1471
+ * Determines whether a rule's `value` needs to be collapsed to a single element because it no
1472
+ * longer represents a list, and what it should become.
1473
+ *
1474
+ * This happens when the value is an array (or a comma-containing string in a `number` input,
1475
+ * which `<input type="number">` can't display) while the operator is not one of the multi-value
1476
+ * operators and the editor is not a multiselect—typically right after the operator changes from
1477
+ * `"in"` or `"between"` to something else.
1478
+ *
1479
+ * The React `useValueEditor` hook applies the result in an effect; other implementations may
1480
+ * apply it wherever is idiomatic.
1481
+ *
1482
+ * @group Value Editors
1483
+ */
1484
+ const getValueEditorReset = ({ skipHook, type, operator, value, inputType }) => {
1485
+ return !skipHook && type !== "multiselect" && !multiValueOperators.has(operator) && (Array.isArray(value) || inputType === "number" && typeof value === "string" && value.includes(",")) ? {
1486
+ reset: true,
1487
+ value: toArray(value, { retainEmptyStrings: true })[0] ?? ""
1488
+ } : {
1489
+ reset: false,
1490
+ value
1491
+ };
1492
+ };
1493
+ /**
1494
+ * Produces the next value for a series of value editors when the editor at `index` changes.
1495
+ *
1496
+ * For `between`/`notBetween`, editing the first bound guarantees an array of at least two
1497
+ * elements, seeding the second from the first available option. The result is a comma-joined
1498
+ * string unless `listsAsArrays` is `true`.
1499
+ *
1500
+ * @group Value Editors
1501
+ */
1502
+ const getMultiValueUpdate = ({ value, index, valueAsArray, operator, values, listsAsArrays, parseNumberMethod }) => {
1503
+ const parsedVal = parseNumber(value, { parseNumbers: parseNumberMethod });
1504
+ const needsBetweenFix = index === 0 && isBetweenOperator(operator) && (valueAsArray.length < 2 || valueAsArray[1] === void 0);
1505
+ if (valueAsArray[index] === parsedVal && !needsBetweenFix) return listsAsArrays ? valueAsArray : joinWith(valueAsArray, ",");
1506
+ const v = [...valueAsArray];
1507
+ v[index] = parsedVal;
1508
+ if (needsBetweenFix) v[1] = getFirstOption(values) ?? "";
1509
+ return listsAsArrays ? v : joinWith(v, ",");
1510
+ };
1511
+ /**
1512
+ * Coerces a value to a `bigint`, falling back to the parsed number when it can't be represented
1513
+ * as one (an empty string or a decimal, for example).
1514
+ *
1515
+ * @group Value Editors
1516
+ */
1517
+ const coerceBigIntValue = (value, parseNumberMethod) => {
1518
+ const valAsMaybeNumber = parseNumber(value, {
1519
+ parseNumbers: parseNumberMethod,
1520
+ bigIntOnOverflow: true
1521
+ });
1522
+ try {
1523
+ return BigInt(valAsMaybeNumber);
1524
+ } catch {
1525
+ return valAsMaybeNumber;
1526
+ }
1527
+ };
1528
+ /**
1529
+ * The `type` attribute an `<input>` should use for a rule. `bigint` values and the `in`/`notIn`
1530
+ * operators (whose value is a comma-separated list) both require a text input.
1531
+ *
1532
+ * @group Value Editors
1533
+ */
1534
+ const coerceInputType = (inputType, operator) => inputType === "bigint" || operator === "in" || operator === "notIn" ? "text" : inputType || "text";
1535
+ /**
1536
+ * Produces the next value for a value selector. Multiselect values are normalized to an array
1537
+ * first, then comma-joined unless `listsAsArrays` is `true`. Single-select values pass through.
1538
+ *
1539
+ * @group Value Editors
1540
+ */
1541
+ const getValueSelectorUpdate = (value, { multiple, listsAsArrays } = {}) => {
1542
+ if (!multiple) return value;
1543
+ const valueAsArray = toArray(value);
1544
+ return listsAsArrays ? valueAsArray : joinWith(valueAsArray, ",");
1545
+ };
1546
+ /**
1547
+ * Normalizes a value selector's current value for display. Multiselect values become an array of
1548
+ * strings so they match option names, which are always strings (e.g. `[42]` becomes `["42"]`).
1549
+ *
1550
+ * @group Value Editors
1551
+ */
1552
+ const normalizeValueSelectorValue = (value, multiple) => multiple ? toArray(value).map(String) : value;
1553
+ //#endregion
1126
1554
  //#region src/utils/transformQuery.ts
1127
1555
  const remapProperties = (obj, propertyMap, deleteRemappedProperties) => {
1128
1556
  const result = {};
@@ -1469,7 +1897,9 @@ const translationMatchFilter = (key, keyToTest, conditions) => keyToTest.startsW
1469
1897
  *
1470
1898
  * @group Export
1471
1899
  */
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] ?? "";
1900
+ const getNLTranslataion = (key, translations, conditions = []) => conditions.length === 0 ? translations[key] ?? defaultNLTranslations[key] ??
1901
+ /* 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] ??
1902
+ /* v8 ignore next -- @preserve */ "";
1473
1903
  /**
1474
1904
  * Transforms
1475
1905
  * - `match: { mode: "atLeast", threshold: 1 }` to `match: { mode: "some" }`
@@ -1495,6 +1925,21 @@ const processMatchMode = (rule) => {
1495
1925
  };
1496
1926
  };
1497
1927
  /**
1928
+ * Base alias assigned to the element binding of a subquery generated for a rule's
1929
+ * {@link MatchMode `match` mode} (e.g. `unnest(field) as elem_alias`).
1930
+ */
1931
+ const subqueryElementAliasBase = "elem_alias";
1932
+ /**
1933
+ * Returns the element alias for a subquery at the given nesting depth. Depth 0 (the common,
1934
+ * non-nested case) uses {@link subqueryElementAliasBase} unchanged; deeper levels are suffixed
1935
+ * with the depth, so a subquery nested within a subquery cannot shadow its parent's binding.
1936
+ *
1937
+ * Deterministic by depth rather than randomized, so output remains stable and snapshot-friendly.
1938
+ *
1939
+ * @group Export
1940
+ */
1941
+ const getSubqueryElementAlias = (subqueryDepth = 0) => subqueryDepth > 0 ? `${subqueryElementAliasBase}_${subqueryDepth}` : subqueryElementAliasBase;
1942
+ /**
1498
1943
  * "Replacer" method for JSON.stringify's second argument. Converts `bigint` values to
1499
1944
  * objects with a `$bigint` property having a value of a string representation of
1500
1945
  * the actual `bigint`-type value.
@@ -1523,7 +1968,7 @@ const bigIntJsonParseReviver = (_key, value) => isPojo(value) && Object.keys(val
1523
1968
  const defaultRuleGroupProcessorCEL = (ruleGroup, options) => {
1524
1969
  const { fields, fallbackExpression, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
1525
1970
  const processRuleGroup = (rg, outermost) => {
1526
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermost ? fallbackExpression : "";
1971
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermost ? fallbackExpression : "";
1527
1972
  const processedRules = [];
1528
1973
  let precedingCombinator = "";
1529
1974
  let firstRule = true;
@@ -1587,11 +2032,16 @@ const defaultRuleProcessorCEL = (rule, opts = {}) => {
1587
2032
  if (matchEval === false) return "";
1588
2033
  else if (matchEval) {
1589
2034
  const { mode, threshold } = matchEval;
1590
- const arrayElementAlias = "elem_alias";
1591
- const nestedArrayFilter = defaultRuleGroupProcessorCEL(transformQuery(rule.value, { ruleProcessor: (r) => ({
2035
+ const subqueryDepth = opts.subqueryDepth ?? 0;
2036
+ const arrayElementAlias = getSubqueryElementAlias(subqueryDepth);
2037
+ const celQuery = transformQuery(rule.value, { ruleProcessor: (r) => ({
1592
2038
  ...r,
1593
2039
  field: `${arrayElementAlias}${r.field ? `.${r.field}` : ""}`
1594
- }) }), opts);
2040
+ }) });
2041
+ const nestedArrayFilter = defaultRuleGroupProcessorCEL(celQuery, {
2042
+ ...opts,
2043
+ subqueryDepth: subqueryDepth + 1
2044
+ });
1595
2045
  switch (mode) {
1596
2046
  case "all": return `${field}.all(${arrayElementAlias}, ${nestedArrayFilter})`;
1597
2047
  case "none":
@@ -1665,7 +2115,7 @@ const defaultRuleGroupProcessorMongoDBQuery = (ruleGroup, options, meta) => {
1665
2115
  const { context, fields, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
1666
2116
  const { inExpressionContext } = context ?? {};
1667
2117
  const processRuleGroup = (rg, outermost) => {
1668
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermost ? mongoDbFallback : false;
2118
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermost ? mongoDbFallback : false;
1669
2119
  const combinator = `$${lc(rg.combinator)}`;
1670
2120
  let hasChildRules = false;
1671
2121
  const expressions = rg.rules.map((rule) => {
@@ -1843,7 +2293,7 @@ const defaultRuleProcessorMongoDB = (rule, options) => {
1843
2293
  const defaultRuleGroupProcessorSpEL = (ruleGroup, options) => {
1844
2294
  const { fields, fallbackExpression, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
1845
2295
  const processRuleGroup = (rg, outermost) => {
1846
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermost ? fallbackExpression : "";
2296
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermost ? fallbackExpression : "";
1847
2297
  const processedRules = [];
1848
2298
  let precedingCombinator = "";
1849
2299
  let firstRule = true;
@@ -2084,7 +2534,7 @@ const cypherCombinatorMap = {
2084
2534
  const defaultRuleGroupProcessorCypher = (ruleGroup, options) => {
2085
2535
  const { fields, fallbackExpression, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
2086
2536
  const processRuleGroup = (rg, outermost) => {
2087
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermost ? fallbackExpression : "";
2537
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermost ? fallbackExpression : "";
2088
2538
  const processedRules = [];
2089
2539
  let precedingCombinator = "";
2090
2540
  let firstRule = true;
@@ -2148,7 +2598,7 @@ const defaultRuleGroupProcessorDrizzle = (ruleGroup, options, _meta) => (columns
2148
2598
  if (!columns || !drizzleOperators) return void 0;
2149
2599
  const { and, not, or } = drizzleOperators;
2150
2600
  const processRuleGroup = (rg, _outermost) => {
2151
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return;
2601
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return;
2152
2602
  const processedRules = rg.rules.map((rule) => {
2153
2603
  if (isRuleGroup(rule)) return processRuleGroup(rule);
2154
2604
  const [validationResult, fieldValidator] = validateRule(rule);
@@ -2181,7 +2631,7 @@ const defaultRuleGroupProcessorDrizzle = (ruleGroup, options, _meta) => (columns
2181
2631
  const defaultRuleGroupProcessorElasticSearch = (ruleGroup, options) => {
2182
2632
  const { fields, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
2183
2633
  const processRuleGroup = (rg) => {
2184
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return false;
2634
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return false;
2185
2635
  const processedRules = rg.rules.map((rule) => {
2186
2636
  if (isRuleGroup(rule)) return processRuleGroup(rule);
2187
2637
  const [validationResult, fieldValidator] = validateRule(rule);
@@ -2216,7 +2666,9 @@ const defaultRuleGroupProcessorGremlin = (ruleGroup, options) => {
2216
2666
  // v8 ignore next -- @preserve
2217
2667
  if (typeof rule === "string" || isRuleGroup(rule)) return void 0;
2218
2668
  const [validationResult, fieldValidator] = validateRule(rule);
2219
- if (!isRuleOrGroupValid(rule, validationResult, fieldValidator) || rule.field === placeholderFieldName || rule.operator === placeholderOperatorName || placeholderValueName !== void 0 && rule.value === placeholderValueName) return;
2669
+ if (!isRuleOrGroupValid(rule, validationResult, fieldValidator) ||
2670
+ /* v8 ignore next 2 -- @preserve */
2671
+ rule.field === placeholderFieldName || rule.operator === placeholderOperatorName || placeholderValueName !== void 0 && rule.value === placeholderValueName) return;
2220
2672
  const fieldData = getOption(fields, rule.field);
2221
2673
  return ruleProcessor(rule, {
2222
2674
  ...options,
@@ -2227,7 +2679,7 @@ const defaultRuleGroupProcessorGremlin = (ruleGroup, options) => {
2227
2679
  };
2228
2680
  /** Recursively processes a nested group into `.and()`/`.or()`/`.not()` form. */
2229
2681
  const processNested = (rg) => {
2230
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return "";
2682
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return "";
2231
2683
  const predicates = [];
2232
2684
  for (const rule of rg.rules) {
2233
2685
  if (typeof rule === "string") continue;
@@ -2245,7 +2697,7 @@ const defaultRuleGroupProcessorGremlin = (ruleGroup, options) => {
2245
2697
  if (predicates.length === 1 && !rg.not) return predicates[0];
2246
2698
  return `.${prefix}(${predicates.map((p) => p.startsWith(".") ? `__${p}` : p).join(", ")})`;
2247
2699
  };
2248
- if (!isRuleOrGroupValid(ruleGroup, validationMap[ruleGroup.id ?? ""])) return fallbackExpression;
2700
+ if (!isRuleOrGroupValid(ruleGroup, validationMap[ruleGroup.id ?? /* v8 ignore start -- @preserve */ ""])) return fallbackExpression;
2249
2701
  const steps = [];
2250
2702
  for (const rule of ruleGroup.rules) {
2251
2703
  if (typeof rule === "string") continue;
@@ -2270,7 +2722,7 @@ const defaultRuleGroupProcessorGremlin = (ruleGroup, options) => {
2270
2722
  const defaultRuleGroupProcessorJSONata = (ruleGroup, options) => {
2271
2723
  const { fields, fallbackExpression, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
2272
2724
  const processRuleGroup = (rg, outermost) => {
2273
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermost ? fallbackExpression : "";
2725
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermost ? fallbackExpression : "";
2274
2726
  const processedRules = [];
2275
2727
  let precedingCombinator = "";
2276
2728
  let firstRule = true;
@@ -2326,7 +2778,7 @@ const defaultRuleGroupProcessorJsonLogic = (ruleGroup, options) => {
2326
2778
  const { fields, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
2327
2779
  const query = convertFromIC(ruleGroup);
2328
2780
  const processRuleGroup = (rg, _outermost) => {
2329
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return false;
2781
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return false;
2330
2782
  const processedRules = rg.rules.map((rule) => {
2331
2783
  if (isRuleGroup(rule)) return processRuleGroup(rule);
2332
2784
  const [validationResult, fieldValidator] = validateRule(rule);
@@ -2354,7 +2806,7 @@ const defaultRuleGroupProcessorJsonLogic = (ruleGroup, options) => {
2354
2806
  const defaultRuleGroupProcessorLDAP = (ruleGroup, options) => {
2355
2807
  const { fields, fallbackExpression, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
2356
2808
  const processRuleGroup = (rg, outermost) => {
2357
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermost ? fallbackExpression : "";
2809
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermost ? fallbackExpression : "";
2358
2810
  const rules = rg.rules.map((rule) => {
2359
2811
  if (isRuleGroup(rule)) return processRuleGroup(rule);
2360
2812
  const [validationResult, fieldValidator] = validateRule(rule);
@@ -2387,7 +2839,7 @@ const isBracketed = (str) => str.startsWith("{") && str.endsWith("}");
2387
2839
  const defaultRuleGroupProcessorMongoDB = (ruleGroup, options, meta) => {
2388
2840
  const { fields, fallbackExpression, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
2389
2841
  const processRuleGroup = (rg, outermost) => {
2390
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermost ? fallbackExpression : "";
2842
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermost ? fallbackExpression : "";
2391
2843
  const combinator = `"$${lc(rg.combinator)}"`;
2392
2844
  let hasChildRules = false;
2393
2845
  const expressions = rg.rules.map((rule) => {
@@ -2424,7 +2876,7 @@ const defaultRuleGroupProcessorMongoDB = (ruleGroup, options, meta) => {
2424
2876
  const defaultRuleGroupProcessorNL = (ruleGroup, options) => {
2425
2877
  const { fields, fallbackExpression, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, translations, validateRule, validationMap } = options;
2426
2878
  const processRuleGroup = (rg, outermostOrLonelyInGroup) => {
2427
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermostOrLonelyInGroup ? fallbackExpression : "";
2879
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermostOrLonelyInGroup ? fallbackExpression : "";
2428
2880
  const rg2 = isRuleGroupTypeIC(rg) && rg.rules.some((r) => typeof r === "string" && lc(r) === "xor") ? convertFromIC(rg) : rg;
2429
2881
  const processedRules = [];
2430
2882
  let precedingCombinator = "";
@@ -2435,7 +2887,8 @@ const defaultRuleGroupProcessorNL = (ruleGroup, options) => {
2435
2887
  continue;
2436
2888
  }
2437
2889
  if (isRuleGroup(rule)) {
2438
- const processedGroup = processRuleGroup(rule, rg2.rules.length === 1 && !(rg2.not || /^xor$/i.test(rg2.combinator ?? "")));
2890
+ const processedGroup = processRuleGroup(rule, rg2.rules.length === 1 && !(rg2.not || /^xor$/i.test(rg2.combinator ??
2891
+ /* v8 ignore start -- @preserve */ "")));
2439
2892
  // v8 ignore else
2440
2893
  if (processedGroup) {
2441
2894
  if (!firstRule && precedingCombinator) {
@@ -2522,7 +2975,7 @@ const defaultRuleGroupProcessorParameterized = (ruleGroup, options) => {
2522
2975
  return sql;
2523
2976
  };
2524
2977
  const processRuleGroup = (rg, outermostOrLonelyInGroup) => {
2525
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermostOrLonelyInGroup ? fallbackExpression : "";
2978
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermostOrLonelyInGroup ? fallbackExpression : "";
2526
2979
  const processedRules = [];
2527
2980
  let precedingCombinator = "";
2528
2981
  let firstRule = true;
@@ -2582,7 +3035,7 @@ const prismaFallback = {};
2582
3035
  const defaultRuleGroupProcessorPrisma = (ruleGroup, options) => {
2583
3036
  const { fields, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
2584
3037
  const processRuleGroup = (rg, outermost) => {
2585
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermost ? prismaFallback : void 0;
3038
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermost ? prismaFallback : void 0;
2586
3039
  const combinator = rg.combinator.toUpperCase();
2587
3040
  let hasChildRules = false;
2588
3041
  const expressions = rg.rules.map((rule) => {
@@ -2621,7 +3074,7 @@ const defaultRuleGroupProcessorSequelize = (ruleGroup, options) => {
2621
3074
  const { sequelizeOperators: Op } = context;
2622
3075
  if (!Op) return void 0;
2623
3076
  const processRuleGroup = (rg, _outermost) => {
2624
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return;
3077
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return;
2625
3078
  const combinator = rg.combinator.toUpperCase();
2626
3079
  let hasChildRules = false;
2627
3080
  const expressions = rg.rules.map((rule) => {
@@ -2660,7 +3113,7 @@ const defaultRuleGroupProcessorSequelize = (ruleGroup, options) => {
2660
3113
  const defaultRuleGroupProcessorSPARQL = (ruleGroup, options) => {
2661
3114
  const { fields, fallbackExpression, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
2662
3115
  const processRuleGroup = (rg, outermost) => {
2663
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermost ? fallbackExpression : "";
3116
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermost ? fallbackExpression : "";
2664
3117
  const processedRules = [];
2665
3118
  let precedingCombinator = "";
2666
3119
  let firstRule = true;
@@ -2715,7 +3168,7 @@ const defaultRuleGroupProcessorSPARQL = (ruleGroup, options) => {
2715
3168
  const defaultRuleGroupProcessorSQL = (ruleGroup, options) => {
2716
3169
  const { fields, fallbackExpression, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
2717
3170
  const processRuleGroup = (rg, outermostOrLonelyInGroup) => {
2718
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return outermostOrLonelyInGroup ? fallbackExpression : "";
3171
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return outermostOrLonelyInGroup ? fallbackExpression : "";
2719
3172
  const processedRules = [];
2720
3173
  let precedingCombinator = "";
2721
3174
  let firstRule = true;
@@ -2783,7 +3236,7 @@ const defaultRuleGroupProcessorTanStackDB = (ruleGroup, options) => (refs) => {
2783
3236
  /* v8 ignore next -- @preserve */
2784
3237
  if (refKeys.length === 0) return fallback;
2785
3238
  const processRuleGroup = (rg) => {
2786
- if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? ""])) return;
3239
+ if (!isRuleOrGroupValid(rg, validationMap[rg.id ?? /* v8 ignore start -- @preserve */ ""])) return;
2787
3240
  const processedRules = rg.rules.map((rule) => {
2788
3241
  if (isRuleGroup(rule)) return processRuleGroup(rule);
2789
3242
  const [validationResult, fieldValidator] = validateRule(rule);
@@ -3186,7 +3639,7 @@ const defaultRuleProcessorCypher = (rule, opts = {}) => {
3186
3639
  * @group Export
3187
3640
  */
3188
3641
  const defaultRuleProcessorDrizzle = (rule, _options) => {
3189
- const opts = _options ?? ( /* v8 ignore start -- @preserve */ {});
3642
+ const opts = _options ?? /* v8 ignore start -- @preserve */ {};
3190
3643
  // v8 ignore next
3191
3644
  const { parseNumbers, preserveValueOrder, context = {} } = opts;
3192
3645
  const { columns, drizzleOperators, useRawFields } = context;
@@ -3207,16 +3660,19 @@ const defaultRuleProcessorDrizzle = (rule, _options) => {
3207
3660
  else if (matchEval) {
3208
3661
  if (opts.preset !== "postgresql") return void 0;
3209
3662
  const { mode, threshold } = matchEval;
3210
- const arrayElementAlias = "elem_alias";
3211
- const nestedArrayFilter = defaultRuleGroupProcessorDrizzle(transformQuery(rule.value, { ruleProcessor: (r) => ({
3663
+ const subqueryDepth = opts.subqueryDepth ?? 0;
3664
+ const arrayElementAlias = getSubqueryElementAlias(subqueryDepth);
3665
+ const sqlQuery = transformQuery(rule.value, { ruleProcessor: (r) => ({
3212
3666
  ...r,
3213
3667
  field: arrayElementAlias
3214
- }) }), {
3668
+ }) });
3669
+ const nestedArrayFilter = defaultRuleGroupProcessorDrizzle(sqlQuery, {
3215
3670
  ...opts,
3216
3671
  context: {
3217
3672
  ...opts.context,
3218
3673
  useRawFields: true
3219
- }
3674
+ },
3675
+ subqueryDepth: subqueryDepth + 1
3220
3676
  });
3221
3677
  switch (mode) {
3222
3678
  case "all": return sql`(select count(*) from unnest(${column}) as ${sql.raw(arrayElementAlias)} where ${nestedArrayFilter({}, drizzleOperators)}) = array_length(${column}, 1)`;
@@ -3749,7 +4205,7 @@ const defaultValueProcessorNL = (rule, opts = {}) => {
3749
4205
  const { escapeQuotes, fields, parseNumbers, quoteFieldNamesWith, quoteValuesWith, fieldIdentifierSeparator, translations } = opts;
3750
4206
  const valueIsField = rule.valueSource === "field";
3751
4207
  const operatorLowerCase = lc(rule.operator);
3752
- const quoteChar = quoteValuesWith || "'";
4208
+ const quoteChar = quoteValuesWith || /* v8 ignore start -- @preserve */ "'";
3753
4209
  const quoteValue = (v) => `${quoteChar}${v}${quoteChar}`;
3754
4210
  const escapeValue = (v) => escapeStringValueQuotes(v, quoteChar, escapeQuotes);
3755
4211
  const wrapAndEscape = (v) => quoteValue(escapeValue(v));
@@ -3757,7 +4213,7 @@ const defaultValueProcessorNL = (rule, opts = {}) => {
3757
4213
  quoteFieldNamesWith,
3758
4214
  fieldIdentifierSeparator
3759
4215
  });
3760
- const t = translations ?? ( /* v8 ignore start -- @preserve */ {});
4216
+ const t = translations ?? /* v8 ignore start -- @preserve */ {};
3761
4217
  const orTL = t.or ?? "or";
3762
4218
  const trueTL = t.true ?? "true";
3763
4219
  const falseTL = t.false ?? "false";
@@ -3769,7 +4225,8 @@ const defaultValueProcessorNL = (rule, opts = {}) => {
3769
4225
  case "between":
3770
4226
  case "notbetween": {
3771
4227
  if (!valueIsField) return defaultValueProcessorByRule(rule, opts);
3772
- const valueAsArray = toArray(rule.value, { retainEmptyStrings: true }).slice(0, 2).map((v) => wrapFieldName(getOption(fields ?? [], v)?.label ?? v));
4228
+ const valueAsArray = toArray(rule.value, { retainEmptyStrings: true }).slice(0, 2).map((v) => wrapFieldName(getOption(fields ??
4229
+ /* v8 ignore start -- @preserve */ [], v)?.label ?? v));
3773
4230
  if (valueAsArray.length < 2 || !isValidValue(valueAsArray[0]) || !isValidValue(valueAsArray[1])) return "";
3774
4231
  return defaultValueProcessorByRule({
3775
4232
  ...rule,
@@ -3780,14 +4237,16 @@ const defaultValueProcessorNL = (rule, opts = {}) => {
3780
4237
  case "notin": {
3781
4238
  const valueAsArray = toArray(rule.value);
3782
4239
  if (valueAsArray.length === 0) return "";
3783
- const valStringArray = valueAsArray.map((v) => valueIsField ? wrapFieldName(getOption(fields ?? [], v)?.label ?? v) : shouldRenderAsNumber(v, parseNumbers) ? `${trimIfString(v)}` : `${wrapAndEscape(v)}`);
4240
+ const valStringArray = valueAsArray.map((v) => valueIsField ? wrapFieldName(getOption(fields ??
4241
+ /* v8 ignore start -- @preserve */ [], v)?.label ?? v) : shouldRenderAsNumber(v, parseNumbers) ? `${trimIfString(v)}` : `${wrapAndEscape(v)}`);
3784
4242
  if (valStringArray.length === 1) return valStringArray[0];
3785
4243
  const oxfordComma = useOxfordComma && valStringArray.length > 2 ? "," : "";
3786
4244
  return `(${`${valStringArray.slice(0, -1).join(listSep)}${oxfordComma} ${orTL} ${valStringArray.at(-1)}`})`;
3787
4245
  }
3788
4246
  }
3789
4247
  if (typeof rule.value === "boolean") return rule.value ? trueTL : falseTL;
3790
- return valueIsField ? wrapFieldName(getOption(fields ?? [], rule.value)?.label ?? rule.value) : shouldRenderAsNumber(rule.value, parseNumbers) ? `${trimIfString(rule.value)}` : `${wrapAndEscape(rule.value)}`;
4248
+ return valueIsField ? wrapFieldName(getOption(fields ??
4249
+ /* v8 ignore start -- @preserve */ [], rule.value)?.label ?? rule.value) : shouldRenderAsNumber(rule.value, parseNumbers) ? `${trimIfString(rule.value)}` : `${wrapAndEscape(rule.value)}`;
3791
4250
  };
3792
4251
  //#endregion
3793
4252
  //#region src/utils/formatQuery/defaultRuleProcessorNL.ts
@@ -3839,7 +4298,7 @@ const defaultOperatorProcessorNL = (rule, opts = {}) => {
3839
4298
  name: field,
3840
4299
  value: field,
3841
4300
  label: field
3842
- } }) ?? []), normalizedOperator) ?? {
4301
+ } }) ?? /* v8 ignore start -- @preserve */ []), normalizedOperator) ?? {
3843
4302
  name: normalizedOperator,
3844
4303
  value: normalizedOperator,
3845
4304
  label: normalizedOperator
@@ -3855,7 +4314,7 @@ const defaultOperatorProcessorNL = (rule, opts = {}) => {
3855
4314
  const defaultRuleProcessorNL = (rule, opts) => {
3856
4315
  const { field, operator } = rule;
3857
4316
  // v8 ignore next
3858
- const { fieldData, quoteFieldNamesWith = ["", ""], fieldIdentifierSeparator = "", quoteValuesWith = `'`, operatorProcessor = defaultOperatorProcessorNL, valueProcessor = defaultValueProcessorNL, concatOperator = "||", wordOrder = "SVO" } = opts ?? ( /* v8 ignore start -- @preserve */ {});
4317
+ const { fieldData, quoteFieldNamesWith = ["", ""], fieldIdentifierSeparator = "", quoteValuesWith = `'`, operatorProcessor = defaultOperatorProcessorNL, valueProcessor = defaultValueProcessorNL, concatOperator = "||", wordOrder = "SVO" } = opts ?? /* v8 ignore start -- @preserve */ {};
3859
4318
  const processedField = getQuotedFieldName(fieldData?.label ?? field, {
3860
4319
  quoteFieldNamesWith,
3861
4320
  fieldIdentifierSeparator
@@ -3930,11 +4389,15 @@ const defaultRuleProcessorSQL = (rule, opts = {}) => {
3930
4389
  else if (matchEval) {
3931
4390
  if (opts?.preset !== "postgresql") return "";
3932
4391
  const { mode, threshold } = matchEval;
3933
- const arrayElementAlias = "elem_alias";
4392
+ const subqueryDepth = opts.subqueryDepth ?? 0;
4393
+ const arrayElementAlias = getSubqueryElementAlias(subqueryDepth);
3934
4394
  const nestedArrayFilter = defaultRuleGroupProcessorSQL(transformQuery(rule.value, { ruleProcessor: (r) => ({
3935
4395
  ...r,
3936
4396
  field: arrayElementAlias
3937
- }) }), opts);
4397
+ }) }), {
4398
+ ...opts,
4399
+ subqueryDepth: subqueryDepth + 1
4400
+ });
3938
4401
  switch (mode) {
3939
4402
  case "all": return `(select count(*) from unnest(${ruleField}) as ${wrapFieldName(arrayElementAlias)} where ${nestedArrayFilter}) = array_length(${ruleField}, 1)`;
3940
4403
  case "none": return `not exists (select 1 from unnest(${ruleField}) as ${wrapFieldName(arrayElementAlias)} where ${nestedArrayFilter})`;
@@ -3992,13 +4455,15 @@ const defaultRuleProcessorParameterized = (rule, opts, meta) => {
3992
4455
  else if (matchEval) {
3993
4456
  if (opts?.preset !== "postgresql") return finalize("");
3994
4457
  const { mode, threshold } = matchEval;
3995
- const arrayElementAlias = "elem_alias";
4458
+ const subqueryDepth = opts.subqueryDepth ?? 0;
4459
+ const arrayElementAlias = getSubqueryElementAlias(subqueryDepth);
3996
4460
  const { sql: nestedSQL, params: nestedParams } = defaultRuleGroupProcessorParameterized(transformQuery(rule.value, { ruleProcessor: (r) => ({
3997
4461
  ...r,
3998
4462
  field: arrayElementAlias
3999
4463
  }) }), {
4000
4464
  ...opts,
4001
- fields: []
4465
+ fields: [],
4466
+ subqueryDepth: subqueryDepth + 1
4002
4467
  });
4003
4468
  if (Array.isArray(nestedParams)) params.push(...nestedParams);
4004
4469
  else Object.assign(paramsNamed, nestedParams);
@@ -4070,7 +4535,7 @@ const defaultRuleProcessorParameterized = (rule, opts, meta) => {
4070
4535
  }
4071
4536
  let paramValue = rule.value;
4072
4537
  if (typeof rule.value === "string") if (shouldRenderAsNumber(rule.value, parseNumbers)) paramValue = parseNumber(rule.value, { parseNumbers });
4073
- else paramValue = /^'.*'$/g.test(value) ? value.replaceAll(/(^'|'$)/g, "") : /* v8 ignore next -- @preserve */ value;
4538
+ else paramValue = /^'.*'$/g.test(value) ? value.replaceAll(/(^'|'$)/g, "") : /* v8 ignore next -- @preserve */ value;
4074
4539
  let paramName = "";
4075
4540
  if (parameterized) params.push(paramValue);
4076
4541
  else {
@@ -4287,7 +4752,7 @@ const defaultRuleProcessorSPARQL = (rule, opts = {}) => {
4287
4752
  * @group Export
4288
4753
  */
4289
4754
  const defaultRuleProcessorTanStackDB = (rule, _options) => {
4290
- const { parseNumbers, preserveValueOrder, context = {} } = _options ?? ( /* v8 ignore start -- @preserve */ {});
4755
+ const { parseNumbers, preserveValueOrder, context = {} } = _options ?? /* v8 ignore start -- @preserve */ {};
4291
4756
  const ops = context.tanStackDbOperators;
4292
4757
  const refs = context._tanstackDbRefs;
4293
4758
  const primaryRef = context._tanstackDbPrimaryRef;
@@ -4523,7 +4988,8 @@ function formatQuery(ruleGroup, optionParam = {}) {
4523
4988
  const ruleProcessor = (typeof ruleProcessor_option === "function" ? ruleProcessor_option : null) ?? (valueProcessorCanActAsRuleProcessor.has(format) && typeof ruleProcessor_option !== "function" && valueProcessor_option ? valueProcessor : null) ?? defaultRuleProcessors[format] ?? defaultRuleProcessorSQL;
4524
4989
  const quoteFieldNamesWith = getQuoteFieldNamesWithArray(quoteFieldNamesWith_option);
4525
4990
  const fields = toFullOptionList(optObj.fields);
4526
- const getOperators = (f, m) => toFullOptionList(getOperators_option(f, m) ?? []);
4991
+ const getOperators = (f, m) => toFullOptionList(getOperators_option(f, m) ??
4992
+ /* v8 ignore start -- @preserve */ []);
4527
4993
  const fallbackExpression = fallbackExpression_option ?? defaultFallbackExpressions[format] ?? defaultFallbackExpressions.sql;
4528
4994
  let validationMap = {};
4529
4995
  // v8 ignore else
@@ -4546,7 +5012,7 @@ function formatQuery(ruleGroup, optionParam = {}) {
4546
5012
  const uniqueFields = toFlatOptionArray(fields);
4547
5013
  for (const f of uniqueFields)
4548
5014
  // v8 ignore else
4549
- if (typeof f.validator === "function") validatorMap[f.value ?? f.name] = f.validator;
5015
+ if (typeof f.validator === "function") validatorMap[f.value ?? /* v8 ignore start -- @preserve */ f.name] = f.validator;
4550
5016
  const validateRule = (rule) => {
4551
5017
  let validationResult;
4552
5018
  let fieldValidator;
@@ -4717,12 +5183,20 @@ const getParentPath = (path) => path.slice(0, -1);
4717
5183
  /**
4718
5184
  * Determines if two paths (each `Path`) are equivalent.
4719
5185
  */
4720
- const pathsAreEqual = (path1, path2) => path1.length === path2.length && path1.every((val, idx) => val === path2[idx]);
5186
+ const pathsAreEqual = (path1, path2) => {
5187
+ if (path1.length !== path2.length) return false;
5188
+ for (let i = 0; i < path1.length; i++) if (path1[i] !== path2[i]) return false;
5189
+ return true;
5190
+ };
4721
5191
  /**
4722
5192
  * Determines if the first path is an ancestor of the second path. The first path must
4723
5193
  * be shorter and exactly match the second path up through the length of the first path.
4724
5194
  */
4725
- const isAncestor = (maybeAncestor, path) => maybeAncestor.length < path.length && new RegExp(`^${maybeAncestor.join("-")}`).test(path.join("-"));
5195
+ const isAncestor = (maybeAncestor, path) => {
5196
+ if (maybeAncestor.length >= path.length) return false;
5197
+ for (let i = 0; i < maybeAncestor.length; i++) if (maybeAncestor[i] !== path[i]) return false;
5198
+ return true;
5199
+ };
4726
5200
  /**
4727
5201
  * Finds the deepest/longest path that two paths have in common.
4728
5202
  */
@@ -4755,11 +5229,38 @@ const pathIsDisabled = (path, query) => {
4755
5229
  }
4756
5230
  return disabled;
4757
5231
  };
5232
+ /**
5233
+ * Determines if the rule or group at the specified path is disabled by `disabledPaths`—the array
5234
+ * form of the `QueryBuilder` `disabled` prop, which disables nodes by position rather than by a
5235
+ * `disabled` property on the node itself. A path is disabled if it appears in `disabledPaths` or
5236
+ * descends from a path that does.
5237
+ *
5238
+ * @group Paths
5239
+ */
5240
+ const pathIsDisabledByPaths = (path, disabledPaths = []) => disabledPaths.some((p) => pathsAreEqual(p, path) || isAncestor(p, path));
5241
+ /**
5242
+ * Builds the {@link PathInfo} for each child of a group at `path`. A child is disabled if its
5243
+ * parent is disabled or if its own path appears in `disabledPaths`.
5244
+ *
5245
+ * @group Paths
5246
+ */
5247
+ const derivePathInfo = (path, childCount, { disabled = false, disabledPaths = [] } = {}) => {
5248
+ const paths = [];
5249
+ for (let i = 0; i < childCount; i++) {
5250
+ const thisPath = [...path, i];
5251
+ paths[i] = {
5252
+ path: thisPath,
5253
+ disabled: disabled || disabledPaths.some((p) => pathsAreEqual(thisPath, p))
5254
+ };
5255
+ }
5256
+ return paths;
5257
+ };
4758
5258
  //#endregion
4759
5259
  //#region src/utils/generateAccessibleDescription.ts
4760
5260
  const generateAccessibleDescription = (params) => pathsAreEqual([], params.path) ? `Query builder` : `Rule group at path ${params.path.join("-")}`;
4761
5261
  //#endregion
4762
5262
  //#region src/utils/generateID.ts
5263
+ /* v8 ignore file -- this is fine */
4763
5264
  const cryptoModule = globalThis.crypto;
4764
5265
  const uuidV4regex = /^[\da-f]{8}-[\da-f]{4}-4[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/i;
4765
5266
  /**
@@ -4808,7 +5309,7 @@ const dummyFD$1 = {
4808
5309
  * `FullOption<MatchMode>[]` of all match modes by default.
4809
5310
  */
4810
5311
  const getMatchModesUtil = (fieldData, getMatchModes) => {
4811
- const fd = fieldData ? toFullOption(fieldData) : /* v8 ignore next -- @preserve */ dummyFD$1;
5312
+ const fd = fieldData ? toFullOption(fieldData) : /* v8 ignore next -- @preserve */ dummyFD$1;
4812
5313
  let matchModes = fd.matchModes ?? false;
4813
5314
  if (!matchModes && getMatchModes) matchModes = getMatchModes(fd.value, { fieldData: fd });
4814
5315
  if (matchModes === true) return defaultMatchModes;
@@ -4826,7 +5327,7 @@ const getFirstOptionsFrom = (opts, r, listsAsArrays) => {
4826
5327
  const firstOption = getFirstOption(opts);
4827
5328
  if (r.operator === "between" || r.operator === "notBetween") {
4828
5329
  const valueAsArray = [firstOption, firstOption];
4829
- return listsAsArrays ? valueAsArray : joinWith(valueAsArray.map((v) => v ?? ""), ",");
5330
+ return listsAsArrays ? valueAsArray : joinWith(valueAsArray.map((v) => v ?? /* v8 ignore start -- @preserve */ ""), ",");
4830
5331
  }
4831
5332
  return firstOption;
4832
5333
  };
@@ -4962,6 +5463,9 @@ const mergeClassnames = (...args) => ({
4962
5463
  removeRule: joinClassnamesByName("removeRule", args),
4963
5464
  notToggle: joinClassnamesByName("notToggle", args),
4964
5465
  shiftActions: joinClassnamesByName("shiftActions", args),
5466
+ undoRedoActions: joinClassnamesByName("undoRedoActions", args),
5467
+ undoAction: joinClassnamesByName("undoAction", args),
5468
+ redoAction: joinClassnamesByName("redoAction", args),
4965
5469
  dragHandle: joinClassnamesByName("dragHandle", args),
4966
5470
  lockRule: joinClassnamesByName("lockRule", args),
4967
5471
  lockGroup: joinClassnamesByName("lockGroup", args),
@@ -4991,6 +5495,58 @@ const mergeClassnames = (...args) => ({
4991
5495
  valueDateTimeRelative: joinClassnamesByName("valueDateTimeRelative", args)
4992
5496
  });
4993
5497
  //#endregion
5498
+ //#region src/utils/optionResolvers.ts
5499
+ /**
5500
+ * Resolves the operator list for a field, applying the same precedence as the `QueryBuilder`
5501
+ * component: the field's own `operators`, then the `getOperators` callback, then the
5502
+ * query-level operator list.
5503
+ *
5504
+ * @group Option Lists
5505
+ */
5506
+ const resolveOperatorList = ({ field, fieldData, getOperators, operators, placeholder, baseOption, autoSelectOption }) => prepareOptionList({
5507
+ optionList: fieldData?.operators ?? getOperators?.(field, { fieldData }) ?? operators,
5508
+ placeholder,
5509
+ baseOption,
5510
+ labelMap: defaultOperatorLabelMap,
5511
+ autoSelectOption
5512
+ }).optionList;
5513
+ /**
5514
+ * Resolves the default operator for a field, applying the same precedence as the `QueryBuilder`
5515
+ * component: the field's own `defaultOperator`, then the `getDefaultOperator` option (a string or
5516
+ * a function), then the first available operator.
5517
+ *
5518
+ * @group Option Lists
5519
+ */
5520
+ const resolveDefaultOperator = ({ field, fieldData, getDefaultOperator, getOperators }) => {
5521
+ if (fieldData?.defaultOperator) return fieldData.defaultOperator;
5522
+ if (getDefaultOperator) return typeof getDefaultOperator === "function" ? getDefaultOperator(field, { fieldData }) : getDefaultOperator;
5523
+ return getFirstOption(getOperators(field, { fieldData })) ?? "";
5524
+ };
5525
+ /**
5526
+ * Resolves the value editor type for a field/operator pair, applying the same precedence as the
5527
+ * `QueryBuilder` component: the field's own `valueEditorType` (a string or a function of the
5528
+ * operator), then the `getValueEditorType` callback, then `"text"`.
5529
+ *
5530
+ * @group Option Lists
5531
+ */
5532
+ const resolveValueEditorType = ({ field, operator, fieldData, getValueEditorType }) => {
5533
+ if (fieldData?.valueEditorType) return typeof fieldData.valueEditorType === "function" ? fieldData.valueEditorType(operator) : fieldData.valueEditorType;
5534
+ return getValueEditorType?.(field, operator, { fieldData }) ?? "text";
5535
+ };
5536
+ /**
5537
+ * Resolves the value option list for a field/operator pair, applying the same precedence as the
5538
+ * `QueryBuilder` component: the field's own `values`, then the `getValues` callback, then an
5539
+ * empty list.
5540
+ *
5541
+ * @group Option Lists
5542
+ */
5543
+ const resolveValueList = ({ field, operator, fieldData, getValues, placeholder, baseOption, autoSelectOption }) => prepareOptionList({
5544
+ optionList: fieldData?.values ?? getValues?.(field, operator, { fieldData }) ?? [],
5545
+ placeholder,
5546
+ baseOption,
5547
+ autoSelectOption
5548
+ }).optionList;
5549
+ //#endregion
4994
5550
  //#region src/utils/preferProp.ts
4995
5551
  const preferPropDefaultTrue = (prop, context) => prop === false ? false : prop ? true : !(context === false);
4996
5552
  const preferPropDefaultFalse = (prop, context) => prop ? true : prop === false ? false : !!context;
@@ -5055,6 +5611,21 @@ const prepareRuleGroup = (queryObject, { idGenerator = generateID } = {}) => {
5055
5611
  * Ensures that a rule or group is valid. See {@link prepareRule} and {@link prepareRuleGroup}.
5056
5612
  */
5057
5613
  const prepareRuleOrGroup = (rg, { idGenerator = generateID } = {}) => isRuleGroup(rg) ? prepareRuleGroup(rg, { idGenerator }) : prepareRule(rg, { idGenerator });
5614
+ /**
5615
+ * Resolves the query a query builder should render from the available sources, in precedence
5616
+ * order: the controlled `query`, then whatever is already in the store, then the uncontrolled
5617
+ * `defaultQuery`, then a freshly created empty group.
5618
+ *
5619
+ * The result is prepared with {@link prepareRuleGroup} unless it already has an `id`, which is
5620
+ * taken to mean it has been prepared before—most often because the caller is passing back the
5621
+ * object it received from `onQueryChange`.
5622
+ *
5623
+ * @group Query Tools
5624
+ */
5625
+ const resolveCandidateQuery = (sources, options) => {
5626
+ const candidateQuery = sources.query ?? sources.storeQuery ?? sources.defaultQuery ?? sources.fallbackQuery;
5627
+ return candidateQuery.id ? candidateQuery : prepareRuleGroup(candidateQuery, options);
5628
+ };
5058
5629
  //#endregion
5059
5630
  //#region src/utils/regenerateIDs.ts
5060
5631
  /**
@@ -5084,6 +5655,28 @@ const regenerateIDs = (subject, { idGenerator = generateID } = {}) => {
5084
5655
  //#endregion
5085
5656
  //#region src/utils/queryTools.ts
5086
5657
  /**
5658
+ * Whether a mutation targeting `pathOrID` is blocked by the given guards, and why.
5659
+ * Returns `null` when the mutation may proceed.
5660
+ *
5661
+ * Exported so that callers which run their own logic before mutating—such as a UI layer that
5662
+ * invokes a confirmation callback—can apply the same rules without duplicating them.
5663
+ *
5664
+ * @group Query Tools
5665
+ */
5666
+ const getGuardAbortReason = (query, pathOrID, guards = {}, { asParent = false } = {}) => {
5667
+ if (guards.queryDisabled) return asParent ? "parent-disabled" : "target-disabled";
5668
+ if (!guards.respectDisabled || pathOrID === void 0) return null;
5669
+ const path = Array.isArray(pathOrID) ? pathOrID : getPathOfID(pathOrID, query);
5670
+ if (path && (pathIsDisabled(path, query) || pathIsDisabledByPaths(path, guards.disabledPaths))) return asParent ? "parent-disabled" : "target-disabled";
5671
+ return null;
5672
+ };
5673
+ /**
5674
+ * Whether adding a group beneath `parentPath` would exceed `maxLevels`.
5675
+ *
5676
+ * @group Query Tools
5677
+ */
5678
+ const exceedsMaxLevels = (parentPath, { maxLevels = Infinity } = {}) => !!parentPath && parentPath.length >= maxLevels;
5679
+ /**
5087
5680
  * Adds a rule or group to a query without mutating the original query.
5088
5681
  *
5089
5682
  * @returns A new query with the rule or group added.
@@ -5099,9 +5692,42 @@ const add = (query, ruleOrGroup, parentPathOrID, options = {}) => produce(query,
5099
5692
  * @group Query Tools
5100
5693
  */
5101
5694
  const addInPlace = (query, ruleOrGroup, parentPathOrID, options = {}) => {
5102
- const { combinators = defaultCombinators, combinatorPreceding, idGenerator = generateID } = options;
5695
+ const { combinators = defaultCombinators, combinatorPreceding, idGenerator = generateID, onAbort } = options;
5103
5696
  const parent = Array.isArray(parentPathOrID) ? findPath(parentPathOrID, query) : findID(parentPathOrID, query);
5104
- if (!parent || !isRuleGroup(parent)) return query;
5697
+ if (!parent) {
5698
+ onAbort?.({
5699
+ reason: "parent-not-found",
5700
+ operation: "add",
5701
+ pathOrID: parentPathOrID
5702
+ });
5703
+ return query;
5704
+ }
5705
+ if (!isRuleGroup(parent)) {
5706
+ onAbort?.({
5707
+ reason: "parent-not-a-group",
5708
+ operation: "add",
5709
+ pathOrID: parentPathOrID
5710
+ });
5711
+ return query;
5712
+ }
5713
+ const parentPath = Array.isArray(parentPathOrID) ? parentPathOrID : getPathOfID(parentPathOrID, query);
5714
+ const addGuardReason = getGuardAbortReason(query, parentPath, options, { asParent: true });
5715
+ if (addGuardReason) {
5716
+ onAbort?.({
5717
+ reason: addGuardReason,
5718
+ operation: "add",
5719
+ pathOrID: parentPathOrID
5720
+ });
5721
+ return query;
5722
+ }
5723
+ if (isRuleGroup(ruleOrGroup) && exceedsMaxLevels(parentPath, options)) {
5724
+ onAbort?.({
5725
+ reason: "max-levels-exceeded",
5726
+ operation: "add",
5727
+ pathOrID: parentPathOrID
5728
+ });
5729
+ return query;
5730
+ }
5105
5731
  if (isRuleGroupTypeIC(parent) && parent.rules.length > 0) {
5106
5732
  const prevCombinator = parent.rules.at(-2);
5107
5733
  parent.rules.push(combinatorPreceding ?? (typeof prevCombinator === "string" ? prevCombinator : getFirstOption(combinators)));
@@ -5122,7 +5748,7 @@ const updatePropRank = {
5122
5748
  value: 4
5123
5749
  };
5124
5750
  /** Stable-sorts `[prop, value]` entries by {@link updatePropRank}. */
5125
- const orderUpdateEntries = (entries) => entries.sort((x, y) => (updatePropRank[x[0]] ?? 3) - (updatePropRank[y[0]] ?? 3));
5751
+ const orderUpdateEntries = (entries) => entries.toSorted((x, y) => (updatePropRank[x[0]] ?? 3) - (updatePropRank[y[0]] ?? 3));
5126
5752
  /**
5127
5753
  * Normalizes the variadic {@link update}/{@link updateInPlace} arguments (single
5128
5754
  * prop+value, parallel arrays, or property map) into a canonically-ordered list
@@ -5185,19 +5811,69 @@ const updateInPlace = ((query, a, b, c, d) => applyUpdatesInPlace(query, a, b, c
5185
5811
  * Updates a single property of a rule or group within a query in place.
5186
5812
  */
5187
5813
  const updateInPlaceSingle = (query, prop, value, pathOrID, options = {}) => {
5188
- const { resetOnFieldChange: _resetOnFieldChange = true, resetOnOperatorChange = false, getRuleDefaultOperator = () => "=", getValueSources = () => ["value"], getRuleDefaultValue = () => "", getMatchModes = () => [] } = options;
5814
+ const { resetOnFieldChange: _resetOnFieldChange = true, resetOnOperatorChange = false, getRuleDefaultOperator = () => "=", getValueSources = () => ["value"], getRuleDefaultValue = () => "", getMatchModes = () => [], onAbort } = options;
5189
5815
  let resetOnFieldChange = _resetOnFieldChange;
5190
5816
  const path = Array.isArray(pathOrID) ? pathOrID : getPathOfID(pathOrID, query);
5191
- if (!path) return query;
5817
+ if (!path) {
5818
+ onAbort?.({
5819
+ reason: "target-not-found",
5820
+ operation: "update",
5821
+ pathOrID
5822
+ });
5823
+ return query;
5824
+ }
5825
+ const updateGuards = prop === "disabled" ? { queryDisabled: options.queryDisabled } : options;
5826
+ const updateGuardReason = getGuardAbortReason(query, path, updateGuards);
5827
+ if (updateGuardReason) {
5828
+ onAbort?.({
5829
+ reason: updateGuardReason,
5830
+ operation: "update",
5831
+ pathOrID
5832
+ });
5833
+ return query;
5834
+ }
5192
5835
  if (prop === "combinator" && !isRuleGroupType(query)) {
5193
- const parentRules = findPath(getParentPath(path), query).rules;
5194
- if (path.at(-1) % 2 === 1) parentRules[path.at(-1)] = value;
5836
+ const parent = findPath(getParentPath(path), query);
5837
+ if (!parent || !isRuleGroup(parent)) {
5838
+ onAbort?.({
5839
+ reason: "target-not-found",
5840
+ operation: "update",
5841
+ pathOrID
5842
+ });
5843
+ return query;
5844
+ }
5845
+ const parentRules = parent.rules;
5846
+ if (path.at(-1) % 2 === 1) if (parentRules[path.at(-1)] === value) onAbort?.({
5847
+ reason: "no-change",
5848
+ operation: "update",
5849
+ pathOrID
5850
+ });
5851
+ else parentRules[path.at(-1)] = value;
5852
+ else onAbort?.({
5853
+ reason: "not-a-combinator-slot",
5854
+ operation: "update",
5855
+ pathOrID
5856
+ });
5195
5857
  return query;
5196
5858
  }
5197
5859
  const ruleOrGroup = findPath(path, query);
5198
- if (!ruleOrGroup) return query;
5860
+ if (!ruleOrGroup) {
5861
+ onAbort?.({
5862
+ reason: "target-not-found",
5863
+ operation: "update",
5864
+ pathOrID
5865
+ });
5866
+ return query;
5867
+ }
5199
5868
  const isGroup = isRuleGroup(ruleOrGroup);
5200
- if (ruleOrGroup[prop] === value) return query;
5869
+ if (ruleOrGroup[prop] === value) {
5870
+ onAbort?.({
5871
+ reason: "no-change",
5872
+ operation: "update",
5873
+ pathOrID
5874
+ });
5875
+ return query;
5876
+ }
5201
5877
  if (prop !== "valueSource") ruleOrGroup[prop] = value;
5202
5878
  if (isGroup) return query;
5203
5879
  let resetValueSource = false;
@@ -5243,7 +5919,7 @@ const updateInPlaceSingle = (query, prop, value, pathOrID, options = {}) => {
5243
5919
  *
5244
5920
  * @group Query Tools
5245
5921
  */
5246
- const remove = (query, pathOrID) => produce(query, (q) => removeInPlace(q, pathOrID));
5922
+ const remove = (query, pathOrID, options = {}) => produce(query, (q) => removeInPlace(q, pathOrID, options));
5247
5923
  /**
5248
5924
  * Removes a rule or group from a query in place.
5249
5925
  *
@@ -5251,12 +5927,45 @@ const remove = (query, pathOrID) => produce(query, (q) => removeInPlace(q, pathO
5251
5927
  *
5252
5928
  * @group Query Tools
5253
5929
  */
5254
- const removeInPlace = (query, pathOrID) => {
5930
+ const removeInPlace = (query, pathOrID, options = {}) => {
5931
+ const { onAbort } = options;
5255
5932
  const path = Array.isArray(pathOrID) ? pathOrID : getPathOfID(pathOrID, query);
5256
- if (!path || path.length === 0 || !isRuleGroupType(query) && !findPath(path, query)) return query;
5933
+ if (!path) {
5934
+ onAbort?.({
5935
+ reason: "target-not-found",
5936
+ operation: "remove",
5937
+ pathOrID
5938
+ });
5939
+ return query;
5940
+ }
5941
+ if (path.length === 0) {
5942
+ onAbort?.({
5943
+ reason: "root-not-allowed",
5944
+ operation: "remove",
5945
+ pathOrID
5946
+ });
5947
+ return query;
5948
+ }
5949
+ const removeGuardReason = getGuardAbortReason(query, path, options);
5950
+ if (removeGuardReason) {
5951
+ onAbort?.({
5952
+ reason: removeGuardReason,
5953
+ operation: "remove",
5954
+ pathOrID
5955
+ });
5956
+ return query;
5957
+ }
5958
+ if (!findPath(path, query)) {
5959
+ onAbort?.({
5960
+ reason: "target-not-found",
5961
+ operation: "remove",
5962
+ pathOrID
5963
+ });
5964
+ return query;
5965
+ }
5257
5966
  const index = path.at(-1);
5258
5967
  const parent = findPath(getParentPath(path), query);
5259
- if (parent && isRuleGroup(parent)) if (!isRuleGroupType(parent) && parent.rules.length > 1) {
5968
+ if (!isRuleGroupType(parent) && parent.rules.length > 1) {
5260
5969
  const idxStartDelete = index === 0 ? 0 : index - 1;
5261
5970
  parent.rules.splice(idxStartDelete, 2);
5262
5971
  } else parent.rules.splice(index, 1);
@@ -5281,7 +5990,8 @@ const getNextPath = (query, currentPath, newPathOrShiftDirection) => {
5281
5990
  return [...getParentPath(parentPath), parentPath.at(-1) + 1];
5282
5991
  } else {
5283
5992
  const evaluationPath = [...getParentPath(currentPath), currentPath.at(-1) + (ic ? 2 : 1)];
5284
- if (isRuleGroup(findPath(evaluationPath, query))) return [...evaluationPath, 0];
5993
+ const entityToEvaluate = findPath(evaluationPath, query);
5994
+ if (isRuleGroup(entityToEvaluate)) return [...evaluationPath, 0];
5285
5995
  else return [...getParentPath(currentPath), currentPath.at(-1) + (ic ? 3 : 2)];
5286
5996
  }
5287
5997
  return currentPath;
@@ -5304,13 +6014,67 @@ const move = (query, oldPathOrID, newPath, options = {}) => produce(query, (q) =
5304
6014
  * @group Query Tools
5305
6015
  */
5306
6016
  const moveInPlace = (query, oldPathOrID, newPath, options = {}) => {
5307
- const { clone = false, combinators = defaultCombinators, idGenerator = generateID } = options;
6017
+ const { clone = false, combinators = defaultCombinators, idGenerator = generateID, onAbort } = options;
5308
6018
  const oldPath = Array.isArray(oldPathOrID) ? oldPathOrID : getPathOfID(oldPathOrID, query);
5309
- if (!oldPath) return query;
5310
- const nextPath = getNextPath(query, oldPath, newPath);
5311
- if (oldPath.length === 0 || pathsAreEqual(oldPath, nextPath) || !findPath(getParentPath(nextPath), query)) return query;
6019
+ if (!oldPath) {
6020
+ onAbort?.({
6021
+ reason: "target-not-found",
6022
+ operation: "move",
6023
+ pathOrID: oldPathOrID
6024
+ });
6025
+ return query;
6026
+ }
5312
6027
  const ruleOrGroupOriginal = findPath(oldPath, query);
5313
- if (!ruleOrGroupOriginal) return query;
6028
+ if (!ruleOrGroupOriginal) {
6029
+ onAbort?.({
6030
+ reason: "target-not-found",
6031
+ operation: "move",
6032
+ pathOrID: oldPathOrID
6033
+ });
6034
+ return query;
6035
+ }
6036
+ const nextPath = getNextPath(query, oldPath, newPath);
6037
+ if (oldPath.length === 0) {
6038
+ onAbort?.({
6039
+ reason: "root-not-allowed",
6040
+ operation: "move",
6041
+ pathOrID: oldPathOrID
6042
+ });
6043
+ return query;
6044
+ }
6045
+ const moveGuardReason = getGuardAbortReason(query, oldPath, options);
6046
+ if (moveGuardReason) {
6047
+ onAbort?.({
6048
+ reason: moveGuardReason,
6049
+ operation: "move",
6050
+ pathOrID: oldPathOrID
6051
+ });
6052
+ return query;
6053
+ }
6054
+ if (pathsAreEqual(oldPath, nextPath)) {
6055
+ onAbort?.({
6056
+ reason: "same-location",
6057
+ operation: "move",
6058
+ pathOrID: oldPathOrID
6059
+ });
6060
+ return query;
6061
+ }
6062
+ if (!findPath(getParentPath(nextPath), query)) {
6063
+ onAbort?.({
6064
+ reason: "destination-not-found",
6065
+ operation: "move",
6066
+ pathOrID: newPath
6067
+ });
6068
+ return query;
6069
+ }
6070
+ if (!clone && isAncestor(oldPath, nextPath)) {
6071
+ onAbort?.({
6072
+ reason: "destination-not-found",
6073
+ operation: "move",
6074
+ pathOrID: newPath
6075
+ });
6076
+ return query;
6077
+ }
5314
6078
  const ruleOrGroup = clone ? regenerateIDs(isDraft(ruleOrGroupOriginal) ? current(ruleOrGroupOriginal) : ruleOrGroupOriginal, { idGenerator }) : ruleOrGroupOriginal;
5315
6079
  const independentCombinators = isRuleGroupTypeIC(query);
5316
6080
  const parentOfRuleToRemove = findPath(getParentPath(oldPath), query);
@@ -5323,20 +6087,23 @@ const moveInPlace = (query, oldPathOrID, newPath, options = {}) => {
5323
6087
  parentOfRuleToRemove.rules.splice(idxStartDelete, deleteLength);
5324
6088
  }
5325
6089
  const newNewPath = [...nextPath];
6090
+ if (independentCombinators && newNewPath.at(-1) % 2 === 1) newNewPath[newNewPath.length - 1] += 1;
5326
6091
  const commonAncestorPath = getCommonAncestorPath(oldPath, nextPath);
5327
- if (!clone && oldPath.length === commonAncestorPath.length + 1 && nextPath[commonAncestorPath.length] > oldPath[commonAncestorPath.length]) newNewPath[commonAncestorPath.length] -= independentCombinators ? 2 : 1;
5328
- const parentToInsertInto = findPath(getParentPath(newNewPath), query);
6092
+ if (!clone && oldPath.length === commonAncestorPath.length + 1 && newNewPath[commonAncestorPath.length] > oldPath[commonAncestorPath.length]) newNewPath[commonAncestorPath.length] -= independentCombinators ? 2 : 1;
6093
+ const newNewParentPath = getParentPath(newNewPath);
6094
+ const parentToInsertInto = findPath(newNewParentPath, query);
5329
6095
  const newIndex = newNewPath.at(-1);
6096
+ const spliceIndex = independentCombinators && newIndex > 0 ? newIndex - 1 : newIndex;
5330
6097
  /**
5331
6098
  * This function 1) glosses over the need for type assertions to splice directly
5332
6099
  * into `parentToInsertInto.rules`, and 2) shortens the actual insertion code.
5333
6100
  */
5334
- const insertRuleOrGroup = (...args) => parentToInsertInto.rules.splice(newIndex, 0, ...args);
6101
+ const insertRuleOrGroup = (...args) => parentToInsertInto.rules.splice(spliceIndex, 0, ...args);
5335
6102
  if (parentToInsertInto.rules.length === 0 || !independentCombinators) insertRuleOrGroup(ruleOrGroup);
5336
6103
  else if (newIndex === 0) if (ruleToRemoveIndex === 0 && oldNextCombinator) insertRuleOrGroup(ruleOrGroup, oldNextCombinator);
5337
6104
  else insertRuleOrGroup(ruleOrGroup, parentToInsertInto.rules[1] ?? oldPrevCombinator ?? getFirstOption(combinators));
5338
6105
  else if (oldPrevCombinator) insertRuleOrGroup(oldPrevCombinator, ruleOrGroup);
5339
- else insertRuleOrGroup(parentToInsertInto.rules[newIndex - 2] ?? oldNextCombinator ?? getFirstOption(combinators), ruleOrGroup);
6106
+ else insertRuleOrGroup(parentToInsertInto.rules[spliceIndex - 2] ?? oldNextCombinator ?? getFirstOption(combinators), ruleOrGroup);
5340
6107
  return query;
5341
6108
  };
5342
6109
  /**
@@ -5355,9 +6122,42 @@ const insert = (query, ruleOrGroup, path, options = {}) => produce(query, (q) =>
5355
6122
  * @group Query Tools
5356
6123
  */
5357
6124
  const insertInPlace = (query, ruleOrGroup, path, options = {}) => {
5358
- const { combinators = defaultCombinators, combinatorPreceding, combinatorSucceeding, idGenerator = generateID, replace = false } = options;
6125
+ const { combinators = defaultCombinators, combinatorPreceding, combinatorSucceeding, idGenerator = generateID, replace = false, onAbort } = options;
5359
6126
  const parentToInsertInto = findPath(getParentPath(path), query);
5360
- if (!parentToInsertInto || !isRuleGroup(parentToInsertInto)) return query;
6127
+ if (!parentToInsertInto) {
6128
+ onAbort?.({
6129
+ reason: "parent-not-found",
6130
+ operation: "insert",
6131
+ pathOrID: path
6132
+ });
6133
+ return query;
6134
+ }
6135
+ if (!isRuleGroup(parentToInsertInto)) {
6136
+ onAbort?.({
6137
+ reason: "parent-not-a-group",
6138
+ operation: "insert",
6139
+ pathOrID: path
6140
+ });
6141
+ return query;
6142
+ }
6143
+ const parentPath = getParentPath(path);
6144
+ const insertGuardReason = getGuardAbortReason(query, parentPath, options, { asParent: true });
6145
+ if (insertGuardReason) {
6146
+ onAbort?.({
6147
+ reason: insertGuardReason,
6148
+ operation: "insert",
6149
+ pathOrID: path
6150
+ });
6151
+ return query;
6152
+ }
6153
+ if (isRuleGroup(ruleOrGroup) && exceedsMaxLevels(parentPath, options)) {
6154
+ onAbort?.({
6155
+ reason: "max-levels-exceeded",
6156
+ operation: "insert",
6157
+ pathOrID: path
6158
+ });
6159
+ return query;
6160
+ }
5361
6161
  const rorg = regenerateIDs(ruleOrGroup, { idGenerator });
5362
6162
  const independentCombinators = isRuleGroupTypeIC(query);
5363
6163
  const newIndex = path.at(-1);
@@ -5397,15 +6197,85 @@ const group = (query, sourcePathOrID, targetPathOrID, options = {}) => produce(q
5397
6197
  * @group Query Tools
5398
6198
  */
5399
6199
  const groupInPlace = (query, sourcePathOrID, targetPathOrID, options = {}) => {
5400
- const { clone = false, combinators = defaultCombinators, idGenerator = generateID } = options;
6200
+ const { clone = false, combinators = defaultCombinators, idGenerator = generateID, onAbort } = options;
5401
6201
  const sourcePath = Array.isArray(sourcePathOrID) ? sourcePathOrID : getPathOfID(sourcePathOrID, query);
5402
6202
  const targetPath = Array.isArray(targetPathOrID) ? targetPathOrID : getPathOfID(targetPathOrID, query);
5403
- if (!sourcePath || !targetPath) return query;
6203
+ if (!sourcePath) {
6204
+ onAbort?.({
6205
+ reason: "target-not-found",
6206
+ operation: "group",
6207
+ pathOrID: sourcePathOrID
6208
+ });
6209
+ return query;
6210
+ }
6211
+ if (!targetPath) {
6212
+ onAbort?.({
6213
+ reason: "target-not-found",
6214
+ operation: "group",
6215
+ pathOrID: targetPathOrID
6216
+ });
6217
+ return query;
6218
+ }
5404
6219
  const nextPath = getNextPath(query, sourcePath, targetPath);
5405
- if (sourcePath.length === 0 || pathsAreEqual(sourcePath, nextPath) || !findPath(getParentPath(nextPath), query)) return query;
6220
+ if (sourcePath.length === 0) {
6221
+ onAbort?.({
6222
+ reason: "root-not-allowed",
6223
+ operation: "group",
6224
+ pathOrID: sourcePathOrID
6225
+ });
6226
+ return query;
6227
+ }
6228
+ const groupGuardReason = getGuardAbortReason(query, sourcePath, options);
6229
+ if (groupGuardReason) {
6230
+ onAbort?.({
6231
+ reason: groupGuardReason,
6232
+ operation: "group",
6233
+ pathOrID: sourcePathOrID
6234
+ });
6235
+ return query;
6236
+ }
6237
+ if (pathsAreEqual(sourcePath, nextPath)) {
6238
+ onAbort?.({
6239
+ reason: "same-location",
6240
+ operation: "group",
6241
+ pathOrID: sourcePathOrID
6242
+ });
6243
+ return query;
6244
+ }
6245
+ if (!findPath(getParentPath(nextPath), query)) {
6246
+ onAbort?.({
6247
+ reason: "destination-not-found",
6248
+ operation: "group",
6249
+ pathOrID: targetPathOrID
6250
+ });
6251
+ return query;
6252
+ }
6253
+ if (!clone && isAncestor(sourcePath, nextPath)) {
6254
+ onAbort?.({
6255
+ reason: "destination-not-found",
6256
+ operation: "group",
6257
+ pathOrID: targetPathOrID
6258
+ });
6259
+ return query;
6260
+ }
5406
6261
  const sourceRuleOrGroupOriginal = findPath(sourcePath, query);
5407
6262
  const targetRuleOrGroup = findPath(targetPath, query);
5408
- if (!sourceRuleOrGroupOriginal || !targetRuleOrGroup) return query;
6263
+ if (!sourceRuleOrGroupOriginal) {
6264
+ onAbort?.({
6265
+ reason: "target-not-found",
6266
+ operation: "group",
6267
+ pathOrID: sourcePathOrID
6268
+ });
6269
+ return query;
6270
+ }
6271
+ if (!targetRuleOrGroup) {
6272
+ onAbort?.({
6273
+ reason: "target-not-found",
6274
+ operation: "group",
6275
+ pathOrID: targetPathOrID
6276
+ });
6277
+ return query;
6278
+ }
5409
6279
  const sourceRuleOrGroup = clone ? regenerateIDs(isDraft(sourceRuleOrGroupOriginal) ? current(sourceRuleOrGroupOriginal) : sourceRuleOrGroupOriginal, { idGenerator }) : sourceRuleOrGroupOriginal;
5410
6280
  const independentCombinators = isRuleGroupTypeIC(query);
5411
6281
  const parentOfRuleToRemove = findPath(getParentPath(sourcePath), query);
@@ -5418,7 +6288,8 @@ const groupInPlace = (query, sourcePathOrID, targetPathOrID, options = {}) => {
5418
6288
  const newNewPath = [...nextPath];
5419
6289
  const commonAncestorPath = getCommonAncestorPath(sourcePath, nextPath);
5420
6290
  if (!clone && sourcePath.length === commonAncestorPath.length + 1 && nextPath[commonAncestorPath.length] > sourcePath[commonAncestorPath.length]) newNewPath[commonAncestorPath.length] -= independentCombinators ? 2 : 1;
5421
- const parentOfTargetPath = findPath(getParentPath(newNewPath), query);
6291
+ const newNewParentPath = getParentPath(newNewPath);
6292
+ const parentOfTargetPath = findPath(newNewParentPath, query);
5422
6293
  const targetPathIndex = newNewPath.at(-1);
5423
6294
  parentOfTargetPath.rules.splice(targetPathIndex, 1, prepareRuleOrGroup(independentCombinators ? { rules: [
5424
6295
  targetRuleOrGroup,
@@ -5431,6 +6302,1248 @@ const groupInPlace = (query, sourcePathOrID, targetPathOrID, options = {}) => {
5431
6302
  return query;
5432
6303
  };
5433
6304
  //#endregion
5434
- 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, 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, substringOperators, toArray, toFlatOptionArray, toFullOption, toFullOptionList, toFullOptionMap, transformQuery, trimIfString, uniqByIdentifier, uniqByName, uniqOptGroups, uniqOptList, update, updateInPlace, uuidV4regex, withParamPrefix, wrapLikeFragment };
6305
+ //#region src/utils/queryActions.ts
6306
+ /**
6307
+ * Builds the query builder's six mutation handlers as pure functions of the current query.
6308
+ *
6309
+ * This is the framework-agnostic core of the action handlers in `useQueryBuilderSchema`. It owns
6310
+ * the policy that surrounds the query tools—disabled gating, the confirmation callback protocol,
6311
+ * `maxLevels`, and debug logging—so an implementation only has to supply its own storage: read
6312
+ * the current query, call the action, and apply a non-`undefined` result.
6313
+ *
6314
+ * @group Query Tools
6315
+ */
6316
+ const createQueryActions = (config = {}) => {
6317
+ 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;
6318
+ const log = (payload) => onLog?.({
6319
+ qbId,
6320
+ ...payload
6321
+ });
6322
+ const guards = {
6323
+ respectDisabled,
6324
+ queryDisabled,
6325
+ disabledPaths
6326
+ };
6327
+ /** Whether the mutation is blocked, using the same rules the query tools apply internally. */
6328
+ const blocked = (query, path, asParent = false) => !!getGuardAbortReason(query, path, guards, { asParent });
6329
+ return {
6330
+ addRule: (query, rule, parentPath, context) => {
6331
+ if (blocked(query, parentPath, true)) {
6332
+ log({
6333
+ type: LogType.parentPathDisabled,
6334
+ rule,
6335
+ parentPath,
6336
+ query
6337
+ });
6338
+ return;
6339
+ }
6340
+ const nextRule = onAddRule ? onAddRule(rule, parentPath, query, context) : true;
6341
+ if (!nextRule) {
6342
+ log({
6343
+ type: LogType.onAddRuleFalse,
6344
+ rule,
6345
+ parentPath,
6346
+ query
6347
+ });
6348
+ return;
6349
+ }
6350
+ const newRule = nextRule === true ? rule : nextRule;
6351
+ const newQuery = add(query, newRule, parentPath, {
6352
+ combinators,
6353
+ combinatorPreceding: newRule.combinatorPreceding,
6354
+ idGenerator
6355
+ });
6356
+ log({
6357
+ type: LogType.add,
6358
+ query,
6359
+ newQuery,
6360
+ newRule,
6361
+ parentPath
6362
+ });
6363
+ return newQuery;
6364
+ },
6365
+ addGroup: (query, ruleGroup, parentPath, context) => {
6366
+ if (parentPath.length >= maxLevels) return void 0;
6367
+ if (blocked(query, parentPath, true)) {
6368
+ log({
6369
+ type: LogType.parentPathDisabled,
6370
+ ruleGroup,
6371
+ parentPath,
6372
+ query
6373
+ });
6374
+ return;
6375
+ }
6376
+ const nextGroup = onAddGroup ? onAddGroup(ruleGroup, parentPath, query, context) : true;
6377
+ if (!nextGroup) {
6378
+ log({
6379
+ type: LogType.onAddGroupFalse,
6380
+ ruleGroup,
6381
+ parentPath,
6382
+ query
6383
+ });
6384
+ return;
6385
+ }
6386
+ const newGroup = nextGroup === true ? ruleGroup : nextGroup;
6387
+ const newQuery = add(query, newGroup, parentPath, {
6388
+ combinators,
6389
+ combinatorPreceding: newGroup.combinatorPreceding ?? void 0,
6390
+ idGenerator
6391
+ });
6392
+ log({
6393
+ type: LogType.add,
6394
+ query,
6395
+ newQuery,
6396
+ newGroup,
6397
+ parentPath
6398
+ });
6399
+ return newQuery;
6400
+ },
6401
+ propChange: (query, prop, value, path) => {
6402
+ if (getGuardAbortReason(query, path, prop === "disabled" ? { queryDisabled } : guards)) {
6403
+ log({
6404
+ type: LogType.pathDisabled,
6405
+ path,
6406
+ prop,
6407
+ value,
6408
+ query
6409
+ });
6410
+ return;
6411
+ }
6412
+ const newQuery = update(query, prop, value, path, {
6413
+ resetOnFieldChange,
6414
+ resetOnOperatorChange,
6415
+ getRuleDefaultOperator,
6416
+ getValueSources,
6417
+ getRuleDefaultValue,
6418
+ getMatchModes
6419
+ });
6420
+ log({
6421
+ type: LogType.update,
6422
+ query,
6423
+ newQuery,
6424
+ prop,
6425
+ value,
6426
+ path
6427
+ });
6428
+ return newQuery;
6429
+ },
6430
+ removeRuleOrGroup: (query, path, context) => {
6431
+ if (blocked(query, path)) {
6432
+ log({
6433
+ type: LogType.pathDisabled,
6434
+ path,
6435
+ query
6436
+ });
6437
+ return;
6438
+ }
6439
+ const ruleOrGroup = findPath(path, query);
6440
+ /* v8 ignore next -- @preserve */
6441
+ if (!ruleOrGroup) return void 0;
6442
+ if (onRemove && !onRemove(ruleOrGroup, path, query, context)) {
6443
+ log({
6444
+ type: LogType.onRemoveFalse,
6445
+ ruleOrGroup,
6446
+ path,
6447
+ query
6448
+ });
6449
+ return;
6450
+ }
6451
+ const newQuery = remove(query, path);
6452
+ log({
6453
+ type: LogType.remove,
6454
+ query,
6455
+ newQuery,
6456
+ path,
6457
+ ruleOrGroup
6458
+ });
6459
+ return newQuery;
6460
+ },
6461
+ moveRule: (query, oldPath, newPath, clone, context) => {
6462
+ if (blocked(query, oldPath)) {
6463
+ log({
6464
+ type: LogType.pathDisabled,
6465
+ oldPath,
6466
+ newPath,
6467
+ query
6468
+ });
6469
+ return;
6470
+ }
6471
+ const ruleOrGroup = findPath(oldPath, query);
6472
+ if (!ruleOrGroup) return void 0;
6473
+ const nextQuery = move(query, oldPath, newPath, {
6474
+ clone,
6475
+ combinators,
6476
+ idGenerator
6477
+ });
6478
+ const isGroup = isRuleGroup(ruleOrGroup);
6479
+ const callback = isGroup ? onMoveGroup : onMoveRule;
6480
+ const callbackResult = callback ? callback(ruleOrGroup, oldPath, newPath, query, nextQuery, {
6481
+ clone,
6482
+ combinators
6483
+ }, context) : true;
6484
+ if (!callbackResult) {
6485
+ log({
6486
+ type: isGroup ? LogType.onMoveGroupFalse : LogType.onMoveRuleFalse,
6487
+ ruleOrGroup,
6488
+ oldPath,
6489
+ newPath,
6490
+ clone,
6491
+ query,
6492
+ nextQuery
6493
+ });
6494
+ return;
6495
+ }
6496
+ const newQuery = isRuleGroup(callbackResult) ? callbackResult : nextQuery;
6497
+ log({
6498
+ type: LogType.move,
6499
+ query,
6500
+ newQuery,
6501
+ oldPath,
6502
+ newPath,
6503
+ clone
6504
+ });
6505
+ return newQuery;
6506
+ },
6507
+ groupRule: (query, sourcePath, targetPath, clone, context) => {
6508
+ if (blocked(query, sourcePath)) {
6509
+ log({
6510
+ type: LogType.pathDisabled,
6511
+ sourcePath,
6512
+ targetPath,
6513
+ query
6514
+ });
6515
+ return;
6516
+ }
6517
+ const ruleOrGroup = findPath(sourcePath, query);
6518
+ if (!ruleOrGroup) return void 0;
6519
+ const nextQuery = group(query, sourcePath, targetPath, {
6520
+ clone,
6521
+ combinators,
6522
+ idGenerator
6523
+ });
6524
+ const isGroup = isRuleGroup(ruleOrGroup);
6525
+ const callback = isGroup ? onGroupGroup : onGroupRule;
6526
+ const callbackResult = callback ? callback(ruleOrGroup, sourcePath, targetPath, query, nextQuery, {
6527
+ clone,
6528
+ combinators
6529
+ }, context) : true;
6530
+ if (!callbackResult) {
6531
+ log({
6532
+ type: isGroup ? LogType.onGroupGroupFalse : LogType.onGroupRuleFalse,
6533
+ ruleOrGroup,
6534
+ sourcePath,
6535
+ targetPath,
6536
+ clone,
6537
+ query,
6538
+ nextQuery
6539
+ });
6540
+ return;
6541
+ }
6542
+ const newQuery = isRuleGroup(callbackResult) ? callbackResult : nextQuery;
6543
+ log({
6544
+ type: LogType.group,
6545
+ query,
6546
+ newQuery,
6547
+ sourcePath,
6548
+ targetPath,
6549
+ clone
6550
+ });
6551
+ return newQuery;
6552
+ }
6553
+ };
6554
+ };
6555
+ //#endregion
6556
+ //#region src/utils/ruleFactory.ts
6557
+ /**
6558
+ * Creates a rule from the given configuration, applying the same precedence as the
6559
+ * `QueryBuilder` component. The `value` is computed in a second pass, once `field`, `operator`,
6560
+ * and `valueSource` are known, since the default value depends on all three.
6561
+ *
6562
+ * @group Query Tools
6563
+ */
6564
+ const createRule = ({ fields, getDefaultField, getRuleDefaultOperator, getValueSources, getMatchModes, getRuleDefaultValue, idGenerator = generateID }) => {
6565
+ let field = getFirstOption(fields) ?? "";
6566
+ if (getDefaultField) if (typeof getDefaultField === "function") {
6567
+ const defaultField = getDefaultField(fields);
6568
+ if (defaultField) field = defaultField;
6569
+ } else field = getDefaultField;
6570
+ const fieldData = getOption(fields, field);
6571
+ const operator = getRuleDefaultOperator(field);
6572
+ const valueSource = getFirstOption(getValueSources(field, operator, { fieldData })) ?? "value";
6573
+ const matchMode = getFirstOption(getMatchModes(field, { fieldData }));
6574
+ const newRule = {
6575
+ id: idGenerator(),
6576
+ field,
6577
+ operator,
6578
+ valueSource,
6579
+ value: "",
6580
+ ...matchMode ? { match: {
6581
+ mode: matchMode,
6582
+ threshold: 1
6583
+ } } : null
6584
+ };
6585
+ return {
6586
+ ...newRule,
6587
+ value: getRuleDefaultValue(newRule)
6588
+ };
6589
+ };
6590
+ /**
6591
+ * Creates a group from the given configuration. Pass `true` for `independentCombinators` to omit
6592
+ * the `combinator` property.
6593
+ *
6594
+ * The group's own `id` is generated _before_ any contained rule's, matching the order the
6595
+ * `QueryBuilder` component has always used. This is observable when `idGenerator` is
6596
+ * deterministic, as it often is in tests.
6597
+ *
6598
+ * @group Query Tools
6599
+ */
6600
+ const createRuleGroup = ({ combinators, addRuleToNewGroups, createRule: createRuleMain, idGenerator = generateID }, independentCombinators) => {
6601
+ const id = idGenerator();
6602
+ const rules = addRuleToNewGroups ? [createRuleMain()] : [];
6603
+ if (independentCombinators) return {
6604
+ id,
6605
+ rules,
6606
+ not: false
6607
+ };
6608
+ return {
6609
+ id,
6610
+ rules,
6611
+ combinator: getFirstOption(combinators) ?? "",
6612
+ not: false
6613
+ };
6614
+ };
6615
+ //#endregion
6616
+ //#region src/utils/signature.ts
6617
+ /**
6618
+ * Signature returned when a change alters the _shape_ of the query—rules or groups added,
6619
+ * removed, moved, or reordered—rather than the properties of a single node. Structural changes
6620
+ * never coalesce with each other, so each one produces its own history entry.
6621
+ */
6622
+ const structuralSignature = "~structural";
6623
+ /**
6624
+ * Signature returned when two queries differ by reference only, with no observable difference
6625
+ * in their properties.
6626
+ */
6627
+ const unchangedSignature = "";
6628
+ /**
6629
+ * Returns the list of property names that differ between two nodes, ignoring `rules` (which is
6630
+ * compared structurally by the caller).
6631
+ */
6632
+ const changedProps = (prev, next) => {
6633
+ const keys = /* @__PURE__ */ new Set([...Object.keys(prev), ...Object.keys(next)]);
6634
+ keys.delete("rules");
6635
+ const changed = [];
6636
+ for (const key of keys) if (!Object.is(prev[key], next[key])) changed.push(key);
6637
+ return changed.toSorted();
6638
+ };
6639
+ const signatureOfNode = (prev, next, parentId, index) => {
6640
+ if (prev === next) return "";
6641
+ if (typeof prev === "string" || typeof next === "string") return typeof prev === typeof next ? `${parentId}:combinator[${index}]` : structuralSignature;
6642
+ const prevIsGroup = isRuleGroup(prev);
6643
+ const nextIsGroup = isRuleGroup(next);
6644
+ if (prevIsGroup !== nextIsGroup) return structuralSignature;
6645
+ if (!prevIsGroup || !nextIsGroup) {
6646
+ const props = changedProps(prev, next);
6647
+ return props.length === 0 ? "" : `${next.id ?? ""}:${props.join(",")}`;
6648
+ }
6649
+ if (prev.rules.length !== next.rules.length) return structuralSignature;
6650
+ const ownProps = changedProps(prev, next);
6651
+ let changedIndex = -1;
6652
+ for (const [i, prevRule] of prev.rules.entries()) if (prevRule !== next.rules[i]) {
6653
+ if (changedIndex !== -1) return structuralSignature;
6654
+ changedIndex = i;
6655
+ }
6656
+ if (changedIndex === -1) return ownProps.length === 0 ? "" : `${next.id ?? ""}:${ownProps.join(",")}`;
6657
+ if (ownProps.length > 0) return structuralSignature;
6658
+ return signatureOfNode(prev.rules[changedIndex], next.rules[changedIndex], next.id ?? "", changedIndex);
6659
+ };
6660
+ /**
6661
+ * Describes _what changed_ between two versions of a query as a short string, so that
6662
+ * consecutive edits to the same property of the same rule (e.g. typing in a value editor) can
6663
+ * be recognized and coalesced into a single history entry.
6664
+ *
6665
+ * Returns {@link structuralSignature} for changes to the shape of the query, {@link unchangedSignature} when the
6666
+ * two queries differ by reference only, and `"<id>:<props>"` otherwise.
6667
+ *
6668
+ * The walk prunes on reference equality, so it costs O(depth) rather than O(size) for the
6669
+ * single-node edits that make up the overwhelming majority of changes.
6670
+ *
6671
+ * Note that this relies on the structural sharing that Immer—and therefore every query
6672
+ * produced by RQB's own update functions—guarantees: nodes that did not change keep their
6673
+ * identity. A query that has been wholly rebuilt (deep-cloned, round-tripped through JSON,
6674
+ * re-parsed from a string) shares no identity with its predecessor, so it is reported as
6675
+ * {@link structuralSignature} and will not coalesce. That is a graceful degradation—every change simply
6676
+ * gets its own history entry—but controlled components that clone the query on every change
6677
+ * will not benefit from coalescing.
6678
+ */
6679
+ const signatureOf = (prev, next) => signatureOfNode(prev, next, "", -1);
6680
+ //#endregion
6681
+ //#region src/utils/QueryManager.ts
6682
+ /**
6683
+ * Abort reasons that {@link QueryManager}'s `strict` mode treats as errors. The remaining
6684
+ * reasons—`"same-location"` and `"no-change"`—describe valid operations that had nothing to do,
6685
+ * so they are reported to `onInvalidTarget` but never throw.
6686
+ *
6687
+ * @group Query Tools
6688
+ */
6689
+ const strictAbortReasons = [
6690
+ "target-not-found",
6691
+ "parent-not-found",
6692
+ "parent-not-a-group",
6693
+ "destination-not-found",
6694
+ "root-not-allowed",
6695
+ "not-a-combinator-slot",
6696
+ "target-disabled",
6697
+ "parent-disabled",
6698
+ "max-levels-exceeded"
6699
+ ];
6700
+ const strictAbortReasonSet = new Set(strictAbortReasons);
6701
+ /**
6702
+ * Thrown by {@link QueryManager} methods in `strict` mode when an operation cannot be carried
6703
+ * out because its target could not be used.
6704
+ *
6705
+ * @group Query Tools
6706
+ */
6707
+ var QueryManagerError = class QueryManagerError extends Error {
6708
+ /** Why the operation was aborted. */
6709
+ code;
6710
+ /** Full details about the aborted operation. */
6711
+ info;
6712
+ constructor(info) {
6713
+ super(`QueryManager: "${info.operation}" aborted (${info.reason})${info.pathOrID === void 0 ? "" : ` for target ${JSON.stringify(info.pathOrID)}`}.`);
6714
+ this.name = "QueryManagerError";
6715
+ this.code = info.reason;
6716
+ this.info = info;
6717
+ Object.setPrototypeOf(this, QueryManagerError.prototype);
6718
+ }
6719
+ };
6720
+ /**
6721
+ * Stateful wrapper around the {@link add}/{@link remove}/{@link update}/{@link move}/
6722
+ * {@link insert}/{@link group} query tools, plus rule/group factories, {@link defaultValidator
6723
+ * validation}, and {@link formatQuery formatting}.
6724
+ *
6725
+ * The query is held internally, so each method takes the same arguments as its `queryTools`
6726
+ * counterpart minus the leading `query` parameter, and returns the manager itself for chaining.
6727
+ * Mutations use the non-`InPlace` tools, so a query previously handed out by
6728
+ * {@link QueryManager.getQuery} is never modified.
6729
+ *
6730
+ * ```ts
6731
+ * const q = new QueryManager(undefined, { fields });
6732
+ * q.add(q.createRule()).add({ field: 'firstName', operator: '=', value: 'Steve' });
6733
+ * q.format('sql');
6734
+ * ```
6735
+ *
6736
+ * Like the underlying query tools, methods are a no-op when the target path or `id` can't be
6737
+ * resolved (including attempts to remove the root group). By default nothing is thrown; pass
6738
+ * `strict: true` to raise a {@link QueryManagerError} instead, or `onInvalidTarget` to observe
6739
+ * aborted operations without changing control flow.
6740
+ *
6741
+ * @group Query Tools
6742
+ */
6743
+ var QueryManager = class QueryManager {
6744
+ #query;
6745
+ #options;
6746
+ #fields;
6747
+ #fieldMap;
6748
+ #operators;
6749
+ #combinators;
6750
+ #idGenerator;
6751
+ #validator;
6752
+ #strict;
6753
+ #respectDisabled;
6754
+ #onInvalidTarget;
6755
+ #listeners = /* @__PURE__ */ new Set();
6756
+ #historyEnabled;
6757
+ #maxHistory;
6758
+ #coalesceMs;
6759
+ #now;
6760
+ #past = [];
6761
+ #future = [];
6762
+ #lastSig;
6763
+ #lastAt = 0;
6764
+ #batchDepth = 0;
6765
+ #batchSnapshot;
6766
+ /** Whether a history-stack method ran inside the batch currently in progress. */
6767
+ #historyBypassed = false;
6768
+ /**
6769
+ * The query the cached fields below were derived from. Caches are keyed on query _identity_
6770
+ * rather than invalidated from {@link QueryManager.#commit} because `undo`, `redo`, and
6771
+ * `batch`'s rollback all assign `#query` directly.
6772
+ */
6773
+ #cacheFor;
6774
+ #idPathIndex;
6775
+ #validation;
6776
+ constructor(query, options = {}) {
6777
+ this.#options = options;
6778
+ this.#idGenerator = options.idGenerator ?? generateID;
6779
+ this.#validator = options.validator ?? defaultValidator;
6780
+ this.#strict = options.strict ?? false;
6781
+ this.#respectDisabled = options.respectDisabled ?? true;
6782
+ this.#onInvalidTarget = options.onInvalidTarget;
6783
+ const history = options.history ?? false;
6784
+ const historyOptions = typeof history === "object" ? history : {};
6785
+ this.#historyEnabled = history !== false;
6786
+ this.#maxHistory = historyOptions.maxHistory ?? 50;
6787
+ this.#coalesceMs = historyOptions.coalesceMs ?? 500;
6788
+ this.#now = options.now ?? Date.now;
6789
+ const { optionList: fields, optionsMap: fieldMap } = prepareOptionList({
6790
+ optionList: options.fields,
6791
+ baseOption: options.baseField,
6792
+ autoSelectOption: options.autoSelectField
6793
+ });
6794
+ this.#fields = freeze(fields, true);
6795
+ this.#fieldMap = freeze(fieldMap, true);
6796
+ this.#operators = prepareOptionList({
6797
+ optionList: options.operators ?? defaultOperators,
6798
+ baseOption: options.baseOperator,
6799
+ labelMap: defaultOperatorLabelMap,
6800
+ autoSelectOption: options.autoSelectOperator
6801
+ }).optionList;
6802
+ this.#combinators = freeze(prepareOptionList({
6803
+ optionList: options.combinators ?? defaultCombinators,
6804
+ baseOption: options.baseCombinator
6805
+ }).optionList, true);
6806
+ this.#query = freeze(query ? prepareRuleGroup(query, { idGenerator: this.#idGenerator }) : this.createRuleGroup(), true);
6807
+ }
6808
+ /** Resolves the field configuration for a field name. */
6809
+ #fieldData(field) {
6810
+ return this.#fieldMap[field] ?? {};
6811
+ }
6812
+ /** Resolves the operator list for a field, mirroring `QueryBuilder`'s precedence. */
6813
+ #operatorsFor(field) {
6814
+ return resolveOperatorList({
6815
+ field,
6816
+ fieldData: this.#fieldData(field),
6817
+ getOperators: this.#options.getOperators,
6818
+ operators: this.#operators,
6819
+ baseOption: this.#options.baseOperator,
6820
+ autoSelectOption: this.#options.autoSelectOperator
6821
+ });
6822
+ }
6823
+ /** Resolves the default operator for a field, mirroring `QueryBuilder`'s precedence. */
6824
+ #defaultOperator(field) {
6825
+ return resolveDefaultOperator({
6826
+ field,
6827
+ fieldData: this.#fieldData(field),
6828
+ getDefaultOperator: this.#options.getDefaultOperator,
6829
+ getOperators: (f) => this.#operatorsFor(f)
6830
+ });
6831
+ }
6832
+ #valueSourcesFor(field, operator) {
6833
+ return getValueSourcesUtil(this.#fieldData(field), operator, this.#options.getValueSources);
6834
+ }
6835
+ #matchModesFor(field) {
6836
+ return getMatchModesUtil(this.#fieldData(field), this.#options.getMatchModes);
6837
+ }
6838
+ #valuesFor(field, operator) {
6839
+ return resolveValueList({
6840
+ field,
6841
+ operator,
6842
+ fieldData: this.#fieldData(field),
6843
+ getValues: this.#options.getValues,
6844
+ autoSelectOption: this.#options.autoSelectValue
6845
+ });
6846
+ }
6847
+ #valueEditorTypeFor(field, operator) {
6848
+ return resolveValueEditorType({
6849
+ field,
6850
+ operator,
6851
+ fieldData: this.#fieldData(field),
6852
+ getValueEditorType: this.#options.getValueEditorType
6853
+ });
6854
+ }
6855
+ /** Computes the default `value` for a rule, mirroring `QueryBuilder`'s precedence. */
6856
+ #defaultValue(rule) {
6857
+ const { getDefaultValue, getParameters, listsAsArrays } = this.#options;
6858
+ return getRuleDefaultValue(rule, {
6859
+ fieldData: this.#fieldData(rule.field),
6860
+ fields: this.#fields,
6861
+ listsAsArrays,
6862
+ getValueEditorType: (f, o) => this.#valueEditorTypeFor(f, o),
6863
+ getValues: (f, o) => this.#valuesFor(f, o),
6864
+ getDefaultValue: getDefaultValue && ((r, misc) => getDefaultValue(r, misc)),
6865
+ getParameters: getParameters && ((f, o, misc) => this.#parametersFor(f, o, misc))
6866
+ });
6867
+ }
6868
+ /**
6869
+ * Resolves the parameter list for a field/operator pair, normalized the same way as every
6870
+ * other option list. Shared by {@link QueryManager.#defaultValue} and
6871
+ * {@link QueryManager.getRuleContext} so both see the same shape.
6872
+ */
6873
+ #parametersFor(field, operator, misc) {
6874
+ return prepareOptionList({
6875
+ optionList: this.#options.getParameters?.(field, operator, misc) ?? [],
6876
+ autoSelectOption: this.#options.autoSelectValue
6877
+ }).optionList;
6878
+ }
6879
+ /** Defaults shared by every mutating method, overridable per call. */
6880
+ #guardOptions() {
6881
+ const { maxLevels } = this.#options;
6882
+ return {
6883
+ maxLevels: (maxLevels ?? 0) > 0 ? Number(maxLevels) : Infinity,
6884
+ respectDisabled: this.#respectDisabled,
6885
+ disabledPaths: this.#options.disabledPaths,
6886
+ queryDisabled: this.#options.queryDisabled
6887
+ };
6888
+ }
6889
+ #toolOptions() {
6890
+ return {
6891
+ combinators: this.#combinators,
6892
+ idGenerator: this.#idGenerator,
6893
+ ...this.#guardOptions()
6894
+ };
6895
+ }
6896
+ /**
6897
+ * Builds the `onAbort` handler passed to the query tools, applying the per-call overrides on
6898
+ * top of the manager's own options.
6899
+ */
6900
+ #onAbort({ strict, onInvalidTarget }) {
6901
+ const strictMain = strict ?? this.#strict;
6902
+ const handler = onInvalidTarget ?? this.#onInvalidTarget;
6903
+ return (info) => {
6904
+ handler?.(info);
6905
+ if (strictMain && strictAbortReasonSet.has(info.reason)) throw new QueryManagerError(info);
6906
+ };
6907
+ }
6908
+ /** Defaults for {@link update}, so resets mirror `QueryBuilder`'s behavior. */
6909
+ #updateOptions() {
6910
+ return {
6911
+ getRuleDefaultOperator: (f) => this.#defaultOperator(f),
6912
+ getRuleDefaultValue: (r) => this.#defaultValue(r),
6913
+ getValueSources: (f, o) => this.#valueSourcesFor(f, o),
6914
+ getMatchModes: (f) => this.#matchModesFor(f),
6915
+ resetOnFieldChange: this.#options.resetOnFieldChange,
6916
+ resetOnOperatorChange: this.#options.resetOnOperatorChange,
6917
+ ...this.#guardOptions()
6918
+ };
6919
+ }
6920
+ /**
6921
+ * Applies a new query, recording history and notifying subscribers as appropriate. Every
6922
+ * mutation funnels through here. A tool that could not resolve its target returns the same
6923
+ * query object, which is treated as a no-op.
6924
+ */
6925
+ #commit(next) {
6926
+ const prev = this.#query;
6927
+ if (prev === next) return;
6928
+ this.#query = next;
6929
+ if (this.#batchDepth > 0) return;
6930
+ this.#record(prev, next);
6931
+ this.#notify();
6932
+ }
6933
+ /**
6934
+ * Records a change, either as a new history entry or by absorbing it into the current one.
6935
+ * Mirrors the recording semantics of the `react-querybuilder/history` entry point.
6936
+ */
6937
+ #record(prev, next) {
6938
+ if (!this.#historyEnabled) return;
6939
+ const sig = signatureOf(prev, next);
6940
+ if (sig === "") return;
6941
+ const now = this.#now();
6942
+ if (!(sig !== "~structural" && sig === this.#lastSig && now - this.#lastAt < this.#coalesceMs)) {
6943
+ this.#past.push(prev);
6944
+ if (this.#past.length > this.#maxHistory) this.#past.shift();
6945
+ this.#future = [];
6946
+ }
6947
+ this.#lastSig = sig;
6948
+ this.#lastAt = now;
6949
+ }
6950
+ #notify() {
6951
+ for (const listener of this.#listeners) listener();
6952
+ }
6953
+ /**
6954
+ * Records that a history-stack method ran inside the current batch. Those methods manage
6955
+ * `#past`/`#future` themselves, so the batch must not also record an entry on completion —
6956
+ * doing so would push a duplicate onto `#past` and clear the redo stack that `undo` just
6957
+ * populated.
6958
+ */
6959
+ #markHistoryBypassed() {
6960
+ if (this.#batchDepth > 0) this.#historyBypassed = true;
6961
+ }
6962
+ /**
6963
+ * Discards every cached derivation when the query has been replaced since they were computed.
6964
+ * Called at the top of each cached reader.
6965
+ */
6966
+ #ensureCache() {
6967
+ if (this.#cacheFor === this.#query) return;
6968
+ this.#cacheFor = this.#query;
6969
+ this.#idPathIndex = void 0;
6970
+ this.#validation = void 0;
6971
+ }
6972
+ /**
6973
+ * The single traversal implementation. Combinator strings in independent-combinator groups
6974
+ * are skipped.
6975
+ *
6976
+ * @yields The subtree rooted at `node`, depth-first in pre-order, starting with `node` itself.
6977
+ */
6978
+ *#walkFrom(node, path, parent) {
6979
+ yield {
6980
+ node,
6981
+ path,
6982
+ parent
6983
+ };
6984
+ if (!isRuleGroup(node)) return;
6985
+ const startGroup = node;
6986
+ for (const [index, child] of startGroup.rules.entries()) {
6987
+ if (typeof child === "string") continue;
6988
+ yield* this.#walkFrom(child, [...path, index], startGroup);
6989
+ }
6990
+ }
6991
+ /** Builds (once per query) the `id` to {@link Path} index backing `findID`/`getPathOfID`. */
6992
+ #index() {
6993
+ this.#ensureCache();
6994
+ if (!this.#idPathIndex) {
6995
+ const index = /* @__PURE__ */ new Map();
6996
+ for (const { node, path } of this.#walkFrom(this.#query, [], null)) if (node.id !== void 0 && !index.has(node.id)) index.set(node.id, path);
6997
+ this.#idPathIndex = index;
6998
+ }
6999
+ return this.#idPathIndex;
7000
+ }
7001
+ /** Resolves a path or `id` to a path, or `null` when the `id` isn't present. */
7002
+ #toPath(pathOrID) {
7003
+ return typeof pathOrID === "string" ? this.#index().get(pathOrID) ?? null : pathOrID;
7004
+ }
7005
+ /**
7006
+ * The current query. The returned object is frozen and structurally shared, so it is safe to
7007
+ * retain and compare by reference to detect changes.
7008
+ *
7009
+ * Like {@link QueryManager.subscribe}, this method is bound to the instance, so it can be
7010
+ * passed as a bare reference (e.g. as the `getSnapshot` argument to `useSyncExternalStore`).
7011
+ */
7012
+ getQuery = () => this.#query;
7013
+ /** Replaces the current query, ensuring every rule and group has an `id`. */
7014
+ setQuery(query) {
7015
+ this.#commit(freeze(prepareRuleGroup(query, { idGenerator: this.#idGenerator }), true));
7016
+ return this;
7017
+ }
7018
+ /**
7019
+ * Creates a rule using the configured fields, operators, and defaults. The rule is not added
7020
+ * to the query—pass it to {@link QueryManager.add} or {@link QueryManager.insert}.
7021
+ */
7022
+ createRule() {
7023
+ return createRule({
7024
+ fields: this.#fields,
7025
+ getDefaultField: this.#options.getDefaultField,
7026
+ getRuleDefaultOperator: (f) => this.#defaultOperator(f),
7027
+ getValueSources: (f, o) => this.#valueSourcesFor(f, o),
7028
+ getMatchModes: (f) => this.#matchModesFor(f),
7029
+ getRuleDefaultValue: (r) => this.#defaultValue(r),
7030
+ idGenerator: this.#idGenerator
7031
+ });
7032
+ }
7033
+ /**
7034
+ * Creates a group. Pass `true` for a group with independent combinators. The group is not
7035
+ * added to the query—pass it to {@link QueryManager.add} or {@link QueryManager.insert}.
7036
+ */
7037
+ createRuleGroup(independentCombinators) {
7038
+ return createRuleGroup({
7039
+ combinators: this.#combinators,
7040
+ addRuleToNewGroups: this.#options.addRuleToNewGroups,
7041
+ createRule: () => this.createRule(),
7042
+ idGenerator: this.#idGenerator
7043
+ }, independentCombinators);
7044
+ }
7045
+ /**
7046
+ * Adds a rule or group to the end of the group at `parentPathOrID`, which defaults to the
7047
+ * root group.
7048
+ */
7049
+ add(ruleOrGroup, parentPathOrID = [], options = {}) {
7050
+ const { strict, onInvalidTarget, ...toolOptions } = options;
7051
+ this.#commit(add(this.#query, ruleOrGroup, parentPathOrID, {
7052
+ ...this.#toolOptions(),
7053
+ ...toolOptions,
7054
+ onAbort: this.#onAbort({
7055
+ strict,
7056
+ onInvalidTarget
7057
+ })
7058
+ }));
7059
+ return this;
7060
+ }
7061
+ /** Removes the rule or group at the given path or `id`. The root group cannot be removed. */
7062
+ remove(pathOrID, options = {}) {
7063
+ const { strict, onInvalidTarget, ...toolOptions } = options;
7064
+ this.#commit(remove(this.#query, pathOrID, {
7065
+ ...this.#guardOptions(),
7066
+ ...toolOptions,
7067
+ onAbort: this.#onAbort({
7068
+ strict,
7069
+ onInvalidTarget
7070
+ })
7071
+ }));
7072
+ return this;
7073
+ }
7074
+ update(a, b, c, d) {
7075
+ const optionsIndex = typeof a === "string" || Array.isArray(a) ? 3 : 2;
7076
+ const args = [
7077
+ a,
7078
+ b,
7079
+ c,
7080
+ d
7081
+ ];
7082
+ const { strict, onInvalidTarget, ...toolOptions } = args[optionsIndex] ?? {};
7083
+ args[optionsIndex] = {
7084
+ ...this.#updateOptions(),
7085
+ ...toolOptions,
7086
+ onAbort: this.#onAbort({
7087
+ strict,
7088
+ onInvalidTarget
7089
+ })
7090
+ };
7091
+ this.#commit(update(this.#query, ...args.slice(0, optionsIndex + 1)));
7092
+ return this;
7093
+ }
7094
+ /** Moves the rule or group at `oldPathOrID` to `newPath`, or shifts it `'up'`/`'down'`. */
7095
+ move(oldPathOrID, newPath, options = {}) {
7096
+ const { strict, onInvalidTarget, ...toolOptions } = options;
7097
+ this.#commit(move(this.#query, oldPathOrID, newPath, {
7098
+ ...this.#toolOptions(),
7099
+ ...toolOptions,
7100
+ onAbort: this.#onAbort({
7101
+ strict,
7102
+ onInvalidTarget
7103
+ })
7104
+ }));
7105
+ return this;
7106
+ }
7107
+ /**
7108
+ * Inserts a rule or group at the given path. Unlike the other methods, this accepts a path
7109
+ * only—inserting _at_ an `id` would be ambiguous.
7110
+ */
7111
+ insert(ruleOrGroup, path, options = {}) {
7112
+ const { strict, onInvalidTarget, ...toolOptions } = options;
7113
+ this.#commit(insert(this.#query, ruleOrGroup, path, {
7114
+ ...this.#toolOptions(),
7115
+ ...toolOptions,
7116
+ onAbort: this.#onAbort({
7117
+ strict,
7118
+ onInvalidTarget
7119
+ })
7120
+ }));
7121
+ return this;
7122
+ }
7123
+ /**
7124
+ * Creates a new group at `targetPathOrID` containing the rules/groups currently at
7125
+ * `targetPathOrID` and `sourcePathOrID`.
7126
+ */
7127
+ group(sourcePathOrID, targetPathOrID, options = {}) {
7128
+ const { strict, onInvalidTarget, ...toolOptions } = options;
7129
+ this.#commit(group(this.#query, sourcePathOrID, targetPathOrID, {
7130
+ ...this.#toolOptions(),
7131
+ ...toolOptions,
7132
+ onAbort: this.#onAbort({
7133
+ strict,
7134
+ onInvalidTarget
7135
+ })
7136
+ }));
7137
+ return this;
7138
+ }
7139
+ /**
7140
+ * Creates an independent manager with the same configuration and the current query.
7141
+ *
7142
+ * Subscribers and history are _not_ carried over: the clone starts with no listeners and an
7143
+ * empty undo stack. Because every mutation produces a new query object, the two managers
7144
+ * share the initial query safely and diverge from the first change.
7145
+ *
7146
+ * Pass `{ regenerateIDs: true }` to give every rule and group in the clone a new `id`, which
7147
+ * is useful when both queries will be used together (e.g. inserted into the same tree).
7148
+ */
7149
+ clone(options) {
7150
+ const query = options?.regenerateIDs ? regenerateIDs(this.#query, { idGenerator: this.#idGenerator }) : this.#query;
7151
+ return new QueryManager(query, this.#options);
7152
+ }
7153
+ /**
7154
+ * Registers a listener called after every change to the query, and returns a function that
7155
+ * unregisters it. Mutations that resolve to a no-op do not notify, and a
7156
+ * {@link QueryManager.batch batch} notifies once no matter how many changes it contains.
7157
+ *
7158
+ * Together with {@link QueryManager.getQuery}, this satisfies React's `useSyncExternalStore`
7159
+ * contract. Both methods are bound to the instance, so they are stable references across
7160
+ * renders and can be passed directly:
7161
+ *
7162
+ * ```ts
7163
+ * const query = useSyncExternalStore(q.subscribe, q.getQuery);
7164
+ * ```
7165
+ *
7166
+ * In React, prefer the `useQueryManager` hook from `react-querybuilder`, which wraps this.
7167
+ */
7168
+ subscribe = (listener) => {
7169
+ this.#listeners.add(listener);
7170
+ return () => {
7171
+ this.#listeners.delete(listener);
7172
+ };
7173
+ };
7174
+ /**
7175
+ * Runs `fn`, deferring history recording and subscriber notification until it returns. The
7176
+ * whole batch becomes a single undo step and triggers a single notification, or neither if
7177
+ * the query ends up unchanged.
7178
+ *
7179
+ * Batches may be nested; only the outermost one commits. If `fn` throws, the query and its
7180
+ * history are restored to their pre-batch state and the error propagates, so a batch either
7181
+ * applies completely or not at all.
7182
+ *
7183
+ * {@link QueryManager.undo}, {@link QueryManager.redo}, and {@link QueryManager.clearHistory}
7184
+ * may be called inside a batch; their notifications are deferred like everything else. Because
7185
+ * they manage the history stacks themselves, a batch containing one of them records no entry of
7186
+ * its own, leaving the stacks exactly as those methods left them.
7187
+ */
7188
+ batch(fn) {
7189
+ this.#batchDepth++;
7190
+ if (this.#batchDepth === 1) this.#batchSnapshot = {
7191
+ query: this.#query,
7192
+ past: [...this.#past],
7193
+ future: [...this.#future],
7194
+ lastSig: this.#lastSig,
7195
+ lastAt: this.#lastAt
7196
+ };
7197
+ try {
7198
+ fn();
7199
+ } catch (error) {
7200
+ if (this.#batchDepth === 1) {
7201
+ const snapshot = this.#batchSnapshot;
7202
+ this.#query = snapshot.query;
7203
+ this.#past = snapshot.past;
7204
+ this.#future = snapshot.future;
7205
+ this.#lastSig = snapshot.lastSig;
7206
+ this.#lastAt = snapshot.lastAt;
7207
+ }
7208
+ throw error;
7209
+ } finally {
7210
+ this.#batchDepth--;
7211
+ if (this.#batchDepth === 0) {
7212
+ const { query: base } = this.#batchSnapshot;
7213
+ this.#batchSnapshot = void 0;
7214
+ if (base !== this.#query) {
7215
+ if (!this.#historyBypassed) this.#record(base, this.#query);
7216
+ this.#notify();
7217
+ }
7218
+ this.#historyBypassed = false;
7219
+ }
7220
+ }
7221
+ return this;
7222
+ }
7223
+ /** Whether there is a previous query to restore. Always `false` unless `history` is enabled. */
7224
+ canUndo() {
7225
+ return this.#past.length > 0;
7226
+ }
7227
+ /** Whether there is an undone query to restore. Always `false` unless `history` is enabled. */
7228
+ canRedo() {
7229
+ return this.#future.length > 0;
7230
+ }
7231
+ /** Restores the previous query. No-op when {@link QueryManager.canUndo} is `false`. */
7232
+ undo() {
7233
+ if (this.#past.length === 0) return this;
7234
+ this.#future.unshift(this.#query);
7235
+ this.#query = this.#past.pop();
7236
+ this.#lastSig = void 0;
7237
+ this.#markHistoryBypassed();
7238
+ if (this.#batchDepth === 0) this.#notify();
7239
+ return this;
7240
+ }
7241
+ /** Restores the most recently undone query. No-op when {@link QueryManager.canRedo} is `false`. */
7242
+ redo() {
7243
+ if (this.#future.length === 0) return this;
7244
+ this.#past.push(this.#query);
7245
+ this.#query = this.#future.shift();
7246
+ this.#lastSig = void 0;
7247
+ this.#markHistoryBypassed();
7248
+ if (this.#batchDepth === 0) this.#notify();
7249
+ return this;
7250
+ }
7251
+ /** Discards all undo/redo history without changing the current query. */
7252
+ clearHistory() {
7253
+ this.#markHistoryBypassed();
7254
+ this.#past = [];
7255
+ this.#future = [];
7256
+ this.#lastSig = void 0;
7257
+ return this;
7258
+ }
7259
+ /**
7260
+ * The recorded history: `past` oldest first, `future` newest first. Both are copies, so
7261
+ * mutating them does not affect the manager.
7262
+ */
7263
+ getHistory() {
7264
+ return {
7265
+ past: [...this.#past],
7266
+ future: [...this.#future]
7267
+ };
7268
+ }
7269
+ /**
7270
+ * Validates the current query with the configured validator.
7271
+ *
7272
+ * The result is cached until the query changes, so a custom `validator` with side effects (or
7273
+ * one that depends on anything other than the query) may run fewer times than expected.
7274
+ */
7275
+ validate() {
7276
+ this.#ensureCache();
7277
+ this.#validation ??= this.#validator(this.#query);
7278
+ return this.#validation;
7279
+ }
7280
+ format(options) {
7281
+ return formatQuery(this.#query, options);
7282
+ }
7283
+ /**
7284
+ * Yields every rule and group in the query, depth-first in pre-order, starting with the root
7285
+ * group itself. Combinator strings in independent-combinator groups are skipped.
7286
+ *
7287
+ * ```ts
7288
+ * for (const { node, path, parent } of qm.walk({ rulesOnly: true })) {
7289
+ * console.log(path, node.field);
7290
+ * }
7291
+ * ```
7292
+ *
7293
+ * Traversal operates on the query as it was when iteration began, so mutating the manager
7294
+ * mid-iteration does not affect a walk already in progress. Because generators are lazy, that
7295
+ * happens on the first iteration step rather than when `walk` is called.
7296
+ *
7297
+ * @yields Every rule and group in the query, subject to `options`.
7298
+ */
7299
+ *walk(options = {}) {
7300
+ const { from, rulesOnly, groupsOnly } = options;
7301
+ let start = this.#query;
7302
+ let startPath = [];
7303
+ let startParent = null;
7304
+ if (from !== void 0) {
7305
+ const path = this.#toPath(from);
7306
+ const node = path && findPath(path, this.#query);
7307
+ if (!path || !node) return;
7308
+ start = node;
7309
+ startPath = path;
7310
+ startParent = path.length === 0 ? null : findPath(getParentPath(path), this.#query);
7311
+ }
7312
+ for (const entry of this.#walkFrom(start, startPath, startParent)) {
7313
+ if (rulesOnly && isRuleGroup(entry.node)) continue;
7314
+ if (groupsOnly && !isRuleGroup(entry.node)) continue;
7315
+ yield entry;
7316
+ }
7317
+ }
7318
+ /** Yields every rule in the query. Shorthand for `walk({ ...options, rulesOnly: true })`. */
7319
+ rules(options = {}) {
7320
+ return this.walk({
7321
+ ...options,
7322
+ rulesOnly: true
7323
+ });
7324
+ }
7325
+ /**
7326
+ * Yields every group in the query, including the root group. Shorthand for
7327
+ * `walk({ ...options, groupsOnly: true })`.
7328
+ */
7329
+ groups(options = {}) {
7330
+ return this.walk({
7331
+ ...options,
7332
+ groupsOnly: true
7333
+ });
7334
+ }
7335
+ /** Returns the first node matching `predicate`, or `null` if there is none. */
7336
+ find(predicate, options = {}) {
7337
+ for (const entry of this.walk(options)) if (predicate(entry)) return entry;
7338
+ return null;
7339
+ }
7340
+ /** Returns every node matching `predicate`. */
7341
+ filter(predicate, options = {}) {
7342
+ const results = [];
7343
+ for (const entry of this.walk(options)) if (predicate(entry)) results.push(entry);
7344
+ return results;
7345
+ }
7346
+ /** Equivalent to {@link QueryManager.walk} with no options, enabling `for...of` and spread. */
7347
+ [Symbol.iterator]() {
7348
+ return this.walk();
7349
+ }
7350
+ /**
7351
+ * Returns the rule or group at the given path, or `null` if the path can't be resolved.
7352
+ *
7353
+ * Unlike the standalone {@link findPath}, which can return `undefined` for an out-of-range
7354
+ * index, unresolvable paths are always normalized to `null` here.
7355
+ */
7356
+ findPath(path) {
7357
+ return findPath(path, this.#query) ?? null;
7358
+ }
7359
+ /**
7360
+ * Returns the rule or group with the given `id`, or `null` if there is none. Backed by an
7361
+ * index built once per query, so repeated lookups are constant time.
7362
+ */
7363
+ findID(id) {
7364
+ const path = this.#index().get(id);
7365
+ return path === void 0 ? null : findPath(path, this.#query);
7366
+ }
7367
+ /**
7368
+ * Returns the {@link Path} of the rule or group with the given `id`, or `null` if there is
7369
+ * none. Backed by an index built once per query, so repeated lookups are constant time.
7370
+ */
7371
+ getPathOfID(id) {
7372
+ return this.#index().get(id) ?? null;
7373
+ }
7374
+ /**
7375
+ * Determines whether the rule or group at the given path is disabled, either itself or by an
7376
+ * ancestor group.
7377
+ */
7378
+ pathIsDisabled(path) {
7379
+ return pathIsDisabled(path, this.#query);
7380
+ }
7381
+ /** Returns the rule or group at the given path or `id`, or `null` if it can't be resolved. */
7382
+ getNode(pathOrID) {
7383
+ const path = this.#toPath(pathOrID);
7384
+ return path === null ? null : findPath(path, this.#query) ?? null;
7385
+ }
7386
+ /**
7387
+ * Returns the rule at the given path or `id`, or `null` if it can't be resolved _or_ resolves
7388
+ * to a group.
7389
+ */
7390
+ getRule(pathOrID) {
7391
+ const node = this.getNode(pathOrID);
7392
+ return node && !isRuleGroup(node) ? node : null;
7393
+ }
7394
+ /**
7395
+ * Returns the group at the given path or `id`, or `null` if it can't be resolved _or_
7396
+ * resolves to a rule.
7397
+ */
7398
+ getGroup(pathOrID) {
7399
+ const node = this.getNode(pathOrID);
7400
+ return node && isRuleGroup(node) ? node : null;
7401
+ }
7402
+ /**
7403
+ * Returns the group containing the rule or group at the given path or `id`. Returns `null`
7404
+ * for the root group, which has no parent, and when the target can't be resolved.
7405
+ */
7406
+ getParent(pathOrID) {
7407
+ const path = this.#toPath(pathOrID);
7408
+ if (path === null || path.length === 0) return null;
7409
+ if (!findPath(path, this.#query)) return null;
7410
+ return findPath(getParentPath(path), this.#query);
7411
+ }
7412
+ /**
7413
+ * The normalized field list, as the `QueryBuilder` component would render it. Needed to
7414
+ * populate a field selector.
7415
+ */
7416
+ getFields() {
7417
+ return this.#fields;
7418
+ }
7419
+ /**
7420
+ * The normalized combinator list, as the `QueryBuilder` component would render it. Needed to
7421
+ * populate a combinator selector.
7422
+ */
7423
+ getCombinators() {
7424
+ return this.#combinators;
7425
+ }
7426
+ /**
7427
+ * The field configuration for a field name. When the field isn't configured, returns the same
7428
+ * minimal fallback (`{ name, value, label }`, all set to the field name) that
7429
+ * {@link QueryManager.getRuleContext} reports as `fieldData`, so both access paths agree.
7430
+ */
7431
+ getFieldData(field) {
7432
+ return getFieldData(field, this.#fieldMap);
7433
+ }
7434
+ /** The operator list for a field, mirroring `QueryBuilder`'s precedence. */
7435
+ getOperators(field) {
7436
+ return this.#operatorsFor(field);
7437
+ }
7438
+ /** The value sources available for a field/operator pair. */
7439
+ getValueSources(field, operator) {
7440
+ return this.#valueSourcesFor(field, operator);
7441
+ }
7442
+ /** The match modes available for a field. */
7443
+ getMatchModes(field) {
7444
+ return this.#matchModesFor(field);
7445
+ }
7446
+ /** The value option list for a field/operator pair. */
7447
+ getValues(field, operator) {
7448
+ return this.#valuesFor(field, operator);
7449
+ }
7450
+ /** The value editor type for a field/operator pair. */
7451
+ getValueEditorType(field, operator) {
7452
+ return this.#valueEditorTypeFor(field, operator);
7453
+ }
7454
+ /**
7455
+ * Resolves everything about a rule that depends on the field/operator configuration—field data,
7456
+ * operators, value editor type, value list, value sources, match modes, and validation result.
7457
+ * Returns `null` when the target can't be resolved or isn't a rule.
7458
+ *
7459
+ * This is the same derivation the `useRule` hook performs, so a non-React implementation can
7460
+ * render a rule without reimplementing the configuration precedence rules.
7461
+ */
7462
+ getRuleContext(pathOrID) {
7463
+ const rule = this.getRule(pathOrID);
7464
+ if (!rule) return null;
7465
+ const validation = this.validate();
7466
+ return deriveRuleContext(rule, {
7467
+ fields: this.#fields,
7468
+ fieldMap: this.#fieldMap,
7469
+ getInputType: (f, o, misc) => this.#options.getInputType?.(f, o, misc) ?? null,
7470
+ getMatchModes: (f) => this.#matchModesFor(f),
7471
+ getOperators: (f) => this.#operatorsFor(f),
7472
+ getParameters: (f, o, misc) => this.#parametersFor(f, o, misc),
7473
+ getValueEditorType: (f, o) => this.#valueEditorTypeFor(f, o),
7474
+ getValues: (f, o) => this.#valuesFor(f, o),
7475
+ getValueSources: (f, o) => this.getValueSources(f, o),
7476
+ getSubQueryBuilderProps: (f, misc) => this.#options.getSubQueryBuilderProps?.(f, misc) ?? {}
7477
+ }, {
7478
+ validationMap: typeof validation === "boolean" ? {} : validation,
7479
+ id: rule.id
7480
+ });
7481
+ }
7482
+ /**
7483
+ * Resolves everything about a rule group that depends on the combinator configuration, plus its
7484
+ * validation result. Returns `null` when the target can't be resolved or isn't a group.
7485
+ *
7486
+ * This is the same derivation the `useRuleGroup` hook performs.
7487
+ */
7488
+ getRuleGroupContext(pathOrID = []) {
7489
+ const ruleGroup = this.getGroup(pathOrID);
7490
+ if (!ruleGroup) return null;
7491
+ const validation = this.validate();
7492
+ return deriveRuleGroupContext(ruleGroup, this.#combinators, {
7493
+ validationMap: typeof validation === "boolean" ? {} : validation,
7494
+ id: ruleGroup.id
7495
+ });
7496
+ }
7497
+ /** Whether the current query uses independent combinators. */
7498
+ isIC() {
7499
+ return isRuleGroupTypeIC(this.#query);
7500
+ }
7501
+ /**
7502
+ * Returns the signature describing how the current query differs from `other`, as used by
7503
+ * this manager's history coalescing.
7504
+ */
7505
+ signatureOf(other) {
7506
+ return signatureOf(this.#query, other);
7507
+ }
7508
+ /** Generates a {@link DiagnosticsResult}. Shorthand for `format('diagnostics')`. */
7509
+ diagnostics() {
7510
+ return formatQuery(this.#query, "diagnostics");
7511
+ }
7512
+ /**
7513
+ * Returns the current query, so `JSON.stringify(queryManager)` produces the same output as
7514
+ * `JSON.stringify(queryManager.getQuery())`.
7515
+ */
7516
+ toJSON() {
7517
+ return this.#query;
7518
+ }
7519
+ /**
7520
+ * Returns a new manager with the same configuration and the current query converted to use
7521
+ * independent combinators. Idempotent, and never modifies this manager. As with
7522
+ * {@link QueryManager.clone}, subscribers and history are not carried over.
7523
+ */
7524
+ toIC() {
7525
+ return new QueryManager(convertToIC(this.#query), this.#options);
7526
+ }
7527
+ /**
7528
+ * Returns a new manager with the same configuration and the current query converted to use a
7529
+ * single combinator per group. Idempotent, and never modifies this manager. As with
7530
+ * {@link QueryManager.clone}, subscribers and history are not carried over.
7531
+ */
7532
+ fromIC() {
7533
+ return new QueryManager(convertFromIC(this.#query), this.#options);
7534
+ }
7535
+ /**
7536
+ * Runs {@link transformQuery} against the current query and returns its result.
7537
+ *
7538
+ * Unlike {@link QueryManager.toIC}/{@link QueryManager.fromIC}, this returns the raw
7539
+ * transformed value rather than a new manager, since `transformQuery` can produce arbitrary
7540
+ * shapes that are no longer valid queries. This manager is never modified.
7541
+ */
7542
+ transform(options) {
7543
+ return transformQuery(this.#query, options);
7544
+ }
7545
+ };
7546
+ //#endregion
7547
+ 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, 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 };
5435
7548
 
5436
7549
  //# sourceMappingURL=react-querybuilder_core.mjs.map