This commit is contained in:
Mike Jolley 2021-06-11 09:54:18 +01:00 committed by GitHub
parent 44b2003ec5
commit a815993b40
6 changed files with 134 additions and 115 deletions

View File

@ -1,6 +1,5 @@
export * from './get-block-map.js'; export * from './get-block-map';
export * from './create-blocks-from-template.js'; export * from './create-blocks-from-template';
export * from './render-parent-block.js'; export * from './render-parent-block';
export * from './render-inner-blocks.js';
export * from './block-styling.js'; export * from './block-styling.js';
export * from './render-standalone-blocks.js'; export * from './render-standalone-blocks';

View File

@ -1,69 +0,0 @@
/**
* External dependencies
*/
import { Suspense, cloneElement, isValidElement } from '@wordpress/element';
import parse from 'html-react-parser';
/**
* Internal dependencies
*/
import { getBlockMap } from './get-block-map';
/**
* Replaces saved block HTML markup with Inner Block Components.
*
* @param {Object} props Render props.
* @param {Array} props.children Children/inner blocks to render.
* @param {string} props.blockName Parent Block Name used to get the block map and for keys.
* @param {number} [props.depth] Depth of inner blocks being rendered.
*/
export const renderInnerBlocks = ( {
children,
blockName: parentBlockName,
depth = 1,
} ) => {
const blockMap = getBlockMap( parentBlockName );
return Array.from( children ).map( ( el, index ) => {
const componentProps = {
...el.dataset,
key: `${ parentBlockName }_${ depth }_${ index }`,
};
const componentChildren =
el.children && el.children.length
? renderInnerBlocks( {
children: el.children,
blockName: parentBlockName,
depth: depth + 1,
} )
: null;
const LayoutComponent =
componentProps.blockName && blockMap[ componentProps.blockName ]
? blockMap[ componentProps.blockName ]
: null;
if ( ! LayoutComponent ) {
const element = parse( el.outerHTML );
if ( isValidElement( element ) ) {
return componentChildren
? cloneElement( element, componentProps, componentChildren )
: cloneElement( element, componentProps );
}
return null;
}
return (
<Suspense
key={ `${ parentBlockName }_${ depth }_${ index }_suspense` }
fallback={ <div className="wc-block-placeholder" /> }
>
<LayoutComponent { ...componentProps }>
{ componentChildren }
</LayoutComponent>
</Suspense>
);
} );
};

View File

@ -1,41 +0,0 @@
/**
* External dependencies
*/
import { renderFrontend } from '@woocommerce/base-utils';
/**
* Internal dependencies
*/
import { renderInnerBlocks } from './render-inner-blocks';
/**
* Renders a block component in the place of a specified set of selectors.
*
* @param {Object} props Render props.
* @param {Function} props.Block React component to use as a replacement.
* @param {string} props.selector CSS selector to match the elements to replace.
* @param {string} [props.blockName] Optional Block Name. Used for inner block component mapping.
* @param {Function} [props.getProps] Function to generate the props object for the block.
*/
export const renderParentBlock = ( {
Block,
selector,
blockName = '',
getProps = () => {},
} ) => {
const getPropsWithChildren = ( el, i ) => {
const children =
el.children && el.children.length
? renderInnerBlocks( {
blockName,
children: el.children,
} )
: null;
return { ...getProps( el, i ), children };
};
renderFrontend( {
Block,
selector,
getProps: getPropsWithChildren,
} );
};

View File

@ -0,0 +1,126 @@
/**
* External dependencies
*/
import { renderFrontend } from '@woocommerce/base-utils';
import {
Fragment,
Suspense,
cloneElement,
isValidElement,
} from '@wordpress/element';
import parse from 'html-react-parser';
interface renderBlockProps {
// 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.ReactNode;
}
interface renderParentBlockProps extends renderBlockProps {
// 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 >;
}
interface renderInnerBlockProps extends renderBlockProps {
children: HTMLCollection;
depth?: number;
}
/**
* Replaces saved block HTML markup with Inner Block Components.
*/
const renderInnerBlocks = ( {
blockName: parentBlockName,
blockMap,
blockWrapper,
depth = 1,
children,
}: renderInnerBlockProps ): ( JSX.Element | null )[] | null => {
return Array.from( children ).map( ( el: Element, index: number ) => {
const { blockName = '', ...componentProps } = {
key: `${ parentBlockName }_${ depth }_${ index }`,
...( el instanceof HTMLElement ? el.dataset : {} ),
};
const componentChildren =
el.children && el.children.length
? renderInnerBlocks( {
children: el.children,
blockName: parentBlockName,
blockMap,
depth: depth + 1,
blockWrapper,
} )
: null;
const LayoutComponent =
blockName && blockMap[ blockName ]
? ( blockMap[ blockName ] as React.ElementType )
: null;
if ( ! LayoutComponent ) {
const element = parse( el.outerHTML );
if ( isValidElement( element ) ) {
return componentChildren
? cloneElement( element, componentProps, componentChildren )
: cloneElement( element, componentProps );
}
return null;
}
const LayoutComponentWrapper = ( blockWrapper
? blockWrapper
: Fragment ) as React.ElementType;
return (
<Suspense
key={ `${ parentBlockName }_${ depth }_${ index }_suspense` }
fallback={ <div className="wc-block-placeholder" /> }
>
<LayoutComponentWrapper>
<LayoutComponent { ...componentProps }>
{ componentChildren }
</LayoutComponent>
</LayoutComponentWrapper>
</Suspense>
);
} );
};
/**
* Renders a block component in the place of a specified set of selectors.
*/
export const renderParentBlock = ( {
Block,
selector,
blockName,
getProps = () => ( {} ),
blockMap,
blockWrapper,
}: renderParentBlockProps ): void => {
const getPropsWithChildren = ( el: Element, i: number ) => {
const children =
el.children && el.children.length
? renderInnerBlocks( {
blockName,
blockMap,
children: el.children,
blockWrapper,
} )
: null;
return { ...getProps( el, i ), children };
};
renderFrontend( {
Block,
selector,
getProps: getPropsWithChildren,
} );
};

View File

@ -3,6 +3,7 @@
*/ */
import { getValidBlockAttributes } from '@woocommerce/base-utils'; import { getValidBlockAttributes } from '@woocommerce/base-utils';
import { import {
getBlockMap,
renderParentBlock, renderParentBlock,
renderStandaloneBlocks, renderStandaloneBlocks,
} from '@woocommerce/atomic-utils'; } from '@woocommerce/atomic-utils';
@ -25,6 +26,7 @@ renderParentBlock( {
blockName: BLOCK_NAME, blockName: BLOCK_NAME,
selector: '.wp-block-woocommerce-single-product', selector: '.wp-block-woocommerce-single-product',
getProps, getProps,
blockMap: getBlockMap( BLOCK_NAME ),
} ); } );
renderStandaloneBlocks(); renderStandaloneBlocks();

View File

@ -0,0 +1,2 @@
// eslint-disable-next-line camelcase
declare let __webpack_public_path__: string;