@ai-matrx/records-ui 0.115.79 → 0.115.81
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/CHANGELOG.md +17 -1
- package/dist/index.cjs +103 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +104 -18
- package/dist/index.js.map +1 -1
- package/dist/pickers.cjs +65 -14
- package/dist/pickers.cjs.map +1 -1
- package/dist/pickers.js +65 -14
- package/dist/pickers.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -2839,6 +2839,14 @@ interface ConditionField {
|
|
|
2839
2839
|
* store compares.
|
|
2840
2840
|
*/
|
|
2841
2841
|
field?: Field;
|
|
2842
|
+
/**
|
|
2843
|
+
* Set when this entry is a field of the table a relation column POINTS AT (lane NOTION-Q): `id` is
|
|
2844
|
+
* `<relation field id>><field id>` and `via` names the relation. Offered only by a view's filter.
|
|
2845
|
+
*/
|
|
2846
|
+
via?: {
|
|
2847
|
+
id: string;
|
|
2848
|
+
fieldId: string;
|
|
2849
|
+
} | undefined;
|
|
2842
2850
|
}
|
|
2843
2851
|
interface ConditionRowProps {
|
|
2844
2852
|
/** The words before the row: "Ask only when", "When". */
|
package/dist/index.d.ts
CHANGED
|
@@ -2839,6 +2839,14 @@ interface ConditionField {
|
|
|
2839
2839
|
* store compares.
|
|
2840
2840
|
*/
|
|
2841
2841
|
field?: Field;
|
|
2842
|
+
/**
|
|
2843
|
+
* Set when this entry is a field of the table a relation column POINTS AT (lane NOTION-Q): `id` is
|
|
2844
|
+
* `<relation field id>><field id>` and `via` names the relation. Offered only by a view's filter.
|
|
2845
|
+
*/
|
|
2846
|
+
via?: {
|
|
2847
|
+
id: string;
|
|
2848
|
+
fieldId: string;
|
|
2849
|
+
} | undefined;
|
|
2842
2850
|
}
|
|
2843
2851
|
interface ConditionRowProps {
|
|
2844
2852
|
/** The words before the row: "Ask only when", "When". */
|
package/dist/index.js
CHANGED
|
@@ -7486,6 +7486,33 @@ var CONDITION_OPS = [
|
|
|
7486
7486
|
{ op: "gte", label: "is at least" },
|
|
7487
7487
|
{ op: "lte", label: "is at most" }
|
|
7488
7488
|
];
|
|
7489
|
+
var LIST_TERMS = [
|
|
7490
|
+
{ op: "contains", label: "contains" },
|
|
7491
|
+
{ op: "not_contains", label: "does not contain" },
|
|
7492
|
+
{ op: "is_empty", label: "is empty" },
|
|
7493
|
+
{ op: "is_not_empty", label: "is not empty" }
|
|
7494
|
+
];
|
|
7495
|
+
var VALUELESS_OPS = /* @__PURE__ */ new Set(["present", "is_empty", "is_not_empty"]);
|
|
7496
|
+
var LINK_SEP = ">";
|
|
7497
|
+
function linkedNode(viaId, innerId, op2, value) {
|
|
7498
|
+
return {
|
|
7499
|
+
op: "linked",
|
|
7500
|
+
match: op2 === "eq" ? "any" : "none",
|
|
7501
|
+
args: [{ field: viaId }, { op: "eq", args: [{ field: innerId }, { const: value }] }]
|
|
7502
|
+
};
|
|
7503
|
+
}
|
|
7504
|
+
function linkedOf(expr) {
|
|
7505
|
+
const node = asNode(expr);
|
|
7506
|
+
if (!node || node["op"] !== "linked") return null;
|
|
7507
|
+
const [rel, cond] = argsOf(node);
|
|
7508
|
+
const innerNode = asNode(cond);
|
|
7509
|
+
const [a, b] = innerNode ? argsOf(innerNode) : [];
|
|
7510
|
+
const match = node["match"] ?? "any";
|
|
7511
|
+
if (!rel || typeof rel["field"] !== "string" || !innerNode || innerNode["op"] !== "eq") return null;
|
|
7512
|
+
if (!a || typeof a["field"] !== "string" || !b || !("const" in b)) return null;
|
|
7513
|
+
if (match !== "any" && match !== "none") return null;
|
|
7514
|
+
return { via: rel["field"], inner: a["field"], op: match === "any" ? "eq" : "ne", value: b["const"] };
|
|
7515
|
+
}
|
|
7489
7516
|
var FilterTermsContext = createContext5(false);
|
|
7490
7517
|
var RELATIVE_DAY_TERMS = [
|
|
7491
7518
|
{ kind: "today", label: "is today" },
|
|
@@ -7598,13 +7625,15 @@ function ChoiceValue({ field: field2, value, onChange }) {
|
|
|
7598
7625
|
}
|
|
7599
7626
|
function conditionIsSimple(expr) {
|
|
7600
7627
|
if (expr === null || expr === void 0) return true;
|
|
7601
|
-
if (relativeDayOf(expr) !== null || meOf(expr) !== null) return true;
|
|
7628
|
+
if (relativeDayOf(expr) !== null || meOf(expr) !== null || linkedOf(expr) !== null) return true;
|
|
7602
7629
|
const node = expr;
|
|
7603
|
-
if (typeof node.op !== "string" || !CONDITION_OPS.some((c) => c.op === node.op))
|
|
7630
|
+
if (typeof node.op !== "string" || !(CONDITION_OPS.some((c) => c.op === node.op) || LIST_TERMS.some((c) => c.op === node.op))) {
|
|
7631
|
+
return false;
|
|
7632
|
+
}
|
|
7604
7633
|
if (!Array.isArray(node.args)) return false;
|
|
7605
7634
|
const first = node.args[0];
|
|
7606
7635
|
if (!first || typeof first.field !== "string") return false;
|
|
7607
|
-
if (node.op
|
|
7636
|
+
if (VALUELESS_OPS.has(node.op)) return node.args.length === 1;
|
|
7608
7637
|
const second = node.args[1];
|
|
7609
7638
|
return node.args.length === 2 && !!second && "const" in second;
|
|
7610
7639
|
}
|
|
@@ -7622,7 +7651,13 @@ function conditionInWords(expr, fields) {
|
|
|
7622
7651
|
lte: "is at most",
|
|
7623
7652
|
and: "and",
|
|
7624
7653
|
or: "or",
|
|
7625
|
-
matches: "matches"
|
|
7654
|
+
matches: "matches",
|
|
7655
|
+
contains: "contains",
|
|
7656
|
+
not_contains: "does not contain",
|
|
7657
|
+
has_any: "has any of",
|
|
7658
|
+
has_all: "has all of",
|
|
7659
|
+
has_none: "has none of",
|
|
7660
|
+
is_exactly: "is exactly"
|
|
7626
7661
|
};
|
|
7627
7662
|
const walk = (node) => {
|
|
7628
7663
|
if (node === null || node === void 0) return "nothing";
|
|
@@ -7643,6 +7678,12 @@ function conditionInWords(expr, fields) {
|
|
|
7643
7678
|
const args = Array.isArray(n["args"]) ? n["args"] : [];
|
|
7644
7679
|
if (op2 === "not") return `not ${walk(args[0])}`;
|
|
7645
7680
|
if (op2 === "present") return `${walk(args[0])} has been answered`;
|
|
7681
|
+
if (op2 === "is_empty") return `${walk(args[0])} is empty`;
|
|
7682
|
+
if (op2 === "is_not_empty") return `${walk(args[0])} is not empty`;
|
|
7683
|
+
if (op2 === "linked") {
|
|
7684
|
+
const how = n["match"] === "every" ? "every" : n["match"] === "none" ? "none of" : "any";
|
|
7685
|
+
return `${how} linked record in ${walk(args[0])}: ${walk(args[1])}`;
|
|
7686
|
+
}
|
|
7646
7687
|
const dayRange = relativeDayOf(n);
|
|
7647
7688
|
if (dayRange) {
|
|
7648
7689
|
const term = RELATIVE_DAY_TERMS.find((t) => t.kind === dayRange.kind);
|
|
@@ -7675,12 +7716,14 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
7675
7716
|
const filterTerms = useContext5(FilterTermsContext);
|
|
7676
7717
|
const relative = relativeDayOf(expr);
|
|
7677
7718
|
const reader = meOf(expr);
|
|
7678
|
-
const
|
|
7679
|
-
const
|
|
7680
|
-
const
|
|
7719
|
+
const linked = linkedOf(expr);
|
|
7720
|
+
const op2 = relative ? `rel:${relative.kind}` : reader ? reader.which : linked ? linked.op : node?.op ?? "";
|
|
7721
|
+
const onField = linked ? `${linked.via}${LINK_SEP}${linked.inner}` : node?.args?.[0]?.field ?? "";
|
|
7722
|
+
const value = relative || reader ? "" : linked ? linked.value : node?.args?.[1]?.const ?? "";
|
|
7681
7723
|
const chosen = fields.find((f) => f.id === onField);
|
|
7682
7724
|
const chosenKind = chosen?.field ? String(editorKindFor(chosen.field)) : "";
|
|
7683
|
-
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" }] : [];
|
|
7725
|
+
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 : [];
|
|
7726
|
+
const baseTerms = chosen?.via ? CONDITION_OPS.filter((c) => c.op === "eq" || c.op === "ne") : CONDITION_OPS;
|
|
7684
7727
|
const relativeTerm = relative ? RELATIVE_DAY_TERMS.find((t) => t.kind === relative.kind) : void 0;
|
|
7685
7728
|
function write(next) {
|
|
7686
7729
|
const picked = next.op;
|
|
@@ -7693,8 +7736,11 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
7693
7736
|
onChange(relativeDayNode(fieldNow, relative.kind, next.days));
|
|
7694
7737
|
return;
|
|
7695
7738
|
}
|
|
7696
|
-
if (next.field !== void 0 && (relative || reader)) {
|
|
7697
|
-
|
|
7739
|
+
if (next.field !== void 0 && (relative || reader || linked)) {
|
|
7740
|
+
const target2 = fields.find((f) => f.id === next.field);
|
|
7741
|
+
onChange(
|
|
7742
|
+
!next.field ? null : target2?.via ? linkedNode(target2.via.id, target2.via.fieldId, "eq", "") : { op: CONDITION_OPS[0].op, args: [{ field: next.field }, { const: "" }] }
|
|
7743
|
+
);
|
|
7698
7744
|
return;
|
|
7699
7745
|
}
|
|
7700
7746
|
const chosenOp = next.op ?? (op2 || (next.field ? CONDITION_OPS[0].op : ""));
|
|
@@ -7704,8 +7750,13 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
7704
7750
|
onChange(null);
|
|
7705
7751
|
return;
|
|
7706
7752
|
}
|
|
7753
|
+
const target = fields.find((f) => f.id === chosenField);
|
|
7754
|
+
if (target?.via) {
|
|
7755
|
+
onChange(linkedNode(target.via.id, target.via.fieldId, chosenOp === "ne" ? "ne" : "eq", chosenValue));
|
|
7756
|
+
return;
|
|
7757
|
+
}
|
|
7707
7758
|
onChange(
|
|
7708
|
-
chosenOp
|
|
7759
|
+
VALUELESS_OPS.has(chosenOp) ? { op: chosenOp, args: [{ field: chosenField }] } : { op: chosenOp, args: [{ field: chosenField }, { const: chosenValue }] }
|
|
7709
7760
|
);
|
|
7710
7761
|
}
|
|
7711
7762
|
return /* @__PURE__ */ jsxs28("div", { className: className ?? "flex flex-wrap items-center gap-1 text-xs", children: [
|
|
@@ -7738,7 +7789,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
7738
7789
|
className: "h-8 w-auto min-w-[6rem] text-xs",
|
|
7739
7790
|
value: op2,
|
|
7740
7791
|
onChange: (next) => write({ op: next }),
|
|
7741
|
-
options: [...
|
|
7792
|
+
options: [...baseTerms, ...extraTerms].map((c) => ({ value: c.op, label: c.label })),
|
|
7742
7793
|
attrs: { "data-condition": "comparison" }
|
|
7743
7794
|
}
|
|
7744
7795
|
),
|
|
@@ -7752,7 +7803,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
7752
7803
|
value: String(relative.days ?? 7),
|
|
7753
7804
|
onChange: (e) => write({ days: Math.max(0, Math.floor(Number(e.target.value) || 0)) })
|
|
7754
7805
|
}
|
|
7755
|
-
) : null : reader || op2
|
|
7806
|
+
) : null : reader || VALUELESS_OPS.has(op2) ? null : pointsAtARecord(chosen) ? (
|
|
7756
7807
|
// A RECORD IS PICKED BY ITS WORDS, NEVER TYPED AS AN ID. The same picker the
|
|
7757
7808
|
// cell uses, on the same `relation_words_many` the cell reads, so a filter and
|
|
7758
7809
|
// the column it filters can never disagree about what a record is called. It
|
|
@@ -7770,7 +7821,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
7770
7821
|
allowCreate: false
|
|
7771
7822
|
}
|
|
7772
7823
|
)
|
|
7773
|
-
) : isChoiceColumn(chosen) && (op2 === "eq" || op2 === "ne") ? /* @__PURE__ */ jsx32(ChoiceValue, { field: chosen.field, value: String(value ?? ""), onChange: (word) => write({ value: word }) }) : /* @__PURE__ */ jsx32(
|
|
7824
|
+
) : isChoiceColumn(chosen) && (op2 === "eq" || op2 === "ne" || op2 === "contains" || op2 === "not_contains") ? /* @__PURE__ */ jsx32(ChoiceValue, { field: chosen.field, value: String(value ?? ""), onChange: (word) => write({ value: word }) }) : /* @__PURE__ */ jsx32(
|
|
7774
7825
|
BasicInput7,
|
|
7775
7826
|
{
|
|
7776
7827
|
className: "h-8 w-28 text-xs",
|
|
@@ -40015,12 +40066,18 @@ function ViewBar({ tableId, activeViewId, onActiveView, seed, settings, onViews,
|
|
|
40015
40066
|
await load();
|
|
40016
40067
|
}
|
|
40017
40068
|
async function copySettingsFrom(source, into) {
|
|
40018
|
-
const
|
|
40069
|
+
const copied = settingsToCopy(source, into);
|
|
40070
|
+
const written = await saveViewPatch(client, tableId, into.id, copied);
|
|
40019
40071
|
if (!written.ok) {
|
|
40020
40072
|
setError(written.error);
|
|
40021
40073
|
return;
|
|
40022
40074
|
}
|
|
40023
40075
|
setCopying(false);
|
|
40076
|
+
if (views) {
|
|
40077
|
+
const next = views.map((v) => v.id === into.id ? { ...v, ...copied } : v);
|
|
40078
|
+
setViews(next);
|
|
40079
|
+
onViews?.(next);
|
|
40080
|
+
}
|
|
40024
40081
|
onSettingsCopied?.(into.id);
|
|
40025
40082
|
await load();
|
|
40026
40083
|
notify?.success(`Copied settings from \u201C${source.name}\u201D`);
|
|
@@ -53121,7 +53178,7 @@ function TableDuplicateDialog({ tableId, name, organizationId, open, onOpenChang
|
|
|
53121
53178
|
// src/ViewFilter.tsx
|
|
53122
53179
|
import { useEffect as useEffect123, useMemo as useMemo81, useState as useState149 } from "react";
|
|
53123
53180
|
import { Button as Button119, Popover as Popover19, PopoverContent as PopoverContent19, PopoverTrigger as PopoverTrigger19 } from "@ai-matrx/design-system";
|
|
53124
|
-
import { useFields as useFields38 } from "@ai-matrx/records/react";
|
|
53181
|
+
import { useFields as useFields38, useOptionalRecordsClient as useOptionalRecordsClient21 } from "@ai-matrx/records/react";
|
|
53125
53182
|
import { jsx as jsx176, jsxs as jsxs163 } from "react/jsx-runtime";
|
|
53126
53183
|
function conditionCount(expr) {
|
|
53127
53184
|
if (!expr || typeof expr !== "object" || Array.isArray(expr)) return 0;
|
|
@@ -53140,10 +53197,39 @@ function ViewFilter({ tableId, view, onApply, className }) {
|
|
|
53140
53197
|
useEffect123(() => {
|
|
53141
53198
|
setDraft(saved);
|
|
53142
53199
|
}, [view?.id, savedKey]);
|
|
53143
|
-
const
|
|
53144
|
-
|
|
53200
|
+
const client = useOptionalRecordsClient21();
|
|
53201
|
+
const relations = useMemo81(
|
|
53202
|
+
() => (fields.data ?? []).filter((f) => f.type === "relation" && !!f.relation_target),
|
|
53145
53203
|
[fields.data]
|
|
53146
53204
|
);
|
|
53205
|
+
const [linkedFields, setLinkedFields] = useState149({});
|
|
53206
|
+
useEffect123(() => {
|
|
53207
|
+
if (!open || !client) return;
|
|
53208
|
+
let cancelled = false;
|
|
53209
|
+
for (const rel of relations) {
|
|
53210
|
+
const target = String(rel.relation_target);
|
|
53211
|
+
void client.fields({ table_id: target }).then((got) => {
|
|
53212
|
+
if (cancelled || !got.ok) return;
|
|
53213
|
+
setLinkedFields((now) => ({ ...now, [rel.id]: got.data ?? [] }));
|
|
53214
|
+
});
|
|
53215
|
+
}
|
|
53216
|
+
return () => {
|
|
53217
|
+
cancelled = true;
|
|
53218
|
+
};
|
|
53219
|
+
}, [open, client, relations]);
|
|
53220
|
+
const conditionFields = useMemo81(() => {
|
|
53221
|
+
const own3 = (fields.data ?? []).map((f) => ({ id: f.id, key: f.key, label: f.label || f.key, field: f }));
|
|
53222
|
+
const reached = relations.flatMap(
|
|
53223
|
+
(rel) => (linkedFields[rel.id] ?? []).filter((inner2) => inner2.type !== "relation").map((inner2) => ({
|
|
53224
|
+
id: `${rel.id}>${inner2.id}`,
|
|
53225
|
+
key: `${rel.key}.${inner2.key}`,
|
|
53226
|
+
label: `${rel.label || rel.key} \u203A ${inner2.label || inner2.key}`,
|
|
53227
|
+
field: inner2,
|
|
53228
|
+
via: { id: rel.id, fieldId: inner2.id }
|
|
53229
|
+
}))
|
|
53230
|
+
);
|
|
53231
|
+
return [...own3, ...reached];
|
|
53232
|
+
}, [fields.data, relations, linkedFields]);
|
|
53147
53233
|
if (!view) return null;
|
|
53148
53234
|
const count = conditionCount(saved);
|
|
53149
53235
|
const changed2 = JSON.stringify(draft) !== savedKey;
|