From 7323e484f6c6a499ff57f14f323c8fa3f5934529 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 24 Mar 2023 12:26:41 +1100 Subject: [PATCH] Sticky Position: Try re-enabling non-root sticky position, and add a visualizer to define sticky area --- packages/block-editor/src/hooks/position.js | 125 ++++++++++++++++-- packages/block-editor/src/hooks/position.scss | 19 +++ 2 files changed, 133 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/hooks/position.js b/packages/block-editor/src/hooks/position.js index 2c5589c1920bbd..3f5e44cf514918 100644 --- a/packages/block-editor/src/hooks/position.js +++ b/packages/block-editor/src/hooks/position.js @@ -13,21 +13,26 @@ import { privateApis as componentsPrivateApis, } from '@wordpress/components'; import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose'; -import { useSelect } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; import { useContext, + useEffect, useMemo, + useRef, + useState, createPortal, Platform, } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; - +import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Internal dependencies */ import BlockList from '../components/block-list'; +import BlockPopover from '../components/block-popover'; import useSetting from '../components/use-setting'; import InspectorControls from '../components/inspector-controls'; +import useBlockDisplayInformation from '../components/use-block-display-information'; import { cleanEmptyObject } from './utils'; import { unlock } from '../lock-unlock'; import { store as blockEditorStore } from '../store'; @@ -203,6 +208,71 @@ export function useIsPositionDisabled( { name: blockName } = {} ) { return ! hasPositionSupport( blockName ) || isDisabled; } +function useVisualizer() { + const [ property, setProperty ] = useState( false ); + const { hideBlockInterface, showBlockInterface } = unlock( + useDispatch( blockEditorStore ) + ); + useEffect( () => { + if ( ! property ) { + showBlockInterface(); + } else { + hideBlockInterface(); + } + }, [ property, showBlockInterface, hideBlockInterface ] ); + + return [ property, setProperty ]; +} + +export function PositionVisualizer( { clientId, attributes, forceShow } ) { + const positionType = attributes?.style?.position?.type; + + const [ isActive, setIsActive ] = useState( false ); + const valueRef = useRef( positionType ); + const timeoutRef = useRef(); + + const clearTimer = () => { + if ( timeoutRef.current ) { + window.clearTimeout( timeoutRef.current ); + } + }; + + useEffect( () => { + if ( + ! isShallowEqual( positionType, valueRef.current ) && + ! forceShow + ) { + setIsActive( true ); + valueRef.current = positionType; + + timeoutRef.current = setTimeout( () => { + setIsActive( false ); + }, 400 ); + } + + return () => { + setIsActive( false ); + clearTimer(); + }; + }, [ positionType, forceShow ] ); + + if ( ! isActive && ! forceShow ) { + return null; + } + + return ( + +
+ + ); +} + /* * Position controls rendered in an inspector control panel. * @@ -222,32 +292,39 @@ export function PositionPanel( props ) { const allowSticky = hasStickyPositionSupport( blockName ); const value = style?.position?.type; - const { hasParents } = useSelect( + const { firstParentClientId } = useSelect( ( select ) => { const { getBlockParents } = select( blockEditorStore ); const parents = getBlockParents( clientId ); - return { - hasParents: parents.length, - }; + return { firstParentClientId: parents[ parents.length - 1 ] }; }, [ clientId ] ); + const blockInformation = useBlockDisplayInformation( firstParentClientId ); + const stickyHelpText = + allowSticky && value === 'sticky' && blockInformation + ? sprintf( + /* translators: %s: the name of the parent block. */ + __( + 'The block will stick to the scrollable area of the parent %s block.' + ), + blockInformation.title + ) + : null; + const options = useMemo( () => { const availableOptions = [ DEFAULT_OPTION ]; // Only display sticky option if the block has no parents (is at the root of the document), // or if the block already has a sticky position value set. - if ( - ( allowSticky && ! hasParents ) || - value === STICKY_OPTION.value - ) { + if ( allowSticky || value === STICKY_OPTION.value ) { availableOptions.push( STICKY_OPTION ); } if ( allowFixed || value === FIXED_OPTION.value ) { availableOptions.push( FIXED_OPTION ); } return availableOptions; - }, [ allowFixed, allowSticky, hasParents, value ] ); + }, [ allowFixed, allowSticky, value ] ); const onChangeType = ( next ) => { // For now, use a hard-coded `0px` value for the position. @@ -272,6 +349,17 @@ export function PositionPanel( props ) { } ); }; + const [ isPositionVisualizerActive, setIsPositionVisualizerActive ] = + useVisualizer(); + + const onMouseOverPosition = () => { + setIsPositionVisualizerActive( true ); + }; + + const onMouseLeaveControls = () => { + setIsPositionVisualizerActive( false ); + }; + const selectedOption = value ? options.find( ( option ) => option.value === value ) || DEFAULT_OPTION : DEFAULT_OPTION; @@ -281,6 +369,13 @@ export function PositionPanel( props ) { web: options.length > 1 ? ( + { firstParentClientId && value === 'sticky' ? ( + + ) : null } { onChangeType( selectedItem.value ); } } + onFocus={ onMouseOverPosition } + onBlur={ onMouseLeaveControls } + onMouseOver={ onMouseOverPosition } size={ '__unstable-large' } /> + { stickyHelpText && ( +

+ { stickyHelpText } +

+ ) }
) : null, native: null, diff --git a/packages/block-editor/src/hooks/position.scss b/packages/block-editor/src/hooks/position.scss index b3bd6b1b9ef041..fe6be4a14fb77b 100644 --- a/packages/block-editor/src/hooks/position.scss +++ b/packages/block-editor/src/hooks/position.scss @@ -16,3 +16,22 @@ text-align: left; } } + +.block-editor__sticky-position-visualizer { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + opacity: 0.5; + border-color: var(--wp-admin-theme-color); + border-style: solid; + box-sizing: border-box; + overflow: hidden; +} + +.block-editor-hooks__position-helptext { + color: $gray-700; + font-size: $helptext-font-size; + margin-bottom: $grid-unit-20; +}