Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81155 views
1
/**
2
* Copyright 2013-2014, Facebook, Inc.
3
* All rights reserved.
4
*
5
* This source code is licensed under the BSD-style license found in the
6
* LICENSE file in the root directory of this source tree. An additional grant
7
* of patent rights can be found in the PATENTS file in the same directory.
8
*
9
* @providesModule ReactDOMSelection
10
*/
11
12
"use strict";
13
14
var ExecutionEnvironment = require('ExecutionEnvironment');
15
16
var getNodeForCharacterOffset = require('getNodeForCharacterOffset');
17
var getTextContentAccessor = require('getTextContentAccessor');
18
19
/**
20
* While `isCollapsed` is available on the Selection object and `collapsed`
21
* is available on the Range object, IE11 sometimes gets them wrong.
22
* If the anchor/focus nodes and offsets are the same, the range is collapsed.
23
*/
24
function isCollapsed(anchorNode, anchorOffset, focusNode, focusOffset) {
25
return anchorNode === focusNode && anchorOffset === focusOffset;
26
}
27
28
/**
29
* Get the appropriate anchor and focus node/offset pairs for IE.
30
*
31
* The catch here is that IE's selection API doesn't provide information
32
* about whether the selection is forward or backward, so we have to
33
* behave as though it's always forward.
34
*
35
* IE text differs from modern selection in that it behaves as though
36
* block elements end with a new line. This means character offsets will
37
* differ between the two APIs.
38
*
39
* @param {DOMElement} node
40
* @return {object}
41
*/
42
function getIEOffsets(node) {
43
var selection = document.selection;
44
var selectedRange = selection.createRange();
45
var selectedLength = selectedRange.text.length;
46
47
// Duplicate selection so we can move range without breaking user selection.
48
var fromStart = selectedRange.duplicate();
49
fromStart.moveToElementText(node);
50
fromStart.setEndPoint('EndToStart', selectedRange);
51
52
var startOffset = fromStart.text.length;
53
var endOffset = startOffset + selectedLength;
54
55
return {
56
start: startOffset,
57
end: endOffset
58
};
59
}
60
61
/**
62
* @param {DOMElement} node
63
* @return {?object}
64
*/
65
function getModernOffsets(node) {
66
var selection = window.getSelection && window.getSelection();
67
68
if (!selection || selection.rangeCount === 0) {
69
return null;
70
}
71
72
var anchorNode = selection.anchorNode;
73
var anchorOffset = selection.anchorOffset;
74
var focusNode = selection.focusNode;
75
var focusOffset = selection.focusOffset;
76
77
var currentRange = selection.getRangeAt(0);
78
79
// If the node and offset values are the same, the selection is collapsed.
80
// `Selection.isCollapsed` is available natively, but IE sometimes gets
81
// this value wrong.
82
var isSelectionCollapsed = isCollapsed(
83
selection.anchorNode,
84
selection.anchorOffset,
85
selection.focusNode,
86
selection.focusOffset
87
);
88
89
var rangeLength = isSelectionCollapsed ? 0 : currentRange.toString().length;
90
91
var tempRange = currentRange.cloneRange();
92
tempRange.selectNodeContents(node);
93
tempRange.setEnd(currentRange.startContainer, currentRange.startOffset);
94
95
var isTempRangeCollapsed = isCollapsed(
96
tempRange.startContainer,
97
tempRange.startOffset,
98
tempRange.endContainer,
99
tempRange.endOffset
100
);
101
102
var start = isTempRangeCollapsed ? 0 : tempRange.toString().length;
103
var end = start + rangeLength;
104
105
// Detect whether the selection is backward.
106
var detectionRange = document.createRange();
107
detectionRange.setStart(anchorNode, anchorOffset);
108
detectionRange.setEnd(focusNode, focusOffset);
109
var isBackward = detectionRange.collapsed;
110
111
return {
112
start: isBackward ? end : start,
113
end: isBackward ? start : end
114
};
115
}
116
117
/**
118
* @param {DOMElement|DOMTextNode} node
119
* @param {object} offsets
120
*/
121
function setIEOffsets(node, offsets) {
122
var range = document.selection.createRange().duplicate();
123
var start, end;
124
125
if (typeof offsets.end === 'undefined') {
126
start = offsets.start;
127
end = start;
128
} else if (offsets.start > offsets.end) {
129
start = offsets.end;
130
end = offsets.start;
131
} else {
132
start = offsets.start;
133
end = offsets.end;
134
}
135
136
range.moveToElementText(node);
137
range.moveStart('character', start);
138
range.setEndPoint('EndToStart', range);
139
range.moveEnd('character', end - start);
140
range.select();
141
}
142
143
/**
144
* In modern non-IE browsers, we can support both forward and backward
145
* selections.
146
*
147
* Note: IE10+ supports the Selection object, but it does not support
148
* the `extend` method, which means that even in modern IE, it's not possible
149
* to programatically create a backward selection. Thus, for all IE
150
* versions, we use the old IE API to create our selections.
151
*
152
* @param {DOMElement|DOMTextNode} node
153
* @param {object} offsets
154
*/
155
function setModernOffsets(node, offsets) {
156
if (!window.getSelection) {
157
return;
158
}
159
160
var selection = window.getSelection();
161
var length = node[getTextContentAccessor()].length;
162
var start = Math.min(offsets.start, length);
163
var end = typeof offsets.end === 'undefined' ?
164
start : Math.min(offsets.end, length);
165
166
// IE 11 uses modern selection, but doesn't support the extend method.
167
// Flip backward selections, so we can set with a single range.
168
if (!selection.extend && start > end) {
169
var temp = end;
170
end = start;
171
start = temp;
172
}
173
174
var startMarker = getNodeForCharacterOffset(node, start);
175
var endMarker = getNodeForCharacterOffset(node, end);
176
177
if (startMarker && endMarker) {
178
var range = document.createRange();
179
range.setStart(startMarker.node, startMarker.offset);
180
selection.removeAllRanges();
181
182
if (start > end) {
183
selection.addRange(range);
184
selection.extend(endMarker.node, endMarker.offset);
185
} else {
186
range.setEnd(endMarker.node, endMarker.offset);
187
selection.addRange(range);
188
}
189
}
190
}
191
192
var useIEOffsets = ExecutionEnvironment.canUseDOM && document.selection;
193
194
var ReactDOMSelection = {
195
/**
196
* @param {DOMElement} node
197
*/
198
getOffsets: useIEOffsets ? getIEOffsets : getModernOffsets,
199
200
/**
201
* @param {DOMElement|DOMTextNode} node
202
* @param {object} offsets
203
*/
204
setOffsets: useIEOffsets ? setIEOffsets : setModernOffsets
205
};
206
207
module.exports = ReactDOMSelection;
208
209