react-querybuilder
Version:
React Query Builder component for constructing queries and filters, with utilities for executing them in various database and evaluation contexts
541 lines (524 loc) • 20.7 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/utils/parseJsonLogic/index.ts
var parseJsonLogic_exports = {};
__export(parseJsonLogic_exports, {
parseJsonLogic: () => parseJsonLogic
});
module.exports = __toCommonJS(parseJsonLogic_exports);
// src/defaults.ts
var defaultJoinChar = ",";
var defaultOperatorNegationMap = {
"=": "!=",
"!=": "=",
"<": ">=",
"<=": ">",
">": "<=",
">=": "<",
beginsWith: "doesNotBeginWith",
doesNotBeginWith: "beginsWith",
endsWith: "doesNotEndWith",
doesNotEndWith: "endsWith",
contains: "doesNotContain",
doesNotContain: "contains",
between: "notBetween",
notBetween: "between",
in: "notIn",
notIn: "in",
notNull: "null",
null: "notNull"
};
var defaultCombinators = [
{ name: "and", value: "and", label: "AND" },
{ name: "or", value: "or", label: "OR" }
];
var defaultCombinatorsExtended = [
...defaultCombinators,
{ name: "xor", value: "xor", label: "XOR" }
];
// src/utils/arrayUtils.ts
var joinWith = (strArr, joinChar = defaultJoinChar) => strArr.map((str) => `${str ?? ""}`.replaceAll(joinChar[0], `\\${joinChar[0]}`)).join(joinChar);
// src/utils/convertQuery.ts
var import_immer = require("immer");
// src/utils/misc.ts
var import_numeric_quantity = require("numeric-quantity");
var lc = (v) => typeof v === "string" ? v.toLowerCase() : v;
var numericRegex = new RegExp(
import_numeric_quantity.numericRegex.source.replace(/^\^/, String.raw`^\s*`).replace(/\$$/, String.raw`\s*$`)
);
var isPojo = (obj) => obj === null || typeof obj !== "object" ? false : Object.getPrototypeOf(obj) === Object.prototype;
// src/utils/isRuleGroup.ts
var isRuleGroup = (rg) => isPojo(rg) && Array.isArray(rg.rules);
var isRuleGroupType = (rg) => isRuleGroup(rg) && typeof rg.combinator === "string";
var isRuleGroupTypeIC = (rg) => isRuleGroup(rg) && rg.combinator === void 0;
// src/utils/convertQuery.ts
var convertToIC = (rg) => {
if (isRuleGroupTypeIC(rg)) {
return rg;
}
const { combinator, ...queryWithoutCombinator } = rg;
const rules = [];
const { length } = rg.rules;
for (const [idx, r] of rg.rules.entries()) {
if (isRuleGroup(r)) {
rules.push(convertToIC(r));
} else {
rules.push(r);
}
if (combinator && idx < length - 1) {
rules.push(combinator);
}
}
return { ...queryWithoutCombinator, rules };
};
// src/utils/objectUtils.ts
var objectKeys = Object.keys;
// src/utils/optGroupUtils.ts
var import_immer2 = require("immer");
var isOptionWithName = (opt) => isPojo(opt) && "name" in opt && typeof opt.name === "string";
var isOptionWithValue = (opt) => isPojo(opt) && "value" in opt && typeof opt.value === "string";
function toFullOption(opt, baseProperties, labelMap) {
const recipe = (0, import_immer2.produce)((draft) => {
const idObj = {};
let needsUpdating = !!baseProperties;
if (typeof draft === "string") {
return {
...baseProperties,
name: draft,
value: draft,
label: labelMap?.[draft] ?? draft
};
}
if (isOptionWithName(draft) && !isOptionWithValue(draft)) {
idObj.value = draft.name;
needsUpdating = true;
} else if (!isOptionWithName(draft) && isOptionWithValue(draft)) {
idObj.name = draft.value;
needsUpdating = true;
}
if (needsUpdating) {
return Object.assign({}, baseProperties, draft, idObj);
}
});
return recipe(opt);
}
function toFullOptionList(optList, baseProperties, labelMap) {
if (!Array.isArray(optList)) {
return [];
}
const recipe = (0, import_immer2.produce)((draft) => {
if (isFlexibleOptionGroupArray(draft)) {
for (const optGroup of draft) {
for (const [idx, opt] of optGroup.options.entries())
optGroup.options[idx] = toFullOption(opt, baseProperties, labelMap);
}
} else {
for (const [idx, opt] of draft.entries())
draft[idx] = toFullOption(opt, baseProperties, labelMap);
}
});
return recipe(optList);
}
var uniqByIdentifier = (originalArray) => {
const names = /* @__PURE__ */ new Set();
const newArray = [];
for (const el of originalArray) {
if (!names.has(el.value ?? el.name)) {
names.add(el.value ?? el.name);
newArray.push(el);
}
}
return originalArray.length === newArray.length ? originalArray : newArray;
};
var isOptionGroupArray = (arr) => Array.isArray(arr) && arr.length > 0 && isPojo(arr[0]) && "options" in arr[0] && Array.isArray(arr[0].options);
var isFlexibleOptionArray = (arr) => {
let isFOA = false;
if (Array.isArray(arr)) {
for (const o of arr) {
if (isOptionWithName(o) || isOptionWithValue(o)) {
isFOA = true;
} else {
return false;
}
}
}
return isFOA;
};
var isFlexibleOptionGroupArray = (arr, { allowEmpty = false } = {}) => {
let isFOGA = false;
if (Array.isArray(arr)) {
for (const og of arr) {
if (isPojo(og) && "options" in og && (isFlexibleOptionArray(og.options) || allowEmpty && Array.isArray(og.options) && og.options.length === 0)) {
isFOGA = true;
} else {
return false;
}
}
}
return isFOGA;
};
var toFlatOptionArray = (arr) => uniqByIdentifier(isOptionGroupArray(arr) ? arr.flatMap((og) => og.options) : arr);
// src/utils/filterFieldsByComparator.ts
var filterByComparator = (field, operator, fieldToCompare) => {
const fullField = toFullOption(field);
const fullFieldToCompare = toFullOption(fieldToCompare);
if (fullField.value === fullFieldToCompare.value) {
return false;
}
if (typeof fullField.comparator === "string") {
return fullField[fullField.comparator] === fullFieldToCompare[fullField.comparator];
}
return fullField.comparator?.(fullFieldToCompare, operator) ?? /* istanbul ignore next */
false;
};
var filterFieldsByComparator = (field, fields, operator) => {
if (!field.comparator) {
const filterOutSameField = (f) => (f.value ?? /* istanbul ignore next */
f.name) !== (field.value ?? /* istanbul ignore next */
field.name);
if (isFlexibleOptionGroupArray(fields)) {
return fields.map((og) => ({
...og,
options: og.options.filter((v) => filterOutSameField(v))
}));
}
return fields.filter((v) => filterOutSameField(v));
}
if (isFlexibleOptionGroupArray(fields)) {
return fields.map((og) => ({
...og,
options: og.options.filter((f) => filterByComparator(field, operator, f))
})).filter((og) => og.options.length > 0);
}
return fields.filter((f) => filterByComparator(field, operator, f));
};
// src/utils/getValueSourcesUtil.ts
var defaultValueSourcesArray = [
{ name: "value", value: "value", label: "value" }
];
var dummyFD = {
name: "name",
value: "name",
valueSources: null,
label: "label"
};
var getValueSourcesUtil = (fieldData, operator, getValueSources) => {
const fd = fieldData ? toFullOption(fieldData) : (
/* istanbul ignore else */
dummyFD
);
let valueSourcesNEW = fd.valueSources ?? false;
if (typeof valueSourcesNEW === "function") {
valueSourcesNEW = valueSourcesNEW(operator);
}
if (!valueSourcesNEW && getValueSources) {
valueSourcesNEW = getValueSources(fd.value, operator, {
fieldData: fd
});
}
if (!valueSourcesNEW) {
return defaultValueSourcesArray;
}
if (isFlexibleOptionArray(valueSourcesNEW)) {
return toFullOptionList(valueSourcesNEW);
}
return valueSourcesNEW.map(
(vs) => defaultValueSourcesArray.find((dmm) => dmm.value === lc(vs)) ?? {
name: vs,
value: vs,
label: vs
}
);
};
// src/utils/parserUtils.ts
var getFieldsArray = (fields) => {
const fieldsArray = fields ? Array.isArray(fields) ? fields : Object.keys(fields).map((fld) => ({ ...fields[fld], name: fld })).sort((a, b) => a.label.localeCompare(b.label)) : [];
return toFlatOptionArray(fieldsArray);
};
function fieldIsValidUtil(params) {
const { fieldsFlat, fieldName, operator, subordinateFieldName, getValueSources } = params;
const vsIncludes = (vs) => {
const vss = getValueSourcesUtil(primaryField, operator, getValueSources);
return isFlexibleOptionArray(vss) && vss.some((vso) => vso.value === vs || vso.name === vs);
};
if (fieldsFlat.length === 0) return true;
let valid = false;
const primaryField = toFullOption(fieldsFlat.find((ff) => ff.name === fieldName));
if (primaryField) {
valid = !(!subordinateFieldName && operator !== "notNull" && operator !== "null" && !vsIncludes("value"));
if (valid && !!subordinateFieldName) {
if (vsIncludes("field") && fieldName !== subordinateFieldName) {
const validSubordinateFields = filterFieldsByComparator(
primaryField,
fieldsFlat,
operator
);
if (!validSubordinateFields.some((vsf) => vsf.name === subordinateFieldName)) {
valid = false;
}
} else {
valid = false;
}
}
}
return valid;
}
// src/utils/prepareQueryObjects.ts
var import_immer3 = require("immer");
// src/utils/generateID.ts
var cryptoModule = globalThis.crypto;
var generateID = () => "00-0-4-2-000".replaceAll(
/[^-]/g,
(s) => ((Math.random() + Math.trunc(s)) * 65536 >> Number.parseInt(s)).toString(16).padStart(4, "0")
);
if (cryptoModule) {
if (typeof cryptoModule.randomUUID === "function") {
generateID = () => cryptoModule.randomUUID();
} else if (typeof cryptoModule.getRandomValues === "function") {
const position19vals = "89ab";
const container = new Uint32Array(32);
generateID = () => {
cryptoModule.getRandomValues(container);
let id = (container[0] % 16).toString(16);
for (let i = 1; i < 32; i++) {
if (i === 12) {
id = `${id}${"4"}`;
} else if (i === 16) {
id = `${id}${position19vals[container[17] % 4]}`;
} else {
id = `${id}${(container[i] % 16).toString(16)}`;
}
if (i === 7 || i === 11 || i === 15 || i === 19) {
id = `${id}${"-"}`;
}
}
return id;
};
}
}
// src/utils/prepareQueryObjects.ts
var prepareRule = (rule, { idGenerator = generateID } = {}) => (0, import_immer3.produce)(rule, (draft) => {
if (!draft.id) {
draft.id = idGenerator();
}
});
var prepareRuleGroup = (queryObject, { idGenerator = generateID } = {}) => (0, import_immer3.produce)(queryObject, (draft) => {
if (!draft.id) {
draft.id = idGenerator();
}
draft.rules = draft.rules.map(
(r) => typeof r === "string" ? r : isRuleGroup(r) ? prepareRuleGroup(r, { idGenerator }) : prepareRule(r, { idGenerator })
);
});
// src/utils/parseJsonLogic/utils.ts
var isJsonLogicVar = (logic) => isPojo(logic) && "var" in logic;
var isRQBJsonLogicVar = (logic) => isJsonLogicVar(logic) && typeof logic.var === "string";
var isJsonLogicEqual = (logic) => isPojo(logic) && "==" in logic;
var isJsonLogicStrictEqual = (logic) => isPojo(logic) && "===" in logic;
var isJsonLogicNotEqual = (logic) => isPojo(logic) && "!=" in logic;
var isJsonLogicStrictNotEqual = (logic) => isPojo(logic) && "!==" in logic;
var isJsonLogicNegation = (logic) => isPojo(logic) && "!" in logic;
var isJsonLogicDoubleNegation = (logic) => isPojo(logic) && "!!" in logic;
var isJsonLogicOr = (logic) => isPojo(logic) && "or" in logic;
var isJsonLogicAnd = (logic) => isPojo(logic) && "and" in logic;
var isJsonLogicGreaterThan = (logic) => isPojo(logic) && ">" in logic;
var isJsonLogicGreaterThanOrEqual = (logic) => isPojo(logic) && ">=" in logic;
var isJsonLogicLessThan = (logic) => isPojo(logic) && "<" in logic && logic["<"].length === 2;
var isJsonLogicLessThanOrEqual = (logic) => isPojo(logic) && "<=" in logic && logic["<="].length === 2;
var isJsonLogicInArray = (logic) => isPojo(logic) && "in" in logic && Array.isArray(logic.in[1]);
var isJsonLogicInString = (logic) => isPojo(logic) && "in" in logic && !Array.isArray(logic.in[1]);
var isJsonLogicAll = (logic) => isPojo(logic) && "all" in logic;
var isJsonLogicNone = (logic) => isPojo(logic) && "none" in logic;
var isJsonLogicSome = (logic) => isPojo(logic) && "some" in logic;
var isJsonLogicBetweenExclusive = (logic) => isPojo(logic) && "<" in logic && Array.isArray(logic["<"]) && logic["<"].length === 3;
var isJsonLogicBetweenInclusive = (logic) => isPojo(logic) && "<=" in logic && Array.isArray(logic["<="]) && logic["<="].length === 3;
var isRQBJsonLogicStartsWith = (logic) => isPojo(logic) && "startsWith" in logic;
var isRQBJsonLogicEndsWith = (logic) => isPojo(logic) && "endsWith" in logic;
// src/utils/parseJsonLogic/parseJsonLogic.ts
var emptyRuleGroup = { combinator: "and", rules: [] };
function parseJsonLogic(rqbJsonLogic, options = {}) {
const fieldsFlat = getFieldsArray(options.fields);
const { getValueSources, listsAsArrays, jsonLogicOperations } = options;
const fieldIsValid = (fieldName, operator, subordinateFieldName) => fieldIsValidUtil({
fieldName,
fieldsFlat,
operator,
subordinateFieldName,
getValueSources
});
function processLogic(logic, outermost) {
if (outermost && !isPojo(logic)) {
return false;
}
const [key, keyValue] = Object.entries(logic || {})?.[0] ?? [];
if (jsonLogicOperations && objectKeys(jsonLogicOperations).includes(key)) {
const rule2 = jsonLogicOperations[key](keyValue);
return rule2 ? outermost && !isRuleGroup(rule2) ? { combinator: "and", rules: [rule2] } : rule2 : false;
}
if (isJsonLogicAnd(logic)) {
return {
combinator: "and",
rules: logic.and.map((l) => processLogic(l)).filter(Boolean)
};
} else if (isJsonLogicOr(logic)) {
return {
combinator: "or",
rules: logic.or.map((l) => processLogic(l)).filter(Boolean)
};
} else if (isJsonLogicNegation(logic)) {
const rule2 = processLogic(logic["!"]);
if (rule2) {
if (!isRuleGroupType(rule2) && (rule2.operator === "between" || rule2.operator === "in" || rule2.operator === "contains" || rule2.operator === "beginsWith" || rule2.operator === "endsWith")) {
const newRule = { ...rule2, operator: defaultOperatorNegationMap[rule2.operator] };
if (outermost) {
return { combinator: "and", rules: [newRule] };
}
return newRule;
} else if (isJsonLogicBetweenExclusive(logic["!"]) || isRuleGroupType(rule2)) {
return { ...rule2, not: true };
}
return { combinator: "and", rules: [rule2], not: true };
}
return false;
} else if (isJsonLogicDoubleNegation(logic)) {
const rule2 = processLogic(logic["!!"]);
return rule2 || false;
}
let rule = false;
let field = "";
let operator = "=";
let value = "";
let valueSource = void 0;
if (
// Basic boolean operations
isJsonLogicEqual(logic) || isJsonLogicStrictEqual(logic) || isJsonLogicNotEqual(logic) || isJsonLogicStrictNotEqual(logic) || isJsonLogicGreaterThan(logic) || isJsonLogicGreaterThanOrEqual(logic) || isJsonLogicLessThan(logic) || isJsonLogicLessThanOrEqual(logic) || isJsonLogicInString(logic) || isRQBJsonLogicStartsWith(logic) || isRQBJsonLogicEndsWith(logic)
) {
const [first, second] = keyValue;
if (isRQBJsonLogicVar(first) && !isPojo(second)) {
field = first.var;
value = second;
} else if (!isPojo(first) && isRQBJsonLogicVar(second)) {
field = second.var;
value = first;
} else if (isRQBJsonLogicVar(first) && isRQBJsonLogicVar(second)) {
field = first.var;
value = second.var;
valueSource = "field";
} else {
return false;
}
if (isJsonLogicEqual(logic) || isJsonLogicStrictEqual(logic)) {
operator = value === null ? "null" : "=";
} else if (isJsonLogicNotEqual(logic) || isJsonLogicStrictNotEqual(logic)) {
operator = value === null ? "notNull" : "!=";
} else if (isJsonLogicInString(logic)) {
operator = "contains";
} else if (isRQBJsonLogicStartsWith(logic)) {
operator = "beginsWith";
} else if (isRQBJsonLogicEndsWith(logic)) {
operator = "endsWith";
} else {
operator = key;
}
if (fieldIsValid(field, operator, valueSource === "field" ? value : void 0)) {
rule = { field, operator, value, valueSource };
}
} else if (isJsonLogicAll(logic) && isRQBJsonLogicVar(logic["all"][0]) || isJsonLogicNone(logic) && isRQBJsonLogicVar(logic["none"][0]) || isJsonLogicSome(logic) && isRQBJsonLogicVar(logic["some"][0])) {
const match = {
mode: isJsonLogicNone(logic) ? "none" : isJsonLogicSome(logic) ? "some" : "all"
};
const [{ var: field2 }, operation] = logic[match.mode];
const matcher = processLogic(operation);
if (!matcher) return false;
rule = {
field: field2,
operator: "=",
match,
value: isRuleGroup(matcher) ? matcher : { combinator: "and", rules: [matcher] }
};
} else if (isJsonLogicBetweenExclusive(logic) && isRQBJsonLogicVar(logic["<"][1])) {
field = logic["<"][1].var;
const values = [logic["<"][0], logic["<"][2]];
if (values.every((v) => isRQBJsonLogicVar(v)) || values.every((el) => typeof el === "string") || values.every((el) => typeof el === "number") || values.every((el) => typeof el === "boolean")) {
return processLogic({
and: [{ ">": [{ var: field }, values[0]] }, { "<": [{ var: field }, values[1]] }]
}) || /* istanbul ignore next */
false;
}
} else if (isJsonLogicBetweenInclusive(logic) && isRQBJsonLogicVar(logic["<="][1])) {
field = logic["<="][1].var;
operator = "between";
const values = [logic["<="][0], logic["<="][2]];
if (logic["<="].every((v) => isRQBJsonLogicVar(v))) {
const vars = values;
valueSource = "field";
const fieldList = vars.map((el) => el.var).filter((sf) => fieldIsValid(field, operator, sf));
value = listsAsArrays ? fieldList : joinWith(fieldList, ",");
} else {
if (values.every((el) => typeof el === "string") || values.every((el) => typeof el === "number") || values.every((el) => typeof el === "boolean")) {
value = listsAsArrays ? values : joinWith(
values.map((el) => `${el}`),
","
);
}
}
if (fieldIsValid(field, operator) && value.length >= 2) {
rule = { field, operator, value, valueSource };
}
} else if (isJsonLogicInArray(logic) && isRQBJsonLogicVar(keyValue[0])) {
field = keyValue[0].var;
operator = "in";
if (logic.in[1].every((v) => isRQBJsonLogicVar(v))) {
valueSource = "field";
const fieldList = logic.in[1].map((el) => el.var).filter((sf) => fieldIsValid(field, operator, sf));
value = listsAsArrays ? fieldList : joinWith(fieldList, ",");
} else {
if (logic.in[1].every((el) => typeof el === "string") || logic.in[1].every((el) => typeof el === "number") || logic.in[1].every((el) => typeof el === "boolean")) {
value = listsAsArrays ? logic.in[1] : joinWith(
logic.in[1].map((el) => `${el}`),
","
);
}
}
if (value.length > 0) {
rule = { field, operator, value, valueSource };
}
}
return rule ? outermost ? { combinator: "and", rules: [rule] } : rule : false;
}
const prepare = options.generateIDs ? prepareRuleGroup : (g) => g;
let logicRoot = rqbJsonLogic;
if (typeof rqbJsonLogic === "string") {
try {
logicRoot = JSON.parse(rqbJsonLogic);
} catch {
return prepare(emptyRuleGroup);
}
}
const result = processLogic(logicRoot, true);
const finalQuery = result || emptyRuleGroup;
return prepare(
options.independentCombinators ? convertToIC(finalQuery) : finalQuery
);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
parseJsonLogic
});
//# sourceMappingURL=parseJsonLogic.js.map