@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/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
# Changelog — @ai-matrx/records-ui
|
|
2
2
|
|
|
3
|
-
## 0.115.
|
|
3
|
+
## 0.115.81
|
|
4
4
|
|
|
5
5
|
UNVERIFIED: this release has no Unreleased entry and the source comparison could not be made (the caller supplied no source comparison). Whether source changed is unknown; consumer action is unknown.
|
|
6
6
|
Check the package's gitHead on npm against this version's gitHead: `git diff <previous gitHead> <this gitHead> -- apps/shared/records-ui/src`.
|
|
7
7
|
|
|
8
|
+
## 0.115.79
|
|
9
|
+
|
|
10
|
+
SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
|
|
11
|
+
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
|
|
12
|
+
Diff: `git diff c3135c57b9 aa17cdaf9c -- apps/shared/records-ui/src`
|
|
13
|
+
|
|
14
|
+
Changed shipped source files (7):
|
|
15
|
+
- `apps/shared/records-ui/src/ImportWizard.tsx`
|
|
16
|
+
- `apps/shared/records-ui/src/TablePage.tsx`
|
|
17
|
+
- `apps/shared/records-ui/src/ViewBar.tsx`
|
|
18
|
+
- `apps/shared/records-ui/src/gridAttachments.tsx`
|
|
19
|
+
- `apps/shared/records-ui/src/gridColumns.tsx`
|
|
20
|
+
- `apps/shared/records-ui/src/importRuleCheck.ts`
|
|
21
|
+
- `apps/shared/records-ui/src/viewActions.ts`
|
|
22
|
+
|
|
8
23
|
## 0.115.78
|
|
9
24
|
|
|
10
25
|
SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
|
|
@@ -31,6 +46,7 @@ Changed shipped source files (3):
|
|
|
31
46
|
## 0.115.76
|
|
32
47
|
### Unreleased
|
|
33
48
|
|
|
49
|
+
- `ViewFilter` / `Condition` (NOTION-Q): a view's filter asks a multi-select column Notion's list questions (contains, does not contain, is empty, is not empty) and offers the fields of every table a relation points at ("Client › Client Type is / is not Past Client", the store's `linked` node: "is" = some linked record is, "is not" = none is). New exports `LIST_TERMS`, `linkedNode`, `linkedOf`; `ConditionField.via`. Needs the store migration `notionq_a_a_multi_pick_and_a_linked_field_filter_a_list`.
|
|
34
50
|
- `ViewSwitcher newRowValues`: the host hands the values a new row starts with (Spaces: a view filtered to "This page's row" hands the relation), and every add path of every layout — the grid's add line and toolbar, board and pipeline "+", gallery, list, calendar, timeline — writes them with the row. A path's own values (a board column's stage) win. Absent: a blank row, as before.
|
|
35
51
|
- The formula editor's function list follows the store's vocabulary: Airtable's remaining functions (ROUNDUP, FLOOR, COUNTA,
|
|
36
52
|
IS_BEFORE, DATETIME_DIFF, WORKDAY_DIFF, ARRAYFLATTEN, REGEX_EXTRACT, RECORD_ID, …) are offered, and CREATED_TIME() and
|
package/dist/index.cjs
CHANGED
|
@@ -8006,6 +8006,33 @@ var CONDITION_OPS = [
|
|
|
8006
8006
|
{ op: "gte", label: "is at least" },
|
|
8007
8007
|
{ op: "lte", label: "is at most" }
|
|
8008
8008
|
];
|
|
8009
|
+
var LIST_TERMS = [
|
|
8010
|
+
{ op: "contains", label: "contains" },
|
|
8011
|
+
{ op: "not_contains", label: "does not contain" },
|
|
8012
|
+
{ op: "is_empty", label: "is empty" },
|
|
8013
|
+
{ op: "is_not_empty", label: "is not empty" }
|
|
8014
|
+
];
|
|
8015
|
+
var VALUELESS_OPS = /* @__PURE__ */ new Set(["present", "is_empty", "is_not_empty"]);
|
|
8016
|
+
var LINK_SEP = ">";
|
|
8017
|
+
function linkedNode(viaId, innerId, op2, value) {
|
|
8018
|
+
return {
|
|
8019
|
+
op: "linked",
|
|
8020
|
+
match: op2 === "eq" ? "any" : "none",
|
|
8021
|
+
args: [{ field: viaId }, { op: "eq", args: [{ field: innerId }, { const: value }] }]
|
|
8022
|
+
};
|
|
8023
|
+
}
|
|
8024
|
+
function linkedOf(expr) {
|
|
8025
|
+
const node = asNode(expr);
|
|
8026
|
+
if (!node || node["op"] !== "linked") return null;
|
|
8027
|
+
const [rel, cond] = argsOf(node);
|
|
8028
|
+
const innerNode = asNode(cond);
|
|
8029
|
+
const [a, b] = innerNode ? argsOf(innerNode) : [];
|
|
8030
|
+
const match = node["match"] ?? "any";
|
|
8031
|
+
if (!rel || typeof rel["field"] !== "string" || !innerNode || innerNode["op"] !== "eq") return null;
|
|
8032
|
+
if (!a || typeof a["field"] !== "string" || !b || !("const" in b)) return null;
|
|
8033
|
+
if (match !== "any" && match !== "none") return null;
|
|
8034
|
+
return { via: rel["field"], inner: a["field"], op: match === "any" ? "eq" : "ne", value: b["const"] };
|
|
8035
|
+
}
|
|
8009
8036
|
var FilterTermsContext = (0, import_react46.createContext)(false);
|
|
8010
8037
|
var RELATIVE_DAY_TERMS = [
|
|
8011
8038
|
{ kind: "today", label: "is today" },
|
|
@@ -8118,13 +8145,15 @@ function ChoiceValue({ field: field2, value, onChange }) {
|
|
|
8118
8145
|
}
|
|
8119
8146
|
function conditionIsSimple(expr) {
|
|
8120
8147
|
if (expr === null || expr === void 0) return true;
|
|
8121
|
-
if (relativeDayOf(expr) !== null || meOf(expr) !== null) return true;
|
|
8148
|
+
if (relativeDayOf(expr) !== null || meOf(expr) !== null || linkedOf(expr) !== null) return true;
|
|
8122
8149
|
const node = expr;
|
|
8123
|
-
if (typeof node.op !== "string" || !CONDITION_OPS.some((c) => c.op === node.op))
|
|
8150
|
+
if (typeof node.op !== "string" || !(CONDITION_OPS.some((c) => c.op === node.op) || LIST_TERMS.some((c) => c.op === node.op))) {
|
|
8151
|
+
return false;
|
|
8152
|
+
}
|
|
8124
8153
|
if (!Array.isArray(node.args)) return false;
|
|
8125
8154
|
const first = node.args[0];
|
|
8126
8155
|
if (!first || typeof first.field !== "string") return false;
|
|
8127
|
-
if (node.op
|
|
8156
|
+
if (VALUELESS_OPS.has(node.op)) return node.args.length === 1;
|
|
8128
8157
|
const second = node.args[1];
|
|
8129
8158
|
return node.args.length === 2 && !!second && "const" in second;
|
|
8130
8159
|
}
|
|
@@ -8142,7 +8171,13 @@ function conditionInWords(expr, fields) {
|
|
|
8142
8171
|
lte: "is at most",
|
|
8143
8172
|
and: "and",
|
|
8144
8173
|
or: "or",
|
|
8145
|
-
matches: "matches"
|
|
8174
|
+
matches: "matches",
|
|
8175
|
+
contains: "contains",
|
|
8176
|
+
not_contains: "does not contain",
|
|
8177
|
+
has_any: "has any of",
|
|
8178
|
+
has_all: "has all of",
|
|
8179
|
+
has_none: "has none of",
|
|
8180
|
+
is_exactly: "is exactly"
|
|
8146
8181
|
};
|
|
8147
8182
|
const walk = (node) => {
|
|
8148
8183
|
if (node === null || node === void 0) return "nothing";
|
|
@@ -8163,6 +8198,12 @@ function conditionInWords(expr, fields) {
|
|
|
8163
8198
|
const args = Array.isArray(n["args"]) ? n["args"] : [];
|
|
8164
8199
|
if (op2 === "not") return `not ${walk(args[0])}`;
|
|
8165
8200
|
if (op2 === "present") return `${walk(args[0])} has been answered`;
|
|
8201
|
+
if (op2 === "is_empty") return `${walk(args[0])} is empty`;
|
|
8202
|
+
if (op2 === "is_not_empty") return `${walk(args[0])} is not empty`;
|
|
8203
|
+
if (op2 === "linked") {
|
|
8204
|
+
const how = n["match"] === "every" ? "every" : n["match"] === "none" ? "none of" : "any";
|
|
8205
|
+
return `${how} linked record in ${walk(args[0])}: ${walk(args[1])}`;
|
|
8206
|
+
}
|
|
8166
8207
|
const dayRange = relativeDayOf(n);
|
|
8167
8208
|
if (dayRange) {
|
|
8168
8209
|
const term = RELATIVE_DAY_TERMS.find((t) => t.kind === dayRange.kind);
|
|
@@ -8195,12 +8236,14 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
8195
8236
|
const filterTerms = (0, import_react46.useContext)(FilterTermsContext);
|
|
8196
8237
|
const relative = relativeDayOf(expr);
|
|
8197
8238
|
const reader = meOf(expr);
|
|
8198
|
-
const
|
|
8199
|
-
const
|
|
8200
|
-
const
|
|
8239
|
+
const linked = linkedOf(expr);
|
|
8240
|
+
const op2 = relative ? `rel:${relative.kind}` : reader ? reader.which : linked ? linked.op : node?.op ?? "";
|
|
8241
|
+
const onField = linked ? `${linked.via}${LINK_SEP}${linked.inner}` : node?.args?.[0]?.field ?? "";
|
|
8242
|
+
const value = relative || reader ? "" : linked ? linked.value : node?.args?.[1]?.const ?? "";
|
|
8201
8243
|
const chosen = fields.find((f) => f.id === onField);
|
|
8202
8244
|
const chosenKind = chosen?.field ? String(editorKindFor(chosen.field)) : "";
|
|
8203
|
-
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" }] : [];
|
|
8245
|
+
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 : [];
|
|
8246
|
+
const baseTerms = chosen?.via ? CONDITION_OPS.filter((c) => c.op === "eq" || c.op === "ne") : CONDITION_OPS;
|
|
8204
8247
|
const relativeTerm = relative ? RELATIVE_DAY_TERMS.find((t) => t.kind === relative.kind) : void 0;
|
|
8205
8248
|
function write(next) {
|
|
8206
8249
|
const picked = next.op;
|
|
@@ -8213,8 +8256,11 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
8213
8256
|
onChange(relativeDayNode(fieldNow, relative.kind, next.days));
|
|
8214
8257
|
return;
|
|
8215
8258
|
}
|
|
8216
|
-
if (next.field !== void 0 && (relative || reader)) {
|
|
8217
|
-
|
|
8259
|
+
if (next.field !== void 0 && (relative || reader || linked)) {
|
|
8260
|
+
const target2 = fields.find((f) => f.id === next.field);
|
|
8261
|
+
onChange(
|
|
8262
|
+
!next.field ? null : target2?.via ? linkedNode(target2.via.id, target2.via.fieldId, "eq", "") : { op: CONDITION_OPS[0].op, args: [{ field: next.field }, { const: "" }] }
|
|
8263
|
+
);
|
|
8218
8264
|
return;
|
|
8219
8265
|
}
|
|
8220
8266
|
const chosenOp = next.op ?? (op2 || (next.field ? CONDITION_OPS[0].op : ""));
|
|
@@ -8224,8 +8270,13 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
8224
8270
|
onChange(null);
|
|
8225
8271
|
return;
|
|
8226
8272
|
}
|
|
8273
|
+
const target = fields.find((f) => f.id === chosenField);
|
|
8274
|
+
if (target?.via) {
|
|
8275
|
+
onChange(linkedNode(target.via.id, target.via.fieldId, chosenOp === "ne" ? "ne" : "eq", chosenValue));
|
|
8276
|
+
return;
|
|
8277
|
+
}
|
|
8227
8278
|
onChange(
|
|
8228
|
-
chosenOp
|
|
8279
|
+
VALUELESS_OPS.has(chosenOp) ? { op: chosenOp, args: [{ field: chosenField }] } : { op: chosenOp, args: [{ field: chosenField }, { const: chosenValue }] }
|
|
8229
8280
|
);
|
|
8230
8281
|
}
|
|
8231
8282
|
return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: className ?? "flex flex-wrap items-center gap-1 text-xs", children: [
|
|
@@ -8258,7 +8309,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
8258
8309
|
className: "h-8 w-auto min-w-[6rem] text-xs",
|
|
8259
8310
|
value: op2,
|
|
8260
8311
|
onChange: (next) => write({ op: next }),
|
|
8261
|
-
options: [...
|
|
8312
|
+
options: [...baseTerms, ...extraTerms].map((c) => ({ value: c.op, label: c.label })),
|
|
8262
8313
|
attrs: { "data-condition": "comparison" }
|
|
8263
8314
|
}
|
|
8264
8315
|
),
|
|
@@ -8272,7 +8323,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
8272
8323
|
value: String(relative.days ?? 7),
|
|
8273
8324
|
onChange: (e) => write({ days: Math.max(0, Math.floor(Number(e.target.value) || 0)) })
|
|
8274
8325
|
}
|
|
8275
|
-
) : null : reader || op2
|
|
8326
|
+
) : null : reader || VALUELESS_OPS.has(op2) ? null : pointsAtARecord(chosen) ? (
|
|
8276
8327
|
// A RECORD IS PICKED BY ITS WORDS, NEVER TYPED AS AN ID. The same picker the
|
|
8277
8328
|
// cell uses, on the same `relation_words_many` the cell reads, so a filter and
|
|
8278
8329
|
// the column it filters can never disagree about what a record is called. It
|
|
@@ -8290,7 +8341,7 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
8290
8341
|
allowCreate: false
|
|
8291
8342
|
}
|
|
8292
8343
|
)
|
|
8293
|
-
) : isChoiceColumn(chosen) && (op2 === "eq" || op2 === "ne") ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ChoiceValue, { field: chosen.field, value: String(value ?? ""), onChange: (word) => write({ value: word }) }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
|
|
8344
|
+
) : isChoiceColumn(chosen) && (op2 === "eq" || op2 === "ne" || op2 === "contains" || op2 === "not_contains") ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ChoiceValue, { field: chosen.field, value: String(value ?? ""), onChange: (word) => write({ value: word }) }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
|
|
8294
8345
|
import_design_system28.BasicInput,
|
|
8295
8346
|
{
|
|
8296
8347
|
className: "h-8 w-28 text-xs",
|
|
@@ -40147,12 +40198,18 @@ function ViewBar({ tableId, activeViewId, onActiveView, seed, settings, onViews,
|
|
|
40147
40198
|
await load();
|
|
40148
40199
|
}
|
|
40149
40200
|
async function copySettingsFrom(source, into) {
|
|
40150
|
-
const
|
|
40201
|
+
const copied = settingsToCopy(source, into);
|
|
40202
|
+
const written = await saveViewPatch(client, tableId, into.id, copied);
|
|
40151
40203
|
if (!written.ok) {
|
|
40152
40204
|
setError(written.error);
|
|
40153
40205
|
return;
|
|
40154
40206
|
}
|
|
40155
40207
|
setCopying(false);
|
|
40208
|
+
if (views) {
|
|
40209
|
+
const next = views.map((v) => v.id === into.id ? { ...v, ...copied } : v);
|
|
40210
|
+
setViews(next);
|
|
40211
|
+
onViews?.(next);
|
|
40212
|
+
}
|
|
40156
40213
|
onSettingsCopied?.(into.id);
|
|
40157
40214
|
await load();
|
|
40158
40215
|
notify?.success(`Copied settings from \u201C${source.name}\u201D`);
|
|
@@ -53157,10 +53214,39 @@ function ViewFilter({ tableId, view, onApply, className }) {
|
|
|
53157
53214
|
(0, import_react310.useEffect)(() => {
|
|
53158
53215
|
setDraft(saved);
|
|
53159
53216
|
}, [view?.id, savedKey]);
|
|
53160
|
-
const
|
|
53161
|
-
|
|
53217
|
+
const client = (0, import_react311.useOptionalRecordsClient)();
|
|
53218
|
+
const relations = (0, import_react310.useMemo)(
|
|
53219
|
+
() => (fields.data ?? []).filter((f) => f.type === "relation" && !!f.relation_target),
|
|
53162
53220
|
[fields.data]
|
|
53163
53221
|
);
|
|
53222
|
+
const [linkedFields, setLinkedFields] = (0, import_react310.useState)({});
|
|
53223
|
+
(0, import_react310.useEffect)(() => {
|
|
53224
|
+
if (!open || !client) return;
|
|
53225
|
+
let cancelled = false;
|
|
53226
|
+
for (const rel of relations) {
|
|
53227
|
+
const target = String(rel.relation_target);
|
|
53228
|
+
void client.fields({ table_id: target }).then((got) => {
|
|
53229
|
+
if (cancelled || !got.ok) return;
|
|
53230
|
+
setLinkedFields((now) => ({ ...now, [rel.id]: got.data ?? [] }));
|
|
53231
|
+
});
|
|
53232
|
+
}
|
|
53233
|
+
return () => {
|
|
53234
|
+
cancelled = true;
|
|
53235
|
+
};
|
|
53236
|
+
}, [open, client, relations]);
|
|
53237
|
+
const conditionFields = (0, import_react310.useMemo)(() => {
|
|
53238
|
+
const own3 = (fields.data ?? []).map((f) => ({ id: f.id, key: f.key, label: f.label || f.key, field: f }));
|
|
53239
|
+
const reached = relations.flatMap(
|
|
53240
|
+
(rel) => (linkedFields[rel.id] ?? []).filter((inner2) => inner2.type !== "relation").map((inner2) => ({
|
|
53241
|
+
id: `${rel.id}>${inner2.id}`,
|
|
53242
|
+
key: `${rel.key}.${inner2.key}`,
|
|
53243
|
+
label: `${rel.label || rel.key} \u203A ${inner2.label || inner2.key}`,
|
|
53244
|
+
field: inner2,
|
|
53245
|
+
via: { id: rel.id, fieldId: inner2.id }
|
|
53246
|
+
}))
|
|
53247
|
+
);
|
|
53248
|
+
return [...own3, ...reached];
|
|
53249
|
+
}, [fields.data, relations, linkedFields]);
|
|
53164
53250
|
if (!view) return null;
|
|
53165
53251
|
const count = conditionCount(saved);
|
|
53166
53252
|
const changed2 = JSON.stringify(draft) !== savedKey;
|