Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block popover: move scroll handling to block tools #31611

Merged
merged 2 commits into from
May 8, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Block popover: move scroll handling to block tools
  • Loading branch information
ellatrix committed May 7, 2021
commit 2af3e0617d32e7b4f7b0e28e82c831e8d95f3957
1 change: 1 addition & 0 deletions packages/block-editor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ _Parameters_

- _$0_ `Object`: Props.
- _$0.children_ `Object`: The block content and style container.
- _$0.\_\_unstableContentRef_ `Object`: Ref holding the content scroll container.

<a name="BlockVerticalAlignmentControl" href="#BlockVerticalAlignmentControl">#</a> **BlockVerticalAlignmentControl**

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import BlockContextualToolbar from './block-contextual-toolbar';
import Inserter from '../inserter';
import { store as blockEditorStore } from '../../store';
import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs';
import { usePopoverScroll } from './use-popover-scroll';

function selector( select ) {
const {
Expand Down Expand Up @@ -52,6 +53,7 @@ function BlockPopover( {
isEmptyDefaultBlock,
capturingClientId,
__unstablePopoverSlot,
__unstableContentRef,
} ) {
const {
isNavigationMode,
Expand Down Expand Up @@ -112,6 +114,8 @@ function BlockPopover( {
const lastSelectedElement = useBlockElement( lastClientId );
const capturingElement = useBlockElement( capturingClientId );

const popoverScrollRef = usePopoverScroll( __unstableContentRef );

if (
! shouldShowBreadcrumb &&
! shouldShowContextualToolbar &&
Expand Down Expand Up @@ -174,6 +178,7 @@ function BlockPopover( {

return (
<Popover
ref={ popoverScrollRef }
noArrow
animate={ false }
position={ popoverPosition }
Expand Down Expand Up @@ -296,7 +301,10 @@ function wrapperSelector( select ) {
};
}

export default function WrappedBlockPopover( { __unstablePopoverSlot } ) {
export default function WrappedBlockPopover( {
__unstablePopoverSlot,
__unstableContentRef,
} ) {
const selected = useSelect( wrapperSelector, [] );

if ( ! selected ) {
Expand Down Expand Up @@ -324,6 +332,7 @@ export default function WrappedBlockPopover( { __unstablePopoverSlot } ) {
isEmptyDefaultBlock={ isEmptyDefaultBlock }
capturingClientId={ capturingClientId }
__unstablePopoverSlot={ __unstablePopoverSlot }
__unstableContentRef={ __unstableContentRef }
/>
);
}
13 changes: 9 additions & 4 deletions packages/block-editor/src/components/block-tools/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import InsertionPoint from './insertion-point';
import BlockPopover from './block-popover';
import { store as blockEditorStore } from '../../store';
import BlockContextualToolbar from './block-contextual-toolbar';
import { usePopoverScroll } from './use-popover-scroll';

/**
* Renders block tools (the block toolbar, select/navigation mode toolbar, the
Expand All @@ -20,24 +21,28 @@ import BlockContextualToolbar from './block-contextual-toolbar';
*
* @param {Object} $0 Props.
* @param {Object} $0.children The block content and style container.
* @param {Object} $0.__unstableContentRef Ref holding the content scroll container.
*/
export default function BlockTools( { children } ) {
export default function BlockTools( { children, __unstableContentRef } ) {
const isLargeViewport = useViewportMatch( 'medium' );
const hasFixedToolbar = useSelect(
( select ) => select( blockEditorStore ).getSettings().hasFixedToolbar,
[]
);

return (
<InsertionPoint>
<InsertionPoint __unstableContentRef={ __unstableContentRef }>
{ ( hasFixedToolbar || ! isLargeViewport ) && (
<BlockContextualToolbar isFixed />
) }
{ /* Even if the toolbar is fixed, the block popover is still
needed for navigation mode. */ }
<BlockPopover />
<BlockPopover __unstableContentRef={ __unstableContentRef } />
{ /* Used for the inline rich text toolbar. */ }
<Popover.Slot name="block-toolbar" />
<Popover.Slot
name="block-toolbar"
ref={ usePopoverScroll( __unstableContentRef ) }
/>
{ children }
{ /* Forward compatibility: a place to render block tools behind the
content so it can be tabbed to properly. */ }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@ import { isRTL } from '@wordpress/i18n';
import Inserter from '../inserter';
import { store as blockEditorStore } from '../../store';
import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs';
import { usePopoverScroll } from './use-popover-scroll';

export const InsertionPointOpenRef = createContext();

function InsertionPointPopover( { __unstablePopoverSlot } ) {
function InsertionPointPopover( {
__unstablePopoverSlot,
__unstableContentRef,
} ) {
const { selectBlock } = useDispatch( blockEditorStore );
const openRef = useContext( InsertionPointOpenRef );
const ref = useRef();
Expand Down Expand Up @@ -161,6 +165,8 @@ function InsertionPointPopover( { __unstablePopoverSlot } ) {
};
}, [ previousElement, nextElement ] );

const popoverScrollRef = usePopoverScroll( __unstableContentRef );

if ( ! previousElement ) {
return null;
}
Expand Down Expand Up @@ -205,6 +211,7 @@ function InsertionPointPopover( { __unstablePopoverSlot } ) {
// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
return (
<Popover
ref={ popoverScrollRef }
noArrow
animate={ false }
getAnchorRect={ getAnchorRect }
Expand Down Expand Up @@ -253,7 +260,11 @@ function InsertionPointPopover( { __unstablePopoverSlot } ) {
/* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
}

export default function InsertionPoint( { children } ) {
export default function InsertionPoint( {
children,
__unstablePopoverSlot,
__unstableContentRef,
} ) {
const isVisible = useSelect( ( select ) => {
const { isMultiSelecting, isBlockInsertionPointVisible } = select(
blockEditorStore
Expand All @@ -264,7 +275,12 @@ export default function InsertionPoint( { children } ) {

return (
<InsertionPointOpenRef.Provider value={ useRef( false ) }>
{ isVisible && <InsertionPointPopover /> }
{ isVisible && (
<InsertionPointPopover
__unstablePopoverSlot={ __unstablePopoverSlot }
__unstableContentRef={ __unstableContentRef }
/>
) }
{ children }
</InsertionPointOpenRef.Provider>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* WordPress dependencies
*/
import { useRefEffect } from '@wordpress/compose';

/**
* Allow scrolling "through" popovers over the canvas. This is only called for
* as long as the pointer is over a popover. Do not use React events because it
* will bubble through portals.
*
* @param {Object} scrollableRef
*/
export function usePopoverScroll( scrollableRef ) {
return useRefEffect(
( node ) => {
if ( ! scrollableRef ) {
return;
}

function onWheel( event ) {
const { deltaX, deltaY } = event;
scrollableRef.current.scrollBy( deltaX, deltaY );
}
node.addEventListener( 'wheel', onWheel );
return () => {
node.removeEventListener( 'wheel', onWheel );
};
},
[ scrollableRef ]
);
}
88 changes: 53 additions & 35 deletions packages/components/src/popover/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { useRef, useState, useLayoutEffect } from '@wordpress/element';
import {
useRef,
useState,
useLayoutEffect,
forwardRef,
} from '@wordpress/element';
import { getRectangleFromRange } from '@wordpress/dom';
import { ESCAPE } from '@wordpress/keycodes';
import deprecated from '@wordpress/deprecated';
Expand Down Expand Up @@ -223,36 +228,39 @@ function getAnchorDocument( anchor ) {
return anchor.ownerDocument;
}

const Popover = ( {
headerTitle,
onClose,
onKeyDown,
children,
className,
noArrow = true,
isAlternate,
// Disable reason: We generate the `...contentProps` rest as remainder
// of props which aren't explicitly handled by this component.
/* eslint-disable no-unused-vars */
position = 'bottom right',
range,
focusOnMount = 'firstElement',
anchorRef,
shouldAnchorIncludePadding,
anchorRect,
getAnchorRect,
expandOnMobile,
animate = true,
onClickOutside,
onFocusOutside,
__unstableStickyBoundaryElement,
__unstableSlotName = SLOT_NAME,
__unstableObserveElement,
__unstableBoundaryParent,
__unstableForcePosition,
/* eslint-enable no-unused-vars */
...contentProps
} ) => {
const Popover = (
{
headerTitle,
onClose,
onKeyDown,
children,
className,
noArrow = true,
isAlternate,
// Disable reason: We generate the `...contentProps` rest as remainder
// of props which aren't explicitly handled by this component.
/* eslint-disable no-unused-vars */
position = 'bottom right',
range,
focusOnMount = 'firstElement',
anchorRef,
shouldAnchorIncludePadding,
anchorRect,
getAnchorRect,
expandOnMobile,
animate = true,
onClickOutside,
onFocusOutside,
__unstableStickyBoundaryElement,
__unstableSlotName = SLOT_NAME,
__unstableObserveElement,
__unstableBoundaryParent,
__unstableForcePosition,
/* eslint-enable no-unused-vars */
...contentProps
},
ref
) => {
const anchorRefFallback = useRef( null );
const contentRef = useRef( null );
const containerRef = useRef();
Expand Down Expand Up @@ -474,6 +482,7 @@ const Popover = ( {
const focusOnMountRef = useFocusOnMount( focusOnMount );
const focusOutsideProps = useFocusOutside( handleOnFocusOutside );
const mergedRefs = useMergeRefs( [
ref,
containerRef,
focusOnMount ? constrainedTabbingRef : null,
focusOnMount ? focusReturnRef : null,
Expand Down Expand Up @@ -616,10 +625,19 @@ const Popover = ( {
return <span ref={ anchorRefFallback }>{ content }</span>;
};

const PopoverContainer = Popover;
const PopoverContainer = forwardRef( Popover );

function PopoverSlot( { name = SLOT_NAME }, ref ) {
return (
<Slot
bubblesVirtually
name={ name }
className="popover-slot"
ref={ ref }
/>
);
}

PopoverContainer.Slot = ( { name = SLOT_NAME } ) => (
<Slot bubblesVirtually name={ name } className="popover-slot" />
);
PopoverContainer.Slot = forwardRef( PopoverSlot );

export default PopoverContainer;
24 changes: 16 additions & 8 deletions packages/components/src/slot-fill/bubbles-virtually/slot.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
/**
* WordPress dependencies
*/
import { useRef, useLayoutEffect, useContext } from '@wordpress/element';
import {
useRef,
useLayoutEffect,
useContext,
forwardRef,
} from '@wordpress/element';
import { useMergeRefs } from '@wordpress/compose';

/**
* Internal dependencies
*/
import SlotFillContext from './slot-fill-context';

export default function Slot( {
name,
fillProps = {},
as: Component = 'div',
...props
} ) {
function Slot(
{ name, fillProps = {}, as: Component = 'div', ...props },
forwardedRef
) {
const registry = useContext( SlotFillContext );
const ref = useRef();

Expand All @@ -34,5 +38,9 @@ export default function Slot( {
registry.updateSlot( name, fillProps );
} );

return <Component ref={ ref } { ...props } />;
return (
<Component ref={ useMergeRefs( [ forwardedRef, ref ] ) } { ...props } />
);
}

export default forwardRef( Slot );
12 changes: 8 additions & 4 deletions packages/components/src/slot-fill/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* WordPress dependencies
*/
import { forwardRef } from '@wordpress/element';

/**
* Internal dependencies
*/
Expand All @@ -20,13 +25,12 @@ export function Fill( props ) {
</>
);
}

export function Slot( { bubblesVirtually, ...props } ) {
export const Slot = forwardRef( ( { bubblesVirtually, ...props }, ref ) => {
if ( bubblesVirtually ) {
return <BubblesVirtuallySlot { ...props } />;
return <BubblesVirtuallySlot { ...props } ref={ ref } />;
}
return <BaseSlot { ...props } />;
}
} );

export function Provider( { children, ...props } ) {
return (
Expand Down
Loading