From b5fd30a8f857177ba3c7daf59cf76e212a0aa58d Mon Sep 17 00:00:00 2001 From: Jon Q Date: Tue, 18 Feb 2020 09:49:43 -0500 Subject: [PATCH 1/7] Add mover interactions --- .../block-list/block-contextual-toolbar.js | 20 +- .../src/components/block-list/style.scss | 3 + .../src/components/block-toolbar/index.js | 60 +++- .../src/components/block-toolbar/style.scss | 27 ++ .../src/components/block-toolbar/utils.js | 260 ++++++++++++++++++ 5 files changed, 349 insertions(+), 21 deletions(-) create mode 100644 packages/block-editor/src/components/block-toolbar/utils.js diff --git a/packages/block-editor/src/components/block-list/block-contextual-toolbar.js b/packages/block-editor/src/components/block-list/block-contextual-toolbar.js index a40c06b5f9fa4..ee8e952b51de5 100644 --- a/packages/block-editor/src/components/block-list/block-contextual-toolbar.js +++ b/packages/block-editor/src/components/block-list/block-contextual-toolbar.js @@ -11,15 +11,17 @@ import { BlockToolbar } from '../'; function BlockContextualToolbar( { focusOnMount, ...props } ) { return ( - - - +
+ + + +
); } diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index 600fb962cefa5..c67d23b2ce057 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -424,6 +424,9 @@ /** * Block Toolbar when contextual. */ +.block-editor-block-contextual-toolbar-wrapper { + padding-left: 48px; +} .block-editor-block-contextual-toolbar { // Adapt the height of the toolbar items. diff --git a/packages/block-editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js index 2f9ba4458824e..998713e817bf7 100644 --- a/packages/block-editor/src/components/block-toolbar/index.js +++ b/packages/block-editor/src/components/block-toolbar/index.js @@ -2,7 +2,8 @@ * WordPress dependencies */ import { useSelect } from '@wordpress/data'; - +import { useRef } from '@wordpress/element'; +import { useViewportMatch } from '@wordpress/compose'; /** * Internal dependencies */ @@ -11,6 +12,7 @@ import BlockSwitcher from '../block-switcher'; import BlockControls from '../block-controls'; import BlockFormatControls from '../block-format-controls'; import BlockSettingsMenu from '../block-settings-menu'; +import { useShowMoversGestures } from './utils'; export default function BlockToolbar( { hideDragHandle } ) { const { @@ -51,6 +53,17 @@ export default function BlockToolbar( { hideDragHandle } ) { }; }, [] ); + const containerNodeRef = useRef(); + const nodeRef = useRef(); + + const { + showMovers, + gestures: showMoversGestures, + } = useShowMoversGestures( { ref: nodeRef } ); + + const shouldShowMovers = + useViewportMatch( 'medium', '<' ) || ( showMovers && hasMovers ); + if ( blockClientIds.length === 0 ) { return null; } @@ -58,18 +71,41 @@ export default function BlockToolbar( { hideDragHandle } ) { const shouldShowVisualToolbar = isValid && mode === 'visual'; const isMultiToolbar = blockClientIds.length > 1; + const animatedMoverStyles = { + opacity: shouldShowMovers ? 1 : 0, + transform: shouldShowMovers ? 'translateX(0px)' : 'translateX(100%)', + }; + return ( -
- { hasMovers && ( - - ) } - { ( shouldShowVisualToolbar || isMultiToolbar ) && ( - - ) } +
+
+
+
+ +
+
+ { ( shouldShowVisualToolbar || isMultiToolbar ) && ( +
+ +
+ ) } +
{ shouldShowVisualToolbar && ! isMultiToolbar && ( <> { + return ref && ref.current.matches( ':hover' ); + }; + + const shouldHideMovers = () => { + const isHovered = getIsHovered(); + + return ! isFocused && ! isHovered; + }; + + const debouncedShowMovers = useCallback( + ( event ) => { + if ( event ) { + event.stopPropagation(); + } + + const timeout = timeoutRef.current; + + if ( timeout && clearTimeout ) { + clearTimeout( timeout ); + } + if ( ! showMovers ) { + setShowMovers( true ); + } + }, + [ showMovers ] + ); + + const debouncedHideMovers = useCallback( + ( event ) => { + if ( event ) { + event.stopPropagation(); + } + + timeoutRef.current = setTimeout( () => { + if ( shouldHideMovers() ) { + setShowMovers( false ); + } + }, debounceTimeout ); + }, + [ isFocused ] + ); + + return { + showMovers, + debouncedShowMovers, + debouncedHideMovers, + }; +} + +/** + * Hook that provides a showMovers state and gesture events for DOM elements + * that interact with the showMovers state. + */ +export function useShowMoversGestures( { ref, debounceTimeout = 500 } ) { + const [ isFocused, setIsFocused ] = useState( false ); + const { + showMovers, + debouncedShowMovers, + debouncedHideMovers, + } = useDebouncedShowMovers( { ref, debounceTimeout, isFocused } ); + + const registerRef = useRef( false ); + + const isFocusedWithin = () => { + return ref && ref.current.contains( document.activeElement ); + }; + + useEffect( () => { + const node = ref.current; + + const handleOnFocus = () => { + if ( isFocusedWithin() ) { + setIsFocused( true ); + debouncedShowMovers(); + } + }; + + const handleOnBlur = () => { + if ( ! isFocusedWithin() ) { + setIsFocused( false ); + debouncedHideMovers(); + } + }; + + /** + * Events are added via DOM events (vs. React synthetic events), + * as the child React components swallow mouse events. + */ + if ( node && ! registerRef.current ) { + node.addEventListener( 'focus', handleOnFocus, true ); + node.addEventListener( 'blur', handleOnBlur, true ); + registerRef.current = true; + } + + return () => { + if ( node ) { + node.removeEventListener( 'focus', handleOnFocus ); + node.removeEventListener( 'blur', handleOnBlur ); + } + }; + }, [ + ref, + registerRef, + setIsFocused, + debouncedShowMovers, + debouncedHideMovers, + ] ); + + return { + showMovers, + gestures: { + onMouseMove: debouncedShowMovers, + onMouseLeave: debouncedHideMovers, + }, + }; +} + +const EDITOR_SELECTOR = '.editor-styles-wrapper'; + +/** + * This is experimental. + */ +export function useExperimentalToolbarPositioning( { ref } ) { + const containerNode = document.querySelector( EDITOR_SELECTOR ); + const translateXRef = useRef( 0 ); + const isViewportSmall = useViewportMatch( 'medium', '<' ); + + // MATH values + const moverWidth = 48; + const buffer = 8; + const offsetLeft = moverWidth + buffer; + + const updatePosition = useCallback( () => { + const node = ref.current; + if ( ! node ) return; + + const targetNode = node.parentElement; + if ( ! targetNode ) return; + + const { x: containerX, right: containerRight } = getCoords( + containerNode + ); + const { x: nodeX, left: nodeLeft, right: nodeRight } = getCoords( + targetNode + ); + + if ( nodeLeft < 0 ) return; + + const currentTranslateX = translateXRef.current; + let nextTranslateX; + + // Computed values + const totalOffsetLeft = nodeX - offsetLeft; + const totalOffsetRight = nodeRight + buffer; + + const isOverflowLeft = totalOffsetLeft < containerX; + const isOverflowRight = totalOffsetRight > containerRight; + + if ( isOverflowLeft ) { + nextTranslateX = containerX - totalOffsetLeft + currentTranslateX; + translateXRef.current = nextTranslateX; + } else if ( isOverflowRight ) { + nextTranslateX = + containerRight - totalOffsetRight + currentTranslateX; + translateXRef.current = nextTranslateX; + } else { + // TODO: Improve reset rendering + translateXRef.current = 0; + } + + if ( isViewportSmall ) { + nextTranslateX = 0; + } + + if ( nextTranslateX ) { + const translateX = Math.round( nextTranslateX ); + targetNode.style.transform = `translateX(${ translateX }px)`; + } + + targetNode.style.opacity = 1; + }, [] ); + + useHideOnInitialRender( { ref } ); + useRequestAnimationFrameLoop( updatePosition ); +} + +function useHideOnInitialRender( { ref } ) { + useEffect( () => { + const node = ref.current; + if ( ! node ) return; + + const targetNode = node.parentElement; + targetNode.style.opacity = 0; + }, [ ref ] ); +} + +function useRequestAnimationFrameLoop( callback ) { + const rafLoopRef = useRef(); + + const rafCallback = ( ...args ) => { + if ( callback ) { + callback( ...args ); + } + rafLoopRef.current = requestAnimationFrame( rafCallback ); + }; + + useEffect( () => { + const cancelAnimationLoop = () => { + if ( rafLoopRef.current ) { + cancelAnimationFrame( rafLoopRef.current ); + } + }; + + rafLoopRef.current = requestAnimationFrame( rafCallback ); + + return () => { + cancelAnimationLoop(); + }; + }, [ rafLoopRef ] ); +} + +function getCoords( node ) { + if ( ! node ) { + return new window.DOMRect( 0, 0, 0, 0 ); + } + + const { x, left, width } = node.getBoundingClientRect(); + + return { + x, + left, + width, + right: x + width, + }; +} From 2d57737ef46b8be84e2e26ade6ab6053d945fc53 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Tue, 18 Feb 2020 13:00:25 -0500 Subject: [PATCH 2/7] Fix rendering of movers height for zoomed views --- packages/block-editor/src/components/block-toolbar/style.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss index 77c8d4e8645d6..5019c14b36ff9 100644 --- a/packages/block-editor/src/components/block-toolbar/style.scss +++ b/packages/block-editor/src/components/block-toolbar/style.scss @@ -203,6 +203,7 @@ .block-editor-block-toolbar__mover-trigger-container { @include break-medium() { + bottom: -1px; left: -1px; position: absolute; top: -1px; @@ -218,6 +219,7 @@ border-bottom-left-radius: 2px; border-top-left-radius: 2px; border-right: none; + height: 100%; transition: all 60ms linear; } From ac99ea20a1ca924fde7e414e7a40f13d49c37ae4 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Tue, 18 Feb 2020 15:51:30 -0500 Subject: [PATCH 3/7] Fix height issue caused by `display` --- .../block-editor/src/components/block-toolbar/style.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss index 5019c14b36ff9..30b89b62d4962 100644 --- a/packages/block-editor/src/components/block-toolbar/style.scss +++ b/packages/block-editor/src/components/block-toolbar/style.scss @@ -225,3 +225,9 @@ @include reduce-motion("transition"); } + +.block-editor-block-toolbar__block-switcher-wrapper { + .block-editor-block-switcher { + display: block; + } +} From 1295bcfd8a97b4ea443324f5e2b295795cebbcbc Mon Sep 17 00:00:00 2001 From: Jon Q Date: Tue, 18 Feb 2020 15:58:16 -0500 Subject: [PATCH 4/7] Remove now unused useExperimentalToolbarPositioning hook --- .../src/components/block-toolbar/index.js | 3 +- .../src/components/block-toolbar/utils.js | 127 +----------------- 2 files changed, 2 insertions(+), 128 deletions(-) diff --git a/packages/block-editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js index 998713e817bf7..fba81f9fb179a 100644 --- a/packages/block-editor/src/components/block-toolbar/index.js +++ b/packages/block-editor/src/components/block-toolbar/index.js @@ -53,7 +53,6 @@ export default function BlockToolbar( { hideDragHandle } ) { }; }, [] ); - const containerNodeRef = useRef(); const nodeRef = useRef(); const { @@ -77,7 +76,7 @@ export default function BlockToolbar( { hideDragHandle } ) { }; return ( -
+
{ - const node = ref.current; - if ( ! node ) return; - - const targetNode = node.parentElement; - if ( ! targetNode ) return; - - const { x: containerX, right: containerRight } = getCoords( - containerNode - ); - const { x: nodeX, left: nodeLeft, right: nodeRight } = getCoords( - targetNode - ); - - if ( nodeLeft < 0 ) return; - - const currentTranslateX = translateXRef.current; - let nextTranslateX; - - // Computed values - const totalOffsetLeft = nodeX - offsetLeft; - const totalOffsetRight = nodeRight + buffer; - - const isOverflowLeft = totalOffsetLeft < containerX; - const isOverflowRight = totalOffsetRight > containerRight; - - if ( isOverflowLeft ) { - nextTranslateX = containerX - totalOffsetLeft + currentTranslateX; - translateXRef.current = nextTranslateX; - } else if ( isOverflowRight ) { - nextTranslateX = - containerRight - totalOffsetRight + currentTranslateX; - translateXRef.current = nextTranslateX; - } else { - // TODO: Improve reset rendering - translateXRef.current = 0; - } - - if ( isViewportSmall ) { - nextTranslateX = 0; - } - - if ( nextTranslateX ) { - const translateX = Math.round( nextTranslateX ); - targetNode.style.transform = `translateX(${ translateX }px)`; - } - - targetNode.style.opacity = 1; - }, [] ); - - useHideOnInitialRender( { ref } ); - useRequestAnimationFrameLoop( updatePosition ); -} - -function useHideOnInitialRender( { ref } ) { - useEffect( () => { - const node = ref.current; - if ( ! node ) return; - - const targetNode = node.parentElement; - targetNode.style.opacity = 0; - }, [ ref ] ); -} - -function useRequestAnimationFrameLoop( callback ) { - const rafLoopRef = useRef(); - - const rafCallback = ( ...args ) => { - if ( callback ) { - callback( ...args ); - } - rafLoopRef.current = requestAnimationFrame( rafCallback ); - }; - - useEffect( () => { - const cancelAnimationLoop = () => { - if ( rafLoopRef.current ) { - cancelAnimationFrame( rafLoopRef.current ); - } - }; - - rafLoopRef.current = requestAnimationFrame( rafCallback ); - - return () => { - cancelAnimationLoop(); - }; - }, [ rafLoopRef ] ); -} - -function getCoords( node ) { - if ( ! node ) { - return new window.DOMRect( 0, 0, 0, 0 ); - } - - const { x, left, width } = node.getBoundingClientRect(); - - return { - x, - left, - width, - right: x + width, - }; -} From e2f0586967f5213626fd805524ba049dfbb57559 Mon Sep 17 00:00:00 2001 From: jasmussen Date: Wed, 19 Feb 2020 10:50:50 +0100 Subject: [PATCH 5/7] Try positioning hack. This improves the situation for the 90% of the time. --- packages/block-editor/src/components/block-list/style.scss | 4 ++-- packages/block-editor/src/components/block-toolbar/style.scss | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index c67d23b2ce057..b51714bf734a7 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -425,7 +425,7 @@ * Block Toolbar when contextual. */ .block-editor-block-contextual-toolbar-wrapper { - padding-left: 48px; + padding-left: $grid-unit-60; // Provide space for the mover control on full-wide items. } .block-editor-block-contextual-toolbar { @@ -543,7 +543,7 @@ // @todo It should position the block transform dialog as the left margin of a block. It currently // positions instead, the mover control. - margin-left: -$grid-unit-60; + margin-left: -$grid-unit-60 - $grid-unit-60 - $border-width; } .block-editor-block-contextual-toolbar[data-align="full"], diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss index 30b89b62d4962..928d8cba5e0f1 100644 --- a/packages/block-editor/src/components/block-toolbar/style.scss +++ b/packages/block-editor/src/components/block-toolbar/style.scss @@ -209,6 +209,7 @@ top: -1px; transform: translateX(-48px); user-select: none; + z-index: -1; // This makes it slide out from underneath the toolbar. } } From 1678ae9e5381165fa29a8b8b5e06f9ec8b443f37 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 19 Feb 2020 14:55:30 +0100 Subject: [PATCH 6/7] Tweak the CSS variables and revert the left position of the toolbar --- packages/block-editor/src/components/block-list/style.scss | 6 +++--- .../block-editor/src/components/block-switcher/style.scss | 2 +- packages/block-editor/src/components/rich-text/style.scss | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index b51714bf734a7..f46b9c3b06c4e 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -425,7 +425,7 @@ * Block Toolbar when contextual. */ .block-editor-block-contextual-toolbar-wrapper { - padding-left: $grid-unit-60; // Provide space for the mover control on full-wide items. + padding-left: $block-toolbar-height; // Provide space for the mover control on full-wide items. } .block-editor-block-contextual-toolbar { @@ -463,7 +463,7 @@ // The button here has a special style to appear as a toolbar. .components-button { font-size: $default-font-size; - height: $grid-unit-60; + height: $block-toolbar-height; padding: $grid-unit-15 $grid-unit-20; // Block UI appearance. @@ -543,7 +543,7 @@ // @todo It should position the block transform dialog as the left margin of a block. It currently // positions instead, the mover control. - margin-left: -$grid-unit-60 - $grid-unit-60 - $border-width; + margin-left: - $block-toolbar-height - $border-width; } .block-editor-block-contextual-toolbar[data-align="full"], diff --git a/packages/block-editor/src/components/block-switcher/style.scss b/packages/block-editor/src/components/block-switcher/style.scss index 4973ba87d0d64..32e7e3ba74f4a 100644 --- a/packages/block-editor/src/components/block-switcher/style.scss +++ b/packages/block-editor/src/components/block-switcher/style.scss @@ -33,7 +33,7 @@ } .components-button.block-editor-block-switcher__no-switcher-icon { - width: $grid-unit-60; + width: $block-toolbar-height; .block-editor-blocks-icon { margin-right: auto; diff --git a/packages/block-editor/src/components/rich-text/style.scss b/packages/block-editor/src/components/rich-text/style.scss index d73a5445d37ff..cd4005ae15c0c 100644 --- a/packages/block-editor/src/components/rich-text/style.scss +++ b/packages/block-editor/src/components/rich-text/style.scss @@ -71,8 +71,8 @@ figcaption.block-editor-rich-text__editable [data-rich-text-placeholder]::before .components-toolbar__control, .components-dropdown-menu__toggle { - min-width: $grid-unit-60; - min-height: $grid-unit-60; + min-width: $block-toolbar-height; + min-height: $block-toolbar-height; padding-left: $grid-unit-15; padding-right: $grid-unit-15; } From c2162fb81517ce1f76210d02cac52a885d57e8f7 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Wed, 19 Feb 2020 09:00:51 -0500 Subject: [PATCH 7/7] Handle null ref for hover check --- packages/block-editor/src/components/block-toolbar/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-toolbar/utils.js b/packages/block-editor/src/components/block-toolbar/utils.js index 95e228046bb4f..dbaf4677bff82 100644 --- a/packages/block-editor/src/components/block-toolbar/utils.js +++ b/packages/block-editor/src/components/block-toolbar/utils.js @@ -17,7 +17,7 @@ export function useDebouncedShowMovers( { const timeoutRef = useRef(); const getIsHovered = () => { - return ref && ref.current.matches( ':hover' ); + return ref?.current && ref.current.matches( ':hover' ); }; const shouldHideMovers = () => {