From 4a24e8560cb5c277ea90631ad81e3453933bc27e Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Wed, 9 Aug 2023 12:04:09 +0300 Subject: [PATCH] Writing flow: fix vertical arrow keys not moving (#53454) --- packages/dom/README.md | 4 +-- packages/dom/src/dom/is-edge.js | 14 ++++----- packages/dom/src/dom/is-horizontal-edge.js | 4 +-- packages/dom/src/dom/is-vertical-edge.js | 4 +-- packages/dom/src/dom/place-caret-at-edge.js | 24 +++------------ packages/dom/src/dom/scroll-if-no-range.js | 34 +++++++++++++++++++++ 6 files changed, 51 insertions(+), 33 deletions(-) create mode 100644 packages/dom/src/dom/scroll-if-no-range.js diff --git a/packages/dom/README.md b/packages/dom/README.md index f87ccbb3ac731e..5440ff2820ccc6 100644 --- a/packages/dom/README.md +++ b/packages/dom/README.md @@ -192,7 +192,7 @@ Check whether the selection is horizontally at the edge of the container. _Parameters_ -- _container_ `Element`: Focusable element. +- _container_ `HTMLElement`: Focusable element. - _isReverse_ `boolean`: Set to true to check left, false for right. _Returns_ @@ -269,7 +269,7 @@ Check whether the selection is vertically at the edge of the container. _Parameters_ -- _container_ `Element`: Focusable element. +- _container_ `HTMLElement`: Focusable element. - _isReverse_ `boolean`: Set to true to check top, false for bottom. _Returns_ diff --git a/packages/dom/src/dom/is-edge.js b/packages/dom/src/dom/is-edge.js index 51c160f915d58d..64df2bc139be56 100644 --- a/packages/dom/src/dom/is-edge.js +++ b/packages/dom/src/dom/is-edge.js @@ -8,15 +8,16 @@ import isSelectionForward from './is-selection-forward'; import hiddenCaretRangeFromPoint from './hidden-caret-range-from-point'; import { assertIsDefined } from '../utils/assert-is-defined'; import isInputOrTextArea from './is-input-or-text-area'; +import { scrollIfNoRange } from './scroll-if-no-range'; /** * Check whether the selection is at the edge of the container. Checks for * horizontal position by default. Set `onlyVertical` to true to check only * vertically. * - * @param {Element} container Focusable element. - * @param {boolean} isReverse Set to true to check left, false to check right. - * @param {boolean} [onlyVertical=false] Set to true to check only vertical position. + * @param {HTMLElement} container Focusable element. + * @param {boolean} isReverse Set to true to check left, false to check right. + * @param {boolean} [onlyVertical=false] Set to true to check only vertical position. * * @return {boolean} True if at the edge, false if not. */ @@ -96,11 +97,8 @@ export default function isEdge( container, isReverse, onlyVertical = false ) { // pixels. `getComputedStyle` may return a value with different units. const x = isReverseDir ? containerRect.left + 1 : containerRect.right - 1; const y = isReverse ? containerRect.top + 1 : containerRect.bottom - 1; - const testRange = hiddenCaretRangeFromPoint( - ownerDocument, - x, - y, - /** @type {HTMLElement} */ ( container ) + const testRange = scrollIfNoRange( container, isReverse, () => + hiddenCaretRangeFromPoint( ownerDocument, x, y, container ) ); if ( ! testRange ) { diff --git a/packages/dom/src/dom/is-horizontal-edge.js b/packages/dom/src/dom/is-horizontal-edge.js index a9b947d1b8643d..b767c2af5fb3c6 100644 --- a/packages/dom/src/dom/is-horizontal-edge.js +++ b/packages/dom/src/dom/is-horizontal-edge.js @@ -6,8 +6,8 @@ import isEdge from './is-edge'; /** * Check whether the selection is horizontally at the edge of the container. * - * @param {Element} container Focusable element. - * @param {boolean} isReverse Set to true to check left, false for right. + * @param {HTMLElement} container Focusable element. + * @param {boolean} isReverse Set to true to check left, false for right. * * @return {boolean} True if at the horizontal edge, false if not. */ diff --git a/packages/dom/src/dom/is-vertical-edge.js b/packages/dom/src/dom/is-vertical-edge.js index d1b74e4cb13755..814eb46c8c0850 100644 --- a/packages/dom/src/dom/is-vertical-edge.js +++ b/packages/dom/src/dom/is-vertical-edge.js @@ -6,8 +6,8 @@ import isEdge from './is-edge'; /** * Check whether the selection is vertically at the edge of the container. * - * @param {Element} container Focusable element. - * @param {boolean} isReverse Set to true to check top, false for bottom. + * @param {HTMLElement} container Focusable element. + * @param {boolean} isReverse Set to true to check top, false for bottom. * * @return {boolean} True if at the vertical edge, false if not. */ diff --git a/packages/dom/src/dom/place-caret-at-edge.js b/packages/dom/src/dom/place-caret-at-edge.js index adb69567e759a7..9cb9958dc88439 100644 --- a/packages/dom/src/dom/place-caret-at-edge.js +++ b/packages/dom/src/dom/place-caret-at-edge.js @@ -5,6 +5,7 @@ import hiddenCaretRangeFromPoint from './hidden-caret-range-from-point'; import { assertIsDefined } from '../utils/assert-is-defined'; import isInputOrTextArea from './is-input-or-text-area'; import isRTL from './is-rtl'; +import { scrollIfNoRange } from './scroll-if-no-range'; /** * Gets the range to place. @@ -70,26 +71,11 @@ export default function placeCaretAtEdge( container, isReverse, x ) { return; } - let range = getRange( container, isReverse, x ); + const range = scrollIfNoRange( container, isReverse, () => + getRange( container, isReverse, x ) + ); - // 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 ) - ) { - container.scrollIntoView( isReverse ); - range = range = getRange( container, isReverse, x ); - - if ( - ! range || - ! range.startContainer || - ! container.contains( range.startContainer ) - ) { - return; - } - } + if ( ! range ) return; const { ownerDocument } = container; const { defaultView } = ownerDocument; diff --git a/packages/dom/src/dom/scroll-if-no-range.js b/packages/dom/src/dom/scroll-if-no-range.js new file mode 100644 index 00000000000000..13f5c4d86769e8 --- /dev/null +++ b/packages/dom/src/dom/scroll-if-no-range.js @@ -0,0 +1,34 @@ +/** + * If no range range can be created or it is outside the container, the element + * may be out of view, so scroll it into view and try again. + * + * @param {HTMLElement} container The container to scroll. + * @param {boolean} alignToTop True to align to top, false to bottom. + * @param {Function} callback The callback to create the range. + * + * @return {?Range} The range returned by the callback. + */ +export function scrollIfNoRange( container, alignToTop, callback ) { + let range = callback(); + + // 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 ) + ) { + container.scrollIntoView( alignToTop ); + range = callback(); + + if ( + ! range || + ! range.startContainer || + ! container.contains( range.startContainer ) + ) { + return null; + } + } + + return range; +}