@ai-matrx/records-ui 0.115.79 → 0.115.80

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.
package/dist/pickers.js CHANGED
@@ -4842,6 +4842,33 @@ var CONDITION_OPS = [
4842
4842
  { op: "gte", label: "is at least" },
4843
4843
  { op: "lte", label: "is at most" }
4844
4844
  ];
4845
+ var LIST_TERMS = [
4846
+ { op: "contains", label: "contains" },
4847
+ { op: "not_contains", label: "does not contain" },
4848
+ { op: "is_empty", label: "is empty" },
4849
+ { op: "is_not_empty", label: "is not empty" }
4850
+ ];
4851
+ var VALUELESS_OPS = /* @__PURE__ */ new Set(["present", "is_empty", "is_not_empty"]);
4852
+ var LINK_SEP = ">";
4853
+ function linkedNode(viaId, innerId, op, value) {
4854
+ return {
4855
+ op: "linked",
4856
+ match: op === "eq" ? "any" : "none",
4857
+ args: [{ field: viaId }, { op: "eq", args: [{ field: innerId }, { const: value }] }]
4858
+ };
4859
+ }
4860
+ function linkedOf(expr) {
4861
+ const node = asNode(expr);
4862
+ if (!node || node["op"] !== "linked") return null;
4863
+ const [rel, cond] = argsOf(node);
4864
+ const innerNode = asNode(cond);
4865
+ const [a, b] = innerNode ? argsOf(innerNode) : [];
4866
+ const match = node["match"] ?? "any";
4867
+ if (!rel || typeof rel["field"] !== "string" || !innerNode || innerNode["op"] !== "eq") return null;
4868
+ if (!a || typeof a["field"] !== "string" || !b || !("const" in b)) return null;
4869
+ if (match !== "any" && match !== "none") return null;
4870
+ return { via: rel["field"], inner: a["field"], op: match === "any" ? "eq" : "ne", value: b["const"] };
4871
+ }
4845
4872
  var FilterTermsContext = createContext4(false);
