@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.cjs CHANGED
@@ -4778,6 +4778,33 @@ var CONDITION_OPS = [
4778
4778
  { op: "gte", label: "is at least" },
4779
4779
  { op: "lte", label: "is at most" }
4780
4780
  ];
4781
+ var LIST_TERMS = [
4782
+ { op: "contains", label: "contains" },
4783
+ { op: "not_contains", label: "does not contain" },
4784
+ { op: "is_empty", label: "is empty" },
4785
+ { op: "is_not_empty", label: "is not empty" }
4786
+ ];
4787
+ var VALUELESS_OPS = /* @__PURE__ */ new Set(["present", "is_empty", "is_not_empty"]);
4788
+ var LINK_SEP = ">";
4789
+ function linkedNode(viaId, innerId, op, value) {
4790
+ return {
4791
+ op: "linked",
4792
+ match: op === "eq" ? "any" : "none",
4793
+ args: [{ field: viaId }, { op: "eq", args: [{ field: innerId }, { const: value }] }]
4794
+ };
4795
+ }
4796
+ function linkedOf(expr) {
4797
+ const node = asNode(expr);
4798
+ if (!node || node["op"] !== "linked") return null;
4799
+ const [rel, cond] = argsOf(node);
4800
+ const innerNode = asNode(cond);
4801
+ const [a, b] = innerNode ? argsOf(innerNode) : [];
4802
+ const match = node["match"] ?? "any";
4803
+ if (!rel || typeof rel["field"] !== "string" || !innerNode || innerNode["op"] !== "eq") return null;
4804
+ if (!a || typeof a["field"] !== "string" || !b || !("const" in b)) return null;
4805
+ if (match !== "any" && match !== "none") return null;
4806
+ return { via: rel["field"], inner: a["field"], op: match === "any" ? "eq" : "ne", value: b["const"] };
4807
+ }
4781
4808
  var FilterTermsContext = (0, import_react36.createContext)(false);
