Generalize live field bindings and use them for Studio canvas gestures
This commit is contained in:
@@ -98,6 +98,7 @@ export const instantiateInterface = (
|
||||
return {
|
||||
...common,
|
||||
kind: "value",
|
||||
...(member.nativeField ? { nativeField: true as const } : {}),
|
||||
...(member.queryRead ? { queryRead: { ...member.queryRead } } : {}),
|
||||
valueType: substitution.value(member.valueType, member.displayName),
|
||||
};
|
||||
|
||||
@@ -132,6 +132,8 @@ interface InterfaceMemberBase<Type = ValueType> {
|
||||
|
||||
export interface ValueInterfaceMember<Type = ValueType> extends InterfaceMemberBase<Type> {
|
||||
kind: "value";
|
||||
/** Every operation resolves directly to the same native state slot. */
|
||||
nativeField?: true;
|
||||
queryRead?: QueryReadContract;
|
||||
valueType: Type;
|
||||
}
|
||||
|
||||
@@ -615,6 +615,23 @@ const collectIdentityIndexes = (
|
||||
);
|
||||
}
|
||||
}
|
||||
if (member.kind === "value" && member.nativeField) {
|
||||
const names = member.operations.map((o) => o.displayName);
|
||||
if (
|
||||
!isStorableType(member.valueType) ||
|
||||
!names.includes("get") ||
|
||||
names.some((n) => !["get", "set", "edit"].includes(n)) ||
|
||||
(names.includes("set") && names.includes("edit"))
|
||||
)
|
||||
issue(
|
||||
issues,
|
||||
"invalid-state-binding",
|
||||
memberPath,
|
||||
"Fields require a storable value, a native getter and at most one setter or editor",
|
||||
);
|
||||
if (member.queryRead && member.queryRead.execution !== "native")
|
||||
issue(issues, "invalid-query-contract", memberPath, "Fields cannot have package-backed query reads");
|
||||
}
|
||||
requireText(issues, member.id, `${memberPath}.id`, "Member ID");
|
||||
requireText(issues, member.displayName, `${memberPath}.displayName`, "Member name");
|
||||
if (memberIds.has(member.id)) {
|
||||
@@ -1758,6 +1775,34 @@ const validateConformances = (
|
||||
}
|
||||
} else binding = entry.binding;
|
||||
const member = operationEntry.member;
|
||||
if (member.kind === "value" && member.nativeField) {
|
||||
const primitive = { get: "read", set: "write", edit: "edit" }[operation.displayName];
|
||||
const getter = member.operations.find((o) => o.displayName === "get");
|
||||
let read: Binding | undefined;
|
||||
try {
|
||||
if (getter)
|
||||
read = resolveForwardedBinding(
|
||||
workspace,
|
||||
conformance.atomId,
|
||||
conformance.interfaceRevisionId,
|
||||
getter.id,
|
||||
).binding;
|
||||
} catch {}
|
||||
if (
|
||||
binding.kind !== "state" ||
|
||||
binding.primitive !== primitive ||
|
||||
read?.kind !== "state" ||
|
||||
read.primitive !== "read" ||
|
||||
read.slotId !== binding.slotId
|
||||
)
|
||||
issue(
|
||||
issues,
|
||||
"invalid-state-binding",
|
||||
bindingPath,
|
||||
"Field operations must resolve to the same native state slot",
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
member.kind === "value" &&
|
||||
operation.displayName === "edit" &&
|
||||
|
||||
Reference in New Issue
Block a user