diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js index 0de484d48cf..ce6a9e40687 100644 --- a/packages/react-dom/src/client/ReactDOM.js +++ b/packages/react-dom/src/client/ReactDOM.js @@ -31,7 +31,6 @@ var invariant = require('fbjs/lib/invariant'); var ReactDOMComponentTree = require('./ReactDOMComponentTree'); var ReactDOMFiberComponent = require('./ReactDOMFiberComponent'); -var ReactInputSelection = require('./ReactInputSelection'); var ReactBrowserEventEmitter = require('../events/ReactBrowserEventEmitter'); var DOMNamespaces = require('../shared/DOMNamespaces'); var { @@ -116,7 +115,6 @@ type HostContextProd = string; type HostContext = HostContextDev | HostContextProd; let eventsEnabled: ?boolean = null; -let selectionInformation: ?mixed = null; /** * True if the supplied DOM node is a valid node element. @@ -219,13 +217,10 @@ var DOMRenderer = ReactFiberReconciler({ prepareForCommit(): void { eventsEnabled = ReactBrowserEventEmitter.isEnabled(); - selectionInformation = ReactInputSelection.getSelectionInformation(); ReactBrowserEventEmitter.setEnabled(false); }, resetAfterCommit(): void { - ReactInputSelection.restoreSelection(selectionInformation); - selectionInformation = null; ReactBrowserEventEmitter.setEnabled(eventsEnabled); eventsEnabled = null; }, diff --git a/packages/react-dom/src/client/ReactDOMSelection.js b/packages/react-dom/src/client/ReactDOMSelection.js index 886a4af6f49..51a5de8a051 100644 --- a/packages/react-dom/src/client/ReactDOMSelection.js +++ b/packages/react-dom/src/client/ReactDOMSelection.js @@ -16,7 +16,11 @@ var {TEXT_NODE} = require('../shared/HTMLNodeType'); * @return {?object} */ function getModernOffsets(outerNode) { - var selection = window.getSelection && window.getSelection(); + var win = window; + if (outerNode.ownerDocument && outerNode.ownerDocument.defaultView) { + win = outerNode.ownerDocument.defaultView; + } + var selection = win.getSelection && win.getSelection(); if (!selection || selection.rangeCount === 0) { return null; @@ -153,11 +157,13 @@ function getModernOffsetsFromPoints( * @param {object} offsets */ function setModernOffsets(node, offsets) { - if (!window.getSelection) { + var doc = node.ownerDocument || document; + + if (!doc.defaultView.getSelection) { return; } - var selection = window.getSelection(); + var selection = doc.defaultView.getSelection(); var length = node[getTextContentAccessor()].length; var start = Math.min(offsets.start, length); var end = offsets.end === undefined ? start : Math.min(offsets.end, length); @@ -183,7 +189,7 @@ function setModernOffsets(node, offsets) { ) { return; } - var range = document.createRange(); + var range = doc.createRange(); range.setStart(startMarker.node, startMarker.offset); selection.removeAllRanges(); diff --git a/packages/react-dom/src/client/ReactInputSelection.js b/packages/react-dom/src/client/ReactInputSelection.js deleted file mode 100644 index 605f7ac7684..00000000000 --- a/packages/react-dom/src/client/ReactInputSelection.js +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Copyright (c) 2013-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -'use strict'; - -var containsNode = require('fbjs/lib/containsNode'); -var focusNode = require('fbjs/lib/focusNode'); -var getActiveElement = require('fbjs/lib/getActiveElement'); - -var ReactDOMSelection = require('./ReactDOMSelection'); -var {ELEMENT_NODE} = require('../shared/HTMLNodeType'); - -function isInDocument(node) { - return containsNode(document.documentElement, node); -} - -/** - * @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 = 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, - }; - }, - - /** - * @restoreSelection: If any selection information was potentially lost, - * restore it. This is useful when performing operations that could remove dom - * nodes and place them back in, resulting in focus being lost. - */ - 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); - } - - // Focusing a node can change the scroll position, which is undesirable - const ancestors = []; - let ancestor = priorFocusedElem; - while ((ancestor = ancestor.parentNode)) { - if (ancestor.nodeType === ELEMENT_NODE) { - ancestors.push({ - element: ancestor, - left: ancestor.scrollLeft, - top: ancestor.scrollTop, - }); - } - } - - focusNode(priorFocusedElem); - - for (let i = 0; i < ancestors.length; i++) { - const info = ancestors[i]; - info.element.scrollLeft = info.left; - info.element.scrollTop = info.top; - } - } - }, - - /** - * @getSelection: Gets the selection bounds of a focused textarea, input or - * contentEditable node. - * -@input: Look up selection bounds of this input - * -@return {start: selectionStart, end: selectionEnd} - */ - getSelection: function(input) { - var selection; - - if ('selectionStart' in input) { - // Modern browser with input or textarea. - selection = { - start: input.selectionStart, - end: input.selectionEnd, - }; - } else { - // Content editable or old IE textarea. - selection = ReactDOMSelection.getOffsets(input); - } - - return selection || {start: 0, end: 0}; - }, - - /** - * @setSelection: Sets the selection bounds of a textarea or input and focuses - * the input. - * -@input Set selection bounds of this input or textarea - * -@offsets Object of same form that is returned from get* - */ - 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 { - ReactDOMSelection.setOffsets(input, offsets); - } - }, -}; - -module.exports = ReactInputSelection; diff --git a/packages/react-dom/src/events/SelectEventPlugin.js b/packages/react-dom/src/events/SelectEventPlugin.js index d3023ae8b04..ab841b53358 100644 --- a/packages/react-dom/src/events/SelectEventPlugin.js +++ b/packages/react-dom/src/events/SelectEventPlugin.js @@ -16,7 +16,6 @@ var shallowEqual = require('fbjs/lib/shallowEqual'); var ReactBrowserEventEmitter = require('./ReactBrowserEventEmitter'); var ReactDOMComponentTree = require('../client/ReactDOMComponentTree'); -var ReactInputSelection = require('../client/ReactInputSelection'); var {DOCUMENT_NODE} = require('../shared/HTMLNodeType'); var skipSelectionChangeEvent = @@ -53,6 +52,22 @@ var mouseDown = false; var isListeningToAllDependencies = ReactBrowserEventEmitter.isListeningToAllDependencies; +/** + * Determine if a node can have a selection associated with it. + * + * @param {DOMElement} node + * @return {boolean} True if the node can have a selection. + */ +function hasSelectionCapabilities(node) { + var nodeName = node && node.nodeName && node.nodeName.toLowerCase(); + return ( + nodeName && + ((nodeName === 'input' && node.type === 'text') || + nodeName === 'textarea' || + node.contentEditable === 'true') + ); +} + /** * Get an object which is a unique representation of the current selection. * @@ -63,22 +78,25 @@ var isListeningToAllDependencies = * @return {object} */ function getSelection(node) { - if ( - 'selectionStart' in node && - ReactInputSelection.hasSelectionCapabilities(node) - ) { + if ('selectionStart' in node && hasSelectionCapabilities(node)) { return { start: node.selectionStart, end: node.selectionEnd, }; - } else if (window.getSelection) { - var selection = window.getSelection(); - return { - anchorNode: selection.anchorNode, - anchorOffset: selection.anchorOffset, - focusNode: selection.focusNode, - focusOffset: selection.focusOffset, - }; + } else { + var win = window; + if (node.ownerDocument && node.ownerDocument.defaultView) { + win = node.ownerDocument.defaultView; + } + if (win.getSelection) { + var selection = win.getSelection(); + return { + anchorNode: selection.anchorNode, + anchorOffset: selection.anchorOffset, + focusNode: selection.focusNode, + focusOffset: selection.focusOffset, + }; + } } } @@ -86,6 +104,7 @@ function getSelection(node) { * Poll selection to see whether it's changed. * * @param {object} nativeEvent + * @param {object} nativeEventTarget * @return {?SyntheticEvent} */ function constructSelectEvent(nativeEvent, nativeEventTarget) { @@ -93,10 +112,15 @@ function constructSelectEvent(nativeEvent, nativeEventTarget) { // selection (this matches native `select` event behavior). In HTML5, select // fires only on input and textarea thus if there's no focused element we // won't dispatch. + var doc = + nativeEventTarget.ownerDocument || + nativeEventTarget.document || + nativeEventTarget; + if ( mouseDown || activeElement == null || - activeElement !== getActiveElement() + activeElement !== getActiveElement(doc) ) { return null; } diff --git a/packages/react-dom/src/events/SyntheticClipboardEvent.js b/packages/react-dom/src/events/SyntheticClipboardEvent.js index d73860b6690..e33689f6e0d 100644 --- a/packages/react-dom/src/events/SyntheticClipboardEvent.js +++ b/packages/react-dom/src/events/SyntheticClipboardEvent.js @@ -15,9 +15,11 @@ var SyntheticEvent = require('events/SyntheticEvent'); */ var ClipboardEventInterface = { clipboardData: function(event) { - return 'clipboardData' in event - ? event.clipboardData - : window.clipboardData; + if ('clipboardData' in event) { + return event.clipboardData; + } + var doc = (event.target && event.target.ownerDocument) || document; + return doc.defaultView.clipboardData; }, }; diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index 683a2d68ee5..bf1353c2453 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -1,180 +1,180 @@ { "bundleSizes": { "react.development.js (UMD_DEV)": { - "size": 54533, - "gzip": 14280 + "size": 56844, + "gzip": 14860 }, "react.production.min.js (UMD_PROD)": { - "size": 6630, - "gzip": 2763 + "size": 6741, + "gzip": 2787 }, "react.development.js (NODE_DEV)": { - "size": 45437, - "gzip": 12025 + "size": 47748, + "gzip": 12598 }, "react.production.min.js (NODE_PROD)": { - "size": 5611, - "gzip": 2364 + "size": 5719, + "gzip": 2392 }, "React-dev.js (FB_DEV)": { - "size": 42912, - "gzip": 11311 + "size": 45223, + "gzip": 11890 }, "React-prod.js (FB_PROD)": { - "size": 24830, - "gzip": 6719 + "size": 24971, + "gzip": 6747 }, "react-dom.development.js (UMD_DEV)": { - "size": 628548, - "gzip": 144062 + "size": 617802, + "gzip": 141145 }, "react-dom.production.min.js (UMD_PROD)": { - "size": 100908, - "gzip": 31646 + "size": 99119, + "gzip": 30917 }, "react-dom.development.js (NODE_DEV)": { - "size": 590784, - "gzip": 135367 + "size": 582493, + "gzip": 132937 }, "react-dom.production.min.js (NODE_PROD)": { - "size": 107149, - "gzip": 33143 + "size": 105058, + "gzip": 32377 }, "ReactDOM-dev.js (FB_DEV)": { - "size": 590759, - "gzip": 135629 + "size": 582453, + "gzip": 133194 }, "ReactDOM-prod.js (FB_PROD)": { - "size": 421333, - "gzip": 93558 + "size": 411679, + "gzip": 90945 }, "react-dom-test-utils.development.js (NODE_DEV)": { - "size": 41553, - "gzip": 11076 + "size": 41544, + "gzip": 11072 }, "react-dom-test-utils.production.min.js (NODE_PROD)": { - "size": 11609, - "gzip": 4250 + "size": 11600, + "gzip": 4242 }, "ReactTestUtils-dev.js (FB_DEV)": { - "size": 41171, - "gzip": 11041 + "size": 41162, + "gzip": 11036 }, "react-dom-unstable-native-dependencies.development.js (UMD_DEV)": { - "size": 84862, - "gzip": 21152 + "size": 84853, + "gzip": 21148 }, "react-dom-unstable-native-dependencies.production.min.js (UMD_PROD)": { "size": 14441, "gzip": 4652 }, "react-dom-unstable-native-dependencies.development.js (NODE_DEV)": { - "size": 80643, - "gzip": 19924 + "size": 80634, + "gzip": 19920 }, "react-dom-unstable-native-dependencies.production.min.js (NODE_PROD)": { "size": 14002, "gzip": 4509 }, "ReactDOMUnstableNativeDependencies-dev.js (FB_DEV)": { - "size": 80136, - "gzip": 19870 + "size": 80127, + "gzip": 19865 }, "ReactDOMUnstableNativeDependencies-prod.js (FB_PROD)": { - "size": 65144, - "gzip": 15547 + "size": 65135, + "gzip": 15542 }, "react-dom-server.browser.development.js (UMD_DEV)": { - "size": 124541, - "gzip": 32144 + "size": 126953, + "gzip": 32659 }, "react-dom-server.browser.production.min.js (UMD_PROD)": { - "size": 15337, - "gzip": 5975 + "size": 15612, + "gzip": 6067 }, "react-dom-server.browser.development.js (NODE_DEV)": { - "size": 94428, - "gzip": 24913 + "size": 96840, + "gzip": 25434 }, "react-dom-server.browser.production.min.js (NODE_PROD)": { - "size": 15066, - "gzip": 5893 + "size": 15340, + "gzip": 5985 }, "ReactDOMServer-dev.js (FB_DEV)": { - "size": 93904, - "gzip": 24858 + "size": 96316, + "gzip": 25374 }, "ReactDOMServer-prod.js (FB_PROD)": { - "size": 42609, - "gzip": 11796 + "size": 43775, + "gzip": 12037 }, "react-dom-server.node.development.js (NODE_DEV)": { - "size": 96701, - "gzip": 25447 + "size": 99113, + "gzip": 25973 }, "react-dom-server.node.production.min.js (NODE_PROD)": { - "size": 15991, - "gzip": 6227 + "size": 16264, + "gzip": 6314 }, "react-art.development.js (UMD_DEV)": { - "size": 374492, - "gzip": 82196 + "size": 375095, + "gzip": 82388 }, "react-art.production.min.js (UMD_PROD)": { - "size": 82762, - "gzip": 25785 + "size": 83657, + "gzip": 25972 }, "react-art.development.js (NODE_DEV)": { - "size": 300260, - "gzip": 63352 + "size": 300861, + "gzip": 63574 }, "react-art.production.min.js (NODE_PROD)": { - "size": 54391, - "gzip": 17050 + "size": 54705, + "gzip": 17077 }, "ReactART-dev.js (FB_DEV)": { - "size": 298866, - "gzip": 63281 + "size": 299452, + "gzip": 63502 }, "ReactART-prod.js (FB_PROD)": { - "size": 223988, - "gzip": 45978 + "size": 223934, + "gzip": 46096 }, "ReactNativeRenderer-dev.js (RN_DEV)": { - "size": 286160, - "gzip": 49086 + "size": 288170, + "gzip": 49500 }, "ReactNativeRenderer-prod.js (RN_PROD)": { - "size": 223851, - "gzip": 38395 + "size": 224870, + "gzip": 38551 }, "ReactRTRenderer-dev.js (RN_DEV)": { - "size": 218007, - "gzip": 36780 + "size": 219175, + "gzip": 37043 }, "ReactRTRenderer-prod.js (RN_PROD)": { - "size": 165490, - "gzip": 27606 + "size": 165741, + "gzip": 27631 }, "ReactCSRenderer-dev.js (RN_DEV)": { - "size": 210392, - "gzip": 35120 + "size": 205708, + "gzip": 34449 }, "ReactCSRenderer-prod.js (RN_PROD)": { - "size": 160483, - "gzip": 26404 + "size": 153897, + "gzip": 25229 }, "react-test-renderer.development.js (NODE_DEV)": { - "size": 304118, - "gzip": 63787 + "size": 304719, + "gzip": 64011 }, "react-test-renderer.production.min.js (NODE_PROD)": { - "size": 55985, - "gzip": 17315 + "size": 56299, + "gzip": 17412 }, "ReactTestRenderer-dev.js (FB_DEV)": { - "size": 302714, - "gzip": 63724 + "size": 303300, + "gzip": 63950 }, "react-test-renderer-shallow.development.js (NODE_DEV)": { "size": 9246, @@ -189,24 +189,24 @@ "gzip": 2250 }, "react-noop-renderer.development.js (NODE_DEV)": { - "size": 293399, - "gzip": 61042 + "size": 294053, + "gzip": 61271 }, "react-reconciler.development.js (NODE_DEV)": { - "size": 278999, - "gzip": 57818 + "size": 279600, + "gzip": 58027 }, "react-reconciler.production.min.js (NODE_PROD)": { - "size": 38245, - "gzip": 11943 + "size": 39145, + "gzip": 12160 }, "react-call-return.development.js (NODE_DEV)": { - "size": 2721, - "gzip": 943 + "size": 2700, + "gzip": 937 }, "react-call-return.production.min.js (NODE_PROD)": { "size": 845, "gzip": 484 } } -} +} \ No newline at end of file