@ai-matrx/records-ui 0.115.78 → 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
@@ -890,7 +890,7 @@ import {
890
890
  SelectItem as SelectItem7,
891
891
  SelectTrigger as SelectTrigger7,
892
892
  SelectValue as SelectValue7,
893
- cn as cn24
893
+ cn as cn25
894
894
  } from "@ai-matrx/design-system";
895
895
 
896
896
  // src/dates.ts
@@ -2046,7 +2046,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
2046
2046
 
2047
2047
  // src/buttonCell.tsx
2048
2048
  import { useState as useState24 } from "react";
2049
- import { Button as Button16, Dialog as Dialog2, DialogContent as DialogContent2, DialogDescription, DialogHeader as DialogHeader2, DialogTitle as DialogTitle2, Popover as Popover4, PopoverContent as PopoverContent4, PopoverTrigger as PopoverTrigger4, cn as cn21 } from "@ai-matrx/design-system";
2049
+ import { Button as Button16, Dialog as Dialog2, DialogContent as DialogContent2, DialogDescription, DialogHeader as DialogHeader2, DialogTitle as DialogTitle2, Popover as Popover4, PopoverContent as PopoverContent4, PopoverTrigger as PopoverTrigger4, cn as cn22 } from "@ai-matrx/design-system";
2050
2050
  import { CHOICE_CHIP_CLASS as CHOICE_CHIP_CLASS2, NEUTRAL_CHIP_CLASS as NEUTRAL_CHIP_CLASS2, isStyleColor as isStyleColor2 } from "@ai-matrx/design-system/data-table";
2051
2051
  import { buttonNeeds as buttonNeeds2, buttonOf as buttonOf2 } from "@ai-matrx/records";
2052
2052
  import { useOptionalRecordsClient as useOptionalRecordsClient8 } from "@ai-matrx/records/react";
@@ -2114,7 +2114,7 @@ import {
2114
2114
  SelectTrigger as SelectTrigger6,
2115
2115
  SelectValue as SelectValue6,
2116
2116
  Separator,
2117
- cn as cn20
2117
+ cn as cn21
2118
2118
  } from "@ai-matrx/design-system";
2119
2119
 
2120
2120
  // src/ReferenceBuilder.tsx
@@ -2841,7 +2841,7 @@ import { tableShapeChanged, useRecordsClient as useRecordsClient6 } from "@ai-ma
2841
2841
  // src/values.tsx
2842
2842
  import { SWATCH_CLASS as SWATCH_CLASS2, formatColumnValue } from "@ai-matrx/design-system/data-table";
2843
2843
  import { formatFieldValue as formatFieldValue3 } from "@ai-matrx/design-system/field-formats";
2844
- import { Badge as Badge5, cn as cn18 } from "@ai-matrx/design-system";
2844
+ import { Badge as Badge5, cn as cn19 } from "@ai-matrx/design-system";
2845
2845
 
2846
2846
  // src/embeddedLook.tsx
2847
2847
  import { createContext as createContext2, useContext as useContext2 } from "react";
