Setup basic column blocks for Cart i2 (https://github.com/woocommerce/woocommerce-blocks/pull/4780)
* setup basic column blocks * fix classnames * fix broken block.json in cart items block * move hacks back * dubplciate columns
This commit is contained in:
parent
f9b9679d62
commit
56551347a6
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useBlockProps } from '@wordpress/block-editor';
|
||||
|
||||
export const Columns = ( {
|
||||
children,
|
||||
...props
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
} ): JSX.Element => {
|
||||
const blockProps = useBlockProps( props );
|
||||
|
||||
return <div { ...blockProps }>{ children }</div>;
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export * from './columns-block';
|
|
@ -1,16 +1,12 @@
|
|||
/* tslint:disable */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { CartCheckoutFeedbackPrompt } from '@woocommerce/editor-components/feedback-prompt';
|
||||
import { InspectorControls } from '@wordpress/block-editor';
|
||||
import {
|
||||
Disabled,
|
||||
PanelBody,
|
||||
ToggleControl,
|
||||
Notice,
|
||||
} from '@wordpress/components';
|
||||
import { InnerBlocks, InspectorControls } from '@wordpress/block-editor';
|
||||
import { PanelBody, ToggleControl, Notice } from '@wordpress/components';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CartCheckoutCompatibilityNotice } from '@woocommerce/editor-components/compatibility-notices';
|
||||
import ViewSwitcher from '@woocommerce/editor-components/view-switcher';
|
||||
|
@ -25,13 +21,13 @@ import {
|
|||
import { createInterpolateElement, useRef } from '@wordpress/element';
|
||||
import { getAdminLink, getSetting } from '@woocommerce/settings';
|
||||
import { previewCart } from '@woocommerce/resource-previews';
|
||||
import { SidebarLayout } from '@woocommerce/base-components/sidebar-layout';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Block from './block.js';
|
||||
import EmptyCartEdit from './empty-cart-edit';
|
||||
import './editor.scss';
|
||||
import { Columns } from './columns';
|
||||
|
||||
const BlockSettings = ( { attributes, setAttributes } ) => {
|
||||
const {
|
||||
|
@ -162,6 +158,10 @@ const BlockSettings = ( { attributes, setAttributes } ) => {
|
|||
);
|
||||
};
|
||||
|
||||
const ALLOWED_BLOCKS: string[] = [
|
||||
'woocommerce/cart-items-block',
|
||||
'woocommerce/cart-totals-block',
|
||||
];
|
||||
/**
|
||||
* Component to handle edit mode of "Cart Block".
|
||||
*
|
||||
|
@ -176,6 +176,13 @@ const BlockSettings = ( { attributes, setAttributes } ) => {
|
|||
* @param {function(any):any} props.setAttributes Setter for attributes.
|
||||
*/
|
||||
const CartEditor = ( { className, attributes, setAttributes } ) => {
|
||||
const cartClassName = classnames( 'wc-block-cart', {
|
||||
'has-dark-controls': attributes.hasDarkControls,
|
||||
} );
|
||||
const defaultInnerBlocksTemplate = [
|
||||
[ 'woocommerce/cart-items-block', {}, [] ],
|
||||
[ 'woocommerce/cart-totals-block', {}, [] ],
|
||||
];
|
||||
return (
|
||||
<div
|
||||
className={ classnames( className, 'wp-block-woocommerce-cart', {
|
||||
|
@ -221,16 +228,26 @@ const CartEditor = ( { className, attributes, setAttributes } ) => {
|
|||
attributes={ attributes }
|
||||
setAttributes={ setAttributes }
|
||||
/>
|
||||
<Disabled>
|
||||
<CartProvider>
|
||||
<Block attributes={ attributes } />
|
||||
</CartProvider>
|
||||
</Disabled>
|
||||
<CartProvider>
|
||||
<Columns>
|
||||
<SidebarLayout
|
||||
className={ cartClassName }
|
||||
>
|
||||
<InnerBlocks
|
||||
allowedBlocks={
|
||||
ALLOWED_BLOCKS
|
||||
}
|
||||
template={
|
||||
defaultInnerBlocksTemplate
|
||||
}
|
||||
templateLock="insert"
|
||||
/>
|
||||
</SidebarLayout>
|
||||
</Columns>
|
||||
</CartProvider>
|
||||
</EditorProvider>
|
||||
<EmptyCartEdit hidden={ true } />
|
||||
</>
|
||||
) }
|
||||
{ currentView === 'empty' && <EmptyCartEdit /> }
|
||||
</BlockErrorBoundary>
|
||||
) }
|
||||
/>
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { getBlockTypes } from '@wordpress/blocks';
|
||||
|
||||
// List of core block types to allow in inner block areas.
|
||||
const coreBlockTypes = [ 'core/paragraph', 'core/image', 'core/separator' ];
|
||||
|
||||
/**
|
||||
* Gets a list of allowed blocks types under a specific parent block type.
|
||||
*/
|
||||
export const getAllowedBlocks = ( block: string ): string[] => [
|
||||
...getBlockTypes()
|
||||
.filter( ( blockType ) =>
|
||||
( blockType?.parent || [] ).includes( block )
|
||||
)
|
||||
.map( ( { name } ) => name ),
|
||||
...coreBlockTypes,
|
||||
];
|
|
@ -6,3 +6,50 @@
|
|||
max-height: 1000px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wp-block-woocommerce-cart-i2 {
|
||||
.wc-block-components-sidebar-layout {
|
||||
display: block;
|
||||
}
|
||||
.block-editor-block-list__layout {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
align-items: flex-start;
|
||||
|
||||
.wc-block-cart__additional_fields {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.wc-block-components-main,
|
||||
.wc-block-components-sidebar,
|
||||
.block-editor-block-list__layout {
|
||||
> :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
.wp-block-woocommerce-cart-totals-block,
|
||||
.wp-block-woocommerce-cart-items-block {
|
||||
.block-editor-block-list__layout {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.wc-lock-selected-block--move {
|
||||
.block-editor-block-mover__move-button-container,
|
||||
.block-editor-block-mover {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
body.wc-lock-selected-block--remove {
|
||||
.block-editor-block-settings-menu__popover {
|
||||
.components-menu-group:last-child {
|
||||
display: none;
|
||||
}
|
||||
.components-menu-group:nth-last-child(2) {
|
||||
margin-bottom: -12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
/**
|
||||
* HACKS
|
||||
*
|
||||
* This file contains functionality to "lock" blocks i.e. to prevent blocks being moved or deleted. This needs to be
|
||||
* kept in place until native support for locking is available in WordPress (estimated WordPress 5.9).
|
||||
*/
|
||||
|
||||
/**
|
||||
* @todo Remove custom block locking (requires native WordPress support)
|
||||
*/
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
useBlockProps,
|
||||
store as blockEditorStore,
|
||||
} from '@wordpress/block-editor';
|
||||
import { isTextField } from '@wordpress/dom';
|
||||
import { useSelect, subscribe, select as _select } from '@wordpress/data';
|
||||
import { useEffect, useRef } from '@wordpress/element';
|
||||
import { MutableRefObject } from 'react';
|
||||
import { BACKSPACE, DELETE } from '@wordpress/keycodes';
|
||||
import { hasFilter } from '@wordpress/hooks';
|
||||
/**
|
||||
* Toggle class on body.
|
||||
*
|
||||
* @param {string} className CSS Class name.
|
||||
* @param {boolean} add True to add, false to remove.
|
||||
*/
|
||||
const toggleBodyClass = ( className: string, add = true ) => {
|
||||
if ( add ) {
|
||||
window.document.body.classList.add( className );
|
||||
} else {
|
||||
window.document.body.classList.remove( className );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* addClassToBody
|
||||
*
|
||||
* This components watches the current selected block and adds a class name to the body if that block is locked. If the
|
||||
* current block is not locked, it removes the class name. The appended body class is used to hide UI elements to prevent
|
||||
* the block from being deleted.
|
||||
*
|
||||
* We use a component so we can react to changes in the store.
|
||||
*/
|
||||
export const addClassToBody = (): void => {
|
||||
if ( ! hasFilter( 'blocks.registerBlockType', 'core/lock/addAttribute' ) ) {
|
||||
subscribe( () => {
|
||||
const blockEditorSelect = _select( blockEditorStore );
|
||||
|
||||
if ( ! blockEditorSelect ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedBlock = blockEditorSelect.getSelectedBlock();
|
||||
|
||||
if ( ! selectedBlock ) {
|
||||
return;
|
||||
}
|
||||
|
||||
toggleBodyClass(
|
||||
'wc-lock-selected-block--remove',
|
||||
!! selectedBlock?.attributes?.lock?.remove
|
||||
);
|
||||
|
||||
toggleBodyClass(
|
||||
'wc-lock-selected-block--move',
|
||||
!! selectedBlock?.attributes?.lock?.move
|
||||
);
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a hook we use in conjunction with useBlockProps. Its goal is to check if a block is locked (move or remove)
|
||||
* and will stop the keydown event from propagating to stop it from being deleted via the keyboard.
|
||||
*
|
||||
* @todo Disable custom locking support if native support is detected.
|
||||
*/
|
||||
const useLockBlock = ( {
|
||||
clientId,
|
||||
ref,
|
||||
attributes,
|
||||
}: {
|
||||
clientId: string;
|
||||
ref: MutableRefObject< Element | undefined >;
|
||||
attributes: Record< string, unknown >;
|
||||
} ): void => {
|
||||
const lockInCore = hasFilter(
|
||||
'blocks.registerBlockType',
|
||||
'core/lock/addAttribute'
|
||||
);
|
||||
const { isSelected } = useSelect(
|
||||
( select ) => {
|
||||
return {
|
||||
isSelected: select( blockEditorStore ).isBlockSelected(
|
||||
clientId
|
||||
),
|
||||
};
|
||||
},
|
||||
[ clientId ]
|
||||
);
|
||||
|
||||
const node = ref.current;
|
||||
|
||||
return useEffect( () => {
|
||||
if ( ! isSelected || ! node || lockInCore ) {
|
||||
return;
|
||||
}
|
||||
function onKeyDown( event: KeyboardEvent ) {
|
||||
const { keyCode, target } = event;
|
||||
if ( keyCode !== BACKSPACE && keyCode !== DELETE ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( target !== node || isTextField( target ) ) {
|
||||
return;
|
||||
}
|
||||
// Prevent the keyboard event from propogating if it supports locking.
|
||||
if ( attributes?.lock?.remove ) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
node.addEventListener( 'keydown', onKeyDown, true );
|
||||
|
||||
return () => {
|
||||
node.removeEventListener( 'keydown', onKeyDown, true );
|
||||
};
|
||||
}, [ node, isSelected, lockInCore, attributes ] );
|
||||
};
|
||||
|
||||
/**
|
||||
* This hook is a light wrapper to useBlockProps, it wraps that hook plus useLockBlock to pass data between them.
|
||||
*/
|
||||
export const useBlockPropsWithLocking = (
|
||||
props: Record< string, unknown > = {}
|
||||
): Record< string, unknown > => {
|
||||
const ref = useRef< Element >();
|
||||
const { attributes } = props;
|
||||
const blockProps = useBlockProps( { ref, ...props } );
|
||||
useLockBlock( {
|
||||
ref,
|
||||
attributes,
|
||||
clientId: blockProps[ 'data-block' ],
|
||||
} );
|
||||
return blockProps;
|
||||
};
|
|
@ -12,7 +12,7 @@ import { registerFeaturePluginBlockType } from '@woocommerce/block-settings';
|
|||
import edit from './edit';
|
||||
import './style.scss';
|
||||
import blockAttributes from './attributes';
|
||||
|
||||
import './inner-blocks';
|
||||
/**
|
||||
* Register and run the Cart block.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "woocommerce/cart-items-block",
|
||||
"version": "1.0.0",
|
||||
"title": "Cart Items block",
|
||||
"description": "Column containing cart items.",
|
||||
"category": "woocommerce",
|
||||
"supports": {
|
||||
"align": false,
|
||||
"html": false,
|
||||
"multiple": false,
|
||||
"reusable": false,
|
||||
"inserter": false
|
||||
},
|
||||
"attributes": {
|
||||
"lock": {
|
||||
"default": {
|
||||
"remove": true,
|
||||
"move": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"parent": [ "woocommerce/cart-i2" ],
|
||||
"textdomain": "woo-gutenberg-products-block",
|
||||
"apiVersion": 2
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
|
||||
import { Main } from '@woocommerce/base-components/sidebar-layout';
|
||||
import { innerBlockAreas } from '@woocommerce/blocks-checkout';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { useForcedLayout } from '../../use-forced-layout';
|
||||
import { getAllowedBlocks } from '../../editor-utils';
|
||||
|
||||
export const Edit = ( { clientId }: { clientId: string } ): JSX.Element => {
|
||||
const blockProps = useBlockProps();
|
||||
const allowedBlocks = getAllowedBlocks( innerBlockAreas.CART_ITEMS );
|
||||
|
||||
useForcedLayout( {
|
||||
clientId,
|
||||
template: allowedBlocks,
|
||||
} );
|
||||
return (
|
||||
<Main className="wc-block-cart__main">
|
||||
<div { ...blockProps }>
|
||||
<InnerBlocks
|
||||
allowedBlocks={ allowedBlocks }
|
||||
templateLock={ false }
|
||||
renderAppender={ InnerBlocks.ButtonBlockAppender }
|
||||
/>
|
||||
</div>
|
||||
</Main>
|
||||
);
|
||||
};
|
||||
|
||||
export const Save = (): JSX.Element => {
|
||||
return (
|
||||
<div { ...useBlockProps.save() }>
|
||||
<InnerBlocks.Content />
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Main } from '@woocommerce/base-components/sidebar-layout';
|
||||
|
||||
const FrontendBlock = ( {
|
||||
children,
|
||||
}: {
|
||||
children: JSX.Element;
|
||||
} ): JSX.Element => {
|
||||
return <Main className="wc-block-cart__main">{ children }</Main>;
|
||||
};
|
||||
|
||||
export default FrontendBlock;
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Icon, column } from '@wordpress/icons';
|
||||
import { registerFeaturePluginBlockType } from '@woocommerce/block-settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Edit, Save } from './edit';
|
||||
import metadata from './block.json';
|
||||
|
||||
registerFeaturePluginBlockType( metadata, {
|
||||
icon: {
|
||||
src: <Icon icon={ column } />,
|
||||
foreground: '#874FB9',
|
||||
},
|
||||
edit: Edit,
|
||||
save: Save,
|
||||
} );
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "woocommerce/cart-totals-block",
|
||||
"version": "1.0.0",
|
||||
"title": "Cart Totals",
|
||||
"description": "Column containing the cart totals.",
|
||||
"category": "woocommerce",
|
||||
"supports": {
|
||||
"align": false,
|
||||
"html": false,
|
||||
"multiple": false,
|
||||
"reusable": false,
|
||||
"inserter": false
|
||||
},
|
||||
"attributes": {
|
||||
"checkbox": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"required": false
|
||||
}
|
||||
},
|
||||
"parent": [ "woocommerce/cart-i2" ],
|
||||
"textdomain": "woo-gutenberg-products-block",
|
||||
"apiVersion": 2
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
|
||||
import { Sidebar } from '@woocommerce/base-components/sidebar-layout';
|
||||
import { innerBlockAreas } from '@woocommerce/blocks-checkout';
|
||||
import Title from '@woocommerce/base-components/title';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
import { useForcedLayout } from '../../use-forced-layout';
|
||||
import { getAllowedBlocks } from '../../editor-utils';
|
||||
|
||||
export const Edit = ( { clientId }: { clientId: string } ): JSX.Element => {
|
||||
const blockProps = useBlockProps();
|
||||
const allowedBlocks = getAllowedBlocks( innerBlockAreas.CART_TOTALS );
|
||||
|
||||
useForcedLayout( {
|
||||
clientId,
|
||||
template: allowedBlocks,
|
||||
} );
|
||||
|
||||
return (
|
||||
<Sidebar className="wc-block-cart__sidebar">
|
||||
<div { ...blockProps }>
|
||||
<Title headingLevel="2" className="wc-block-cart__totals-title">
|
||||
{ __( 'Cart totals', 'woo-gutenberg-products-block' ) }
|
||||
</Title>
|
||||
<InnerBlocks
|
||||
allowedBlocks={ allowedBlocks }
|
||||
templateLock={ false }
|
||||
/>
|
||||
</div>
|
||||
</Sidebar>
|
||||
);
|
||||
};
|
||||
|
||||
export const Save = (): JSX.Element => {
|
||||
return (
|
||||
<div { ...useBlockProps.save() }>
|
||||
<InnerBlocks.Content />
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Sidebar } from '@woocommerce/base-components/sidebar-layout';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
const FrontendBlock = ( {
|
||||
children,
|
||||
}: {
|
||||
children: JSX.Element;
|
||||
} ): JSX.Element => {
|
||||
return <Sidebar className="wc-block-cart__sidebar">{ children }</Sidebar>;
|
||||
};
|
||||
|
||||
export default FrontendBlock;
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Icon, column } from '@wordpress/icons';
|
||||
import { registerFeaturePluginBlockType } from '@woocommerce/block-settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Edit, Save } from './edit';
|
||||
import metadata from './block.json';
|
||||
|
||||
registerFeaturePluginBlockType( metadata, {
|
||||
icon: {
|
||||
src: <Icon icon={ column } />,
|
||||
foreground: '#874FB9',
|
||||
},
|
||||
edit: Edit,
|
||||
save: Save,
|
||||
} );
|
|
@ -0,0 +1,8 @@
|
|||
.is-mobile,
|
||||
.is-small,
|
||||
.is-medium {
|
||||
.wc-block-cart__sidebar {
|
||||
margin-bottom: $gap-large;
|
||||
order: 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './cart-items-block';
|
||||
import './cart-totals-block';
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useLayoutEffect, useRef } from '@wordpress/element';
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
import {
|
||||
createBlock,
|
||||
getBlockType,
|
||||
Block,
|
||||
AttributeSource,
|
||||
} from '@wordpress/blocks';
|
||||
|
||||
const isBlockLocked = ( {
|
||||
attributes,
|
||||
}: {
|
||||
attributes: Record< string, AttributeSource.Attribute >;
|
||||
} ) => Boolean( attributes.lock?.remove || attributes.lock?.default?.remove );
|
||||
|
||||
export const useForcedLayout = ( {
|
||||
clientId,
|
||||
template,
|
||||
}: {
|
||||
clientId: string;
|
||||
template: Array< string >;
|
||||
} ): void => {
|
||||
const currentTemplate = useRef( template );
|
||||
const { insertBlock } = useDispatch( 'core/block-editor' );
|
||||
const { innerBlocks, templateTypes } = useSelect(
|
||||
( select ) => {
|
||||
return {
|
||||
innerBlocks: select( 'core/block-editor' ).getBlocks(
|
||||
clientId
|
||||
),
|
||||
templateTypes: currentTemplate.current.map( ( blockName ) =>
|
||||
getBlockType( blockName )
|
||||
),
|
||||
};
|
||||
},
|
||||
[ clientId, currentTemplate ]
|
||||
);
|
||||
/**
|
||||
* If the current inner blocks differ from the registered blocks, push the differences.
|
||||
*
|
||||
*/
|
||||
useLayoutEffect( () => {
|
||||
if ( ! clientId ) {
|
||||
return;
|
||||
}
|
||||
// Missing check to see if registered block is 'forced'
|
||||
templateTypes.forEach( ( block: Block | undefined ) => {
|
||||
if (
|
||||
block &&
|
||||
isBlockLocked( block ) &&
|
||||
! innerBlocks.find(
|
||||
( { name }: { name: string } ) => name === block.name
|
||||
)
|
||||
) {
|
||||
const newBlock = createBlock( block.name );
|
||||
insertBlock( newBlock, innerBlocks.length, clientId, false );
|
||||
}
|
||||
} );
|
||||
}, [ clientId, innerBlocks, insertBlock, templateTypes ] );
|
||||
};
|
|
@ -13,6 +13,9 @@ export enum innerBlockAreas {
|
|||
BILLING_ADDRESS = 'woocommerce/checkout-billing-address-block',
|
||||
SHIPPING_METHODS = 'woocommerce/checkout-shipping-methods-block',
|
||||
PAYMENT_METHODS = 'woocommerce/checkout-payment-methods-block',
|
||||
CART = 'woocommerce/cart-i2',
|
||||
CART_ITEMS = 'woocommerce/cart-items-block',
|
||||
CART_TOTALS = 'woocommerce/cart-totals-block',
|
||||
}
|
||||
|
||||
interface CheckoutBlockOptionsMetadata extends Partial< BlockConfiguration > {
|
||||
|
|
Loading…
Reference in New Issue