[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:
parent
1f736a0a22
commit
50b29d20f7
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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 ] );
|
||||
};
|
|
@ -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;
|
||||
};
|
|
@ -128,6 +128,7 @@ const initializeAssembleHub = () => {
|
|||
showListViewByDefault: false,
|
||||
showBlockBreadcrumbs: true,
|
||||
} );
|
||||
|
||||
// @ts-ignore No types for this exist yet.
|
||||
dispatch( editSiteStore ).updateSettings( settings );
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
CYS: Show popover when the user clicks on the pattern
|
Loading…
Reference in New Issue