@ai-matrx/records-ui 0.93.4 → 0.93.6
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 +18 -0
- package/dist/index.cjs +95 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -2
- package/dist/index.d.ts +68 -2
- package/dist/index.js +496 -404
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog — @ai-matrx/records-ui
|
|
2
2
|
|
|
3
|
+
## 0.93.6
|
|
4
|
+
|
|
5
|
+
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
6
|
+
`git diff npm/records-ui/v0.93.4..npm/records-ui/v0.93.6 -- apps/shared/records-ui`).
|
|
7
|
+
No source changes intended and no consumer action required.
|
|
8
|
+
|
|
9
|
+
## 0.93.5
|
|
10
|
+
### Unreleased
|
|
11
|
+
### page-pass shared fix (2026-09-27): custom fields — the design-system Select, one-line empty, and an agent twin for "Add field"
|
|
12
|
+
|
|
13
|
+
- **"Holds" is the design-system `Select`** (it was a native `<select>`); the name box is `BasicInput`.
|
|
14
|
+
- **Empty is one line**: "Custom fields · None yet · Add field" — no "0", no rule under nothing.
|
|
15
|
+
- **New prop `agentDoor`** on `CustomFieldsSection`: the section hands its host one door (`CustomFieldsAgentDoor` — live `state()`, a whole-request `check()` that reports every problem at once, and `addField()` through `custom.entity_field_declare` followed by a read-back). New exports: `CustomFieldsAgentDoor`, `CustomFieldsAgentState`, `CustomFieldAddRequest`, `CustomFieldAddResult`, `customFieldRequestProblems`, `CUSTOM_FIELD_ADDABLE_TYPES`.
|
|
16
|
+
- **Proof:** `custom-fields-empty-select-and-agent-door.test.tsx` (Harbor Lane Dental's message templates).
|
|
17
|
+
|
|
18
|
+
#### Consumer action
|
|
19
|
+
- None required. matrx-frontend's `EntityCustomFields` passes `agentDoor` to register the platform write target `custom_fields_add` on every page that embeds the section.
|
|
20
|
+
|
|
3
21
|
## 0.93.4
|
|
4
22
|
### Unreleased
|
|
5
23
|
### lane RENDER-3 (2026-09-27): one cell edit redraws one row — and none of the data table's toolbar, headers or footer
|
package/dist/index.cjs
CHANGED
|
@@ -54,6 +54,7 @@ __export(src_exports, {
|
|
|
54
54
|
CHECKLIST_REQUIREMENT_LABEL: () => CHECKLIST_REQUIREMENT_LABEL,
|
|
55
55
|
CONDITION_OPS: () => CONDITION_OPS,
|
|
56
56
|
CONTEXT_POLICY_LABEL: () => CONTEXT_POLICY_LABEL,
|
|
57
|
+
CUSTOM_FIELD_ADDABLE_TYPES: () => ADDABLE_TYPES,
|
|
57
58
|
CaptureRun: () => CaptureRun,
|
|
58
59
|
CaptureSheet: () => CaptureSheet,
|
|
59
60
|
ChartBlock: () => ChartBlock,
|
|
@@ -246,6 +247,7 @@ __export(src_exports, {
|
|
|
246
247
|
conditionValue: () => conditionValue,
|
|
247
248
|
controlFor: () => controlFor,
|
|
248
249
|
currencyCodeFor: () => currencyCodeFor,
|
|
250
|
+
customFieldRequestProblems: () => customFieldRequestProblems,
|
|
249
251
|
dashboardDeclareArgs: () => dashboardDeclareArgs,
|
|
250
252
|
dashboardFromSummary: () => dashboardFromSummary,
|
|
251
253
|
dateFormatFor: () => dateFormatFor,
|
|
@@ -13347,6 +13349,32 @@ var ADDABLE_TYPES = ADDABLE_TYPE_IDS.map((value) => ({
|
|
|
13347
13349
|
value,
|
|
13348
13350
|
label: fieldTypeChoice(value)?.label ?? value
|
|
13349
13351
|
}));
|
|
13352
|
+
var ADDABLE_TYPE_SET = new Set(ADDABLE_TYPE_IDS);
|
|
13353
|
+
function customFieldRequestProblems(requests, existingLabels) {
|
|
13354
|
+
const problems = [];
|
|
13355
|
+
const taken = new Set(existingLabels.map((l) => l.trim().toLowerCase()));
|
|
13356
|
+
const asked = /* @__PURE__ */ new Set();
|
|
13357
|
+
if (requests.length === 0) problems.push("Name at least one field to add.");
|
|
13358
|
+
requests.forEach((request, index) => {
|
|
13359
|
+
const at = requests.length > 1 ? `Field ${index + 1}: ` : "";
|
|
13360
|
+
const label = typeof request?.label === "string" ? request.label.trim() : "";
|
|
13361
|
+
if (!label) {
|
|
13362
|
+
problems.push(`${at}a field needs a name.`);
|
|
13363
|
+
return;
|
|
13364
|
+
}
|
|
13365
|
+
const type = request.type ?? "text";
|
|
13366
|
+
if (!ADDABLE_TYPE_SET.has(type)) {
|
|
13367
|
+
problems.push(
|
|
13368
|
+
`${at}"${label}" cannot hold "${String(type)}". Choose one of: ${ADDABLE_TYPE_IDS.join(", ")}.`
|
|
13369
|
+
);
|
|
13370
|
+
}
|
|
13371
|
+
const key = label.toLowerCase();
|
|
13372
|
+
if (taken.has(key)) problems.push(`${at}there is already a field named "${label}".`);
|
|
13373
|
+
else if (asked.has(key)) problems.push(`${at}"${label}" is asked for twice.`);
|
|
13374
|
+
asked.add(key);
|
|
13375
|
+
});
|
|
13376
|
+
return problems;
|
|
13377
|
+
}
|
|
13350
13378
|
function asField(f) {
|
|
13351
13379
|
return {
|
|
13352
13380
|
id: f.id,
|
|
@@ -13367,8 +13395,10 @@ function CustomFieldsSection({
|
|
|
13367
13395
|
entityToken,
|
|
13368
13396
|
recordId,
|
|
13369
13397
|
title = "Custom fields",
|
|
13370
|
-
className
|
|
13398
|
+
className,
|
|
13399
|
+
agentDoor
|
|
13371
13400
|
}) {
|
|
13401
|
+
const client = (0, import_react68.useRecordsClient)();
|
|
13372
13402
|
const read = (0, import_react68.useEntityRecord)(entityToken, recordId);
|
|
13373
13403
|
const rights = (0, import_react68.useEntityFieldRights)(entityToken);
|
|
13374
13404
|
const mutation = (0, import_react68.useEntityFieldMutation)();
|
|
@@ -13379,14 +13409,70 @@ function CustomFieldsSection({
|
|
|
13379
13409
|
const [draft, setDraft] = (0, import_react67.useState)({});
|
|
13380
13410
|
const fields = (0, import_react67.useMemo)(() => read.data?.fields ?? [], [read.data]);
|
|
13381
13411
|
const mayAdd = rights.data?.may_declare === true;
|
|
13412
|
+
const live = (0, import_react67.useRef)({ fields, rights: rights.data, loading: rights.loading, reload: read.reload });
|
|
13413
|
+
(0, import_react67.useEffect)(() => {
|
|
13414
|
+
live.current = { fields, rights: rights.data, loading: rights.loading, reload: read.reload };
|
|
13415
|
+
});
|
|
13416
|
+
(0, import_react67.useEffect)(() => {
|
|
13417
|
+
if (!agentDoor) return;
|
|
13418
|
+
const state = () => {
|
|
13419
|
+
const now = live.current;
|
|
13420
|
+
return {
|
|
13421
|
+
entityToken,
|
|
13422
|
+
recordId,
|
|
13423
|
+
entityLabel: now.rights?.label ?? null,
|
|
13424
|
+
mayAdd: now.rights ? now.rights.may_declare === true : now.loading ? null : false,
|
|
13425
|
+
refusal: now.rights && !now.rights.may_declare ? now.rights.reason ?? null : null,
|
|
13426
|
+
fields: now.fields.map((f) => ({ key: f.key, label: f.label, type: f.type })),
|
|
13427
|
+
types: ADDABLE_TYPES
|
|
13428
|
+
};
|
|
13429
|
+
};
|
|
13430
|
+
const check = (requests) => {
|
|
13431
|
+
const now = state();
|
|
13432
|
+
if (now.mayAdd === false) {
|
|
13433
|
+
return [now.refusal ?? "This person may not add a field here."];
|
|
13434
|
+
}
|
|
13435
|
+
if (now.mayAdd === null) return ["The section is still reading who may add a field. Try again in a moment."];
|
|
13436
|
+
return customFieldRequestProblems(requests, now.fields.map((f) => f.label));
|
|
13437
|
+
};
|
|
13438
|
+
const addField = async (request) => {
|
|
13439
|
+
const problems = check([request]);
|
|
13440
|
+
if (problems.length > 0) return { ok: false, message: problems.join(" ") };
|
|
13441
|
+
const label = request.label.trim();
|
|
13442
|
+
const type = request.type ?? "text";
|
|
13443
|
+
const declared2 = await client.entityFieldDeclare({ token: entityToken, spec: { label, type } });
|
|
13444
|
+
if (!declared2.ok) {
|
|
13445
|
+
const hint = hintForAPerson(declared2.error.hint);
|
|
13446
|
+
return { ok: false, message: hint ? `${declared2.error.message} ${hint}` : declared2.error.message };
|
|
13447
|
+
}
|
|
13448
|
+
const back = await client.entityRecordRead({ token: entityToken, record_id: recordId });
|
|
13449
|
+
live.current.reload();
|
|
13450
|
+
if (!back.ok || !(back.data?.fields ?? []).some((f) => f.id === declared2.data)) {
|
|
13451
|
+
return {
|
|
13452
|
+
ok: false,
|
|
13453
|
+
message: `The store took "${label}" but this record does not show it yet. Nothing was lost \u2014 read the page again before adding it a second time.`
|
|
13454
|
+
};
|
|
13455
|
+
}
|
|
13456
|
+
return { ok: true, field_id: declared2.data, label, type };
|
|
13457
|
+
};
|
|
13458
|
+
return agentDoor({ entityToken, recordId, state, check, addField });
|
|
13459
|
+
}, [agentDoor, client, entityToken, recordId]);
|
|
13382
13460
|
if (!read.loading && !read.error && fields.length === 0 && !mayAdd) return null;
|
|
13383
13461
|
if (read.error) return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(RefusalLine, { error: read.error, className });
|
|
13384
13462
|
const values = read.data?.custom ?? {};
|
|
13385
13463
|
const written = read.data?.custom_written ?? {};
|
|
13464
|
+
const empty = fields.length === 0;
|
|
13465
|
+
if (empty && !adding) {
|
|
13466
|
+
return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("section", { className: (0, import_design_system38.cn)("flex min-w-0 items-center gap-2 text-xs", className), children: [
|
|
13467
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)("h3", { className: "text-sm font-medium", children: title }),
|
|
13468
|
+
!read.loading ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "text-muted-foreground", children: "None yet" }) : null,
|
|
13469
|
+
mayAdd ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_design_system38.Button, { size: "sm", variant: "ghost", className: "ml-auto", onClick: () => setAdding(true), children: "Add field" }) : null
|
|
13470
|
+
] });
|
|
13471
|
+
}
|
|
13386
13472
|
return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("section", { className: (0, import_design_system38.cn)("flex min-w-0 flex-col gap-2 text-xs", className), children: [
|
|
13387
13473
|
/* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
13388
13474
|
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)("h3", { className: "text-sm font-medium", children: title }),
|
|
13389
|
-
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "text-muted-foreground", children: fields.length }),
|
|
13475
|
+
!empty ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "text-muted-foreground", children: fields.length }) : null,
|
|
13390
13476
|
fields.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
13391
13477
|
import_design_system38.Button,
|
|
13392
13478
|
{
|
|
@@ -13455,9 +13541,9 @@ function CustomFieldsSection({
|
|
|
13455
13541
|
/* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("label", { className: "flex flex-col gap-1", children: [
|
|
13456
13542
|
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "text-muted-foreground", children: "Name" }),
|
|
13457
13543
|
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
13458
|
-
|
|
13544
|
+
import_design_system38.BasicInput,
|
|
13459
13545
|
{
|
|
13460
|
-
className: "h-8
|
|
13546
|
+
className: "h-8",
|
|
13461
13547
|
value: newLabel,
|
|
13462
13548
|
onChange: (e) => setNewLabel(e.target.value),
|
|
13463
13549
|
placeholder: "Account tier",
|
|
@@ -13465,17 +13551,12 @@ function CustomFieldsSection({
|
|
|
13465
13551
|
}
|
|
13466
13552
|
)
|
|
13467
13553
|
] }),
|
|
13468
|
-
/* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("
|
|
13554
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
13469
13555
|
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "text-muted-foreground", children: "Holds" }),
|
|
13470
|
-
/* @__PURE__ */ (0, import_jsx_runtime45.
|
|
13471
|
-
"
|
|
13472
|
-
{
|
|
13473
|
-
|
|
13474
|
-
value: newType,
|
|
13475
|
-
onChange: (e) => setNewType(e.target.value),
|
|
13476
|
-
children: ADDABLE_TYPES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("option", { value: t.value, children: t.label }, t.value))
|
|
13477
|
-
}
|
|
13478
|
-
)
|
|
13556
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(import_design_system38.Select, { value: newType, onValueChange: setNewType, children: [
|
|
13557
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_design_system38.SelectTrigger, { size: "sm", "aria-label": "What this field holds", children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_design_system38.SelectValue, {}) }),
|
|
13558
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_design_system38.SelectContent, { children: ADDABLE_TYPES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_design_system38.SelectItem, { value: t.value, children: t.label }, t.value)) })
|
|
13559
|
+
] })
|
|
13479
13560
|
] }),
|
|
13480
13561
|
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_design_system38.Button, { size: "sm", type: "submit", disabled: mutation.saving || newLabel.trim() === "", children: mutation.saving ? "Adding\u2026" : "Add" })
|
|
13481
13562
|
]
|