@ai-matrx/records-ui 0.68.0 → 0.69.0
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 +27 -0
- package/dist/index.cjs +118 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +118 -6
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.cts
CHANGED
|
@@ -1406,8 +1406,18 @@ interface RelationPickerProps {
|
|
|
1406
1406
|
disabled?: boolean | undefined;
|
|
1407
1407
|
id?: string | undefined;
|
|
1408
1408
|
className?: string | undefined;
|
|
1409
|
+
/**
|
|
1410
|
+
* WHETHER A PERSON MAY MAKE THE THING SHE IS LOOKING FOR (RELATION-CREATE, 2026-09-22).
|
|
1411
|
+
*
|
|
1412
|
+
* On by default, because that is what this control is for on every surface a
|
|
1413
|
+
* person fills a record in: the grid cell, the generated form, a public form
|
|
1414
|
+
* runner. The one surface that passes `false` is the filter builder — picking
|
|
1415
|
+
* a value to FILTER by is not a reason to put a new customer in the book, and
|
|
1416
|
+
* a Create button there would write a row nobody asked for.
|
|
1417
|
+
*/
|
|
1418
|
+
allowCreate?: boolean | undefined;
|
|
1409
1419
|
}
|
|
1410
|
-
declare function RelationPicker({ field, value, onChange, disabled, id, className }: RelationPickerProps): react.JSX.Element;
|
|
1420
|
+
declare function RelationPicker({ field, value, onChange, disabled, id, className, allowCreate }: RelationPickerProps): react.JSX.Element;
|
|
1411
1421
|
|
|
1412
1422
|
interface FieldEditorControlProps {
|
|
1413
1423
|
field: Field;
|
package/dist/index.d.ts
CHANGED
|
@@ -1406,8 +1406,18 @@ interface RelationPickerProps {
|
|
|
1406
1406
|
disabled?: boolean | undefined;
|
|
1407
1407
|
id?: string | undefined;
|
|
1408
1408
|
className?: string | undefined;
|
|
1409
|
+
/**
|
|
1410
|
+
* WHETHER A PERSON MAY MAKE THE THING SHE IS LOOKING FOR (RELATION-CREATE, 2026-09-22).
|
|
1411
|
+
*
|
|
1412
|
+
* On by default, because that is what this control is for on every surface a
|
|
1413
|
+
* person fills a record in: the grid cell, the generated form, a public form
|
|
1414
|
+
* runner. The one surface that passes `false` is the filter builder — picking
|
|
1415
|
+
* a value to FILTER by is not a reason to put a new customer in the book, and
|
|
1416
|
+
* a Create button there would write a row nobody asked for.
|
|
1417
|
+
*/
|
|
1418
|
+
allowCreate?: boolean | undefined;
|
|
1409
1419
|
}
|
|
1410
|
-
declare function RelationPicker({ field, value, onChange, disabled, id, className }: RelationPickerProps): react.JSX.Element;
|
|
1420
|
+
declare function RelationPicker({ field, value, onChange, disabled, id, className, allowCreate }: RelationPickerProps): react.JSX.Element;
|
|
1411
1421
|
|
|
1412
1422
|
interface FieldEditorControlProps {
|
|
1413
1423
|
field: Field;
|
package/dist/index.js
CHANGED
|
@@ -667,22 +667,28 @@ import {
|
|
|
667
667
|
cn as cn2
|
|
668
668
|
} from "@ai-matrx/design-system";
|
|
669
669
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
670
|
-
function RelationPicker({ field, value, onChange, disabled, id, className }) {
|
|
670
|
+
function RelationPicker({ field, value, onChange, disabled, id, className, allowCreate = true }) {
|
|
671
671
|
const client = useRecordsClient();
|
|
672
672
|
const [rows, setRows] = useState2(null);
|
|
673
673
|
const [words, setWords] = useState2({});
|
|
674
674
|
const [refusal, setRefusal] = useState2(null);
|
|
675
675
|
const [search, setSearch] = useState2("");
|
|
676
676
|
const [open, setOpen] = useState2(false);
|
|
677
|
+
const [target, setTarget] = useState2(null);
|
|
678
|
+
const [targetRefusal, setTargetRefusal] = useState2(null);
|
|
679
|
+
const [creating, setCreating] = useState2(false);
|
|
680
|
+
const [newTitle, setNewTitle] = useState2("");
|
|
681
|
+
const [busy, setBusy] = useState2(false);
|
|
682
|
+
const [createRefusal, setCreateRefusal] = useState2(null);
|
|
677
683
|
const picked = useMemo3(
|
|
678
684
|
() => Array.isArray(value) ? value.map(String) : value === null || value === void 0 ? [] : [String(value)],
|
|
679
685
|
[value]
|
|
680
686
|
);
|
|
681
687
|
useEffect(() => {
|
|
682
|
-
const
|
|
683
|
-
if (!
|
|
688
|
+
const target2 = field.relation_target;
|
|
689
|
+
if (!target2) return;
|
|
684
690
|
let cancelled = false;
|
|
685
|
-
void client.list({ table_id:
|
|
691
|
+
void client.list({ table_id: target2, limit: 200 }).then((result) => {
|
|
686
692
|
if (cancelled) return;
|
|
687
693
|
if (result.ok) setRows(result.data.rows);
|
|
688
694
|
else setRefusal(refusalLineForAPerson(result.error));
|
|
@@ -711,6 +717,28 @@ function RelationPicker({ field, value, onChange, disabled, id, className }) {
|
|
|
711
717
|
cancelled = true;
|
|
712
718
|
};
|
|
713
719
|
}, [client, field.id, rows, picked, words]);
|
|
720
|
+
useEffect(() => {
|
|
721
|
+
if (!open || !allowCreate || target || targetRefusal) return;
|
|
722
|
+
const id2 = field.relation_target;
|
|
723
|
+
if (!id2) return;
|
|
724
|
+
let cancelled = false;
|
|
725
|
+
void client.tableList().then((result) => {
|
|
726
|
+
if (cancelled) return;
|
|
727
|
+
if (!result.ok) {
|
|
728
|
+
setTargetRefusal(refusalLineForAPerson(result.error));
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
const found = result.data.find((t) => t.id === id2) ?? null;
|
|
732
|
+
if (found) setTarget(found);
|
|
733
|
+
else
|
|
734
|
+
setTargetRefusal(
|
|
735
|
+
"That Table is not one you can open, so a new record cannot be added to it from here."
|
|
736
|
+
);
|
|
737
|
+
});
|
|
738
|
+
return () => {
|
|
739
|
+
cancelled = true;
|
|
740
|
+
};
|
|
741
|
+
}, [client, open, allowCreate, target, targetRefusal, field.relation_target]);
|
|
714
742
|
if (!field.relation_target) {
|
|
715
743
|
return /* @__PURE__ */ jsxs2("p", { className: "text-xs text-muted-foreground", children: [
|
|
716
744
|
fieldName(field),
|
|
@@ -730,6 +758,39 @@ function RelationPicker({ field, value, onChange, disabled, id, className }) {
|
|
|
730
758
|
else if (!full) next.add(recordId);
|
|
731
759
|
onChange(next.size === 0 ? null : [...next]);
|
|
732
760
|
};
|
|
761
|
+
const targetWord = (target?.label_singular ?? "").trim() || (target?.name ?? "").trim() || "record";
|
|
762
|
+
const create = async (rawTitle) => {
|
|
763
|
+
const title = rawTitle.trim();
|
|
764
|
+
const tableId = field.relation_target;
|
|
765
|
+
if (!tableId || title === "" || busy) return;
|
|
766
|
+
if (!target) {
|
|
767
|
+
setCreateRefusal("Still reading that Table. Try again in a moment.");
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
const titleField = target.title_field;
|
|
771
|
+
if (!titleField) {
|
|
772
|
+
setCreateRefusal(
|
|
773
|
+
`${target.name} has no column that names a record yet, so a new one would have nothing to show here. A table admin picks its title column in that table's settings.`
|
|
774
|
+
);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
setBusy(true);
|
|
778
|
+
setCreateRefusal(null);
|
|
779
|
+
const written = await client.recordWrite({ table_id: tableId, data: { [titleField]: title } });
|
|
780
|
+
if (!written.ok) {
|
|
781
|
+
setBusy(false);
|
|
782
|
+
setCreateRefusal(refusalLineForAPerson(written.error));
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const createdId = String(written.data);
|
|
786
|
+
const refreshed = await client.list({ table_id: tableId, limit: 200 });
|
|
787
|
+
if (refreshed.ok) setRows(refreshed.data.rows);
|
|
788
|
+
setBusy(false);
|
|
789
|
+
setCreating(false);
|
|
790
|
+
setNewTitle("");
|
|
791
|
+
setSearch("");
|
|
792
|
+
toggle(createdId);
|
|
793
|
+
};
|
|
733
794
|
const visible = (rows ?? []).filter(
|
|
734
795
|
(row) => search.trim() === "" ? true : titleOf(row.id).toLowerCase().includes(search.trim().toLowerCase())
|
|
735
796
|
);
|
|
@@ -776,7 +837,57 @@ function RelationPicker({ field, value, onChange, disabled, id, className }) {
|
|
|
776
837
|
}
|
|
777
838
|
) }, row.id)) }),
|
|
778
839
|
rows && visible.length === 0 ? /* @__PURE__ */ jsx3("p", { className: "p-2 text-xs text-muted-foreground", children: "Nothing in that table matches." }) : null
|
|
779
|
-
] })
|
|
840
|
+
] }),
|
|
841
|
+
allowCreate && !full ? /* @__PURE__ */ jsxs2("div", { className: "mt-1 border-t pt-1", children: [
|
|
842
|
+
targetRefusal ? /* @__PURE__ */ jsx3("p", { className: "p-2 text-xs text-destructive", children: targetRefusal }) : !target ? /* @__PURE__ */ jsx3("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading that table\u2026" }) : creating ? /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1 p-1", children: [
|
|
843
|
+
/* @__PURE__ */ jsx3(
|
|
844
|
+
BasicInput2,
|
|
845
|
+
{
|
|
846
|
+
autoFocus: true,
|
|
847
|
+
value: newTitle,
|
|
848
|
+
onChange: (e) => setNewTitle(e.target.value),
|
|
849
|
+
onKeyDown: (e) => {
|
|
850
|
+
if (e.key === "Enter") {
|
|
851
|
+
e.preventDefault();
|
|
852
|
+
void create(newTitle);
|
|
853
|
+
}
|
|
854
|
+
},
|
|
855
|
+
placeholder: `New ${targetWord}`,
|
|
856
|
+
className: "h-7 flex-1 text-xs",
|
|
857
|
+
"aria-label": `Name the new ${targetWord}`
|
|
858
|
+
}
|
|
859
|
+
),
|
|
860
|
+
/* @__PURE__ */ jsx3(
|
|
861
|
+
Button2,
|
|
862
|
+
{
|
|
863
|
+
type: "button",
|
|
864
|
+
size: "sm",
|
|
865
|
+
variant: "default",
|
|
866
|
+
disabled: busy || newTitle.trim() === "",
|
|
867
|
+
onClick: () => void create(newTitle),
|
|
868
|
+
children: busy ? "Creating\u2026" : "Create"
|
|
869
|
+
}
|
|
870
|
+
)
|
|
871
|
+
] }) : /* @__PURE__ */ jsx3(
|
|
872
|
+
Button2,
|
|
873
|
+
{
|
|
874
|
+
type: "button",
|
|
875
|
+
size: "sm",
|
|
876
|
+
variant: "ghost",
|
|
877
|
+
className: "w-full justify-start text-xs",
|
|
878
|
+
disabled: busy,
|
|
879
|
+
onClick: () => {
|
|
880
|
+
if (search.trim() !== "") void create(search);
|
|
881
|
+
else {
|
|
882
|
+
setNewTitle("");
|
|
883
|
+
setCreating(true);
|
|
884
|
+
}
|
|
885
|
+
},
|
|
886
|
+
children: search.trim() !== "" ? `Create \u201C${search.trim()}\u201D` : `New ${targetWord}`
|
|
887
|
+
}
|
|
888
|
+
),
|
|
889
|
+
createRefusal ? /* @__PURE__ */ jsx3("p", { className: "p-2 text-xs text-destructive", children: createRefusal }) : null
|
|
890
|
+
] }) : null
|
|
780
891
|
] })
|
|
781
892
|
] })
|
|
782
893
|
] });
|
|
@@ -5210,7 +5321,8 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
5210
5321
|
value: value === "" ? null : value,
|
|
5211
5322
|
onChange: (next) => write({ value: next === null || next === void 0 ? "" : String(next) }),
|
|
5212
5323
|
id: "condition-value-record",
|
|
5213
|
-
className: "min-w-[8rem]"
|
|
5324
|
+
className: "min-w-[8rem]",
|
|
5325
|
+
allowCreate: false
|
|
5214
5326
|
}
|
|
5215
5327
|
)
|
|
5216
5328
|
) : /* @__PURE__ */ jsx18(
|