@@ -3940,7 +3940,7 @@ function linkChipText(raw) {
3940
3940
  // src/gridAttachments.tsx
3941
3941
  import { useEffect as useEffect11, useRef as useRef10, useState as useState17 } from "react";
3942
3942
  import { useOptionalRecordsClient as useOptionalRecordsClient5 } from "@ai-matrx/records/react";
3943
- import { Badge as Badge3, Button as Button10, Dialog, DialogContent, DialogHeader, DialogTitle } from "@ai-matrx/design-system";
3943
+ import { Badge as Badge3, Button as Button10, cn as cn17, Dialog, DialogContent, DialogHeader, DialogTitle } from "@ai-matrx/design-system";
3944
3944
 
3945
3945
  // src/FilePicker.tsx
3946
3946
  import { useCallback as useCallback4, useId, useRef as useRef9, useState as useState16 } from "react";
@@ -4096,46 +4096,63 @@ function AttachmentChip({ id, onRemove, onPreview }) {
4096
4096
  const words = read?.name ?? (read ? FILE_CHIP_UNREAD : "\u2026");
4097
4097
  const href = read?.fileId ? host.hrefForFile?.({ fileId: read.fileId }) : void 0;
4098
4098
  const title = onPreview ? `Preview ${words}` : href ? `Open ${words}` : read?.why ?? FILE_CHIP_NO_PAGE;
4099
- return /* @__PURE__ */ jsxs19(Badge3, { variant: "secondary", className: "max-w-[14rem] gap-1 text-[11px]", "data-records-file-chip": id, title, children: [
4100
- onPreview ? /* @__PURE__ */ jsx21(
4101
- "button",
4102
- {
4103
- type: "button",
4104
- className: "truncate underline-offset-2 hover:underline",
4105
- "data-records-file-preview": id,
4099
+ return /* @__PURE__ */ jsxs19(
4100
+ Badge3,
4101
+ {
4102
+ variant: "secondary",
4103
+ className: cn17("max-w-[14rem] gap-1 text-[11px]", onPreview && "cursor-pointer"),
4104
+ "data-records-file-chip": id,
4105
+ title,
4106
+ ...onPreview ? {
4106
4107
  onClick: (event) => {
4107
4108
  event.stopPropagation();
4108
4109
  onPreview();
4109
4110
  },
4110
4111
  onMouseDown: (event) => event.stopPropagation(),
4111
- onDoubleClick: (event) => event.stopPropagation(),
4112
- children: words
4113
- }
4114
- ) : href ? /* @__PURE__ */ jsx21(
4115
- OutboundLink4,
4116
- {
4117
- href,
4118
- className: "truncate underline-offset-2 hover:underline",
4119
- "data-records-file-link": read?.fileId ?? "",
4120
- onClick: (event) => event.stopPropagation(),
4121
- onMouseDown: (event) => event.stopPropagation(),
4122
- children: words
4123
- }
4124
- ) : /* @__PURE__ */ jsx21("span", { className: "truncate", children: words }),
4125
- onRemove ? /* @__PURE__ */ jsx21(
4126
- "button",
4127
- {
4128
- type: "button",
4129
- "aria-label": `Remove ${words}`,
4130
- className: "shrink-0 rounded px-0.5 text-muted-foreground hover:text-foreground",
4131
- onClick: (event) => {
4132
- event.stopPropagation();
4133
- onRemove();
4134
- },
4135
- children: "\xD7"
4136
- }
4137
- ) : null
4138
- ] });
4112
+ onDoubleClick: (event) => event.stopPropagation()
4113
+ } : {},
4114
+ children: [
4115
+ onPreview ? /* @__PURE__ */ jsx21(
4116
+ "button",
4117
+ {
4118
+ type: "button",
4119
+ className: "truncate underline-offset-2 hover:underline",
4120
+ "data-records-file-preview": id,
4121
+ onClick: (event) => {
4122
+ event.stopPropagation();
4123
+ onPreview();
4124
+ },
4125
+ onMouseDown: (event) => event.stopPropagation(),
4126
+ onDoubleClick: (event) => event.stopPropagation(),
4127
+ children: words
4128
+ }
4129
+ ) : href ? /* @__PURE__ */ jsx21(
4130
+ OutboundLink4,
4131
+ {
4132
+ href,
4133
+ className: "truncate underline-offset-2 hover:underline",
4134
+ "data-records-file-link": read?.fileId ?? "",
4135
+ onClick: (event) => event.stopPropagation(),
4136
+ onMouseDown: (event) => event.stopPropagation(),
4137
+ children: words
4138
+ }
4139
+ ) : /* @__PURE__ */ jsx21("span", { className: "truncate", children: words }),
4140
+ onRemove ? /* @__PURE__ */ jsx21(
4141
+ "button",
4142
+ {
4143
+ type: "button",
4144
+ "aria-label": `Remove ${words}`,
4145
+ className: "shrink-0 rounded px-0.5 text-muted-foreground hover:text-foreground",
4146
+ onClick: (event) => {
4147
+ event.stopPropagation();
4148
+ onRemove();
4149
+ },
4150
+ children: "\xD7"
4151
+ }
4152
+ ) : null
4153
+ ]
4154
+ }
4155
+ );
4139
4156
  }
4140
4157
  function AttachmentChips({ value, onRemove }) {
4141
4158
  const host = useRecordsUi();
@@ -4176,6 +4193,18 @@ function AttachmentViewer({
4176
4193
  {
4177
4194
  className: "sm:max-w-3xl",
4178
4195
  "data-records-attachment-viewer": id,
4196
+ tabIndex: -1,
4197
+ onOpenAutoFocus: (event) => {
4198
+ event.preventDefault();
4199
+ event.currentTarget.focus();
4200
+ },
4201
+ onCloseAutoFocus: (event) => {
4202
+ const chip = document.querySelector(`[data-records-file-preview="${ids[index]}"]`);
4203
+ if (chip) {
4204
+ event.preventDefault();
4205
+ chip.focus();
4206
+ }
4207
+ },
4179
4208
  onKeyDown: (event) => {
4180
4209
  if (event.key === "ArrowLeft") {
4181
4210
  event.preventDefault();
@@ -4284,7 +4313,7 @@ function AttachmentControl({
4284
4313
 
4285
4314
  // src/ReferenceChips.tsx
4286
4315
  import { entityRefsOf as entityRefsOf2 } from "@ai-matrx/records";
4287
- import { Badge as Badge4, Skeleton as Skeleton2, cn as cn17 } from "@ai-matrx/design-system";
4316
+ import { Badge as Badge4, Skeleton as Skeleton2, cn as cn18 } from "@ai-matrx/design-system";
4288
4317
 
4289
4318
  // src/kindValues.ts
4290
4319
  import { tryDecodeDirective } from "@ai-matrx/content-ir/directives";
@@ -4389,7 +4418,7 @@ function ReferenceChip({ reference, waiting }) {
4389
4418
  return /* @__PURE__ */ jsxs20(
4390
4419
  "span",
4391
4420
  {
4392
- className: cn17(
4421
+ className: cn18(
4393
4422
  "inline-flex max-w-full min-w-0 items-center gap-1.5 text-sm text-foreground hover:underline [&_a]:text-inherit [&_a]:no-underline [&_a:hover]:underline",
4394
4423
  reference.archived && "opacity-70"
4395
4424
  ),
@@ -4409,7 +4438,7 @@ function ReferenceChip({ reference, waiting }) {
4409
4438
  Badge4,
4410
4439
  {
4411
4440
  variant: "secondary",
4412
- className: cn17(
4441
+ className: cn18(
4413
4442
  "max-w-full min-w-0 gap-1 px-1.5 py-0 text-[11px] font-normal",
4414
4443
  reference.archived && "opacity-70"
4415
4444
  ),
@@ -4437,7 +4466,7 @@ function KindValueCell({
4437
4466
  return /* @__PURE__ */ jsx22("span", { className: "flex min-w-0 max-w-full items-center", "data-records-kind-value": kv.kind, children: host.renderKind({ value: kv.value, kind: kv.kind, where, title }) });
4438
4467
  }
4439
4468
  const named = kv.items.map(kindItemWords).filter((w) => w !== null);
4440
- return /* @__PURE__ */ jsx22("span", { className: cn17("flex min-w-0 flex-wrap gap-1"), "data-records-kind-value": kv.kind, title: JSON.stringify(kv.value), children: named.length > 0 ? named.map((w, i) => /* @__PURE__ */ jsx22(Badge4, { variant: "secondary", className: "px-1.5 py-0 text-[11px] font-normal", children: w }, `${w}-${i}`)) : /* @__PURE__ */ jsx22(Badge4, { variant: "outline", className: "px-1.5 py-0 text-[11px] font-normal", children: kindLabel(kv.kind) }) });
4469
+ return /* @__PURE__ */ jsx22("span", { className: cn18("flex min-w-0 flex-wrap gap-1"), "data-records-kind-value": kv.kind, title: JSON.stringify(kv.value), children: named.length > 0 ? named.map((w, i) => /* @__PURE__ */ jsx22(Badge4, { variant: "secondary", className: "px-1.5 py-0 text-[11px] font-normal", children: w }, `${w}-${i}`)) : /* @__PURE__ */ jsx22(Badge4, { variant: "outline", className: "px-1.5 py-0 text-[11px] font-normal", children: kindLabel(kv.kind) }) });
4441
4470
  }
4442
4471
 
4443
4472
  // src/formulaNames.ts
@@ -4604,11 +4633,11 @@ function renderValue(field, value, document2, labels, stillWaitingFor, look) {
4604
4633
  Badge5,
4605
4634
  {
4606
4635
  variant: "outline",
4607
- className: cn18("text-[11px]", chip.className),
4636
+ className: cn19("text-[11px]", chip.className),
4608
4637
  "data-records-choice-chip": "",
4609
4638
  ...chip.tinted ? { "data-records-choice-tint": "" } : {},
4610
4639
  children: [
4611
- look?.choiceDot ? /* @__PURE__ */ jsx23("span", { "aria-hidden": "true", "data-matrx-pill-ink": "", "data-records-choice-dot": "", className: cn18("mr-1.5 inline-block size-1.5 shrink-0 rounded-full", chip.dot) }) : null,
4640
+ look?.choiceDot ? /* @__PURE__ */ jsx23("span", { "aria-hidden": "true", "data-matrx-pill-ink": "", "data-records-choice-dot": "", className: cn19("mr-1.5 inline-block size-1.5 shrink-0 rounded-full", chip.dot) }) : null,
4612
4641
  statusMark(field, item, Array.isArray(value) ? void 0 : document2),
4613
4642
  scalarText(field, item, labels, Array.isArray(value) ? void 0 : stillWaitingFor)
4614
4643
  ]
@@ -4657,7 +4686,7 @@ function renderValue(field, value, document2, labels, stillWaitingFor, look) {
4657
4686
  if (typeof value === "boolean") {
4658
4687
  return /* @__PURE__ */ jsx23("span", { className: kind === "checkbox" ? "text-sm" : "", title: value ? "Yes" : "No", children: value ? "\u2713" : "\u2014" });
4659
4688
  }
4660
- return /* @__PURE__ */ jsx23("span", { className: cn18("truncate", kind === "number" || kind === "currency" || kind === "percent" ? "tabular-nums" : ""), children: scalarText(field, value, labels, stillWaitingFor) });
4689
+ return /* @__PURE__ */ jsx23("span", { className: cn19("truncate", kind === "number" || kind === "currency" || kind === "percent" ? "tabular-nums" : ""), children: scalarText(field, value, labels, stillWaitingFor) });
4661
4690
  }
4662
4691
  function scalarText(field, value, labels, stillWaitingFor) {
4663
4692
  if (value === null || value === void 0) return "\u2014";
@@ -4813,6 +4842,33 @@ var CONDITION_OPS = [
4813
4842
  { op: "gte", label: "is at least" },
4814
4843
  { op: "lte", label: "is at most" }
4815
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
+ }
4816
4872
  var FilterTermsContext = createContext4(false);
4817
4873
  var RELATIVE_DAY_TERMS = [
4818
4874
  { kind: "today", label: "is today" },
@@ -4925,13 +4981,15 @@ function ChoiceValue({ field, value, onChange }) {
4925
4981
  }
4926
4982
  function conditionIsSimple(expr) {
4927
4983
  if (expr === null || expr === void 0) return true;
4928
- if (relativeDayOf(expr) !== null || meOf(expr) !== null) return true;
4984
+ if (relativeDayOf(expr) !== null || meOf(expr) !== null || linkedOf(expr) !== null) return true;
4929
4985
  const node = expr;
4930
- 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
+ }
4931
4989
  if (!Array.isArray(node.args)) return false;
4932
4990
  const first = node.args[0];
4933
4991
  if (!first || typeof first.field !== "string") return false;
4934
- if (node.op === "present") return node.args.length === 1;
4992
+ if (VALUELESS_OPS.has(node.op)) return node.args.length === 1;
4935
4993
  const second = node.args[1];
4936
4994
  return node.args.length === 2 && !!second && "const" in second;
4937
4995
  }
@@ -4949,7 +5007,13 @@ function conditionInWords(expr, fields) {
4949
5007
  lte: "is at most",
4950
5008
  and: "and",
4951
5009
  or: "or",
4952
- 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"
4953
5017
  };
4954
5018
  const walk = (node) => {
4955
5019
  if (node === null || node === void 0) return "nothing";
@@ -4970,6 +5034,12 @@ function conditionInWords(expr, fields) {
4970
5034
  const args = Array.isArray(n["args"]) ? n["args"] : [];
4971
5035
  if (op === "not") return `not ${walk(args[0])}`;
4972
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
+ }
4973
5043
  const dayRange = relativeDayOf(n);
4974
5044
  if (dayRange) {
4975
5045
  const term = RELATIVE_DAY_TERMS.find((t) => t.kind === dayRange.kind);
@@ -5002,12 +5072,14 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5002
5072
  const filterTerms = useContext4(FilterTermsContext);
5003
5073
  const relative = relativeDayOf(expr);
5004
5074
  const reader = meOf(expr);
5005
- const op = relative ? `rel:${relative.kind}` : reader ? reader.which : node?.op ?? "";
5006
- const onField = node?.args?.[0]?.field ?? "";
5007
- 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 ?? "";
5008
5079
  const chosen = fields.find((f) => f.id === onField);
5009
5080
  const chosenKind = chosen?.field ? String(editorKindFor(chosen.field)) : "";
5010
- 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;
5011
5083
  const relativeTerm = relative ? RELATIVE_DAY_TERMS.find((t) => t.kind === relative.kind) : void 0;
5012
5084
  function write(next) {
5013
5085
  const picked = next.op;
@@ -5020,8 +5092,11 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5020
5092
  onChange(relativeDayNode(fieldNow, relative.kind, next.days));
5021
5093
  return;
5022
5094
  }
5023
- if (next.field !== void 0 && (relative || reader)) {
5024
- 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
+ );
5025
5100
  return;
5026
5101
  }
5027
5102
  const chosenOp = next.op ?? (op || (next.field ? CONDITION_OPS[0].op : ""));
@@ -5031,8 +5106,13 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5031
5106
  onChange(null);
5032
5107
  return;
5033
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
+ }
5034
5114
  onChange(
5035
- 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 }] }
5036
5116
  );
5037
5117
  }
5038
5118
  return /* @__PURE__ */ jsxs23("div", { className: className ?? "flex flex-wrap items-center gap-1 text-xs", children: [
@@ -5065,7 +5145,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5065
5145
  className: "h-8 w-auto min-w-[6rem] text-xs",
5066
5146
  value: op,
5067
5147
  onChange: (next) => write({ op: next }),
5068
- options: [...CONDITION_OPS, ...extraTerms].map((c) => ({ value: c.op, label: c.label })),
5148
+ options: [...baseTerms, ...extraTerms].map((c) => ({ value: c.op, label: c.label })),
5069
5149
  attrs: { "data-condition": "comparison" }
5070
5150
  }
5071
5151
  ),
@@ -5079,7 +5159,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5079
5159
  value: String(relative.days ?? 7),
5080
5160
  onChange: (e) => write({ days: Math.max(0, Math.floor(Number(e.target.value) || 0)) })
5081
5161
  }
5082
- ) : null : reader || op === "present" ? null : pointsAtARecord(chosen) ? (
5162
+ ) : null : reader || VALUELESS_OPS.has(op) ? null : pointsAtARecord(chosen) ? (
5083
5163
  // A RECORD IS PICKED BY ITS WORDS, NEVER TYPED AS AN ID. The same picker the
5084
5164
  // cell uses, on the same `relation_words_many` the cell reads, so a filter and
5085
5165
  // the column it filters can never disagree about what a record is called. It
@@ -5097,7 +5177,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
5097
5177
  allowCreate: false
5098
5178
  }
5099
5179
  )
5100
- ) : 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(
5101
5181
  BasicInput7,
5102
5182
  {
5103
5183
  className: "h-8 w-28 text-xs",
@@ -6026,7 +6106,7 @@ import { useOptionalRecordsClient as useOptionalRecordsClient7 } from "@ai-matrx
6026
6106
 
6027
6107
  // src/AccentSwatches.tsx
6028
6108
  import { PORTAL_ACCENTS } from "@ai-matrx/records";
6029
- import { cn as cn19 } from "@ai-matrx/design-system";
6109
+ import { cn as cn20 } from "@ai-matrx/design-system";
6030
6110
  import { STYLE_COLOR_LABELS as STYLE_COLOR_LABELS2, SWATCH_CLASS as SWATCH_CLASS3 } from "@ai-matrx/design-system/data-table";
6031
6111
  import { jsx as jsx30, jsxs as jsxs28 } from "react/jsx-runtime";
6032
6112
  function AccentSwatches({ value, onChange, options = PORTAL_ACCENTS, noneLabel = "App colour" }) {
@@ -6038,7 +6118,7 @@ function AccentSwatches({ value, onChange, options = PORTAL_ACCENTS, noneLabel =
6038
6118
  role: "radio",
6039
6119
  "aria-checked": value === "",
6040
6120
  onClick: () => onChange(""),
6041
- className: cn19(
6121
+ className: cn20(
6042
6122
  "h-7 rounded border px-2 text-xs",
6043
6123
  value === "" ? "border-foreground" : "border-border text-muted-foreground"
6044
6124
  ),
@@ -6054,7 +6134,7 @@ function AccentSwatches({ value, onChange, options = PORTAL_ACCENTS, noneLabel =
6054
6134
  "aria-label": STYLE_COLOR_LABELS2[c],
6055
6135
  title: STYLE_COLOR_LABELS2[c],
6056
6136
  onClick: () => onChange(c),
6057
- className: cn19("h-7 w-7 rounded-full border-2", SWATCH_CLASS3[c], value === c ? "border-foreground" : "border-transparent")
6137
+ className: cn20("h-7 w-7 rounded-full border-2", SWATCH_CLASS3[c], value === c ? "border-foreground" : "border-transparent")
6058
6138
  },
6059
6139
  c
6060
6140
  ))
@@ -6527,7 +6607,7 @@ function FieldEditor(props) {
6527
6607
  const offersMany = multiApplies(type) && !isTitle;
6528
6608
  if (!mayShape) {
6529
6609
  if (onEntity && entityRights.loading) return null;
6530
- return /* @__PURE__ */ jsx32("p", { className: cn20("text-xs text-muted-foreground", className), children: props.tableLabel && storeLabel && whyNot ? whyNot.split(storeLabel).join(props.tableLabel) : whyNot });
6610
+ return /* @__PURE__ */ jsx32("p", { className: cn21("text-xs text-muted-foreground", className), children: props.tableLabel && storeLabel && whyNot ? whyNot.split(storeLabel).join(props.tableLabel) : whyNot });
6531
6611
  }
6532
6612
  const options = newChoices.map((r) => r.words.trim()).filter((word) => word !== "");
6533
6613
  const shownStatusRows = type !== "status" ? [] : statusGroupRows(field, editing ? choiceList ?? [] : newChoices, statusKeys, statusGroups);
@@ -6727,7 +6807,7 @@ function FieldEditor(props) {
6727
6807
  if (gone) (onRemoved ?? onSaved)?.();
6728
6808
  else setAskingToRemove(false);
6729
6809
  };
6730
- return /* @__PURE__ */ jsxs30("div", { className: cn20("flex min-w-0 flex-col gap-3 text-xs", className), "data-records-field-editor": "", children: [
6810
+ return /* @__PURE__ */ jsxs30("div", { className: cn21("flex min-w-0 flex-col gap-3 text-xs", className), "data-records-field-editor": "", children: [
6731
6811
  /* @__PURE__ */ jsxs30("div", { className: "flex items-center gap-2", children: [
6732
6812
  /* @__PURE__ */ jsx32("span", { className: "font-medium", children: field ? `Field \xB7 ${fieldName(field)}` : "New field" }),
6733
6813
  /* @__PURE__ */ jsx32("span", { className: "ml-auto text-muted-foreground", children: tableLabel })
@@ -7184,8 +7264,8 @@ function FieldEditor(props) {
7184
7264
  the dialog card 0.2278 and closing it 0.1303. The consequence is the tooltip of
7185
7265
  "Remove it" (interface-text: the slot holds a label, never a paragraph). */
7186
7266
  /* @__PURE__ */ jsxs30("div", { className: "ml-auto grid shrink-0 items-center", "data-field-editor-remove": askingToRemove ? "asking" : "idle", children: [
7187
- /* @__PURE__ */ jsx32("div", { className: cn20("col-start-1 row-start-1 flex items-center justify-end gap-2", askingToRemove && "invisible"), "aria-hidden": askingToRemove || void 0, children: /* @__PURE__ */ jsx32(Button15, { size: "sm", variant: "outline", tabIndex: askingToRemove ? -1 : void 0, onClick: () => setAskingToRemove(true), children: "Remove this field" }) }),
7188
- /* @__PURE__ */ jsxs30("div", { className: cn20("col-start-1 row-start-1 flex items-center justify-end gap-2", !askingToRemove && "invisible"), "aria-hidden": !askingToRemove || void 0, children: [
7267
+ /* @__PURE__ */ jsx32("div", { className: cn21("col-start-1 row-start-1 flex items-center justify-end gap-2", askingToRemove && "invisible"), "aria-hidden": askingToRemove || void 0, children: /* @__PURE__ */ jsx32(Button15, { size: "sm", variant: "outline", tabIndex: askingToRemove ? -1 : void 0, onClick: () => setAskingToRemove(true), children: "Remove this field" }) }),
7268
+ /* @__PURE__ */ jsxs30("div", { className: cn21("col-start-1 row-start-1 flex items-center justify-end gap-2", !askingToRemove && "invisible"), "aria-hidden": !askingToRemove || void 0, children: [
7189
7269
  /* @__PURE__ */ jsx32("span", { className: "whitespace-nowrap text-destructive", "data-field-editor-remove-ask": "", children: "Archive this field?" }),
7190
7270
  /* @__PURE__ */ jsx32(
7191
7271
  Button15,
@@ -7624,7 +7704,7 @@ function ButtonValue({ field, recordId, inCell }) {
7624
7704
  "data-records-button-unset": needs ? "" : void 0,
7625
7705
  "aria-busy": busy || void 0,
7626
7706
  disabled: busy,
7627
- className: cn21("max-w-full gap-1 border px-2 font-medium", inCell ? "h-5" : "h-6", color, needs && "border-dashed opacity-80"),
7707
+ className: cn22("max-w-full gap-1 border px-2 font-medium", inCell ? "h-5" : "h-6", color, needs && "border-dashed opacity-80"),
7628
7708
  onPointerDown: stop,
7629
7709
  onDoubleClick: stop,
7630
7710
  onClick: (event) => {
@@ -7678,7 +7758,7 @@ function ButtonValue({ field, recordId, inCell }) {
7678
7758
  import { useCallback as useCallback6, useEffect as useEffect18, useMemo as useMemo11, useRef as useRef13, useState as useState25 } from "react";
7679
7759
  import { rankTypedMatches as rankTypedMatches3 } from "@ai-matrx/records";
7680
7760
  import { useOptionalRecordsClient as useOptionalRecordsClient9 } from "@ai-matrx/records/react";
7681
- import { cn as cn22 } from "@ai-matrx/design-system";
7761
+ import { cn as cn23 } from "@ai-matrx/design-system";
7682
7762
  import { Fragment as Fragment11, jsx as jsx34, jsxs as jsxs32 } from "react/jsx-runtime";
7683
7763
  function personFromRow(row) {
7684
7764
  const data = row.document ?? {};
@@ -7760,10 +7840,10 @@ function PersonPicker({ field, value, onChange, disabled, id, className, autoOpe
7760
7840
  }, [client, personTable, picked.length, people.length]);
7761
7841
  const listed = rankTypedMatches3(members ?? [], search, memberName);
7762
7842
  if (!membersPort || !client) {
7763
- return /* @__PURE__ */ jsx34("p", { className: cn22("text-xs text-muted-foreground", className), children: NO_MEMBERS_REASON });
7843
+ return /* @__PURE__ */ jsx34("p", { className: cn23("text-xs text-muted-foreground", className), children: NO_MEMBERS_REASON });
7764
7844
  }
7765
7845
  if (!personTable) {
7766
- return /* @__PURE__ */ jsxs32("p", { className: cn22("text-xs text-muted-foreground", className), children: [
7846
+ return /* @__PURE__ */ jsxs32("p", { className: cn23("text-xs text-muted-foreground", className), children: [
7767
7847
  fieldName(field),
7768
7848
  " is a person field, but it does not say which Table the people are records of yet, so there is nothing to pick from. A table admin sets its target in the field editor."
7769
7849
  ] });
@@ -7814,7 +7894,7 @@ function PersonPicker({ field, value, onChange, disabled, id, className, autoOpe
7814
7894
  );
7815
7895
  const anchored = autoOpen === true;
7816
7896
  const cellChips = picked.map((recordId) => /* @__PURE__ */ jsx34(PersonChip, { name: nameOfRecord(recordId), ...disabled || anchored ? {} : { onRemove: () => drop(recordId) } }, recordId));
7817
- return /* @__PURE__ */ jsxs32("div", { className: cn22("flex flex-wrap items-center gap-1", className), children: [
7897
+ return /* @__PURE__ */ jsxs32("div", { className: cn23("flex flex-wrap items-center gap-1", className), children: [
7818
7898
  anchored && !disabled ? null : cellChips,
7819
7899
  disabled ? null : (
7820
7900
  // THE ONE "PICK OR ADD" BUILDER (lane CELL-EDITORS, 2026-10-06): the person list used to carry
@@ -8022,7 +8102,7 @@ function formatControl(args) {
8022
8102
 
8023
8103
  // src/typedDateHelper.tsx
8024
8104
  import { useState as useState28 } from "react";
8025
- import { Popover as Popover5, PopoverContent as PopoverContent5, PopoverTrigger as PopoverTrigger5, cn as cn23 } from "@ai-matrx/design-system";
8105
+ import { Popover as Popover5, PopoverContent as PopoverContent5, PopoverTrigger as PopoverTrigger5, cn as cn24 } from "@ai-matrx/design-system";
8026
8106
  import { readTypedDate, readTypedTime, typedDateText, typedTimeText } from "@ai-matrx/records";
8027
8107
  import { jsx as jsx37, jsxs as jsxs35 } from "react/jsx-runtime";
8028
8108
  var WEEK = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
@@ -8147,7 +8227,7 @@ function TypedDateHelper({
8147
8227
  type: "button",
8148
8228
  ...keepFocus,
8149
8229
  "data-records-date-helper-day": key,
8150
- className: cn23(
8230
+ className: cn24(
8151
8231
  "h-6 w-6 rounded tabular-nums hover:bg-muted",
8152
8232
  key === held.day ? "bg-primary text-primary-foreground hover:bg-primary" : "",
8153
8233
  key === todayKey && key !== held.day ? "ring-1 ring-ring" : ""
@@ -8166,7 +8246,7 @@ function TypedDateHelper({
8166
8246
  type: "button",
8167
8247
  ...keepFocus,
8168
8248
  "data-records-date-helper-time": `${pad(t.h)}:${pad(t.m)}`,
8169
- className: cn23(
8249
+ className: cn24(
8170
8250
  "whitespace-nowrap rounded px-2 py-0.5 text-left tabular-nums hover:bg-muted",
8171
8251
  held.time && held.time.h === t.h && held.time.m === t.m ? "bg-primary text-primary-foreground" : ""
8172
8252
  ),
@@ -8955,7 +9035,7 @@ function OpenChoicePicker({
8955
9035
  const pickedOptions = current.map((v) => options.find((o) => isTheChosen(o, v)));
8956
9036
  const cellChips = pickedWords.map((word, i) => {
8957
9037
  const chip = chipOf(pickedOptions[i], word);
8958
- return /* @__PURE__ */ jsx38(Badge6, { variant: "outline", className: cn24("text-[11px]", chip.className), "data-records-choice-chip": "", children: word }, word);
9038
+ return /* @__PURE__ */ jsx38(Badge6, { variant: "outline", className: cn25("text-[11px]", chip.className), "data-records-choice-chip": "", children: word }, word);
8959
9039
  });
8960
9040
  const headChips = pickedWords.map((word, i) => {
8961
9041
  const option = pickedOptions[i];
@@ -9028,7 +9108,7 @@ function RecordChip({
9028
9108
  // name. So it caps at its container and ends in an ellipsis with the whole name in
9029
9109
  // its tooltip, rather than being cut mid-word by whatever box it landed in
9030
9110
  // (lane FIX-14, 2026-09-22).
9031
- /* @__PURE__ */ jsxs36(Badge6, { variant: "secondary", className: cn24("max-w-full gap-1 text-[11px]", className), title, children: [
9111
+ /* @__PURE__ */ jsxs36(Badge6, { variant: "secondary", className: cn25("max-w-full gap-1 text-[11px]", className), title, children: [
9032
9112
  /* @__PURE__ */ jsx38("span", { className: "min-w-0 truncate", children: title }),
9033
9113
  onRemove ? /* @__PURE__ */ jsx38("button", { type: "button", onClick: onRemove, "aria-label": `Remove ${title}`, className: "shrink-0 opacity-60 hover:opacity-100", children: "\xD7" }) : null
9034
9114
  ] })
@@ -9083,7 +9163,7 @@ function ChoiceAnswer({
9083
9163
  tabIndex: field.multi || choice.key === tabStop ? 0 : -1,
9084
9164
  onClick: () => press(choice),
9085
9165
  "data-records-choice-answer": choice.key,
9086
- className: cn24(
9166
+ className: cn25(
9087
9167
  "flex min-h-11 w-full items-center gap-3 rounded-md border px-3 py-2 text-left text-base font-medium text-foreground transition-colors sm:min-h-10 sm:text-sm",
9088
9168
  chosen ? "border-primary bg-primary/10" : "border-primary/30 bg-primary/[0.03] hover:bg-primary/10"
9089
9169
  ),
@@ -9092,7 +9172,7 @@ function ChoiceAnswer({
9092
9172
  "kbd",
9093
9173
  {
9094
9174
  "aria-hidden": "true",
9095
- className: cn24(
9175
+ className: cn25(
9096
9176
  "inline-flex h-6 w-6 shrink-0 items-center justify-center rounded border font-sans text-xs font-semibold",
9097
9177
  chosen ? "border-primary bg-primary text-primary-foreground" : "border-primary/40 text-primary"
9098
9178
  ),
@@ -9400,7 +9480,7 @@ function RecordPicker({ tableId, value, onChange, disabled }) {
9400
9480
  import { useEffect as useEffect23, useMemo as useMemo13 } from "react";
9401
9481
  import { isEntityReferenceConfig } from "@ai-matrx/records";
9402
9482
  import { useFields as useFields4, useTable as useTable3 } from "@ai-matrx/records/react";
9403
- import { BasicInput as BasicInput12, Button as Button19, CreatablePicker as CreatablePicker2, cn as cn25 } from "@ai-matrx/design-system";
9483
+ import { BasicInput as BasicInput12, Button as Button19, CreatablePicker as CreatablePicker2, cn as cn26 } from "@ai-matrx/design-system";
9404
9484
  import { jsx as jsx41, jsxs as jsxs38 } from "react/jsx-runtime";
9405
9485
  var DATA_BINDING_SHAPES = [
9406
9486
  { value: "collection", label: "The whole table" },
@@ -9576,7 +9656,7 @@ function BindingDetails({
9576
9656
  /* @__PURE__ */ jsx41(
9577
9657
  "textarea",
9578
9658
  {
9579
- className: cn25(
9659
+ className: cn26(
9580
9660
  "min-h-[3.5rem] w-full rounded-md border border-border bg-background px-2 py-1.5 font-mono text-xs"
9581
9661
  ),
9582
9662
  "aria-label": "Each row reads as",