Skip to content

Commit

Permalink
Popover: add anchor prop (#43691)
Browse files Browse the repository at this point in the history
* Popover: add new anchor prop, mark other anchor props as deprecated

* Add `anchor` prop to Storybook

* Add WP version for deprecated props removal

* Do not render fallback anchor if there is already a prop-derived anchor

* Block inbetween inserter: use Popover's new anchor prop (#43693)

* BlockPopoverInbetween: refactor to use `anchor` prop

* Simplify logic, use DOMRect

* Add missing hook deps

* ListViewDropIndicator: use Popover s new anchor prop (#43694)

* Temporarily disable derpecation warnings

* Block toolbar: use Popover's new anchor prop (#43692)

* Block toolbar: use anchor prop instead of anchorRef.{top,bottom}

* Update packages/block-editor/src/components/block-popover/index.js

Co-authored-by: Lena Morita <lena@jaguchi.com>

Co-authored-by: Lena Morita <lena@jaguchi.com>

* Dropdown: use Popover s new anchor prop (#43698)

* BlockPopover: prevent error when `selectedElement` is not defined

* Try to avoid infinite loop

* Update PanelRow docs

* Edit navigation menu actions: use Popover s new anchor prop

* BorderBoxControl: use Popover's new anchor prop (#43789)

* BorderBoxControl: use new `anchor` prop for `Popover`

* Make sure anchor value is `undefined` instead of `null`

* Image URL Input: use new anchor prop for Popover (#43784)

* Image URL Input: use new anchor prop for Popover

* Prevent value from being `null`

* Edit site Actions: use new anchor prop for Popover (#43810)

* Buttons block: use new Popover anchor prop (#43785)

* Buttons block: use new `anchor` prop for `Popover`

* Prevent anchor value from being `null`

* Navigation block: use new anchor prop for Popover (#43786)

* Navigation block: use new `anchor` prop for `Popover`

* Use anchor for the Navigation submenu block too

* Prevent anchor value from being `null`

* Post Date block: use new anchor prop for Popover (#43787)

* Post Date block: use new `anchor` prop for `Popover`

* Prevent anchor value from being `null`

* Tooltip: refactor using Popover's new anchor prop (#43799)

* Tooltip: use Popover s new anchor prop

* Use internal state to force re-renders when the anchor ref changes

* Simplify code

* Improve docs around using state instead of refs for the anchor element

* Allow `anchor` to be `null`

* Edit Post: use Popover's new anchor prop (#43808)

* Edit Post: use Popover s new anchor prop

* Update comment

* SImplify code

* Update packages/edit-post/src/components/sidebar/post-schedule/index.js

Co-authored-by: Daniel Richards <daniel.richards@automattic.com>

* Allow passing a `null` anchor

Co-authored-by: Daniel Richards <daniel.richards@automattic.com>

* Refactor `useAnchorRef` and related components to work with the new Popover `anchor` prop (#43713)

* useAnchorRef: return a VirtualElement instead of a range

* Update useAnchorRef usage in FormatToolbarContainer, use anchor prop

* Update remaining `useAnchorRef` usages, switch to the `anchor` prop

* useAnchorRef: normalize `null` returns to `undefined` as it is not a valid `anchor` value

* Revert changes to native RichText component

* Update docs

* Allow useAnchorRef to return `null`

* Re-enable deprecation warnings

* Remove fall back to `undefined` from `null`

* Ensure reactive updates when the popover anchor updates

* Refactor SocialLinkEdit component to use `anchor` instead of `anchorRef`

* CHANGELOG

* Add new `useAnchor` hook instead of changing existing `useAnchorRef` hook

* Fix API docs

* Update Popover unit tests

* Remove unused import

* Use DOMRect in the DomRectWithOwnerDocument type

* Improve the wording of deprecation warnings

* Put more emphasis on storing anchor in local state

Co-authored-by: Lena Morita <lena@jaguchi.com>
Co-authored-by: Daniel Richards <daniel.richards@automattic.com>
  • Loading branch information
3 people committed Sep 14, 2022
1 parent 7137b14 commit ae31a59
Show file tree
Hide file tree
Showing 42 changed files with 663 additions and 271 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

102 changes: 50 additions & 52 deletions packages/block-editor/src/components/block-popover/inbetween.js
Expand Up @@ -8,7 +8,6 @@ import classnames from 'classnames';
*/
import { useSelect } from '@wordpress/data';
import {
useCallback,
useMemo,
createContext,
useReducer,
Expand Down Expand Up @@ -107,66 +106,65 @@ function BlockPopoverInbetween( {
isVisible,
] );

const getAnchorRect = useCallback( () => {
const popoverAnchor = useMemo( () => {
if ( ( ! previousElement && ! nextElement ) || ! isVisible ) {
return {};
return undefined;
}

const { ownerDocument } = previousElement || nextElement;

const previousRect = previousElement
? previousElement.getBoundingClientRect()
: null;
const nextRect = nextElement
? nextElement.getBoundingClientRect()
: null;
return {
ownerDocument,
getBoundingClientRect() {
const previousRect = previousElement
? previousElement.getBoundingClientRect()
: null;
const nextRect = nextElement
? nextElement.getBoundingClientRect()
: null;

if ( isVertical ) {
if ( isRTL() ) {
return {
top: previousRect ? previousRect.bottom : nextRect.top,
left: previousRect ? previousRect.right : nextRect.right,
right: previousRect ? previousRect.right : nextRect.right,
bottom: previousRect ? previousRect.bottom : nextRect.top,
height: 0,
width: 0,
ownerDocument,
};
}
let left = 0;
let top = 0;

return {
top: previousRect ? previousRect.bottom : nextRect.top,
left: previousRect ? previousRect.left : nextRect.left,
right: previousRect ? previousRect.left : nextRect.left,
bottom: previousRect ? previousRect.bottom : nextRect.top,
height: 0,
width: 0,
ownerDocument,
};
}
if ( isVertical ) {
// vertical
top = previousRect ? previousRect.bottom : nextRect.top;

if ( isRTL() ) {
return {
top: previousRect ? previousRect.top : nextRect.top,
left: previousRect ? previousRect.left : nextRect.right,
right: previousRect ? previousRect.left : nextRect.right,
bottom: previousRect ? previousRect.top : nextRect.top,
height: 0,
width: 0,
ownerDocument,
};
}
if ( isRTL() ) {
// vertical, rtl
left = previousRect
? previousRect.right
: nextRect.right;
} else {
// vertical, ltr
left = previousRect ? previousRect.left : nextRect.left;
}
} else {
top = previousRect ? previousRect.top : nextRect.top;

return {
top: previousRect ? previousRect.top : nextRect.top,
left: previousRect ? previousRect.right : nextRect.left,
right: previousRect ? previousRect.right : nextRect.left,
bottom: previousRect ? previousRect.left : nextRect.right,
height: 0,
width: 0,
ownerDocument,
if ( isRTL() ) {
// non vertical, rtl
left = previousRect
? previousRect.left
: nextRect.right;
} else {
// non vertical, ltr
left = previousRect
? previousRect.right
: nextRect.left;
}
}

return new window.DOMRect( left, top, 0, 0 );
},
};
}, [ previousElement, nextElement, positionRecompute, isVisible ] );
}, [
previousElement,
nextElement,
positionRecompute,
isVertical,
isVisible,
] );

const popoverScrollRef = usePopoverScroll( __unstableContentRef );

Expand Down Expand Up @@ -229,7 +227,7 @@ function BlockPopoverInbetween( {
<Popover
ref={ popoverScrollRef }
animate={ false }
getAnchorRect={ getAnchorRect }
anchor={ popoverAnchor }
focusOnMount={ false }
// Render in the old slot if needed for backward compatibility,
// otherwise render in place (not in the default popover slot).
Expand Down
50 changes: 44 additions & 6 deletions packages/block-editor/src/components/block-popover/index.js
Expand Up @@ -47,22 +47,60 @@ function BlockPopover(
};
}, [ selectedElement, lastSelectedElement, __unstableRefreshSize ] );

const popoverAnchor = useMemo( () => {
if (
! selectedElement ||
( bottomClientId && ! lastSelectedElement )
) {
return undefined;
}

return {
getBoundingClientRect() {
const selectedBCR = selectedElement.getBoundingClientRect();
const lastSelectedBCR =
lastSelectedElement?.getBoundingClientRect();

// Get the biggest rectangle that encompasses completely the currently
// selected element and the last selected element:
// - for top/left coordinates, use the smaller numbers
// - for the bottom/right coordinates, use the largest numbers
const left = Math.min(
selectedBCR.left,
lastSelectedBCR?.left ?? Infinity
);
const top = Math.min(
selectedBCR.top,
lastSelectedBCR?.top ?? Infinity
);
const right = Math.max(
selectedBCR.right,
lastSelectedBCR.right ?? -Infinity
);
const bottom = Math.max(
selectedBCR.bottom,
lastSelectedBCR.bottom ?? -Infinity
);
const width = right - left;
const height = bottom - top;

return new window.DOMRect( left, top, width, height );
},
ownerDocument: selectedElement.ownerDocument,
};
}, [ bottomClientId, lastSelectedElement, selectedElement ] );

if ( ! selectedElement || ( bottomClientId && ! lastSelectedElement ) ) {
return null;
}

const anchorRef = {
top: selectedElement,
bottom: lastSelectedElement,
};

return (
<Popover
ref={ mergedRefs }
animate={ false }
position="top right left"
focusOnMount={ false }
anchorRef={ anchorRef }
anchor={ popoverAnchor }
// Render in the old slot if needed for backward compatibility,
// otherwise render in place (not in the default popover slot).
__unstableSlotName={ __unstablePopoverSlot || null }
Expand Down
65 changes: 33 additions & 32 deletions packages/block-editor/src/components/list-view/drop-indicator.js
Expand Up @@ -68,40 +68,41 @@ export default function ListViewDropIndicator( {
};
}, [ getDropIndicatorIndent, targetElement ] );

const getAnchorRect = useCallback( () => {
if ( ! targetElement ) {
return {};
const popoverAnchor = useMemo( () => {
const isValidDropPosition =
dropPosition === 'top' ||
dropPosition === 'bottom' ||
dropPosition === 'inside';
if ( ! targetElement || ! isValidDropPosition ) {
return undefined;
}

const ownerDocument = targetElement.ownerDocument;
const rect = targetElement.getBoundingClientRect();
const indent = getDropIndicatorIndent();

const anchorRect = {
left: rect.left + indent,
right: rect.right,
width: 0,
height: 0,
ownerDocument,
return {
ownerDocument: targetElement.ownerDocument,
getBoundingClientRect() {
const rect = targetElement.getBoundingClientRect();
const indent = getDropIndicatorIndent();

const left = rect.left + indent;
const right = rect.right;
let top = 0;
let bottom = 0;

if ( dropPosition === 'top' ) {
top = rect.top;
bottom = rect.top;
} else {
// `dropPosition` is either `bottom` or `inside`
top = rect.bottom;
bottom = rect.bottom;
}

const width = right - left;
const height = bottom - top;

return new window.DOMRect( left, top, width, height );
},
};

if ( dropPosition === 'top' ) {
return {
...anchorRect,
top: rect.top,
bottom: rect.top,
};
}

if ( dropPosition === 'bottom' || dropPosition === 'inside' ) {
return {
...anchorRect,
top: rect.bottom,
bottom: rect.bottom,
};
}

return {};
}, [ targetElement, dropPosition, getDropIndicatorIndent ] );

if ( ! targetElement ) {
Expand All @@ -111,7 +112,7 @@ export default function ListViewDropIndicator( {
return (
<Popover
animate={ false }
getAnchorRect={ getAnchorRect }
anchor={ popoverAnchor }
focusOnMount={ false }
className="block-editor-list-view-drop-indicator"
>
Expand Down
Expand Up @@ -7,7 +7,7 @@ import { useSelect } from '@wordpress/data';
import {
isCollapsed,
getActiveFormats,
useAnchorRef,
useAnchor,
store as richTextStore,
} from '@wordpress/rich-text';

Expand All @@ -19,28 +19,32 @@ import FormatToolbar from './format-toolbar';
import NavigableToolbar from '../navigable-toolbar';
import { store as blockEditorStore } from '../../store';

function InlineSelectionToolbar( { value, anchorRef, activeFormats } ) {
function InlineSelectionToolbar( {
value,
editableContentElement,
activeFormats,
} ) {
const lastFormat = activeFormats[ activeFormats.length - 1 ];
const lastFormatType = lastFormat?.type;
const settings = useSelect(
( select ) => select( richTextStore ).getFormatType( lastFormatType ),
[ lastFormatType ]
);
const selectionRef = useAnchorRef( {
ref: anchorRef,
const popoverAnchor = useAnchor( {
editableContentElement,
value,
settings,
} );

return <InlineToolbar anchorRef={ selectionRef } />;
return <InlineToolbar popoverAnchor={ popoverAnchor } />;
}

function InlineToolbar( { anchorRef } ) {
function InlineToolbar( { popoverAnchor } ) {
return (
<Popover
position="top center"
focusOnMount={ false }
anchorRef={ anchorRef }
anchor={ popoverAnchor }
className="block-editor-rich-text__inline-format-toolbar"
__unstableSlotName="block-toolbar"
>
Expand All @@ -57,14 +61,18 @@ function InlineToolbar( { anchorRef } ) {
);
}

const FormatToolbarContainer = ( { inline, anchorRef, value } ) => {
const FormatToolbarContainer = ( {
inline,
editableContentElement,
value,
} ) => {
const hasInlineToolbar = useSelect(
( select ) => select( blockEditorStore ).getSettings().hasInlineToolbar,
[]
);

if ( inline ) {
return <InlineToolbar anchorRef={ anchorRef } />;
return <InlineToolbar popoverAnchor={ editableContentElement } />;
}

if ( hasInlineToolbar ) {
Expand All @@ -76,7 +84,7 @@ const FormatToolbarContainer = ( { inline, anchorRef, value } ) => {

return (
<InlineSelectionToolbar
anchorRef={ anchorRef }
editableContentElement={ editableContentElement }
value={ value }
activeFormats={ activeFormats }
/>
Expand Down
4 changes: 2 additions & 2 deletions packages/block-editor/src/components/rich-text/index.js
Expand Up @@ -328,7 +328,7 @@ function RichTextWrapper(
}

function onFocus() {
anchorRef.current.focus();
anchorRef.current?.focus();
}

const TagName = tagName;
Expand All @@ -354,7 +354,7 @@ function RichTextWrapper(
{ isSelected && hasFormats && (
<FormatToolbarContainer
inline={ inlineToolbar }
anchorRef={ anchorRef }
editableContentElement={ anchorRef.current }
value={ value }
/>
) }
Expand Down

0 comments on commit ae31a59

Please sign in to comment.