From aca801a11162e881fd79f8c975af61e9a4d7daae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Fri, 2 Apr 2021 20:44:58 +0200 Subject: [PATCH] Writing flow: fix horizontal caret placing for empty editable with placeholder (#30463) * Writing flow: fix horizontal caret placing for empty rich text with placeholder * Prevent placeholder from catching selection (esp in FF) * Scroll into view if needed --- .../src/query-pagination-next/edit.js | 1 + .../src/query-pagination-previous/edit.js | 1 + .../src/site-title/edit/index.js | 1 + packages/dom/README.md | 1 + .../src/dom/place-caret-at-horizontal-edge.js | 51 +++++++++++++------ .../src/component/use-inline-warning.js | 2 +- packages/rich-text/src/to-tree.js | 2 + 7 files changed, 42 insertions(+), 17 deletions(-) diff --git a/packages/block-library/src/query-pagination-next/edit.js b/packages/block-library/src/query-pagination-next/edit.js index 4e48c0bcecaa6b..e2bb7160e4dcca 100644 --- a/packages/block-library/src/query-pagination-next/edit.js +++ b/packages/block-library/src/query-pagination-next/edit.js @@ -12,6 +12,7 @@ export default function QueryPaginationNextEdit( { <RichText tagName="a" + style={ { display: 'inline-block' } } aria-label={ __( 'Site title text' ) } placeholder={ __( 'Write site titleā€¦' ) } value={ title } diff --git a/packages/dom/README.md b/packages/dom/README.md index fe4dc671d4e6e6..75bfd1e2e2ba20 100644 --- a/packages/dom/README.md +++ b/packages/dom/README.md @@ -272,6 +272,7 @@ _Parameters_ - _container_ `Element`: Focusable element. - _isReverse_ `boolean`: True for end, false for start. +- _mayUseScroll_ `boolean`: Whether to allow scrolling. <a name="placeCaretAtVerticalEdge" href="#placeCaretAtVerticalEdge">#</a> **placeCaretAtVerticalEdge** diff --git a/packages/dom/src/dom/place-caret-at-horizontal-edge.js b/packages/dom/src/dom/place-caret-at-horizontal-edge.js index 3aa0d8fbe69107..bcc79715915d7f 100644 --- a/packages/dom/src/dom/place-caret-at-horizontal-edge.js +++ b/packages/dom/src/dom/place-caret-at-horizontal-edge.js @@ -3,13 +3,23 @@ */ import { includes } from 'lodash'; +/** + * Internal dependencies + */ +import hiddenCaretRangeFromPoint from './hidden-caret-range-from-point'; + /** * Places the caret at start or end of a given element. * - * @param {Element} container Focusable element. - * @param {boolean} isReverse True for end, false for start. + * @param {Element} container Focusable element. + * @param {boolean} isReverse True for end, false for start. + * @param {boolean} mayUseScroll Whether to allow scrolling. */ -export default function placeCaretAtHorizontalEdge( container, isReverse ) { +export default function placeCaretAtHorizontalEdge( + container, + isReverse, + mayUseScroll +) { if ( ! container ) { return; } @@ -37,25 +47,34 @@ export default function placeCaretAtHorizontalEdge( container, isReverse ) { return; } - // Select on extent child of the container, not the container itself. This - // avoids the selection always being `endOffset` of 1 when placed at end, - // where `startContainer`, `endContainer` would always be container itself. - const rangeTarget = container[ isReverse ? 'lastChild' : 'firstChild' ]; + const { ownerDocument } = container; + const containerRect = container.getBoundingClientRect(); + // When placing at the end (isReverse), find the closest range to the bottom + // right corner. When placing at the start, to the top left corner. + const x = isReverse ? containerRect.right - 1 : containerRect.left + 1; + const y = isReverse ? containerRect.bottom - 1 : containerRect.top + 1; + const range = hiddenCaretRangeFromPoint( ownerDocument, x, y, container ); + + // If no range range can be created or it is outside the container, the + // element may be out of view. + if ( + ! range || + ! range.startContainer || + ! container.contains( range.startContainer ) + ) { + if ( ! mayUseScroll ) { + return; + } - // If no range target, it implies that the container is empty. Focusing is - // sufficient for caret to be placed correctly. - if ( ! rangeTarget ) { + // Only try to scroll into view once to avoid an infinite loop. + mayUseScroll = false; + container.scrollIntoView( isReverse ); + placeCaretAtHorizontalEdge( container, isReverse, mayUseScroll ); return; } - const { ownerDocument } = container; const { defaultView } = ownerDocument; const selection = defaultView.getSelection(); - const range = ownerDocument.createRange(); - - range.selectNodeContents( rangeTarget ); - range.collapse( ! isReverse ); - selection.removeAllRanges(); selection.addRange( range ); } diff --git a/packages/rich-text/src/component/use-inline-warning.js b/packages/rich-text/src/component/use-inline-warning.js index 0056771cc1dc2f..f68bd9dd3d5e5a 100644 --- a/packages/rich-text/src/component/use-inline-warning.js +++ b/packages/rich-text/src/component/use-inline-warning.js @@ -15,7 +15,7 @@ export function useInlineWarning( { ref } ) { if ( computedStyle.display === 'inline' ) { // eslint-disable-next-line no-console - console.warn( message ); + console.error( message ); } } }, [] ); diff --git a/packages/rich-text/src/to-tree.js b/packages/rich-text/src/to-tree.js index 885f74da2ca303..b41820a8ee8ecb 100644 --- a/packages/rich-text/src/to-tree.js +++ b/packages/rich-text/src/to-tree.js @@ -305,6 +305,8 @@ export function toTree( { // selection. The placeholder is also not editable after // all. contenteditable: 'false', + style: + 'pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;', }, } ); }