rebase from live enviornment

This commit is contained in:
Rachit Bhargava
2024-01-09 22:14:20 -05:00
parent ff0b49a046
commit 3a22fcaa4a
15968 changed files with 2344674 additions and 45234 deletions

View File

@@ -0,0 +1,55 @@
/**
* External dependencies
*/
import {
BlockConfiguration,
registerBlockType,
unregisterBlockType,
registerBlockVariation,
unregisterBlockVariation,
BlockVariation,
BlockAttributes,
} from '@wordpress/blocks';
export interface BlockRegistrationStrategy {
register(
blockNameOrMetadata: string | Partial< BlockConfiguration >,
blockSettings: Partial< BlockConfiguration >
): boolean;
unregister( blockName: string, variationName?: string ): boolean;
}
export class BlockTypeStrategy implements BlockRegistrationStrategy {
register(
blockNameOrMetadata: string | Partial< BlockConfiguration >,
blockSettings: Partial< BlockConfiguration >
): boolean {
return Boolean(
// @ts-expect-error: `registerBlockType` is typed in WordPress core
registerBlockType( blockNameOrMetadata, blockSettings )
);
}
unregister( blockName: string ): boolean {
return Boolean( unregisterBlockType( blockName ) );
}
}
// Strategy for BlockVariation
export class BlockVariationStrategy implements BlockRegistrationStrategy {
register(
blockName: string,
blockSettings: Partial< BlockConfiguration >
): boolean {
return Boolean(
registerBlockVariation(
blockName,
blockSettings as BlockVariation< BlockAttributes >
)
);
}
unregister( blockName: string, variationName: string ): boolean {
return Boolean( unregisterBlockVariation( blockName, variationName ) );
}
}

View File

