@@ -1477,6 +1482,11 @@ exports[`Liquid > jsx > Typescript Test > referencingFunInsideHook 1`] = `
"
`;
+exports[`Liquid > jsx > Typescript Test > renderBlock 1`] = `
+"{% if shouldWrap %} {% endif %}
+"
+`;
+
exports[`Liquid > jsx > Typescript Test > renderContentExample 1`] = `
"
diff --git a/packages/core/src/__tests__/__snapshots__/lit.test.ts.snap b/packages/core/src/__tests__/__snapshots__/lit.test.ts.snap
index bcf4737a9c..1cd573b73d 100644
--- a/packages/core/src/__tests__/__snapshots__/lit.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/lit.test.ts.snap
@@ -3335,6 +3335,293 @@ export default class OnUpdate extends LitElement {
"
`;
+exports[`Lit > jsx > Javascript Test > renderBlock 1`] = `
+"import { getBlockActions } from \\"../../functions/get-block-actions.js\\";
+import { getBlockComponentOptions } from \\"../../functions/get-block-component-options.js\\";
+import { getBlockProperties } from \\"../../functions/get-block-properties.js\\";
+import { getBlockTag } from \\"../../functions/get-block-tag.js\\";
+import { getProcessedBlock } from \\"../../functions/get-processed-block.js\\";
+import { evaluate } from \\"../../functions/evaluate.js\\";
+import \\"./block-styles.js\\";
+import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\";
+import \\"./render-repeated-block.js\\";
+import { TARGET } from \\"../../constants/target.js\\";
+import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\";
+import \\"./render-component-with-context.js\\";
+import \\"./render-component.js\\";
+import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\";
+
+import { LitElement, html, css } from \\"lit\\";
+import { customElement, property, state, query } from \\"lit/decorators.js\\";
+
+import type {
+ BuilderContextInterface,
+ RegisteredComponent,
+} from \\"../../context/types.js\\";
+import type { BuilderBlock } from \\"../../types/builder-block.js\\";
+import type { Nullable } from \\"../../types/typescript.js\\";
+import type { RenderComponentProps } from \\"./render-component\\";
+import type { RepeatData } from \\"./types.js\\";
+export type RenderBlockProps = {
+ block: BuilderBlock;
+ context: BuilderContextInterface;
+};
+
+const spread = (properties) =>
+ directive((part) => {
+ for (const property in properties) {
+ const value = properties[attr];
+ part.element[property] = value;
+ }
+ });
+
+@customElement(\\"render-block\\")
+export default class RenderBlock extends LitElement {
+ createRenderRoot() {
+ return this;
+ }
+
+ @property() block: any;
+ @property() context: any;
+
+ get component() {
+ const componentName = getProcessedBlock({
+ block: this.block,
+ this: this.context.state,
+ context: this.context.context,
+ shouldEvaluateBindings: false,
+ }).component?.name;
+
+ if (!componentName) {
+ return null;
+ }
+
+ const ref = this.context.registeredComponents[componentName];
+
+ if (!ref) {
+ // TODO: Public doc page with more info about this message
+ console.warn(\`
+ Could not find a registered component named \\"\${componentName}\\".
+ If you registered it, is the file that registered it imported by the file that needs to render it?\`);
+ return undefined;
+ } else {
+ return ref;
+ }
+ }
+ get tag() {
+ return getBlockTag(this.useBlock);
+ }
+ get useBlock() {
+ return this.repeatItemData
+ ? this.block
+ : getProcessedBlock({
+ block: this.block,
+ this: this.context.state,
+ context: this.context.context,
+ shouldEvaluateBindings: true,
+ });
+ }
+ get actions() {
+ return getBlockActions({
+ block: this.useBlock,
+ this: this.context.state,
+ context: this.context.context,
+ });
+ }
+ get attributes() {
+ const blockProperties = getBlockProperties(this.useBlock);
+ return {
+ ...blockProperties,
+ ...(TARGET === \\"reactNative\\"
+ ? {
+ style: getReactNativeBlockStyles({
+ block: this.useBlock,
+ context: this.context,
+ blockStyles: blockProperties.style,
+ }),
+ }
+ : {}),
+ };
+ }
+ get shouldWrap() {
+ return !this.component?.noWrap;
+ }
+ get renderComponentProps() {
+ return {
+ blockChildren: this.useChildren,
+ componentRef: this.component?.component,
+ componentOptions: {
+ ...getBlockComponentOptions(this.useBlock),
+
+ /**
+ * These attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then
+ * they are provided to the component itself directly.
+ */
+ ...(this.shouldWrap
+ ? {}
+ : {
+ attributes: { ...this.attributes, ...this.actions },
+ }),
+ customBreakpoints: this.childrenContext?.content?.meta?.breakpoints,
+ },
+ context: this.childrenContext,
+ };
+ }
+ get useChildren() {
+ // TO-DO: When should \`canHaveChildren\` dictate rendering?
+ // This is currently commented out because some Builder components (e.g. Box) do not have \`canHaveChildren: true\`,
+ // but still receive and need to render children.
+ // return state.componentInfo?.canHaveChildren ? state.useBlock.children : [];
+ return this.useBlock.children ?? [];
+ }
+ get childrenWithoutParentComponent() {
+ /**
+ * When there is no \`componentRef\`, there might still be children that need to be rendered. In this case,
+ * we render them outside of \`componentRef\`.
+ * NOTE: We make sure not to render this if \`repeatItemData\` is non-null, because that means we are rendering an array of
+ * blocks, and the children will be repeated within those blocks.
+ */
+ const shouldRenderChildrenOutsideRef =
+ !this.component?.component && !this.repeatItemData;
+ return shouldRenderChildrenOutsideRef ? this.useChildren : [];
+ }
+ get repeatItemData() {
+ /**
+ * we don't use \`state.useBlock\` here because the processing done within its logic includes evaluating the block's bindings,
+ * which will not work if there is a repeat.
+ */
+ const { repeat, ...blockWithoutRepeat } = this.block;
+
+ if (!repeat?.collection) {
+ return undefined;
+ }
+
+ const itemsArray = evaluate({
+ code: repeat.collection,
+ this: this.context.state,
+ context: this.context.context,
+ });
+
+ if (!Array.isArray(itemsArray)) {
+ return undefined;
+ }
+
+ const collectionName = repeat.collection.split(\\".\\").pop();
+ const itemNameToUse =
+ repeat.itemName || (collectionName ? collectionName + \\"Item\\" : \\"item\\");
+ const repeatArray = itemsArray.map((item, index) => ({
+ context: {
+ ...this.context,
+ this: {
+ ...this.context.state,
+ $index: index,
+ $item: item,
+ [itemNameToUse]: item,
+ [\`$\${itemNameToUse}Index\`]: index,
+ },
+ },
+ block: blockWithoutRepeat,
+ }));
+ return repeatArray;
+ }
+ get inheritedTextStyles() {
+ if (TARGET !== \\"reactNative\\") {
+ return {};
+ }
+
+ const styles = getReactNativeBlockStyles({
+ block: this.useBlock,
+ context: this.context,
+ blockStyles: this.attributes.style,
+ });
+ return extractTextStyles(styles);
+ }
+ get childrenContext() {
+ return {
+ apiKey: this.context.apiKey,
+ this: this.context.state,
+ content: this.context.content,
+ context: this.context.context,
+ registeredComponents: this.context.registeredComponents,
+ inheritedStyles: this.inheritedTextStyles,
+ };
+ }
+ get renderComponentTag() {
+ if (TARGET === \\"reactNative\\") {
+ return RenderComponentWithContext;
+ } else if (TARGET === \\"vue3\\") {
+ // vue3 expects a string for the component tag
+ return \\"RenderComponent\\";
+ } else {
+ return RenderComponent;
+ }
+ }
+
+ render() {
+ return html\`
+
+ \${
+ this.shouldWrap
+ ? html\`\${
+ isEmptyHtmlElement(this.tag)
+ ? html\`
\`
+ : null
+ }
+ \${
+ !isEmptyHtmlElement(this.tag) && this.repeatItemData
+ ? html\`\${this.repeatItemData?.map(
+ (data, index) =>
+ html\`
\`
+ )}\`
+ : null
+ }
+ \${
+ !isEmptyHtmlElement(this.tag) && !this.repeatItemData
+ ? html\`
+
+
+
+
+
+
+ \${this.childrenWithoutParentComponent?.map(
+ (child, index) =>
+ html\`\`
+ )}
+
+
+ \${this.childrenWithoutParentComponent?.map(
+ (child, index) =>
+ html\`\`
+ )}
+
+ \`
+ : null
+ }\`
+ : html\`
\`
+ }
+ \`;
+ }
+}
+"
+`;
+
exports[`Lit > jsx > Javascript Test > renderContentExample 1`] = `
"import {
sendComponentsToVisualEditor,
@@ -7219,6 +7506,293 @@ export default class OnUpdate extends LitElement {
"
`;
+exports[`Lit > jsx > Typescript Test > renderBlock 1`] = `
+"import { getBlockActions } from \\"../../functions/get-block-actions.js\\";
+import { getBlockComponentOptions } from \\"../../functions/get-block-component-options.js\\";
+import { getBlockProperties } from \\"../../functions/get-block-properties.js\\";
+import { getBlockTag } from \\"../../functions/get-block-tag.js\\";
+import { getProcessedBlock } from \\"../../functions/get-processed-block.js\\";
+import { evaluate } from \\"../../functions/evaluate.js\\";
+import \\"./block-styles.js\\";
+import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\";
+import \\"./render-repeated-block.js\\";
+import { TARGET } from \\"../../constants/target.js\\";
+import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\";
+import \\"./render-component-with-context.js\\";
+import \\"./render-component.js\\";
+import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\";
+
+import { LitElement, html, css } from \\"lit\\";
+import { customElement, property, state, query } from \\"lit/decorators.js\\";
+
+import type {
+ BuilderContextInterface,
+ RegisteredComponent,
+} from \\"../../context/types.js\\";
+import type { BuilderBlock } from \\"../../types/builder-block.js\\";
+import type { Nullable } from \\"../../types/typescript.js\\";
+import type { RenderComponentProps } from \\"./render-component\\";
+import type { RepeatData } from \\"./types.js\\";
+export type RenderBlockProps = {
+ block: BuilderBlock;
+ context: BuilderContextInterface;
+};
+
+const spread = (properties) =>
+ directive((part) => {
+ for (const property in properties) {
+ const value = properties[attr];
+ part.element[property] = value;
+ }
+ });
+
+@customElement(\\"render-block\\")
+export default class RenderBlock extends LitElement {
+ createRenderRoot() {
+ return this;
+ }
+
+ @property() block: any;
+ @property() context: any;
+
+ get component() {
+ const componentName = getProcessedBlock({
+ block: this.block,
+ this: this.context.state,
+ context: this.context.context,
+ shouldEvaluateBindings: false,
+ }).component?.name;
+
+ if (!componentName) {
+ return null;
+ }
+
+ const ref = this.context.registeredComponents[componentName];
+
+ if (!ref) {
+ // TODO: Public doc page with more info about this message
+ console.warn(\`
+ Could not find a registered component named \\"\${componentName}\\".
+ If you registered it, is the file that registered it imported by the file that needs to render it?\`);
+ return undefined;
+ } else {
+ return ref;
+ }
+ }
+ get tag() {
+ return getBlockTag(this.useBlock);
+ }
+ get useBlock() {
+ return this.repeatItemData
+ ? this.block
+ : getProcessedBlock({
+ block: this.block,
+ this: this.context.state,
+ context: this.context.context,
+ shouldEvaluateBindings: true,
+ });
+ }
+ get actions() {
+ return getBlockActions({
+ block: this.useBlock,
+ this: this.context.state,
+ context: this.context.context,
+ });
+ }
+ get attributes() {
+ const blockProperties = getBlockProperties(this.useBlock);
+ return {
+ ...blockProperties,
+ ...(TARGET === \\"reactNative\\"
+ ? {
+ style: getReactNativeBlockStyles({
+ block: this.useBlock,
+ context: this.context,
+ blockStyles: blockProperties.style,
+ }),
+ }
+ : {}),
+ };
+ }
+ get shouldWrap() {
+ return !this.component?.noWrap;
+ }
+ get renderComponentProps() {
+ return {
+ blockChildren: this.useChildren,
+ componentRef: this.component?.component,
+ componentOptions: {
+ ...getBlockComponentOptions(this.useBlock),
+
+ /**
+ * These attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then
+ * they are provided to the component itself directly.
+ */
+ ...(this.shouldWrap
+ ? {}
+ : {
+ attributes: { ...this.attributes, ...this.actions },
+ }),
+ customBreakpoints: this.childrenContext?.content?.meta?.breakpoints,
+ },
+ context: this.childrenContext,
+ };
+ }
+ get useChildren() {
+ // TO-DO: When should \`canHaveChildren\` dictate rendering?
+ // This is currently commented out because some Builder components (e.g. Box) do not have \`canHaveChildren: true\`,
+ // but still receive and need to render children.
+ // return state.componentInfo?.canHaveChildren ? state.useBlock.children : [];
+ return this.useBlock.children ?? [];
+ }
+ get childrenWithoutParentComponent() {
+ /**
+ * When there is no \`componentRef\`, there might still be children that need to be rendered. In this case,
+ * we render them outside of \`componentRef\`.
+ * NOTE: We make sure not to render this if \`repeatItemData\` is non-null, because that means we are rendering an array of
+ * blocks, and the children will be repeated within those blocks.
+ */
+ const shouldRenderChildrenOutsideRef =
+ !this.component?.component && !this.repeatItemData;
+ return shouldRenderChildrenOutsideRef ? this.useChildren : [];
+ }
+ get repeatItemData() {
+ /**
+ * we don't use \`state.useBlock\` here because the processing done within its logic includes evaluating the block's bindings,
+ * which will not work if there is a repeat.
+ */
+ const { repeat, ...blockWithoutRepeat } = this.block;
+
+ if (!repeat?.collection) {
+ return undefined;
+ }
+
+ const itemsArray = evaluate({
+ code: repeat.collection,
+ this: this.context.state,
+ context: this.context.context,
+ });
+
+ if (!Array.isArray(itemsArray)) {
+ return undefined;
+ }
+
+ const collectionName = repeat.collection.split(\\".\\").pop();
+ const itemNameToUse =
+ repeat.itemName || (collectionName ? collectionName + \\"Item\\" : \\"item\\");
+ const repeatArray = itemsArray.map((item, index) => ({
+ context: {
+ ...this.context,
+ this: {
+ ...this.context.state,
+ $index: index,
+ $item: item,
+ [itemNameToUse]: item,
+ [\`$\${itemNameToUse}Index\`]: index,
+ },
+ },
+ block: blockWithoutRepeat,
+ }));
+ return repeatArray;
+ }
+ get inheritedTextStyles() {
+ if (TARGET !== \\"reactNative\\") {
+ return {};
+ }
+
+ const styles = getReactNativeBlockStyles({
+ block: this.useBlock,
+ context: this.context,
+ blockStyles: this.attributes.style,
+ });
+ return extractTextStyles(styles);
+ }
+ get childrenContext() {
+ return {
+ apiKey: this.context.apiKey,
+ this: this.context.state,
+ content: this.context.content,
+ context: this.context.context,
+ registeredComponents: this.context.registeredComponents,
+ inheritedStyles: this.inheritedTextStyles,
+ };
+ }
+ get renderComponentTag() {
+ if (TARGET === \\"reactNative\\") {
+ return RenderComponentWithContext;
+ } else if (TARGET === \\"vue3\\") {
+ // vue3 expects a string for the component tag
+ return \\"RenderComponent\\";
+ } else {
+ return RenderComponent;
+ }
+ }
+
+ render() {
+ return html\`
+
+ \${
+ this.shouldWrap
+ ? html\`\${
+ isEmptyHtmlElement(this.tag)
+ ? html\`
\`
+ : null
+ }
+ \${
+ !isEmptyHtmlElement(this.tag) && this.repeatItemData
+ ? html\`\${this.repeatItemData?.map(
+ (data, index) =>
+ html\`
\`
+ )}\`
+ : null
+ }
+ \${
+ !isEmptyHtmlElement(this.tag) && !this.repeatItemData
+ ? html\`
+
+
+
+
+
+
+ \${this.childrenWithoutParentComponent?.map(
+ (child, index) =>
+ html\`\`
+ )}
+
+
+ \${this.childrenWithoutParentComponent?.map(
+ (child, index) =>
+ html\`\`
+ )}
+
+ \`
+ : null
+ }\`
+ : html\`
\`
+ }
+ \`;
+ }
+}
+"
+`;
+
exports[`Lit > jsx > Typescript Test > renderContentExample 1`] = `
"import {
sendComponentsToVisualEditor,
diff --git a/packages/core/src/__tests__/__snapshots__/marko.test.ts.snap b/packages/core/src/__tests__/__snapshots__/marko.test.ts.snap
index 66a9fea852..8852f5e76b 100644
--- a/packages/core/src/__tests__/__snapshots__/marko.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/marko.test.ts.snap
@@ -1799,6 +1799,240 @@ exports[`Marko > jsx > Javascript Test > referencingFunInsideHook 1`] = `
"
`;
+exports[`Marko > jsx > Javascript Test > renderBlock 1`] = `
+"import { getBlockActions } from \\"../../functions/get-block-actions.js\\";
+import { getBlockComponentOptions } from \\"../../functions/get-block-component-options.js\\";
+import { getBlockProperties } from \\"../../functions/get-block-properties.js\\";
+import { getBlockTag } from \\"../../functions/get-block-tag.js\\";
+import { getProcessedBlock } from \\"../../functions/get-processed-block.js\\";
+import { evaluate } from \\"../../functions/evaluate.js\\";
+import BlockStyles from \\"./block-styles.marko\\";
+import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\";
+import RenderRepeatedBlock from \\"./render-repeated-block.marko\\";
+import { TARGET } from \\"../../constants/target.js\\";
+import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\";
+import RenderComponentWithContext from \\"./render-component-with-context.marko\\";
+import RenderComponent from \\"./render-component.marko\\";
+import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\";
+
+class {
+ get component() {
+ const componentName = getProcessedBlock({
+ block: this.input.block,
+ state: this.input.context.state,
+ context: this.input.context.context,
+ shouldEvaluateBindings: false,
+ }).component?.name;
+
+ if (!componentName) {
+ return null;
+ }
+
+ const ref = this.input.context.registeredComponents[componentName];
+
+ if (!ref) {
+ // TODO: Public doc page with more info about this message
+ console.warn(\`
+ Could not find a registered component named \\"\${componentName}\\".
+ If you registered it, is the file that registered it imported by the file that needs to render it?\`);
+ return undefined;
+ } else {
+ return ref;
+ }
+ }
+ get tag() {
+ return getBlockTag(this.useBlock);
+ }
+ get useBlock() {
+ return this.repeatItemData
+ ? this.input.block
+ : getProcessedBlock({
+ block: this.input.block,
+ state: this.input.context.state,
+ context: this.input.context.context,
+ shouldEvaluateBindings: true,
+ });
+ }
+ get actions() {
+ return getBlockActions({
+ block: this.useBlock,
+ state: this.input.context.state,
+ context: this.input.context.context,
+ });
+ }
+ get attributes() {
+ const blockProperties = getBlockProperties(this.useBlock);
+ return {
+ ...blockProperties,
+ ...(TARGET === \\"reactNative\\"
+ ? {
+ style: getReactNativeBlockStyles({
+ block: this.useBlock,
+ context: this.input.context,
+ blockStyles: blockProperties.style,
+ }),
+ }
+ : {}),
+ };
+ }
+ get shouldWrap() {
+ return !this.component?.noWrap;
+ }
+ get renderComponentProps() {
+ return {
+ blockChildren: this.useChildren,
+ componentRef: this.component?.component,
+ componentOptions: {
+ ...getBlockComponentOptions(this.useBlock),
+
+ /**
+ * These attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then
+ * they are provided to the component itself directly.
+ */
+ ...(this.shouldWrap
+ ? {}
+ : {
+ attributes: { ...this.attributes, ...this.actions },
+ }),
+ customBreakpoints: this.childrenContext?.content?.meta?.breakpoints,
+ },
+ context: this.childrenContext,
+ };
+ }
+ get useChildren() {
+ // TO-DO: When should \`canHaveChildren\` dictate rendering?
+ // This is currently commented out because some Builder components (e.g. Box) do not have \`canHaveChildren: true\`,
+ // but still receive and need to render children.
+ // return state.componentInfo?.canHaveChildren ? state.useBlock.children : [];
+ return this.useBlock.children ?? [];
+ }
+ get childrenWithoutParentComponent() {
+ /**
+ * When there is no \`componentRef\`, there might still be children that need to be rendered. In this case,
+ * we render them outside of \`componentRef\`.
+ * NOTE: We make sure not to render this if \`repeatItemData\` is non-null, because that means we are rendering an array of
+ * blocks, and the children will be repeated within those blocks.
+ */
+ const shouldRenderChildrenOutsideRef =
+ !this.component?.component && !this.repeatItemData;
+ return shouldRenderChildrenOutsideRef ? this.useChildren : [];
+ }
+ get repeatItemData() {
+ /**
+ * we don't use \`state.useBlock\` here because the processing done within its logic includes evaluating the block's bindings,
+ * which will not work if there is a repeat.
+ */
+ const { repeat, ...blockWithoutRepeat } = this.input.block;
+
+ if (!repeat?.collection) {
+ return undefined;
+ }
+
+ const itemsArray = evaluate({
+ code: repeat.collection,
+ state: this.input.context.state,
+ context: this.input.context.context,
+ });
+
+ if (!Array.isArray(itemsArray)) {
+ return undefined;
+ }
+
+ const collectionName = repeat.collection.split(\\".\\").pop();
+ const itemNameToUse =
+ repeat.itemName || (collectionName ? collectionName + \\"Item\\" : \\"item\\");
+ const repeatArray = itemsArray.map((item, index) => ({
+ context: {
+ ...this.input.context,
+ state: {
+ ...this.input.context.state,
+ $index: index,
+ $item: item,
+ [itemNameToUse]: item,
+ [\`$\${itemNameToUse}Index\`]: index,
+ },
+ },
+ block: blockWithoutRepeat,
+ }));
+ return repeatArray;
+ }
+ get inheritedTextStyles() {
+ if (TARGET !== \\"reactNative\\") {
+ return {};
+ }
+
+ const styles = getReactNativeBlockStyles({
+ block: this.useBlock,
+ context: this.input.context,
+ blockStyles: this.attributes.style,
+ });
+ return extractTextStyles(styles);
+ }
+ get childrenContext() {
+ return {
+ apiKey: this.input.context.apiKey,
+ state: this.input.context.state,
+ content: this.input.context.content,
+ context: this.input.context.context,
+ registeredComponents: this.input.context.registeredComponents,
+ inheritedStyles: this.inheritedTextStyles,
+ };
+ }
+ get renderComponentTag() {
+ if (TARGET === \\"reactNative\\") {
+ return RenderComponentWithContext;
+ } else if (TARGET === \\"vue3\\") {
+ // vue3 expects a string for the component tag
+ return \\"RenderComponent\\";
+ } else {
+ return RenderComponent;
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
"
+`;
+
exports[`Marko > jsx > Javascript Test > renderContentExample 1`] = `
"import {
sendComponentsToVisualEditor,
@@ -3744,6 +3978,240 @@ exports[`Marko > jsx > Typescript Test > referencingFunInsideHook 1`] = `
"
`;
+exports[`Marko > jsx > Typescript Test > renderBlock 1`] = `
+"import { getBlockActions } from \\"../../functions/get-block-actions.js\\";
+import { getBlockComponentOptions } from \\"../../functions/get-block-component-options.js\\";
+import { getBlockProperties } from \\"../../functions/get-block-properties.js\\";
+import { getBlockTag } from \\"../../functions/get-block-tag.js\\";
+import { getProcessedBlock } from \\"../../functions/get-processed-block.js\\";
+import { evaluate } from \\"../../functions/evaluate.js\\";
+import BlockStyles from \\"./block-styles.marko\\";
+import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\";
+import RenderRepeatedBlock from \\"./render-repeated-block.marko\\";
+import { TARGET } from \\"../../constants/target.js\\";
+import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\";
+import RenderComponentWithContext from \\"./render-component-with-context.marko\\";
+import RenderComponent from \\"./render-component.marko\\";
+import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\";
+
+class {
+ get component() {
+ const componentName = getProcessedBlock({
+ block: this.input.block,
+ state: this.input.context.state,
+ context: this.input.context.context,
+ shouldEvaluateBindings: false,
+ }).component?.name;
+
+ if (!componentName) {
+ return null;
+ }
+
+ const ref = this.input.context.registeredComponents[componentName];
+
+ if (!ref) {
+ // TODO: Public doc page with more info about this message
+ console.warn(\`
+ Could not find a registered component named \\"\${componentName}\\".
+ If you registered it, is the file that registered it imported by the file that needs to render it?\`);
+ return undefined;
+ } else {
+ return ref;
+ }
+ }
+ get tag() {
+ return getBlockTag(this.useBlock);
+ }
+ get useBlock() {
+ return this.repeatItemData
+ ? this.input.block
+ : getProcessedBlock({
+ block: this.input.block,
+ state: this.input.context.state,
+ context: this.input.context.context,
+ shouldEvaluateBindings: true,
+ });
+ }
+ get actions() {
+ return getBlockActions({
+ block: this.useBlock,
+ state: this.input.context.state,
+ context: this.input.context.context,
+ });
+ }
+ get attributes() {
+ const blockProperties = getBlockProperties(this.useBlock);
+ return {
+ ...blockProperties,
+ ...(TARGET === \\"reactNative\\"
+ ? {
+ style: getReactNativeBlockStyles({
+ block: this.useBlock,
+ context: this.input.context,
+ blockStyles: blockProperties.style,
+ }),
+ }
+ : {}),
+ };
+ }
+ get shouldWrap() {
+ return !this.component?.noWrap;
+ }
+ get renderComponentProps() {
+ return {
+ blockChildren: this.useChildren,
+ componentRef: this.component?.component,
+ componentOptions: {
+ ...getBlockComponentOptions(this.useBlock),
+
+ /**
+ * These attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then
+ * they are provided to the component itself directly.
+ */
+ ...(this.shouldWrap
+ ? {}
+ : {
+ attributes: { ...this.attributes, ...this.actions },
+ }),
+ customBreakpoints: this.childrenContext?.content?.meta?.breakpoints,
+ },
+ context: this.childrenContext,
+ };
+ }
+ get useChildren() {
+ // TO-DO: When should \`canHaveChildren\` dictate rendering?
+ // This is currently commented out because some Builder components (e.g. Box) do not have \`canHaveChildren: true\`,
+ // but still receive and need to render children.
+ // return state.componentInfo?.canHaveChildren ? state.useBlock.children : [];
+ return this.useBlock.children ?? [];
+ }
+ get childrenWithoutParentComponent() {
+ /**
+ * When there is no \`componentRef\`, there might still be children that need to be rendered. In this case,
+ * we render them outside of \`componentRef\`.
+ * NOTE: We make sure not to render this if \`repeatItemData\` is non-null, because that means we are rendering an array of
+ * blocks, and the children will be repeated within those blocks.
+ */
+ const shouldRenderChildrenOutsideRef =
+ !this.component?.component && !this.repeatItemData;
+ return shouldRenderChildrenOutsideRef ? this.useChildren : [];
+ }
+ get repeatItemData() {
+ /**
+ * we don't use \`state.useBlock\` here because the processing done within its logic includes evaluating the block's bindings,
+ * which will not work if there is a repeat.
+ */
+ const { repeat, ...blockWithoutRepeat } = this.input.block;
+
+ if (!repeat?.collection) {
+ return undefined;
+ }
+
+ const itemsArray = evaluate({
+ code: repeat.collection,
+ state: this.input.context.state,
+ context: this.input.context.context,
+ });
+
+ if (!Array.isArray(itemsArray)) {
+ return undefined;
+ }
+
+ const collectionName = repeat.collection.split(\\".\\").pop();
+ const itemNameToUse =
+ repeat.itemName || (collectionName ? collectionName + \\"Item\\" : \\"item\\");
+ const repeatArray = itemsArray.map((item, index) => ({
+ context: {
+ ...this.input.context,
+ state: {
+ ...this.input.context.state,
+ $index: index,
+ $item: item,
+ [itemNameToUse]: item,
+ [\`$\${itemNameToUse}Index\`]: index,
+ },
+ },
+ block: blockWithoutRepeat,
+ }));
+ return repeatArray;
+ }
+ get inheritedTextStyles() {
+ if (TARGET !== \\"reactNative\\") {
+ return {};
+ }
+
+ const styles = getReactNativeBlockStyles({
+ block: this.useBlock,
+ context: this.input.context,
+ blockStyles: this.attributes.style,
+ });
+ return extractTextStyles(styles);
+ }
+ get childrenContext() {
+ return {
+ apiKey: this.input.context.apiKey,
+ state: this.input.context.state,
+ content: this.input.context.content,
+ context: this.input.context.context,
+ registeredComponents: this.input.context.registeredComponents,
+ inheritedStyles: this.inheritedTextStyles,
+ };
+ }
+ get renderComponentTag() {
+ if (TARGET === \\"reactNative\\") {
+ return RenderComponentWithContext;
+ } else if (TARGET === \\"vue3\\") {
+ // vue3 expects a string for the component tag
+ return \\"RenderComponent\\";
+ } else {
+ return RenderComponent;
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
"
+`;
+
exports[`Marko > jsx > Typescript Test > renderContentExample 1`] = `
"import {
sendComponentsToVisualEditor,
diff --git a/packages/core/src/__tests__/__snapshots__/parse-jsx.test.ts.snap b/packages/core/src/__tests__/__snapshots__/parse-jsx.test.ts.snap
index 3b59e89fa9..ae5763419b 100644
--- a/packages/core/src/__tests__/__snapshots__/parse-jsx.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/parse-jsx.test.ts.snap
@@ -9717,6 +9717,749 @@ exports[`Parse JSX > referencingFunInsideHook 1`] = `
}
`;
+exports[`Parse JSX > renderBlock 1`] = `
+{
+ "@type": "@builder.io/mitosis/component",
+ "children": [
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {
+ "when": {
+ "code": "state.shouldWrap",
+ },
+ },
+ "children": [
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {},
+ "children": [],
+ "meta": {},
+ "name": "div",
+ "properties": {
+ "_text": "
+ ",
+ },
+ "scope": {},
+ },
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {},
+ "children": [],
+ "meta": {},
+ "name": "div",
+ "properties": {
+ "_text": "
+ ",
+ },
+ "scope": {},
+ },
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {
+ "when": {
+ "code": "isEmptyHtmlElement(state.tag)",
+ },
+ },
+ "children": [
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {},
+ "children": [],
+ "meta": {},
+ "name": "div",
+ "properties": {
+ "_text": "
+ ",
+ },
+ "scope": {},
+ },
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {
+ "state.actions": {
+ "code": "state.actions",
+ "type": "spread",
+ },
+ "state.attributes": {
+ "code": "state.attributes",
+ "type": "spread",
+ },
+ },
+ "children": [],
+ "meta": {},
+ "name": "state.tag",
+ "properties": {},
+ "scope": {},
+ },
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {},
+ "children": [],
+ "meta": {},
+ "name": "div",
+ "properties": {
+ "_text": "
+ ",
+ },
+ "scope": {},
+ },
+ ],
+ "meta": {},
+ "name": "Show",
+ "properties": {},
+ "scope": {},
+ },
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {},
+ "children": [],
+ "meta": {},
+ "name": "div",
+ "properties": {
+ "_text": "
+ ",
+ },
+ "scope": {},
+ },
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {
+ "when": {
+ "code": "!isEmptyHtmlElement(state.tag) && state.repeatItemData",
+ },
+ },
+ "children": [
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {},
+ "children": [],
+ "meta": {},
+ "name": "div",
+ "properties": {
+ "_text": "
+ ",
+ },
+ "scope": {},
+ },
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {
+ "each": {
+ "code": "state.repeatItemData",
+ },
+ },
+ "children": [
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {
+ "block": {
+ "code": "data.block",
+ },
+ "key": {
+ "code": "index",
+ },
+ "repeatContext": {
+ "code": "data.context",
+ },
+ },
+ "children": [],
+ "meta": {},
+ "name": "RenderRepeatedBlock",
+ "properties": {},
+ "scope": {},
+ },
+ ],
+ "meta": {},
+ "name": "For",
+ "properties": {},
+ "scope": {
+ "forName": "data",
+ "indexName": "index",
+ },
+ },
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {},
+ "children": [],
+ "meta": {},
+ "name": "div",
+ "properties": {
+ "_text": "
+ ",
+ },
+ "scope": {},
+ },
+ ],
+ "meta": {},
+ "name": "Show",
+ "properties": {},
+ "scope": {},
+ },
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {},
+ "children": [],
+ "meta": {},
+ "name": "div",
+ "properties": {
+ "_text": "
+ ",
+ },
+ "scope": {},
+ },
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {
+ "when": {
+ "code": "!isEmptyHtmlElement(state.tag) && !state.repeatItemData",
+ },
+ },
+ "children": [
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {},
+ "children": [],
+ "meta": {},
+ "name": "div",
+ "properties": {
+ "_text": "
+ ",
+ },
+ "scope": {},
+ },
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {
+ "state.actions": {
+ "code": "state.actions",
+ "type": "spread",
+ },
+ "state.attributes": {
+ "code": "state.attributes",
+ "type": "spread",
+ },
+ },
+ "children": [
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {},
+ "children": [],
+ "meta": {},
+ "name": "div",
+ "properties": {
+ "_text": "
+ ",
+ },
+ "scope": {},
+ },
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {
+ "state.renderComponentProps": {
+ "code": "state.renderComponentProps",
+ "type": "spread",
+ },
+ },
+ "children": [],
+ "meta": {},
+ "name": "state.renderComponentTag",
+ "properties": {},
+ "scope": {},
+ },
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {},
+ "children": [],
+ "meta": {},
+ "name": "div",
+ "properties": {
+ "_text": "
+ ",
+ },
+ "scope": {},
+ },
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {},
+ "children": [],
+ "meta": {},
+ "name": "div",
+ "properties": {
+ "_text": "
+ ",
+ },
+ "scope": {},
+ },
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {
+ "each": {
+ "code": "state.childrenWithoutParentComponent",
+ },
+ },
+ "children": [
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {
+ "block": {
+ "code": "child",
+ },
+ "context": {
+ "code": "state.childrenContext",
+ },
+ "key": {
+ "code": "'render-block-' + child.id",
+ },
+ },
+ "children": [],
+ "meta": {},
+ "name": "RenderBlock",
+ "properties": {},
+ "scope": {},
+ },
+ ],
+ "meta": {},
+ "name": "For",
+ "properties": {},
+ "scope": {
+ "forName": "child",
+ },
+ },
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {},
+ "children": [],
+ "meta": {},
+ "name": "div",
+ "properties": {
+ "_text": "
+ ",
+ },
+ "scope": {},
+ },
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {
+ "each": {
+ "code": "state.childrenWithoutParentComponent",
+ },
+ },
+ "children": [
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {
+ "block": {
+ "code": "child",
+ },
+ "context": {
+ "code": "state.childrenContext",
+ },
+ "key": {
+ "code": "'block-style-' + child.id",
+ },
+ },
+ "children": [],
+ "meta": {},
+ "name": "BlockStyles",
+ "properties": {},
+ "scope": {},
+ },
+ ],
+ "meta": {},
+ "name": "For",
+ "properties": {},
+ "scope": {
+ "forName": "child",
+ },
+ },
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {},
+ "children": [],
+ "meta": {},
+ "name": "div",
+ "properties": {
+ "_text": "
+ ",
+ },
+ "scope": {},
+ },
+ ],
+ "meta": {},
+ "name": "state.tag",
+ "properties": {},
+ "scope": {},
+ },
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {},
+ "children": [],
+ "meta": {},
+ "name": "div",
+ "properties": {
+ "_text": "
+ ",
+ },
+ "scope": {},
+ },
+ ],
+ "meta": {},
+ "name": "Show",
+ "properties": {},
+ "scope": {},
+ },
+ {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {},
+ "children": [],
+ "meta": {},
+ "name": "div",
+ "properties": {
+ "_text": "
+ ",
+ },
+ "scope": {},
+ },
+ ],
+ "meta": {
+ "else": {
+ "@type": "@builder.io/mitosis/node",
+ "bindings": {
+ "state.renderComponentProps": {
+ "code": "state.renderComponentProps",
+ "type": "spread",
+ },
+ },
+ "children": [],
+ "meta": {},
+ "name": "state.renderComponentTag",
+ "properties": {},
+ "scope": {},
+ },
+ },
+ "name": "Show",
+ "properties": {},
+ "scope": {},
+ },
+ ],
+ "context": {
+ "get": {},
+ "set": {},
+ },
+ "exports": {},
+ "hooks": {},
+ "imports": [
+ {
+ "imports": {
+ "getBlockActions": "getBlockActions",
+ },
+ "path": "../../functions/get-block-actions.js",
+ },
+ {
+ "imports": {
+ "getBlockComponentOptions": "getBlockComponentOptions",
+ },
+ "path": "../../functions/get-block-component-options.js",
+ },
+ {
+ "imports": {
+ "getBlockProperties": "getBlockProperties",
+ },
+ "path": "../../functions/get-block-properties.js",
+ },
+ {
+ "imports": {
+ "getBlockTag": "getBlockTag",
+ },
+ "path": "../../functions/get-block-tag.js",
+ },
+ {
+ "imports": {
+ "getProcessedBlock": "getProcessedBlock",
+ },
+ "path": "../../functions/get-processed-block.js",
+ },
+ {
+ "imports": {
+ "evaluate": "evaluate",
+ },
+ "path": "../../functions/evaluate.js",
+ },
+ {
+ "imports": {
+ "BlockStyles": "default",
+ },
+ "path": "./block-styles.lite",
+ },
+ {
+ "imports": {
+ "isEmptyHtmlElement": "isEmptyHtmlElement",
+ },
+ "path": "./render-block.helpers.js",
+ },
+ {
+ "imports": {
+ "RenderRepeatedBlock": "default",
+ },
+ "path": "./render-repeated-block.lite",
+ },
+ {
+ "imports": {
+ "TARGET": "TARGET",
+ },
+ "path": "../../constants/target.js",
+ },
+ {
+ "imports": {
+ "extractTextStyles": "extractTextStyles",
+ },
+ "path": "../../functions/extract-text-styles.js",
+ },
+ {
+ "imports": {
+ "RenderComponentWithContext": "default",
+ },
+ "path": "./render-component-with-context.lite",
+ },
+ {
+ "imports": {
+ "RenderComponent": "default",
+ },
+ "path": "./render-component.lite",
+ },
+ {
+ "imports": {
+ "getReactNativeBlockStyles": "getReactNativeBlockStyles",
+ },
+ "path": "../../functions/get-react-native-block-styles.js",
+ },
+ ],
+ "inputs": [],
+ "meta": {},
+ "name": "RenderBlock",
+ "propsTypeRef": "RenderBlockProps",
+ "refs": {},
+ "state": {
+ "actions": {
+ "code": "get actions() {
+ return getBlockActions({
+ block: state.useBlock,
+ state: props.context.state,
+ context: props.context.context
+ });
+}",
+ "type": "getter",
+ },
+ "attributes": {
+ "code": "get attributes() {
+ const blockProperties = getBlockProperties(state.useBlock);
+ return { ...blockProperties,
+ ...(TARGET === 'reactNative' ? {
+ style: getReactNativeBlockStyles({
+ block: state.useBlock,
+ context: props.context,
+ blockStyles: blockProperties.style
+ })
+ } : {})
+ };
+}",
+ "type": "getter",
+ },
+ "childrenContext": {
+ "code": "get childrenContext() {
+ return {
+ apiKey: props.context.apiKey,
+ state: props.context.state,
+ content: props.context.content,
+ context: props.context.context,
+ registeredComponents: props.context.registeredComponents,
+ inheritedStyles: state.inheritedTextStyles
+ };
+}",
+ "type": "getter",
+ },
+ "childrenWithoutParentComponent": {
+ "code": "get childrenWithoutParentComponent() {
+ /**
+ * When there is no \`componentRef\`, there might still be children that need to be rendered. In this case,
+ * we render them outside of \`componentRef\`.
+ * NOTE: We make sure not to render this if \`repeatItemData\` is non-null, because that means we are rendering an array of
+ * blocks, and the children will be repeated within those blocks.
+ */
+ const shouldRenderChildrenOutsideRef = !state.component?.component && !state.repeatItemData;
+ return shouldRenderChildrenOutsideRef ? state.useChildren : [];
+}",
+ "type": "getter",
+ },
+ "component": {
+ "code": "get component() {
+ const componentName = getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: false
+ }).component?.name;
+
+ if (!componentName) {
+ return null;
+ }
+
+ const ref = props.context.registeredComponents[componentName];
+
+ if (!ref) {
+ // TODO: Public doc page with more info about this message
+ console.warn(\`
+ Could not find a registered component named \\"\${componentName}\\".
+ If you registered it, is the file that registered it imported by the file that needs to render it?\`);
+ return undefined;
+ } else {
+ return ref;
+ }
+}",
+ "type": "getter",
+ },
+ "inheritedTextStyles": {
+ "code": "get inheritedTextStyles() {
+ if (TARGET !== 'reactNative') {
+ return {};
+ }
+
+ const styles = getReactNativeBlockStyles({
+ block: state.useBlock,
+ context: props.context,
+ blockStyles: state.attributes.style
+ });
+ return extractTextStyles(styles);
+}",
+ "type": "getter",
+ },
+ "renderComponentProps": {
+ "code": "get renderComponentProps() {
+ return {
+ blockChildren: state.useChildren,
+ componentRef: state.component?.component,
+ componentOptions: { ...getBlockComponentOptions(state.useBlock),
+
+ /**
+ * These attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then
+ * they are provided to the component itself directly.
+ */
+ ...(state.shouldWrap ? {} : {
+ attributes: { ...state.attributes,
+ ...state.actions
+ }
+ }),
+ customBreakpoints: state.childrenContext?.content?.meta?.breakpoints
+ },
+ context: state.childrenContext
+ };
+}",
+ "type": "getter",
+ },
+ "renderComponentTag": {
+ "code": "get renderComponentTag() {
+ if (TARGET === 'reactNative') {
+ return RenderComponentWithContext;
+ } else if (TARGET === 'vue3') {
+ // vue3 expects a string for the component tag
+ return 'RenderComponent';
+ } else {
+ return RenderComponent;
+ }
+}",
+ "type": "getter",
+ },
+ "repeatItemData": {
+ "code": "get repeatItemData() {
+ /**
+ * we don't use \`state.useBlock\` here because the processing done within its logic includes evaluating the block's bindings,
+ * which will not work if there is a repeat.
+ */
+ const {
+ repeat,
+ ...blockWithoutRepeat
+ } = props.block;
+
+ if (!repeat?.collection) {
+ return undefined;
+ }
+
+ const itemsArray = evaluate({
+ code: repeat.collection,
+ state: props.context.state,
+ context: props.context.context
+ });
+
+ if (!Array.isArray(itemsArray)) {
+ return undefined;
+ }
+
+ const collectionName = repeat.collection.split('.').pop();
+ const itemNameToUse = repeat.itemName || (collectionName ? collectionName + 'Item' : 'item');
+ const repeatArray = itemsArray.map
((item, index) => ({
+ context: { ...props.context,
+ state: { ...props.context.state,
+ $index: index,
+ $item: item,
+ [itemNameToUse]: item,
+ [\`$\${itemNameToUse}Index\`]: index
+ }
+ },
+ block: blockWithoutRepeat
+ }));
+ return repeatArray;
+}",
+ "type": "getter",
+ },
+ "shouldWrap": {
+ "code": "get shouldWrap() {
+ return !state.component?.noWrap;
+}",
+ "type": "getter",
+ },
+ "tag": {
+ "code": "get tag() {
+ return getBlockTag(state.useBlock);
+}",
+ "type": "getter",
+ },
+ "useBlock": {
+ "code": "get useBlock() {
+ return state.repeatItemData ? props.block : getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: true
+ });
+}",
+ "type": "getter",
+ },
+ "useChildren": {
+ "code": "get useChildren() {
+ // TO-DO: When should \`canHaveChildren\` dictate rendering?
+ // This is currently commented out because some Builder components (e.g. Box) do not have \`canHaveChildren: true\`,
+ // but still receive and need to render children.
+ // return state.componentInfo?.canHaveChildren ? state.useBlock.children : [];
+ return state.useBlock.children ?? [];
+}",
+ "type": "getter",
+ },
+ },
+ "subComponents": [],
+ "types": [
+ "import type { BuilderContextInterface, RegisteredComponent } from '../../context/types.js';",
+ "import type { BuilderBlock } from '../../types/builder-block.js';",
+ "import type { Nullable } from '../../types/typescript.js';",
+ "import type { RenderComponentProps } from \\"./render-component\\";",
+ "import type { RepeatData } from './types.js';",
+ "export type RenderBlockProps = {
+ block: BuilderBlock;
+ context: BuilderContextInterface;
+};",
+ ],
+}
+`;
+
exports[`Parse JSX > renderContentExample 1`] = `
{
"@type": "@builder.io/mitosis/component",
diff --git a/packages/core/src/__tests__/__snapshots__/preact.test.ts.snap b/packages/core/src/__tests__/__snapshots__/preact.test.ts.snap
index 2e27ce11e4..9b09d5cac5 100644
--- a/packages/core/src/__tests__/__snapshots__/preact.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/preact.test.ts.snap
@@ -2237,6 +2237,269 @@ export default function OnUpdate(props) {
"
`;
+exports[`Preact > jsx > Javascript Test > renderBlock 1`] = `
+"/** @jsx h */
+import { h, Fragment } from \\"preact\\";
+import { useState } from \\"preact/hooks\\";
+import { getBlockActions } from \\"../../functions/get-block-actions.js\\";
+import { getBlockComponentOptions } from \\"../../functions/get-block-component-options.js\\";
+import { getBlockProperties } from \\"../../functions/get-block-properties.js\\";
+import { getBlockTag } from \\"../../functions/get-block-tag.js\\";
+import { getProcessedBlock } from \\"../../functions/get-processed-block.js\\";
+import { evaluate } from \\"../../functions/evaluate.js\\";
+import BlockStyles from \\"./block-styles.lite\\";
+import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\";
+import RenderRepeatedBlock from \\"./render-repeated-block.lite\\";
+import { TARGET } from \\"../../constants/target.js\\";
+import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\";
+import RenderComponentWithContext from \\"./render-component-with-context.lite\\";
+import RenderComponent from \\"./render-component.lite\\";
+import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\";
+
+export default function RenderBlock(props) {
+ function component() {
+ const componentName = getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: false,
+ }).component?.name;
+
+ if (!componentName) {
+ return null;
+ }
+
+ const ref = props.context.registeredComponents[componentName];
+
+ if (!ref) {
+ // TODO: Public doc page with more info about this message
+ console.warn(\`
+ Could not find a registered component named \\"\${componentName}\\".
+ If you registered it, is the file that registered it imported by the file that needs to render it?\`);
+ return undefined;
+ } else {
+ return ref;
+ }
+ }
+
+ function tag() {
+ return getBlockTag(useBlock());
+ }
+
+ function useBlock() {
+ return repeatItemData()
+ ? props.block
+ : getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: true,
+ });
+ }
+
+ function actions() {
+ return getBlockActions({
+ block: useBlock(),
+ state: props.context.state,
+ context: props.context.context,
+ });
+ }
+
+ function attributes() {
+ const blockProperties = getBlockProperties(useBlock());
+ return {
+ ...blockProperties,
+ ...(TARGET === \\"reactNative\\"
+ ? {
+ style: getReactNativeBlockStyles({
+ block: useBlock(),
+ context: props.context,
+ blockStyles: blockProperties.style,
+ }),
+ }
+ : {}),
+ };
+ }
+
+ function shouldWrap() {
+ return !component?.()?.noWrap;
+ }
+
+ function renderComponentProps() {
+ return {
+ blockChildren: useChildren(),
+ componentRef: component?.()?.component,
+ componentOptions: {
+ ...getBlockComponentOptions(useBlock()),
+
+ /**
+ * These attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then
+ * they are provided to the component itself directly.
+ */
+ ...(shouldWrap()
+ ? {}
+ : {
+ attributes: { ...attributes(), ...actions() },
+ }),
+ customBreakpoints: childrenContext?.()?.content?.meta?.breakpoints,
+ },
+ context: childrenContext(),
+ };
+ }
+
+ function useChildren() {
+ // TO-DO: When should \`canHaveChildren\` dictate rendering?
+ // This is currently commented out because some Builder components (e.g. Box) do not have \`canHaveChildren: true\`,
+ // but still receive and need to render children.
+ // return state.componentInfo?.canHaveChildren ? useBlock().children : [];
+ return useBlock().children ?? [];
+ }
+
+ function childrenWithoutParentComponent() {
+ /**
+ * When there is no \`componentRef\`, there might still be children that need to be rendered. In this case,
+ * we render them outside of \`componentRef\`.
+ * NOTE: We make sure not to render this if \`repeatItemData\` is non-null, because that means we are rendering an array of
+ * blocks, and the children will be repeated within those blocks.
+ */
+ const shouldRenderChildrenOutsideRef =
+ !component?.()?.component && !repeatItemData();
+ return shouldRenderChildrenOutsideRef ? useChildren() : [];
+ }
+
+ function repeatItemData() {
+ /**
+ * we don't use \`useBlock()\` here because the processing done within its logic includes evaluating the block's bindings,
+ * which will not work if there is a repeat.
+ */
+ const { repeat, ...blockWithoutRepeat } = props.block;
+
+ if (!repeat?.collection) {
+ return undefined;
+ }
+
+ const itemsArray = evaluate({
+ code: repeat.collection,
+ state: props.context.state,
+ context: props.context.context,
+ });
+
+ if (!Array.isArray(itemsArray)) {
+ return undefined;
+ }
+
+ const collectionName = repeat.collection.split(\\".\\").pop();
+ const itemNameToUse =
+ repeat.itemName || (collectionName ? collectionName + \\"Item\\" : \\"item\\");
+ const repeatArray = itemsArray.map((item, index) => ({
+ context: {
+ ...props.context,
+ state: {
+ ...props.context.state,
+ $index: index,
+ $item: item,
+ [itemNameToUse]: item,
+ [\`$\${itemNameToUse}Index\`]: index,
+ },
+ },
+ block: blockWithoutRepeat,
+ }));
+ return repeatArray;
+ }
+
+ function inheritedTextStyles() {
+ if (TARGET !== \\"reactNative\\") {
+ return {};
+ }
+
+ const styles = getReactNativeBlockStyles({
+ block: useBlock(),
+ context: props.context,
+ blockStyles: attributes().style,
+ });
+ return extractTextStyles(styles);
+ }
+
+ function childrenContext() {
+ return {
+ apiKey: props.context.apiKey,
+ state: props.context.state,
+ content: props.context.content,
+ context: props.context.context,
+ registeredComponents: props.context.registeredComponents,
+ inheritedStyles: inheritedTextStyles(),
+ };
+ }
+
+ function renderComponentTag() {
+ if (TARGET === \\"reactNative\\") {
+ return RenderComponentWithContext;
+ } else if (TARGET === \\"vue3\\") {
+ // vue3 expects a string for the component tag
+ return \\"RenderComponent\\";
+ } else {
+ return RenderComponent;
+ }
+ }
+
+ const [componentInfo, setComponentInfo] = useState(() => null);
+
+ const RenderComponentTagRef = renderComponentTag();
+ const TagRef = tag();
+
+ return (
+
+ {shouldWrap() ? (
+
+ {isEmptyHtmlElement(tag()) ? (
+
+
+
+ ) : null}
+ {!isEmptyHtmlElement(tag()) && repeatItemData() ? (
+
+ {repeatItemData()?.map((data, index) => (
+
+ ))}
+
+ ) : null}
+ {!isEmptyHtmlElement(tag()) && !repeatItemData() ? (
+
+
+
+
+ {childrenWithoutParentComponent()?.map((child) => (
+
+ ))}
+
+ {childrenWithoutParentComponent()?.map((child) => (
+
+ ))}
+
+
+ ) : null}
+
+ ) : (
+
+ )}
+
+ );
+}
+"
+`;
+
exports[`Preact > jsx > Javascript Test > renderContentExample 1`] = `
"/** @jsx h */
import { h, Fragment } from \\"preact\\";
@@ -5146,6 +5409,281 @@ export default function OnUpdate(props: any) {
"
`;
+exports[`Preact > jsx > Typescript Test > renderBlock 1`] = `
+"/** @jsx h */
+import { h, Fragment } from \\"preact\\";
+import { useState } from \\"preact/hooks\\";
+import type {
+ BuilderContextInterface,
+ RegisteredComponent,
+} from \\"../../context/types.js\\";
+import type { BuilderBlock } from \\"../../types/builder-block.js\\";
+import type { Nullable } from \\"../../types/typescript.js\\";
+import type { RenderComponentProps } from \\"./render-component\\";
+import type { RepeatData } from \\"./types.js\\";
+export type RenderBlockProps = {
+ block: BuilderBlock;
+ context: BuilderContextInterface;
+};
+import { getBlockActions } from \\"../../functions/get-block-actions.js\\";
+import { getBlockComponentOptions } from \\"../../functions/get-block-component-options.js\\";
+import { getBlockProperties } from \\"../../functions/get-block-properties.js\\";
+import { getBlockTag } from \\"../../functions/get-block-tag.js\\";
+import { getProcessedBlock } from \\"../../functions/get-processed-block.js\\";
+import { evaluate } from \\"../../functions/evaluate.js\\";
+import BlockStyles from \\"./block-styles.lite\\";
+import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\";
+import RenderRepeatedBlock from \\"./render-repeated-block.lite\\";
+import { TARGET } from \\"../../constants/target.js\\";
+import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\";
+import RenderComponentWithContext from \\"./render-component-with-context.lite\\";
+import RenderComponent from \\"./render-component.lite\\";
+import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\";
+
+export default function RenderBlock(props: RenderBlockProps) {
+ function component() {
+ const componentName = getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: false,
+ }).component?.name;
+
+ if (!componentName) {
+ return null;
+ }
+
+ const ref = props.context.registeredComponents[componentName];
+
+ if (!ref) {
+ // TODO: Public doc page with more info about this message
+ console.warn(\`
+ Could not find a registered component named \\"\${componentName}\\".
+ If you registered it, is the file that registered it imported by the file that needs to render it?\`);
+ return undefined;
+ } else {
+ return ref;
+ }
+ }
+
+ function tag() {
+ return getBlockTag(useBlock());
+ }
+
+ function useBlock() {
+ return repeatItemData()
+ ? props.block
+ : getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: true,
+ });
+ }
+
+ function actions() {
+ return getBlockActions({
+ block: useBlock(),
+ state: props.context.state,
+ context: props.context.context,
+ });
+ }
+
+ function attributes() {
+ const blockProperties = getBlockProperties(useBlock());
+ return {
+ ...blockProperties,
+ ...(TARGET === \\"reactNative\\"
+ ? {
+ style: getReactNativeBlockStyles({
+ block: useBlock(),
+ context: props.context,
+ blockStyles: blockProperties.style,
+ }),
+ }
+ : {}),
+ };
+ }
+
+ function shouldWrap() {
+ return !component?.()?.noWrap;
+ }
+
+ function renderComponentProps() {
+ return {
+ blockChildren: useChildren(),
+ componentRef: component?.()?.component,
+ componentOptions: {
+ ...getBlockComponentOptions(useBlock()),
+
+ /**
+ * These attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then
+ * they are provided to the component itself directly.
+ */
+ ...(shouldWrap()
+ ? {}
+ : {
+ attributes: { ...attributes(), ...actions() },
+ }),
+ customBreakpoints: childrenContext?.()?.content?.meta?.breakpoints,
+ },
+ context: childrenContext(),
+ };
+ }
+
+ function useChildren() {
+ // TO-DO: When should \`canHaveChildren\` dictate rendering?
+ // This is currently commented out because some Builder components (e.g. Box) do not have \`canHaveChildren: true\`,
+ // but still receive and need to render children.
+ // return state.componentInfo?.canHaveChildren ? useBlock().children : [];
+ return useBlock().children ?? [];
+ }
+
+ function childrenWithoutParentComponent() {
+ /**
+ * When there is no \`componentRef\`, there might still be children that need to be rendered. In this case,
+ * we render them outside of \`componentRef\`.
+ * NOTE: We make sure not to render this if \`repeatItemData\` is non-null, because that means we are rendering an array of
+ * blocks, and the children will be repeated within those blocks.
+ */
+ const shouldRenderChildrenOutsideRef =
+ !component?.()?.component && !repeatItemData();
+ return shouldRenderChildrenOutsideRef ? useChildren() : [];
+ }
+
+ function repeatItemData() {
+ /**
+ * we don't use \`useBlock()\` here because the processing done within its logic includes evaluating the block's bindings,
+ * which will not work if there is a repeat.
+ */
+ const { repeat, ...blockWithoutRepeat } = props.block;
+
+ if (!repeat?.collection) {
+ return undefined;
+ }
+
+ const itemsArray = evaluate({
+ code: repeat.collection,
+ state: props.context.state,
+ context: props.context.context,
+ });
+
+ if (!Array.isArray(itemsArray)) {
+ return undefined;
+ }
+
+ const collectionName = repeat.collection.split(\\".\\").pop();
+ const itemNameToUse =
+ repeat.itemName || (collectionName ? collectionName + \\"Item\\" : \\"item\\");
+ const repeatArray = itemsArray.map((item, index) => ({
+ context: {
+ ...props.context,
+ state: {
+ ...props.context.state,
+ $index: index,
+ $item: item,
+ [itemNameToUse]: item,
+ [\`$\${itemNameToUse}Index\`]: index,
+ },
+ },
+ block: blockWithoutRepeat,
+ }));
+ return repeatArray;
+ }
+
+ function inheritedTextStyles() {
+ if (TARGET !== \\"reactNative\\") {
+ return {};
+ }
+
+ const styles = getReactNativeBlockStyles({
+ block: useBlock(),
+ context: props.context,
+ blockStyles: attributes().style,
+ });
+ return extractTextStyles(styles);
+ }
+
+ function childrenContext() {
+ return {
+ apiKey: props.context.apiKey,
+ state: props.context.state,
+ content: props.context.content,
+ context: props.context.context,
+ registeredComponents: props.context.registeredComponents,
+ inheritedStyles: inheritedTextStyles(),
+ };
+ }
+
+ function renderComponentTag() {
+ if (TARGET === \\"reactNative\\") {
+ return RenderComponentWithContext;
+ } else if (TARGET === \\"vue3\\") {
+ // vue3 expects a string for the component tag
+ return \\"RenderComponent\\";
+ } else {
+ return RenderComponent;
+ }
+ }
+
+ const [componentInfo, setComponentInfo] = useState(() => null);
+
+ const RenderComponentTagRef = renderComponentTag();
+ const TagRef = tag();
+
+ return (
+
+ {shouldWrap() ? (
+
+ {isEmptyHtmlElement(tag()) ? (
+
+
+
+ ) : null}
+ {!isEmptyHtmlElement(tag()) && repeatItemData() ? (
+
+ {repeatItemData()?.map((data, index) => (
+
+ ))}
+
+ ) : null}
+ {!isEmptyHtmlElement(tag()) && !repeatItemData() ? (
+
+
+
+
+ {childrenWithoutParentComponent()?.map((child) => (
+
+ ))}
+
+ {childrenWithoutParentComponent()?.map((child) => (
+
+ ))}
+
+
+ ) : null}
+
+ ) : (
+
+ )}
+
+ );
+}
+"
+`;
+
exports[`Preact > jsx > Typescript Test > renderContentExample 1`] = `
"/** @jsx h */
import { h, Fragment } from \\"preact\\";
diff --git a/packages/core/src/__tests__/__snapshots__/qwik.test.ts.snap b/packages/core/src/__tests__/__snapshots__/qwik.test.ts.snap
index 2531e0cfb6..9cfe970af2 100644
--- a/packages/core/src/__tests__/__snapshots__/qwik.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/qwik.test.ts.snap
@@ -3297,6 +3297,271 @@ export default OnUpdate;
"
`;
+exports[`qwik > jsx > Javascript Test > renderBlock 1`] = `
+"// GENERATED BY MITOSIS
+
+import { TARGET } from \\"../../constants/target.js\\";
+import { evaluate } from \\"../../functions/evaluate.js\\";
+import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\";
+import { getBlockActions } from \\"../../functions/get-block-actions.js\\";
+import { getBlockComponentOptions } from \\"../../functions/get-block-component-options.js\\";
+import { getBlockProperties } from \\"../../functions/get-block-properties.js\\";
+import { getBlockTag } from \\"../../functions/get-block-tag.js\\";
+import { getProcessedBlock } from \\"../../functions/get-processed-block.js\\";
+import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\";
+import BlockStyles from \\"./block-styles\\";
+import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\";
+import RenderComponent from \\"./render-component\\";
+import RenderComponentWithContext from \\"./render-component-with-context\\";
+import RenderRepeatedBlock from \\"./render-repeated-block\\";
+import { Fragment, component$, h } from \\"@builder.io/qwik\\";
+export const component = function component(props, state) {
+ const componentName = getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: false,
+ }).component?.name;
+
+ if (!componentName) {
+ return null;
+ }
+
+ const ref = props.context.registeredComponents[componentName];
+
+ if (!ref) {
+ // TODO: Public doc page with more info about this message
+ console.warn(\`
+ Could not find a registered component named \\"\${componentName}\\".
+ If you registered it, is the file that registered it imported by the file that needs to render it?\`);
+ return undefined;
+ } else {
+ return ref;
+ }
+};
+export const tag = function tag(props, state) {
+ return getBlockTag(useBlock(props, state));
+};
+export const useBlock = function useBlock(props, state) {
+ return repeatItemData(props, state)
+ ? props.block
+ : getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: true,
+ });
+};
+export const actions = function actions(props, state) {
+ return getBlockActions({
+ block: useBlock(props, state),
+ state: props.context.state,
+ context: props.context.context,
+ });
+};
+export const attributes = function attributes(props, state) {
+ const blockProperties = getBlockProperties(useBlock(props, state));
+ return {
+ ...blockProperties,
+ ...(TARGET === \\"reactNative\\"
+ ? {
+ style: getReactNativeBlockStyles({
+ block: useBlock(props, state),
+ context: props.context,
+ blockStyles: blockProperties.style,
+ }),
+ }
+ : {}),
+ };
+};
+export const shouldWrap = function shouldWrap(props, state) {
+ return !component(props, state)?.noWrap;
+};
+export const renderComponentProps = function renderComponentProps(
+ props,
+ state
+) {
+ return {
+ blockChildren: useChildren(props, state),
+ componentRef: component(props, state)?.component,
+ componentOptions: {
+ ...getBlockComponentOptions(useBlock(props, state)),
+
+ /**
+ * These attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then
+ * they are provided to the component itself directly.
+ */
+ ...(shouldWrap(props, state)
+ ? {}
+ : {
+ attributes: {
+ ...attributes(props, state),
+ ...actions(props, state),
+ },
+ }),
+ customBreakpoints: childrenContext(props, state)?.content?.meta
+ ?.breakpoints,
+ },
+ context: childrenContext(props, state),
+ };
+};
+export const useChildren = function useChildren(props, state) {
+ // TO-DO: When should \`canHaveChildren\` dictate rendering?
+ // This is currently commented out because some Builder components (e.g. Box) do not have \`canHaveChildren: true\`,
+ // but still receive and need to render children.
+ // return state.componentInfo?.canHaveChildren ? state.useBlock.children : [];
+ return useBlock(props, state).children ?? [];
+};
+export const childrenWithoutParentComponent =
+ function childrenWithoutParentComponent(props, state) {
+ /**
+ * When there is no \`componentRef\`, there might still be children that need to be rendered. In this case,
+ * we render them outside of \`componentRef\`.
+ * NOTE: We make sure not to render this if \`repeatItemData\` is non-null, because that means we are rendering an array of
+ * blocks, and the children will be repeated within those blocks.
+ */
+ const shouldRenderChildrenOutsideRef =
+ !component(props, state)?.component && !repeatItemData(props, state);
+ return shouldRenderChildrenOutsideRef ? useChildren(props, state) : [];
+ };
+export const repeatItemData = function repeatItemData(props, state) {
+ /**
+ * we don't use \`state.useBlock\` here because the processing done within its logic includes evaluating the block's bindings,
+ * which will not work if there is a repeat.
+ */
+ const { repeat, ...blockWithoutRepeat } = props.block;
+
+ if (!repeat?.collection) {
+ return undefined;
+ }
+
+ const itemsArray = evaluate({
+ code: repeat.collection,
+ state: props.context.state,
+ context: props.context.context,
+ });
+
+ if (!Array.isArray(itemsArray)) {
+ return undefined;
+ }
+
+ const collectionName = repeat.collection.split(\\".\\").pop();
+ const itemNameToUse =
+ repeat.itemName || (collectionName ? collectionName + \\"Item\\" : \\"item\\");
+ const repeatArray = itemsArray.map((item, index) => ({
+ context: {
+ ...props.context,
+ state: {
+ ...props.context.state,
+ $index: index,
+ $item: item,
+ [itemNameToUse]: item,
+ [\`$\${itemNameToUse}Index\`]: index,
+ },
+ },
+ block: blockWithoutRepeat,
+ }));
+ return repeatArray;
+};
+export const inheritedTextStyles = function inheritedTextStyles(props, state) {
+ if (TARGET !== \\"reactNative\\") {
+ return {};
+ }
+
+ const styles = getReactNativeBlockStyles({
+ block: useBlock(props, state),
+ context: props.context,
+ blockStyles: attributes(props, state).style,
+ });
+ return extractTextStyles(styles);
+};
+export const childrenContext = function childrenContext(props, state) {
+ return {
+ apiKey: props.context.apiKey,
+ state: props.context.state,
+ content: props.context.content,
+ context: props.context.context,
+ registeredComponents: props.context.registeredComponents,
+ inheritedStyles: inheritedTextStyles(props, state),
+ };
+};
+export const renderComponentTag = function renderComponentTag(props, state) {
+ if (TARGET === \\"reactNative\\") {
+ return RenderComponentWithContext;
+ } else if (TARGET === \\"vue3\\") {
+ // vue3 expects a string for the component tag
+ return \\"RenderComponent\\";
+ } else {
+ return RenderComponent;
+ }
+};
+export const RenderBlock = component$((props) => {
+ const state = {};
+ return (
+ <>
+ {shouldWrap(props, state) ? (
+ <>
+ {isEmptyHtmlElement(tag(props, state)) ? (
+
+ ) : null}
+ {!isEmptyHtmlElement(tag(props, state)) &&
+ repeatItemData(props, state)
+ ? (repeatItemData(props, state) || []).map(function (data, index) {
+ return (
+
+ );
+ })
+ : null}
+ {!isEmptyHtmlElement(tag(props, state)) &&
+ !repeatItemData(props, state) ? (
+
+
+ {(childrenWithoutParentComponent(props, state) || []).map(
+ function (child) {
+ return (
+
+ );
+ }
+ )}
+ {(childrenWithoutParentComponent(props, state) || []).map(
+ function (child) {
+ return (
+
+ );
+ }
+ )}
+
+ ) : null}
+ >
+ ) : (
+
+ )}
+ >
+ );
+});
+export default RenderBlock;
+"
+`;
+
exports[`qwik > jsx > Javascript Test > renderContentExample 1`] = `
"// GENERATED BY MITOSIS
@@ -6111,6 +6376,283 @@ export default OnUpdate;
"
`;
+exports[`qwik > jsx > Typescript Test > renderBlock 1`] = `
+"// GENERATED BY MITOSIS
+
+import { TARGET } from \\"../../constants/target.js\\";
+import { evaluate } from \\"../../functions/evaluate.js\\";
+import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\";
+import { getBlockActions } from \\"../../functions/get-block-actions.js\\";
+import { getBlockComponentOptions } from \\"../../functions/get-block-component-options.js\\";
+import { getBlockProperties } from \\"../../functions/get-block-properties.js\\";
+import { getBlockTag } from \\"../../functions/get-block-tag.js\\";
+import { getProcessedBlock } from \\"../../functions/get-processed-block.js\\";
+import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\";
+import BlockStyles from \\"./block-styles\\";
+import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\";
+import RenderComponent from \\"./render-component\\";
+import RenderComponentWithContext from \\"./render-component-with-context\\";
+import RenderRepeatedBlock from \\"./render-repeated-block\\";
+import { Fragment, component$, h } from \\"@builder.io/qwik\\";
+import type {
+ BuilderContextInterface,
+ RegisteredComponent,
+} from \\"../../context/types.js\\";
+import type { BuilderBlock } from \\"../../types/builder-block.js\\";
+import type { Nullable } from \\"../../types/typescript.js\\";
+import type { RenderComponentProps } from \\"./render-component\\";
+import type { RepeatData } from \\"./types.js\\";
+export type RenderBlockProps = {
+ block: BuilderBlock;
+ context: BuilderContextInterface;
+};
+export const component = function component(props, state) {
+ const componentName = getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: false,
+ }).component?.name;
+
+ if (!componentName) {
+ return null;
+ }
+
+ const ref = props.context.registeredComponents[componentName];
+
+ if (!ref) {
+ // TODO: Public doc page with more info about this message
+ console.warn(\`
+ Could not find a registered component named \\"\${componentName}\\".
+ If you registered it, is the file that registered it imported by the file that needs to render it?\`);
+ return undefined;
+ } else {
+ return ref;
+ }
+};
+export const tag = function tag(props, state) {
+ return getBlockTag(useBlock(props, state));
+};
+export const useBlock = function useBlock(props, state) {
+ return repeatItemData(props, state)
+ ? props.block
+ : getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: true,
+ });
+};
+export const actions = function actions(props, state) {
+ return getBlockActions({
+ block: useBlock(props, state),
+ state: props.context.state,
+ context: props.context.context,
+ });
+};
+export const attributes = function attributes(props, state) {
+ const blockProperties = getBlockProperties(useBlock(props, state));
+ return {
+ ...blockProperties,
+ ...(TARGET === \\"reactNative\\"
+ ? {
+ style: getReactNativeBlockStyles({
+ block: useBlock(props, state),
+ context: props.context,
+ blockStyles: blockProperties.style,
+ }),
+ }
+ : {}),
+ };
+};
+export const shouldWrap = function shouldWrap(props, state) {
+ return !component(props, state)?.noWrap;
+};
+export const renderComponentProps = function renderComponentProps(
+ props,
+ state
+) {
+ return {
+ blockChildren: useChildren(props, state),
+ componentRef: component(props, state)?.component,
+ componentOptions: {
+ ...getBlockComponentOptions(useBlock(props, state)),
+
+ /**
+ * These attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then
+ * they are provided to the component itself directly.
+ */
+ ...(shouldWrap(props, state)
+ ? {}
+ : {
+ attributes: {
+ ...attributes(props, state),
+ ...actions(props, state),
+ },
+ }),
+ customBreakpoints: childrenContext(props, state)?.content?.meta
+ ?.breakpoints,
+ },
+ context: childrenContext(props, state),
+ };
+};
+export const useChildren = function useChildren(props, state) {
+ // TO-DO: When should \`canHaveChildren\` dictate rendering?
+ // This is currently commented out because some Builder components (e.g. Box) do not have \`canHaveChildren: true\`,
+ // but still receive and need to render children.
+ // return state.componentInfo?.canHaveChildren ? state.useBlock.children : [];
+ return useBlock(props, state).children ?? [];
+};
+export const childrenWithoutParentComponent =
+ function childrenWithoutParentComponent(props, state) {
+ /**
+ * When there is no \`componentRef\`, there might still be children that need to be rendered. In this case,
+ * we render them outside of \`componentRef\`.
+ * NOTE: We make sure not to render this if \`repeatItemData\` is non-null, because that means we are rendering an array of
+ * blocks, and the children will be repeated within those blocks.
+ */
+ const shouldRenderChildrenOutsideRef =
+ !component(props, state)?.component && !repeatItemData(props, state);
+ return shouldRenderChildrenOutsideRef ? useChildren(props, state) : [];
+ };
+export const repeatItemData = function repeatItemData(props, state) {
+ /**
+ * we don't use \`state.useBlock\` here because the processing done within its logic includes evaluating the block's bindings,
+ * which will not work if there is a repeat.
+ */
+ const { repeat, ...blockWithoutRepeat } = props.block;
+
+ if (!repeat?.collection) {
+ return undefined;
+ }
+
+ const itemsArray = evaluate({
+ code: repeat.collection,
+ state: props.context.state,
+ context: props.context.context,
+ });
+
+ if (!Array.isArray(itemsArray)) {
+ return undefined;
+ }
+
+ const collectionName = repeat.collection.split(\\".\\").pop();
+ const itemNameToUse =
+ repeat.itemName || (collectionName ? collectionName + \\"Item\\" : \\"item\\");
+ const repeatArray = itemsArray.map((item, index) => ({
+ context: {
+ ...props.context,
+ state: {
+ ...props.context.state,
+ $index: index,
+ $item: item,
+ [itemNameToUse]: item,
+ [\`$\${itemNameToUse}Index\`]: index,
+ },
+ },
+ block: blockWithoutRepeat,
+ }));
+ return repeatArray;
+};
+export const inheritedTextStyles = function inheritedTextStyles(props, state) {
+ if (TARGET !== \\"reactNative\\") {
+ return {};
+ }
+
+ const styles = getReactNativeBlockStyles({
+ block: useBlock(props, state),
+ context: props.context,
+ blockStyles: attributes(props, state).style,
+ });
+ return extractTextStyles(styles);
+};
+export const childrenContext = function childrenContext(props, state) {
+ return {
+ apiKey: props.context.apiKey,
+ state: props.context.state,
+ content: props.context.content,
+ context: props.context.context,
+ registeredComponents: props.context.registeredComponents,
+ inheritedStyles: inheritedTextStyles(props, state),
+ };
+};
+export const renderComponentTag = function renderComponentTag(props, state) {
+ if (TARGET === \\"reactNative\\") {
+ return RenderComponentWithContext;
+ } else if (TARGET === \\"vue3\\") {
+ // vue3 expects a string for the component tag
+ return \\"RenderComponent\\";
+ } else {
+ return RenderComponent;
+ }
+};
+export const RenderBlock = component$((props: RenderBlockProps) => {
+ const state: any = {};
+ return (
+ <>
+ {shouldWrap(props, state) ? (
+ <>
+ {isEmptyHtmlElement(tag(props, state)) ? (
+
+ ) : null}
+ {!isEmptyHtmlElement(tag(props, state)) &&
+ repeatItemData(props, state)
+ ? (repeatItemData(props, state) || []).map(function (data, index) {
+ return (
+
+ );
+ })
+ : null}
+ {!isEmptyHtmlElement(tag(props, state)) &&
+ !repeatItemData(props, state) ? (
+
+
+ {(childrenWithoutParentComponent(props, state) || []).map(
+ function (child) {
+ return (
+
+ );
+ }
+ )}
+ {(childrenWithoutParentComponent(props, state) || []).map(
+ function (child) {
+ return (
+
+ );
+ }
+ )}
+
+ ) : null}
+ >
+ ) : (
+
+ )}
+ >
+ );
+});
+export default RenderBlock;
+"
+`;
+
exports[`qwik > jsx > Typescript Test > renderContentExample 1`] = `
"// GENERATED BY MITOSIS
diff --git a/packages/core/src/__tests__/__snapshots__/react-native.test.ts.snap b/packages/core/src/__tests__/__snapshots__/react-native.test.ts.snap
index abdf1ac776..1b3b07a44a 100644
--- a/packages/core/src/__tests__/__snapshots__/react-native.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/react-native.test.ts.snap
@@ -2160,6 +2160,268 @@ export default function OnUpdate(props) {
"
`;
+exports[`React Native > jsx > Javascript Test > renderBlock 1`] = `
+"import * as React from \\"react\\";
+import { View, StyleSheet, Image, Text } from \\"react-native\\";
+import { useState } from \\"react\\";
+import { getBlockActions } from \\"../../functions/get-block-actions.js\\";
+import { getBlockComponentOptions } from \\"../../functions/get-block-component-options.js\\";
+import { getBlockProperties } from \\"../../functions/get-block-properties.js\\";
+import { getBlockTag } from \\"../../functions/get-block-tag.js\\";
+import { getProcessedBlock } from \\"../../functions/get-processed-block.js\\";
+import { evaluate } from \\"../../functions/evaluate.js\\";
+import BlockStyles from \\"./block-styles.lite\\";
+import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\";
+import RenderRepeatedBlock from \\"./render-repeated-block.lite\\";
+import { TARGET } from \\"../../constants/target.js\\";
+import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\";
+import RenderComponentWithContext from \\"./render-component-with-context.lite\\";
+import RenderComponent from \\"./render-component.lite\\";
+import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\";
+
+export default function RenderBlock(props) {
+ function component() {
+ const componentName = getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: false,
+ }).component?.name;
+
+ if (!componentName) {
+ return null;
+ }
+
+ const ref = props.context.registeredComponents[componentName];
+
+ if (!ref) {
+ // TODO: Public doc page with more info about this message
+ console.warn(\`
+ Could not find a registered component named \\"\${componentName}\\".
+ If you registered it, is the file that registered it imported by the file that needs to render it?\`);
+ return undefined;
+ } else {
+ return ref;
+ }
+ }
+
+ function tag() {
+ return getBlockTag(useBlock());
+ }
+
+ function useBlock() {
+ return repeatItemData()
+ ? props.block
+ : getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: true,
+ });
+ }
+
+ function actions() {
+ return getBlockActions({
+ block: useBlock(),
+ state: props.context.state,
+ context: props.context.context,
+ });
+ }
+
+ function attributes() {
+ const blockProperties = getBlockProperties(useBlock());
+ return {
+ ...blockProperties,
+ ...(TARGET === \\"reactNative\\"
+ ? {
+ style: getReactNativeBlockStyles({
+ block: useBlock(),
+ context: props.context,
+ blockStyles: blockProperties.style,
+ }),
+ }
+ : {}),
+ };
+ }
+
+ function shouldWrap() {
+ return !component?.()?.noWrap;
+ }
+
+ function renderComponentProps() {
+ return {
+ blockChildren: useChildren(),
+ componentRef: component?.()?.component,
+ componentOptions: {
+ ...getBlockComponentOptions(useBlock()),
+
+ /**
+ * These attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then
+ * they are provided to the component itself directly.
+ */
+ ...(shouldWrap()
+ ? {}
+ : {
+ attributes: { ...attributes(), ...actions() },
+ }),
+ customBreakpoints: childrenContext?.()?.content?.meta?.breakpoints,
+ },
+ context: childrenContext(),
+ };
+ }
+
+ function useChildren() {
+ // TO-DO: When should \`canHaveChildren\` dictate rendering?
+ // This is currently commented out because some Builder components (e.g. Box) do not have \`canHaveChildren: true\`,
+ // but still receive and need to render children.
+ // return state.componentInfo?.canHaveChildren ? useBlock().children : [];
+ return useBlock().children ?? [];
+ }
+
+ function childrenWithoutParentComponent() {
+ /**
+ * When there is no \`componentRef\`, there might still be children that need to be rendered. In this case,
+ * we render them outside of \`componentRef\`.
+ * NOTE: We make sure not to render this if \`repeatItemData\` is non-null, because that means we are rendering an array of
+ * blocks, and the children will be repeated within those blocks.
+ */
+ const shouldRenderChildrenOutsideRef =
+ !component?.()?.component && !repeatItemData();
+ return shouldRenderChildrenOutsideRef ? useChildren() : [];
+ }
+
+ function repeatItemData() {
+ /**
+ * we don't use \`useBlock()\` here because the processing done within its logic includes evaluating the block's bindings,
+ * which will not work if there is a repeat.
+ */
+ const { repeat, ...blockWithoutRepeat } = props.block;
+
+ if (!repeat?.collection) {
+ return undefined;
+ }
+
+ const itemsArray = evaluate({
+ code: repeat.collection,
+ state: props.context.state,
+ context: props.context.context,
+ });
+
+ if (!Array.isArray(itemsArray)) {
+ return undefined;
+ }
+
+ const collectionName = repeat.collection.split(\\".\\").pop();
+ const itemNameToUse =
+ repeat.itemName || (collectionName ? collectionName + \\"Item\\" : \\"item\\");
+ const repeatArray = itemsArray.map((item, index) => ({
+ context: {
+ ...props.context,
+ state: {
+ ...props.context.state,
+ $index: index,
+ $item: item,
+ [itemNameToUse]: item,
+ [\`$\${itemNameToUse}Index\`]: index,
+ },
+ },
+ block: blockWithoutRepeat,
+ }));
+ return repeatArray;
+ }
+
+ function inheritedTextStyles() {
+ if (TARGET !== \\"reactNative\\") {
+ return {};
+ }
+
+ const styles = getReactNativeBlockStyles({
+ block: useBlock(),
+ context: props.context,
+ blockStyles: attributes().style,
+ });
+ return extractTextStyles(styles);
+ }
+
+ function childrenContext() {
+ return {
+ apiKey: props.context.apiKey,
+ state: props.context.state,
+ content: props.context.content,
+ context: props.context.context,
+ registeredComponents: props.context.registeredComponents,
+ inheritedStyles: inheritedTextStyles(),
+ };
+ }
+
+ function renderComponentTag() {
+ if (TARGET === \\"reactNative\\") {
+ return RenderComponentWithContext;
+ } else if (TARGET === \\"vue3\\") {
+ // vue3 expects a string for the component tag
+ return \\"RenderComponent\\";
+ } else {
+ return RenderComponent;
+ }
+ }
+
+ const [componentInfo, setComponentInfo] = useState(() => null);
+
+ const RenderComponentTagRef = renderComponentTag();
+
+ return (
+ <>
+ {shouldWrap() ? (
+ <>
+ {isEmptyHtmlElement(tag()) ? (
+ <>
+
+ >
+ ) : null}
+ {!isEmptyHtmlElement(tag()) && repeatItemData() ? (
+ <>
+ {repeatItemData()?.map((data, index) => (
+
+ ))}
+ >
+ ) : null}
+ {!isEmptyHtmlElement(tag()) && !repeatItemData() ? (
+ <>
+
+
+
+ {childrenWithoutParentComponent()?.map((child) => (
+
+ ))}
+
+ {childrenWithoutParentComponent()?.map((child) => (
+
+ ))}
+
+ >
+ ) : null}
+ >
+ ) : (
+
+ )}
+ >
+ );
+}
+"
+`;
+
exports[`React Native > jsx > Javascript Test > renderContentExample 1`] = `
"import * as React from \\"react\\";
import { View, StyleSheet, Image, Text } from \\"react-native\\";
@@ -4985,6 +5247,280 @@ export default function OnUpdate(props: any) {
"
`;
+exports[`React Native > jsx > Typescript Test > renderBlock 1`] = `
+"import * as React from \\"react\\";
+import { View, StyleSheet, Image, Text } from \\"react-native\\";
+import { useState } from \\"react\\";
+import type {
+ BuilderContextInterface,
+ RegisteredComponent,
+} from \\"../../context/types.js\\";
+import type { BuilderBlock } from \\"../../types/builder-block.js\\";
+import type { Nullable } from \\"../../types/typescript.js\\";
+import type { RenderComponentProps } from \\"./render-component\\";
+import type { RepeatData } from \\"./types.js\\";
+export type RenderBlockProps = {
+ block: BuilderBlock;
+ context: BuilderContextInterface;
+};
+import { getBlockActions } from \\"../../functions/get-block-actions.js\\";
+import { getBlockComponentOptions } from \\"../../functions/get-block-component-options.js\\";
+import { getBlockProperties } from \\"../../functions/get-block-properties.js\\";
+import { getBlockTag } from \\"../../functions/get-block-tag.js\\";
+import { getProcessedBlock } from \\"../../functions/get-processed-block.js\\";
+import { evaluate } from \\"../../functions/evaluate.js\\";
+import BlockStyles from \\"./block-styles.lite\\";
+import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\";
+import RenderRepeatedBlock from \\"./render-repeated-block.lite\\";
+import { TARGET } from \\"../../constants/target.js\\";
+import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\";
+import RenderComponentWithContext from \\"./render-component-with-context.lite\\";
+import RenderComponent from \\"./render-component.lite\\";
+import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\";
+
+export default function RenderBlock(props: RenderBlockProps) {
+ function component() {
+ const componentName = getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: false,
+ }).component?.name;
+
+ if (!componentName) {
+ return null;
+ }
+
+ const ref = props.context.registeredComponents[componentName];
+
+ if (!ref) {
+ // TODO: Public doc page with more info about this message
+ console.warn(\`
+ Could not find a registered component named \\"\${componentName}\\".
+ If you registered it, is the file that registered it imported by the file that needs to render it?\`);
+ return undefined;
+ } else {
+ return ref;
+ }
+ }
+
+ function tag() {
+ return getBlockTag(useBlock());
+ }
+
+ function useBlock() {
+ return repeatItemData()
+ ? props.block
+ : getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: true,
+ });
+ }
+
+ function actions() {
+ return getBlockActions({
+ block: useBlock(),
+ state: props.context.state,
+ context: props.context.context,
+ });
+ }
+
+ function attributes() {
+ const blockProperties = getBlockProperties(useBlock());
+ return {
+ ...blockProperties,
+ ...(TARGET === \\"reactNative\\"
+ ? {
+ style: getReactNativeBlockStyles({
+ block: useBlock(),
+ context: props.context,
+ blockStyles: blockProperties.style,
+ }),
+ }
+ : {}),
+ };
+ }
+
+ function shouldWrap() {
+ return !component?.()?.noWrap;
+ }
+
+ function renderComponentProps() {
+ return {
+ blockChildren: useChildren(),
+ componentRef: component?.()?.component,
+ componentOptions: {
+ ...getBlockComponentOptions(useBlock()),
+
+ /**
+ * These attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then
+ * they are provided to the component itself directly.
+ */
+ ...(shouldWrap()
+ ? {}
+ : {
+ attributes: { ...attributes(), ...actions() },
+ }),
+ customBreakpoints: childrenContext?.()?.content?.meta?.breakpoints,
+ },
+ context: childrenContext(),
+ };
+ }
+
+ function useChildren() {
+ // TO-DO: When should \`canHaveChildren\` dictate rendering?
+ // This is currently commented out because some Builder components (e.g. Box) do not have \`canHaveChildren: true\`,
+ // but still receive and need to render children.
+ // return state.componentInfo?.canHaveChildren ? useBlock().children : [];
+ return useBlock().children ?? [];
+ }
+
+ function childrenWithoutParentComponent() {
+ /**
+ * When there is no \`componentRef\`, there might still be children that need to be rendered. In this case,
+ * we render them outside of \`componentRef\`.
+ * NOTE: We make sure not to render this if \`repeatItemData\` is non-null, because that means we are rendering an array of
+ * blocks, and the children will be repeated within those blocks.
+ */
+ const shouldRenderChildrenOutsideRef =
+ !component?.()?.component && !repeatItemData();
+ return shouldRenderChildrenOutsideRef ? useChildren() : [];
+ }
+
+ function repeatItemData() {
+ /**
+ * we don't use \`useBlock()\` here because the processing done within its logic includes evaluating the block's bindings,
+ * which will not work if there is a repeat.
+ */
+ const { repeat, ...blockWithoutRepeat } = props.block;
+
+ if (!repeat?.collection) {
+ return undefined;
+ }
+
+ const itemsArray = evaluate({
+ code: repeat.collection,
+ state: props.context.state,
+ context: props.context.context,
+ });
+
+ if (!Array.isArray(itemsArray)) {
+ return undefined;
+ }
+
+ const collectionName = repeat.collection.split(\\".\\").pop();
+ const itemNameToUse =
+ repeat.itemName || (collectionName ? collectionName + \\"Item\\" : \\"item\\");
+ const repeatArray = itemsArray.map((item, index) => ({
+ context: {
+ ...props.context,
+ state: {
+ ...props.context.state,
+ $index: index,
+ $item: item,
+ [itemNameToUse]: item,
+ [\`$\${itemNameToUse}Index\`]: index,
+ },
+ },
+ block: blockWithoutRepeat,
+ }));
+ return repeatArray;
+ }
+
+ function inheritedTextStyles() {
+ if (TARGET !== \\"reactNative\\") {
+ return {};
+ }
+
+ const styles = getReactNativeBlockStyles({
+ block: useBlock(),
+ context: props.context,
+ blockStyles: attributes().style,
+ });
+ return extractTextStyles(styles);
+ }
+
+ function childrenContext() {
+ return {
+ apiKey: props.context.apiKey,
+ state: props.context.state,
+ content: props.context.content,
+ context: props.context.context,
+ registeredComponents: props.context.registeredComponents,
+ inheritedStyles: inheritedTextStyles(),
+ };
+ }
+
+ function renderComponentTag() {
+ if (TARGET === \\"reactNative\\") {
+ return RenderComponentWithContext;
+ } else if (TARGET === \\"vue3\\") {
+ // vue3 expects a string for the component tag
+ return \\"RenderComponent\\";
+ } else {
+ return RenderComponent;
+ }
+ }
+
+ const [componentInfo, setComponentInfo] = useState(() => null);
+
+ const RenderComponentTagRef = renderComponentTag();
+
+ return (
+ <>
+ {shouldWrap() ? (
+ <>
+ {isEmptyHtmlElement(tag()) ? (
+ <>
+
+ >
+ ) : null}
+ {!isEmptyHtmlElement(tag()) && repeatItemData() ? (
+ <>
+ {repeatItemData()?.map((data, index) => (
+
+ ))}
+ >
+ ) : null}
+ {!isEmptyHtmlElement(tag()) && !repeatItemData() ? (
+ <>
+
+
+
+ {childrenWithoutParentComponent()?.map((child) => (
+
+ ))}
+
+ {childrenWithoutParentComponent()?.map((child) => (
+
+ ))}
+
+ >
+ ) : null}
+ >
+ ) : (
+
+ )}
+ >
+ );
+}
+"
+`;
+
exports[`React Native > jsx > Typescript Test > renderContentExample 1`] = `
"import * as React from \\"react\\";
import { View, StyleSheet, Image, Text } from \\"react-native\\";
diff --git a/packages/core/src/__tests__/__snapshots__/react.test.ts.snap b/packages/core/src/__tests__/__snapshots__/react.test.ts.snap
index 17aece1d13..2431cd7985 100644
--- a/packages/core/src/__tests__/__snapshots__/react.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/react.test.ts.snap
@@ -2169,6 +2169,268 @@ export default function OnUpdate(props) {
"
`;
+exports[`React > jsx > Javascript Test > renderBlock 1`] = `
+"import * as React from \\"react\\";
+import { useState } from \\"react\\";
+import { getBlockActions } from \\"../../functions/get-block-actions.js\\";
+import { getBlockComponentOptions } from \\"../../functions/get-block-component-options.js\\";
+import { getBlockProperties } from \\"../../functions/get-block-properties.js\\";
+import { getBlockTag } from \\"../../functions/get-block-tag.js\\";
+import { getProcessedBlock } from \\"../../functions/get-processed-block.js\\";
+import { evaluate } from \\"../../functions/evaluate.js\\";
+import BlockStyles from \\"./block-styles.lite\\";
+import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\";
+import RenderRepeatedBlock from \\"./render-repeated-block.lite\\";
+import { TARGET } from \\"../../constants/target.js\\";
+import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\";
+import RenderComponentWithContext from \\"./render-component-with-context.lite\\";
+import RenderComponent from \\"./render-component.lite\\";
+import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\";
+
+export default function RenderBlock(props) {
+ function component() {
+ const componentName = getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: false,
+ }).component?.name;
+
+ if (!componentName) {
+ return null;
+ }
+
+ const ref = props.context.registeredComponents[componentName];
+
+ if (!ref) {
+ // TODO: Public doc page with more info about this message
+ console.warn(\`
+ Could not find a registered component named \\"\${componentName}\\".
+ If you registered it, is the file that registered it imported by the file that needs to render it?\`);
+ return undefined;
+ } else {
+ return ref;
+ }
+ }
+
+ function tag() {
+ return getBlockTag(useBlock());
+ }
+
+ function useBlock() {
+ return repeatItemData()
+ ? props.block
+ : getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: true,
+ });
+ }
+
+ function actions() {
+ return getBlockActions({
+ block: useBlock(),
+ state: props.context.state,
+ context: props.context.context,
+ });
+ }
+
+ function attributes() {
+ const blockProperties = getBlockProperties(useBlock());
+ return {
+ ...blockProperties,
+ ...(TARGET === \\"reactNative\\"
+ ? {
+ style: getReactNativeBlockStyles({
+ block: useBlock(),
+ context: props.context,
+ blockStyles: blockProperties.style,
+ }),
+ }
+ : {}),
+ };
+ }
+
+ function shouldWrap() {
+ return !component?.()?.noWrap;
+ }
+
+ function renderComponentProps() {
+ return {
+ blockChildren: useChildren(),
+ componentRef: component?.()?.component,
+ componentOptions: {
+ ...getBlockComponentOptions(useBlock()),
+
+ /**
+ * These attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then
+ * they are provided to the component itself directly.
+ */
+ ...(shouldWrap()
+ ? {}
+ : {
+ attributes: { ...attributes(), ...actions() },
+ }),
+ customBreakpoints: childrenContext?.()?.content?.meta?.breakpoints,
+ },
+ context: childrenContext(),
+ };
+ }
+
+ function useChildren() {
+ // TO-DO: When should \`canHaveChildren\` dictate rendering?
+ // This is currently commented out because some Builder components (e.g. Box) do not have \`canHaveChildren: true\`,
+ // but still receive and need to render children.
+ // return state.componentInfo?.canHaveChildren ? useBlock().children : [];
+ return useBlock().children ?? [];
+ }
+
+ function childrenWithoutParentComponent() {
+ /**
+ * When there is no \`componentRef\`, there might still be children that need to be rendered. In this case,
+ * we render them outside of \`componentRef\`.
+ * NOTE: We make sure not to render this if \`repeatItemData\` is non-null, because that means we are rendering an array of
+ * blocks, and the children will be repeated within those blocks.
+ */
+ const shouldRenderChildrenOutsideRef =
+ !component?.()?.component && !repeatItemData();
+ return shouldRenderChildrenOutsideRef ? useChildren() : [];
+ }
+
+ function repeatItemData() {
+ /**
+ * we don't use \`useBlock()\` here because the processing done within its logic includes evaluating the block's bindings,
+ * which will not work if there is a repeat.
+ */
+ const { repeat, ...blockWithoutRepeat } = props.block;
+
+ if (!repeat?.collection) {
+ return undefined;
+ }
+
+ const itemsArray = evaluate({
+ code: repeat.collection,
+ state: props.context.state,
+ context: props.context.context,
+ });
+
+ if (!Array.isArray(itemsArray)) {
+ return undefined;
+ }
+
+ const collectionName = repeat.collection.split(\\".\\").pop();
+ const itemNameToUse =
+ repeat.itemName || (collectionName ? collectionName + \\"Item\\" : \\"item\\");
+ const repeatArray = itemsArray.map((item, index) => ({
+ context: {
+ ...props.context,
+ state: {
+ ...props.context.state,
+ $index: index,
+ $item: item,
+ [itemNameToUse]: item,
+ [\`$\${itemNameToUse}Index\`]: index,
+ },
+ },
+ block: blockWithoutRepeat,
+ }));
+ return repeatArray;
+ }
+
+ function inheritedTextStyles() {
+ if (TARGET !== \\"reactNative\\") {
+ return {};
+ }
+
+ const styles = getReactNativeBlockStyles({
+ block: useBlock(),
+ context: props.context,
+ blockStyles: attributes().style,
+ });
+ return extractTextStyles(styles);
+ }
+
+ function childrenContext() {
+ return {
+ apiKey: props.context.apiKey,
+ state: props.context.state,
+ content: props.context.content,
+ context: props.context.context,
+ registeredComponents: props.context.registeredComponents,
+ inheritedStyles: inheritedTextStyles(),
+ };
+ }
+
+ function renderComponentTag() {
+ if (TARGET === \\"reactNative\\") {
+ return RenderComponentWithContext;
+ } else if (TARGET === \\"vue3\\") {
+ // vue3 expects a string for the component tag
+ return \\"RenderComponent\\";
+ } else {
+ return RenderComponent;
+ }
+ }
+
+ const [componentInfo, setComponentInfo] = useState(() => null);
+
+ const RenderComponentTagRef = renderComponentTag();
+ const TagRef = tag();
+
+ return (
+ <>
+ {shouldWrap() ? (
+ <>
+ {isEmptyHtmlElement(tag()) ? (
+ <>
+
+ >
+ ) : null}
+ {!isEmptyHtmlElement(tag()) && repeatItemData() ? (
+ <>
+ {repeatItemData()?.map((data, index) => (
+
+ ))}
+ >
+ ) : null}
+ {!isEmptyHtmlElement(tag()) && !repeatItemData() ? (
+ <>
+
+
+
+ {childrenWithoutParentComponent()?.map((child) => (
+
+ ))}
+
+ {childrenWithoutParentComponent()?.map((child) => (
+
+ ))}
+
+ >
+ ) : null}
+ >
+ ) : (
+
+ )}
+ >
+ );
+}
+"
+`;
+
exports[`React > jsx > Javascript Test > renderContentExample 1`] = `
"import * as React from \\"react\\";
import { useContext, useEffect } from \\"react\\";
@@ -4994,6 +5256,280 @@ export default function OnUpdate(props: any) {
"
`;
+exports[`React > jsx > Typescript Test > renderBlock 1`] = `
+"import * as React from \\"react\\";
+import { useState } from \\"react\\";
+import type {
+ BuilderContextInterface,
+ RegisteredComponent,
+} from \\"../../context/types.js\\";
+import type { BuilderBlock } from \\"../../types/builder-block.js\\";
+import type { Nullable } from \\"../../types/typescript.js\\";
+import type { RenderComponentProps } from \\"./render-component\\";
+import type { RepeatData } from \\"./types.js\\";
+export type RenderBlockProps = {
+ block: BuilderBlock;
+ context: BuilderContextInterface;
+};
+import { getBlockActions } from \\"../../functions/get-block-actions.js\\";
+import { getBlockComponentOptions } from \\"../../functions/get-block-component-options.js\\";
+import { getBlockProperties } from \\"../../functions/get-block-properties.js\\";
+import { getBlockTag } from \\"../../functions/get-block-tag.js\\";
+import { getProcessedBlock } from \\"../../functions/get-processed-block.js\\";
+import { evaluate } from \\"../../functions/evaluate.js\\";
+import BlockStyles from \\"./block-styles.lite\\";
+import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\";
+import RenderRepeatedBlock from \\"./render-repeated-block.lite\\";
+import { TARGET } from \\"../../constants/target.js\\";
+import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\";
+import RenderComponentWithContext from \\"./render-component-with-context.lite\\";
+import RenderComponent from \\"./render-component.lite\\";
+import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\";
+
+export default function RenderBlock(props: RenderBlockProps) {
+ function component() {
+ const componentName = getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: false,
+ }).component?.name;
+
+ if (!componentName) {
+ return null;
+ }
+
+ const ref = props.context.registeredComponents[componentName];
+
+ if (!ref) {
+ // TODO: Public doc page with more info about this message
+ console.warn(\`
+ Could not find a registered component named \\"\${componentName}\\".
+ If you registered it, is the file that registered it imported by the file that needs to render it?\`);
+ return undefined;
+ } else {
+ return ref;
+ }
+ }
+
+ function tag() {
+ return getBlockTag(useBlock());
+ }
+
+ function useBlock() {
+ return repeatItemData()
+ ? props.block
+ : getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: true,
+ });
+ }
+
+ function actions() {
+ return getBlockActions({
+ block: useBlock(),
+ state: props.context.state,
+ context: props.context.context,
+ });
+ }
+
+ function attributes() {
+ const blockProperties = getBlockProperties(useBlock());
+ return {
+ ...blockProperties,
+ ...(TARGET === \\"reactNative\\"
+ ? {
+ style: getReactNativeBlockStyles({
+ block: useBlock(),
+ context: props.context,
+ blockStyles: blockProperties.style,
+ }),
+ }
+ : {}),
+ };
+ }
+
+ function shouldWrap() {
+ return !component?.()?.noWrap;
+ }
+
+ function renderComponentProps() {
+ return {
+ blockChildren: useChildren(),
+ componentRef: component?.()?.component,
+ componentOptions: {
+ ...getBlockComponentOptions(useBlock()),
+
+ /**
+ * These attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then
+ * they are provided to the component itself directly.
+ */
+ ...(shouldWrap()
+ ? {}
+ : {
+ attributes: { ...attributes(), ...actions() },
+ }),
+ customBreakpoints: childrenContext?.()?.content?.meta?.breakpoints,
+ },
+ context: childrenContext(),
+ };
+ }
+
+ function useChildren() {
+ // TO-DO: When should \`canHaveChildren\` dictate rendering?
+ // This is currently commented out because some Builder components (e.g. Box) do not have \`canHaveChildren: true\`,
+ // but still receive and need to render children.
+ // return state.componentInfo?.canHaveChildren ? useBlock().children : [];
+ return useBlock().children ?? [];
+ }
+
+ function childrenWithoutParentComponent() {
+ /**
+ * When there is no \`componentRef\`, there might still be children that need to be rendered. In this case,
+ * we render them outside of \`componentRef\`.
+ * NOTE: We make sure not to render this if \`repeatItemData\` is non-null, because that means we are rendering an array of
+ * blocks, and the children will be repeated within those blocks.
+ */
+ const shouldRenderChildrenOutsideRef =
+ !component?.()?.component && !repeatItemData();
+ return shouldRenderChildrenOutsideRef ? useChildren() : [];
+ }
+
+ function repeatItemData() {
+ /**
+ * we don't use \`useBlock()\` here because the processing done within its logic includes evaluating the block's bindings,
+ * which will not work if there is a repeat.
+ */
+ const { repeat, ...blockWithoutRepeat } = props.block;
+
+ if (!repeat?.collection) {
+ return undefined;
+ }
+
+ const itemsArray = evaluate({
+ code: repeat.collection,
+ state: props.context.state,
+ context: props.context.context,
+ });
+
+ if (!Array.isArray(itemsArray)) {
+ return undefined;
+ }
+
+ const collectionName = repeat.collection.split(\\".\\").pop();
+ const itemNameToUse =
+ repeat.itemName || (collectionName ? collectionName + \\"Item\\" : \\"item\\");
+ const repeatArray = itemsArray.map((item, index) => ({
+ context: {
+ ...props.context,
+ state: {
+ ...props.context.state,
+ $index: index,
+ $item: item,
+ [itemNameToUse]: item,
+ [\`$\${itemNameToUse}Index\`]: index,
+ },
+ },
+ block: blockWithoutRepeat,
+ }));
+ return repeatArray;
+ }
+
+ function inheritedTextStyles() {
+ if (TARGET !== \\"reactNative\\") {
+ return {};
+ }
+
+ const styles = getReactNativeBlockStyles({
+ block: useBlock(),
+ context: props.context,
+ blockStyles: attributes().style,
+ });
+ return extractTextStyles(styles);
+ }
+
+ function childrenContext() {
+ return {
+ apiKey: props.context.apiKey,
+ state: props.context.state,
+ content: props.context.content,
+ context: props.context.context,
+ registeredComponents: props.context.registeredComponents,
+ inheritedStyles: inheritedTextStyles(),
+ };
+ }
+
+ function renderComponentTag() {
+ if (TARGET === \\"reactNative\\") {
+ return RenderComponentWithContext;
+ } else if (TARGET === \\"vue3\\") {
+ // vue3 expects a string for the component tag
+ return \\"RenderComponent\\";
+ } else {
+ return RenderComponent;
+ }
+ }
+
+ const [componentInfo, setComponentInfo] = useState(() => null);
+
+ const RenderComponentTagRef = renderComponentTag();
+ const TagRef = tag();
+
+ return (
+ <>
+ {shouldWrap() ? (
+ <>
+ {isEmptyHtmlElement(tag()) ? (
+ <>
+
+ >
+ ) : null}
+ {!isEmptyHtmlElement(tag()) && repeatItemData() ? (
+ <>
+ {repeatItemData()?.map((data, index) => (
+
+ ))}
+ >
+ ) : null}
+ {!isEmptyHtmlElement(tag()) && !repeatItemData() ? (
+ <>
+
+
+
+ {childrenWithoutParentComponent()?.map((child) => (
+
+ ))}
+
+ {childrenWithoutParentComponent()?.map((child) => (
+
+ ))}
+
+ >
+ ) : null}
+ >
+ ) : (
+
+ )}
+ >
+ );
+}
+"
+`;
+
exports[`React > jsx > Typescript Test > renderContentExample 1`] = `
"import * as React from \\"react\\";
import { useContext, useEffect } from \\"react\\";
diff --git a/packages/core/src/__tests__/__snapshots__/rsc.test.ts.snap b/packages/core/src/__tests__/__snapshots__/rsc.test.ts.snap
index 1bccc27915..7b9c3f4dc9 100644
--- a/packages/core/src/__tests__/__snapshots__/rsc.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/rsc.test.ts.snap
@@ -1985,6 +1985,267 @@ export default function OnUpdate(props) {
"
`;
+exports[`RSC > jsx > Javascript Test > renderBlock 1`] = `
+"import * as React from \\"react\\";
+import { getBlockActions } from \\"../../functions/get-block-actions.js\\";
+import { getBlockComponentOptions } from \\"../../functions/get-block-component-options.js\\";
+import { getBlockProperties } from \\"../../functions/get-block-properties.js\\";
+import { getBlockTag } from \\"../../functions/get-block-tag.js\\";
+import { getProcessedBlock } from \\"../../functions/get-processed-block.js\\";
+import { evaluate } from \\"../../functions/evaluate.js\\";
+import BlockStyles from \\"./block-styles.lite\\";
+import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\";
+import RenderRepeatedBlock from \\"./render-repeated-block.lite\\";
+import { TARGET } from \\"../../constants/target.js\\";
+import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\";
+import RenderComponentWithContext from \\"./render-component-with-context.lite\\";
+import RenderComponent from \\"./render-component.lite\\";
+import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\";
+
+export default function RenderBlock(props) {
+ const _context = { ...props[\\"_context\\"] };
+
+ const state = {
+ get component() {
+ const componentName = getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: false,
+ }).component?.name;
+
+ if (!componentName) {
+ return null;
+ }
+
+ const ref = props.context.registeredComponents[componentName];
+
+ if (!ref) {
+ // TODO: Public doc page with more info about this message
+ console.warn(\`
+ Could not find a registered component named \\"\${componentName}\\".
+ If you registered it, is the file that registered it imported by the file that needs to render it?\`);
+ return undefined;
+ } else {
+ return ref;
+ }
+ },
+ get tag() {
+ return getBlockTag(state.useBlock);
+ },
+ get useBlock() {
+ return state.repeatItemData
+ ? props.block
+ : getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: true,
+ });
+ },
+ get actions() {
+ return getBlockActions({
+ block: state.useBlock,
+ state: props.context.state,
+ context: props.context.context,
+ });
+ },
+ get attributes() {
+ const blockProperties = getBlockProperties(state.useBlock);
+ return {
+ ...blockProperties,
+ ...(TARGET === \\"reactNative\\"
+ ? {
+ style: getReactNativeBlockStyles({
+ block: state.useBlock,
+ context: props.context,
+ blockStyles: blockProperties.style,
+ }),
+ }
+ : {}),
+ };
+ },
+ get shouldWrap() {
+ return !state.component?.noWrap;
+ },
+ get renderComponentProps() {
+ return {
+ blockChildren: state.useChildren,
+ componentRef: state.component?.component,
+ componentOptions: {
+ ...getBlockComponentOptions(state.useBlock),
+
+ /**
+ * These attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then
+ * they are provided to the component itself directly.
+ */
+ ...(state.shouldWrap
+ ? {}
+ : {
+ attributes: { ...state.attributes, ...state.actions },
+ }),
+ customBreakpoints: state.childrenContext?.content?.meta?.breakpoints,
+ },
+ context: state.childrenContext,
+ };
+ },
+ get useChildren() {
+ // TO-DO: When should \`canHaveChildren\` dictate rendering?
+ // This is currently commented out because some Builder components (e.g. Box) do not have \`canHaveChildren: true\`,
+ // but still receive and need to render children.
+ // return state.componentInfo?.canHaveChildren ? state.useBlock.children : [];
+ return state.useBlock.children ?? [];
+ },
+ get childrenWithoutParentComponent() {
+ /**
+ * When there is no \`componentRef\`, there might still be children that need to be rendered. In this case,
+ * we render them outside of \`componentRef\`.
+ * NOTE: We make sure not to render this if \`repeatItemData\` is non-null, because that means we are rendering an array of
+ * blocks, and the children will be repeated within those blocks.
+ */
+ const shouldRenderChildrenOutsideRef =
+ !state.component?.component && !state.repeatItemData;
+ return shouldRenderChildrenOutsideRef ? state.useChildren : [];
+ },
+ get repeatItemData() {
+ /**
+ * we don't use \`state.useBlock\` here because the processing done within its logic includes evaluating the block's bindings,
+ * which will not work if there is a repeat.
+ */
+ const { repeat, ...blockWithoutRepeat } = props.block;
+
+ if (!repeat?.collection) {
+ return undefined;
+ }
+
+ const itemsArray = evaluate({
+ code: repeat.collection,
+ state: props.context.state,
+ context: props.context.context,
+ });
+
+ if (!Array.isArray(itemsArray)) {
+ return undefined;
+ }
+
+ const collectionName = repeat.collection.split(\\".\\").pop();
+ const itemNameToUse =
+ repeat.itemName || (collectionName ? collectionName + \\"Item\\" : \\"item\\");
+ const repeatArray = itemsArray.map((item, index) => ({
+ context: {
+ ...props.context,
+ state: {
+ ...props.context.state,
+ $index: index,
+ $item: item,
+ [itemNameToUse]: item,
+ [\`$\${itemNameToUse}Index\`]: index,
+ },
+ },
+ block: blockWithoutRepeat,
+ }));
+ return repeatArray;
+ },
+ get inheritedTextStyles() {
+ if (TARGET !== \\"reactNative\\") {
+ return {};
+ }
+
+ const styles = getReactNativeBlockStyles({
+ block: state.useBlock,
+ context: props.context,
+ blockStyles: state.attributes.style,
+ });
+ return extractTextStyles(styles);
+ },
+ get childrenContext() {
+ return {
+ apiKey: props.context.apiKey,
+ state: props.context.state,
+ content: props.context.content,
+ context: props.context.context,
+ registeredComponents: props.context.registeredComponents,
+ inheritedStyles: state.inheritedTextStyles,
+ };
+ },
+ get renderComponentTag() {
+ if (TARGET === \\"reactNative\\") {
+ return RenderComponentWithContext;
+ } else if (TARGET === \\"vue3\\") {
+ // vue3 expects a string for the component tag
+ return \\"RenderComponent\\";
+ } else {
+ return RenderComponent;
+ }
+ },
+ componentInfo: null,
+ };
+
+ const RenderComponentTagRef = state.renderComponentTag;
+ const TagRef = state.tag;
+
+ return (
+ <>
+ {state.shouldWrap ? (
+ <>
+ {isEmptyHtmlElement(state.tag) ? (
+ <>
+
+ >
+ ) : null}
+ {!isEmptyHtmlElement(state.tag) && state.repeatItemData ? (
+ <>
+ {state.repeatItemData?.map((data, index) => (
+
+ ))}
+ >
+ ) : null}
+ {!isEmptyHtmlElement(state.tag) && !state.repeatItemData ? (
+ <>
+
+
+
+ {state.childrenWithoutParentComponent?.map((child) => (
+
+ ))}
+
+ {state.childrenWithoutParentComponent?.map((child) => (
+
+ ))}
+
+ >
+ ) : null}
+ >
+ ) : (
+
+ )}
+ >
+ );
+}
+"
+`;
+
exports[`RSC > jsx > Javascript Test > renderContentExample 1`] = `
"import * as React from \\"react\\";
import {
@@ -4652,6 +4913,279 @@ export default function OnUpdate(props: any) {
"
`;
+exports[`RSC > jsx > Typescript Test > renderBlock 1`] = `
+"import * as React from \\"react\\";
+import type {
+ BuilderContextInterface,
+ RegisteredComponent,
+} from \\"../../context/types.js\\";
+import type { BuilderBlock } from \\"../../types/builder-block.js\\";
+import type { Nullable } from \\"../../types/typescript.js\\";
+import type { RenderComponentProps } from \\"./render-component\\";
+import type { RepeatData } from \\"./types.js\\";
+export type RenderBlockProps = {
+ block: BuilderBlock;
+ context: BuilderContextInterface;
+};
+import { getBlockActions } from \\"../../functions/get-block-actions.js\\";
+import { getBlockComponentOptions } from \\"../../functions/get-block-component-options.js\\";
+import { getBlockProperties } from \\"../../functions/get-block-properties.js\\";
+import { getBlockTag } from \\"../../functions/get-block-tag.js\\";
+import { getProcessedBlock } from \\"../../functions/get-processed-block.js\\";
+import { evaluate } from \\"../../functions/evaluate.js\\";
+import BlockStyles from \\"./block-styles.lite\\";
+import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\";
+import RenderRepeatedBlock from \\"./render-repeated-block.lite\\";
+import { TARGET } from \\"../../constants/target.js\\";
+import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\";
+import RenderComponentWithContext from \\"./render-component-with-context.lite\\";
+import RenderComponent from \\"./render-component.lite\\";
+import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\";
+
+export default function RenderBlock(props: RenderBlockProps) {
+ const _context = { ...props[\\"_context\\"] };
+
+ const state = {
+ get component() {
+ const componentName = getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: false,
+ }).component?.name;
+
+ if (!componentName) {
+ return null;
+ }
+
+ const ref = props.context.registeredComponents[componentName];
+
+ if (!ref) {
+ // TODO: Public doc page with more info about this message
+ console.warn(\`
+ Could not find a registered component named \\"\${componentName}\\".
+ If you registered it, is the file that registered it imported by the file that needs to render it?\`);
+ return undefined;
+ } else {
+ return ref;
+ }
+ },
+ get tag() {
+ return getBlockTag(state.useBlock);
+ },
+ get useBlock() {
+ return state.repeatItemData
+ ? props.block
+ : getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: true,
+ });
+ },
+ get actions() {
+ return getBlockActions({
+ block: state.useBlock,
+ state: props.context.state,
+ context: props.context.context,
+ });
+ },
+ get attributes() {
+ const blockProperties = getBlockProperties(state.useBlock);
+ return {
+ ...blockProperties,
+ ...(TARGET === \\"reactNative\\"
+ ? {
+ style: getReactNativeBlockStyles({
+ block: state.useBlock,
+ context: props.context,
+ blockStyles: blockProperties.style,
+ }),
+ }
+ : {}),
+ };
+ },
+ get shouldWrap() {
+ return !state.component?.noWrap;
+ },
+ get renderComponentProps() {
+ return {
+ blockChildren: state.useChildren,
+ componentRef: state.component?.component,
+ componentOptions: {
+ ...getBlockComponentOptions(state.useBlock),
+
+ /**
+ * These attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then
+ * they are provided to the component itself directly.
+ */
+ ...(state.shouldWrap
+ ? {}
+ : {
+ attributes: { ...state.attributes, ...state.actions },
+ }),
+ customBreakpoints: state.childrenContext?.content?.meta?.breakpoints,
+ },
+ context: state.childrenContext,
+ };
+ },
+ get useChildren() {
+ // TO-DO: When should \`canHaveChildren\` dictate rendering?
+ // This is currently commented out because some Builder components (e.g. Box) do not have \`canHaveChildren: true\`,
+ // but still receive and need to render children.
+ // return state.componentInfo?.canHaveChildren ? state.useBlock.children : [];
+ return state.useBlock.children ?? [];
+ },
+ get childrenWithoutParentComponent() {
+ /**
+ * When there is no \`componentRef\`, there might still be children that need to be rendered. In this case,
+ * we render them outside of \`componentRef\`.
+ * NOTE: We make sure not to render this if \`repeatItemData\` is non-null, because that means we are rendering an array of
+ * blocks, and the children will be repeated within those blocks.
+ */
+ const shouldRenderChildrenOutsideRef =
+ !state.component?.component && !state.repeatItemData;
+ return shouldRenderChildrenOutsideRef ? state.useChildren : [];
+ },
+ get repeatItemData() {
+ /**
+ * we don't use \`state.useBlock\` here because the processing done within its logic includes evaluating the block's bindings,
+ * which will not work if there is a repeat.
+ */
+ const { repeat, ...blockWithoutRepeat } = props.block;
+
+ if (!repeat?.collection) {
+ return undefined;
+ }
+
+ const itemsArray = evaluate({
+ code: repeat.collection,
+ state: props.context.state,
+ context: props.context.context,
+ });
+
+ if (!Array.isArray(itemsArray)) {
+ return undefined;
+ }
+
+ const collectionName = repeat.collection.split(\\".\\").pop();
+ const itemNameToUse =
+ repeat.itemName || (collectionName ? collectionName + \\"Item\\" : \\"item\\");
+ const repeatArray = itemsArray.map((item, index) => ({
+ context: {
+ ...props.context,
+ state: {
+ ...props.context.state,
+ $index: index,
+ $item: item,
+ [itemNameToUse]: item,
+ [\`$\${itemNameToUse}Index\`]: index,
+ },
+ },
+ block: blockWithoutRepeat,
+ }));
+ return repeatArray;
+ },
+ get inheritedTextStyles() {
+ if (TARGET !== \\"reactNative\\") {
+ return {};
+ }
+
+ const styles = getReactNativeBlockStyles({
+ block: state.useBlock,
+ context: props.context,
+ blockStyles: state.attributes.style,
+ });
+ return extractTextStyles(styles);
+ },
+ get childrenContext() {
+ return {
+ apiKey: props.context.apiKey,
+ state: props.context.state,
+ content: props.context.content,
+ context: props.context.context,
+ registeredComponents: props.context.registeredComponents,
+ inheritedStyles: state.inheritedTextStyles,
+ };
+ },
+ get renderComponentTag() {
+ if (TARGET === \\"reactNative\\") {
+ return RenderComponentWithContext;
+ } else if (TARGET === \\"vue3\\") {
+ // vue3 expects a string for the component tag
+ return \\"RenderComponent\\";
+ } else {
+ return RenderComponent;
+ }
+ },
+ componentInfo: null,
+ };
+
+ const RenderComponentTagRef = state.renderComponentTag;
+ const TagRef = state.tag;
+
+ return (
+ <>
+ {state.shouldWrap ? (
+ <>
+ {isEmptyHtmlElement(state.tag) ? (
+ <>
+
+ >
+ ) : null}
+ {!isEmptyHtmlElement(state.tag) && state.repeatItemData ? (
+ <>
+ {state.repeatItemData?.map((data, index) => (
+
+ ))}
+ >
+ ) : null}
+ {!isEmptyHtmlElement(state.tag) && !state.repeatItemData ? (
+ <>
+
+
+
+ {state.childrenWithoutParentComponent?.map((child) => (
+
+ ))}
+
+ {state.childrenWithoutParentComponent?.map((child) => (
+
+ ))}
+
+ >
+ ) : null}
+ >
+ ) : (
+
+ )}
+ >
+ );
+}
+"
+`;
+
exports[`RSC > jsx > Typescript Test > renderContentExample 1`] = `
"import * as React from \\"react\\";
diff --git a/packages/core/src/__tests__/__snapshots__/solid.test.ts.snap b/packages/core/src/__tests__/__snapshots__/solid.test.ts.snap
index 73db6ac351..4294df4388 100644
--- a/packages/core/src/__tests__/__snapshots__/solid.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/solid.test.ts.snap
@@ -4661,6 +4661,562 @@ export default OnUpdate;
"
`;
+exports[`Solid > jsx > Javascript Test > renderBlock 1`] = `
+"import { Show, For, createSignal } from \\"solid-js\\";
+import { Dynamic } from \\"solid-js/web\\";
+
+import { getBlockActions } from \\"../../functions/get-block-actions.js\\";
+import { getBlockComponentOptions } from \\"../../functions/get-block-component-options.js\\";
+import { getBlockProperties } from \\"../../functions/get-block-properties.js\\";
+import { getBlockTag } from \\"../../functions/get-block-tag.js\\";
+import { getProcessedBlock } from \\"../../functions/get-processed-block.js\\";
+import { evaluate } from \\"../../functions/evaluate.js\\";
+import BlockStyles from \\"./block-styles.jsx\\";
+import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\";
+import RenderRepeatedBlock from \\"./render-repeated-block.jsx\\";
+import { TARGET } from \\"../../constants/target.js\\";
+import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\";
+import RenderComponentWithContext from \\"./render-component-with-context.jsx\\";
+import RenderComponent from \\"./render-component.jsx\\";
+import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\";
+
+function RenderBlock(props) {
+ function component() {
+ const componentName = getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: false,
+ }).component?.name;
+
+ if (!componentName) {
+ return null;
+ }
+
+ const ref = props.context.registeredComponents[componentName];
+
+ if (!ref) {
+ // TODO: Public doc page with more info about this message
+ console.warn(\`
+ Could not find a registered component named \\"\${componentName}\\".
+ If you registered it, is the file that registered it imported by the file that needs to render it?\`);
+ return undefined;
+ } else {
+ return ref;
+ }
+ }
+
+ function tag() {
+ return getBlockTag(useBlock());
+ }
+
+ function useBlock() {
+ return repeatItemData()
+ ? props.block
+ : getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: true,
+ });
+ }
+
+ function actions() {
+ return getBlockActions({
+ block: useBlock(),
+ state: props.context.state,
+ context: props.context.context,
+ });
+ }
+
+ function attributes() {
+ const blockProperties = getBlockProperties(useBlock());
+ return {
+ ...blockProperties,
+ ...(TARGET === \\"reactNative\\"
+ ? {
+ style: getReactNativeBlockStyles({
+ block: useBlock(),
+ context: props.context,
+ blockStyles: blockProperties.style,
+ }),
+ }
+ : {}),
+ };
+ }
+
+ function shouldWrap() {
+ return !component()?.noWrap;
+ }
+
+ function renderComponentProps() {
+ return {
+ blockChildren: useChildren(),
+ componentRef: component()?.component,
+ componentOptions: {
+ ...getBlockComponentOptions(useBlock()),
+
+ /**
+ * These attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then
+ * they are provided to the component itself directly.
+ */
+ ...(shouldWrap()
+ ? {}
+ : {
+ attributes: { ...attributes(), ...actions() },
+ }),
+ customBreakpoints: childrenContext()?.content?.meta?.breakpoints,
+ },
+ context: childrenContext(),
+ };
+ }
+
+ function useChildren() {
+ // TO-DO: When should \`canHaveChildren\` dictate rendering?
+ // This is currently commented out because some Builder components (e.g. Box) do not have \`canHaveChildren: true\`,
+ // but still receive and need to render children.
+ // return state.componentInfo?.canHaveChildren ? state.useBlock.children : [];
+ return useBlock().children ?? [];
+ }
+
+ function childrenWithoutParentComponent() {
+ /**
+ * When there is no \`componentRef\`, there might still be children that need to be rendered. In this case,
+ * we render them outside of \`componentRef\`.
+ * NOTE: We make sure not to render this if \`repeatItemData\` is non-null, because that means we are rendering an array of
+ * blocks, and the children will be repeated within those blocks.
+ */
+ const shouldRenderChildrenOutsideRef =
+ !component()?.component && !repeatItemData();
+ return shouldRenderChildrenOutsideRef ? useChildren() : [];
+ }
+
+ function repeatItemData() {
+ /**
+ * we don't use \`state.useBlock\` here because the processing done within its logic includes evaluating the block's bindings,
+ * which will not work if there is a repeat.
+ */
+ const { repeat, ...blockWithoutRepeat } = props.block;
+
+ if (!repeat?.collection) {
+ return undefined;
+ }
+
+ const itemsArray = evaluate({
+ code: repeat.collection,
+ state: props.context.state,
+ context: props.context.context,
+ });
+
+ if (!Array.isArray(itemsArray)) {
+ return undefined;
+ }
+
+ const collectionName = repeat.collection.split(\\".\\").pop();
+ const itemNameToUse =
+ repeat.itemName || (collectionName ? collectionName + \\"Item\\" : \\"item\\");
+ const repeatArray = itemsArray.map((item, index) => ({
+ context: {
+ ...props.context,
+ state: {
+ ...props.context.state,
+ $index: index,
+ $item: item,
+ [itemNameToUse]: item,
+ [\`$\${itemNameToUse}Index\`]: index,
+ },
+ },
+ block: blockWithoutRepeat,
+ }));
+ return repeatArray;
+ }
+
+ function inheritedTextStyles() {
+ if (TARGET !== \\"reactNative\\") {
+ return {};
+ }
+
+ const styles = getReactNativeBlockStyles({
+ block: useBlock(),
+ context: props.context,
+ blockStyles: attributes().style,
+ });
+ return extractTextStyles(styles);
+ }
+
+ function childrenContext() {
+ return {
+ apiKey: props.context.apiKey,
+ state: props.context.state,
+ content: props.context.content,
+ context: props.context.context,
+ registeredComponents: props.context.registeredComponents,
+ inheritedStyles: inheritedTextStyles(),
+ };
+ }
+
+ function renderComponentTag() {
+ if (TARGET === \\"reactNative\\") {
+ return RenderComponentWithContext;
+ } else if (TARGET === \\"vue3\\") {
+ // vue3 expects a string for the component tag
+ return \\"RenderComponent\\";
+ } else {
+ return RenderComponent;
+ }
+ }
+
+ return (
+
+ }
+ when={shouldWrap()}
+ >
+
+
+
+
+
+ {(data, _index) => {
+ const index = _index();
+ return (
+
+ );
+ }}
+
+
+
+
+
+
+ {(child, _index) => {
+ const index = _index();
+ return (
+
+ );
+ }}
+
+
+ {(child, _index) => {
+ const index = _index();
+ return (
+
+ );
+ }}
+
+
+
+
+ );
+}
+
+export default RenderBlock;
+"
+`;
+
+exports[`Solid > jsx > Javascript Test > renderBlock 2`] = `
+"import { Show, For, createSignal } from \\"solid-js\\";
+import { Dynamic } from \\"solid-js/web\\";
+
+import { css } from \\"solid-styled-components\\";
+
+import { getBlockActions } from \\"../../functions/get-block-actions.js\\";
+import { getBlockComponentOptions } from \\"../../functions/get-block-component-options.js\\";
+import { getBlockProperties } from \\"../../functions/get-block-properties.js\\";
+import { getBlockTag } from \\"../../functions/get-block-tag.js\\";
+import { getProcessedBlock } from \\"../../functions/get-processed-block.js\\";
+import { evaluate } from \\"../../functions/evaluate.js\\";
+import BlockStyles from \\"./block-styles.jsx\\";
+import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\";
+import RenderRepeatedBlock from \\"./render-repeated-block.jsx\\";
+import { TARGET } from \\"../../constants/target.js\\";
+import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\";
+import RenderComponentWithContext from \\"./render-component-with-context.jsx\\";
+import RenderComponent from \\"./render-component.jsx\\";
+import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\";
+
+function RenderBlock(props) {
+ function component() {
+ const componentName = getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: false,
+ }).component?.name;
+
+ if (!componentName) {
+ return null;
+ }
+
+ const ref = props.context.registeredComponents[componentName];
+
+ if (!ref) {
+ // TODO: Public doc page with more info about this message
+ console.warn(\`
+ Could not find a registered component named \\"\${componentName}\\".
+ If you registered it, is the file that registered it imported by the file that needs to render it?\`);
+ return undefined;
+ } else {
+ return ref;
+ }
+ }
+
+ function tag() {
+ return getBlockTag(useBlock());
+ }
+
+ function useBlock() {
+ return repeatItemData()
+ ? props.block
+ : getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: true,
+ });
+ }
+
+ function actions() {
+ return getBlockActions({
+ block: useBlock(),
+ state: props.context.state,
+ context: props.context.context,
+ });
+ }
+
+ function attributes() {
+ const blockProperties = getBlockProperties(useBlock());
+ return {
+ ...blockProperties,
+ ...(TARGET === \\"reactNative\\"
+ ? {
+ style: getReactNativeBlockStyles({
+ block: useBlock(),
+ context: props.context,
+ blockStyles: blockProperties.style,
+ }),
+ }
+ : {}),
+ };
+ }
+
+ function shouldWrap() {
+ return !component()?.noWrap;
+ }
+
+ function renderComponentProps() {
+ return {
+ blockChildren: useChildren(),
+ componentRef: component()?.component,
+ componentOptions: {
+ ...getBlockComponentOptions(useBlock()),
+
+ /**
+ * These attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then
+ * they are provided to the component itself directly.
+ */
+ ...(shouldWrap()
+ ? {}
+ : {
+ attributes: { ...attributes(), ...actions() },
+ }),
+ customBreakpoints: childrenContext()?.content?.meta?.breakpoints,
+ },
+ context: childrenContext(),
+ };
+ }
+
+ function useChildren() {
+ // TO-DO: When should \`canHaveChildren\` dictate rendering?
+ // This is currently commented out because some Builder components (e.g. Box) do not have \`canHaveChildren: true\`,
+ // but still receive and need to render children.
+ // return state.componentInfo?.canHaveChildren ? state.useBlock.children : [];
+ return useBlock().children ?? [];
+ }
+
+ function childrenWithoutParentComponent() {
+ /**
+ * When there is no \`componentRef\`, there might still be children that need to be rendered. In this case,
+ * we render them outside of \`componentRef\`.
+ * NOTE: We make sure not to render this if \`repeatItemData\` is non-null, because that means we are rendering an array of
+ * blocks, and the children will be repeated within those blocks.
+ */
+ const shouldRenderChildrenOutsideRef =
+ !component()?.component && !repeatItemData();
+ return shouldRenderChildrenOutsideRef ? useChildren() : [];
+ }
+
+ function repeatItemData() {
+ /**
+ * we don't use \`state.useBlock\` here because the processing done within its logic includes evaluating the block's bindings,
+ * which will not work if there is a repeat.
+ */
+ const { repeat, ...blockWithoutRepeat } = props.block;
+
+ if (!repeat?.collection) {
+ return undefined;
+ }
+
+ const itemsArray = evaluate({
+ code: repeat.collection,
+ state: props.context.state,
+ context: props.context.context,
+ });
+
+ if (!Array.isArray(itemsArray)) {
+ return undefined;
+ }
+
+ const collectionName = repeat.collection.split(\\".\\").pop();
+ const itemNameToUse =
+ repeat.itemName || (collectionName ? collectionName + \\"Item\\" : \\"item\\");
+ const repeatArray = itemsArray.map((item, index) => ({
+ context: {
+ ...props.context,
+ state: {
+ ...props.context.state,
+ $index: index,
+ $item: item,
+ [itemNameToUse]: item,
+ [\`$\${itemNameToUse}Index\`]: index,
+ },
+ },
+ block: blockWithoutRepeat,
+ }));
+ return repeatArray;
+ }
+
+ function inheritedTextStyles() {
+ if (TARGET !== \\"reactNative\\") {
+ return {};
+ }
+
+ const styles = getReactNativeBlockStyles({
+ block: useBlock(),
+ context: props.context,
+ blockStyles: attributes().style,
+ });
+ return extractTextStyles(styles);
+ }
+
+ function childrenContext() {
+ return {
+ apiKey: props.context.apiKey,
+ state: props.context.state,
+ content: props.context.content,
+ context: props.context.context,
+ registeredComponents: props.context.registeredComponents,
+ inheritedStyles: inheritedTextStyles(),
+ };
+ }
+
+ function renderComponentTag() {
+ if (TARGET === \\"reactNative\\") {
+ return RenderComponentWithContext;
+ } else if (TARGET === \\"vue3\\") {
+ // vue3 expects a string for the component tag
+ return \\"RenderComponent\\";
+ } else {
+ return RenderComponent;
+ }
+ }
+
+ return (
+ <>
+
+ }
+ when={shouldWrap()}
+ >
+
+
+
+
+
+ {(data, _index) => {
+ const index = _index();
+ return (
+
+ );
+ }}
+
+
+
+
+
+
+ {(child, _index) => {
+ const index = _index();
+ return (
+
+ );
+ }}
+
+
+ {(child, _index) => {
+ const index = _index();
+ return (
+
+ );
+ }}
+
+
+
+
+ >
+ );
+}
+
+export default RenderBlock;
+"
+`;
+
exports[`Solid > jsx > Javascript Test > renderContentExample 1`] = `
"import { onMount, on, createEffect } from \\"solid-js\\";
@@ -10004,6 +10560,562 @@ export default OnUpdate;
"
`;
+exports[`Solid > jsx > Typescript Test > renderBlock 1`] = `
+"import { Show, For, createSignal } from \\"solid-js\\";
+import { Dynamic } from \\"solid-js/web\\";
+
+import { getBlockActions } from \\"../../functions/get-block-actions.js\\";
+import { getBlockComponentOptions } from \\"../../functions/get-block-component-options.js\\";
+import { getBlockProperties } from \\"../../functions/get-block-properties.js\\";
+import { getBlockTag } from \\"../../functions/get-block-tag.js\\";
+import { getProcessedBlock } from \\"../../functions/get-processed-block.js\\";
+import { evaluate } from \\"../../functions/evaluate.js\\";
+import BlockStyles from \\"./block-styles.jsx\\";
+import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\";
+import RenderRepeatedBlock from \\"./render-repeated-block.jsx\\";
+import { TARGET } from \\"../../constants/target.js\\";
+import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\";
+import RenderComponentWithContext from \\"./render-component-with-context.jsx\\";
+import RenderComponent from \\"./render-component.jsx\\";
+import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\";
+
+function RenderBlock(props) {
+ function component() {
+ const componentName = getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: false,
+ }).component?.name;
+
+ if (!componentName) {
+ return null;
+ }
+
+ const ref = props.context.registeredComponents[componentName];
+
+ if (!ref) {
+ // TODO: Public doc page with more info about this message
+ console.warn(\`
+ Could not find a registered component named \\"\${componentName}\\".
+ If you registered it, is the file that registered it imported by the file that needs to render it?\`);
+ return undefined;
+ } else {
+ return ref;
+ }
+ }
+
+ function tag() {
+ return getBlockTag(useBlock());
+ }
+
+ function useBlock() {
+ return repeatItemData()
+ ? props.block
+ : getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: true,
+ });
+ }
+
+ function actions() {
+ return getBlockActions({
+ block: useBlock(),
+ state: props.context.state,
+ context: props.context.context,
+ });
+ }
+
+ function attributes() {
+ const blockProperties = getBlockProperties(useBlock());
+ return {
+ ...blockProperties,
+ ...(TARGET === \\"reactNative\\"
+ ? {
+ style: getReactNativeBlockStyles({
+ block: useBlock(),
+ context: props.context,
+ blockStyles: blockProperties.style,
+ }),
+ }
+ : {}),
+ };
+ }
+
+ function shouldWrap() {
+ return !component()?.noWrap;
+ }
+
+ function renderComponentProps() {
+ return {
+ blockChildren: useChildren(),
+ componentRef: component()?.component,
+ componentOptions: {
+ ...getBlockComponentOptions(useBlock()),
+
+ /**
+ * These attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then
+ * they are provided to the component itself directly.
+ */
+ ...(shouldWrap()
+ ? {}
+ : {
+ attributes: { ...attributes(), ...actions() },
+ }),
+ customBreakpoints: childrenContext()?.content?.meta?.breakpoints,
+ },
+ context: childrenContext(),
+ };
+ }
+
+ function useChildren() {
+ // TO-DO: When should \`canHaveChildren\` dictate rendering?
+ // This is currently commented out because some Builder components (e.g. Box) do not have \`canHaveChildren: true\`,
+ // but still receive and need to render children.
+ // return state.componentInfo?.canHaveChildren ? state.useBlock.children : [];
+ return useBlock().children ?? [];
+ }
+
+ function childrenWithoutParentComponent() {
+ /**
+ * When there is no \`componentRef\`, there might still be children that need to be rendered. In this case,
+ * we render them outside of \`componentRef\`.
+ * NOTE: We make sure not to render this if \`repeatItemData\` is non-null, because that means we are rendering an array of
+ * blocks, and the children will be repeated within those blocks.
+ */
+ const shouldRenderChildrenOutsideRef =
+ !component()?.component && !repeatItemData();
+ return shouldRenderChildrenOutsideRef ? useChildren() : [];
+ }
+
+ function repeatItemData() {
+ /**
+ * we don't use \`state.useBlock\` here because the processing done within its logic includes evaluating the block's bindings,
+ * which will not work if there is a repeat.
+ */
+ const { repeat, ...blockWithoutRepeat } = props.block;
+
+ if (!repeat?.collection) {
+ return undefined;
+ }
+
+ const itemsArray = evaluate({
+ code: repeat.collection,
+ state: props.context.state,
+ context: props.context.context,
+ });
+
+ if (!Array.isArray(itemsArray)) {
+ return undefined;
+ }
+
+ const collectionName = repeat.collection.split(\\".\\").pop();
+ const itemNameToUse =
+ repeat.itemName || (collectionName ? collectionName + \\"Item\\" : \\"item\\");
+ const repeatArray = itemsArray.map((item, index) => ({
+ context: {
+ ...props.context,
+ state: {
+ ...props.context.state,
+ $index: index,
+ $item: item,
+ [itemNameToUse]: item,
+ [\`$\${itemNameToUse}Index\`]: index,
+ },
+ },
+ block: blockWithoutRepeat,
+ }));
+ return repeatArray;
+ }
+
+ function inheritedTextStyles() {
+ if (TARGET !== \\"reactNative\\") {
+ return {};
+ }
+
+ const styles = getReactNativeBlockStyles({
+ block: useBlock(),
+ context: props.context,
+ blockStyles: attributes().style,
+ });
+ return extractTextStyles(styles);
+ }
+
+ function childrenContext() {
+ return {
+ apiKey: props.context.apiKey,
+ state: props.context.state,
+ content: props.context.content,
+ context: props.context.context,
+ registeredComponents: props.context.registeredComponents,
+ inheritedStyles: inheritedTextStyles(),
+ };
+ }
+
+ function renderComponentTag() {
+ if (TARGET === \\"reactNative\\") {
+ return RenderComponentWithContext;
+ } else if (TARGET === \\"vue3\\") {
+ // vue3 expects a string for the component tag
+ return \\"RenderComponent\\";
+ } else {
+ return RenderComponent;
+ }
+ }
+
+ return (
+
+ }
+ when={shouldWrap()}
+ >
+
+
+
+
+
+ {(data, _index) => {
+ const index = _index();
+ return (
+
+ );
+ }}
+
+
+
+
+
+
+ {(child, _index) => {
+ const index = _index();
+ return (
+
+ );
+ }}
+
+
+ {(child, _index) => {
+ const index = _index();
+ return (
+
+ );
+ }}
+
+
+
+
+ );
+}
+
+export default RenderBlock;
+"
+`;
+
+exports[`Solid > jsx > Typescript Test > renderBlock 2`] = `
+"import { Show, For, createSignal } from \\"solid-js\\";
+import { Dynamic } from \\"solid-js/web\\";
+
+import { css } from \\"solid-styled-components\\";
+
+import { getBlockActions } from \\"../../functions/get-block-actions.js\\";
+import { getBlockComponentOptions } from \\"../../functions/get-block-component-options.js\\";
+import { getBlockProperties } from \\"../../functions/get-block-properties.js\\";
+import { getBlockTag } from \\"../../functions/get-block-tag.js\\";
+import { getProcessedBlock } from \\"../../functions/get-processed-block.js\\";
+import { evaluate } from \\"../../functions/evaluate.js\\";
+import BlockStyles from \\"./block-styles.jsx\\";
+import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\";
+import RenderRepeatedBlock from \\"./render-repeated-block.jsx\\";
+import { TARGET } from \\"../../constants/target.js\\";
+import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\";
+import RenderComponentWithContext from \\"./render-component-with-context.jsx\\";
+import RenderComponent from \\"./render-component.jsx\\";
+import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\";
+
+function RenderBlock(props) {
+ function component() {
+ const componentName = getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: false,
+ }).component?.name;
+
+ if (!componentName) {
+ return null;
+ }
+
+ const ref = props.context.registeredComponents[componentName];
+
+ if (!ref) {
+ // TODO: Public doc page with more info about this message
+ console.warn(\`
+ Could not find a registered component named \\"\${componentName}\\".
+ If you registered it, is the file that registered it imported by the file that needs to render it?\`);
+ return undefined;
+ } else {
+ return ref;
+ }
+ }
+
+ function tag() {
+ return getBlockTag(useBlock());
+ }
+
+ function useBlock() {
+ return repeatItemData()
+ ? props.block
+ : getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: true,
+ });
+ }
+
+ function actions() {
+ return getBlockActions({
+ block: useBlock(),
+ state: props.context.state,
+ context: props.context.context,
+ });
+ }
+
+ function attributes() {
+ const blockProperties = getBlockProperties(useBlock());
+ return {
+ ...blockProperties,
+ ...(TARGET === \\"reactNative\\"
+ ? {
+ style: getReactNativeBlockStyles({
+ block: useBlock(),
+ context: props.context,
+ blockStyles: blockProperties.style,
+ }),
+ }
+ : {}),
+ };
+ }
+
+ function shouldWrap() {
+ return !component()?.noWrap;
+ }
+
+ function renderComponentProps() {
+ return {
+ blockChildren: useChildren(),
+ componentRef: component()?.component,
+ componentOptions: {
+ ...getBlockComponentOptions(useBlock()),
+
+ /**
+ * These attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then
+ * they are provided to the component itself directly.
+ */
+ ...(shouldWrap()
+ ? {}
+ : {
+ attributes: { ...attributes(), ...actions() },
+ }),
+ customBreakpoints: childrenContext()?.content?.meta?.breakpoints,
+ },
+ context: childrenContext(),
+ };
+ }
+
+ function useChildren() {
+ // TO-DO: When should \`canHaveChildren\` dictate rendering?
+ // This is currently commented out because some Builder components (e.g. Box) do not have \`canHaveChildren: true\`,
+ // but still receive and need to render children.
+ // return state.componentInfo?.canHaveChildren ? state.useBlock.children : [];
+ return useBlock().children ?? [];
+ }
+
+ function childrenWithoutParentComponent() {
+ /**
+ * When there is no \`componentRef\`, there might still be children that need to be rendered. In this case,
+ * we render them outside of \`componentRef\`.
+ * NOTE: We make sure not to render this if \`repeatItemData\` is non-null, because that means we are rendering an array of
+ * blocks, and the children will be repeated within those blocks.
+ */
+ const shouldRenderChildrenOutsideRef =
+ !component()?.component && !repeatItemData();
+ return shouldRenderChildrenOutsideRef ? useChildren() : [];
+ }
+
+ function repeatItemData() {
+ /**
+ * we don't use \`state.useBlock\` here because the processing done within its logic includes evaluating the block's bindings,
+ * which will not work if there is a repeat.
+ */
+ const { repeat, ...blockWithoutRepeat } = props.block;
+
+ if (!repeat?.collection) {
+ return undefined;
+ }
+
+ const itemsArray = evaluate({
+ code: repeat.collection,
+ state: props.context.state,
+ context: props.context.context,
+ });
+
+ if (!Array.isArray(itemsArray)) {
+ return undefined;
+ }
+
+ const collectionName = repeat.collection.split(\\".\\").pop();
+ const itemNameToUse =
+ repeat.itemName || (collectionName ? collectionName + \\"Item\\" : \\"item\\");
+ const repeatArray = itemsArray.map((item, index) => ({
+ context: {
+ ...props.context,
+ state: {
+ ...props.context.state,
+ $index: index,
+ $item: item,
+ [itemNameToUse]: item,
+ [\`$\${itemNameToUse}Index\`]: index,
+ },
+ },
+ block: blockWithoutRepeat,
+ }));
+ return repeatArray;
+ }
+
+ function inheritedTextStyles() {
+ if (TARGET !== \\"reactNative\\") {
+ return {};
+ }
+
+ const styles = getReactNativeBlockStyles({
+ block: useBlock(),
+ context: props.context,
+ blockStyles: attributes().style,
+ });
+ return extractTextStyles(styles);
+ }
+
+ function childrenContext() {
+ return {
+ apiKey: props.context.apiKey,
+ state: props.context.state,
+ content: props.context.content,
+ context: props.context.context,
+ registeredComponents: props.context.registeredComponents,
+ inheritedStyles: inheritedTextStyles(),
+ };
+ }
+
+ function renderComponentTag() {
+ if (TARGET === \\"reactNative\\") {
+ return RenderComponentWithContext;
+ } else if (TARGET === \\"vue3\\") {
+ // vue3 expects a string for the component tag
+ return \\"RenderComponent\\";
+ } else {
+ return RenderComponent;
+ }
+ }
+
+ return (
+ <>
+
+ }
+ when={shouldWrap()}
+ >
+
+
+
+
+
+ {(data, _index) => {
+ const index = _index();
+ return (
+
+ );
+ }}
+
+
+
+
+
+
+ {(child, _index) => {
+ const index = _index();
+ return (
+
+ );
+ }}
+
+
+ {(child, _index) => {
+ const index = _index();
+ return (
+
+ );
+ }}
+
+
+
+
+ >
+ );
+}
+
+export default RenderBlock;
+"
+`;
+
exports[`Solid > jsx > Typescript Test > renderContentExample 1`] = `
"import { onMount, on, createEffect } from \\"solid-js\\";
diff --git a/packages/core/src/__tests__/__snapshots__/stencil.test.ts.snap b/packages/core/src/__tests__/__snapshots__/stencil.test.ts.snap
index 64fef2f4a2..515882107e 100644
--- a/packages/core/src/__tests__/__snapshots__/stencil.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/stencil.test.ts.snap
@@ -2436,6 +2436,17 @@ export default class OnUpdate {
"
`;
+exports[`Stencil > jsx > Javascript Test > renderBlock 1`] = `
+"':' expected. (205:15)
+ 203 | return (
+ 204 |
+> 205 | {this.shouldWrap ? (
+ | ^
+ 206 | <>{isEmptyHtmlElement(this.tag) ? (
+ 207 | <>>
+ 208 | ) : null}"
+`;
+
exports[`Stencil > jsx > Javascript Test > renderContentExample 1`] = `
"import {
sendComponentsToVisualEditor,
@@ -5218,6 +5229,17 @@ export default class OnUpdate {
"
`;
+exports[`Stencil > jsx > Typescript Test > renderBlock 1`] = `
+"':' expected. (205:15)
+ 203 | return (
+ 204 |
+> 205 | {this.shouldWrap ? (
+ | ^
+ 206 | <>{isEmptyHtmlElement(this.tag) ? (
+ 207 | <>>
+ 208 | ) : null}"
+`;
+
exports[`Stencil > jsx > Typescript Test > renderContentExample 1`] = `
"import {
sendComponentsToVisualEditor,
diff --git a/packages/core/src/__tests__/__snapshots__/svelte.test.ts.snap b/packages/core/src/__tests__/__snapshots__/svelte.test.ts.snap
index 374ea64b13..50bc6bbec5 100644
--- a/packages/core/src/__tests__/__snapshots__/svelte.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/svelte.test.ts.snap
@@ -1838,6 +1838,244 @@ exports[`Svelte > jsx > Javascript Test > referencingFunInsideHook 1`] = `
"
`;
+exports[`Svelte > jsx > Javascript Test > renderBlock 1`] = `
+"
+
+{#if shouldWrap()}
+ {#if isEmptyHtmlElement(tag())}
+
+ {/if}
+
+ {#if !isEmptyHtmlElement(tag()) && repeatItemData()}
+ {#each repeatItemData() as data, index (index)}
+
+ {/each}
+ {/if}
+
+ {#if !isEmptyHtmlElement(tag()) && !repeatItemData()}
+
+
+
+ {#each childrenWithoutParentComponent() as child (\\"render-block-\\" + child.id)}
+
+ {/each}
+
+ {#each childrenWithoutParentComponent() as child (\\"block-style-\\" + child.id)}
+
+ {/each}
+
+ {/if}
+{:else}
+
+{/if}"
+`;
+
exports[`Svelte > jsx > Javascript Test > renderContentExample 1`] = `
"
+
+
+
+{#if shouldWrap()}
+ {#if isEmptyHtmlElement(tag())}
+
+ {/if}
+
+ {#if !isEmptyHtmlElement(tag()) && repeatItemData()}
+ {#each repeatItemData() as data, index (index)}
+
+ {/each}
+ {/if}
+
+ {#if !isEmptyHtmlElement(tag()) && !repeatItemData()}
+
+
+
+ {#each childrenWithoutParentComponent() as child (\\"render-block-\\" + child.id)}
+
+ {/each}
+
+ {#each childrenWithoutParentComponent() as child (\\"block-style-\\" + child.id)}
+
+ {/each}
+
+ {/if}
+{:else}
+
+{/if}"
+`;
+
exports[`Svelte > jsx > Typescript Test > renderContentExample 1`] = `
""
`;
+exports[`Vue > jsx > Javascript Test > renderBlock 1`] = `
+"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"
+`;
+
exports[`Vue > jsx > Javascript Test > renderContentExample 1`] = `
"
@@ -4887,6 +5135,266 @@ function zoo() {
"
`;
+exports[`Vue > jsx > Typescript Test > renderBlock 1`] = `
+"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"
+`;
+
exports[`Vue > jsx > Typescript Test > renderContentExample 1`] = `
"
diff --git a/packages/core/src/__tests__/__snapshots__/vue.test.ts.snap b/packages/core/src/__tests__/__snapshots__/vue.test.ts.snap
index cad5da1aee..163af2f58c 100644
--- a/packages/core/src/__tests__/__snapshots__/vue.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/vue.test.ts.snap
@@ -2454,6 +2454,251 @@ export default {
"
`;
+exports[`Vue > jsx > Javascript Test > renderBlock 1`] = `
+"
+
+
+
+
+
+
+
+
+
+
+
+"
+`;
+
exports[`Vue > jsx > Javascript Test > renderContentExample 1`] = `
"
@@ -5562,6 +5807,264 @@ export default {
"
`;
+exports[`Vue > jsx > Typescript Test > renderBlock 1`] = `
+"
+
+
+
+
+
+
+
+
+
+
+
+"
+`;
+
exports[`Vue > jsx > Typescript Test > renderContentExample 1`] = `
"
diff --git a/packages/core/src/__tests__/__snapshots__/webcomponent.test.ts.snap b/packages/core/src/__tests__/__snapshots__/webcomponent.test.ts.snap
index 9c47de8111..8e914d1b22 100644
--- a/packages/core/src/__tests__/__snapshots__/webcomponent.test.ts.snap
+++ b/packages/core/src/__tests__/__snapshots__/webcomponent.test.ts.snap
@@ -10593,6 +10593,16 @@ customElements.define(\\"on-update\\", OnUpdate);
"
`;
+exports[`webcomponent > jsx > Javascript Test > renderBlock 1`] = `
+"unknown: Unexpected token, expected \\"(\\" (1:17)
+
+> 1 | let _ = function function component() {
+ | ^
+ 2 | const componentName = getProcessedBlock({
+ 3 | block: props.block,
+ 4 | self.state: props.context.state,"
+`;
+
exports[`webcomponent > jsx > Javascript Test > renderContentExample 1`] = `
"type Props = {
customComponents: string[];
@@ -22900,6 +22910,16 @@ customElements.define(\\"on-update\\", OnUpdate);
"
`;
+exports[`webcomponent > jsx > Typescript Test > renderBlock 1`] = `
+"unknown: Unexpected token, expected \\"(\\" (1:17)
+
+> 1 | let _ = function function component() {
+ | ^
+ 2 | const componentName = getProcessedBlock({
+ 3 | block: props.block,
+ 4 | self.state: props.context.state,"
+`;
+
exports[`webcomponent > jsx > Typescript Test > renderContentExample 1`] = `
"type Props = {
customComponents: string[];
diff --git a/packages/core/src/__tests__/data/blocks/render-block.raw.tsx b/packages/core/src/__tests__/data/blocks/render-block.raw.tsx
new file mode 100644
index 0000000000..c9e974c76a
--- /dev/null
+++ b/packages/core/src/__tests__/data/blocks/render-block.raw.tsx
@@ -0,0 +1,261 @@
+import type { BuilderContextInterface, RegisteredComponent } from '../../context/types.js';
+import { getBlockActions } from '../../functions/get-block-actions.js';
+import { getBlockComponentOptions } from '../../functions/get-block-component-options.js';
+import { getBlockProperties } from '../../functions/get-block-properties.js';
+import { getBlockTag } from '../../functions/get-block-tag.js';
+import { getProcessedBlock } from '../../functions/get-processed-block.js';
+import type { BuilderBlock } from '../../types/builder-block.js';
+import type { Nullable } from '../../types/typescript.js';
+import { evaluate } from '../../functions/evaluate.js';
+import BlockStyles from './block-styles.lite';
+import { isEmptyHtmlElement } from './render-block.helpers.js';
+import type { RenderComponentProps } from './render-component.lite';
+import { For, Show, useStore } from '@builder.io/mitosis';
+import type { RepeatData } from './types.js';
+import RenderRepeatedBlock from './render-repeated-block.lite';
+import { TARGET } from '../../constants/target.js';
+import { extractTextStyles } from '../../functions/extract-text-styles.js';
+import RenderComponentWithContext from './render-component-with-context.lite';
+import RenderComponent from './render-component.lite';
+import { getReactNativeBlockStyles } from '../../functions/get-react-native-block-styles.js';
+
+export type RenderBlockProps = {
+ block: BuilderBlock;
+ context: BuilderContextInterface;
+};
+
+export default function RenderBlock(props: RenderBlockProps) {
+ const state = useStore({
+ get component(): Nullable {
+ const componentName = getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: false,
+ }).component?.name;
+
+ if (!componentName) {
+ return null;
+ }
+
+ const ref = props.context.registeredComponents[componentName];
+
+ if (!ref) {
+ // TODO: Public doc page with more info about this message
+ console.warn(`
+ Could not find a registered component named "${componentName}".
+ If you registered it, is the file that registered it imported by the file that needs to render it?`);
+ return undefined;
+ } else {
+ return ref;
+ }
+ },
+ get tag() {
+ return getBlockTag(state.useBlock);
+ },
+ get useBlock(): BuilderBlock {
+ return state.repeatItemData
+ ? props.block
+ : getProcessedBlock({
+ block: props.block,
+ state: props.context.state,
+ context: props.context.context,
+ shouldEvaluateBindings: true,
+ });
+ },
+ get actions() {
+ return getBlockActions({
+ block: state.useBlock,
+ state: props.context.state,
+ context: props.context.context,
+ });
+ },
+ get attributes() {
+ const blockProperties = getBlockProperties(state.useBlock);
+ return {
+ ...blockProperties,
+ ...(TARGET === 'reactNative'
+ ? {
+ style: getReactNativeBlockStyles({
+ block: state.useBlock,
+ context: props.context,
+ blockStyles: blockProperties.style,
+ }),
+ }
+ : {}),
+ };
+ },
+
+ get shouldWrap() {
+ return !state.component?.noWrap;
+ },
+
+ get renderComponentProps(): RenderComponentProps {
+ return {
+ blockChildren: state.useChildren,
+ componentRef: state.component?.component,
+ componentOptions: {
+ ...getBlockComponentOptions(state.useBlock),
+ /**
+ * These attributes are passed to the wrapper element when there is one. If `noWrap` is set to true, then
+ * they are provided to the component itself directly.
+ */
+ ...(state.shouldWrap
+ ? {}
+ : {
+ attributes: {
+ ...state.attributes,
+ ...state.actions,
+ },
+ }),
+ customBreakpoints: state.childrenContext?.content?.meta?.breakpoints,
+ },
+ context: state.childrenContext,
+ };
+ },
+ get useChildren() {
+ // TO-DO: When should `canHaveChildren` dictate rendering?
+ // This is currently commented out because some Builder components (e.g. Box) do not have `canHaveChildren: true`,
+ // but still receive and need to render children.
+ // return state.componentInfo?.canHaveChildren ? state.useBlock.children : [];
+ return state.useBlock.children ?? [];
+ },
+ get childrenWithoutParentComponent() {
+ /**
+ * When there is no `componentRef`, there might still be children that need to be rendered. In this case,
+ * we render them outside of `componentRef`.
+ * NOTE: We make sure not to render this if `repeatItemData` is non-null, because that means we are rendering an array of
+ * blocks, and the children will be repeated within those blocks.
+ */
+ const shouldRenderChildrenOutsideRef = !state.component?.component && !state.repeatItemData;
+
+ return shouldRenderChildrenOutsideRef ? state.useChildren : [];
+ },
+
+ get repeatItemData(): RepeatData[] | undefined {
+ /**
+ * we don't use `state.useBlock` here because the processing done within its logic includes evaluating the block's bindings,
+ * which will not work if there is a repeat.
+ */
+ const { repeat, ...blockWithoutRepeat } = props.block;
+
+ if (!repeat?.collection) {
+ return undefined;
+ }
+
+ const itemsArray = evaluate({
+ code: repeat.collection,
+ state: props.context.state,
+ context: props.context.context,
+ });
+
+ if (!Array.isArray(itemsArray)) {
+ return undefined;
+ }
+
+ const collectionName = repeat.collection.split('.').pop();
+ const itemNameToUse = repeat.itemName || (collectionName ? collectionName + 'Item' : 'item');
+
+ const repeatArray = itemsArray.map((item, index) => ({
+ context: {
+ ...props.context,
+ state: {
+ ...props.context.state,
+ $index: index,
+ $item: item,
+ [itemNameToUse]: item,
+ [`$${itemNameToUse}Index`]: index,
+ },
+ },
+ block: blockWithoutRepeat,
+ }));
+
+ return repeatArray;
+ },
+
+ get inheritedTextStyles() {
+ if (TARGET !== 'reactNative') {
+ return {};
+ }
+
+ const styles = getReactNativeBlockStyles({
+ block: state.useBlock,
+ context: props.context,
+ blockStyles: state.attributes.style,
+ });
+
+ return extractTextStyles(styles);
+ },
+
+ get childrenContext(): BuilderContextInterface {
+ return {
+ apiKey: props.context.apiKey,
+ state: props.context.state,
+ content: props.context.content,
+ context: props.context.context,
+ registeredComponents: props.context.registeredComponents,
+ inheritedStyles: state.inheritedTextStyles,
+ };
+ },
+
+ get renderComponentTag(): any {
+ if (TARGET === 'reactNative') {
+ return RenderComponentWithContext;
+ } else if (TARGET === 'vue3') {
+ // vue3 expects a string for the component tag
+ return 'RenderComponent';
+ } else {
+ return RenderComponent;
+ }
+ },
+ });
+
+ return (
+ }
+ >
+ {/*
+ * Svelte is super finicky, and does not allow an empty HTML element (e.g. `img`) to have logic inside of it,
+ * _even_ if that logic ends up not rendering anything.
+ */}
+
+
+
+
+
+ {(data, index) => (
+
+ )}
+
+
+
+
+
+ {/**
+ * We need to run two separate loops for content + styles to workaround the fact that Vue 2
+ * does not support multiple root elements.
+ */}
+
+ {(child) => (
+
+ )}
+
+
+ {(child) => (
+
+ )}
+
+
+
+
+ );
+}
diff --git a/packages/core/src/__tests__/shared.ts b/packages/core/src/__tests__/shared.ts
index 935b8c2519..8e5f7cfd9e 100644
--- a/packages/core/src/__tests__/shared.ts
+++ b/packages/core/src/__tests__/shared.ts
@@ -93,6 +93,7 @@ const video = getRawFile('./data/blocks/video.raw.tsx');
const referencingFunInsideHook = getRawFile(
'./data/blocks/referencing-function-inside-hook.raw.tsx',
);
+const renderBlock = getRawFile('./data/blocks/render-block.raw.tsx');
const multipleSpreads = getRawFile('./data/spread/multiple-spreads.raw.tsx');
const spreadAttrs = getRawFile('./data/spread/spread-attrs.raw.tsx');
const spreadNestedProps = getRawFile('./data/spread/spread-nested-props.raw.tsx');
@@ -192,6 +193,7 @@ const BASIC_TESTS: Tests = {
contentState,
referencingFunInsideHook,
svgComponent,
+ renderBlock,
};
const SLOTS_TESTS: Tests = {