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