[Experimental] [CYS - Full Composability]: Add tooltip when user clicks on pattern (#47583)

* CYS: Show popover when the user clicks on the pattern

* Add changefile(s) from automation for the following project(s): woocommerce

* fix errors

* add documentation

* fix calculation

* improve performance

* remove not necessary else branch

* restore pnpm-lock

* improve function naming

* fix label

* improve logic

* remove dispatch

* remove console.log

* fix setLogoBlockIds

* fix build

---------

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Luigi Teschio 2024-06-05 14:27:35 +02:00 committed by GitHub
parent 1f736a0a22
commit 50b29d20f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 577 additions and 332 deletions

View File

@ -5,9 +5,9 @@
/**
* External dependencies
*/
import { useResizeObserver, pure, useRefEffect } from '@wordpress/compose';
import { useResizeObserver, pure } from '@wordpress/compose';
import { useContext, useMemo, useState } from '@wordpress/element';
import { Disabled } from '@wordpress/components';
import { Disabled, Popover } from '@wordpress/components';
import {
__unstableEditorStyles as EditorStyles,
__unstableIframe as Iframe,
@ -18,7 +18,6 @@ import {
} from '@wordpress/block-editor';
// @ts-ignore No types for this exist yet.
import { unlock } from '@wordpress/edit-site/build-module/lock-unlock';
import { noop } from 'lodash';
/**
* Internal dependencies
@ -32,7 +31,11 @@ import { CustomizeStoreContext } from '.';
import { isAIFlow } from '../guards';
import { selectBlockOnHover } from './utils/select-block-on-hover';
import { useDispatch, useSelect } from '@wordpress/data';
import { isFullComposabilityFeatureAndAPIAvailable } from './utils/is-full-composability-enabled';
import { PopoverStatus, usePopoverHandler } from './hooks/use-popover-handler';
import { noop } from 'lodash';
import { useAddAutoBlockPreviewEventListenersAndObservers } from './hooks/auto-block-preview-event-listener';
import { IsResizingContext } from './resizable-frame';
import { __ } from '@wordpress/i18n';
// @ts-ignore No types for this exist yet.
const { Provider: DisabledProvider } = Disabled.Context;
@ -52,14 +55,13 @@ export type ScaledBlockPreviewProps = {
[ key: string ]: unknown;
};
additionalStyles: string;
onClickNavigationItem: ( event: MouseEvent ) => void;
isNavigable?: boolean;
isScrollable?: boolean;
autoScale?: boolean;
setLogoBlockContext?: boolean;
CustomIframeComponent?: React.ComponentType<
Parameters< typeof Iframe >[ 0 ]
>;
isPatternPreview: boolean;
};
function ScaledBlockPreview( {
@ -67,17 +69,15 @@ function ScaledBlockPreview( {
containerWidth,
settings,
additionalStyles,
onClickNavigationItem,
isNavigable = false,
isScrollable = true,
autoScale = true,
setLogoBlockContext = false,
isPatternPreview,
CustomIframeComponent = Iframe,
}: ScaledBlockPreviewProps ) {
const [ contentHeight, setContentHeight ] = useState< number | null >(
null
);
const { setLogoBlockIds } = useContext( LogoBlockContext );
const { setLogoBlockIds, logoBlockIds } = useContext( LogoBlockContext );
const [ fontFamilies ] = useGlobalSetting(
'typography.fontFamilies.theme'
) as [ FontFamily[] ];
@ -91,6 +91,15 @@ function ScaledBlockPreview( {
viewportWidth = containerWidth;
}
const [ iframeRef, setIframeRef ] = useState< HTMLElement | null >( null );
const [
popoverStatus,
virtualElement,
updatePopoverPosition,
setPopoverStatus,
] = usePopoverHandler();
// @ts-expect-error No types for this exist yet.
const { selectBlock, setBlockEditingMode } =
useDispatch( blockEditorStore );
@ -117,222 +126,108 @@ function ScaledBlockPreview( {
const aspectRatio = contentHeight
? containerWidth / ( contentHeight * scale )
: 0;
// Initialize on render instead of module top level, to avoid circular dependency issues.
MemoizedBlockList = MemoizedBlockList || pure( BlockList );
const updateIframeContent = ( bodyElement: HTMLBodyElement ) => {
let navigationContainers: NodeListOf< HTMLDivElement >;
let siteTitles: NodeListOf< HTMLAnchorElement >;
const isResizing = useContext( IsResizingContext );
const onMouseMove = ( event: MouseEvent ) => {
event.stopImmediatePropagation();
};
const onClickNavigation = ( event: MouseEvent ) => {
event.preventDefault();
onClickNavigationItem( event );
};
const possiblyRemoveAllListeners = () => {
bodyElement.removeEventListener( 'mousemove', onMouseMove, false );
if ( navigationContainers ) {
navigationContainers.forEach( ( element ) => {
element.removeEventListener( 'click', onClickNavigation );
} );
}
if ( siteTitles ) {
siteTitles.forEach( ( element ) => {
element.removeEventListener( 'click', onClickNavigation );
} );
}
};
const enableNavigation = () => {
// Remove contenteditable and inert attributes from editable elements so that users can click on navigation links.
bodyElement
.querySelectorAll(
'.block-editor-rich-text__editable[contenteditable="true"]'
)
.forEach( ( element ) => {
element.removeAttribute( 'contenteditable' );
} );
bodyElement
.querySelectorAll( '*[inert="true"]' )
.forEach( ( element ) => {
element.removeAttribute( 'inert' );
} );
possiblyRemoveAllListeners();
navigationContainers = bodyElement.querySelectorAll(
'.wp-block-navigation__container'
);
navigationContainers.forEach( ( element ) => {
element.addEventListener( 'click', onClickNavigation, true );
} );
siteTitles = bodyElement.querySelectorAll(
'.wp-block-site-title a'
);
siteTitles.forEach( ( element ) => {
element.addEventListener( 'click', onClickNavigation, true );
} );
};
const findAndSetLogoBlock = () => {
// Get the current logo block client ID from DOM and set it in the logo block context. This is used for the logo settings. See: ./sidebar/sidebar-navigation-screen-logo.tsx
// Ideally, we should be able to get the logo block client ID from the block editor store but it is not available.
// We should update this code once the there is a selector in the block editor store that can be used to get the logo block client ID.
const siteLogos = bodyElement.querySelectorAll(
'.wp-block-site-logo'
);
const logoBlockIds = Array.from( siteLogos )
.map( ( siteLogo ) => {
return siteLogo.getAttribute( 'data-block' );
} )
.filter( Boolean ) as string[];
setLogoBlockIds( logoBlockIds );
};
const onChange = () => {
if ( autoScale ) {
const rootContainer =
bodyElement.querySelector( '.is-root-container' );
setContentHeight(
rootContainer ? rootContainer.clientHeight : null
);
}
if ( isNavigable ) {
enableNavigation();
}
if ( setLogoBlockContext ) {
findAndSetLogoBlock();
}
};
// Stop mousemove event listener to disable block tool insertion feature.
bodyElement.addEventListener( 'mousemove', onMouseMove, true );
if ( isFullComposabilityFeatureAndAPIAvailable() ) {
bodyElement.addEventListener( 'click', ( event ) => {
selectBlockOnHover( event, {
selectBlockByClientId: selectBlock,
getBlockParents,
setBlockEditingMode,
} );
} );
bodyElement.addEventListener(
'mouseover',
( event ) => {
selectBlockOnHover( event, {
selectBlockByClientId: selectBlock,
getBlockParents,
setBlockEditingMode: () => void 0,
} );
},
true
);
useAddAutoBlockPreviewEventListenersAndObservers(
{
documentElement: iframeRef,
autoScale,
isPatternPreview,
contentHeight,
logoBlockIds,
},
{
selectBlockOnHover,
selectBlock,
getBlockParents,
setBlockEditingMode,
updatePopoverPosition,
setLogoBlockIds,
setContentHeight,
setPopoverStatus,
}
const observer = new window.MutationObserver( onChange );
observer.observe( bodyElement, {
attributes: true,
characterData: false,
subtree: true,
childList: true,
} );
return () => {
observer.disconnect();
possiblyRemoveAllListeners();
if ( setLogoBlockContext ) {
setLogoBlockIds( [] );
}
};
};
);
return (
<DisabledProvider value={ true }>
<div
className="block-editor-block-preview__content"
style={
autoScale
? {
transform: `scale(${ scale })`,
// Using width + aspect-ratio instead of height here triggers browsers' native
// handling of scrollbar's visibility. It prevents the flickering issue seen
// in https://github.com/WordPress/gutenberg/issues/52027.
// See https://github.com/WordPress/gutenberg/pull/52921 for more info.
aspectRatio,
maxHeight:
contentHeight !== null &&
contentHeight > MAX_HEIGHT
? MAX_HEIGHT * scale
: undefined,
}
: {}
}
>
<CustomIframeComponent
aria-hidden
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore disabled prop exists
scrolling={ isScrollable ? 'yes' : 'no' }
tabIndex={ -1 }
readonly={
isFullComposabilityFeatureAndAPIAvailable()
? false
: true
}
<>
{ ! isPatternPreview &&
virtualElement &&
popoverStatus === PopoverStatus.VISIBLE &&
! isResizing && (
<Popover
// @ts-ignore No types for this exist yet.
anchor={ virtualElement }
as="div"
variant="unstyled"
className="components-tooltip woocommerce-customize-store_popover-tooltip"
>
<span>
{ __(
'You can edit your content later in the Editor',
'woocommerce'
) }
</span>
</Popover>
) }
<DisabledProvider value={ true }>
<div
className="block-editor-block-preview__content"
style={
autoScale
? {
position: 'absolute',
width: viewportWidth,
height: contentHeight,
pointerEvents: 'none',
// This is a catch-all max-height for patterns.
// See: https://github.com/WordPress/gutenberg/pull/38175.
maxHeight: MAX_HEIGHT,
transform: `scale(${ scale })`,
// Using width + aspect-ratio instead of height here triggers browsers' native
// handling of scrollbar's visibility. It prevents the flickering issue seen
// in https://github.com/WordPress/gutenberg/issues/52027.
// See https://github.com/WordPress/gutenberg/pull/52921 for more info.
aspectRatio,
maxHeight:
contentHeight !== null &&
contentHeight > MAX_HEIGHT
? MAX_HEIGHT * scale
: undefined,
}
: {}
}
contentRef={ useRefEffect(
( bodyElement: HTMLBodyElement ) => {
const {
ownerDocument: { documentElement },
} = bodyElement;
documentElement.classList.add(
'block-editor-block-preview__content-iframe'
);
documentElement.style.position = 'absolute';
documentElement.style.width = '100%';
// Necessary for contentResizeListener to work.
bodyElement.style.boxSizing = 'border-box';
bodyElement.style.position = 'absolute';
bodyElement.style.width = '100%';
const cleanup = updateIframeContent( bodyElement );
return () => {
cleanup();
setContentHeight( null );
};
},
[ isNavigable ]
) }
>
<EditorStyles styles={ editorStyles } />
<style>
{ `
<CustomIframeComponent
aria-hidden
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore disabled prop exists
scrolling={ isScrollable ? 'yes' : 'no' }
tabIndex={ -1 }
readonly={ false }
style={
autoScale
? {
position: 'absolute',
width: viewportWidth,
pointerEvents: 'none',
height: contentHeight,
// This is a catch-all max-height for patterns.
// See: https://github.com/WordPress/gutenberg/pull/38175.
maxHeight: MAX_HEIGHT,
}
: {}
}
contentRef={ ( bodyElement: HTMLElement ) => {
if ( ! bodyElement || iframeRef !== null ) {
return;
}
const documentElement =
bodyElement.ownerDocument.documentElement;
setIframeRef( documentElement );
} }
>
<EditorStyles styles={ editorStyles } />
<style>
{ `
.block-editor-block-list__block::before,
.has-child-selected > .is-selected::after,
.is-hovered:not(.is-selected.is-hovered)::after,
@ -360,18 +255,19 @@ function ScaledBlockPreview( {
${ additionalStyles }
` }
</style>
<MemoizedBlockList renderAppender={ false } />
<PreloadFonts />
{ isAIFlow( context.flowType ) && (
<FontFamiliesLoaderDotCom
fontFamilies={ externalFontFamilies }
onLoad={ noop }
/>
) }
</CustomIframeComponent>
</div>
</DisabledProvider>
</style>
<MemoizedBlockList renderAppender={ false } />
<PreloadFonts />
{ isAIFlow( context.flowType ) && (
<FontFamiliesLoaderDotCom
fontFamilies={ externalFontFamilies }
onLoad={ noop }
/>
) }
</CustomIframeComponent>
</div>
</DisabledProvider>
</>
);
}

View File

@ -9,21 +9,14 @@ import {
// @ts-expect-error No types for this exist yet.
} from '@wordpress/block-editor';
// @ts-expect-error No types for this exist yet.
import { store as coreStore, useEntityRecords } from '@wordpress/core-data';
import { store as coreStore } from '@wordpress/core-data';
import { useDispatch, useSelect } from '@wordpress/data';
// @ts-expect-error No types for this exist yet.
import { privateApis as routerPrivateApis } from '@wordpress/router';
// @ts-expect-error No types for this exist yet.
import { unlock } from '@wordpress/edit-site/build-module/lock-unlock';
import { useQuery } from '@woocommerce/navigation';
// @ts-expect-error No types for this exist yet.
import useSiteEditorSettings from '@wordpress/edit-site/build-module/components/block-editor/use-site-editor-settings';
import {
useCallback,
useContext,
useEffect,
useMemo,
} from '@wordpress/element';
import { useContext, useEffect, useMemo } from '@wordpress/element';
// @ts-expect-error No types for this exist yet.
import { store as editSiteStore } from '@wordpress/edit-site/build-module/store';
@ -43,47 +36,9 @@ import {
findButtonBlockInsideCoverBlockProductHeroPatternAndUpdate,
} from './utils/hero-pattern';
const { useHistory } = unlock( routerPrivateApis );
type Page = {
link: string;
title: { rendered: string; raw: string };
[ key: string ]: unknown;
};
const findPageIdByLinkOrTitle = ( event: MouseEvent, _pages: Page[] ) => {
const target = event.target as HTMLAnchorElement;
const clickedPage =
_pages.find( ( page ) => page.link === target.href ) ||
_pages.find( ( page ) => page.title.rendered === target.innerText );
return clickedPage ? clickedPage.id : null;
};
const findPageIdByBlockClientId = ( event: MouseEvent ) => {
const navLink = ( event.target as HTMLAnchorElement ).closest(
'.wp-block-navigation-link'
);
if ( navLink ) {
const blockClientId = navLink.getAttribute( 'data-block' );
const navLinkBlocks =
// @ts-expect-error No types for this exist yet.
select( blockEditorStore ).getBlocksByClientId( blockClientId );
if ( navLinkBlocks && navLinkBlocks.length ) {
return navLinkBlocks[ 0 ].attributes.id;
}
}
return null;
};
// We only show the edit option when page count is <= MAX_PAGE_COUNT
// Performance of Navigation Links is not good past this value.
const MAX_PAGE_COUNT = 100;
const { GlobalStylesContext } = unlock( blockEditorPrivateApis );
export const BlockEditorContainer = () => {
const history = useHistory();
const settings = useSiteEditorSettings();
const currentTemplate:
@ -125,45 +80,6 @@ export const BlockEditorContainer = () => {
scrollDirection
);
// // See packages/block-library/src/page-list/edit.js.
const { records: pages } = useEntityRecords( 'postType', 'page', {
per_page: MAX_PAGE_COUNT,
_fields: [ 'id', 'link', 'menu_order', 'parent', 'title', 'type' ],
// TODO: When https://core.trac.wordpress.org/ticket/39037 REST API support for multiple orderby
// values is resolved, update 'orderby' to [ 'menu_order', 'post_title' ] to provide a consistent
// sort.
orderby: 'menu_order',
order: 'asc',
} );
const onClickNavigationItem = useCallback(
( event: MouseEvent ) => {
// If the user clicks on a navigation item, we want to update the URL to reflect the page they are on.
// Because of bug in the block library (See https://github.com/woocommerce/team-ghidorah/issues/253#issuecomment-1665106817), we're not able to use href link to find the page ID. Instead, we'll use the link/title first, and if that doesn't work, we'll use the block client ID. It depends on the header block type to determine which one to use.
// This is a temporary solution until the block library is fixed.
const pageId =
findPageIdByLinkOrTitle( event, pages ) ||
findPageIdByBlockClientId( event );
if ( pageId ) {
history.push( {
...urlParams,
postId: pageId,
postType: 'page',
} );
return;
}
// Home page
const { postId, postType, ...params } = urlParams;
history.push( {
...params,
} );
},
[ history, urlParams, pages ]
);
const { highlightedBlockClientId } = useContext( HighlightedBlockContext );
const isHighlighting = highlightedBlockClientId !== null;
const additionalStyles = isHighlighting
@ -271,7 +187,6 @@ export const BlockEditorContainer = () => {
onChange={ onChange }
settings={ settings }
additionalStyles={ additionalStyles }
onClickNavigationItem={ onClickNavigationItem }
/>
);
};

View File

@ -18,7 +18,6 @@ export const BlockEditor = memo(
settings,
additionalStyles,
isScrollable,
onClickNavigationItem,
onChange,
}: {
renderedBlocks: BlockInstance[];
@ -27,7 +26,6 @@ export const BlockEditor = memo(
};
additionalStyles: string;
isScrollable: boolean;
onClickNavigationItem: ( event: MouseEvent ) => void;
onChange: ChangeHandler;
} ) => {
return (
@ -38,14 +36,13 @@ export const BlockEditor = memo(
onChange={ onChange }
settings={ settings }
additionalStyles={ additionalStyles }
isNavigable={ false }
isScrollable={ isScrollable }
onClickNavigationItem={ onClickNavigationItem }
// Don't use sub registry so that we can get the logo block from the main registry on the logo sidebar navigation screen component.
useSubRegistry={ false }
autoScale={ false }
setLogoBlockContext={ true }
CustomIframeComponent={ Iframe }
isPatternPreview={ false }
/>
</div>
</div>

View File

@ -80,6 +80,7 @@ function BlockPattern( { pattern, onClick, onHover, composite, showTooltip } ) {
isScrollable={ false }
autoScale={ true }
CustomIframeComponent={ Iframe }
isPatternPreview={ true }
/>
{ ! showTooltip && (
<div className="block-editor-block-patterns-list__item-title">

View File

@ -7,7 +7,7 @@
*/
// @ts-ignore No types for this exist yet.
import { BlockEditorProvider } from '@wordpress/block-editor';
import { memo } from '@wordpress/element';
import { memo, useContext } from '@wordpress/element';
import { BlockInstance } from '@wordpress/blocks';
/**
@ -20,21 +20,26 @@ import {
import { ChangeHandler } from './hooks/use-editor-blocks';
import { Toolbar } from './toolbar/toolbar';
import { isFullComposabilityFeatureAndAPIAvailable } from './utils/is-full-composability-enabled';
import { IsResizingContext } from './resizable-frame';
export const BlockPreview = ( {
blocks,
settings,
useSubRegistry = true,
onChange,
isPatternPreview,
...props
}: {
blocks: BlockInstance | BlockInstance[];
settings: Record< string, unknown >;
onChange?: ChangeHandler | undefined;
useSubRegistry?: boolean;
isPatternPreview: boolean;
} & Omit< ScaledBlockPreviewProps, 'containerWidth' > ) => {
const renderedBlocks = Array.isArray( blocks ) ? blocks : [ blocks ];
const isResizing = useContext( IsResizingContext );
return (
<>
<BlockEditorProvider
@ -44,8 +49,14 @@ export const BlockPreview = ( {
onChange={ onChange }
useSubRegistry={ useSubRegistry }
>
{ isFullComposabilityFeatureAndAPIAvailable() && <Toolbar /> }
<AutoHeightBlockPreview settings={ settings } { ...props } />
{ isFullComposabilityFeatureAndAPIAvailable() &&
! isPatternPreview &&
! isResizing && <Toolbar /> }
<AutoHeightBlockPreview
isPatternPreview={ isPatternPreview }
settings={ settings }
{ ...props }
/>
</BlockEditorProvider>
</>
);

View File

@ -0,0 +1,322 @@
/**
* External dependencies
*/
import { useEffect } from '@wordpress/element';
/**
* Internal dependencies
*/
import { isFullComposabilityFeatureAndAPIAvailable } from '../utils/is-full-composability-enabled';
import { PopoverStatus } from './use-popover-handler';
const setStyle = ( documentElement: HTMLElement ) => {
const element = documentElement.ownerDocument.documentElement;
element.classList.add( 'block-editor-block-preview__content-iframe' );
element.style.position = 'absolute';
element.style.width = '100%';
// Necessary for contentResizeListener to work.
documentElement.style.boxSizing = 'border-box';
documentElement.style.position = 'absolute';
documentElement.style.width = '100%';
};
/**
* Sets the height of the iframe to the height of the root container
*/
const setContentHeightPatternPreview = (
documentElement: HTMLElement,
autoScale: boolean,
{
setContentHeight,
}: Pick< useAutoBlockPreviewEventListenersCallbacks, 'setContentHeight' >
) => {
const onChange = () => {
if ( autoScale ) {
const rootContainer =
documentElement.ownerDocument.body.querySelector(
'.is-root-container'
);
setContentHeight(
rootContainer ? rootContainer.clientHeight : null
);
}
};
const observer = new window.MutationObserver( onChange );
observer.observe( documentElement, {
attributes: true,
characterData: false,
subtree: true,
childList: true,
} );
return observer;
};
const findAndSetLogoBlock = (
{
documentElement,
}: Pick< useAutoBlockPreviewEventListenersValues, 'autoScale' > & {
documentElement: HTMLElement;
},
{
setLogoBlockIds,
}: Pick< useAutoBlockPreviewEventListenersCallbacks, 'setLogoBlockIds' >
) => {
const observer = new window.MutationObserver( () => {
// Get the current logo block client ID from DOM and set it in the logo block context. This is used for the logo settings. See: ./sidebar/sidebar-navigation-screen-logo.tsx
// Ideally, we should be able to get the logo block client ID from the block editor store but it is not available.
// We should update this code once the there is a selector in the block editor store that can be used to get the logo block client ID.
const siteLogos = documentElement.querySelectorAll(
'.wp-block-site-logo'
);
const logoBlockIds = Array.from( siteLogos )
.map( ( siteLogo ) => {
return siteLogo.getAttribute( 'data-block' );
} )
.filter( Boolean ) as string[];
setLogoBlockIds( logoBlockIds );
} );
observer.observe( documentElement, {
subtree: true,
childList: true,
} );
return observer;
};
/**
* Adds inert attribute to all inner blocks to prevent them from being focused or clicked.
*/
const addInertToAllInnerBlocks = ( documentElement: HTMLElement ) => {
const body = documentElement.ownerDocument.body;
const observerChildList = new window.MutationObserver( () => {
const parentBlocks = body.getElementsByClassName(
'block-editor-block-list__layout'
)[ 0 ].children;
for ( const parentBlock of parentBlocks ) {
parentBlock.setAttribute( 'data-is-parent-block', 'true' );
}
for ( const disableClick of documentElement.querySelectorAll(
"[data-is-parent-block='true'] *, header *, footer *"
) ) {
disableClick.setAttribute( 'inert', 'true' );
}
} );
observerChildList.observe( body, {
childList: true,
} );
return observerChildList;
};
const updateSelectedBlock = (
documentElement: HTMLElement,
{
selectBlock,
selectBlockOnHover,
getBlockParents,
setBlockEditingMode,
updatePopoverPosition,
}: Pick<
useAutoBlockPreviewEventListenersCallbacks,
| 'selectBlockOnHover'
| 'selectBlock'
| 'getBlockParents'
| 'setBlockEditingMode'
| 'updatePopoverPosition'
>
) => {
const body = documentElement.ownerDocument.body;
const handleOnClick = ( event: MouseEvent ) => {
const clickedBlockClientId = selectBlockOnHover( event, {
selectBlockByClientId: selectBlock,
getBlockParents,
setBlockEditingMode,
} );
updatePopoverPosition( {
mainBodyWidth: window.document.body.clientWidth,
iframeWidth: body.clientWidth,
event,
hoveredBlockClientId: null,
clickedBlockClientId: clickedBlockClientId as string,
} );
};
const handleMouseMove = ( event: MouseEvent ) => {
const selectedBlockClientId = selectBlockOnHover( event, {
selectBlockByClientId: selectBlock,
getBlockParents,
setBlockEditingMode: () => void 0,
} );
if ( selectedBlockClientId ) {
updatePopoverPosition( {
mainBodyWidth: window.document.body.clientWidth,
iframeWidth: body.clientWidth,
event,
hoveredBlockClientId: selectedBlockClientId,
clickedBlockClientId: null,
} );
}
};
body.addEventListener( 'click', handleOnClick );
body.addEventListener( 'mousemove', handleMouseMove );
return () => {
body.removeEventListener( 'click', handleOnClick );
body.removeEventListener( 'mousemove', handleMouseMove );
};
};
export const hidePopoverWhenMouseLeaveIframe = (
iframeRef: HTMLElement,
setPopoverStatus: ( popoverStatus: PopoverStatus ) => void
) => {
const handleMouseLeave = () => {
setPopoverStatus( PopoverStatus.HIDDEN );
};
if ( iframeRef ) {
iframeRef.addEventListener( 'mouseleave', handleMouseLeave );
}
return () => {
if ( iframeRef ) {
iframeRef.removeEventListener( 'mouseleave', handleMouseLeave );
}
};
};
type useAutoBlockPreviewEventListenersValues = {
documentElement: HTMLElement | null;
autoScale: boolean;
isPatternPreview: boolean;
contentHeight: number | null;
logoBlockIds: string[];
};
type useAutoBlockPreviewEventListenersCallbacks = {
selectBlockOnHover: (
event: MouseEvent,
options: {
selectBlockByClientId: (
clientId: string,
initialPosition: 0 | -1 | null
) => void;
getBlockParents: ( clientId: string ) => string[];
setBlockEditingMode?: ( clientId: string ) => void;
}
) => string | undefined;
selectBlock: ( clientId: string ) => void;
getBlockParents: ( clientId: string ) => string[];
setBlockEditingMode: ( clientId: string ) => void;
updatePopoverPosition: ( options: {
mainBodyWidth: number;
iframeWidth: number;
event: MouseEvent;
hoveredBlockClientId: string | null;
clickedBlockClientId: string | null;
} ) => void;
setLogoBlockIds: ( logoBlockIds: string[] ) => void;
setContentHeight: ( contentHeight: number | null ) => void;
setPopoverStatus: ( popoverStatus: PopoverStatus ) => void;
};
/**
* Adds event listeners and observers to the auto block preview iframe.
*
*/
export const useAddAutoBlockPreviewEventListenersAndObservers = (
{
documentElement,
autoScale,
isPatternPreview,
logoBlockIds,
}: useAutoBlockPreviewEventListenersValues,
{
selectBlockOnHover,
selectBlock,
getBlockParents,
setBlockEditingMode,
updatePopoverPosition,
setLogoBlockIds,
setContentHeight,
setPopoverStatus,
}: useAutoBlockPreviewEventListenersCallbacks
) => {
useEffect( () => {
const observers: Array< MutationObserver > = [];
const unsubscribeCallbacks: Array< () => void > = [];
if ( ! documentElement ) {
return;
}
// Set the height of the iframe to the height of the root container only when the block preview is used to preview a pattern.
if ( isPatternPreview ) {
const heightObserver = setContentHeightPatternPreview(
documentElement,
autoScale,
{
setContentHeight,
}
);
observers.push( heightObserver );
}
setStyle( documentElement );
if ( logoBlockIds.length === 0 ) {
const logoObserver = findAndSetLogoBlock(
{ autoScale, documentElement },
{
setLogoBlockIds,
}
);
observers.push( logoObserver );
}
if (
isFullComposabilityFeatureAndAPIAvailable() &&
! isPatternPreview
) {
const removeEventListenerHidePopover =
hidePopoverWhenMouseLeaveIframe(
documentElement,
setPopoverStatus
);
const removeEventListenersSelectedBlock = updateSelectedBlock(
documentElement,
{
selectBlock,
selectBlockOnHover,
getBlockParents,
setBlockEditingMode,
updatePopoverPosition,
}
);
const inertObserver = addInertToAllInnerBlocks( documentElement );
observers.push( inertObserver );
unsubscribeCallbacks.push( removeEventListenersSelectedBlock );
unsubscribeCallbacks.push( removeEventListenerHidePopover );
}
return () => {
observers.forEach( ( observer ) => observer.disconnect() );
unsubscribeCallbacks.forEach( ( callback ) => callback() );
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ documentElement, logoBlockIds ] );
};

View File

@ -0,0 +1,96 @@
/**
* External dependencies
*/
import { useState } from 'react';
export enum PopoverStatus {
VISIBLE = 'VISIBLE',
HIDDEN = 'HIDDEN',
}
type VirtualElement = Pick< Element, 'getBoundingClientRect' >;
const generateGetBoundingClientRect = ( x = 0, y = 0 ) => {
return () => ( {
width: 0,
height: 0,
top: y,
right: x,
bottom: y,
left: x,
} );
};
let clickedClientId: string | null = null;
let hoveredClientId: string | null = null;
export const usePopoverHandler = () => {
const [ popoverStatus, setPopoverStatus ] = useState< PopoverStatus >(
PopoverStatus.HIDDEN
);
const defaultVirtualElement = {
getBoundingClientRect: generateGetBoundingClientRect(),
} as VirtualElement;
const [ virtualElement, setVirtualElement ] = useState< VirtualElement >(
defaultVirtualElement
);
const updatePopoverPosition = ( {
mainBodyWidth,
iframeWidth,
event,
clickedBlockClientId,
hoveredBlockClientId,
}: {
mainBodyWidth: number;
iframeWidth: number;
event: MouseEvent;
clickedBlockClientId: string | null;
hoveredBlockClientId: string | null;
} ) => {
const iframe = window.document.querySelector(
'iframe[name="editor-canvas"]'
) as HTMLElement;
clickedClientId =
clickedBlockClientId === null
? clickedClientId
: clickedBlockClientId;
hoveredClientId =
hoveredBlockClientId === null
? hoveredClientId
: hoveredBlockClientId;
if ( clickedClientId === hoveredClientId ) {
if ( popoverStatus === PopoverStatus.HIDDEN ) {
setPopoverStatus( PopoverStatus.VISIBLE );
}
const iframeRect = iframe.getBoundingClientRect();
const newElement = {
getBoundingClientRect: generateGetBoundingClientRect(
event.clientX +
( mainBodyWidth - iframeWidth - iframeRect.left ) +
200,
event.clientY + iframeRect.top + 40
),
} as VirtualElement;
setVirtualElement( newElement );
return;
}
setPopoverStatus( PopoverStatus.HIDDEN );
clickedClientId = null;
};
return [
popoverStatus,
virtualElement,
updatePopoverPosition,
setPopoverStatus,
] as const;
};

View File

@ -128,6 +128,7 @@ const initializeAssembleHub = () => {
showListViewByDefault: false,
showBlockBreadcrumbs: true,
} );
// @ts-ignore No types for this exist yet.
dispatch( editSiteStore ).updateSettings( settings );

View File

@ -5,7 +5,7 @@
* External dependencies
*/
import clsx from 'clsx';
import { useState, useRef } from '@wordpress/element';
import { useState, useRef, createContext } from '@wordpress/element';
import {
ResizableBox,
Tooltip,
@ -66,6 +66,8 @@ function calculateNewHeight( width, initialAspectRatio ) {
return width / intermediateAspectRatio;
}
export const IsResizingContext = createContext( false );
function ResizableFrame( {
isFullWidth,
isOversized,
@ -314,7 +316,9 @@ function ResizableFrame( {
transition={ frameTransition }
style={ innerContentStyle }
>
{ children }
<IsResizingContext.Provider value={ isResizing }>
{ children }
</IsResizingContext.Provider>
</motion.div>
</ResizableBox>
);

View File

@ -833,3 +833,10 @@ body.woocommerce-assembler {
.components-resizable-box__handle.components-resizable-box__side-handle.components-resizable-box__handle-left {
display: none;
}
.woocommerce-customize-store_popover-tooltip {
.components-popover__content {
width: fit-content;
user-select: none;
}
}

View File

@ -1,4 +1,4 @@
const BLOCK_SELECTOR = '.block-editor-block-list__block';
const BLOCK_SELECTOR = "[data-is-parent-block='true'], header, footer";
const getBlockClientId = ( node: HTMLElement ) => {
while ( node && node.nodeType !== node.ELEMENT_NODE ) {
@ -23,8 +23,6 @@ export const selectBlockOnHover = (
event: MouseEvent,
{
selectBlockByClientId,
getBlockParents,
setBlockEditingMode,
}: {
selectBlockByClientId: (
clientId: string,
@ -42,14 +40,7 @@ export const selectBlockOnHover = (
return;
}
const blockParents = getBlockParents( blockClientId );
selectBlockByClientId( blockClientId, null );
if ( ! blockParents || blockParents.length === 0 ) {
selectBlockByClientId( blockClientId, null );
} else {
if ( setBlockEditingMode ) {
setBlockEditingMode( blockClientId, 'disabled' );
}
selectBlockByClientId( blockParents[ 0 ], null );
}
return blockClientId;
};

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
CYS: Show popover when the user clicks on the pattern