UNPKG

anujs-stb

Version:

a React16-compact mini framework

399 lines (357 loc) 13.4 kB
(function umd(root, factory) { if (typeof exports === "object" && typeof module === "object") { module.exports = factory(require("react")); } else if (typeof define === "function" && define.amd) { define(["react"], factory); } else if (typeof exports === "object") { exports["ReactInputSelection"] = factory(require("react")); } else { root["ReactInputSelection"] = factory(root["React"]); } })(this, function (ReactInAnujs) { var isBrowser = typeof document === "object"; function isInDocument(node) { if (!isBrowser) { return false; } return containsNode(document.documentElement, node); } function containsNode(a, b) { if (b) { while ((b = b.parentNode)) { if (b === a) { return true; } } } return false; } function focusNode(node) { //如果此元素不可见,IE8会抛错 try { node.focus(); } catch (e) { // no catch } } function getNodeTag(node) { return node.nodeName ? node .nodeName .toLowerCase() : ""; } function getActiveElement(doc) { doc = doc || (isBrowser ? document : undefined); if (typeof doc === "undefined") { return null; } try { return doc.activeElement || doc.body; } catch (e) { return doc.body; } } /** * @ReactInputSelection: React input selection module. Based on Selection.js, * but modified to be suitable for react and has a couple of bug fixes (doesn"t * assume buttons have range selections allowed). * Input selection module for React. */ var ReactInputSelection = { hasSelectionCapabilities: function (elem) { var nodeName = getNodeTag(elem || {}); return nodeName && (nodeName === "input" && elem.type === "text" || nodeName === "textarea" || elem.contentEditable === "true"); }, getSelectionInformation: function () { let focusedElem = getActiveElement(); var selectionRange = ReactInputSelection.hasSelectionCapabilities(focusedElem) ? ReactInputSelection.getSelection(focusedElem) : null; return {focusedElem, selectionRange}; }, restoreSelection: function (lastInformation) { var curFocusedElem = getActiveElement(); var priorFocusedElem = lastInformation.focusedElem; var priorSelectionRange = lastInformation.selectionRange; if (curFocusedElem && isInDocument(priorFocusedElem)) { if (ReactInputSelection.hasSelectionCapabilities(priorFocusedElem)) { ReactInputSelection.setSelection(priorFocusedElem, priorSelectionRange); } focusNode(priorFocusedElem); } }, getSelection: function (input) { var selection; if ("selectionStart" in input) { // Modern browser with input or textarea. selection = { start: input.selectionStart, end: input.selectionEnd }; } else if (document.selection && getNodeTag(input) === "input") { // IE8 input. var range = document .selection .createRange(); // There can only be one selection per document in IE, so it must be in our // element. if (range.parentElement() === input) { selection = { start: -range.moveStart("character", -input.value.length), end: -range.moveEnd("character", -input.value.length) }; } } else { // Content editable or old IE textarea. selection = ReactDOMSelection.getOffsets(input); } return selection || { start: 0, end: 0 }; }, 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 && getNodeTag(input) === "input") { var range = input.createTextRange(); range.collapse(true); range.moveStart("character", start); range.moveEnd("character", end - start); range.select(); } else { ReactDOMSelection.setOffsets(input, offsets); } } }; function isCollapsed(anchorNode, anchorOffset, focusNode, focusOffset) { return anchorNode === focusNode && anchorOffset === focusOffset; } 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); // 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 }; } /** * @param {DOMElement|DOMTextNode} node * @param {object} offsets */ 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(); } /** * 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 programmatically 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.textContent.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 = isBrowser && "selection" in document && !("getSelection" in window); var ReactDOMSelection = { getOffsets: useIEOffsets ? getIEOffsets : getModernOffsets, setOffsets: useIEOffsets ? setIEOffsets : setModernOffsets }; 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; } } 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)); } } //重写options var priorSelectionInformation = {}; var options = ReactInAnujs.options; function restoreSelectionInterface() { ReactInputSelection.restoreSelection(priorSelectionInformation); } function getSelectionInterface() { var a = ReactInputSelection.getSelectionInformation(); if (a.focusedElem && a.selectionRange) { priorSelectionInformation = a; }else{ priorSelectionInformation = {}; } } var newOptions = { beforePatch: getSelectionInterface, afterPatch: restoreSelectionInterface, }; function fixOptions(obj, name, oldFn, fn) { if (oldFn) { obj[name] = function (a) { fn(a); oldFn(a); }; } else { obj[name] = fn; } } for (let i in newOptions) { fixOptions(options, i, options[i], newOptions[i]); } return ReactInputSelection; });