-
Notifications
You must be signed in to change notification settings - Fork 4k
/
is-edge.js
127 lines (107 loc) · 4.22 KB
/
is-edge.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/**
* Internal dependencies
*/
import isRTL from './is-rtl';
import getRangeHeight from './get-range-height';
import getRectangleFromRange from './get-rectangle-from-range';
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 {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.
*/
export default function isEdge( container, isReverse, onlyVertical = false ) {
if (
isInputOrTextArea( container ) &&
typeof container.selectionStart === 'number'
) {
if ( container.selectionStart !== container.selectionEnd ) {
return false;
}
if ( isReverse ) {
return container.selectionStart === 0;
}
return container.value.length === container.selectionStart;
}
if ( ! container.isContentEditable ) {
return true;
}
const { ownerDocument } = container;
const { defaultView } = ownerDocument;
assertIsDefined( defaultView, 'defaultView' );
const selection = defaultView.getSelection();
if ( ! selection || ! selection.rangeCount ) {
return false;
}
const range = selection.getRangeAt( 0 );
const collapsedRange = range.cloneRange();
const isForward = isSelectionForward( selection );
const isCollapsed = selection.isCollapsed;
// Collapse in direction of selection.
if ( ! isCollapsed ) {
collapsedRange.collapse( ! isForward );
}
const collapsedRangeRect = getRectangleFromRange( collapsedRange );
const rangeRect = getRectangleFromRange( range );
if ( ! collapsedRangeRect || ! rangeRect ) {
return false;
}
// Only consider the multiline selection at the edge if the direction is
// towards the edge. The selection is multiline if it is taller than the
// collapsed selection.
const rangeHeight = getRangeHeight( range );
if (
! isCollapsed &&
rangeHeight &&
rangeHeight > collapsedRangeRect.height &&
isForward === isReverse
) {
return false;
}
// In the case of RTL scripts, the horizontal edge is at the opposite side.
const isReverseDir = isRTL( container ) ? ! isReverse : isReverse;
const containerRect = container.getBoundingClientRect();
// To check if a selection is at the edge, we insert a test selection at the
// edge of the container and check if the selections have the same vertical
// or horizontal position. If they do, the selection is at the edge.
// This method proves to be better than a DOM-based calculation for the
// horizontal edge, since it ignores empty textnodes and a trailing line
// break element. In other words, we need to check visual positioning, not
// DOM positioning.
// It also proves better than using the computed style for the vertical
// edge, because we cannot know the padding and line height reliably in
// 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 = scrollIfNoRange( container, isReverse, () =>
hiddenCaretRangeFromPoint( ownerDocument, x, y, container )
);
if ( ! testRange ) {
return false;
}
const testRect = getRectangleFromRange( testRange );
if ( ! testRect ) {
return false;
}
const verticalSide = isReverse ? 'top' : 'bottom';
const horizontalSide = isReverseDir ? 'left' : 'right';
const verticalDiff = testRect[ verticalSide ] - rangeRect[ verticalSide ];
const horizontalDiff =
testRect[ horizontalSide ] - collapsedRangeRect[ horizontalSide ];
// Allow the position to be 1px off.
const hasVerticalDiff = Math.abs( verticalDiff ) <= 1;
const hasHorizontalDiff = Math.abs( horizontalDiff ) <= 1;
return onlyVertical
? hasVerticalDiff
: hasVerticalDiff && hasHorizontalDiff;
}