RubyLouvre/anu

View on GitHub
scripts/build/ReactSelection.js

Summary

Maintainability
F
1 wk
Test Coverage
import { options, Fragment } from "../src/util";
import { Children } from "../src/Children";
import * as eventSystem from "../src/event";
import { PropTypes } from "../src/PropTypes";
import { Component } from "../src/Component";
import { win as window, contains as containsNode} from "../src/browser";
import { createClass } from "../src/createClass";
import { cloneElement } from "../src/cloneElement";
import { PureComponent } from "../src/PureComponent";
import { createElement } from "../src/createElement";
import { createContext } from "../src/createContext";
import { createPortal } from "../src/createPortal";
import { render, findDOMNode, isValidElement, unmountComponentAtNode, unstable_renderSubtreeIntoContainer } from "../src/diff";

var React;
if (window.React && window.React.options) {
    React = window.React; //解决引入多个
} else {
    React = window.React = window.ReactDOM = {
        version: "VERSION",
        render,
        hydrate: render,
        options,
        Fragment,
        PropTypes,
        Children,
        createPortal,
        createContext,
        Component,
        eventSystem,
        findDOMNode,
        createClass,
        createElement,
        cloneElement,
        PureComponent,
        isValidElement,
        unmountComponentAtNode,
        unstable_renderSubtreeIntoContainer,
        createFactory(type) {
            console.warn("createFactory is deprecated"); // eslint-disable-line
            var factory = createElement.bind(null, type);
            factory.type = type;
            return factory;
        }
    };
}

function isInDocument(node) {
    if (!inBrowser) {
        return false;
    }
    return containsNode(document.documentElement, node);
}

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 || (inBrowser
        ? 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 = inBrowser && "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 = {};
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]);
}


window.React = window.ReactDOM = React;

export default React;