4846
4873
  var RELATIVE_DAY_TERMS = [
4847
4874
  { kind: "today", label: "is today" },
@@ -4954,13 +4981,15 @@ function ChoiceValue({ field, value, onChange }) {
4954
4981
  }
4955
4982
  function conditionIsSimple(expr) {
4956
4983
  if (expr === null || expr === void 0) return true;
4957
- if (relativeDayOf(expr) !== null || meOf(expr) !== null) return true;
4984
+ if (relativeDayOf(expr) !== null || meOf(expr) !== null || linkedOf(expr) !== null) return true;
4958
4985
  const node = expr;
4959
- if (typeof node.op !== "string" || !CONDITION_OPS.some((c) => c.op === node.op)) return false;
4986
+ if (typeof node.op !== "string" || !(CONDITION_OPS.some((c) => c.op === node.op) || LIST_TERMS.some((c) => c.op === node.op))) {
4987
+ return false;
4988
+ }
4960
4989
  if (!Array.isArray(node.args)) return false;
4961
4990
  const first = node.args[0];
4962
4991
  if (!first || typeof first.field !== "string") return false;
4963
- if (node.op === "present") return node.args.length === 1;
4992
+ if (VALUELESS_OPS.has(node.op)) return node.args.length === 1;
4964
4993
  const second = node.args[1];
4965
4994
  return node.args.length === 2 && !!second && "const" in second;
4966
4995
  }
@@ -4978,7 +5007,13 @@ function conditionInWords(expr, fields) {
4978
5007
  lte: "is at most",
4979
5008
  and: "and",
4980
5009
  or: "or",
4981
- matches: "matches"
5010
+ matches: "matches",
5011
+ contains: "contains",
5012
+ not_contains: "does not contain",
5013
+ has_any: "has any of",
5014
+ has_all: "has all of",
5015
+ has_none: "has none of",
5016
+ is_exactly: "is exactly"
4982
5017
  };
4983
5018
  const walk = (node) => {
4984
5019
  if (node === null || node === void 0) return "nothing";
@@ -4999,6 +5034,12 @@ function conditionInWords(expr, fields) {
4999
5034
  const args = Array.isArray(n["args"]) ? n["args"] : [];
5000
5035
  if (op === "not") return `not ${walk(args[0])}`;
5001
5036
  if (op === "present") return `${walk(args[0])} has been answered`;
5037
+ if (op === "is_empty") return `${walk(args[0])} is empty`;
5038
+ if (op === "is_not_empty") return `${walk(args[0])} is not empty`;
5039
+ if (op === "linked") {
5040
+ const how = n["match"] === "every" ? "every" : n["match"] === "none" ? "none of" : "any";
5041
+ return `${how} linked record in ${walk(args[0])}: ${walk(args[1])}`;
5042
+ }
5002
5043
  const dayRange = relativeDayOf(n);
5003
5044
  if (dayRange) {
5004
5045
  const term = RELATIVE_DAY_TERMS.find((t) => t.kind === dayRange.kind);
@@ -5031,12 +5072,14 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5031
5072
  const filterTerms = useContext4(FilterTermsContext);
5032
5073
  const relative = relativeDayOf(expr);
5033
5074
  const reader = meOf(expr);
5034
- const op = relative ? `rel:${relative.kind}` : reader ? reader.which : node?.op ?? "";
5035
- const onField = node?.args?.[0]?.field ?? "";
5036
- const value = relative || reader ? "" : node?.args?.[1]?.const ?? "";
5075
+ const linked = linkedOf(expr);
5076
+ const op = relative ? `rel:${relative.kind}` : reader ? reader.which : linked ? linked.op : node?.op ?? "";
5077
+ const onField = linked ? `${linked.via}${LINK_SEP}${linked.inner}` : node?.args?.[0]?.field ?? "";
5078
+ const value = relative || reader ? "" : linked ? linked.value : node?.args?.[1]?.const ?? "";
5037
5079
  const chosen = fields.find((f) => f.id === onField);
5038
5080
  const chosenKind = chosen?.field ? String(editorKindFor(chosen.field)) : "";
5039
- const extraTerms = !filterTerms ? [] : chosenKind === "datetime" ? RELATIVE_DAY_TERMS.map((t) => ({ op: `rel:${t.kind}`, label: t.label })) : chosenKind === "member" ? [{ op: "me", label: "is me" }, { op: "not_me", label: "is not me" }] : [];
5081
+ const extraTerms = !filterTerms || chosen?.via ? [] : chosenKind === "datetime" ? RELATIVE_DAY_TERMS.map((t) => ({ op: `rel:${t.kind}`, label: t.label })) : chosenKind === "member" ? [{ op: "me", label: "is me" }, { op: "not_me", label: "is not me" }] : chosenKind === "multi_select" ? LIST_TERMS : [];
5082
+ const baseTerms = chosen?.via ? CONDITION_OPS.filter((c) => c.op === "eq" || c.op === "ne") : CONDITION_OPS;
5040
5083
  const relativeTerm = relative ? RELATIVE_DAY_TERMS.find((t) => t.kind === relative.kind) : void 0;
5041
5084
  function write(next) {
5042
5085
  const picked = next.op;
@@ -5049,8 +5092,11 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5049
5092
  onChange(relativeDayNode(fieldNow, relative.kind, next.days));
5050
5093
  return;
5051
5094
  }
5052
- if (next.field !== void 0 && (relative || reader)) {
5053
- onChange(next.field ? { op: CONDITION_OPS[0].op, args: [{ field: next.field }, { const: "" }] } : null);
5095
+ if (next.field !== void 0 && (relative || reader || linked)) {
5096
+ const target2 = fields.find((f) => f.id === next.field);
5097
+ onChange(
5098
+ !next.field ? null : target2?.via ? linkedNode(target2.via.id, target2.via.fieldId, "eq", "") : { op: CONDITION_OPS[0].op, args: [{ field: next.field }, { const: "" }] }
5099
+ );
5054
5100
  return;
5055
5101
  }
5056
5102
  const chosenOp = next.op ?? (op || (next.field ? CONDITION_OPS[0].op : ""));
@@ -5060,8 +5106,13 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5060
5106
  onChange(null);
5061
5107
  return;
5062
5108
  }
5109
+ const target = fields.find((f) => f.id === chosenField);
5110
+ if (target?.via) {
5111
+ onChange(linkedNode(target.via.id, target.via.fieldId, chosenOp === "ne" ? "ne" : "eq", chosenValue));
5112
+ return;
5113
+ }
5063
5114
  onChange(
5064
- chosenOp === "present" ? { op: "present", args: [{ field: chosenField }] } : { op: chosenOp, args: [{ field: chosenField }, { const: chosenValue }] }
5115
+ VALUELESS_OPS.has(chosenOp) ? { op: chosenOp, args: [{ field: chosenField }] } : { op: chosenOp, args: [{ field: chosenField }, { const: chosenValue }] }
5065
5116
  );
5066
5117
  }
5067
5118
  return /* @__PURE__ */ jsxs23("div", { className: className ?? "flex flex-wrap items-center gap-1 text-xs", children: [
@@ -5094,7 +5145,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5094
5145
  className: "h-8 w-auto min-w-[6rem] text-xs",
5095
5146
  value: op,
5096
5147
  onChange: (next) => write({ op: next }),
5097
- options: [...CONDITION_OPS, ...extraTerms].map((c) => ({ value: c.op, label: c.label })),
5148
+ options: [...baseTerms, ...extraTerms].map((c) => ({ value: c.op, label: c.label })),
5098
5149
  attrs: { "data-condition": "comparison" }
5099
5150
  }
5100
5151
  ),
@@ -5108,7 +5159,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5108
5159
  value: String(relative.days ?? 7),
5109
5160
  onChange: (e) => write({ days: Math.max(0, Math.floor(Number(e.target.value) || 0)) })
5110
5161
  }
5111
- ) : null : reader || op === "present" ? null : pointsAtARecord(chosen) ? (
5162
+ ) : null : reader || VALUELESS_OPS.has(op) ? null : pointsAtARecord(chosen) ? (
5112
5163
  // A RECORD IS PICKED BY ITS WORDS, NEVER TYPED AS AN ID. The same picker the
5113
5164
  // cell uses, on the same `relation_words_many` the cell reads, so a filter and
5114
5165
  // the column it filters can never disagree about what a record is called. It
@@ -5126,7 +5177,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5126
5177
  allowCreate: false
5127
5178
  }
5128
5179
  )
5129
- ) : isChoiceColumn(chosen) && (op === "eq" || op === "ne") ? /* @__PURE__ */ jsx25(ChoiceValue, { field: chosen.field, value: String(value ?? ""), onChange: (word) => write({ value: word }) }) : /* @__PURE__ */ jsx25(
5180
+ ) : isChoiceColumn(chosen) && (op === "eq" || op === "ne" || op === "contains" || op === "not_contains") ? /* @__PURE__ */ jsx25(ChoiceValue, { field: chosen.field, value: String(value ?? ""), onChange: (word) => write({ value: word }) }) : /* @__PURE__ */ jsx25(
5130
5181
  BasicInput7,
5131
5182
  {
5132
5183
  className: "h-8 w-28 text-xs",