-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
DOM: merge place caret at edge functions
- Loading branch information
Showing
4 changed files
with
100 additions
and
145 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
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'; | ||
|
||
/** | ||
* Gets the range to place. | ||
* | ||
* @param {HTMLElement} container Focusable element. | ||
* @param {boolean} isReverse True for end, false for start. | ||
* @param {number|undefined} x X coordinate to vertically position. | ||
* | ||
* @return {Range|null} The range to place. | ||
*/ | ||
function getRange( container, isReverse, x ) { | ||
const { ownerDocument } = container; | ||
// In the case of RTL scripts, the horizontal edge is at the opposite side. | ||
const isReverseDir = isRTL( container ) ? ! isReverse : isReverse; | ||
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. | ||
if ( x === undefined ) { | ||
x = isReverse ? containerRect.right - 1 : containerRect.left + 1; | ||
} | ||
const y = isReverseDir ? containerRect.bottom - 1 : containerRect.top + 1; | ||
return hiddenCaretRangeFromPoint( ownerDocument, x, y, container ); | ||
} | ||
|
||
/** | ||
* Places the caret at start or end of a given element. | ||
* | ||
* @param {HTMLElement} container Focusable element. | ||
* @param {boolean} isReverse True for end, false for start. | ||
* @param {number|undefined} x X coordinate to vertically position. | ||
*/ | ||
export default function placeCaretAtEdge( container, isReverse, x ) { | ||
if ( ! container ) { | ||
return; | ||
} | ||
|
||
container.focus(); | ||
|
||
if ( isInputOrTextArea( container ) ) { | ||
// The element may not support selection setting. | ||
if ( typeof container.selectionStart !== 'number' ) { | ||
return; | ||
} | ||
|
||
if ( isReverse ) { | ||
container.selectionStart = container.value.length; | ||
container.selectionEnd = container.value.length; | ||
} else { | ||
container.selectionStart = 0; | ||
container.selectionEnd = 0; | ||
} | ||
|
||
return; | ||
} | ||
|
||
if ( ! container.isContentEditable ) { | ||
return; | ||
} | ||
|
||
let range = 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; | ||
} | ||
} | ||
|
||
const { ownerDocument } = container; | ||
const { defaultView } = ownerDocument; | ||
assertIsDefined( defaultView, 'defaultView' ); | ||
const selection = defaultView.getSelection(); | ||
assertIsDefined( selection, 'selection' ); | ||
selection.removeAllRanges(); | ||
selection.addRange( range ); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,73 +1,15 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import placeCaretAtHorizontalEdge from './place-caret-at-horizontal-edge'; | ||
import hiddenCaretRangeFromPoint from './hidden-caret-range-from-point'; | ||
import { assertIsDefined } from '../utils/assert-is-defined'; | ||
import placeCaretAtEdge from './place-caret-at-edge'; | ||
|
||
/** | ||
* Places the caret at the top or bottom of a given element. | ||
* | ||
* @param {HTMLElement} container Focusable element. | ||
* @param {boolean} isReverse True for bottom, false for top. | ||
* @param {DOMRect} [rect] The rectangle to position the caret with. | ||
* @param {boolean} [mayUseScroll=true] True to allow scrolling, false to disallow. | ||
*/ | ||
export default function placeCaretAtVerticalEdge( | ||
container, | ||
isReverse, | ||
rect, | ||
mayUseScroll = true | ||
) { | ||
if ( ! container ) { | ||
return; | ||
} | ||
|
||
if ( ! rect || ! container.isContentEditable ) { | ||
placeCaretAtHorizontalEdge( container, isReverse ); | ||
return; | ||
} | ||
|
||
container.focus(); | ||
|
||
// Offset by a buffer half the height of the caret rect. This is needed | ||
// because caretRangeFromPoint may default to the end of the selection if | ||
// offset is too close to the edge. It's unclear how to precisely calculate | ||
// this threshold; it may be the padded area of some combination of line | ||
// height, caret height, and font size. The buffer offset is effectively | ||
// equivalent to a point at half the height of a line of text. | ||
const buffer = rect.height / 2; | ||
const editableRect = container.getBoundingClientRect(); | ||
const x = rect.left; | ||
const y = isReverse | ||
? editableRect.bottom - buffer | ||
: editableRect.top + buffer; | ||
|
||
const { ownerDocument } = container; | ||
const { defaultView } = ownerDocument; | ||
const range = hiddenCaretRangeFromPoint( ownerDocument, x, y, container ); | ||
|
||
if ( ! range || ! container.contains( range.startContainer ) ) { | ||
if ( | ||
mayUseScroll && | ||
( ! range || | ||
! range.startContainer || | ||
! range.startContainer.contains( container ) ) | ||
) { | ||
// Might be out of view. | ||
// Easier than attempting to calculate manually. | ||
container.scrollIntoView( isReverse ); | ||
placeCaretAtVerticalEdge( container, isReverse, rect, false ); | ||
return; | ||
} | ||
|
||
placeCaretAtHorizontalEdge( container, isReverse ); | ||
return; | ||
} | ||
|
||
assertIsDefined( defaultView, 'defaultView' ); | ||
const selection = defaultView.getSelection(); | ||
assertIsDefined( selection, 'selection' ); | ||
selection.removeAllRanges(); | ||
selection.addRange( range ); | ||
export default function placeCaretAtVerticalEdge( container, isReverse, rect ) { | ||
return placeCaretAtEdge( container, isReverse, rect?.left ); | ||
} |