You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
212 lines
6.0 KiB
212 lines
6.0 KiB
10 years ago
|
/**
|
||
|
* Copyright 2013-2015, Facebook, Inc.
|
||
|
* All rights reserved.
|
||
|
*
|
||
|
* This source code is licensed under the BSD-style license found in the
|
||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||
|
*
|
||
|
* @providesModule ReactDOMSelection
|
||
|
*/
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
var ExecutionEnvironment = require("./ExecutionEnvironment");
|
||
|
|
||
|
var getNodeForCharacterOffset = require("./getNodeForCharacterOffset");
|
||
|
var getTextContentAccessor = require("./getTextContentAccessor");
|
||
|
|
||
|
/**
|
||
|
* While `isCollapsed` is available on the Selection object and `collapsed`
|
||
|
* is available on the Range object, IE11 sometimes gets them wrong.
|
||
|
* If the anchor/focus nodes and offsets are the same, the range is collapsed.
|
||
|
*/
|
||
|
function isCollapsed(anchorNode, anchorOffset, focusNode, focusOffset) {
|
||
|
return anchorNode === focusNode && anchorOffset === focusOffset;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the appropriate anchor and focus node/offset pairs for IE.
|
||
|
*
|
||
|
* The catch here is that IE's selection API doesn't provide information
|
||
|
* about whether the selection is forward or backward, so we have to
|
||
|
* behave as though it's always forward.
|
||
|
*
|
||
|
* IE text differs from modern selection in that it behaves as though
|
||
|
* block elements end with a new line. This means character offsets will
|
||
|
* differ between the two APIs.
|
||
|
*
|
||
|
* @param {DOMElement} node
|
||
|
* @return {object}
|
||
|
*/
|
||
|
function getIEOffsets(node) {
|
||
|
var selection = document.selection;
|
||
|
var selectedRange = selection.createRange();
|
||
|
var selectedLength = selectedRange.text.length;
|
||
|
|
||
|
// Duplicate selection so we can move range without breaking user selection.
|
||
|
var fromStart = selectedRange.duplicate();
|
||
|
fromStart.moveToElementText(node);
|
||
|
fromStart.setEndPoint('EndToStart', selectedRange);
|
||
|
|
||
|
var startOffset = fromStart.text.length;
|
||
|
var endOffset = startOffset + selectedLength;
|
||
|
|
||
|
return {
|
||
|
start: startOffset,
|
||
|
end: endOffset
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param {DOMElement} node
|
||
|
* @return {?object}
|
||
|
*/
|
||
|
function getModernOffsets(node) {
|
||
|
var selection = window.getSelection && window.getSelection();
|
||
|
|
||
|
if (!selection || selection.rangeCount === 0) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
var anchorNode = selection.anchorNode;
|
||
|
var anchorOffset = selection.anchorOffset;
|
||
|
var focusNode = selection.focusNode;
|
||
|
var focusOffset = selection.focusOffset;
|
||
|
|
||
|
var currentRange = selection.getRangeAt(0);
|
||
|
|
||
|
// If the node and offset values are the same, the selection is collapsed.
|
||
|
// `Selection.isCollapsed` is available natively, but IE sometimes gets
|
||
|
// this value wrong.
|
||
|
var isSelectionCollapsed = isCollapsed(
|
||
|
selection.anchorNode,
|
||
|
selection.anchorOffset,
|
||
|
selection.focusNode,
|
||
|
selection.focusOffset
|
||
|
);
|
||
|
|
||
|
var rangeLength = isSelectionCollapsed ? 0 : currentRange.toString().length;
|
||
|
|
||
|
var tempRange = currentRange.cloneRange();
|
||
|
tempRange.selectNodeContents(node);
|
||
|
tempRange.setEnd(currentRange.startContainer, currentRange.startOffset);
|
||
|
|
||
|
var isTempRangeCollapsed = isCollapsed(
|
||
|
tempRange.startContainer,
|
||
|
tempRange.startOffset,
|
||
|
tempRange.endContainer,
|
||
|
tempRange.endOffset
|
||
|
);
|
||
|
|
||
|
var start = isTempRangeCollapsed ? 0 : tempRange.toString().length;
|
||
|
var end = start + rangeLength;
|
||
|
|
||
|
// Detect whether the selection is backward.
|
||
|
var detectionRange = document.createRange();
|
||
|
detectionRange.setStart(anchorNode, anchorOffset);
|
||
|
detectionRange.setEnd(focusNode, focusOffset);
|
||
|
var isBackward = detectionRange.collapsed;
|
||
|
|
||
|
return {
|
||
|
start: isBackward ? end : start,
|
||
|
end: isBackward ? start : end
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param {DOMElement|DOMTextNode} node
|
||
|
* @param {object} offsets
|
||
|
*/
|
||
|
function setIEOffsets(node, offsets) {
|
||
|
var range = document.selection.createRange().duplicate();
|
||
|
var start, end;
|
||
|
|
||
|
if (typeof offsets.end === 'undefined') {
|
||
|
start = offsets.start;
|
||
|
end = start;
|
||
|
} else if (offsets.start > offsets.end) {
|
||
|
start = offsets.end;
|
||
|
end = offsets.start;
|
||
|
} else {
|
||
|
start = offsets.start;
|
||
|
end = offsets.end;
|
||
|
}
|
||
|
|
||
|
range.moveToElementText(node);
|
||
|
range.moveStart('character', start);
|
||
|
range.setEndPoint('EndToStart', range);
|
||
|
range.moveEnd('character', end - start);
|
||
|
range.select();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* In modern non-IE browsers, we can support both forward and backward
|
||
|
* selections.
|
||
|
*
|
||
|
* Note: IE10+ supports the Selection object, but it does not support
|
||
|
* the `extend` method, which means that even in modern IE, it's not possible
|
||
|
* to programatically create a backward selection. Thus, for all IE
|
||
|
* versions, we use the old IE API to create our selections.
|
||
|
*
|
||
|
* @param {DOMElement|DOMTextNode} node
|
||
|
* @param {object} offsets
|
||
|
*/
|
||
|
function setModernOffsets(node, offsets) {
|
||
|
if (!window.getSelection) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var selection = window.getSelection();
|
||
|
var length = node[getTextContentAccessor()].length;
|
||
|
var start = Math.min(offsets.start, length);
|
||
|
var end = typeof offsets.end === 'undefined' ?
|
||
|
start : Math.min(offsets.end, length);
|
||
|
|
||
|
// IE 11 uses modern selection, but doesn't support the extend method.
|
||
|
// Flip backward selections, so we can set with a single range.
|
||
|
if (!selection.extend && start > end) {
|
||
|
var temp = end;
|
||
|
end = start;
|
||
|
start = temp;
|
||
|
}
|
||
|
|
||
|
var startMarker = getNodeForCharacterOffset(node, start);
|
||
|
var endMarker = getNodeForCharacterOffset(node, end);
|
||
|
|
||
|
if (startMarker && endMarker) {
|
||
|
var range = document.createRange();
|
||
|
range.setStart(startMarker.node, startMarker.offset);
|
||
|
selection.removeAllRanges();
|
||
|
|
||
|
if (start > end) {
|
||
|
selection.addRange(range);
|
||
|
selection.extend(endMarker.node, endMarker.offset);
|
||
|
} else {
|
||
|
range.setEnd(endMarker.node, endMarker.offset);
|
||
|
selection.addRange(range);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var useIEOffsets = (
|
||
|
ExecutionEnvironment.canUseDOM &&
|
||
|
'selection' in document &&
|
||
|
!('getSelection' in window)
|
||
|
);
|
||
|
|
||
|
var ReactDOMSelection = {
|
||
|
/**
|
||
|
* @param {DOMElement} node
|
||
|
*/
|
||
|
getOffsets: useIEOffsets ? getIEOffsets : getModernOffsets,
|
||
|
|
||
|
/**
|
||
|
* @param {DOMElement|DOMTextNode} node
|
||
|
* @param {object} offsets
|
||
|
*/
|
||
|
setOffsets: useIEOffsets ? setIEOffsets : setModernOffsets
|
||
|
};
|
||
|
|
||
|
module.exports = ReactDOMSelection;
|