@react-querybuilder/core 8.21.2 → 8.22.1

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