@@ -0,0 +1,182 @@
/**
* External dependencies
*/
import { getBlockType } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import {
TemplateChangeDetector,
TemplateChangeDetectorObserver,
} from './template-change-detector';
import {
BlockRegistrationStrategy,
BlockTypeStrategy,
BlockVariationStrategy,
} from './block-registration-strategy';
import { BLOCKS_WITH_RESTRICTION } from './blocks-with-restriction';
/**
* Manages the registration and unregistration of blocks based on template or page restrictions.
*
* This class implements the TemplateChangeDetectorObserver interface and is responsible for managing the registration and unregistration of blocks based on the restrictions defined in the BLOCKS_WITH_RESTRICTION constant.
*
* The class maintains a list of unregistered blocks and uses a block registration strategy to register and unregister blocks as needed. The strategy used depends on whether the block is a variation block or a regular block.
*
* The `run` method is the main entry point for the class. It is called with a TemplateChangeDetector object and registers and unregisters blocks based on the current template and whether the editor is in post or page mode.
*/
export class BlockRegistrationManager
implements TemplateChangeDetectorObserver
{
private unregisteredBlocks: string[] = [];
private blockRegistrationStrategy: BlockRegistrationStrategy;
constructor() {
this.blockRegistrationStrategy = new BlockTypeStrategy();
}
/**
* Determines whether a block should be registered based on the current template or page.
*
* This method checks whether a block with restrictions should be registered based on the current template ID and
* whether the editor is in post or page mode. It checks whether the current template ID starts with any of the
* allowed templates or template parts for the block, and whether the block is available in the post or page editor.
*
* @param {Object} params - The parameters for the method.
* @param {string} params.blockWithRestrictionName - The name of the block with restrictions.
* @param {string} params.currentTemplateId - The ID of the current template.
* @param {boolean} params.isPostOrPage - Whether the editor is in a post or page.
* @return {boolean} True if the block should be registered, false otherwise.
*/
private shouldBlockBeRegistered( {
blockWithRestrictionName,
currentTemplateId,
isPostOrPage,
}: {
blockWithRestrictionName: string;
currentTemplateId: string;
isPostOrPage: boolean;
} ) {
const {
allowedTemplates,
allowedTemplateParts,
availableInPostOrPageEditor,
} = BLOCKS_WITH_RESTRICTION[ blockWithRestrictionName ];
const shouldBeAvailableOnTemplate = Object.keys(
allowedTemplates
).some( ( allowedTemplate ) =>
currentTemplateId.startsWith( allowedTemplate )
);
const shouldBeAvailableOnTemplatePart = Object.keys(
allowedTemplateParts
).some( ( allowedTemplate ) =>
currentTemplateId.startsWith( allowedTemplate )
);
const shouldBeAvailableOnPostOrPageEditor =
isPostOrPage && availableInPostOrPageEditor;
return (
shouldBeAvailableOnTemplate ||
shouldBeAvailableOnTemplatePart ||
shouldBeAvailableOnPostOrPageEditor
);
}
/**
* Unregisters blocks before entering a restricted area based on the current template or page/post.
*
* This method iterates over all blocks with restrictions and unregisters them if they should not be registered
* based on the current template ID and whether the editor is in a post or page. It uses a block registration
* strategy to unregister the blocks, which depends on whether the block is a variation block or a regular block.
*
* @param {Object} params - The parameters for the method.
* @param {string} params.currentTemplateId - The ID of the current template.
* @param {boolean} params.isPostOrPage - Whether the editor is in post or page mode.
*/
unregisterBlocksBeforeEnteringRestrictedArea( {
currentTemplateId,
isPostOrPage,
}: {
currentTemplateId: string;
isPostOrPage: boolean;
} ) {
for ( const blockWithRestrictionName of Object.keys(
BLOCKS_WITH_RESTRICTION
) ) {
if (
this.shouldBlockBeRegistered( {
blockWithRestrictionName,
currentTemplateId,
isPostOrPage,
} )
) {
continue;
}
if ( ! getBlockType( blockWithRestrictionName ) ) {
continue;
}
this.blockRegistrationStrategy = BLOCKS_WITH_RESTRICTION[
blockWithRestrictionName
].isVariationBlock
? new BlockVariationStrategy()
: new BlockTypeStrategy();
this.blockRegistrationStrategy.unregister(
blockWithRestrictionName
);
this.unregisteredBlocks.push( blockWithRestrictionName );
}
}
/**
* Registers blocks after leaving a restricted area.
*
* This method iterates over all unregistered blocks and registers them if they are not restricted in the current context.
* It uses a block registration strategy to register the blocks, which depends on whether the block is a variation block or a regular block.
* If the block is successfully registered, it is removed from the list of unregistered blocks.
*/
registerBlocksAfterLeavingRestrictedArea() {
for ( const unregisteredBlockName of this.unregisteredBlocks ) {
if ( ! getBlockType( unregisteredBlockName ) ) {
continue;
}
const restrictedBlockData =
BLOCKS_WITH_RESTRICTION[ unregisteredBlockName ];
this.blockRegistrationStrategy = BLOCKS_WITH_RESTRICTION[
unregisteredBlockName
].isVariationBlock
? new BlockVariationStrategy()
: new BlockTypeStrategy();
const isBlockRegistered = this.blockRegistrationStrategy.register(
restrictedBlockData.blockMetadata,
restrictedBlockData.blockSettings
);
this.unregisteredBlocks = isBlockRegistered
? this.unregisteredBlocks.filter(
( blockName ) => blockName !== unregisteredBlockName
)
: this.unregisteredBlocks;
}
}
/**
* Runs the block registration manager.
*
* This method is the main entry point for the block registration manager. It is called with a TemplateChangeDetector object,
* and registers and unregisters blocks based on the current template and whether the editor is in a post or page.
*
* @param {TemplateChangeDetector} templateChangeDetector - The template change detector object.
*/
run( templateChangeDetector: TemplateChangeDetector ) {
this.registerBlocksAfterLeavingRestrictedArea();
this.unregisterBlocksBeforeEnteringRestrictedArea( {
currentTemplateId:
templateChangeDetector.getCurrentTemplateId() || '',
isPostOrPage: templateChangeDetector.getIsPostOrPage(),
} );
}
}

