diff --git a/apps/builder/app/shared/instance-utils.ts b/apps/builder/app/shared/instance-utils.ts index fc5961b92e..a33e009b5d 100644 --- a/apps/builder/app/shared/instance-utils.ts +++ b/apps/builder/app/shared/instance-utils.ts @@ -18,6 +18,7 @@ import { insertInstancesMutable, reparentInstanceMutable, getAncestorInstanceSelector, + insertPropsCopyMutable, } from "./tree-utils"; import { removeByMutable } from "./array-utils"; @@ -25,15 +26,17 @@ export const insertNewComponentInstance = ( component: string, dropTarget: DroppableTarget ) => { - const insertedInstances = createComponentInstance(component); + const { instances: insertedInstances, props: insertedProps } = + createComponentInstance(component); const rootInstanceId = insertedInstances[0].id; - store.createTransaction([instancesStore], (instances) => { + store.createTransaction([instancesStore, propsStore], (instances, props) => { insertInstancesMutable( instances, insertedInstances, [rootInstanceId], dropTarget ); + insertPropsCopyMutable(props, insertedProps, new Map()); }); selectedInstanceSelectorStore.set([ diff --git a/apps/builder/app/shared/tree-utils.ts b/apps/builder/app/shared/tree-utils.ts index 1d6f42ac51..a3fd1a8d31 100644 --- a/apps/builder/app/shared/tree-utils.ts +++ b/apps/builder/app/shared/tree-utils.ts @@ -53,7 +53,7 @@ export const areInstanceSelectorsEqual = ( export const createComponentInstance = (component: Instance["component"]) => { const componentMeta = getComponentMeta(component); - const { children, instances } = generateDataFromEmbedTemplate( + const { children, instances, props } = generateDataFromEmbedTemplate( componentMeta?.children ?? [] ); // put first to be interpreted as root @@ -63,7 +63,7 @@ export const createComponentInstance = (component: Instance["component"]) => { component, children, }); - return instances; + return { instances, props }; }; const isInstanceDroppable = (instance: Instance) => { diff --git a/packages/react-sdk/src/embed-template.test.ts b/packages/react-sdk/src/embed-template.test.ts index 4b782a5c04..d1cd339e53 100644 --- a/packages/react-sdk/src/embed-template.test.ts +++ b/packages/react-sdk/src/embed-template.test.ts @@ -3,7 +3,7 @@ import { generateDataFromEmbedTemplate } from "./embed-template"; const expectString = expect.any(String) as unknown as string; -test("generate tree from template", () => { +test("generate data for embedding from instances and text", () => { expect( generateDataFromEmbedTemplate([ { type: "text", value: "hello" }, @@ -38,5 +38,68 @@ test("generate tree from template", () => { children: [], }, ], + props: [], + }); +}); + +test("generate data for embedding from props", () => { + expect( + generateDataFromEmbedTemplate([ + { + type: "instance", + component: "Box1", + props: [ + { type: "string", name: "data-prop1", value: "value1" }, + { type: "string", name: "data-prop2", value: "value2" }, + ], + children: [ + { + type: "instance", + component: "Box2", + props: [{ type: "string", name: "data-prop3", value: "value3" }], + children: [], + }, + ], + }, + ]) + ).toEqual({ + children: [{ type: "id", value: expectString }], + instances: [ + { + type: "instance", + id: expectString, + component: "Box1", + children: [{ type: "id", value: expectString }], + }, + { + type: "instance", + id: expectString, + component: "Box2", + children: [], + }, + ], + props: [ + { + type: "string", + id: expectString, + instanceId: expectString, + name: "data-prop1", + value: "value1", + }, + { + type: "string", + id: expectString, + instanceId: expectString, + name: "data-prop2", + value: "value2", + }, + { + type: "string", + id: expectString, + instanceId: expectString, + name: "data-prop3", + value: "value3", + }, + ], }); }); diff --git a/packages/react-sdk/src/embed-template.ts b/packages/react-sdk/src/embed-template.ts index 9f399ac692..b94e6969b3 100644 --- a/packages/react-sdk/src/embed-template.ts +++ b/packages/react-sdk/src/embed-template.ts @@ -1,6 +1,10 @@ import { z } from "zod"; import { nanoid } from "nanoid"; -import type { Instance, InstancesList } from "@webstudio-is/project-build"; +import { + type Instance, + type InstancesList, + PropsList, +} from "@webstudio-is/project-build"; const EmbedTemplateText = z.object({ type: z.literal("text"), @@ -9,32 +13,73 @@ const EmbedTemplateText = z.object({ type EmbedTemplateText = z.infer; +const EmbedTemplateProp = z.union([ + z.object({ + type: z.literal("number"), + name: z.string(), + value: z.number(), + }), + z.object({ + type: z.literal("string"), + name: z.string(), + value: z.string(), + }), + z.object({ + type: z.literal("boolean"), + name: z.string(), + value: z.boolean(), + }), + z.object({ + type: z.literal("string[]"), + name: z.string(), + value: z.array(z.string()), + }), +]); + +type EmbedTemplateProp = z.infer; + type EmbedTemplateInstance = { type: "instance"; component: string; + props?: EmbedTemplateProp[]; children: Array; }; -const EmbedTemplateInstancen: z.ZodType = z.object({ +const EmbedTemplateInstance: z.ZodType = z.object({ type: z.literal("instance"), component: z.string(), + props: z.optional(z.array(EmbedTemplateProp)), children: z.lazy(() => WsEmbedTemplate), }); export const WsEmbedTemplate = z.array( - z.union([z.lazy(() => EmbedTemplateInstancen), EmbedTemplateText]) + z.union([z.lazy(() => EmbedTemplateInstance), EmbedTemplateText]) ); export type WsEmbedTemplate = z.infer; const createInstancesFromTemplate = ( treeTemplate: WsEmbedTemplate, - instances: InstancesList + instances: InstancesList, + props: PropsList ) => { const parentChildren: Instance["children"] = []; for (const item of treeTemplate) { if (item.type === "instance") { const instanceId = nanoid(); + + // populate props + if (item.props) { + for (const prop of item.props) { + props.push({ + id: nanoid(), + instanceId, + ...prop, + }); + } + } + + // populate instances const instance: Instance = { type: "instance", id: instanceId, @@ -43,7 +88,11 @@ const createInstancesFromTemplate = ( }; instances.push(instance); // traverse children after to preserve top down order - instance.children = createInstancesFromTemplate(item.children, instances); + instance.children = createInstancesFromTemplate( + item.children, + instances, + props + ); parentChildren.push({ type: "id", value: instanceId, @@ -63,6 +112,7 @@ export const generateDataFromEmbedTemplate = ( treeTemplate: WsEmbedTemplate ) => { const instances: InstancesList = []; - const children = createInstancesFromTemplate(treeTemplate, instances); - return { children, instances }; + const props: PropsList = []; + const children = createInstancesFromTemplate(treeTemplate, instances, props); + return { children, instances, props }; };