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.
211 lines
6.0 KiB
211 lines
6.0 KiB
/** |
|
* 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;
|
|
|