Update Product Image block to support inner blocks (#57586)

Implements inner blocks support in the Product Image block, allowing for more flexible layouts.
Refactors rendering logic to accommodate new inner blocks structure.

Backwards-compatibility is ensured by still parsing the legacy
`showOnSaleBadge` attribute when present, but not allowing users to modify
it via the Inspector Controls.

Older blocks will be migrated to the new form when the users access the editor.

---------

Co-authored-by: Karol Manijak <20098064+kmanijak@users.noreply.github.com>
This commit is contained in:
Lucio Giannotta 2025-06-02 13:59:48 +02:00 committed by GitHub
parent be9f2f08f4
commit 81a4be0cfc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 376 additions and 177 deletions

View file

@ -0,0 +1,4 @@
Significance: minor
Type: enhancement
Enhanced the Product Image block to act as a container for inner blocks, allowing for more flexible layouts.

View file

@ -14,7 +14,12 @@ import { withProductDataContext } from '@woocommerce/shared-hocs';
import { useStoreEvents } from '@woocommerce/base-context/hooks';
import type { HTMLAttributes } from 'react';
import { decodeEntities } from '@wordpress/html-entities';
import { isString, objectHasProp } from '@woocommerce/types';
import {
isString,
objectHasProp,
isEmpty,
ProductResponseItem,
} from '@woocommerce/types';
/**
* Internal dependencies
@ -22,6 +27,7 @@ import { isString, objectHasProp } from '@woocommerce/types';
import ProductSaleBadge from '../sale-badge/block';
import './style.scss';
import { BlockAttributes, ImageSizing } from './types';
import { isTryingToDisplayLegacySaleBadge } from './utils';
const ImagePlaceholder = ( props ): JSX.Element => {
return (
@ -102,18 +108,35 @@ const Image = ( {
type Props = BlockAttributes &
HTMLAttributes< HTMLDivElement > & { style?: Record< string, unknown > };
type LegacyProps = Props & {
product?: ProductResponseItem;
};
// props.product is not listed in the BlockAttributes explicitly,
// but it is implicitly passed from the All Products block.
// This is what distinguishes this block from the other usage of the Product Image component.
const displayLegacySaleBadge = ( props: LegacyProps ) => {
const { product } = props;
const isInAllProducts = ! isEmpty( product );
if ( isInAllProducts ) {
return isTryingToDisplayLegacySaleBadge( props.showSaleBadge );
}
return false;
};
export const Block = ( props: Props ): JSX.Element | null => {
const {
className,
imageSizing = ImageSizing.SINGLE,
showProductLink = true,
showSaleBadge,
saleBadgeAlign = 'right',
height,
width,
scale,
aspectRatio,
children,
className,
height,
imageSizing = ImageSizing.SINGLE,
scale,
showProductLink = true,
style,
width,
...restProps
} = props;
const styleProps = useStyleProps( props );
@ -123,20 +146,23 @@ export const Block = ( props: Props ): JSX.Element | null => {
if ( ! product.id ) {
return (
<div
className={ clsx(
className,
'wc-block-components-product-image',
{
[ `${ parentClassName }__product-image` ]:
parentClassName,
},
styleProps.className
) }
style={ styleProps.style }
>
<ImagePlaceholder />
</div>
<>
<div
className={ clsx(
className,
'wc-block-components-product-image',
{
[ `${ parentClassName }__product-image` ]:
parentClassName,
},
styleProps.className
) }
style={ styleProps.style }
>
<ImagePlaceholder />
</div>
{ children }
</>
);
}
const hasProductImages = !! product.images.length;
@ -158,42 +184,47 @@ export const Block = ( props: Props ): JSX.Element | null => {
};
return (
<div
className={ clsx(
className,
'wc-block-components-product-image',
{
[ `${ parentClassName }__product-image` ]: parentClassName,
},
styleProps.className
) }
style={ styleProps.style }
>
<ParentComponent { ...( showProductLink && anchorProps ) }>
{ !! showSaleBadge && (
<>
<div
className={ clsx(
className,
'wc-block-components-product-image',
{
[ `${ parentClassName }__product-image` ]:
parentClassName,
},
styleProps.className
) }
style={ styleProps.style }
>
{ /* For backwards compatibility in All Products blocks. */ }
{ displayLegacySaleBadge( props ) && (
<ProductSaleBadge
align={ saleBadgeAlign }
align={ props.saleBadgeAlign || 'right' }
{ ...restProps }
/>
) }
<Image
fallbackAlt={ decodeEntities( product.name ) }
image={ image }
loaded={ ! isLoading }
showFullSize={ imageSizing !== ImageSizing.THUMBNAIL }
width={ width }
height={ height }
scale={ scale }
aspectRatio={
objectHasProp( style, 'dimensions' ) &&
objectHasProp( style.dimensions, 'aspectRatio' ) &&
isString( style.dimensions.aspectRatio )
? style.dimensions.aspectRatio
: aspectRatio
}
/>
</ParentComponent>
</div>
<ParentComponent { ...( showProductLink && anchorProps ) }>
<Image
fallbackAlt={ decodeEntities( product.name ) }
image={ image }
loaded={ ! isLoading }
showFullSize={ imageSizing !== ImageSizing.THUMBNAIL }
width={ width }
height={ height }
scale={ scale }
aspectRatio={
objectHasProp( style, 'dimensions' ) &&
objectHasProp( style.dimensions, 'aspectRatio' ) &&
isString( style.dimensions.aspectRatio )
? style.dimensions.aspectRatio
: aspectRatio
}
/>
</ParentComponent>
</div>
{ children }
</>
);
};

View file