4782
4809
  var RELATIVE_DAY_TERMS = [
4783
4810
  { kind: "today", label: "is today" },
@@ -4890,13 +4917,15 @@ function ChoiceValue({ field, value, onChange }) {
4890
4917
  }
4891
4918
  function conditionIsSimple(expr) {
4892
4919
  if (expr === null || expr === void 0) return true;
4893
- if (relativeDayOf(expr) !== null || meOf(expr) !== null) return true;
4920
+ if (relativeDayOf(expr) !== null || meOf(expr) !== null || linkedOf(expr) !== null) return true;
4894
4921
  const node = expr;
4895
- if (typeof node.op !== "string" || !CONDITION_OPS.some((c) => c.op === node.op)) return false;
4922
+ if (typeof node.op !== "string" || !(CONDITION_OPS.some((c) => c.op === node.op) || LIST_TERMS.some((c) => c.op === node.op))) {
4923
+ return false;
4924
+ }
4896
4925
  if (!Array.isArray(node.args)) return false;
4897
4926
  const first = node.args[0];
4898
4927
  if (!first || typeof first.field !== "string") return false;
4899
- if (node.op === "present") return node.args.length === 1;
4928
+ if (VALUELESS_OPS.has(node.op)) return node.args.length === 1;
4900
4929
  const second = node.args[1];
4901
4930
  return node.args.length === 2 && !!second && "const" in second;
4902
4931
  }
@@ -4914,7 +4943,13 @@ function conditionInWords(expr, fields) {
4914
4943
  lte: "is at most",
4915
4944
  and: "and",
4916
4945
  or: "or",
4917
- matches: "matches"
4946
+ matches: "matches",
4947
+ contains: "contains",
4948
+ not_contains: "does not contain",
4949
+ has_any: "has any of",
4950
+ has_all: "has all of",
4951
+ has_none: "has none of",
4952
+ is_exactly: "is exactly"
4918
4953
  };
4919
4954
  const walk = (node) => {
4920
4955
  if (node === null || node === void 0) return "nothing";
@@ -4935,6 +4970,12 @@ function conditionInWords(expr, fields) {
4935
4970
  const args = Array.isArray(n["args"]) ? n["args"] : [];
4936
4971
  if (op === "not") return `not ${walk(args[0])}`;
4937
4972
  if (op === "present") return `${walk(args[0])} has been answered`;
4973
+ if (op === "is_empty") return `${walk(args[0])} is empty`;
4974
+ if (op === "is_not_empty") return `${walk(args[0])} is not empty`;
4975
+ if (op === "linked") {
4976
+ const how = n["match"] === "every" ? "every" : n["match"] === "none" ? "none of" : "any";
4977
+ return `${how} linked record in ${walk(args[0])}: ${walk(args[1])}`;
4978
+ }
4938
4979
  const dayRange = relativeDayOf(n);
4939
4980
  if (dayRange) {
4940
4981
  const term = RELATIVE_DAY_TERMS.find((t) => t.kind === dayRange.kind);
@@ -4967,12 +5008,14 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
4967
5008
  const filterTerms = (0, import_react36.useContext)(FilterTermsContext);
4968
5009
  const relative = relativeDayOf(expr);
4969
5010
  const reader = meOf(expr);
4970
- const op = relative ? `rel:${relative.kind}` : reader ? reader.which : node?.op ?? "";
4971
- const onField = node?.args?.[0]?.field ?? "";
4972
- const value = relative || reader ? "" : node?.args?.[1]?.const ?? "";
5011
+ const linked = linkedOf(expr);
5012
+ const op = relative ? `rel:${relative.kind}` : reader ? reader.which : linked ? linked.op : node?.op ?? "";
5013
+ const onField = linked ? `${linked.via}${LINK_SEP}${linked.inner}` : node?.args?.[0]?.field ?? "";
5014
+ const value = relative || reader ? "" : linked ? linked.value : node?.args?.[1]?.const ?? "";
4973
5015
  const chosen = fields.find((f) => f.id === onField);
4974
5016
  const chosenKind = chosen?.field ? String(editorKindFor(chosen.field)) : "";
4975
- 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" }] : [];
5017
+ 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 : [];
5018
+ const baseTerms = chosen?.via ? CONDITION_OPS.filter((c) => c.op === "eq" || c.op === "ne") : CONDITION_OPS;
4976
5019
  const relativeTerm = relative ? RELATIVE_DAY_TERMS.find((t) => t.kind === relative.kind) : void 0;
4977
5020
  function write(next) {
4978
5021
  const picked = next.op;
@@ -4985,8 +5028,11 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
4985
5028
  onChange(relativeDayNode(fieldNow, relative.kind, next.days));
4986
5029
  return;
4987
5030
  }
4988
- if (next.field !== void 0 && (relative || reader)) {
4989
- onChange(next.field ? { op: CONDITION_OPS[0].op, args: [{ field: next.field }, { const: "" }] } : null);
5031
+ if (next.field !== void 0 && (relative || reader || linked)) {
5032
+ const target2 = fields.find((f) => f.id === next.field);
5033
+ onChange(
5034
+ !next.field ? null : target2?.via ? linkedNode(target2.via.id, target2.via.fieldId, "eq", "") : { op: CONDITION_OPS[0].op, args: [{ field: next.field }, { const: "" }] }
5035
+ );
4990
5036
  return;
4991
5037
  }
4992
5038
  const chosenOp = next.op ?? (op || (next.field ? CONDITION_OPS[0].op : ""));
@@ -4996,8 +5042,13 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
4996
5042
  onChange(null);
4997
5043
  return;
4998
5044
  }
5045
+ const target = fields.find((f) => f.id === chosenField);
5046
+ if (target?.via) {
5047
+ onChange(linkedNode(target.via.id, target.via.fieldId, chosenOp === "ne" ? "ne" : "eq", chosenValue));
5048
+ return;
5049
+ }
4999
5050
  onChange(
5000
- chosenOp === "present" ? { op: "present", args: [{ field: chosenField }] } : { op: chosenOp, args: [{ field: chosenField }, { const: chosenValue }] }
5051
+ VALUELESS_OPS.has(chosenOp) ? { op: chosenOp, args: [{ field: chosenField }] } : { op: chosenOp, args: [{ field: chosenField }, { const: chosenValue }] }
5001
5052
  );
5002
5053
  }
5003
5054
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: className ?? "flex flex-wrap items-center gap-1 text-xs", children: [
@@ -5030,7 +5081,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5030
5081
  className: "h-8 w-auto min-w-[6rem] text-xs",
5031
5082
  value: op,
5032
5083
  onChange: (next) => write({ op: next }),
5033
- options: [...CONDITION_OPS, ...extraTerms].map((c) => ({ value: c.op, label: c.label })),
5084
+ options: [...baseTerms, ...extraTerms].map((c) => ({ value: c.op, label: c.label })),
5034
5085
  attrs: { "data-condition": "comparison" }
5035
5086
  }
5036
5087
  ),
@@ -5044,7 +5095,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5044
5095
  value: String(relative.days ?? 7),
5045
5096
  onChange: (e) => write({ days: Math.max(0, Math.floor(Number(e.target.value) || 0)) })
5046
5097
  }
5047
- ) : null : reader || op === "present" ? null : pointsAtARecord(chosen) ? (
5098
+ ) : null : reader || VALUELESS_OPS.has(op) ? null : pointsAtARecord(chosen) ? (
5048
5099
  // A RECORD IS PICKED BY ITS WORDS, NEVER TYPED AS AN ID. The same picker the
5049
5100
  // cell uses, on the same `relation_words_many` the cell reads, so a filter and
5050
5101
  // the column it filters can never disagree about what a record is called. It
@@ -5062,7 +5113,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5062
5113
  allowCreate: false
5063
5114
  }
5064
5115
  )
5065
- ) : isChoiceColumn(chosen) && (op === "eq" || op === "ne") ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ChoiceValue, { field: chosen.field, value: String(value ?? ""), onChange: (word) => write({ value: word }) }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
5116
+ ) : isChoiceColumn(chosen) && (op === "eq" || op === "ne" || op === "contains" || op === "not_contains") ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ChoiceValue, { field: chosen.field, value: String(value ?? ""), onChange: (word) => write({ value: word }) }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
5066
5117
  import_design_system23.BasicInput,
5067
5118
  {
5068
5119
  className: "h-8 w-28 text-xs",