diff --git a/packages/core/src/__tests__/__snapshots__/alpine.test.ts.snap b/packages/core/src/__tests__/__snapshots__/alpine.test.ts.snap index 08770e89cc..70d62c0dbf 100644 --- a/packages/core/src/__tests__/__snapshots__/alpine.test.ts.snap +++ b/packages/core/src/__tests__/__snapshots__/alpine.test.ts.snap @@ -1963,6 +1963,240 @@ exports[`Alpine.js > jsx > Javascript Test > referencingFunInsideHook 1`] = ` " `; +exports[`Alpine.js > jsx > Javascript Test > renderBlock 1`] = ` +" + +" +`; + exports[`Alpine.js > jsx > Javascript Test > renderContentExample 1`] = ` "unknown: Unexpected token (2:51) @@ -4182,6 +4416,240 @@ exports[`Alpine.js > jsx > Typescript Test > referencingFunInsideHook 1`] = ` " `; +exports[`Alpine.js > jsx > Typescript Test > renderBlock 1`] = ` +" + +" +`; + exports[`Alpine.js > jsx > Typescript Test > renderContentExample 1`] = ` "unknown: Unexpected token (2:51) diff --git a/packages/core/src/__tests__/__snapshots__/angular.import.test.ts.snap b/packages/core/src/__tests__/__snapshots__/angular.import.test.ts.snap index 21aa7f8909..5db6ec8809 100644 --- a/packages/core/src/__tests__/__snapshots__/angular.import.test.ts.snap +++ b/packages/core/src/__tests__/__snapshots__/angular.import.test.ts.snap @@ -3477,6 +3477,276 @@ export class OnUpdateModule {} " `; +exports[`Angular with Preserve Imports and File Extensions > jsx > Javascript Test > renderBlock 1`] = ` +"import { NgModule } from \\"@angular/core\\"; +import { CommonModule } from \\"@angular/common\\"; + +import { Component, Input } from \\"@angular/core\\"; + +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\\"; + +@Component({ + selector: \\"render-block, RenderBlock\\", + template: \` + + + + + + + + + + + + + + + + + + + + + + + + + + \`, +}) +export class RenderBlock { + isEmptyHtmlElement = isEmptyHtmlElement; + + @Input() block: RenderBlockProps[\\"block\\"]; + @Input() context: RenderBlockProps[\\"context\\"]; + + 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 this.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 this.attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then + * they are provided to the this.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 this.component tag + return \\"RenderComponent\\"; + } else { + return RenderComponent; + } + } +} + +@NgModule({ + declarations: [RenderBlock], + imports: [ + CommonModule, + RenderRepeatedBlockModule, + RenderBlockModule, + BlockStylesModule, + ], + exports: [RenderBlock], +}) +export class RenderBlockModule {} +" +`; + exports[`Angular with Preserve Imports and File Extensions > jsx > Javascript Test > renderContentExample 1`] = ` "import { NgModule } from \\"@angular/core\\"; import { CommonModule } from \\"@angular/common\\"; @@ -7497,6 +7767,276 @@ export class OnUpdateModule {} " `; +exports[`Angular with Preserve Imports and File Extensions > jsx > Typescript Test > renderBlock 1`] = ` +"import { NgModule } from \\"@angular/core\\"; +import { CommonModule } from \\"@angular/common\\"; + +import { Component, Input } from \\"@angular/core\\"; + +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\\"; + +@Component({ + selector: \\"render-block, RenderBlock\\", + template: \` + + + + + + + + + + + + + + + + + + + + + + + + + + \`, +}) +export class RenderBlock { + isEmptyHtmlElement = isEmptyHtmlElement; + + @Input() block: RenderBlockProps[\\"block\\"]; + @Input() context: RenderBlockProps[\\"context\\"]; + + 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 this.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 this.attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then + * they are provided to the this.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 this.component tag + return \\"RenderComponent\\"; + } else { + return RenderComponent; + } + } +} + +@NgModule({ + declarations: [RenderBlock], + imports: [ + CommonModule, + RenderRepeatedBlockModule, + RenderBlockModule, + BlockStylesModule, + ], + exports: [RenderBlock], +}) +export class RenderBlockModule {} +" +`; + exports[`Angular with Preserve Imports and File Extensions > jsx > Typescript Test > renderContentExample 1`] = ` "import { NgModule } from \\"@angular/core\\"; import { CommonModule } from \\"@angular/common\\"; diff --git a/packages/core/src/__tests__/__snapshots__/angular.mapper.test.ts.snap b/packages/core/src/__tests__/__snapshots__/angular.mapper.test.ts.snap index 7d80e80be7..d698628602 100644 --- a/packages/core/src/__tests__/__snapshots__/angular.mapper.test.ts.snap +++ b/packages/core/src/__tests__/__snapshots__/angular.mapper.test.ts.snap @@ -3549,6 +3549,277 @@ export class OnUpdateModule {} " `; +exports[`Angular with Import Mapper Tests > jsx > Javascript Test > renderBlock 1`] = ` +"import { NgModule } from \\"@angular/core\\"; +import { CommonModule } from \\"@angular/common\\"; + +import { Component, Input } from \\"@angular/core\\"; + +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 BlockStylesModule from \\"./block-styles.lite/angular\\"; +import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\"; +import RenderRepeatedBlockModule from \\"./render-repeated-block.lite/angular\\"; +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 { getReactNativeBlockStylesModule } from \\"../../functions/get-react-native-block-styles.js\\"; + +@Component({ + selector: \\"render-block, RenderBlock\\", + template: \` + + + + + + + + + + + + + + + + + + + + + + + + + + \`, +}) +export class RenderBlock { + isEmptyHtmlElement = isEmptyHtmlElement; + + @Input() block: RenderBlockProps[\\"block\\"]; + @Input() context: RenderBlockProps[\\"context\\"]; + + 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 this.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 this.attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then + * they are provided to the this.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 this.component tag + return \\"RenderComponent\\"; + } else { + return RenderComponent; + } + } +} + +@NgModule({ + declarations: [RenderBlock], + imports: [ + CommonModule, + RenderRepeatedBlockModule, + RenderBlockModule, + BlockStylesModule, + ], + exports: [RenderBlock], + bootstrap: [SomeOtherComponent], +}) +export class RenderBlockModule {} +" +`; + exports[`Angular with Import Mapper Tests > jsx > Javascript Test > renderContentExample 1`] = ` "import { NgModule } from \\"@angular/core\\"; import { CommonModule } from \\"@angular/common\\"; @@ -7657,6 +7928,277 @@ export class OnUpdateModule {} " `; +exports[`Angular with Import Mapper Tests > jsx > Typescript Test > renderBlock 1`] = ` +"import { NgModule } from \\"@angular/core\\"; +import { CommonModule } from \\"@angular/common\\"; + +import { Component, Input } from \\"@angular/core\\"; + +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 BlockStylesModule from \\"./block-styles.lite/angular\\"; +import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\"; +import RenderRepeatedBlockModule from \\"./render-repeated-block.lite/angular\\"; +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 { getReactNativeBlockStylesModule } from \\"../../functions/get-react-native-block-styles.js\\"; + +@Component({ + selector: \\"render-block, RenderBlock\\", + template: \` + + + + + + + + + + + + + + + + + + + + + + + + + + \`, +}) +export class RenderBlock { + isEmptyHtmlElement = isEmptyHtmlElement; + + @Input() block: RenderBlockProps[\\"block\\"]; + @Input() context: RenderBlockProps[\\"context\\"]; + + 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 this.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 this.attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then + * they are provided to the this.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 this.component tag + return \\"RenderComponent\\"; + } else { + return RenderComponent; + } + } +} + +@NgModule({ + declarations: [RenderBlock], + imports: [ + CommonModule, + RenderRepeatedBlockModule, + RenderBlockModule, + BlockStylesModule, + ], + exports: [RenderBlock], + bootstrap: [SomeOtherComponent], +}) +export class RenderBlockModule {} +" +`; + exports[`Angular with Import Mapper Tests > jsx > Typescript Test > renderContentExample 1`] = ` "import { NgModule } from \\"@angular/core\\"; import { CommonModule } from \\"@angular/common\\"; diff --git a/packages/core/src/__tests__/__snapshots__/angular.test.ts.snap b/packages/core/src/__tests__/__snapshots__/angular.test.ts.snap index d3ca828129..ffdf717258 100644 --- a/packages/core/src/__tests__/__snapshots__/angular.test.ts.snap +++ b/packages/core/src/__tests__/__snapshots__/angular.test.ts.snap @@ -7168,6 +7168,545 @@ export class OnUpdateModule {} " `; +exports[`Angular > jsx > Javascript Test > renderBlock 1`] = ` +"import { NgModule } from \\"@angular/core\\"; +import { CommonModule } from \\"@angular/common\\"; + +import { Component, Input } from \\"@angular/core\\"; + +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 { isEmptyHtmlElement } from \\"./render-block.helpers.js\\"; +import { TARGET } from \\"../../constants/target.js\\"; +import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\"; +import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\"; + +@Component({ + selector: \\"render-block, RenderBlock\\", + template: \` + + + + + + + + + + + + + + + + + + + + + + + + + + \`, +}) +export class RenderBlock { + isEmptyHtmlElement = isEmptyHtmlElement; + + @Input() block: RenderBlockProps[\\"block\\"]; + @Input() context: RenderBlockProps[\\"context\\"]; + + 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 this.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 this.attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then + * they are provided to the this.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 this.component tag + return \\"RenderComponent\\"; + } else { + return RenderComponent; + } + } +} + +@NgModule({ + declarations: [RenderBlock], + imports: [ + CommonModule, + RenderRepeatedBlockModule, + RenderBlockModule, + BlockStylesModule, + ], + exports: [RenderBlock], +}) +export class RenderBlockModule {} +" +`; + +exports[`Angular > jsx > Javascript Test > renderBlock 2`] = ` +"import { NgModule } from \\"@angular/core\\"; +import { CommonModule } from \\"@angular/common\\"; + +import { Component, Input } from \\"@angular/core\\"; +import { CommonModule } from \\"@angular/common\\"; + +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\\"; +import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\"; +import RenderRepeatedBlock from \\"./render-repeated-block\\"; +import { TARGET } from \\"../../constants/target.js\\"; +import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\"; +import RenderComponentWithContext from \\"./render-component-with-context\\"; +import RenderComponent from \\"./render-component\\"; +import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\"; + +@Component({ + selector: \\"render-block, RenderBlock\\", + template: \` + + + + + + + + + + + + + + + + + + + + + + + + + + \`, + standalone: true, + imports: [CommonModule, RenderRepeatedBlock, RenderBlock, BlockStyles], +}) +export class RenderBlock { + isEmptyHtmlElement = isEmptyHtmlElement; + + @Input() block: RenderBlockProps[\\"block\\"]; + @Input() context: RenderBlockProps[\\"context\\"]; + + 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 this.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 this.attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then + * they are provided to the this.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 this.component tag + return \\"RenderComponent\\"; + } else { + return RenderComponent; + } + } +} + +@NgModule({ + declarations: [RenderBlock], + imports: [ + CommonModule, + RenderRepeatedBlockModule, + RenderBlockModule, + BlockStylesModule, + ], + exports: [RenderBlock], +}) +export class RenderBlockModule {} +" +`; + exports[`Angular > jsx > Javascript Test > renderContentExample 1`] = ` "import { NgModule } from \\"@angular/core\\"; import { CommonModule } from \\"@angular/common\\"; @@ -15466,6 +16005,545 @@ export class OnUpdateModule {} " `; +exports[`Angular > jsx > Typescript Test > renderBlock 1`] = ` +"import { NgModule } from \\"@angular/core\\"; +import { CommonModule } from \\"@angular/common\\"; + +import { Component, Input } from \\"@angular/core\\"; + +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 { isEmptyHtmlElement } from \\"./render-block.helpers.js\\"; +import { TARGET } from \\"../../constants/target.js\\"; +import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\"; +import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\"; + +@Component({ + selector: \\"render-block, RenderBlock\\", + template: \` + + + + + + + + + + + + + + + + + + + + + + + + + + \`, +}) +export class RenderBlock { + isEmptyHtmlElement = isEmptyHtmlElement; + + @Input() block: RenderBlockProps[\\"block\\"]; + @Input() context: RenderBlockProps[\\"context\\"]; + + 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 this.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 this.attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then + * they are provided to the this.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 this.component tag + return \\"RenderComponent\\"; + } else { + return RenderComponent; + } + } +} + +@NgModule({ + declarations: [RenderBlock], + imports: [ + CommonModule, + RenderRepeatedBlockModule, + RenderBlockModule, + BlockStylesModule, + ], + exports: [RenderBlock], +}) +export class RenderBlockModule {} +" +`; + +exports[`Angular > jsx > Typescript Test > renderBlock 2`] = ` +"import { NgModule } from \\"@angular/core\\"; +import { CommonModule } from \\"@angular/common\\"; + +import { Component, Input } from \\"@angular/core\\"; +import { CommonModule } from \\"@angular/common\\"; + +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\\"; +import { isEmptyHtmlElement } from \\"./render-block.helpers.js\\"; +import RenderRepeatedBlock from \\"./render-repeated-block\\"; +import { TARGET } from \\"../../constants/target.js\\"; +import { extractTextStyles } from \\"../../functions/extract-text-styles.js\\"; +import RenderComponentWithContext from \\"./render-component-with-context\\"; +import RenderComponent from \\"./render-component\\"; +import { getReactNativeBlockStyles } from \\"../../functions/get-react-native-block-styles.js\\"; + +@Component({ + selector: \\"render-block, RenderBlock\\", + template: \` + + + + + + + + + + + + + + + + + + + + + + + + + + \`, + standalone: true, + imports: [CommonModule, RenderRepeatedBlock, RenderBlock, BlockStyles], +}) +export class RenderBlock { + isEmptyHtmlElement = isEmptyHtmlElement; + + @Input() block: RenderBlockProps[\\"block\\"]; + @Input() context: RenderBlockProps[\\"context\\"]; + + 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 this.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 this.attributes are passed to the wrapper element when there is one. If \`noWrap\` is set to true, then + * they are provided to the this.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 this.component tag + return \\"RenderComponent\\"; + } else { + return RenderComponent; + } + } +} + +@NgModule({ + declarations: [RenderBlock], + imports: [ + CommonModule, + RenderRepeatedBlockModule, + RenderBlockModule, + BlockStylesModule, + ], + exports: [RenderBlock], +}) +export class RenderBlockModule {} +" +`; + exports[`Angular > jsx > Typescript Test > renderContentExample 1`] = ` "import { NgModule } from \\"@angular/core\\"; import { CommonModule } from \\"@angular/common\\"; diff --git a/packages/core/src/__tests__/__snapshots__/html.test.ts.snap b/packages/core/src/__tests__/__snapshots__/html.test.ts.snap index 26cc7eab85..adb33e6c5e 100644 --- a/packages/core/src/__tests__/__snapshots__/html.test.ts.snap +++ b/packages/core/src/__tests__/__snapshots__/html.test.ts.snap @@ -4428,6 +4428,396 @@ exports[`Html > jsx > Javascript Test > referencingFunInsideHook 1`] = ` " `; +exports[`Html > jsx > Javascript Test > renderBlock 1`] = ` +" + +" +`; + exports[`Html > jsx > Javascript Test > renderContentExample 1`] = ` "
@@ -9671,6 +10061,396 @@ exports[`Html > jsx > Typescript Test > referencingFunInsideHook 1`] = ` " `; +exports[`Html > jsx > Typescript Test > renderBlock 1`] = ` +" + +" +`; + exports[`Html > jsx > Typescript Test > renderContentExample 1`] = ` "
diff --git a/packages/core/src/__tests__/__snapshots__/liquid.test.ts.snap b/packages/core/src/__tests__/__snapshots__/liquid.test.ts.snap index d0a25ce3ca..7ef964aef1 100644 --- a/packages/core/src/__tests__/__snapshots__/liquid.test.ts.snap +++ b/packages/core/src/__tests__/__snapshots__/liquid.test.ts.snap @@ -670,6 +670,11 @@ exports[`Liquid > jsx > Javascript Test > referencingFunInsideHook 1`] = ` " `; +exports[`Liquid > jsx > Javascript Test > renderBlock 1`] = ` +"{% if shouldWrap %} {% endif %} +" +`; + exports[`Liquid > jsx > Javascript Test > renderContentExample 1`] = ` "
@@ -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`] = ` "