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 ? ( + + ) : ( + + ) } + ); } }