View File

@@ -0,0 +1,42 @@
/**
* External dependencies
*/
import { BlockConfiguration } from '@wordpress/blocks';
import { ProductGalleryBlockSettings } from '@woocommerce/blocks/product-gallery/settings';
/**
* Internal dependencies
*/
import productGalleryBlockMetadata from '../../../blocks/product-gallery/block.json';
export interface BlocksWithRestriction {
[ key: string ]: {
blockMetadata: Partial< BlockConfiguration >;
blockSettings: Partial< BlockConfiguration >;
allowedTemplates: {
[ key: string ]: boolean;
};
allowedTemplateParts: {
[ key: string ]: boolean;
};
availableInPostOrPageEditor: boolean;
isVariationBlock: boolean;
};
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error: `metadata` currently does not have a type definition in WordPress core
export const BLOCKS_WITH_RESTRICTION: BlocksWithRestriction = {
[ productGalleryBlockMetadata.name ]: {
blockMetadata: productGalleryBlockMetadata,
blockSettings: ProductGalleryBlockSettings,
allowedTemplates: {
'single-product': true,
},
allowedTemplateParts: {
'product-gallery': true,
},
availableInPostOrPageEditor: false,
isVariationBlock: false,
},
};

View File

@@ -0,0 +1,16 @@
/**
* External dependencies
*/
import domReady from '@wordpress/dom-ready';
/**
* Internal dependencies
*/
import { BlockRegistrationManager } from './blocks-registration-manager';
import { TemplateChangeDetector } from './template-change-detector';
domReady( () => {
const templateChangeDetector = new TemplateChangeDetector();
const blockRegistrationManager = new BlockRegistrationManager();
templateChangeDetector.add( blockRegistrationManager );
} );

View File

@@ -0,0 +1,115 @@
/**
* External dependencies
*/
import { subscribe, select } from '@wordpress/data';
import { isNumber } from '@woocommerce/types';
interface TemplateChangeDetectorSubject {
add( observer: TemplateChangeDetectorObserver ): void;
getPreviousTemplateId(): string | undefined;
getCurrentTemplateId(): string | undefined;
notify(): void;
}
export interface TemplateChangeDetectorObserver {
run( subject: TemplateChangeDetectorSubject ): void;
}
/**
* This class implements the TemplateChangeDetectorSubject interface and is responsible for detecting changes in the
* current template or page and notifying any observers of these changes. It maintains a list of observers and provides methods
* to add observers and notify them of changes.
*
* The class also provides methods to get the previous and current template IDs and whether the editor is in a post or page.
*
* The `checkIfTemplateHasChangedAndNotifySubscribers` method is the main method of the class. It checks if the current
* template has changed and, if so, notifies all observers.
*/
export class TemplateChangeDetector implements TemplateChangeDetectorSubject {
private previousTemplateId: string | undefined;
private currentTemplateId: string | undefined;
private isPostOrPage: boolean;
private observers: TemplateChangeDetectorObserver[] = [];
constructor() {
this.isPostOrPage = false;
subscribe( () => {
this.checkIfTemplateHasChangedAndNotifySubscribers();
}, 'core/edit-site' );
}
public add( observer: TemplateChangeDetectorObserver ): void {
this.observers.push( observer );
}
/**
* Trigger an update in each subscriber.
*/
public notify(): void {
for ( const observer of this.observers ) {
observer.run( this );
}
}
public getPreviousTemplateId() {
return this.previousTemplateId;
}
public getCurrentTemplateId() {
return this.currentTemplateId;
}
public getIsPostOrPage() {
return this.isPostOrPage;
}
/**
* Parses the template ID.
*
* This method takes a template or a post ID and returns it parsed in the expected format.
*
* @param {string | number | undefined} templateId - The template ID to parse.
* @return {string | undefined} The parsed template ID.
*/
private parseTemplateId(
templateId: string | number | undefined
): string | undefined {
if ( isNumber( templateId ) ) {
return String( templateId );
}
return templateId?.split( '//' )[ 1 ];
}
/**
* Checks if the current template or page has changed and notifies subscribers.
*
* If the current template ID has changed and is not undefined (which means that it is not a page, post or template), it notifies all subscribers.
*/
public checkIfTemplateHasChangedAndNotifySubscribers(): void {
this.previousTemplateId = this.currentTemplateId;
const postOrPageId = select( 'core/editor' )?.getCurrentPostId<
string | number | undefined
>();
this.isPostOrPage = Boolean( postOrPageId );
const editedPostId =
postOrPageId ||
select( 'core/edit-site' )?.getEditedPostId<
string | number | undefined
>();
this.currentTemplateId = this.parseTemplateId( editedPostId );
const hasChangedTemplate =
this.previousTemplateId !== this.currentTemplateId;
const hasTemplateId = Boolean( this.currentTemplateId );
if ( ! hasChangedTemplate || ! hasTemplateId ) {
return;
}
this.notify();
}
}

View File

@@ -0,0 +1,18 @@
/**
* External dependencies
*/
import { createBlock } from '@wordpress/blocks';
/**
* Creates blocks for a given inner blocks Template.
*
* @param {Array} template Inner Blocks Template.
*/
export const createBlocksFromTemplate = ( template ) => {
return template.map( ( [ name, atts = {}, innerBlocks = [] ] ) => {
const children = innerBlocks
? createBlocksFromTemplate( innerBlocks )
: [];
return createBlock( name, atts, children );
} );
};

View File

@@ -0,0 +1,20 @@
/**
* External dependencies
*/
import { getRegisteredBlockComponents } from '@woocommerce/blocks-registry';
import type { RegisteredBlockComponent } from '@woocommerce/types';
/**
* Internal dependencies
*/
import '../blocks/component-init';
/**
* Map named Blocks to defined React Components to render on the frontend.
*
* @param {string} blockName Name of the parent block.
*/
export const getBlockMap = (
blockName: string
): Record< string, RegisteredBlockComponent > =>
getRegisteredBlockComponents( blockName );

View File

@@ -0,0 +1,5 @@
export * from './get-block-map';
export * from './create-blocks-from-template';
export * from './render-parent-block';
export * from './render-standalone-blocks';
export * from './register-block-single-product-template';

View File

@@ -0,0 +1,121 @@
/**
* External dependencies
*/
import { isNumber, isEmpty } from '@woocommerce/types';
import {
BlockAttributes,
BlockConfiguration,
BlockVariation,
getBlockType,
registerBlockType,
registerBlockVariation,
unregisterBlockType,
unregisterBlockVariation,
} from '@wordpress/blocks';
import { subscribe, select } from '@wordpress/data';
// Creating a local cache to prevent multiple registration tries.
const blocksRegistered = new Set();
function parseTemplateId( templateId: string | number | undefined ) {
// With GB 16.3.0 the return type can be a number: https://github.com/WordPress/gutenberg/issues/53230
const parsedTemplateId = isNumber( templateId ) ? undefined : templateId;
return parsedTemplateId?.split( '//' )[ 1 ];
}
export const registerBlockSingleProductTemplate = ( {
blockName,
blockMetadata,
blockSettings,
isVariationBlock = false,
variationName,
isAvailableOnPostEditor,
}: {
blockName: string;
blockMetadata: Partial< BlockConfiguration >;
blockSettings: Partial< BlockConfiguration >;
isAvailableOnPostEditor: boolean;
isVariationBlock?: boolean;
variationName?: string;
} ) => {
let currentTemplateId: string | undefined = '';
subscribe( () => {
const previousTemplateId = currentTemplateId;
const store = select( 'core/edit-site' );
// With GB 16.3.0 the return type can be a number: https://github.com/WordPress/gutenberg/issues/53230
currentTemplateId = parseTemplateId(
store?.getEditedPostId< string | number | undefined >()
);
const hasChangedTemplate = previousTemplateId !== currentTemplateId;
const hasTemplateId = Boolean( currentTemplateId );
if ( ! hasChangedTemplate || ! hasTemplateId || ! blockName ) {
return;
}
let isBlockRegistered = Boolean( getBlockType( blockName ) );
/**
* We need to unregister the block each time the user visits or leaves the Single Product template.
*
* The Single Product template is the only template where the `ancestor` property is not needed because it provides the context
* for the product blocks. We need to unregister and re-register the block to remove or add the `ancestor` property depending on which
* location (template, post, page, etc.) the user is in.
*
*/
if (
isBlockRegistered &&
( currentTemplateId?.includes( 'single-product' ) ||
previousTemplateId?.includes( 'single-product' ) )
) {
if ( isVariationBlock && variationName ) {
unregisterBlockVariation( blockName, variationName );
} else {
unregisterBlockType( blockName );
}
isBlockRegistered = false;
}
if ( ! isBlockRegistered ) {
if ( isVariationBlock ) {
// @ts-expect-error: `registerBlockType` is not typed in WordPress core
registerBlockVariation( blockName, blockSettings );
} else {
const ancestor = isEmpty( blockSettings?.ancestor )
? [ 'woocommerce/single-product' ]
: blockSettings?.ancestor;
// @ts-expect-error: `registerBlockType` is not typed in WordPress core
registerBlockType( blockMetadata, {
...blockSettings,
ancestor: ! currentTemplateId?.includes( 'single-product' )
? ancestor
: undefined,
} );
}
}
}, 'core/edit-site' );
subscribe( () => {
const isBlockRegistered = Boolean( variationName )
? blocksRegistered.has( variationName )
: blocksRegistered.has( blockName );
// This subscribe callback could be invoked with the core/blocks store
// which would cause infinite registration loops because of the `registerBlockType` call.
// This local cache helps prevent that.
if ( ! isBlockRegistered && isAvailableOnPostEditor ) {
if ( isVariationBlock ) {
blocksRegistered.add( variationName );
registerBlockVariation(
blockName,
blockSettings as BlockVariation< BlockAttributes >
);
} else {
blocksRegistered.add( blockName );
// @ts-expect-error: `registerBlockType` is typed in WordPress core
registerBlockType( blockMetadata, blockSettings );
}
}
}, 'core/edit-post' );
};

View File

@@ -0,0 +1,318 @@
/**
* External dependencies
*/
import { renderFrontend } from '@woocommerce/base-utils';
import { CURRENT_USER_IS_ADMIN } from '@woocommerce/settings';
import {
Fragment,
Suspense,
isValidElement,
cloneElement,
} from '@wordpress/element';
import parse from 'html-react-parser';
import {
getRegisteredBlocks,
hasInnerBlocks,
} from '@woocommerce/blocks-checkout';
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
/**
* This file contains logic used on the frontend to convert DOM elements (saved by the block editor) to React
* Components. These components are registered using registerBlockComponent() and registerCheckoutBlock() and map 1:1
* to a block by name.
*
* Blocks using this system will have their blockName stored as a data attribute, for example:
* <div data-block-name="woocommerce/product-title"></div>
*
* This block name is then read, and using the map, dynamically converted to a real React Component.
*
* @see registerBlockComponent
* @see registerCheckoutBlock
*/
/**
* Gets a component from the block map for a given block name, or returns null if a component is not registered.
*/
const getBlockComponentFromMap = (
block: string,
blockMap: Record< string, React.ReactNode >
): React.ElementType | null => {
return block && blockMap[ block ]
? ( blockMap[ block ] as React.ElementType )
: null;
};
/**
* Render forced blocks which are missing from the template.
*
* Forced blocks are registered in registerCheckoutBlock. If a block is forced, it will be inserted in the editor
* automatically, however, until that happens they may be missing from the frontend. To fix this, we look up what blocks
* are registered as forced, and then append them here if they are missing.
*
* @see registerCheckoutBlock
*/
const renderForcedBlocks = (
block: string,
blockMap: Record< string, React.ReactNode >,
// Current children from the parent (siblings of the forced block)
blockChildren: NodeListOf< ChildNode > | null,
// Wrapper for inner components.
blockWrapper?: React.ElementType
) => {
if ( ! hasInnerBlocks( block ) ) {
return null;
}
const currentBlocks = blockChildren
? ( Array.from( blockChildren )
.map( ( node: Node ) =>
node instanceof HTMLElement
? node?.dataset.blockName || null
: null
)
.filter( Boolean ) as string[] )
: [];
const forcedBlocks = getRegisteredBlocks( block ).filter(
( { blockName, force } ) =>
force === true && ! currentBlocks.includes( blockName )
);
// This will wrap inner blocks with the provided wrapper. If no wrapper is provided, we default to Fragment.
const InnerBlockComponentWrapper = blockWrapper ? blockWrapper : Fragment;
return (
<>
{ forcedBlocks.map(
(
{ blockName, component },
index: number
): JSX.Element | null => {
const ForcedComponent = component
? component
: getBlockComponentFromMap( blockName, blockMap );
return ForcedComponent ? (
<BlockErrorBoundary
key={ `${ blockName }_blockerror` }
text={ `Unexpected error in: ${ blockName }` }
showErrorBlock={ CURRENT_USER_IS_ADMIN as boolean }
>
<InnerBlockComponentWrapper>
<ForcedComponent
key={ `${ blockName }_forced_${ index }` }
/>
</InnerBlockComponentWrapper>
</BlockErrorBoundary>
) : null;
}
) }
</>
);
};
interface renderInnerBlocksProps {
// Block (parent) being rendered. Used for inner block component mapping.
block: string;
// Map of block names to block components for children.
blockMap: Record< string, React.ReactNode >;
// Wrapper for inner components.
blockWrapper?: React.ElementType | undefined;
// Elements from the DOM being converted to components.
children: HTMLCollection | NodeList;
// Depth within the DOM hierarchy.
depth?: number;
}
/**
* Recursively replace block markup in the DOM with React Components.
*/
const renderInnerBlocks = ( {
// This is the parent block we're working within (see renderParentBlock)
block,
// This is the map of blockNames->components
blockMap,
// Component which inner blocks are wrapped with.
blockWrapper,
// The children from the DOM we're currently iterating over.
children,
// Current depth of the children. Used to ensure keys are unique.
depth = 1,
}: renderInnerBlocksProps ): ( string | JSX.Element | null )[] | null => {
if ( ! children || children.length === 0 ) {
return null;
}
return Array.from( children ).map( ( node: Node, index: number ) => {
/**
* This will grab the blockName from the data- attributes stored in block markup. Without a blockName, we cannot
* convert the HTMLElement to a React component.
*/
const { blockName = '', ...componentProps } = {
...( node instanceof HTMLElement ? node.dataset : {} ),
className: node instanceof Element ? node?.className : '',
};
const componentKey = `${ block }_${ depth }_${ index }`;
const InnerBlockComponent = getBlockComponentFromMap(
blockName,
blockMap
);
/**
* If the component cannot be found, or blockName is missing, return the original element. This also ensures
* that children within the element are processed also, since it may be an element containing block markup.
*
* Note we use childNodes rather than children so that text nodes are also rendered.
*/
if ( ! InnerBlockComponent ) {
const parsedElement = parse(
( node instanceof Element && node?.outerHTML ) ||
node?.textContent ||
''
);
// Returns text nodes without manipulation.
if ( typeof parsedElement === 'string' && !! parsedElement ) {
return parsedElement;
}
// Do not render invalid elements.
if ( ! isValidElement( parsedElement ) ) {
return null;
}
const renderedChildren = node.childNodes.length
? renderInnerBlocks( {
block,
blockMap,
children: node.childNodes,
depth: depth + 1,
blockWrapper,
} )
: undefined;
// We pass props here rather than componentProps to avoid the data attributes being renamed.
return renderedChildren
? cloneElement(
parsedElement,
{
key: componentKey,
...( parsedElement?.props || {} ),
},
renderedChildren
)
: cloneElement( parsedElement, {
key: componentKey,
...( parsedElement?.props || {} ),
} );
}
// This will wrap inner blocks with the provided wrapper. If no wrapper is provided, we default to Fragment.
const InnerBlockComponentWrapper = blockWrapper
? blockWrapper
: Fragment;
return (
<Suspense
key={ `${ block }_${ depth }_${ index }_suspense` }
fallback={ <div className="wc-block-placeholder" /> }
>
{ /* Prevent third party components from breaking the entire checkout */ }
<BlockErrorBoundary
text={ `Unexpected error in: ${ blockName }` }
showErrorBlock={ CURRENT_USER_IS_ADMIN as boolean }
>
<InnerBlockComponentWrapper>
<InnerBlockComponent
key={ componentKey }
{ ...componentProps }
>
{
/**
* Within this Inner Block Component we also need to recursively render it's children. This
* is done here with a depth+1. The same block map and parent is used, but we pass new
* children from this element.
*/
renderInnerBlocks( {
block,
blockMap,
children: node.childNodes,
depth: depth + 1,
blockWrapper,
} )
}
{
/**
* In addition to the inner blocks, we may also need to render FORCED blocks which have not
* yet been added to the inner block template. We do this by comparing the current children
* to the list of registered forced blocks.
*
* @see registerCheckoutBlock
*/
renderForcedBlocks(
blockName,
blockMap,
node.childNodes,
blockWrapper
)
}
</InnerBlockComponent>
</InnerBlockComponentWrapper>
</BlockErrorBoundary>
</Suspense>
);
} );
};
/**
* Render a parent block on the frontend.
*
* This is the main entry point used on the frontend to convert Block Markup (with inner blocks) in the DOM to React
* Components.
*
* This uses renderFrontend(). The difference is, renderFrontend renders a single block, but renderParentBlock() also
* handles inner blocks by recursively running over children from the DOM.
*
* @see renderInnerBlocks
* @see renderFrontend
*/
export const renderParentBlock = ( {
Block,
selector,
blockName,
getProps = () => ( {} ),
blockMap,
blockWrapper,
}: {
// Parent Block Name. Used for inner block component mapping.
blockName: string;
// Map of block names to block components for children.
blockMap: Record< string, React.ReactNode >;
// Wrapper for inner components.
blockWrapper?: React.ElementType;
// React component to use as a replacement.
Block: React.FunctionComponent;
// CSS selector to match the elements to replace.
selector: string;
// Function to generate the props object for the block.
getProps: ( el: Element, i: number ) => Record< string, unknown >;
} ): void => {
/**
* In addition to getProps, we need to render and return the children. This adds children to props.
*/
const getPropsWithChildren = ( element: Element, i: number ) => {
const children = renderInnerBlocks( {
block: blockName,
blockMap,
children: element.children || [],
blockWrapper,
} );
return { ...getProps( element, i ), children };
};
/**
* The only difference between using renderParentBlock and renderFrontend is that here we provide children.
*/
renderFrontend( {
Block,
selector,
getProps: getPropsWithChildren,
} );
};

View File

@@ -0,0 +1,27 @@
/**
* External dependencies
*/
import { renderFrontend } from '@woocommerce/base-utils';
/**
* Internal dependencies
*/
import { getBlockMap } from './get-block-map';
export const renderStandaloneBlocks = () => {
const blockMap = getBlockMap( '' );
Object.keys( blockMap ).forEach( ( blockName ) => {
const selector = '.wp-block-' + blockName.replace( '/', '-' );
const getProps = ( el ) => {
return el.dataset;
};
renderFrontend( {
Block: blockMap[ blockName ],
selector,
getProps,
} );
} );
};