diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md
index c275c7319f9c4a..ad945e6e30d59a 100644
--- a/packages/block-editor/CHANGELOG.md
+++ b/packages/block-editor/CHANGELOG.md
@@ -10,6 +10,7 @@
- Added the usage of `mediaPreview` for the `Placeholder` component to the `MediaPlaceholder` component.
- Added a an `onDoubleClick` event handler to the `MediaPlaceholder` component.
- Added a way to pass special `ref` property to the `PlainText` component.
+- The `URLPopover` component now passes through all unhandled props to the underlying Popover component.
### Breaking Changes
diff --git a/packages/block-editor/src/components/url-popover/README.md b/packages/block-editor/src/components/url-popover/README.md
index 4705ecadfe4373..bd5c6342555458 100644
--- a/packages/block-editor/src/components/url-popover/README.md
+++ b/packages/block-editor/src/components/url-popover/README.md
@@ -85,7 +85,7 @@ class MyURLPopover extends Component {
## Props
-The component accepts the following props.
+The component accepts the following props. Any other props are passed through to the underlying `Popover` component ([refer to props documentation](/packages/components/src/popover/README.md)).
### position
@@ -104,14 +104,6 @@ an element.
- Required: No
- Default: "firstElement"
-### onClose
-
-Callback that triggers when the user indicates the popover should close (e.g. they've used the escape key or clicked
-outside of the popover.)
-
-- Type: `Function`
-- Required: No
-
### renderSettings
Callback used to return the React Elements that will be rendered inside the settings drawer. When this function
diff --git a/packages/block-editor/src/components/url-popover/index.js b/packages/block-editor/src/components/url-popover/index.js
index d173d110c3977f..103126d420abc0 100644
--- a/packages/block-editor/src/components/url-popover/index.js
+++ b/packages/block-editor/src/components/url-popover/index.js
@@ -29,10 +29,9 @@ class URLPopover extends Component {
const {
children,
renderSettings,
- onClose,
- onClickOutside,
position = 'bottom center',
focusOnMount = 'firstElement',
+ ...popoverProps
} = this.props;
const {
@@ -46,8 +45,7 @@ class URLPopover extends Component {
className="editor-url-popover block-editor-url-popover"
focusOnMount={ focusOnMount }
position={ position }
- onClose={ onClose }
- onClickOutside={ onClickOutside }
+ { ...popoverProps }
>
{ children }
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index ee616f4404f151..d3b144349c85cd 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -5,6 +5,7 @@
- Added a new `render` property to `FormFileUpload` component. Allowing users of the component to custom the UI for their needs.
- Added a new `BaseControl.VisualLabel` component.
- Added a new `preview` prop to the `Placeholder` component which allows to display a preview, for example a media preview when the Placeholder is used in media editing contexts.
+- Added a new `anchorRect` prop to `Popover` which enables a developer to provide a custom `DOMRect` object at which to position the popover.
### Bug fixes
diff --git a/packages/components/src/index.js b/packages/components/src/index.js
index c5c34089cdb7ce..65c44bb660ef38 100644
--- a/packages/components/src/index.js
+++ b/packages/components/src/index.js
@@ -43,7 +43,7 @@ export { default as PanelHeader } from './panel/header';
export { default as PanelRow } from './panel/row';
export { default as Placeholder } from './placeholder';
export { default as Popover } from './popover';
-export { default as PositionedAtSelection } from './positioned-at-selection';
+export { default as __unstablePositionedAtSelection } from './positioned-at-selection';
export { default as QueryControls } from './query-controls';
export { default as RadioControl } from './radio-control';
export { default as RangeControl } from './range-control';
diff --git a/packages/components/src/popover/README.md b/packages/components/src/popover/README.md
index f071dbcad2a645..99097e08546b55 100644
--- a/packages/components/src/popover/README.md
+++ b/packages/components/src/popover/README.md
@@ -123,6 +123,13 @@ Opt-in prop to show popovers fullscreen on mobile, pass `false` in this prop to
- Required: No
- Default: `false`
+### anchorRect
+
+A custom `DOMRect` object at which to position the popover.
+
+- Type: `DOMRect`
+- Required: No
+
## Methods
### refresh
diff --git a/packages/components/src/popover/index.js b/packages/components/src/popover/index.js
index 47212a3e12f9ca..bdf1b5c0c7c624 100644
--- a/packages/components/src/popover/index.js
+++ b/packages/components/src/popover/index.js
@@ -69,7 +69,7 @@ class Popover extends Component {
}
componentDidMount() {
- this.toggleAutoRefresh( true );
+ this.toggleAutoRefresh( ! this.props.hasOwnProperty( 'anchorRect' ) );
this.refresh();
/*
@@ -87,6 +87,15 @@ class Popover extends Component {
if ( prevProps.position !== this.props.position ) {
this.computePopoverPosition( this.state.popoverSize, this.anchorRect );
}
+
+ if ( prevProps.anchorRect !== this.props.anchorRect ) {
+ this.refreshOnAnchorMove();
+ }
+
+ const hasAnchorRect = this.props.hasOwnProperty( 'anchorRect' );
+ if ( hasAnchorRect !== prevProps.hasOwnProperty( 'anchorRect' ) ) {
+ this.toggleAutoRefresh( ! hasAnchorRect );
+ }
}
componentWillUnmount() {
@@ -129,8 +138,7 @@ class Popover extends Component {
* will only refresh the popover position if the anchor moves.
*/
refreshOnAnchorMove() {
- const { getAnchorRect = this.getAnchorRect } = this.props;
- const anchorRect = getAnchorRect( this.anchorNode.current );
+ const anchorRect = this.getAnchorRect( this.anchorNode.current );
const didAnchorRectChange = ! isShallowEqual( anchorRect, this.anchorRect );
if ( didAnchorRectChange ) {
this.anchorRect = anchorRect;
@@ -144,8 +152,7 @@ class Popover extends Component {
* position.
*/
refresh() {
- const { getAnchorRect = this.getAnchorRect } = this.props;
- const anchorRect = getAnchorRect( this.anchorNode.current );
+ const anchorRect = this.getAnchorRect( this.anchorNode.current );
const contentRect = this.contentNode.current.getBoundingClientRect();
const popoverSize = {
width: contentRect.width,
@@ -191,6 +198,16 @@ class Popover extends Component {
}
getAnchorRect( anchor ) {
+ const { getAnchorRect, anchorRect } = this.props;
+
+ if ( anchorRect ) {
+ return anchorRect;
+ }
+
+ if ( getAnchorRect ) {
+ return getAnchorRect( anchor );
+ }
+
if ( ! anchor || ! anchor.parentNode ) {
return;
}
diff --git a/packages/format-library/src/image/index.js b/packages/format-library/src/image/index.js
index 0433941fa4b257..2e2bc942de76b3 100644
--- a/packages/format-library/src/image/index.js
+++ b/packages/format-library/src/image/index.js
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
-import { Path, SVG, TextControl, Popover, IconButton, PositionedAtSelection } from '@wordpress/components';
+import { Path, SVG, TextControl, Popover, IconButton, __unstablePositionedAtSelection } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { insertObject } from '@wordpress/rich-text';
@@ -113,7 +113,7 @@ export const image = {
return null;
} }
/> }
- { isObjectActive &&
+ { isObjectActive && <__unstablePositionedAtSelection key={ key }>
{ /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ }
- }
+ }
);
}
diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js
index 58b38bed2c64e5..e94ce68514fb3b 100644
--- a/packages/format-library/src/link/inline.js
+++ b/packages/format-library/src/link/inline.js
@@ -7,15 +7,15 @@ import classnames from 'classnames';
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
-import { Component, createRef } from '@wordpress/element';
+import { Component, createRef, useMemo } from '@wordpress/element';
import {
ExternalLink,
IconButton,
ToggleControl,
withSpokenMessages,
- PositionedAtSelection,
} from '@wordpress/components';
import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes';
+import { getRectangleFromRange } from '@wordpress/dom';
import { prependHTTP, safeDecodeURI, filterURLForDisplay } from '@wordpress/url';
import {
create,
@@ -77,6 +77,39 @@ const LinkViewerUrl = ( { url } ) => {
);
};
+const URLPopoverAtLink = ( { isActive, addingLink, value, ...props } ) => {
+ const anchorRect = useMemo( () => {
+ const range = window.getSelection().getRangeAt( 0 );
+ if ( ! range ) {
+ return;
+ }
+
+ if ( addingLink ) {
+ return getRectangleFromRange( range );
+ }
+
+ let element = range.startContainer;
+
+ // If the caret is right before the element, select the next element.
+ element = element.nextElementSibling || element;
+
+ while ( element.nodeType !== window.Node.ELEMENT_NODE ) {
+ element = element.parentNode;
+ }
+
+ const closest = element.closest( 'a' );
+ if ( closest ) {
+ return closest.getBoundingClientRect();
+ }
+ }, [ isActive, addingLink, value.start, value.end ] );
+
+ if ( ! anchorRect ) {
+ return null;
+ }
+
+ return
;
+};
+
const LinkViewer = ( { url, editLink } ) => {
return (
// Disable reason: KeyPress must be suppressed so the block doesn't hide the toolbar
@@ -221,37 +254,36 @@ class InlineLinkUI extends Component {
const showInput = isShowingInput( this.props, this.state );
return (
-
(
+
+ ) }
>
- (
-
- ) }
- >
- { showInput ? (
-
- ) : (
-
- ) }
-
-
+ { showInput ? (
+
+ ) : (
+
+ ) }
+
);
}
}