mirror of
https://github.com/webstudio-is/webstudio.git
synced 2025-03-14 09:57:02 +00:00
refactor: rewrite webhook form template with jsx (#4721)
Big one - added `new Variable(name, initialValue)` to defined unique variable - added `expression\`${variable}\`` to access variables by reference - added `new Action(args, expression\`\`)` support - added `ws:show` prop which is converted to data-ws-show under the hood All these features are necessary to represent all webhook form features. They also may be handy in tests.
This commit is contained in:
@ -131,17 +131,21 @@ const $componentOptions = computed(
|
||||
});
|
||||
}
|
||||
for (const [name, meta] of templates) {
|
||||
const label = getInstanceLabel({ component: name }, meta);
|
||||
if (meta.category === "hidden" || meta.category === "internal") {
|
||||
continue;
|
||||
}
|
||||
const componentMeta = metas.get(name);
|
||||
const label =
|
||||
meta.label ??
|
||||
componentMeta?.label ??
|
||||
getInstanceLabel({ component: name }, meta);
|
||||
componentOptions.push({
|
||||
tokens: ["components", label, meta.category],
|
||||
type: "component",
|
||||
component: name,
|
||||
label,
|
||||
category: meta.category,
|
||||
icon: meta.icon ?? metas.get(name)?.icon ?? "",
|
||||
icon: meta.icon ?? componentMeta?.icon ?? "",
|
||||
order: meta.order,
|
||||
});
|
||||
}
|
||||
|
@ -68,13 +68,17 @@ const $metas = computed(
|
||||
});
|
||||
}
|
||||
for (const [name, templateMeta] of templates) {
|
||||
const componentMeta = componentMetas.get(name);
|
||||
metas.push({
|
||||
name,
|
||||
category: templateMeta.category ?? "hidden",
|
||||
order: templateMeta.order,
|
||||
label: getInstanceLabel({ component: name }, templateMeta),
|
||||
label:
|
||||
templateMeta.label ??
|
||||
componentMeta?.label ??
|
||||
getInstanceLabel({ component: name }, templateMeta),
|
||||
description: templateMeta.description,
|
||||
icon: templateMeta.icon ?? componentMetas.get(name)?.icon ?? "",
|
||||
icon: templateMeta.icon ?? componentMeta?.icon ?? "",
|
||||
});
|
||||
}
|
||||
const metasByCategory = mapGroupBy(metas, (meta) => meta.category);
|
||||
|
@ -241,7 +241,6 @@
|
||||
border-bottom-width: 1px;
|
||||
border-left-width: 1px;
|
||||
outline-width: 1px;
|
||||
min-height: 20px;
|
||||
}
|
||||
:where(label.w-input-label) {
|
||||
box-sizing: border-box;
|
||||
|
@ -5,6 +5,7 @@ export { meta as Heading } from "./heading.template";
|
||||
export { meta as Paragraph } from "./paragraph.template";
|
||||
export { meta as Link } from "./link.template";
|
||||
export { meta as Button } from "./button.template";
|
||||
export { meta as Form } from "./webhook-form.template";
|
||||
export { meta as Blockquote } from "./blockquote.template";
|
||||
export { meta as List } from "./list.template";
|
||||
export { meta as ListItem } from "./list-item.template";
|
||||
|
47
packages/sdk-components-react/src/webhook-form.template.tsx
Normal file
47
packages/sdk-components-react/src/webhook-form.template.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import {
|
||||
$,
|
||||
ActionValue,
|
||||
expression,
|
||||
PlaceholderValue,
|
||||
Variable,
|
||||
type TemplateMeta,
|
||||
} from "@webstudio-is/template";
|
||||
|
||||
const formState = new Variable("formState", "initial");
|
||||
|
||||
export const meta: TemplateMeta = {
|
||||
category: "data",
|
||||
order: 1,
|
||||
description: "Collect user data and send it to any webhook.",
|
||||
template: (
|
||||
<$.Form
|
||||
state={expression`${formState}`}
|
||||
onStateChange={
|
||||
new ActionValue(["state"], expression`${formState} = state`)
|
||||
}
|
||||
>
|
||||
<$.Box
|
||||
ws:label="Form Content"
|
||||
ws:show={expression`${formState} === 'initial' || ${formState} === 'error'`}
|
||||
>
|
||||
<$.Label>{new PlaceholderValue("Name")}</$.Label>
|
||||
<$.Input name="name" />
|
||||
<$.Label>{new PlaceholderValue("Email")}</$.Label>
|
||||
<$.Input name="email" />
|
||||
<$.Button>{new PlaceholderValue("Submit")}</$.Button>
|
||||
</$.Box>
|
||||
<$.Box
|
||||
ws:label="Success Message"
|
||||
ws:show={expression`${formState} === 'success'`}
|
||||
>
|
||||
{new PlaceholderValue("Thank you for getting in touch!")}
|
||||
</$.Box>
|
||||
<$.Box
|
||||
ws:label="Error Message"
|
||||
ws:show={expression`${formState} === 'error'`}
|
||||
>
|
||||
{new PlaceholderValue("Sorry, something went wrong.")}
|
||||
</$.Box>
|
||||
</$.Form>
|
||||
),
|
||||
};
|
@ -1,126 +1,23 @@
|
||||
import { WebhookFormIcon } from "@webstudio-is/icons/svg";
|
||||
import type { WsComponentMeta, WsComponentPropsMeta } from "@webstudio-is/sdk";
|
||||
import { showAttribute } from "@webstudio-is/react-sdk";
|
||||
import { form } from "@webstudio-is/sdk/normalize.css";
|
||||
import { props } from "./__generated__/webhook-form.props";
|
||||
import { meta as baseMeta } from "./form.ws";
|
||||
|
||||
export const meta: WsComponentMeta = {
|
||||
...baseMeta,
|
||||
category: "data",
|
||||
label: "Webhook Form",
|
||||
description: "Collect user data and send it to any webhook.",
|
||||
order: 1,
|
||||
icon: WebhookFormIcon,
|
||||
type: "container",
|
||||
constraints: {
|
||||
relation: "ancestor",
|
||||
component: { $nin: ["Form", "Button", "Link"] },
|
||||
},
|
||||
presetStyle: {
|
||||
form,
|
||||
},
|
||||
states: [
|
||||
{ selector: "[data-state=error]", label: "Error" },
|
||||
{ selector: "[data-state=success]", label: "Success" },
|
||||
],
|
||||
template: [
|
||||
{
|
||||
type: "instance",
|
||||
component: "Form",
|
||||
variables: {
|
||||
formState: { initialValue: "initial" },
|
||||
},
|
||||
props: [
|
||||
{
|
||||
type: "expression",
|
||||
name: "state",
|
||||
code: "formState",
|
||||
},
|
||||
{
|
||||
type: "action",
|
||||
name: "onStateChange",
|
||||
value: [
|
||||
{ type: "execute", args: ["state"], code: `formState = state` },
|
||||
],
|
||||
},
|
||||
],
|
||||
children: [
|
||||
{
|
||||
type: "instance",
|
||||
label: "Form Content",
|
||||
component: "Box",
|
||||
props: [
|
||||
{
|
||||
type: "expression",
|
||||
name: showAttribute,
|
||||
code: "formState === 'initial' || formState === 'error'",
|
||||
},
|
||||
],
|
||||
children: [
|
||||
{
|
||||
type: "instance",
|
||||
component: "Label",
|
||||
children: [{ type: "text", value: "Name", placeholder: true }],
|
||||
},
|
||||
{
|
||||
type: "instance",
|
||||
component: "Input",
|
||||
props: [{ type: "string", name: "name", value: "name" }],
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
type: "instance",
|
||||
component: "Label",
|
||||
children: [{ type: "text", value: "Email", placeholder: true }],
|
||||
},
|
||||
{
|
||||
type: "instance",
|
||||
component: "Input",
|
||||
props: [{ type: "string", name: "name", value: "email" }],
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
type: "instance",
|
||||
component: "Button",
|
||||
children: [{ type: "text", value: "Submit", placeholder: true }],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
type: "instance",
|
||||
label: "Success Message",
|
||||
component: "Box",
|
||||
props: [
|
||||
{
|
||||
type: "expression",
|
||||
name: showAttribute,
|
||||
code: "formState === 'success'",
|
||||
},
|
||||
],
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: "Thank you for getting in touch!",
|
||||
placeholder: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
type: "instance",
|
||||
label: "Error Message",
|
||||
component: "Box",
|
||||
props: [
|
||||
{
|
||||
type: "expression",
|
||||
name: showAttribute,
|
||||
code: "formState === 'error'",
|
||||
},
|
||||
],
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: "Sorry, something went wrong.",
|
||||
placeholder: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const propsMeta: WsComponentPropsMeta = {
|
||||
|
@ -19,6 +19,7 @@
|
||||
"@webstudio-is/css-data": "workspace:*",
|
||||
"@webstudio-is/css-engine": "workspace:*",
|
||||
"@webstudio-is/sdk": "workspace:*",
|
||||
"@webstudio-is/react-sdk": "workspace:*",
|
||||
"react": "18.3.0-canary-14898b6a9-20240318"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -1,12 +1,16 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { showAttribute } from "@webstudio-is/react-sdk";
|
||||
import {
|
||||
$,
|
||||
ActionValue,
|
||||
AssetValue,
|
||||
expression,
|
||||
ExpressionValue,
|
||||
PageValue,
|
||||
ParameterValue,
|
||||
PlaceholderValue,
|
||||
renderTemplate,
|
||||
Variable,
|
||||
} from "./jsx";
|
||||
import { css } from "./css";
|
||||
|
||||
@ -291,3 +295,179 @@ test("avoid generating style data without styles", () => {
|
||||
expect(styleSourceSelections).toEqual([]);
|
||||
expect(styles).toEqual([]);
|
||||
});
|
||||
|
||||
test("render variable used in prop expression", () => {
|
||||
const count = new Variable("count", 1);
|
||||
const { props, dataSources } = renderTemplate(
|
||||
<$.Body ws:id="body" data-count={expression`${count}`}></$.Body>
|
||||
);
|
||||
expect(props).toEqual([
|
||||
{
|
||||
id: "body:data-count",
|
||||
instanceId: "body",
|
||||
name: "data-count",
|
||||
type: "expression",
|
||||
value: "$ws$dataSource$0",
|
||||
},
|
||||
]);
|
||||
expect(dataSources).toEqual([
|
||||
{
|
||||
type: "variable",
|
||||
id: "0",
|
||||
scopeInstanceId: "body",
|
||||
name: "count",
|
||||
value: { type: "number", value: 1 },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("render variable used in child expression", () => {
|
||||
const count = new Variable("count", 1);
|
||||
const { instances, dataSources } = renderTemplate(
|
||||
<$.Body ws:id="body">{expression`${count}`}</$.Body>
|
||||
);
|
||||
expect(instances).toEqual([
|
||||
{
|
||||
type: "instance",
|
||||
id: "body",
|
||||
component: "Body",
|
||||
children: [{ type: "expression", value: "$ws$dataSource$0" }],
|
||||
},
|
||||
]);
|
||||
expect(dataSources).toEqual([
|
||||
{
|
||||
type: "variable",
|
||||
id: "0",
|
||||
scopeInstanceId: "body",
|
||||
name: "count",
|
||||
value: { type: "number", value: 1 },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("compose expression from multiple variables", () => {
|
||||
const count = new Variable("count", 1);
|
||||
const step = new Variable("step", 2);
|
||||
const { props, dataSources } = renderTemplate(
|
||||
<$.Body
|
||||
ws:id="body"
|
||||
data-count={expression`Count is ${count} + ${step}`}
|
||||
></$.Body>
|
||||
);
|
||||
expect(props).toEqual([
|
||||
{
|
||||
id: "body:data-count",
|
||||
instanceId: "body",
|
||||
name: "data-count",
|
||||
type: "expression",
|
||||
value: "Count is $ws$dataSource$0 + $ws$dataSource$1",
|
||||
},
|
||||
]);
|
||||
expect(dataSources).toEqual([
|
||||
{
|
||||
type: "variable",
|
||||
id: "0",
|
||||
scopeInstanceId: "body",
|
||||
name: "count",
|
||||
value: { type: "number", value: 1 },
|
||||
},
|
||||
{
|
||||
type: "variable",
|
||||
id: "1",
|
||||
scopeInstanceId: "body",
|
||||
name: "step",
|
||||
value: { type: "number", value: 2 },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("preserve same variable on multiple instances", () => {
|
||||
const count = new Variable("count", 1);
|
||||
const { props, dataSources } = renderTemplate(
|
||||
<$.Body ws:id="body" data-count={expression`${count}`}>
|
||||
<$.Box ws:id="box" data-count={expression`${count}`}></$.Box>
|
||||
</$.Body>
|
||||
);
|
||||
expect(props).toEqual([
|
||||
{
|
||||
id: "body:data-count",
|
||||
instanceId: "body",
|
||||
name: "data-count",
|
||||
type: "expression",
|
||||
value: "$ws$dataSource$0",
|
||||
},
|
||||
{
|
||||
id: "box:data-count",
|
||||
instanceId: "box",
|
||||
name: "data-count",
|
||||
type: "expression",
|
||||
value: "$ws$dataSource$0",
|
||||
},
|
||||
]);
|
||||
expect(dataSources).toEqual([
|
||||
{
|
||||
type: "variable",
|
||||
id: "0",
|
||||
scopeInstanceId: "body",
|
||||
name: "count",
|
||||
value: { type: "number", value: 1 },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("render variable inside of action", () => {
|
||||
const count = new Variable("count", 1);
|
||||
const { props, dataSources } = renderTemplate(
|
||||
<$.Body
|
||||
ws:id="body"
|
||||
data-count={expression`${count}`}
|
||||
onInc={new ActionValue(["step"], expression`${count} = ${count} + step`)}
|
||||
></$.Body>
|
||||
);
|
||||
expect(props).toEqual([
|
||||
{
|
||||
id: "body:data-count",
|
||||
instanceId: "body",
|
||||
name: "data-count",
|
||||
type: "expression",
|
||||
value: "$ws$dataSource$0",
|
||||
},
|
||||
{
|
||||
id: "body:onInc",
|
||||
instanceId: "body",
|
||||
name: "onInc",
|
||||
type: "action",
|
||||
value: [
|
||||
{
|
||||
type: "execute",
|
||||
args: ["step"],
|
||||
code: "$ws$dataSource$0 = $ws$dataSource$0 + step",
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
expect(dataSources).toEqual([
|
||||
{
|
||||
type: "variable",
|
||||
id: "0",
|
||||
scopeInstanceId: "body",
|
||||
name: "count",
|
||||
value: { type: "number", value: 1 },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("render ws:show attribute", () => {
|
||||
const { props } = renderTemplate(
|
||||
<$.Body ws:id="body" ws:show={true}></$.Body>
|
||||
);
|
||||
expect(props).toEqual([
|
||||
{
|
||||
id: "body:data-ws-show",
|
||||
instanceId: "body",
|
||||
name: showAttribute,
|
||||
type: "boolean",
|
||||
value: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { Fragment, type JSX, type ReactNode } from "react";
|
||||
import { encodeDataSourceVariable } from "@webstudio-is/sdk";
|
||||
import type {
|
||||
Breakpoint,
|
||||
DataSource,
|
||||
Instance,
|
||||
Instances,
|
||||
Prop,
|
||||
@ -10,8 +12,38 @@ import type {
|
||||
StyleSourceSelection,
|
||||
WebstudioFragment,
|
||||
} from "@webstudio-is/sdk";
|
||||
import { showAttribute } from "@webstudio-is/react-sdk";
|
||||
import type { TemplateStyleDecl } from "./css";
|
||||
|
||||
export class Variable {
|
||||
name: string;
|
||||
initialValue: unknown;
|
||||
constructor(name: string, initialValue: unknown) {
|
||||
this.name = name;
|
||||
this.initialValue = initialValue;
|
||||
}
|
||||
}
|
||||
|
||||
class Expression {
|
||||
chunks: string[];
|
||||
variables: Variable[];
|
||||
constructor(chunks: string[], variables: Variable[]) {
|
||||
this.chunks = chunks;
|
||||
this.variables = variables;
|
||||
}
|
||||
serialize(variableIds: string[]): string {
|
||||
const values = variableIds.map(encodeDataSourceVariable);
|
||||
return String.raw({ raw: this.chunks }, ...values);
|
||||
}
|
||||
}
|
||||
|
||||
export const expression = (
|
||||
chunks: TemplateStringsArray,
|
||||
...variables: Variable[]
|
||||
): Expression => {
|
||||
return new Expression(Array.from(chunks), variables);
|
||||
};
|
||||
|
||||
export class ExpressionValue {
|
||||
value: string;
|
||||
constructor(expression: string) {
|
||||
@ -34,9 +66,15 @@ export class ResourceValue {
|
||||
}
|
||||
|
||||
export class ActionValue {
|
||||
value: { type: "execute"; args: string[]; code: string };
|
||||
constructor(args: string[], code: string) {
|
||||
this.value = { type: "execute", args, code };
|
||||
args: string[];
|
||||
expression: Expression;
|
||||
constructor(args: string[], code: string | Expression) {
|
||||
this.args = args;
|
||||
if (typeof code === "string") {
|
||||
this.expression = new Expression([code], []);
|
||||
} else {
|
||||
this.expression = code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,6 +103,12 @@ export class PlaceholderValue {
|
||||
}
|
||||
}
|
||||
|
||||
const isChildValue = (child: unknown) =>
|
||||
typeof child === "string" ||
|
||||
child instanceof PlaceholderValue ||
|
||||
child instanceof ExpressionValue ||
|
||||
child instanceof Expression;
|
||||
|
||||
const traverseJsx = (
|
||||
element: JSX.Element,
|
||||
callback: (
|
||||
@ -80,13 +124,7 @@ const traverseJsx = (
|
||||
const result: Instance["children"] = [];
|
||||
if (element.type === Fragment) {
|
||||
for (const child of children) {
|
||||
if (typeof child === "string") {
|
||||
continue;
|
||||
}
|
||||
if (child instanceof PlaceholderValue) {
|
||||
continue;
|
||||
}
|
||||
if (child instanceof ExpressionValue) {
|
||||
if (isChildValue(child)) {
|
||||
continue;
|
||||
}
|
||||
result.push(...traverseJsx(child, callback));
|
||||
@ -96,13 +134,7 @@ const traverseJsx = (
|
||||
const child = callback(element, children);
|
||||
result.push(child);
|
||||
for (const child of children) {
|
||||
if (typeof child === "string") {
|
||||
continue;
|
||||
}
|
||||
if (child instanceof PlaceholderValue) {
|
||||
continue;
|
||||
}
|
||||
if (child instanceof ExpressionValue) {
|
||||
if (isChildValue(child)) {
|
||||
continue;
|
||||
}
|
||||
traverseJsx(child, callback);
|
||||
@ -118,6 +150,7 @@ export const renderTemplate = (root: JSX.Element): WebstudioFragment => {
|
||||
const styleSources: StyleSource[] = [];
|
||||
const styleSourceSelections: StyleSourceSelection[] = [];
|
||||
const styles: StyleDecl[] = [];
|
||||
const dataSources = new Map<Variable, DataSource>();
|
||||
const ids = new Map<unknown, string>();
|
||||
const getId = (key: unknown) => {
|
||||
let id = ids.get(key);
|
||||
@ -128,6 +161,30 @@ export const renderTemplate = (root: JSX.Element): WebstudioFragment => {
|
||||
}
|
||||
return id;
|
||||
};
|
||||
const getVariableId = (instanceId: string, variable: Variable) => {
|
||||
const id = getId(variable);
|
||||
if (dataSources.has(variable)) {
|
||||
return id;
|
||||
}
|
||||
let value: Extract<DataSource, { type: "variable" }>["value"];
|
||||
if (typeof variable.initialValue === "string") {
|
||||
value = { type: "string", value: variable.initialValue };
|
||||
} else if (typeof variable.initialValue === "number") {
|
||||
value = { type: "number", value: variable.initialValue };
|
||||
} else if (typeof variable.initialValue === "boolean") {
|
||||
value = { type: "boolean", value: variable.initialValue };
|
||||
} else {
|
||||
value = { type: "json", value: variable.initialValue };
|
||||
}
|
||||
dataSources.set(variable, {
|
||||
type: "variable",
|
||||
scopeInstanceId: instanceId,
|
||||
id,
|
||||
name: variable.name,
|
||||
value,
|
||||
});
|
||||
return id;
|
||||
};
|
||||
// lazily create breakpoint
|
||||
const getBreakpointId = () => {
|
||||
if (breakpoints.length > 0) {
|
||||
@ -142,7 +199,9 @@ export const renderTemplate = (root: JSX.Element): WebstudioFragment => {
|
||||
};
|
||||
const children = traverseJsx(root, (element, children) => {
|
||||
const instanceId = element.props?.["ws:id"] ?? getId(element);
|
||||
for (const [name, value] of Object.entries({ ...element.props })) {
|
||||
for (const entry of Object.entries({ ...element.props })) {
|
||||
const [_name, value] = entry;
|
||||
let [name] = entry;
|
||||
if (name === "ws:id" || name === "ws:label" || name === "children") {
|
||||
continue;
|
||||
}
|
||||
@ -166,8 +225,19 @@ export const renderTemplate = (root: JSX.Element): WebstudioFragment => {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (name === "ws:show") {
|
||||
name = showAttribute;
|
||||
}
|
||||
const propId = `${instanceId}:${name}`;
|
||||
const base = { id: propId, instanceId, name };
|
||||
if (value instanceof Expression) {
|
||||
const values = value.variables.map((variable) =>
|
||||
getVariableId(instanceId, variable)
|
||||
);
|
||||
const expression = value.serialize(values);
|
||||
props.push({ ...base, type: "expression", value: expression });
|
||||
continue;
|
||||
}
|
||||
if (value instanceof ExpressionValue) {
|
||||
props.push({ ...base, type: "expression", value: value.value });
|
||||
continue;
|
||||
@ -181,7 +251,13 @@ export const renderTemplate = (root: JSX.Element): WebstudioFragment => {
|
||||
continue;
|
||||
}
|
||||
if (value instanceof ActionValue) {
|
||||
props.push({ ...base, type: "action", value: [value.value] });
|
||||
const code = value.expression.serialize(
|
||||
value.expression.variables.map((variable) =>
|
||||
getVariableId(instanceId, variable)
|
||||
)
|
||||
);
|
||||
const action = { type: "execute" as const, args: value.args, code };
|
||||
props.push({ ...base, type: "action", value: [action] });
|
||||
continue;
|
||||
}
|
||||
if (value instanceof AssetValue) {
|
||||
@ -214,15 +290,25 @@ export const renderTemplate = (root: JSX.Element): WebstudioFragment => {
|
||||
...(element.props?.["ws:label"]
|
||||
? { label: element.props?.["ws:label"] }
|
||||
: undefined),
|
||||
children: children.map((child): Instance["children"][number] =>
|
||||
typeof child === "string"
|
||||
? { type: "text", value: child }
|
||||
: child instanceof PlaceholderValue
|
||||
? { type: "text", value: child.value, placeholder: true }
|
||||
: child instanceof ExpressionValue
|
||||
? { type: "expression", value: child.value }
|
||||
: { type: "id", value: child.props?.["ws:id"] ?? getId(child) }
|
||||
),
|
||||
children: children.map((child): Instance["children"][number] => {
|
||||
if (typeof child === "string") {
|
||||
return { type: "text", value: child };
|
||||
}
|
||||
if (child instanceof PlaceholderValue) {
|
||||
return { type: "text", value: child.value, placeholder: true };
|
||||
}
|
||||
if (child instanceof Expression) {
|
||||
const values = child.variables.map((variable) =>
|
||||
getVariableId(instanceId, variable)
|
||||
);
|
||||
const expression = child.serialize(values);
|
||||
return { type: "expression", value: expression };
|
||||
}
|
||||
if (child instanceof ExpressionValue) {
|
||||
return { type: "expression", value: child.value };
|
||||
}
|
||||
return { type: "id", value: child.props?.["ws:id"] ?? getId(child) };
|
||||
}),
|
||||
};
|
||||
instances.push(instance);
|
||||
return { type: "id", value: instance.id };
|
||||
@ -235,8 +321,8 @@ export const renderTemplate = (root: JSX.Element): WebstudioFragment => {
|
||||
styleSources,
|
||||
styleSourceSelections,
|
||||
styles,
|
||||
dataSources: Array.from(dataSources.values()),
|
||||
assets: [],
|
||||
dataSources: [],
|
||||
resources: [],
|
||||
};
|
||||
};
|
||||
@ -261,7 +347,8 @@ type ComponentProps = Record<string, unknown> &
|
||||
"ws:id"?: string;
|
||||
"ws:label"?: string;
|
||||
"ws:style"?: TemplateStyleDecl[];
|
||||
children?: ReactNode | ExpressionValue;
|
||||
"ws:show"?: boolean | Expression;
|
||||
children?: ReactNode | ExpressionValue | Expression | PlaceholderValue;
|
||||
};
|
||||
|
||||
type Component = { displayName: string } & ((
|
||||
|
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@ -1985,6 +1985,9 @@ importers:
|
||||
'@webstudio-is/css-engine':
|
||||
specifier: workspace:*
|
||||
version: link:../css-engine
|
||||
'@webstudio-is/react-sdk':
|
||||
specifier: workspace:*
|
||||
version: link:../react-sdk
|
||||
'@webstudio-is/sdk':
|
||||
specifier: workspace:*
|
||||
version: link:../sdk
|
||||
|
Reference in New Issue
Block a user