isTextInputElement判断节点是否extarea或input元素。
getTextContentAccessor通过'textContent'或'innerText'属性获取节点的文本。
FallbackCompositionState获取输入框或文本框更新的值。
getNodeForCharacterOffset获取选中文案起始位置或结束位置的节点,root为选中文案的祖先节点,offset为起始位置或结束位置。
ReactDOMSelection获取或设置节点的选中文本(待认识)。
ReactInputSelection判断输入框是否有选中文本的能力,设置或获取输入框的选中文本。
isTextInputElement.js
'use strict'; var supportedInputTypes = { 'color': true, 'date': true, 'datetime': true, 'datetime-local': true, 'email': true, 'month': true, 'number': true, 'password': true, 'range': true, 'search': true, 'tel': true, 'text': true, 'time': true, 'url': true, 'week': true }; // 判断节点是否extarea或input元素 function isTextInputElement(elem) { var nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase(); if (nodeName === 'input') { return !!supportedInputTypes[elem.type]; } if (nodeName === 'textarea') { return true; } return false; } module.exports = isTextInputElement;
getTextContentAccessor.js
'use strict'; var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment'); var contentKey = null; // 通过'textContent'或'innerText'属性获取节点的文本 function getTextContentAccessor() { if (!contentKey && ExecutionEnvironment.canUseDOM) { // SVG <text> elements don't support innerText even contentKey = 'textContent' in document.documentElement ? 'textContent' : 'innerText'; } return contentKey; } module.exports = getTextContentAccessor;
FallbackCompositionState.js
'use strict'; var _assign = require('object-assign'); var PooledClass = require('./PooledClass'); // 通过'textContent'或'innerText'属性获取节点的文本 var getTextContentAccessor = require('./getTextContentAccessor'); // 获取输入框或文本框更新的值 function FallbackCompositionState(root) { this._root = root; this._startText = this.getText();// 当前输入框的值 this._fallbackText = null; } _assign(FallbackCompositionState.prototype, { destructor: function () { this._root = null; this._startText = null; this._fallbackText = null; }, // 获取输入框的值 getText: function () { if ('value' in this._root) { return this._root.value; } return this._root[getTextContentAccessor()]; }, // 与旧文本比较,获取输入框插入的值,this._fallbackText通过PooledClass构造函数提供的realse方法清空 getData: function () { if (this._fallbackText) { return this._fallbackText; } var start; var startValue = this._startText; var startLength = startValue.length; var end; var endValue = this.getText(); var endLength = endValue.length; for (start = 0; start < startLength; start++) { if (startValue[start] !== endValue[start]) { break; } } var minEnd = startLength - start; for (end = 1; end <= minEnd; end++) { if (startValue[startLength - end] !== endValue[endLength - end]) { break; } } var sliceTail = end > 1 ? 1 - end : undefined; this._fallbackText = endValue.slice(start, sliceTail); return this._fallbackText; } }); PooledClass.addPoolingTo(FallbackCompositionState); module.exports = FallbackCompositionState;
getNodeForCharacterOffset.js
'use strict'; // 获取最底层的子节点 function getLeafNode(node) { while (node && node.firstChild) { node = node.firstChild; } return node; } // 获取下一个节点 function getSiblingNode(node) { while (node) { if (node.nextSibling) { return node.nextSibling; } node = node.parentNode; } } // 获取选中文案起始位置或结束位置的节点,root为选中文案的祖先节点,offset为起始位置或结束位置 function getNodeForCharacterOffset(root, offset) { var node = getLeafNode(root); var nodeStart = 0; var nodeEnd = 0; while (node) { if (node.nodeType === 3) { nodeEnd = nodeStart + node.textContent.length; if (nodeStart <= offset && nodeEnd >= offset) { return { node: node, offset: offset - nodeStart }; } nodeStart = nodeEnd; } node = getLeafNode(getSiblingNode(node)); } } module.exports = getNodeForCharacterOffset;
ReactDOMSelection.js
'use strict'; var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment'); // getNodeForCharacterOffset(root,offset)获取选中文案起始位置或结束位置的节点 // root为选中文案的祖先节点,offset为起始位置或结束位置 var getNodeForCharacterOffset = require('./getNodeForCharacterOffset'); // 通过'textContent'或'innerText'属性获取节点的文本 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; } // 获取Ie下选中文本的起始位置和结束位置 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 }; } // 获取现代浏览器选中文本的起始位置和结束位置 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); // In Firefox, range.startContainer and range.endContainer can be "anonymous // divs", e.g. the up/down buttons on an <input type="number">. Anonymous // divs do not seem to expose properties, triggering a "Permission denied // error" if any of its properties are accessed. The only seemingly possible // way to avoid erroring is to access a property that typically works for // non-anonymous divs and catch any error that may otherwise arise. See // https://bugzilla.mozilla.org/show_bug.cgi?id=208427 try { /* eslint-disable no-unused-expressions */ currentRange.startContainer.nodeType; currentRange.endContainer.nodeType; /* eslint-enable no-unused-expressions */ } catch (e) { return null; } // 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 }; } // ie下对节点node选中文本 function setIEOffsets(node, offsets) { var range = document.selection.createRange().duplicate(); var start, end; if (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(); } // 现代浏览器对节点node选中文本 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 = 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 = { // 获取节点选中文本的{start,end} getOffsets: useIEOffsets ? getIEOffsets : getModernOffsets, // 设置节点的选中文本 setOffsets: useIEOffsets ? setIEOffsets : setModernOffsets }; module.exports = ReactDOMSelection;
ReactInputSelection.js
'use strict'; // 获取或设置所有节点的选中文本 var ReactDOMSelection = require('./ReactDOMSelection'); // containsNode(outerNode,innerNode),判断outerNode节点是否包含innerNode var containsNode = require('fbjs/lib/containsNode'); // focusNode(node),使node节点获得焦点 var focusNode = require('fbjs/lib/focusNode'); // getActiveElement(),获得焦点的元素 var getActiveElement = require('fbjs/lib/getActiveElement'); function isInDocument(node) { return containsNode(document.documentElement, node); } // 判断输入框是否有选中文本的能力,设置或获取输入框的选中文本 var ReactInputSelection = { // 判断elem元素是否有文本选中功能 hasSelectionCapabilities: function (elem) { var nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase(); return nodeName && (nodeName === 'input' && elem.type === 'text' || nodeName === 'textarea' || elem.contentEditable === 'true'); }, // 获取取得焦点的元素,以及文案选中情况 getSelectionInformation: function () { var focusedElem = getActiveElement(); return { focusedElem: focusedElem, selectionRange: ReactInputSelection.hasSelectionCapabilities(focusedElem) ? ReactInputSelection.getSelection(focusedElem) : null }; }, // 以priorSelectionInformation={focusedElem,selectionRange}数据,选中节点及特定的文案 restoreSelection: function (priorSelectionInformation) { var curFocusedElem = getActiveElement(); var priorFocusedElem = priorSelectionInformation.focusedElem; var priorSelectionRange = priorSelectionInformation.selectionRange; if (curFocusedElem !== priorFocusedElem && isInDocument(priorFocusedElem)) { if (ReactInputSelection.hasSelectionCapabilities(priorFocusedElem)) { ReactInputSelection.setSelection(priorFocusedElem, priorSelectionRange); } focusNode(priorFocusedElem); } }, // 获取输入框、文本框及contentEditable节点的选中文案,返回值为{start,end}形式 getSelection: function (input) { var selection; // 现代浏览器输入框、文本框的选中文案 if ('selectionStart' in input) { selection = { start: input.selectionStart, end: input.selectionEnd }; // IE8输入框的选中文案 } else if (document.selection && input.nodeName && input.nodeName.toLowerCase() === 'input') { var range = document.selection.createRange(); if (range.parentElement() === input) { selection = { start: -range.moveStart('character', -input.value.length), end: -range.moveEnd('character', -input.value.length) }; } // contentEditable节点的选中文案,及旧IE版本下的文本框 } else { selection = ReactDOMSelection.getOffsets(input); } return selection || { start: 0, end: 0 }; }, // 根据offsets={start,end}设置输入框input的选中文本 setSelection: function (input, offsets) { var start = offsets.start; var end = offsets.end; if (end === undefined) { end = start; } if ('selectionStart' in input) { input.selectionStart = start; input.selectionEnd = Math.min(end, input.value.length); } else if (document.selection && input.nodeName && input.nodeName.toLowerCase() === 'input') { var range = input.createTextRange(); range.collapse(true); range.moveStart('character', start); range.moveEnd('character', end - start); range.select(); } else { ReactDOMSelection.setOffsets(input, offsets); } } }; module.exports = ReactInputSelection;