@ -0,0 +1,45 @@
/**
* External dependencies
*/
import { createBlock } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import metadata from './block.json';
import { BlockAttributes } from './types';
import save from '../save';
import { isTryingToDisplayLegacySaleBadge } from './utils';
// In v2, we're migrating the `showSaleBadge` attribute to an inner block.
const v1 = {
save,
attributes: metadata.attributes,
isEligible: ( { showSaleBadge }: BlockAttributes ) =>
isTryingToDisplayLegacySaleBadge( showSaleBadge ),
migrate: ( attributes: BlockAttributes ) => {
const { showSaleBadge, saleBadgeAlign } = attributes;
// If showSaleBadge is false, it means that the sale badge was explicitly set to false.
if ( showSaleBadge === false ) {
return [ attributes ];
}
// Otherwise, it's either:
// - true explicitly or
// - undefined (implicit true by default).
return [
{
...attributes,
showSaleBadge: false,
},
[
createBlock( 'woocommerce/product-sale-badge', {
align: saleBadgeAlign,
} ),
],
];
},
};
export default [ v1 ];

View file

@ -2,14 +2,23 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
import { createInterpolateElement, useEffect } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import {
InspectorControls,
useBlockProps,
useInnerBlocksProps,
store as blockEditorStore,
} from '@wordpress/block-editor';
import {
createInterpolateElement,
useEffect,
useRef,
} from '@wordpress/element';
import { getAdminLink, getSettingWithCoercion } from '@woocommerce/settings';
import { isBoolean } from '@woocommerce/types';
import type { BlockEditProps } from '@wordpress/blocks';
import { ProductQueryContext as Context } from '@woocommerce/blocks/product-query/types';
import {
Disabled,
PanelBody,
ToggleControl,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@ -27,39 +36,77 @@ import {
*/
import Block from './block';
import withProductSelector from '../shared/with-product-selector';
import { useIsDescendentOfSingleProductBlock } from '../shared/use-is-descendent-of-single-product-block';
import { BLOCK_ICON as icon } from './constants';
import { title, description } from './block.json';
import { BlockAttributes, ImageSizing } from './types';
import { ImageSizeSettings } from './image-size-settings';
type SaleBadgeAlignProps = 'left' | 'center' | 'right';
const TEMPLATE = [
[
'woocommerce/product-sale-badge',
{
align: 'right',
},
],
];
const Edit = ( {
attributes,
setAttributes,
context,
clientId,
}: BlockEditProps< BlockAttributes > & { context: Context } ): JSX.Element => {
const {
showProductLink,
imageSizing,
showSaleBadge,
saleBadgeAlign,
width,
height,
scale,
} = attributes;
const { showProductLink, imageSizing, width, height, scale } = attributes;
const ref = useRef< HTMLDivElement >( null );
const blockProps = useBlockProps( { style: { width, height } } );
const wasBlockJustInserted = useSelect(
( select ) =>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore method exists but not typed
select( blockEditorStore ).wasBlockJustInserted( clientId ),
[ clientId ]
);
const innerBlockProps = useInnerBlocksProps(
{
className: 'wc-block-components-product-image__inner-container',
},
{
dropZoneElement: ref.current,
template: wasBlockJustInserted ? TEMPLATE : undefined,
}
);
const isDescendentOfQueryLoop = Number.isFinite( context.queryId );
const { isDescendentOfSingleProductBlock } =
useIsDescendentOfSingleProductBlock( {
blockClientId: blockProps?.id,
} );
const isBlockTheme = getSettingWithCoercion(
'isBlockTheme',
false,
isBoolean
);
useEffect(
() => setAttributes( { isDescendentOfQueryLoop } ),
[ setAttributes, isDescendentOfQueryLoop ]
);
useEffect( () => {
if ( isDescendentOfQueryLoop || isDescendentOfSingleProductBlock ) {
setAttributes( {
isDescendentOfQueryLoop,
isDescendentOfSingleProductBlock,
showSaleBadge: false,
} );
} else {
setAttributes( {
isDescendentOfQueryLoop,
isDescendentOfSingleProductBlock,
} );
}
}, [
isDescendentOfQueryLoop,
isDescendentOfSingleProductBlock,
setAttributes,
] );
return (
<div { ...blockProps }>
@ -84,45 +131,6 @@ const Edit = ( {
} )
}
/>
<ToggleControl
label={ __( 'Show On-Sale Badge', 'woocommerce' ) }
help={ __(
'Display a “sale” badge if the product is on-sale.',
'woocommerce'
) }
checked={ showSaleBadge }
onChange={ () =>
setAttributes( {
showSaleBadge: ! showSaleBadge,
} )
}
/>
{ showSaleBadge && (
<ToggleGroupControl
label={ __(
'Sale Badge Alignment',
'woocommerce'
) }
isBlock
value={ saleBadgeAlign }
onChange={ ( value: SaleBadgeAlignProps ) =>
setAttributes( { saleBadgeAlign: value } )
}
>
<ToggleGroupControlOption
value="left"
label={ __( 'Left', 'woocommerce' ) }
/>
<ToggleGroupControlOption
value="center"
label={ __( 'Center', 'woocommerce' ) }
/>
<ToggleGroupControlOption
value="right"
label={ __( 'Right', 'woocommerce' ) }
/>
</ToggleGroupControl>
) }
<ToggleGroupControl
label={ __( 'Image Sizing', 'woocommerce' ) }
isBlock
@ -164,9 +172,9 @@ const Edit = ( {
</ToggleGroupControl>
</PanelBody>
</InspectorControls>
<Disabled>
<Block { ...{ ...attributes, ...context } } />
</Disabled>
<Block { ...{ ...attributes, ...context } }>
<div { ...innerBlockProps } />
</Block>
</div>
);
};

View file

@ -1,18 +1,31 @@
/**
* External dependencies
*/
import clsx from 'clsx';
import { InnerBlocks } from '@wordpress/block-editor';
import { registerBlockType } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import save from '../save';
import { BlockAttributes } from './types';
import deprecated from './deprecated';
import edit from './edit';
import { BLOCK_ICON as icon } from './constants';
import metadata from './block.json';
registerBlockType( metadata, {
save,
deprecated,
icon,
edit,
save: ( { attributes }: { attributes: BlockAttributes } ) => {
if (
attributes.isDescendentOfQueryLoop ||
attributes.isDescendentOfSingleProductBlock
) {
return <InnerBlocks.Content />;
}
return <div className={ clsx( 'is-loading', attributes.className ) } />;
},
} );

View file

@ -56,6 +56,10 @@
}
}
.editor-styles-wrapper .wc-block-components-product-image__inner-container {
justify-content: flex-start;
}
.is-loading .wc-block-components-product-image {
@include placeholder($include-border-radius: false);
width: auto;
@ -63,6 +67,18 @@
.wc-block-components-product-image {
margin: 0 0 $gap-small;
&__inner-container {
display: flex;
flex-direction: column;
padding: $gap-small;
position: absolute;
bottom: 0;
left: 0;
right: 0;
top: 0;
z-index: 1;
}
}
.wc-block-product-image__tools-panel .components-input-control {

View file

@ -16,8 +16,10 @@ export interface BlockAttributes {
saleBadgeAlign: 'left' | 'center' | 'right';
// Size of image to use.
imageSizing: ImageSizing;
// Whether or not be a children of Query Loop Block.
// Whether or not the block is within the context of a Query Loop Block.
isDescendentOfQueryLoop: boolean;
// Whether or not the block is within the context of a Single Product Block.
isDescendentOfSingleProductBlock: boolean;
// Height of the image.
height?: string;
// Width of the image.

View file

@ -0,0 +1,11 @@
export const isTryingToDisplayLegacySaleBadge = ( showSaleBadge?: boolean ) => {
// If the block is pristine, it doesn't have a showSaleBadge attribute
// but it is supposed to be `true` by default.
if ( showSaleBadge === undefined ) {
return true;
}
// If the block was edited, it will have a showSaleBadge attribute
// that we should respect.
return showSaleBadge;
};

View file

@ -4,7 +4,16 @@
"description": "Displays an on-sale badge if the product is on-sale.",
"category": "woocommerce-product-elements",
"attributes": {
"productId": { "type": "number", "default": 0 }
"productId": { "type": "number", "default": 0 },
"isDescendentOfQueryLoop": { "type": "boolean", "default": false },
"isDescendentOfSingleProductBlock": {
"type": "boolean",
"default": false
},
"isDescendentOfSingleProductTemplate": {
"type": "boolean",
"default": false
}
},
"supports": {
"interactivity": {

View file

@ -1,3 +1,6 @@
export interface BlockAttributes {
productId: number;
isDescendentOfQueryLoop?: boolean | undefined;
isDescendentOfSingleProductTemplate?: boolean;
isDescendentOfSingleProductBlock?: boolean;
}

View file

@ -126,7 +126,16 @@ export const INNER_BLOCKS_PRODUCT_TEMPLATE: InnerBlockTemplate = [
'woocommerce/product-image',
{
imageSizing: ImageSizing.THUMBNAIL,
showSaleBadge: false,
},
[
[
'woocommerce/product-sale-badge',
{
align: 'right',
},
],
],
],
[
'core/post-title',

View file

@ -31,10 +31,20 @@ export const DEFAULT_INNER_BLOCKS: InnerBlockTemplate[] = [
[
'woocommerce/product-image',
{
// Keep the attribute as false explicitly because we're using the inner block template
// that includes the product-sale-badge block.
showSaleBadge: false,
isDescendentOfSingleProductBlock: true,
imageSizing: ImageSizing.SINGLE,
},
[
[
'woocommerce/product-sale-badge',
{
align: 'right',
},
],
],
],
],
],

View file

@ -175,8 +175,10 @@ Note: some blocks might fail to render because they are based on products having
<!-- wp:column {"width":"66.66%"} -->
<div class="wp-block-column" style="flex-basis:66.66%"><!-- wp:woocommerce/product-collection {"query":{"perPage":9,"pages":0,"offset":0,"postType":"product","order":"asc","orderBy":"title"","search":"","exclude":[],"inherit":false,"taxQuery":{},"isProductCollectionBlock":true,"woocommerceOnSale":false,"woocommerceStockStatus":["instock","outofstock","onbackorder"],"woocommerceAttributes":[],"woocommerceHandPickedProducts":[]},"tagName":"div","displayLayout":{"type":"flex","columns":3}} -->
<div class="wp-block-woocommerce-product-collection"><!-- wp:woocommerce/product-template -->
<!-- wp:woocommerce/product-image {"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} /-->
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} -->
<!-- wp:woocommerce/product-sale-badge {"isDescendentOfQueryLoop":true,"align":"right"} /-->
<!-- /wp:woocommerce/product-image -->
<!-- wp:post-title {"textAlign":"center","level":3,"isLink":true,"style":{"spacing":{"margin":{"bottom":"0.75rem","top":"0"}}},"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title"} /-->
<!-- wp:woocommerce/product-price {"isDescendentOfQueryLoop":true,"textAlign":"center","fontSize":"small"} /-->

View file

@ -1,7 +1,8 @@
<!-- wp:woocommerce/product-collection {"queryId":0,"query":{"perPage":16,"pages":0,"offset":0,"postType":"product","order":"asc","orderBy":"title","search":"","exclude":[],"inherit":false,"taxQuery":{},"isProductCollectionBlock":true,"featured":false,"woocommerceOnSale":false,"woocommerceStockStatus":["instock","outofstock","onbackorder"],"woocommerceAttributes":[],"woocommerceHandPickedProducts":[]},"tagName":"div","displayLayout":{"type":"flex","columns":3,"shrinkColumns":true}} -->
<div class="wp-block-woocommerce-product-collection"><!-- wp:woocommerce/product-template -->
<!-- wp:woocommerce/product-image {"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} /-->
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} -->
<!-- wp:woocommerce/product-sale-badge {"isDescendentOfQueryLoop":true,"align":"right"} /-->
<!-- /wp:woocommerce/product-image -->
<!-- wp:post-title {"textAlign":"center","level":3,"isLink":true,"style":{"spacing":{"margin":{"bottom":"0.75rem","top":"0"}}},"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title"} /-->
<!-- wp:woocommerce/product-price {"isDescendentOfQueryLoop":true,"textAlign":"center","fontSize":"small"} /-->

View file

@ -12,8 +12,9 @@
<!-- wp:woocommerce/product-collection {"query":{"perPage":9,"pages":0,"offset":0,"postType":"product","order":"asc","orderBy":"title","search":"","exclude":[],"inherit":true,"taxQuery":[],"isProductCollectionBlock":true,"featured":false,"woocommerceOnSale":false,"woocommerceStockStatus":["instock","outofstock","onbackorder"],"woocommerceAttributes":[],"woocommerceHandPickedProducts":[]},"tagName":"div","displayLayout":{"type":"flex","columns":3,"shrinkColumns":true},"queryContextIncludes":["collection"]} -->
<div class='wp-block-woocommerce-product-collection'>
<!-- wp:woocommerce/product-template -->
<!-- wp:woocommerce/product-image {"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} /-->
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} -->
<!-- wp:woocommerce/product-sale-badge {"isDescendentOfQueryLoop":true,"align":"right"} /-->
<!-- /wp:woocommerce/product-image -->
<!-- wp:post-title {"textAlign":"center","level":3,"isLink":true,"style":{"spacing":{"margin":{"bottom":"0.75rem","top":"0"}}},"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title"} /-->
<!-- wp:woocommerce/product-price {"isDescendentOfQueryLoop":true,"textAlign":"center","fontSize":"small"} /-->

View file

@ -27,8 +27,9 @@
<!-- wp:woocommerce/product-collection {"query":{"perPage":9,"pages":0,"offset":0,"postType":"product","order":"asc","orderBy":"title","search":"","exclude":[],"inherit":true,"taxQuery":[],"isProductCollectionBlock":true,"featured":false,"woocommerceOnSale":false,"woocommerceStockStatus":["instock","outofstock","onbackorder"],"woocommerceAttributes":[],"woocommerceHandPickedProducts":[]},"tagName":"div","displayLayout":{"type":"flex","columns":3,"shrinkColumns":true},"queryContextIncludes":["collection"]} -->
<div class="wp-block-woocommerce-product-collection">
<!-- wp:woocommerce/product-template -->
<!-- wp:woocommerce/product-image {"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} /-->
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} -->
<!-- wp:woocommerce/product-sale-badge {"isDescendentOfQueryLoop":true,"align":"right"} /-->
<!-- /wp:woocommerce/product-image -->
<!-- wp:post-title {"textAlign":"center","level":3,"isLink":true,"style":{"spacing":{"margin":{"bottom":"0.75rem","top":"0"}}},"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title"} /-->
<!-- wp:woocommerce/product-price {"isDescendentOfQueryLoop":true,"textAlign":"center","fontSize":"small"} /-->

View file

@ -66,8 +66,9 @@
<!-- wp:woocommerce/product-collection {"query":{"perPage":9,"pages":0,"offset":0,"postType":"product","order":"asc","orderBy":"title","search":"","exclude":[],"inherit":true,"taxQuery":[],"isProductCollectionBlock":true,"featured":false,"woocommerceOnSale":false,"woocommerceStockStatus":["instock","outofstock","onbackorder"],"woocommerceAttributes":[],"woocommerceHandPickedProducts":[]},"tagName":"div","displayLayout":{"type":"flex","columns":3,"shrinkColumns":true},"queryContextIncludes":["collection"]} -->
<div class="wp-block-woocommerce-product-collection">
<!-- wp:woocommerce/product-template -->
<!-- wp:woocommerce/product-image {"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} /-->
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} -->
<!-- wp:woocommerce/product-sale-badge {"isDescendentOfQueryLoop":true,"align":"right"} /-->
<!-- /wp:woocommerce/product-image -->
<!-- wp:post-title {"textAlign":"center","level":3,"isLink":true,"style":{"spacing":{"margin":{"bottom":"0.75rem","top":"0"}}},"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title"} /-->
<!-- wp:woocommerce/product-price {"isDescendentOfQueryLoop":true,"textAlign":"center","fontSize":"small"} /-->

View file

@ -9,8 +9,9 @@
<!-- wp:woocommerce/product-collection {"query":{"perPage":9,"pages":0,"offset":0,"postType":"product","order":"asc","orderBy":"title","search":"","exclude":[],"inherit":true,"taxQuery":[],"isProductCollectionBlock":true,"featured":false,"woocommerceOnSale":false,"woocommerceStockStatus":["instock","outofstock","onbackorder"],"woocommerceAttributes":[],"woocommerceHandPickedProducts":[]},"tagName":"div","displayLayout":{"type":"flex","columns":3,"shrinkColumns":true},"queryContextIncludes":["collection"]} -->
<div class='wp-block-woocommerce-product-collection'>
<!-- wp:woocommerce/product-template -->
<!-- wp:woocommerce/product-image {"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} /-->
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} -->
<!-- wp:woocommerce/product-sale-badge {"isDescendentOfQueryLoop":true,"align":"right"} /-->
<!-- /wp:woocommerce/product-image -->
<!-- wp:post-title {"textAlign":"center","level":3,"isLink":true,"style":{"spacing":{"margin":{"bottom":"0.75rem","top":"0"}}},"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title"} /-->
<!-- wp:woocommerce/product-price {"isDescendentOfQueryLoop":true,"textAlign":"center","fontSize":"small"} /-->

View file

@ -9,8 +9,9 @@
<!-- wp:woocommerce/product-collection {"query":{"perPage":9,"pages":0,"offset":0,"postType":"product","order":"asc","orderBy":"title","search":"","exclude":[],"inherit":true,"taxQuery":[],"isProductCollectionBlock":true,"featured":false,"woocommerceOnSale":false,"woocommerceStockStatus":["instock","outofstock","onbackorder"],"woocommerceAttributes":[],"woocommerceHandPickedProducts":[]},"tagName":"div","displayLayout":{"type":"flex","columns":3,"shrinkColumns":true},"queryContextIncludes":["collection"]} -->
<div class="wp-block-woocommerce-product-collection">
<!-- wp:woocommerce/product-template -->
<!-- wp:woocommerce/product-image {"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} /-->
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} -->
<!-- wp:woocommerce/product-sale-badge {"isDescendentOfQueryLoop":true,"align":"right"} /-->
<!-- /wp:woocommerce/product-image -->
<!-- wp:post-title {"textAlign":"center","level":3,"isLink":true,"style":{"spacing":{"margin":{"bottom":"0.75rem","top":"0"}}},"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title"} /-->
<!-- wp:woocommerce/product-price {"isDescendentOfQueryLoop":true,"textAlign":"center","fontSize":"small"} /-->

View file

@ -9,7 +9,9 @@
<!-- wp:woocommerce/product-collection {"query":{"perPage":9,"pages":0,"offset":0,"postType":"product","order":"asc","orderBy":"title","search":"","exclude":[],"inherit":true,"taxQuery":[],"isProductCollectionBlock":true,"featured":false,"woocommerceOnSale":false,"woocommerceStockStatus":["instock","outofstock","onbackorder"],"woocommerceAttributes":[],"woocommerceHandPickedProducts":[]},"tagName":"div","displayLayout":{"type":"flex","columns":3,"shrinkColumns":true},"queryContextIncludes":["collection"]} -->
<div class='wp-block-woocommerce-product-collection'>
<!-- wp:woocommerce/product-template -->
<!-- wp:woocommerce/product-image {"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} /-->
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} -->
<!-- wp:woocommerce/product-sale-badge {"isDescendentOfQueryLoop":true,"align":"right"} /-->
<!-- /wp:woocommerce/product-image -->
<!-- wp:post-title {"textAlign":"center","level":3,"isLink":true,"style":{"spacing":{"margin":{"bottom":"0.75rem","top":"0"}}},"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title"} /-->

View file

@ -11,7 +11,9 @@
<!-- wp:woocommerce/product-collection {"query":{"perPage":3,"pages":0,"offset":0,"postType":"product","order":"asc","orderBy":"title","search":"","exclude":[],"inherit":false,"taxQuery":{},"isProductCollectionBlock":true,"woocommerceOnSale":false,"woocommerceStockStatus":["instock","outofstock","onbackorder"],"woocommerceAttributes":[],"woocommerceHandPickedProducts":[]},"tagName":"div","dimensions":{"widthType":"fill","fixedWidth":""},"displayLayout":{"type":"flex","columns":3},"align":"wide"} -->
<div class="wp-block-woocommerce-product-collection alignwide">
<!-- wp:woocommerce/product-template -->
<!-- wp:woocommerce/product-image {"aspectRatio":"3/5","imageSizing":"single","isDescendentOfQueryLoop":true} /-->
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"aspectRatio":"3/5","imageSizing":"single","isDescendentOfQueryLoop":true} -->
<!-- wp:woocommerce/product-sale-badge {"isDescendentOfQueryLoop":true,"align":"right"} /-->
<!-- /wp:woocommerce/product-image -->
<!-- wp:woocommerce/product-rating {"isDescendentOfQueryLoop":true,"textAlign":"center"} /-->

View file

@ -25,7 +25,9 @@ $products_title = __( 'Staff picks', 'woocommerce' );
<!-- wp:woocommerce/product-collection {"queryId":2,"query":{"perPage":4,"pages":0,"offset":0,"postType":"product","order":"asc","orderBy":"title","search":"","exclude":[],"inherit":false,"taxQuery":[],"isProductCollectionBlock":true,"woocommerceOnSale":false,"woocommerceStockStatus":["instock","outofstock","onbackorder"],"woocommerceAttributes":[],"woocommerceHandPickedProducts":[]},"tagName":"div","dimensions":{"widthType":"fill","fixedWidth":""},"displayLayout":{"type":"flex","columns":4},"queryContextIncludes":["collection"],"align":"wide"} -->
<div class="wp-block-woocommerce-product-collection alignwide">
<!-- wp:woocommerce/product-template -->
<!-- wp:woocommerce/product-image {"isDescendentOfQueryLoop":true,"aspectRatio":"3/5"} /-->
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"isDescendentOfQueryLoop":true,"aspectRatio":"3/5"} -->
<!-- wp:woocommerce/product-sale-badge {"isDescendentOfQueryLoop":true,"align":"right"} /-->
<!-- /wp:woocommerce/product-image -->
<!-- wp:post-title {"textAlign":"center","level":2,"isLink":true,"style":{"spacing":{"margin":{"bottom":"0.75rem","top":"0"}}},"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title","style":{"typography":{"lineHeight":"1.4"}}} /-->

View file

@ -25,7 +25,9 @@ $products_title = __( 'Our latest and greatest', 'woocommerce' );
<!-- wp:woocommerce/product-collection {"queryId":7,"query":{"perPage":5,"pages":0,"offset":0,"postType":"product","order":"asc","orderBy":"title","search":"","exclude":[],"inherit":false,"taxQuery":[],"isProductCollectionBlock":true,"woocommerceOnSale":false,"woocommerceStockStatus":["instock","outofstock","onbackorder"],"woocommerceAttributes":[],"woocommerceHandPickedProducts":[]},"tagName":"div","dimensions":{"widthType":"fill","fixedWidth":""},"displayLayout":{"type":"flex","columns":5},"queryContextIncludes":["collection"],"align":"wide"} -->
<div class="wp-block-woocommerce-product-collection alignwide">
<!-- wp:woocommerce/product-template -->
<!-- wp:woocommerce/product-image {"isDescendentOfQueryLoop":true,"aspectRatio":"3/5"} /-->
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"isDescendentOfQueryLoop":true,"aspectRatio":"3/5"} -->
<!-- wp:woocommerce/product-sale-badge {"isDescendentOfQueryLoop":true,"align":"right"} /-->
<!-- /wp:woocommerce/product-image -->
<!-- wp:post-title {"textAlign":"left","level":2,"isLink":true,"style":{"spacing":{"margin":{"bottom":"0.75rem","top":"0"}}},"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title","style":{"typography":{"lineHeight":"1.4"}}} /-->

View file

@ -29,7 +29,9 @@ $collection_title = __( 'Shop new arrivals', 'woocommerce' );
<!-- wp:woocommerce/product-collection {"queryId":2,"query":{"perPage":5,"pages":0,"offset":0,"postType":"product","order":"asc","orderBy":"title","search":"","exclude":[],"inherit":false,"taxQuery":[],"isProductCollectionBlock":true,"woocommerceOnSale":false,"woocommerceStockStatus":["instock","outofstock","onbackorder"],"woocommerceAttributes":[],"woocommerceHandPickedProducts":[]},"tagName":"div","dimensions":{"widthType":"fill","fixedWidth":""},"displayLayout":{"type":"flex","columns":5},"queryContextIncludes":["collection"],"align":"wide"} -->
<div class="wp-block-woocommerce-product-collection alignwide">
<!-- wp:woocommerce/product-template -->
<!-- wp:woocommerce/product-image {"isDescendentOfQueryLoop":true,"aspectRatio":"1"} /-->
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"isDescendentOfQueryLoop":true,"aspectRatio":"1"} -->
<!-- wp:woocommerce/product-sale-badge {"isDescendentOfQueryLoop":true,"align":"right"} /-->
<!-- /wp:woocommerce/product-image -->
<!-- wp:post-title {"textAlign":"left","level":2,"isLink":true,"style":{"spacing":{"margin":{"bottom":"0.75rem","top":"0"}}},"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title","style":{"typography":{"lineHeight":"1.4"}}} /-->

View file

@ -27,6 +27,9 @@ $products_title = __( 'Bestsellers', 'woocommerce' );
<div class="wp-block-woocommerce-product-collection alignwide">
<!-- wp:woocommerce/product-template -->
<!-- wp:woocommerce/product-image {"isDescendentOfQueryLoop":true,"aspectRatio":"3/4"} /-->
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"isDescendentOfQueryLoop":true,"aspectRatio":"3/4"} -->
<!-- wp:woocommerce/product-sale-badge {"isDescendentOfQueryLoop":true,"align":"right"} /-->
<!-- /wp:woocommerce/product-image -->
<!-- wp:post-title {"textAlign":"center","level":6,"isLink":true,"style":{"typography":{"textTransform":"capitalize"},"spacing":{"margin":{"top":"12px","bottom":"8px"}}}} /-->

View file

@ -21,7 +21,9 @@
<!-- /wp:heading -->
<!-- wp:woocommerce/product-template -->
<!-- wp:woocommerce/product-image {"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} /-->
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} -->
<!-- wp:woocommerce/product-sale-badge {"isDescendentOfQueryLoop":true,"align":"right"} /-->
<!-- /wp:woocommerce/product-image -->
<!-- wp:post-title {"textAlign":"center","level":3,"isLink":true,"style":{"spacing":{"margin":{"bottom":"0.75rem","top":"0"}}},"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title"} /-->
<!-- wp:woocommerce/product-price {"isDescendentOfQueryLoop":true,"textAlign":"center","fontSize":"small"} /-->
<!-- wp:woocommerce/product-button {"textAlign":"center","isDescendentOfQueryLoop":true,"fontSize":"small"} /-->

View file

@ -45,13 +45,12 @@ class ProductImage extends AbstractBlock {
private function parse_attributes( $attributes ) {
// These should match what's set in JS `registerBlockType`.
$defaults = array(
'showProductLink' => true,
'showSaleBadge' => true,
'saleBadgeAlign' => 'right',
'imageSizing' => 'single',
'productId' => 'number',
'isDescendentOfQueryLoop' => 'false',
'scale' => 'cover',
'showProductLink' => true,
'imageSizing' => 'single',
'productId' => 'number',
'isDescendentOfQueryLoop' => 'false',
'isDescendentOfSingleProductBlock' => 'false',
'scale' => 'cover',
);
return wp_parse_args( $attributes, $defaults );
@ -65,7 +64,11 @@ class ProductImage extends AbstractBlock {
* @return string
*/
private function render_on_sale_badge( $product, $attributes ) {
if ( ! $product->is_on_sale() || false === $attributes['showSaleBadge'] ) {
if (
! $product->is_on_sale()
|| ! isset( $attributes['showSaleBadge'] )
|| ( isset( $attributes['showSaleBadge'] ) && false === $attributes['showSaleBadge'] )
) {
return '';
}
@ -93,27 +96,33 @@ class ProductImage extends AbstractBlock {
* @param string $on_sale_badge Return value from $render_image.
* @param string $product_image Return value from $render_on_sale_badge.
* @param array $attributes Attributes.
* @param string $inner_blocks_content Rendered HTML of inner blocks.
* @return string
*/
private function render_anchor( $product, $on_sale_badge, $product_image, $attributes ) {
private function render_anchor( $product, $on_sale_badge, $product_image, $attributes, $inner_blocks_content ) {
$product_permalink = $product->get_permalink();
$is_link = true === $attributes['showProductLink'];
$pointer_events = $is_link ? '' : 'pointer-events: none;';
$is_link = isset( $attributes['showProductLink'] ) ? $attributes['showProductLink'] : true;
$href_attribute = $is_link ? sprintf( 'href="%s"', esc_url( $product_permalink ) ) : 'href="#" onclick="return false;"';
$wrapper_style = ! $is_link ? 'pointer-events: none; cursor: default;' : '';
$directive = $is_link ? 'data-wp-on--click="woocommerce/product-collection::actions.viewProduct"' : '';
$inner_blocks_container = sprintf(
'<div class="wc-block-components-product-image__inner-container">%s</div>',
$inner_blocks_content
);
return sprintf(
'<a href="%1$s" style="%2$s" %3$s>%4$s %5$s</a>',
$product_permalink,
$pointer_events,
'<a %1$s style="%2$s" %3$s>%4$s%5$s%6$s</a>',
$href_attribute,
esc_attr( $wrapper_style ),
$directive,
$on_sale_badge,
$product_image
$product_image,
$inner_blocks_container
);
}
/**
* Render Image.
*
@ -178,7 +187,6 @@ class ProductImage extends AbstractBlock {
$this->asset_data_registry->add( 'isBlockTheme', wp_is_block_theme() );
}
/**
* Include and render the block
*
@ -188,11 +196,6 @@ class ProductImage extends AbstractBlock {
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
if ( ! empty( $content ) ) {
parent::register_block_type_assets();
$this->register_chunk_translations( [ $this->block_name ] );
return $content;
}
$parsed_attributes = $this->parse_attributes( $attributes );
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'extra_classes' ) );
@ -218,19 +221,21 @@ class ProductImage extends AbstractBlock {
);
if ( $product ) {
return sprintf(
'<div %1$s>
%2$s
</div>',
$wrapper_attributes,
$this->render_anchor(
$product,
$this->render_on_sale_badge( $product, $parsed_attributes ),
$this->render_image( $product, $parsed_attributes ),
$parsed_attributes
)
$inner_content = $this->render_anchor(
$product,
$this->render_on_sale_badge( $product, $parsed_attributes ),
$this->render_image( $product, $parsed_attributes ),
$attributes,
$content
);
return sprintf(
'<div %1$s>%2$s</div>',
$wrapper_attributes,
$inner_content
);
}
return '';
}
}

View file

@ -19,7 +19,9 @@
<!-- wp:woocommerce/product-collection {"queryId":0,"query":{"woocommerceAttributes":[],"woocommerceStockStatus":["instock","outofstock","onbackorder"],"taxQuery":{},"isProductCollectionBlock":true,"perPage":10,"pages":0,"offset":0,"postType":"product","order":"asc","orderBy":"title","author":"","search":"","exclude":[],"sticky":"","inherit":true},"tagName":"div","dimensions":{"widthType":"fill","fixedWidth":""},"displayLayout":{"type":"flex","columns":3,"shrinkColumns":true},"convertedFromProducts":false,"queryContextIncludes":["collection"],"align":"wide"} -->
<div class="wp-block-woocommerce-product-collection alignwide">
<!-- wp:woocommerce/product-template -->
<!-- wp:woocommerce/product-image {"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} /-->
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} -->
<!-- wp:woocommerce/product-sale-badge {"isDescendentOfQueryLoop":true,"align":"right"} /-->
<!-- /wp:woocommerce/product-image -->
<!-- wp:post-title {"textAlign":"center","level":2,"isLink":true,"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title","style":{"typography":{"lineHeight":"1.4"}}} /-->
<!-- wp:woocommerce/product-price {"textAlign":"center","isDescendentOfQueryLoop":true,"fontSize":"small","style":{"spacing":{"margin":{"bottom":"1rem"}}}} /-->
<!-- wp:woocommerce/product-button {"textAlign":"center","isDescendentOfQueryLoop":true,"fontSize":"small","style":{"spacing":{"margin":{"bottom":"1rem"}}}} /-->

View file

@ -17,7 +17,9 @@
<!-- wp:woocommerce/product-collection {"queryId":0,"query":{"woocommerceAttributes":[],"woocommerceStockStatus":["instock","outofstock","onbackorder"],"taxQuery":{},"isProductCollectionBlock":true,"perPage":10,"pages":0,"offset":0,"postType":"product","order":"asc","orderBy":"title","author":"","search":"","exclude":[],"sticky":"","inherit":true},"tagName":"div","dimensions":{"widthType":"fill","fixedWidth":""},"displayLayout":{"type":"flex","columns":3,"shrinkColumns":true},"convertedFromProducts":false,"queryContextIncludes":["collection"],"align":"wide"} -->
<div class="wp-block-woocommerce-product-collection alignwide">
<!-- wp:woocommerce/product-template -->
<!-- wp:woocommerce/product-image {"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} /-->
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} -->
<!-- wp:woocommerce/product-sale-badge {"isDescendentOfQueryLoop":true,"align":"right"} /-->
<!-- /wp:woocommerce/product-image -->
<!-- wp:post-title {"textAlign":"center","level":2,"isLink":true,"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title","style":{"typography":{"lineHeight":"1.4"}}} /-->
<!-- wp:woocommerce/product-price {"textAlign":"center","isDescendentOfQueryLoop":true,"fontSize":"small","style":{"spacing":{"margin":{"bottom":"1rem"}}}} /-->
<!-- wp:woocommerce/product-button {"textAlign":"center","isDescendentOfQueryLoop":true,"fontSize":"small","style":{"spacing":{"margin":{"bottom":"1rem"}}}} /-->

View file

@ -21,7 +21,9 @@
<!-- wp:woocommerce/product-collection {"queryId":0,"query":{"woocommerceAttributes":[],"woocommerceStockStatus":["instock","outofstock","onbackorder"],"taxQuery":{},"isProductCollectionBlock":true,"perPage":10,"pages":0,"offset":0,"postType":"product","order":"asc","orderBy":"title","author":"","search":"","exclude":[],"sticky":"","inherit":true},"tagName":"div","dimensions":{"widthType":"fill","fixedWidth":""},"displayLayout":{"type":"flex","columns":3,"shrinkColumns":true},"convertedFromProducts":false,"queryContextIncludes":["collection"],"align":"wide"} -->
<div class="wp-block-woocommerce-product-collection alignwide">
<!-- wp:woocommerce/product-template -->
<!-- wp:woocommerce/product-image {"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} /-->
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} -->
<!-- wp:woocommerce/product-sale-badge {"isDescendentOfQueryLoop":true,"align":"right"} /-->
<!-- /wp:woocommerce/product-image -->
<!-- wp:post-title {"textAlign":"center","level":2,"isLink":true,"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title","style":{"typography":{"lineHeight":"1.4"}}} /-->
<!-- wp:woocommerce/product-price {"textAlign":"center","isDescendentOfQueryLoop":true,"fontSize":"small","style":{"spacing":{"margin":{"bottom":"1rem"}}}} /-->
<!-- wp:woocommerce/product-button {"textAlign":"center","isDescendentOfQueryLoop":true,"fontSize":"small","style":{"spacing":{"margin":{"bottom":"1rem"}}}} /-->

View file

@ -21,7 +21,9 @@
<!-- wp:woocommerce/product-collection {"queryId":0,"query":{"woocommerceAttributes":[],"woocommerceStockStatus":["instock","outofstock","onbackorder"],"taxQuery":{},"isProductCollectionBlock":true,"perPage":10,"pages":0,"offset":0,"postType":"product","order":"asc","orderBy":"title","author":"","search":"","exclude":[],"sticky":"","inherit":true},"tagName":"div","dimensions":{"widthType":"fill","fixedWidth":""},"displayLayout":{"type":"flex","columns":3,"shrinkColumns":true},"convertedFromProducts":false,"queryContextIncludes":["collection"],"align":"wide"} -->
<div class="wp-block-woocommerce-product-collection alignwide">
<!-- wp:woocommerce/product-template -->
<!-- wp:woocommerce/product-image {"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} /-->
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} -->
<!-- wp:woocommerce/product-sale-badge {"isDescendentOfQueryLoop":true,"align":"right"} /-->
<!-- /wp:woocommerce/product-image -->
<!-- wp:post-title {"textAlign":"center","level":2,"isLink":true,"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title","style":{"typography":{"lineHeight":"1.4"}}} /-->
<!-- wp:woocommerce/product-price {"textAlign":"center","isDescendentOfQueryLoop":true,"fontSize":"small","style":{"spacing":{"margin":{"bottom":"1rem"}}}} /-->
<!-- wp:woocommerce/product-button {"textAlign":"center","isDescendentOfQueryLoop":true,"fontSize":"small","style":{"spacing":{"margin":{"bottom":"1rem"}}}} /-->

View file

@ -21,7 +21,9 @@
<!-- wp:woocommerce/product-collection {"queryId":0,"query":{"woocommerceAttributes":[],"woocommerceStockStatus":["instock","outofstock","onbackorder"],"taxQuery":{},"isProductCollectionBlock":true,"perPage":10,"pages":0,"offset":0,"postType":"product","order":"asc","orderBy":"title","author":"","search":"","exclude":[],"sticky":"","inherit":true},"tagName":"div","dimensions":{"widthType":"fill","fixedWidth":""},"displayLayout":{"type":"flex","columns":3,"shrinkColumns":true},"convertedFromProducts":false,"queryContextIncludes":["collection"],"align":"wide"} -->
<div class="wp-block-woocommerce-product-collection alignwide">
<!-- wp:woocommerce/product-template -->
<!-- wp:woocommerce/product-image {"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} /-->
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} -->
<!-- wp:woocommerce/product-sale-badge {"isDescendentOfQueryLoop":true,"align":"right"} /-->
<!-- /wp:woocommerce/product-image -->
<!-- wp:post-title {"textAlign":"center","level":2,"isLink":true,"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title","style":{"typography":{"lineHeight":"1.4"}}} /-->
<!-- wp:woocommerce/product-price {"textAlign":"center","isDescendentOfQueryLoop":true,"fontSize":"small","style":{"spacing":{"margin":{"bottom":"1rem"}}}} /-->
<!-- wp:woocommerce/product-button {"textAlign":"center","isDescendentOfQueryLoop":true,"fontSize":"small","style":{"spacing":{"margin":{"bottom":"1rem"}}}} /-->