elements don't support innerText even when does.
- contentKey = 'textContent' in document.documentElement ? 'textContent' : 'innerText';
+function getFallbackBeforeInputChars(topLevelType, nativeEvent) {
+ // If we are currently composing (IME) and using a fallback to do so,
+ // try to extract the composed characters from the fallback object.
+ // If composition event is available, we extract a string only at
+ // compositionevent, otherwise extract it at fallback events.
+ if (isComposing) {
+ if (topLevelType === TOP_COMPOSITION_END || !canUseCompositionEvent && isFallbackCompositionEnd(topLevelType, nativeEvent)) {
+ var chars = getData();
+ reset();
+ isComposing = false;
+ return chars;
+ }
+ return null;
+ }
+
+ switch (topLevelType) {
+ case TOP_PASTE:
+ // If a paste event occurs after a keypress, throw out the input
+ // chars. Paste events should not lead to BeforeInput events.
+ return null;
+ case TOP_KEY_PRESS:
+ /**
+ * As of v27, Firefox may fire keypress events even when no character
+ * will be inserted. A few possibilities:
+ *
+ * - `which` is `0`. Arrow keys, Esc key, etc.
+ *
+ * - `which` is the pressed key code, but no char is available.
+ * Ex: 'AltGr + d` in Polish. There is no modified character for
+ * this key combination and no character is inserted into the
+ * document, but FF fires the keypress for char code `100` anyway.
+ * No `input` event will occur.
+ *
+ * - `which` is the pressed key code, but a command combination is
+ * being used. Ex: `Cmd+C`. No character is inserted, and no
+ * `input` event will occur.
+ */
+ if (!isKeypressCommand(nativeEvent)) {
+ // IE fires the `keypress` event when a user types an emoji via
+ // Touch keyboard of Windows. In such a case, the `char` property
+ // holds an emoji character like `\uD83D\uDE0A`. Because its length
+ // is 2, the property `which` does not represent an emoji correctly.
+ // In such a case, we directly return the `char` property instead of
+ // using `which`.
+ if (nativeEvent.char && nativeEvent.char.length > 1) {
+ return nativeEvent.char;
+ } else if (nativeEvent.which) {
+ return String.fromCharCode(nativeEvent.which);
+ }
+ }
+ return null;
+ case TOP_COMPOSITION_END:
+ return useFallbackCompositionData && !isUsingKoreanIME(nativeEvent) ? null : nativeEvent.data;
+ default:
+ return null;
}
- return contentKey;
}
/**
- * This helper object stores information about text content of a target node,
- * allowing comparison of content before and after a given event.
+ * Extract a SyntheticInputEvent for `beforeInput`, based on either native
+ * `textInput` or fallback behavior.
*
- * Identify the node where selection currently begins, then observe
- * both its text content and its current position in the DOM. Since the
- * browser may natively replace the target node during composition, we can
- * use its position to find its replacement.
+ * @return {?object} A SyntheticInputEvent.
+ */
+function extractBeforeInputEvent(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
+ var chars = void 0;
+
+ if (canUseTextInputEvent) {
+ chars = getNativeBeforeInputChars(topLevelType, nativeEvent);
+ } else {
+ chars = getFallbackBeforeInputChars(topLevelType, nativeEvent);
+ }
+
+ // If no characters are being inserted, no BeforeInput event should
+ // be fired.
+ if (!chars) {
+ return null;
+ }
+
+ var event = SyntheticInputEvent.getPooled(eventTypes.beforeInput, targetInst, nativeEvent, nativeEventTarget);
+
+ event.data = chars;
+ accumulateTwoPhaseDispatches(event);
+ return event;
+}
+
+/**
+ * Create an `onBeforeInput` event to match
+ * http://www.w3.org/TR/2013/WD-DOM-Level-3-Events-20131105/#events-inputevents.
+ *
+ * This event plugin is based on the native `textInput` event
+ * available in Chrome, Safari, Opera, and IE. This event fires after
+ * `onKeyPress` and `onCompositionEnd`, but before `onInput`.
*
+ * `beforeInput` is spec'd but not implemented in any browsers, and
+ * the `input` event does not provide any useful information about what has
+ * actually been added, contrary to the spec. Thus, `textInput` is the best
+ * available event to identify the characters that have actually been inserted
+ * into the target node.
*
+ * This plugin is also responsible for emitting `composition` events, thus
+ * allowing us to share composition fallback code for both `beforeInput` and
+ * `composition` event types.
*/
-var compositionState = {
- _root: null,
- _startText: null,
- _fallbackText: null
+var BeforeInputEventPlugin = {
+ eventTypes: eventTypes,
+
+ extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
+ var composition = extractCompositionEvent(topLevelType, targetInst, nativeEvent, nativeEventTarget);
+
+ var beforeInput = extractBeforeInputEvent(topLevelType, targetInst, nativeEvent, nativeEventTarget);
+
+ if (composition === null) {
+ return beforeInput;
+ }
+
+ if (beforeInput === null) {
+ return composition;
+ }
+
+ return [composition, beforeInput];
+ }
};
-function initialize(nativeEventTarget) {
- compositionState._root = nativeEventTarget;
- compositionState._startText = getText();
- return true;
+// Use to restore controlled state after a change event has fired.
+
+var restoreImpl = null;
+var restoreTarget = null;
+var restoreQueue = null;
+
+function restoreStateOfTarget(target) {
+ // We perform this translation at the end of the event loop so that we
+ // always receive the correct fiber here
+ var internalInstance = getInstanceFromNode(target);
+ if (!internalInstance) {
+ // Unmounted
+ return;
+ }
+ !(typeof restoreImpl === 'function') ? invariant(false, 'setRestoreImplementation() needs to be called to handle a target for controlled events. This error is likely caused by a bug in React. Please file an issue.') : void 0;
+ var props = getFiberCurrentPropsFromNode(internalInstance.stateNode);
+ restoreImpl(internalInstance.stateNode, internalInstance.type, props);
}
-function reset() {
- compositionState._root = null;
- compositionState._startText = null;
- compositionState._fallbackText = null;
+function setRestoreImplementation(impl) {
+ restoreImpl = impl;
}
-function getData() {
- if (compositionState._fallbackText) {
- return compositionState._fallbackText;
+function enqueueStateRestore(target) {
+ if (restoreTarget) {
+ if (restoreQueue) {
+ restoreQueue.push(target);
+ } else {
+ restoreQueue = [target];
+ }
+ } else {
+ restoreTarget = target;
}
+}
- var start;
- var startValue = compositionState._startText;
- var startLength = startValue.length;
- var end;
- var endValue = getText();
- var endLength = endValue.length;
+function needsStateRestore() {
+ return restoreTarget !== null || restoreQueue !== null;
+}
- for (start = 0; start < startLength; start++) {
- if (startValue[start] !== endValue[start]) {
- break;
- }
+function restoreStateIfNeeded() {
+ if (!restoreTarget) {
+ return;
}
+ var target = restoreTarget;
+ var queuedTargets = restoreQueue;
+ restoreTarget = null;
+ restoreQueue = null;
- var minEnd = startLength - start;
- for (end = 1; end <= minEnd; end++) {
- if (startValue[startLength - end] !== endValue[endLength - end]) {
- break;
+ restoreStateOfTarget(target);
+ if (queuedTargets) {
+ for (var i = 0; i < queuedTargets.length; i++) {
+ restoreStateOfTarget(queuedTargets[i]);
}
}
-
- var sliceTail = end > 1 ? 1 - end : undefined;
- compositionState._fallbackText = endValue.slice(start, sliceTail);
- return compositionState._fallbackText;
}
-function getText() {
- if ('value' in compositionState._root) {
- return compositionState._root.value;
+// Used as a way to call batchedUpdates when we don't have a reference to
+// the renderer. Such as when we're dispatching events or if third party
+// libraries need to call batchedUpdates. Eventually, this API will go away when
+// everything is batched by default. We'll then have a similar API to opt-out of
+// scheduled work and instead do synchronous work.
+
+// Defaults
+var _batchedUpdatesImpl = function (fn, bookkeeping) {
+ return fn(bookkeeping);
+};
+var _interactiveUpdatesImpl = function (fn, a, b) {
+ return fn(a, b);
+};
+var _flushInteractiveUpdatesImpl = function () {};
+
+var isBatching = false;
+function batchedUpdates(fn, bookkeeping) {
+ if (isBatching) {
+ // If we are currently inside another batch, we need to wait until it
+ // fully completes before restoring state.
+ return fn(bookkeeping);
+ }
+ isBatching = true;
+ try {
+ return _batchedUpdatesImpl(fn, bookkeeping);
+ } finally {
+ // Here we wait until all updates have propagated, which is important
+ // when using controlled components within layers:
+ // https://github.com/facebook/react/issues/1698
+ // Then we restore state of any controlled component.
+ isBatching = false;
+ var controlledComponentsHavePendingUpdates = needsStateRestore();
+ if (controlledComponentsHavePendingUpdates) {
+ // If a controlled event was fired, we may need to restore the state of
+ // the DOM node back to the controlled value. This is necessary when React
+ // bails out of the update without touching the DOM.
+ _flushInteractiveUpdatesImpl();
+ restoreStateIfNeeded();
+ }
}
- return compositionState._root[getTextContentAccessor()];
}
-/* eslint valid-typeof: 0 */
+function interactiveUpdates(fn, a, b) {
+ return _interactiveUpdatesImpl(fn, a, b);
+}
-var didWarnForAddedNewProperty = false;
-var isProxySupported = typeof Proxy === 'function';
-var EVENT_POOL_SIZE = 10;
-var shouldBeReleasedProperties = ['dispatchConfig', '_targetInst', 'nativeEvent', 'isDefaultPrevented', 'isPropagationStopped', '_dispatchListeners', '_dispatchInstances'];
+
+function setBatchingImplementation(batchedUpdatesImpl, interactiveUpdatesImpl, flushInteractiveUpdatesImpl) {
+ _batchedUpdatesImpl = batchedUpdatesImpl;
+ _interactiveUpdatesImpl = interactiveUpdatesImpl;
+ _flushInteractiveUpdatesImpl = flushInteractiveUpdatesImpl;
+}
/**
- * @interface Event
- * @see http://www.w3.org/TR/DOM-Level-3-Events/
+ * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary
*/
-var EventInterface = {
- type: null,
- target: null,
- // currentTarget is set when dispatching; no use in copying it here
- currentTarget: emptyFunction.thatReturnsNull,
- eventPhase: null,
- bubbles: null,
- cancelable: null,
- timeStamp: function (event) {
- return event.timeStamp || Date.now();
- },
- defaultPrevented: null,
- isTrusted: null
+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
};
-/**
- * Synthetic events are dispatched by event plugins, typically in response to a
- * top-level event delegation handler.
- *
- * These systems should generally use pooling to reduce the frequency of garbage
- * collection. The system should check `isPersistent` to determine whether the
- * event should be released into the pool after being dispatched. Users that
- * need a persisted event should invoke `persist`.
- *
- * Synthetic events (and subclasses) implement the DOM Level 3 Events API by
- * normalizing browser quirks. Subclasses do not necessarily have to implement a
- * DOM interface; custom application-specific events can also subclass this.
- *
- * @param {object} dispatchConfig Configuration used to dispatch this event.
- * @param {*} targetInst Marker identifying the event target.
- * @param {object} nativeEvent Native browser event.
- * @param {DOMEventTarget} nativeEventTarget Target node.
- */
-function SyntheticEvent(dispatchConfig, targetInst, nativeEvent, nativeEventTarget) {
- {
- // these have a getter/setter for warnings
- delete this.nativeEvent;
- delete this.preventDefault;
- delete this.stopPropagation;
- }
-
- this.dispatchConfig = dispatchConfig;
- this._targetInst = targetInst;
- this.nativeEvent = nativeEvent;
+function isTextInputElement(elem) {
+ var nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();
- var Interface = this.constructor.Interface;
- for (var propName in Interface) {
- if (!Interface.hasOwnProperty(propName)) {
- continue;
- }
- {
- delete this[propName]; // this has a getter/setter for warnings
- }
- var normalize = Interface[propName];
- if (normalize) {
- this[propName] = normalize(nativeEvent);
- } else {
- if (propName === 'target') {
- this.target = nativeEventTarget;
- } else {
- this[propName] = nativeEvent[propName];
- }
- }
+ if (nodeName === 'input') {
+ return !!supportedInputTypes[elem.type];
}
- var defaultPrevented = nativeEvent.defaultPrevented != null ? nativeEvent.defaultPrevented : nativeEvent.returnValue === false;
- if (defaultPrevented) {
- this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
- } else {
- this.isDefaultPrevented = emptyFunction.thatReturnsFalse;
+ if (nodeName === 'textarea') {
+ return true;
}
- this.isPropagationStopped = emptyFunction.thatReturnsFalse;
- return this;
-}
-
-_assign(SyntheticEvent.prototype, {
- preventDefault: function () {
- this.defaultPrevented = true;
- var event = this.nativeEvent;
- if (!event) {
- return;
- }
-
- if (event.preventDefault) {
- event.preventDefault();
- } else if (typeof event.returnValue !== 'unknown') {
- event.returnValue = false;
- }
- this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
- },
-
- stopPropagation: function () {
- var event = this.nativeEvent;
- if (!event) {
- return;
- }
-
- if (event.stopPropagation) {
- event.stopPropagation();
- } else if (typeof event.cancelBubble !== 'unknown') {
- // The ChangeEventPlugin registers a "propertychange" event for
- // IE. This event does not support bubbling or cancelling, and
- // any references to cancelBubble throw "Member not found". A
- // typeof check of "unknown" circumvents this issue (and is also
- // IE specific).
- event.cancelBubble = true;
- }
-
- this.isPropagationStopped = emptyFunction.thatReturnsTrue;
- },
-
- /**
- * We release all dispatched `SyntheticEvent`s after each event loop, adding
- * them back into the pool. This allows a way to hold onto a reference that
- * won't be added back into the pool.
- */
- persist: function () {
- this.isPersistent = emptyFunction.thatReturnsTrue;
- },
- /**
- * Checks if this event should be released back into the pool.
- *
- * @return {boolean} True if this should not be released, false otherwise.
- */
- isPersistent: emptyFunction.thatReturnsFalse,
+ return false;
+}
- /**
- * `PooledClass` looks for `destructor` on each instance it releases.
- */
- destructor: function () {
- var Interface = this.constructor.Interface;
- for (var propName in Interface) {
- {
- Object.defineProperty(this, propName, getPooledWarningPropertyDefinition(propName, Interface[propName]));
- }
- }
- for (var i = 0; i < shouldBeReleasedProperties.length; i++) {
- this[shouldBeReleasedProperties[i]] = null;
- }
- {
- Object.defineProperty(this, 'nativeEvent', getPooledWarningPropertyDefinition('nativeEvent', null));
- Object.defineProperty(this, 'preventDefault', getPooledWarningPropertyDefinition('preventDefault', emptyFunction));
- Object.defineProperty(this, 'stopPropagation', getPooledWarningPropertyDefinition('stopPropagation', emptyFunction));
- }
- }
-});
+/**
+ * HTML nodeType values that represent the type of the node
+ */
-SyntheticEvent.Interface = EventInterface;
+var ELEMENT_NODE = 1;
+var TEXT_NODE = 3;
+var COMMENT_NODE = 8;
+var DOCUMENT_NODE = 9;
+var DOCUMENT_FRAGMENT_NODE = 11;
/**
- * Helper to reduce boilerplate when creating subclasses.
+ * Gets the target node from a native browser event by accounting for
+ * inconsistencies in browser DOM APIs.
*
- * @param {function} Class
- * @param {?object} Interface
+ * @param {object} nativeEvent Native browser event.
+ * @return {DOMEventTarget} Target node.
*/
-SyntheticEvent.augmentClass = function (Class, Interface) {
- var Super = this;
-
- var E = function () {};
- E.prototype = Super.prototype;
- var prototype = new E();
-
- _assign(prototype, Class.prototype);
- Class.prototype = prototype;
- Class.prototype.constructor = Class;
-
- Class.Interface = _assign({}, Super.Interface, Interface);
- Class.augmentClass = Super.augmentClass;
- addEventPoolingTo(Class);
-};
+function getEventTarget(nativeEvent) {
+ // Fallback to nativeEvent.srcElement for IE9
+ // https://github.com/facebook/react/issues/12506
+ var target = nativeEvent.target || nativeEvent.srcElement || window;
-/** Proxying after everything set on SyntheticEvent
- * to resolve Proxy issue on some WebKit browsers
- * in which some Event properties are set to undefined (GH#10010)
- */
-{
- if (isProxySupported) {
- /*eslint-disable no-func-assign */
- SyntheticEvent = new Proxy(SyntheticEvent, {
- construct: function (target, args) {
- return this.apply(target, Object.create(target.prototype), args);
- },
- apply: function (constructor, that, args) {
- return new Proxy(constructor.apply(that, args), {
- set: function (target, prop, value) {
- if (prop !== 'isPersistent' && !target.constructor.Interface.hasOwnProperty(prop) && shouldBeReleasedProperties.indexOf(prop) === -1) {
- warning(didWarnForAddedNewProperty || target.isPersistent(), "This synthetic event is reused for performance reasons. If you're " + "seeing this, you're adding a new property in the synthetic event object. " + 'The property is never released. See ' + 'https://fb.me/react-event-pooling for more information.');
- didWarnForAddedNewProperty = true;
- }
- target[prop] = value;
- return true;
- }
- });
- }
- });
- /*eslint-enable no-func-assign */
+ // Normalize SVG element events #4963
+ if (target.correspondingUseElement) {
+ target = target.correspondingUseElement;
}
-}
-addEventPoolingTo(SyntheticEvent);
+ // Safari may fire events on text nodes (Node.TEXT_NODE is 3).
+ // @see http://www.quirksmode.org/js/events_properties.html
+ return target.nodeType === TEXT_NODE ? target.parentNode : target;
+}
/**
- * Helper to nullify syntheticEvent instance properties when destructing
+ * Checks if an event is supported in the current execution environment.
*
- * @param {String} propName
- * @param {?object} getVal
- * @return {object} defineProperty object
+ * NOTE: This will not work correctly for non-generic events such as `change`,
+ * `reset`, `load`, `error`, and `select`.
+ *
+ * Borrows from Modernizr.
+ *
+ * @param {string} eventNameSuffix Event name, e.g. "click".
+ * @return {boolean} True if the event is supported.
+ * @internal
+ * @license Modernizr 3.0.0pre (Custom Build) | MIT
*/
-function getPooledWarningPropertyDefinition(propName, getVal) {
- var isFunction = typeof getVal === 'function';
- return {
- configurable: true,
- set: set,
- get: get
- };
-
- function set(val) {
- var action = isFunction ? 'setting the method' : 'setting the property';
- warn(action, 'This is effectively a no-op');
- return val;
+function isEventSupported(eventNameSuffix) {
+ if (!canUseDOM) {
+ return false;
}
- function get() {
- var action = isFunction ? 'accessing the method' : 'accessing the property';
- var result = isFunction ? 'This is a no-op function' : 'This is set to null';
- warn(action, result);
- return getVal;
- }
+ var eventName = 'on' + eventNameSuffix;
+ var isSupported = eventName in document;
- function warn(action, result) {
- var warningCondition = false;
- warning(warningCondition, "This synthetic event is reused for performance reasons. If you're seeing this, " + "you're %s `%s` on a released/nullified synthetic event. %s. " + 'If you must keep the original synthetic event around, use event.persist(). ' + 'See https://fb.me/react-event-pooling for more information.', action, propName, result);
+ if (!isSupported) {
+ var element = document.createElement('div');
+ element.setAttribute(eventName, 'return;');
+ isSupported = typeof element[eventName] === 'function';
}
-}
-function getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) {
- var EventConstructor = this;
- if (EventConstructor.eventPool.length) {
- var instance = EventConstructor.eventPool.pop();
- EventConstructor.call(instance, dispatchConfig, targetInst, nativeEvent, nativeInst);
- return instance;
- }
- return new EventConstructor(dispatchConfig, targetInst, nativeEvent, nativeInst);
+ return isSupported;
}
-function releasePooledEvent(event) {
- var EventConstructor = this;
- !(event instanceof EventConstructor) ? invariant(false, 'Trying to release an event instance into a pool of a different type.') : void 0;
- event.destructor();
- if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {
- EventConstructor.eventPool.push(event);
- }
+function isCheckable(elem) {
+ var type = elem.type;
+ var nodeName = elem.nodeName;
+ return nodeName && nodeName.toLowerCase() === 'input' && (type === 'checkbox' || type === 'radio');
}
-function addEventPoolingTo(EventConstructor) {
- EventConstructor.eventPool = [];
- EventConstructor.getPooled = getPooledEvent;
- EventConstructor.release = releasePooledEvent;
+function getTracker(node) {
+ return node._valueTracker;
}
-var SyntheticEvent$1 = SyntheticEvent;
-
-/**
- * @interface Event
- * @see http://www.w3.org/TR/DOM-Level-3-Events/#events-compositionevents
- */
-var CompositionEventInterface = {
- data: null
-};
-
-/**
- * @param {object} dispatchConfig Configuration used to dispatch this event.
- * @param {string} dispatchMarker Marker identifying the event target.
- * @param {object} nativeEvent Native browser event.
- * @extends {SyntheticEvent}
- */
-function SyntheticCompositionEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
- return SyntheticEvent$1.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
+function detachTracker(node) {
+ node._valueTracker = null;
}
-SyntheticEvent$1.augmentClass(SyntheticCompositionEvent, CompositionEventInterface);
+function getValueFromNode(node) {
+ var value = '';
+ if (!node) {
+ return value;
+ }
-/**
- * @interface Event
- * @see http://www.w3.org/TR/2013/WD-DOM-Level-3-Events-20131105
- * /#events-inputevents
- */
-var InputEventInterface = {
- data: null
-};
+ if (isCheckable(node)) {
+ value = node.checked ? 'true' : 'false';
+ } else {
+ value = node.value;
+ }
-/**
- * @param {object} dispatchConfig Configuration used to dispatch this event.
- * @param {string} dispatchMarker Marker identifying the event target.
- * @param {object} nativeEvent Native browser event.
- * @extends {SyntheticEvent}
- */
-function SyntheticInputEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
- return SyntheticEvent$1.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
+ return value;
}
-SyntheticEvent$1.augmentClass(SyntheticInputEvent, InputEventInterface);
+function trackValueOnNode(node) {
+ var valueField = isCheckable(node) ? 'checked' : 'value';
+ var descriptor = Object.getOwnPropertyDescriptor(node.constructor.prototype, valueField);
-var END_KEYCODES = [9, 13, 27, 32]; // Tab, Return, Esc, Space
-var START_KEYCODE = 229;
+ var currentValue = '' + node[valueField];
-var canUseCompositionEvent = ExecutionEnvironment.canUseDOM && 'CompositionEvent' in window;
+ // if someone has already defined a value or Safari, then bail
+ // and don't track value will cause over reporting of changes,
+ // but it's better then a hard failure
+ // (needed for certain tests that spyOn input values and Safari)
+ if (node.hasOwnProperty(valueField) || typeof descriptor === 'undefined' || typeof descriptor.get !== 'function' || typeof descriptor.set !== 'function') {
+ return;
+ }
+ var get = descriptor.get,
+ set = descriptor.set;
-var documentMode = null;
-if (ExecutionEnvironment.canUseDOM && 'documentMode' in document) {
- documentMode = document.documentMode;
-}
+ Object.defineProperty(node, valueField, {
+ configurable: true,
+ get: function () {
+ return get.call(this);
+ },
+ set: function (value) {
+ currentValue = '' + value;
+ set.call(this, value);
+ }
+ });
+ // We could've passed this the first time
+ // but it triggers a bug in IE11 and Edge 14/15.
+ // Calling defineProperty() again should be equivalent.
+ // https://github.com/facebook/react/issues/11768
+ Object.defineProperty(node, valueField, {
+ enumerable: descriptor.enumerable
+ });
-// Webkit offers a very useful `textInput` event that can be used to
-// directly represent `beforeInput`. The IE `textinput` event is not as
-// useful, so we don't use it.
-var canUseTextInputEvent = ExecutionEnvironment.canUseDOM && 'TextEvent' in window && !documentMode && !isPresto();
+ var tracker = {
+ getValue: function () {
+ return currentValue;
+ },
+ setValue: function (value) {
+ currentValue = '' + value;
+ },
+ stopTracking: function () {
+ detachTracker(node);
+ delete node[valueField];
+ }
+ };
+ return tracker;
+}
-// In IE9+, we have access to composition events, but the data supplied
-// by the native compositionend event may be incorrect. Japanese ideographic
-// spaces, for instance (\u3000) are not recorded correctly.
-var useFallbackCompositionData = ExecutionEnvironment.canUseDOM && (!canUseCompositionEvent || documentMode && documentMode > 8 && documentMode <= 11);
+function track(node) {
+ if (getTracker(node)) {
+ return;
+ }
-/**
- * Opera <= 12 includes TextEvent in window, but does not fire
- * text input events. Rely on keypress instead.
- */
-function isPresto() {
- var opera = window.opera;
- return typeof opera === 'object' && typeof opera.version === 'function' && parseInt(opera.version(), 10) <= 12;
+ // TODO: Once it's just Fiber we can move this to node._wrapperState
+ node._valueTracker = trackValueOnNode(node);
}
-var SPACEBAR_CODE = 32;
-var SPACEBAR_CHAR = String.fromCharCode(SPACEBAR_CODE);
-
-// Events and their corresponding property names.
-var eventTypes = {
- beforeInput: {
- phasedRegistrationNames: {
- bubbled: 'onBeforeInput',
- captured: 'onBeforeInputCapture'
- },
- dependencies: ['topCompositionEnd', 'topKeyPress', 'topTextInput', 'topPaste']
- },
- compositionEnd: {
- phasedRegistrationNames: {
- bubbled: 'onCompositionEnd',
- captured: 'onCompositionEndCapture'
- },
- dependencies: ['topBlur', 'topCompositionEnd', 'topKeyDown', 'topKeyPress', 'topKeyUp', 'topMouseDown']
- },
- compositionStart: {
- phasedRegistrationNames: {
- bubbled: 'onCompositionStart',
- captured: 'onCompositionStartCapture'
- },
- dependencies: ['topBlur', 'topCompositionStart', 'topKeyDown', 'topKeyPress', 'topKeyUp', 'topMouseDown']
- },
- compositionUpdate: {
- phasedRegistrationNames: {
- bubbled: 'onCompositionUpdate',
- captured: 'onCompositionUpdateCapture'
- },
- dependencies: ['topBlur', 'topCompositionUpdate', 'topKeyDown', 'topKeyPress', 'topKeyUp', 'topMouseDown']
+function updateValueIfChanged(node) {
+ if (!node) {
+ return false;
}
-};
-
-// Track whether we've ever handled a keypress on the space key.
-var hasSpaceKeypress = false;
-/**
- * Return whether a native keypress event is assumed to be a command.
- * This is required because Firefox fires `keypress` events for key commands
- * (cut, copy, select-all, etc.) even though no character is inserted.
- */
-function isKeypressCommand(nativeEvent) {
- return (nativeEvent.ctrlKey || nativeEvent.altKey || nativeEvent.metaKey) &&
- // ctrlKey && altKey is equivalent to AltGr, and is not a command.
- !(nativeEvent.ctrlKey && nativeEvent.altKey);
-}
+ var tracker = getTracker(node);
+ // if there is no tracker at this point it's unlikely
+ // that trying again will succeed
+ if (!tracker) {
+ return true;
+ }
-/**
- * Translate native top level events into event types.
- *
- * @param {string} topLevelType
- * @return {object}
- */
-function getCompositionEventType(topLevelType) {
- switch (topLevelType) {
- case 'topCompositionStart':
- return eventTypes.compositionStart;
- case 'topCompositionEnd':
- return eventTypes.compositionEnd;
- case 'topCompositionUpdate':
- return eventTypes.compositionUpdate;
+ var lastValue = tracker.getValue();
+ var nextValue = getValueFromNode(node);
+ if (nextValue !== lastValue) {
+ tracker.setValue(nextValue);
+ return true;
}
+ return false;
}
-/**
- * Does our fallback best-guess model think this event signifies that
- * composition has begun?
- *
- * @param {string} topLevelType
- * @param {object} nativeEvent
- * @return {boolean}
- */
-function isFallbackCompositionStart(topLevelType, nativeEvent) {
- return topLevelType === 'topKeyDown' && nativeEvent.keyCode === START_KEYCODE;
-}
+var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
-/**
- * Does our fallback mode think that this event is the end of composition?
- *
- * @param {string} topLevelType
- * @param {object} nativeEvent
- * @return {boolean}
- */
-function isFallbackCompositionEnd(topLevelType, nativeEvent) {
- switch (topLevelType) {
- case 'topKeyUp':
- // Command keys insert or clear IME input.
- return END_KEYCODES.indexOf(nativeEvent.keyCode) !== -1;
- case 'topKeyDown':
- // Expect IME keyCode on each keydown. If we get any other
- // code we must have exited earlier.
- return nativeEvent.keyCode !== START_KEYCODE;
- case 'topKeyPress':
- case 'topMouseDown':
- case 'topBlur':
- // Events are not possible without cancelling IME.
- return true;
- default:
- return false;
+var BEFORE_SLASH_RE = /^(.*)[\\\/]/;
+
+var describeComponentFrame = function (name, source, ownerName) {
+ var sourceInfo = '';
+ if (source) {
+ var path = source.fileName;
+ var fileName = path.replace(BEFORE_SLASH_RE, '');
+ {
+ // In DEV, include code for a common special case:
+ // prefer "folder/index.js" instead of just "index.js".
+ if (/^index\./.test(fileName)) {
+ var match = path.match(BEFORE_SLASH_RE);
+ if (match) {
+ var pathBeforeSlash = match[1];
+ if (pathBeforeSlash) {
+ var folderName = pathBeforeSlash.replace(BEFORE_SLASH_RE, '');
+ fileName = folderName + '/' + fileName;
+ }
+ }
+ }
+ }
+ sourceInfo = ' (at ' + fileName + ':' + source.lineNumber + ')';
+ } else if (ownerName) {
+ sourceInfo = ' (created by ' + ownerName + ')';
}
-}
+ return '\n in ' + (name || 'Unknown') + sourceInfo;
+};
-/**
- * Google Input Tools provides composition data via a CustomEvent,
- * with the `data` property populated in the `detail` object. If this
- * is available on the event object, use it. If not, this is a plain
- * composition event and we have nothing special to extract.
- *
- * @param {object} nativeEvent
- * @return {?string}
- */
-function getDataFromCustomEvent(nativeEvent) {
- var detail = nativeEvent.detail;
- if (typeof detail === 'object' && 'data' in detail) {
- return detail.data;
+// The Symbol used to tag the ReactElement-like types. If there is no native Symbol
+// nor polyfill, then a plain number is used for performance.
+var hasSymbol = typeof Symbol === 'function' && Symbol.for;
+
+var REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for('react.element') : 0xeac7;
+var REACT_PORTAL_TYPE = hasSymbol ? Symbol.for('react.portal') : 0xeaca;
+var REACT_FRAGMENT_TYPE = hasSymbol ? Symbol.for('react.fragment') : 0xeacb;
+var REACT_STRICT_MODE_TYPE = hasSymbol ? Symbol.for('react.strict_mode') : 0xeacc;
+var REACT_PROFILER_TYPE = hasSymbol ? Symbol.for('react.profiler') : 0xead2;
+var REACT_PROVIDER_TYPE = hasSymbol ? Symbol.for('react.provider') : 0xeacd;
+var REACT_CONTEXT_TYPE = hasSymbol ? Symbol.for('react.context') : 0xeace;
+
+var REACT_CONCURRENT_MODE_TYPE = hasSymbol ? Symbol.for('react.concurrent_mode') : 0xeacf;
+var REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol.for('react.forward_ref') : 0xead0;
+var REACT_SUSPENSE_TYPE = hasSymbol ? Symbol.for('react.suspense') : 0xead1;
+var REACT_MEMO_TYPE = hasSymbol ? Symbol.for('react.memo') : 0xead3;
+var REACT_LAZY_TYPE = hasSymbol ? Symbol.for('react.lazy') : 0xead4;
+
+var MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
+var FAUX_ITERATOR_SYMBOL = '@@iterator';
+
+function getIteratorFn(maybeIterable) {
+ if (maybeIterable === null || typeof maybeIterable !== 'object') {
+ return null;
+ }
+ var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];
+ if (typeof maybeIterator === 'function') {
+ return maybeIterator;
}
return null;
}
-// Track the current IME composition status, if any.
-var isComposing = false;
+var Pending = 0;
+var Resolved = 1;
+var Rejected = 2;
-/**
- * @return {?object} A SyntheticCompositionEvent.
- */
-function extractCompositionEvent(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
- var eventType;
- var fallbackData;
+function refineResolvedLazyComponent(lazyComponent) {
+ return lazyComponent._status === Resolved ? lazyComponent._result : null;
+}
- if (canUseCompositionEvent) {
- eventType = getCompositionEventType(topLevelType);
- } else if (!isComposing) {
- if (isFallbackCompositionStart(topLevelType, nativeEvent)) {
- eventType = eventTypes.compositionStart;
- }
- } else if (isFallbackCompositionEnd(topLevelType, nativeEvent)) {
- eventType = eventTypes.compositionEnd;
- }
+function getWrappedName(outerType, innerType, wrapperName) {
+ var functionName = innerType.displayName || innerType.name || '';
+ return outerType.displayName || (functionName !== '' ? wrapperName + '(' + functionName + ')' : wrapperName);
+}
- if (!eventType) {
+function getComponentName(type) {
+ if (type == null) {
+ // Host root, text node or just invalid type.
return null;
}
-
- if (useFallbackCompositionData) {
- // The current composition is stored statically and must not be
- // overwritten while composition continues.
- if (!isComposing && eventType === eventTypes.compositionStart) {
- isComposing = initialize(nativeEventTarget);
- } else if (eventType === eventTypes.compositionEnd) {
- if (isComposing) {
- fallbackData = getData();
- }
+ {
+ if (typeof type.tag === 'number') {
+ warningWithoutStack$1(false, 'Received an unexpected object in getComponentName(). ' + 'This is likely a bug in React. Please file an issue.');
}
}
-
- var event = SyntheticCompositionEvent.getPooled(eventType, targetInst, nativeEvent, nativeEventTarget);
-
- if (fallbackData) {
- // Inject data generated from fallback path into the synthetic event.
- // This matches the property of native CompositionEventInterface.
- event.data = fallbackData;
- } else {
- var customData = getDataFromCustomEvent(nativeEvent);
- if (customData !== null) {
- event.data = customData;
+ if (typeof type === 'function') {
+ return type.displayName || type.name || null;
+ }
+ if (typeof type === 'string') {
+ return type;
+ }
+ switch (type) {
+ case REACT_CONCURRENT_MODE_TYPE:
+ return 'ConcurrentMode';
+ case REACT_FRAGMENT_TYPE:
+ return 'Fragment';
+ case REACT_PORTAL_TYPE:
+ return 'Portal';
+ case REACT_PROFILER_TYPE:
+ return 'Profiler';
+ case REACT_STRICT_MODE_TYPE:
+ return 'StrictMode';
+ case REACT_SUSPENSE_TYPE:
+ return 'Suspense';
+ }
+ if (typeof type === 'object') {
+ switch (type.$$typeof) {
+ case REACT_CONTEXT_TYPE:
+ return 'Context.Consumer';
+ case REACT_PROVIDER_TYPE:
+ return 'Context.Provider';
+ case REACT_FORWARD_REF_TYPE:
+ return getWrappedName(type, type.render, 'ForwardRef');
+ case REACT_MEMO_TYPE:
+ return getComponentName(type.type);
+ case REACT_LAZY_TYPE:
+ {
+ var thenable = type;
+ var resolvedThenable = refineResolvedLazyComponent(thenable);
+ if (resolvedThenable) {
+ return getComponentName(resolvedThenable);
+ }
+ }
}
}
-
- accumulateTwoPhaseDispatches(event);
- return event;
+ return null;
}
-/**
- * @param {TopLevelTypes} topLevelType Record from `BrowserEventConstants`.
- * @param {object} nativeEvent Native browser event.
- * @return {?string} The string corresponding to this `beforeInput` event.
- */
-function getNativeBeforeInputChars(topLevelType, nativeEvent) {
- switch (topLevelType) {
- case 'topCompositionEnd':
- return getDataFromCustomEvent(nativeEvent);
- case 'topKeyPress':
- /**
- * If native `textInput` events are available, our goal is to make
- * use of them. However, there is a special case: the spacebar key.
- * In Webkit, preventing default on a spacebar `textInput` event
- * cancels character insertion, but it *also* causes the browser
- * to fall back to its default spacebar behavior of scrolling the
- * page.
- *
- * Tracking at:
- * https://code.google.com/p/chromium/issues/detail?id=355103
- *
- * To avoid this issue, use the keypress event as if no `textInput`
- * event is available.
- */
- var which = nativeEvent.which;
- if (which !== SPACEBAR_CODE) {
- return null;
- }
-
- hasSpaceKeypress = true;
- return SPACEBAR_CHAR;
-
- case 'topTextInput':
- // Record the characters to be added to the DOM.
- var chars = nativeEvent.data;
+var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
- // If it's a spacebar character, assume that we have already handled
- // it at the keypress level and bail immediately. Android Chrome
- // doesn't give us keycodes, so we need to blacklist it.
- if (chars === SPACEBAR_CHAR && hasSpaceKeypress) {
- return null;
+function describeFiber(fiber) {
+ switch (fiber.tag) {
+ case IndeterminateComponent:
+ case LazyComponent:
+ case FunctionComponent:
+ case ClassComponent:
+ case HostComponent:
+ case Mode:
+ case SuspenseComponent:
+ var owner = fiber._debugOwner;
+ var source = fiber._debugSource;
+ var name = getComponentName(fiber.type);
+ var ownerName = null;
+ if (owner) {
+ ownerName = getComponentName(owner.type);
}
+ return describeComponentFrame(name, source, ownerName);
+ default:
+ return '';
+ }
+}
- return chars;
+function getStackByFiberInDevAndProd(workInProgress) {
+ var info = '';
+ var node = workInProgress;
+ do {
+ info += describeFiber(node);
+ node = node.return;
+ } while (node);
+ return info;
+}
- default:
- // For other native event types, do nothing.
+var current = null;
+var phase = null;
+
+function getCurrentFiberOwnerNameInDevOrNull() {
+ {
+ if (current === null) {
return null;
+ }
+ var owner = current._debugOwner;
+ if (owner !== null && typeof owner !== 'undefined') {
+ return getComponentName(owner.type);
+ }
}
+ return null;
}
-/**
- * For browsers that do not provide the `textInput` event, extract the
- * appropriate string to use for SyntheticInputEvent.
- *
- * @param {string} topLevelType Record from `BrowserEventConstants`.
- * @param {object} nativeEvent Native browser event.
- * @return {?string} The fallback string for this `beforeInput` event.
- */
-function getFallbackBeforeInputChars(topLevelType, nativeEvent) {
- // If we are currently composing (IME) and using a fallback to do so,
- // try to extract the composed characters from the fallback object.
- // If composition event is available, we extract a string only at
- // compositionevent, otherwise extract it at fallback events.
- if (isComposing) {
- if (topLevelType === 'topCompositionEnd' || !canUseCompositionEvent && isFallbackCompositionEnd(topLevelType, nativeEvent)) {
- var chars = getData();
- reset();
- isComposing = false;
- return chars;
+function getCurrentFiberStackInDev() {
+ {
+ if (current === null) {
+ return '';
}
- return null;
+ // Safe because if current fiber exists, we are reconciling,
+ // and it is guaranteed to be the work-in-progress version.
+ return getStackByFiberInDevAndProd(current);
}
+ return '';
+}
- switch (topLevelType) {
- case 'topPaste':
- // If a paste event occurs after a keypress, throw out the input
- // chars. Paste events should not lead to BeforeInput events.
- return null;
- case 'topKeyPress':
- /**
- * As of v27, Firefox may fire keypress events even when no character
- * will be inserted. A few possibilities:
- *
- * - `which` is `0`. Arrow keys, Esc key, etc.
- *
- * - `which` is the pressed key code, but no char is available.
- * Ex: 'AltGr + d` in Polish. There is no modified character for
- * this key combination and no character is inserted into the
- * document, but FF fires the keypress for char code `100` anyway.
- * No `input` event will occur.
- *
- * - `which` is the pressed key code, but a command combination is
- * being used. Ex: `Cmd+C`. No character is inserted, and no
- * `input` event will occur.
- */
- if (!isKeypressCommand(nativeEvent)) {
- // IE fires the `keypress` event when a user types an emoji via
- // Touch keyboard of Windows. In such a case, the `char` property
- // holds an emoji character like `\uD83D\uDE0A`. Because its length
- // is 2, the property `which` does not represent an emoji correctly.
- // In such a case, we directly return the `char` property instead of
- // using `which`.
- if (nativeEvent.char && nativeEvent.char.length > 1) {
- return nativeEvent.char;
- } else if (nativeEvent.which) {
- return String.fromCharCode(nativeEvent.which);
- }
- }
- return null;
- case 'topCompositionEnd':
- return useFallbackCompositionData ? null : nativeEvent.data;
- default:
- return null;
+function resetCurrentFiber() {
+ {
+ ReactDebugCurrentFrame.getCurrentStack = null;
+ current = null;
+ phase = null;
}
}
-/**
- * Extract a SyntheticInputEvent for `beforeInput`, based on either native
- * `textInput` or fallback behavior.
- *
- * @return {?object} A SyntheticInputEvent.
- */
-function extractBeforeInputEvent(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
- var chars;
-
- if (canUseTextInputEvent) {
- chars = getNativeBeforeInputChars(topLevelType, nativeEvent);
- } else {
- chars = getFallbackBeforeInputChars(topLevelType, nativeEvent);
+function setCurrentFiber(fiber) {
+ {
+ ReactDebugCurrentFrame.getCurrentStack = getCurrentFiberStackInDev;
+ current = fiber;
+ phase = null;
}
+}
- // If no characters are being inserted, no BeforeInput event should
- // be fired.
- if (!chars) {
- return null;
+function setCurrentPhase(lifeCyclePhase) {
+ {
+ phase = lifeCyclePhase;
}
-
- var event = SyntheticInputEvent.getPooled(eventTypes.beforeInput, targetInst, nativeEvent, nativeEventTarget);
-
- event.data = chars;
- accumulateTwoPhaseDispatches(event);
- return event;
}
/**
- * Create an `onBeforeInput` event to match
- * http://www.w3.org/TR/2013/WD-DOM-Level-3-Events-20131105/#events-inputevents.
- *
- * This event plugin is based on the native `textInput` event
- * available in Chrome, Safari, Opera, and IE. This event fires after
- * `onKeyPress` and `onCompositionEnd`, but before `onInput`.
- *
- * `beforeInput` is spec'd but not implemented in any browsers, and
- * the `input` event does not provide any useful information about what has
- * actually been added, contrary to the spec. Thus, `textInput` is the best
- * available event to identify the characters that have actually been inserted
- * into the target node.
- *
- * This plugin is also responsible for emitting `composition` events, thus
- * allowing us to share composition fallback code for both `beforeInput` and
- * `composition` event types.
+ * Similar to invariant but only logs a warning if the condition is not met.
+ * This can be used to log issues in development environments in critical
+ * paths. Removing the logging code for production environments will keep the
+ * same logic and follow the same code paths.
*/
-var BeforeInputEventPlugin = {
- eventTypes: eventTypes,
- extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
- return [extractCompositionEvent(topLevelType, targetInst, nativeEvent, nativeEventTarget), extractBeforeInputEvent(topLevelType, targetInst, nativeEvent, nativeEventTarget)];
- }
-};
+var warning = warningWithoutStack$1;
-// Use to restore controlled state after a change event has fired.
+{
+ warning = function (condition, format) {
+ if (condition) {
+ return;
+ }
+ var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
+ var stack = ReactDebugCurrentFrame.getStackAddendum();
+ // eslint-disable-next-line react-internal/warning-and-invariant-args
-var fiberHostComponent = null;
+ for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
+ args[_key - 2] = arguments[_key];
+ }
-var ReactControlledComponentInjection = {
- injectFiberControlledHostComponent: function (hostComponentImpl) {
- // The fiber implementation doesn't use dynamic dispatch so we need to
- // inject the implementation.
- fiberHostComponent = hostComponentImpl;
- }
-};
+ warningWithoutStack$1.apply(undefined, [false, format + '%s'].concat(args, [stack]));
+ };
+}
-var restoreTarget = null;
-var restoreQueue = null;
+var warning$1 = warning;
-function restoreStateOfTarget(target) {
- // We perform this translation at the end of the event loop so that we
- // always receive the correct fiber here
- var internalInstance = getInstanceFromNode(target);
- if (!internalInstance) {
- // Unmounted
- return;
- }
- !(fiberHostComponent && typeof fiberHostComponent.restoreControlledState === 'function') ? invariant(false, 'Fiber needs to be injected to handle a fiber target for controlled events. This error is likely caused by a bug in React. Please file an issue.') : void 0;
- var props = getFiberCurrentPropsFromNode(internalInstance.stateNode);
- fiberHostComponent.restoreControlledState(internalInstance.stateNode, internalInstance.type, props);
-}
+// A reserved attribute.
+// It is handled by React separately and shouldn't be written to the DOM.
+var RESERVED = 0;
-var injection$3 = ReactControlledComponentInjection;
+// A simple string attribute.
+// Attributes that aren't in the whitelist are presumed to have this type.
+var STRING = 1;
-function enqueueStateRestore(target) {
- if (restoreTarget) {
- if (restoreQueue) {
- restoreQueue.push(target);
- } else {
- restoreQueue = [target];
- }
- } else {
- restoreTarget = target;
- }
-}
+// A string attribute that accepts booleans in React. In HTML, these are called
+// "enumerated" attributes with "true" and "false" as possible values.
+// When true, it should be set to a "true" string.
+// When false, it should be set to a "false" string.
+var BOOLEANISH_STRING = 2;
-function restoreStateIfNeeded() {
- if (!restoreTarget) {
- return;
- }
- var target = restoreTarget;
- var queuedTargets = restoreQueue;
- restoreTarget = null;
- restoreQueue = null;
+// A real boolean attribute.
+// When true, it should be present (set either to an empty string or its name).
+// When false, it should be omitted.
+var BOOLEAN = 3;
- restoreStateOfTarget(target);
- if (queuedTargets) {
- for (var i = 0; i < queuedTargets.length; i++) {
- restoreStateOfTarget(queuedTargets[i]);
- }
- }
-}
+// An attribute that can be used as a flag as well as with a value.
+// When true, it should be present (set either to an empty string or its name).
+// When false, it should be omitted.
+// For any other value, should be present with that value.
+var OVERLOADED_BOOLEAN = 4;
-var ReactControlledComponent = Object.freeze({
- injection: injection$3,
- enqueueStateRestore: enqueueStateRestore,
- restoreStateIfNeeded: restoreStateIfNeeded
-});
+// An attribute that must be numeric or parse as a numeric.
+// When falsy, it should be removed.
+var NUMERIC = 5;
-// Used as a way to call batchedUpdates when we don't have a reference to
-// the renderer. Such as when we're dispatching events or if third party
-// libraries need to call batchedUpdates. Eventually, this API will go away when
-// everything is batched by default. We'll then have a similar API to opt-out of
-// scheduled work and instead do synchronous work.
+// An attribute that must be positive numeric or parse as a positive numeric.
+// When falsy, it should be removed.
+var POSITIVE_NUMERIC = 6;
-// Defaults
-var fiberBatchedUpdates = function (fn, bookkeeping) {
- return fn(bookkeeping);
-};
+/* eslint-disable max-len */
+var ATTRIBUTE_NAME_START_CHAR = ':A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD';
+/* eslint-enable max-len */
+var ATTRIBUTE_NAME_CHAR = ATTRIBUTE_NAME_START_CHAR + '\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040';
-var isNestingBatched = false;
-function batchedUpdates(fn, bookkeeping) {
- if (isNestingBatched) {
- // If we are currently inside another batch, we need to wait until it
- // fully completes before restoring state. Therefore, we add the target to
- // a queue of work.
- return fiberBatchedUpdates(fn, bookkeeping);
+
+var ROOT_ATTRIBUTE_NAME = 'data-reactroot';
+var VALID_ATTRIBUTE_NAME_REGEX = new RegExp('^[' + ATTRIBUTE_NAME_START_CHAR + '][' + ATTRIBUTE_NAME_CHAR + ']*$');
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+var illegalAttributeNameCache = {};
+var validatedAttributeNameCache = {};
+
+function isAttributeNameSafe(attributeName) {
+ if (hasOwnProperty.call(validatedAttributeNameCache, attributeName)) {
+ return true;
}
- isNestingBatched = true;
- try {
- return fiberBatchedUpdates(fn, bookkeeping);
- } finally {
- // Here we wait until all updates have propagated, which is important
- // when using controlled components within layers:
- // https://github.com/facebook/react/issues/1698
- // Then we restore state of any controlled component.
- isNestingBatched = false;
- restoreStateIfNeeded();
+ if (hasOwnProperty.call(illegalAttributeNameCache, attributeName)) {
+ return false;
+ }
+ if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)) {
+ validatedAttributeNameCache[attributeName] = true;
+ return true;
+ }
+ illegalAttributeNameCache[attributeName] = true;
+ {
+ warning$1(false, 'Invalid attribute name: `%s`', attributeName);
}
+ return false;
}
-var ReactGenericBatchingInjection = {
- injectFiberBatchedUpdates: function (_batchedUpdates) {
- fiberBatchedUpdates = _batchedUpdates;
+function shouldIgnoreAttribute(name, propertyInfo, isCustomComponentTag) {
+ if (propertyInfo !== null) {
+ return propertyInfo.type === RESERVED;
}
-};
-
-var injection$4 = ReactGenericBatchingInjection;
-
-/**
- * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary
- */
-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
-};
-
-function isTextInputElement(elem) {
- var nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();
+ if (isCustomComponentTag) {
+ return false;
+ }
+ if (name.length > 2 && (name[0] === 'o' || name[0] === 'O') && (name[1] === 'n' || name[1] === 'N')) {
+ return true;
+ }
+ return false;
+}
- if (nodeName === 'input') {
- return !!supportedInputTypes[elem.type];
+function shouldRemoveAttributeWithWarning(name, value, propertyInfo, isCustomComponentTag) {
+ if (propertyInfo !== null && propertyInfo.type === RESERVED) {
+ return false;
+ }
+ switch (typeof value) {
+ case 'function':
+ // $FlowIssue symbol is perfectly valid here
+ case 'symbol':
+ // eslint-disable-line
+ return true;
+ case 'boolean':
+ {
+ if (isCustomComponentTag) {
+ return false;
+ }
+ if (propertyInfo !== null) {
+ return !propertyInfo.acceptsBooleans;
+ } else {
+ var prefix = name.toLowerCase().slice(0, 5);
+ return prefix !== 'data-' && prefix !== 'aria-';
+ }
+ }
+ default:
+ return false;
}
+}
- if (nodeName === 'textarea') {
+function shouldRemoveAttribute(name, value, propertyInfo, isCustomComponentTag) {
+ if (value === null || typeof value === 'undefined') {
return true;
}
-
+ if (shouldRemoveAttributeWithWarning(name, value, propertyInfo, isCustomComponentTag)) {
+ return true;
+ }
+ if (isCustomComponentTag) {
+ return false;
+ }
+ if (propertyInfo !== null) {
+ switch (propertyInfo.type) {
+ case BOOLEAN:
+ return !value;
+ case OVERLOADED_BOOLEAN:
+ return value === false;
+ case NUMERIC:
+ return isNaN(value);
+ case POSITIVE_NUMERIC:
+ return isNaN(value) || value < 1;
+ }
+ }
return false;
}
-/**
- * HTML nodeType values that represent the type of the node
- */
+function getPropertyInfo(name) {
+ return properties.hasOwnProperty(name) ? properties[name] : null;
+}
-var ELEMENT_NODE = 1;
-var TEXT_NODE = 3;
-var COMMENT_NODE = 8;
-var DOCUMENT_NODE = 9;
-var DOCUMENT_FRAGMENT_NODE = 11;
+function PropertyInfoRecord(name, type, mustUseProperty, attributeName, attributeNamespace) {
+ this.acceptsBooleans = type === BOOLEANISH_STRING || type === BOOLEAN || type === OVERLOADED_BOOLEAN;
+ this.attributeName = attributeName;
+ this.attributeNamespace = attributeNamespace;
+ this.mustUseProperty = mustUseProperty;
+ this.propertyName = name;
+ this.type = type;
+}
+
+// When adding attributes to this list, be sure to also add them to
+// the `possibleStandardNames` module to ensure casing and incorrect
+// name warnings.
+var properties = {};
+
+// These props are reserved by React. They shouldn't be written to the DOM.
+['children', 'dangerouslySetInnerHTML',
+// TODO: This prevents the assignment of defaultValue to regular
+// elements (not just inputs). Now that ReactDOMInput assigns to the
+// defaultValue property -- do we need this?
+'defaultValue', 'defaultChecked', 'innerHTML', 'suppressContentEditableWarning', 'suppressHydrationWarning', 'style'].forEach(function (name) {
+ properties[name] = new PropertyInfoRecord(name, RESERVED, false, // mustUseProperty
+ name, // attributeName
+ null);
+} // attributeNamespace
+);
+
+// A few React string attributes have a different name.
+// This is a mapping from React prop names to the attribute names.
+[['acceptCharset', 'accept-charset'], ['className', 'class'], ['htmlFor', 'for'], ['httpEquiv', 'http-equiv']].forEach(function (_ref) {
+ var name = _ref[0],
+ attributeName = _ref[1];
+
+ properties[name] = new PropertyInfoRecord(name, STRING, false, // mustUseProperty
+ attributeName, // attributeName
+ null);
+} // attributeNamespace
+);
+
+// These are "enumerated" HTML attributes that accept "true" and "false".
+// In React, we let users pass `true` and `false` even though technically
+// these aren't boolean attributes (they are coerced to strings).
+['contentEditable', 'draggable', 'spellCheck', 'value'].forEach(function (name) {
+ properties[name] = new PropertyInfoRecord(name, BOOLEANISH_STRING, false, // mustUseProperty
+ name.toLowerCase(), // attributeName
+ null);
+} // attributeNamespace
+);
+
+// These are "enumerated" SVG attributes that accept "true" and "false".
+// In React, we let users pass `true` and `false` even though technically
+// these aren't boolean attributes (they are coerced to strings).
+// Since these are SVG attributes, their attribute names are case-sensitive.
+['autoReverse', 'externalResourcesRequired', 'focusable', 'preserveAlpha'].forEach(function (name) {
+ properties[name] = new PropertyInfoRecord(name, BOOLEANISH_STRING, false, // mustUseProperty
+ name, // attributeName
+ null);
+} // attributeNamespace
+);
+
+// These are HTML boolean attributes.
+['allowFullScreen', 'async',
+// Note: there is a special case that prevents it from being written to the DOM
+// on the client side because the browsers are inconsistent. Instead we call focus().
+'autoFocus', 'autoPlay', 'controls', 'default', 'defer', 'disabled', 'formNoValidate', 'hidden', 'loop', 'noModule', 'noValidate', 'open', 'playsInline', 'readOnly', 'required', 'reversed', 'scoped', 'seamless',
+// Microdata
+'itemScope'].forEach(function (name) {
+ properties[name] = new PropertyInfoRecord(name, BOOLEAN, false, // mustUseProperty
+ name.toLowerCase(), // attributeName
+ null);
+} // attributeNamespace
+);
+
+// These are the few React props that we set as DOM properties
+// rather than attributes. These are all booleans.
+['checked',
+// Note: `option.selected` is not updated if `select.multiple` is
+// disabled with `removeAttribute`. We have special logic for handling this.
+'multiple', 'muted', 'selected'].forEach(function (name) {
+ properties[name] = new PropertyInfoRecord(name, BOOLEAN, true, // mustUseProperty
+ name, // attributeName
+ null);
+} // attributeNamespace
+);
+
+// These are HTML attributes that are "overloaded booleans": they behave like
+// booleans, but can also accept a string value.
+['capture', 'download'].forEach(function (name) {
+ properties[name] = new PropertyInfoRecord(name, OVERLOADED_BOOLEAN, false, // mustUseProperty
+ name, // attributeName
+ null);
+} // attributeNamespace
+);
+
+// These are HTML attributes that must be positive numbers.
+['cols', 'rows', 'size', 'span'].forEach(function (name) {
+ properties[name] = new PropertyInfoRecord(name, POSITIVE_NUMERIC, false, // mustUseProperty
+ name, // attributeName
+ null);
+} // attributeNamespace
+);
+
+// These are HTML attributes that must be numbers.
+['rowSpan', 'start'].forEach(function (name) {
+ properties[name] = new PropertyInfoRecord(name, NUMERIC, false, // mustUseProperty
+ name.toLowerCase(), // attributeName
+ null);
+} // attributeNamespace
+);
+
+var CAMELIZE = /[\-\:]([a-z])/g;
+var capitalize = function (token) {
+ return token[1].toUpperCase();
+};
+
+// This is a list of all SVG attributes that need special casing, namespacing,
+// or boolean value assignment. Regular attributes that just accept strings
+// and have the same names are omitted, just like in the HTML whitelist.
+// Some of these attributes can be hard to find. This list was created by
+// scrapping the MDN documentation.
+['accent-height', 'alignment-baseline', 'arabic-form', 'baseline-shift', 'cap-height', 'clip-path', 'clip-rule', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'dominant-baseline', 'enable-background', 'fill-opacity', 'fill-rule', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-name', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'horiz-adv-x', 'horiz-origin-x', 'image-rendering', 'letter-spacing', 'lighting-color', 'marker-end', 'marker-mid', 'marker-start', 'overline-position', 'overline-thickness', 'paint-order', 'panose-1', 'pointer-events', 'rendering-intent', 'shape-rendering', 'stop-color', 'stop-opacity', 'strikethrough-position', 'strikethrough-thickness', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-decoration', 'text-rendering', 'underline-position', 'underline-thickness', 'unicode-bidi', 'unicode-range', 'units-per-em', 'v-alphabetic', 'v-hanging', 'v-ideographic', 'v-mathematical', 'vector-effect', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'word-spacing', 'writing-mode', 'xmlns:xlink', 'x-height'].forEach(function (attributeName) {
+ var name = attributeName.replace(CAMELIZE, capitalize);
+ properties[name] = new PropertyInfoRecord(name, STRING, false, // mustUseProperty
+ attributeName, null);
+} // attributeNamespace
+);
+
+// String SVG attributes with the xlink namespace.
+['xlink:actuate', 'xlink:arcrole', 'xlink:href', 'xlink:role', 'xlink:show', 'xlink:title', 'xlink:type'].forEach(function (attributeName) {
+ var name = attributeName.replace(CAMELIZE, capitalize);
+ properties[name] = new PropertyInfoRecord(name, STRING, false, // mustUseProperty
+ attributeName, 'http://www.w3.org/1999/xlink');
+});
+
+// String SVG attributes with the xml namespace.
+['xml:base', 'xml:lang', 'xml:space'].forEach(function (attributeName) {
+ var name = attributeName.replace(CAMELIZE, capitalize);
+ properties[name] = new PropertyInfoRecord(name, STRING, false, // mustUseProperty
+ attributeName, 'http://www.w3.org/XML/1998/namespace');
+});
+
+// Special case: this attribute exists both in HTML and SVG.
+// Its "tabindex" attribute name is case-sensitive in SVG so we can't just use
+// its React `tabIndex` name, like we do for attributes that exist only in HTML.
+properties.tabIndex = new PropertyInfoRecord('tabIndex', STRING, false, // mustUseProperty
+'tabindex', // attributeName
+null);
/**
- * Gets the target node from a native browser event by accounting for
- * inconsistencies in browser DOM APIs.
- *
- * @param {object} nativeEvent Native browser event.
- * @return {DOMEventTarget} Target node.
+ * Get the value for a property on a node. Only used in DEV for SSR validation.
+ * The "expected" argument is used as a hint of what the expected value is.
+ * Some properties have multiple equivalent values.
*/
-function getEventTarget(nativeEvent) {
- var target = nativeEvent.target || nativeEvent.srcElement || window;
+function getValueForProperty(node, name, expected, propertyInfo) {
+ {
+ if (propertyInfo.mustUseProperty) {
+ var propertyName = propertyInfo.propertyName;
- // Normalize SVG element events #4963
- if (target.correspondingUseElement) {
- target = target.correspondingUseElement;
- }
+ return node[propertyName];
+ } else {
+ var attributeName = propertyInfo.attributeName;
- // Safari may fire events on text nodes (Node.TEXT_NODE is 3).
- // @see http://www.quirksmode.org/js/events_properties.html
- return target.nodeType === TEXT_NODE ? target.parentNode : target;
+ var stringValue = null;
+
+ if (propertyInfo.type === OVERLOADED_BOOLEAN) {
+ if (node.hasAttribute(attributeName)) {
+ var value = node.getAttribute(attributeName);
+ if (value === '') {
+ return true;
+ }
+ if (shouldRemoveAttribute(name, expected, propertyInfo, false)) {
+ return value;
+ }
+ if (value === '' + expected) {
+ return expected;
+ }
+ return value;
+ }
+ } else if (node.hasAttribute(attributeName)) {
+ if (shouldRemoveAttribute(name, expected, propertyInfo, false)) {
+ // We had an attribute but shouldn't have had one, so read it
+ // for the error message.
+ return node.getAttribute(attributeName);
+ }
+ if (propertyInfo.type === BOOLEAN) {
+ // If this was a boolean, it doesn't matter what the value is
+ // the fact that we have it is the same as the expected.
+ return expected;
+ }
+ // Even if this property uses a namespace we use getAttribute
+ // because we assume its namespaced name is the same as our config.
+ // To use getAttributeNS we need the local name which we don't have
+ // in our config atm.
+ stringValue = node.getAttribute(attributeName);
+ }
+
+ if (shouldRemoveAttribute(name, expected, propertyInfo, false)) {
+ return stringValue === null ? expected : stringValue;
+ } else if (stringValue === '' + expected) {
+ return expected;
+ } else {
+ return stringValue;
+ }
+ }
+ }
}
-var useHasFeature;
-if (ExecutionEnvironment.canUseDOM) {
- useHasFeature = document.implementation && document.implementation.hasFeature &&
- // always returns true in newer browsers as per the standard.
- // @see http://dom.spec.whatwg.org/#dom-domimplementation-hasfeature
- document.implementation.hasFeature('', '') !== true;
+/**
+ * Get the value for a attribute on a node. Only used in DEV for SSR validation.
+ * The third argument is used as a hint of what the expected value is. Some
+ * attributes have multiple equivalent values.
+ */
+function getValueForAttribute(node, name, expected) {
+ {
+ if (!isAttributeNameSafe(name)) {
+ return;
+ }
+ if (!node.hasAttribute(name)) {
+ return expected === undefined ? undefined : null;
+ }
+ var value = node.getAttribute(name);
+ if (value === '' + expected) {
+ return expected;
+ }
+ return value;
+ }
}
/**
- * Checks if an event is supported in the current execution environment.
- *
- * NOTE: This will not work correctly for non-generic events such as `change`,
- * `reset`, `load`, `error`, and `select`.
- *
- * Borrows from Modernizr.
+ * Sets the value for a property on a node.
*
- * @param {string} eventNameSuffix Event name, e.g. "click".
- * @param {?boolean} capture Check if the capture phase is supported.
- * @return {boolean} True if the event is supported.
- * @internal
- * @license Modernizr 3.0.0pre (Custom Build) | MIT
+ * @param {DOMElement} node
+ * @param {string} name
+ * @param {*} value
*/
-function isEventSupported(eventNameSuffix, capture) {
- if (!ExecutionEnvironment.canUseDOM || capture && !('addEventListener' in document)) {
- return false;
+function setValueForProperty(node, name, value, isCustomComponentTag) {
+ var propertyInfo = getPropertyInfo(name);
+ if (shouldIgnoreAttribute(name, propertyInfo, isCustomComponentTag)) {
+ return;
+ }
+ if (shouldRemoveAttribute(name, value, propertyInfo, isCustomComponentTag)) {
+ value = null;
+ }
+ // If the prop isn't in the special list, treat it as a simple attribute.
+ if (isCustomComponentTag || propertyInfo === null) {
+ if (isAttributeNameSafe(name)) {
+ var _attributeName = name;
+ if (value === null) {
+ node.removeAttribute(_attributeName);
+ } else {
+ node.setAttribute(_attributeName, '' + value);
+ }
+ }
+ return;
}
+ var mustUseProperty = propertyInfo.mustUseProperty;
- var eventName = 'on' + eventNameSuffix;
- var isSupported = eventName in document;
+ if (mustUseProperty) {
+ var propertyName = propertyInfo.propertyName;
- if (!isSupported) {
- var element = document.createElement('div');
- element.setAttribute(eventName, 'return;');
- isSupported = typeof element[eventName] === 'function';
- }
+ if (value === null) {
+ var type = propertyInfo.type;
- if (!isSupported && useHasFeature && eventNameSuffix === 'wheel') {
- // This is the only way to test support for the `wheel` event in IE9+.
- isSupported = document.implementation.hasFeature('Events.wheel', '3.0');
+ node[propertyName] = type === BOOLEAN ? false : '';
+ } else {
+ // Contrary to `setAttribute`, object properties are properly
+ // `toString`ed by IE8/9.
+ node[propertyName] = value;
+ }
+ return;
}
+ // The rest are treated as attributes with special cases.
+ var attributeName = propertyInfo.attributeName,
+ attributeNamespace = propertyInfo.attributeNamespace;
- return isSupported;
-}
-
-function isCheckable(elem) {
- var type = elem.type;
- var nodeName = elem.nodeName;
- return nodeName && nodeName.toLowerCase() === 'input' && (type === 'checkbox' || type === 'radio');
-}
+ if (value === null) {
+ node.removeAttribute(attributeName);
+ } else {
+ var _type = propertyInfo.type;
-function getTracker(node) {
- return node._valueTracker;
+ var attributeValue = void 0;
+ if (_type === BOOLEAN || _type === OVERLOADED_BOOLEAN && value === true) {
+ attributeValue = '';
+ } else {
+ // `setAttribute` with objects becomes only `[object]` in IE8/9,
+ // ('' + value) makes it output the correct toString()-value.
+ attributeValue = '' + value;
+ }
+ if (attributeNamespace) {
+ node.setAttributeNS(attributeNamespace, attributeName, attributeValue);
+ } else {
+ node.setAttribute(attributeName, attributeValue);
+ }
+ }
}
-function detachTracker(node) {
- node._valueTracker = null;
+// Flow does not allow string concatenation of most non-string types. To work
+// around this limitation, we use an opaque type that can only be obtained by
+// passing the value through getToStringValue first.
+function toString(value) {
+ return '' + value;
}
-function getValueFromNode(node) {
- var value = '';
- if (!node) {
- return value;
- }
-
- if (isCheckable(node)) {
- value = node.checked ? 'true' : 'false';
- } else {
- value = node.value;
+function getToStringValue(value) {
+ switch (typeof value) {
+ case 'boolean':
+ case 'number':
+ case 'object':
+ case 'string':
+ case 'undefined':
+ return value;
+ default:
+ // function, symbol are assigned as empty strings
+ return '';
}
-
- return value;
}
-function trackValueOnNode(node) {
- var valueField = isCheckable(node) ? 'checked' : 'value';
- var descriptor = Object.getOwnPropertyDescriptor(node.constructor.prototype, valueField);
+var ReactDebugCurrentFrame$1 = null;
- var currentValue = '' + node[valueField];
+var ReactControlledValuePropTypes = {
+ checkPropTypes: null
+};
- // if someone has already defined a value or Safari, then bail
- // and don't track value will cause over reporting of changes,
- // but it's better then a hard failure
- // (needed for certain tests that spyOn input values and Safari)
- if (node.hasOwnProperty(valueField) || typeof descriptor.get !== 'function' || typeof descriptor.set !== 'function') {
- return;
- }
+{
+ ReactDebugCurrentFrame$1 = ReactSharedInternals.ReactDebugCurrentFrame;
- Object.defineProperty(node, valueField, {
- enumerable: descriptor.enumerable,
- configurable: true,
- get: function () {
- return descriptor.get.call(this);
- },
- set: function (value) {
- currentValue = '' + value;
- descriptor.set.call(this, value);
- }
- });
+ var hasReadOnlyValue = {
+ button: true,
+ checkbox: true,
+ image: true,
+ hidden: true,
+ radio: true,
+ reset: true,
+ submit: true
+ };
- var tracker = {
- getValue: function () {
- return currentValue;
- },
- setValue: function (value) {
- currentValue = '' + value;
+ var propTypes = {
+ value: function (props, propName, componentName) {
+ if (hasReadOnlyValue[props.type] || props.onChange || props.readOnly || props.disabled || props[propName] == null) {
+ return null;
+ }
+ return new Error('You provided a `value` prop to a form field without an ' + '`onChange` handler. This will render a read-only field. If ' + 'the field should be mutable use `defaultValue`. Otherwise, ' + 'set either `onChange` or `readOnly`.');
},
- stopTracking: function () {
- detachTracker(node);
- delete node[valueField];
+ checked: function (props, propName, componentName) {
+ if (props.onChange || props.readOnly || props.disabled || props[propName] == null) {
+ return null;
+ }
+ return new Error('You provided a `checked` prop to a form field without an ' + '`onChange` handler. This will render a read-only field. If ' + 'the field should be mutable use `defaultChecked`. Otherwise, ' + 'set either `onChange` or `readOnly`.');
}
};
- return tracker;
+
+ /**
+ * Provide a linked `value` attribute for controlled forms. You should not use
+ * this outside of the ReactDOM controlled form components.
+ */
+ ReactControlledValuePropTypes.checkPropTypes = function (tagName, props) {
+ checkPropTypes(propTypes, props, 'prop', tagName, ReactDebugCurrentFrame$1.getStackAddendum);
+ };
}
-function track(node) {
- if (getTracker(node)) {
- return;
- }
+var enableUserTimingAPI = true;
- // TODO: Once it's just Fiber we can move this to node._wrapperState
- node._valueTracker = trackValueOnNode(node);
-}
+var enableHooks = false;
+// Helps identify side effects in begin-phase lifecycle hooks and setState reducers:
+var debugRenderPhaseSideEffects = false;
-function updateValueIfChanged(node) {
- if (!node) {
- return false;
- }
+// In some cases, StrictMode should also double-render lifecycles.
+// This can be confusing for tests though,
+// And it can be bad for performance in production.
+// This feature flag can be used to control the behavior:
+var debugRenderPhaseSideEffectsForStrictMode = true;
- var tracker = getTracker(node);
- // if there is no tracker at this point it's unlikely
- // that trying again will succeed
- if (!tracker) {
- return true;
- }
+// To preserve the "Pause on caught exceptions" behavior of the debugger, we
+// replay the begin phase of a failed component inside invokeGuardedCallback.
+var replayFailedUnitOfWorkWithInvokeGuardedCallback = true;
- var lastValue = tracker.getValue();
- var nextValue = getValueFromNode(node);
- if (nextValue !== lastValue) {
- tracker.setValue(nextValue);
- return true;
- }
- return false;
-}
+// Warn about deprecated, async-unsafe lifecycles; relates to RFC #6:
+var warnAboutDeprecatedLifecycles = false;
-var eventTypes$1 = {
- change: {
- phasedRegistrationNames: {
- bubbled: 'onChange',
- captured: 'onChangeCapture'
- },
- dependencies: ['topBlur', 'topChange', 'topClick', 'topFocus', 'topInput', 'topKeyDown', 'topKeyUp', 'topSelectionChange']
- }
-};
+// Gather advanced timing metrics for Profiler subtrees.
+var enableProfilerTimer = true;
-function createAndAccumulateChangeEvent(inst, nativeEvent, target) {
- var event = SyntheticEvent$1.getPooled(eventTypes$1.change, inst, nativeEvent, target);
- event.type = 'change';
- // Flag this event loop as needing state restore.
- enqueueStateRestore(target);
- accumulateTwoPhaseDispatches(event);
- return event;
+// Trace which interactions trigger each commit.
+var enableSchedulerTracing = true;
+
+// Only used in www builds.
+
+
+// Only used in www builds.
+
+
+// React Fire: prevent the value and checked attributes from syncing
+// with their related DOM properties
+var disableInputAttributeSyncing = false;
+
+// These APIs will no longer be "unstable" in the upcoming 16.7 release,
+// Control this behavior with a flag to support 16.6 minor releases in the meanwhile.
+var enableStableConcurrentModeAPIs = false;
+
+// TODO: direct imports like some-package/src/* are bad. Fix me.
+var didWarnValueDefaultValue = false;
+var didWarnCheckedDefaultChecked = false;
+var didWarnControlledToUncontrolled = false;
+var didWarnUncontrolledToControlled = false;
+
+function isControlled(props) {
+ var usesChecked = props.type === 'checkbox' || props.type === 'radio';
+ return usesChecked ? props.checked != null : props.value != null;
}
-/**
- * For IE shims
- */
-var activeElement = null;
-var activeElementInst = null;
/**
- * SECTION: handle `change` event
+ * Implements an host component that allows setting these optional
+ * props: `checked`, `value`, `defaultChecked`, and `defaultValue`.
+ *
+ * If `checked` or `value` are not supplied (or null/undefined), user actions
+ * that affect the checked state or value will trigger updates to the element.
+ *
+ * If they are supplied (and not null/undefined), the rendered element will not
+ * trigger updates to the element. Instead, the props must change in order for
+ * the rendered element to be updated.
+ *
+ * The rendered element will be initialized as unchecked (or `defaultChecked`)
+ * with an empty value (or `defaultValue`).
+ *
+ * See http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html
*/
-function shouldUseChangeEvent(elem) {
- var nodeName = elem.nodeName && elem.nodeName.toLowerCase();
- return nodeName === 'select' || nodeName === 'input' && elem.type === 'file';
-}
-function manualDispatchChangeEvent(nativeEvent) {
- var event = createAndAccumulateChangeEvent(activeElementInst, nativeEvent, getEventTarget(nativeEvent));
+function getHostProps(element, props) {
+ var node = element;
+ var checked = props.checked;
- // If change and propertychange bubbled, we'd just bind to it like all the
- // other events and have it go through ReactBrowserEventEmitter. Since it
- // doesn't, we manually listen for the events and so we have to enqueue and
- // process the abstract event manually.
- //
- // Batching is necessary here in order to ensure that all event handlers run
- // before the next rerender (including event handlers attached to ancestor
- // elements instead of directly on the input). Without this, controlled
- // components don't work properly in conjunction with event bubbling because
- // the component is rerendered and the value reverted before all the event
- // handlers can run. See https://github.com/facebook/react/issues/708.
- batchedUpdates(runEventInBatch, event);
-}
+ var hostProps = _assign({}, props, {
+ defaultChecked: undefined,
+ defaultValue: undefined,
+ value: undefined,
+ checked: checked != null ? checked : node._wrapperState.initialChecked
+ });
-function runEventInBatch(event) {
- enqueueEvents(event);
- processEventQueue(false);
+ return hostProps;
}
-function getInstIfValueChanged(targetInst) {
- var targetNode = getNodeFromInstance$1(targetInst);
- if (updateValueIfChanged(targetNode)) {
- return targetInst;
- }
-}
+function initWrapperState(element, props) {
+ {
+ ReactControlledValuePropTypes.checkPropTypes('input', props);
-function getTargetInstForChangeEvent(topLevelType, targetInst) {
- if (topLevelType === 'topChange') {
- return targetInst;
+ if (props.checked !== undefined && props.defaultChecked !== undefined && !didWarnCheckedDefaultChecked) {
+ warning$1(false, '%s contains an input of type %s with both checked and defaultChecked props. ' + 'Input elements must be either controlled or uncontrolled ' + '(specify either the checked prop, or the defaultChecked prop, but not ' + 'both). Decide between using a controlled or uncontrolled input ' + 'element and remove one of these props. More info: ' + 'https://fb.me/react-controlled-components', getCurrentFiberOwnerNameInDevOrNull() || 'A component', props.type);
+ didWarnCheckedDefaultChecked = true;
+ }
+ if (props.value !== undefined && props.defaultValue !== undefined && !didWarnValueDefaultValue) {
+ warning$1(false, '%s contains an input of type %s with both value and defaultValue props. ' + 'Input elements must be either controlled or uncontrolled ' + '(specify either the value prop, or the defaultValue prop, but not ' + 'both). Decide between using a controlled or uncontrolled input ' + 'element and remove one of these props. More info: ' + 'https://fb.me/react-controlled-components', getCurrentFiberOwnerNameInDevOrNull() || 'A component', props.type);
+ didWarnValueDefaultValue = true;
+ }
}
-}
-/**
- * SECTION: handle `input` event
- */
-var isInputEventSupported = false;
-if (ExecutionEnvironment.canUseDOM) {
- // IE9 claims to support the input event but fails to trigger it when
- // deleting text, so we ignore its input events.
- isInputEventSupported = isEventSupported('input') && (!document.documentMode || document.documentMode > 9);
-}
+ var node = element;
+ var defaultValue = props.defaultValue == null ? '' : props.defaultValue;
-/**
- * (For IE <=9) Starts tracking propertychange events on the passed-in element
- * and override the value property so that we can distinguish user events from
- * value changes in JS.
- */
-function startWatchingForValueChange(target, targetInst) {
- activeElement = target;
- activeElementInst = targetInst;
- activeElement.attachEvent('onpropertychange', handlePropertyChange);
+ node._wrapperState = {
+ initialChecked: props.checked != null ? props.checked : props.defaultChecked,
+ initialValue: getToStringValue(props.value != null ? props.value : defaultValue),
+ controlled: isControlled(props)
+ };
}
-/**
- * (For IE <=9) Removes the event listeners from the currently-tracked element,
- * if any exists.
- */
-function stopWatchingForValueChange() {
- if (!activeElement) {
- return;
+function updateChecked(element, props) {
+ var node = element;
+ var checked = props.checked;
+ if (checked != null) {
+ setValueForProperty(node, 'checked', checked, false);
}
- activeElement.detachEvent('onpropertychange', handlePropertyChange);
- activeElement = null;
- activeElementInst = null;
}
-/**
- * (For IE <=9) Handles a propertychange event, sending a `change` event if
- * the value of the active element has changed.
- */
-function handlePropertyChange(nativeEvent) {
- if (nativeEvent.propertyName !== 'value') {
+function updateWrapper(element, props) {
+ var node = element;
+ {
+ var _controlled = isControlled(props);
+
+ if (!node._wrapperState.controlled && _controlled && !didWarnUncontrolledToControlled) {
+ warning$1(false, 'A component is changing an uncontrolled input of type %s to be controlled. ' + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', props.type);
+ didWarnUncontrolledToControlled = true;
+ }
+ if (node._wrapperState.controlled && !_controlled && !didWarnControlledToUncontrolled) {
+ warning$1(false, 'A component is changing a controlled input of type %s to be uncontrolled. ' + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', props.type);
+ didWarnControlledToUncontrolled = true;
+ }
+ }
+
+ updateChecked(element, props);
+
+ var value = getToStringValue(props.value);
+ var type = props.type;
+
+ if (value != null) {
+ if (type === 'number') {
+ if (value === 0 && node.value === '' ||
+ // We explicitly want to coerce to number here if possible.
+ // eslint-disable-next-line
+ node.value != value) {
+ node.value = toString(value);
+ }
+ } else if (node.value !== toString(value)) {
+ node.value = toString(value);
+ }
+ } else if (type === 'submit' || type === 'reset') {
+ // Submit/reset inputs need the attribute removed completely to avoid
+ // blank-text buttons.
+ node.removeAttribute('value');
return;
}
- if (getInstIfValueChanged(activeElementInst)) {
- manualDispatchChangeEvent(nativeEvent);
+
+ if (disableInputAttributeSyncing) {
+ // When not syncing the value attribute, React only assigns a new value
+ // whenever the defaultValue React prop has changed. When not present,
+ // React does nothing
+ if (props.hasOwnProperty('defaultValue')) {
+ setDefaultValue(node, props.type, getToStringValue(props.defaultValue));
+ }
+ } else {
+ // When syncing the value attribute, the value comes from a cascade of
+ // properties:
+ // 1. The value React property
+ // 2. The defaultValue React property
+ // 3. Otherwise there should be no change
+ if (props.hasOwnProperty('value')) {
+ setDefaultValue(node, props.type, value);
+ } else if (props.hasOwnProperty('defaultValue')) {
+ setDefaultValue(node, props.type, getToStringValue(props.defaultValue));
+ }
+ }
+
+ if (disableInputAttributeSyncing) {
+ // When not syncing the checked attribute, the attribute is directly
+ // controllable from the defaultValue React property. It needs to be
+ // updated as new props come in.
+ if (props.defaultChecked == null) {
+ node.removeAttribute('checked');
+ } else {
+ node.defaultChecked = !!props.defaultChecked;
+ }
+ } else {
+ // When syncing the checked attribute, it only changes when it needs
+ // to be removed, such as transitioning from a checkbox into a text input
+ if (props.checked == null && props.defaultChecked != null) {
+ node.defaultChecked = !!props.defaultChecked;
+ }
}
}
-function handleEventsForInputEventPolyfill(topLevelType, target, targetInst) {
- if (topLevelType === 'topFocus') {
- // In IE9, propertychange fires for most input events but is buggy and
- // doesn't fire when text is deleted, but conveniently, selectionchange
- // appears to fire in all of the remaining cases so we catch those and
- // forward the event if the value has changed
- // In either case, we don't want to call the event handler if the value
- // is changed from JS so we redefine a setter for `.value` that updates
- // our activeElementValue variable, allowing us to ignore those changes
- //
- // stopWatching() should be a noop here but we call it just in case we
- // missed a blur event somehow.
- stopWatchingForValueChange();
- startWatchingForValueChange(target, targetInst);
- } else if (topLevelType === 'topBlur') {
- stopWatchingForValueChange();
+function postMountWrapper(element, props, isHydrating) {
+ var node = element;
+
+ // Do not assign value if it is already set. This prevents user text input
+ // from being lost during SSR hydration.
+ if (props.hasOwnProperty('value') || props.hasOwnProperty('defaultValue')) {
+ var type = props.type;
+ var isButton = type === 'submit' || type === 'reset';
+
+ // Avoid setting value attribute on submit/reset inputs as it overrides the
+ // default value provided by the browser. See: #12872
+ if (isButton && (props.value === undefined || props.value === null)) {
+ return;
+ }
+
+ var _initialValue = toString(node._wrapperState.initialValue);
+
+ // Do not assign value if it is already set. This prevents user text input
+ // from being lost during SSR hydration.
+ if (!isHydrating) {
+ if (disableInputAttributeSyncing) {
+ var value = getToStringValue(props.value);
+
+ // When not syncing the value attribute, the value property points
+ // directly to the React prop. Only assign it if it exists.
+ if (value != null) {
+ // Always assign on buttons so that it is possible to assign an
+ // empty string to clear button text.
+ //
+ // Otherwise, do not re-assign the value property if is empty. This
+ // potentially avoids a DOM write and prevents Firefox (~60.0.1) from
+ // prematurely marking required inputs as invalid. Equality is compared
+ // to the current value in case the browser provided value is not an
+ // empty string.
+ if (isButton || value !== node.value) {
+ node.value = toString(value);
+ }
+ }
+ } else {
+ // When syncing the value attribute, the value property should use
+ // the wrapperState._initialValue property. This uses:
+ //
+ // 1. The value React property when present
+ // 2. The defaultValue React property when present
+ // 3. An empty string
+ if (_initialValue !== node.value) {
+ node.value = _initialValue;
+ }
+ }
+ }
+
+ if (disableInputAttributeSyncing) {
+ // When not syncing the value attribute, assign the value attribute
+ // directly from the defaultValue React property (when present)
+ var defaultValue = getToStringValue(props.defaultValue);
+ if (defaultValue != null) {
+ node.defaultValue = toString(defaultValue);
+ }
+ } else {
+ // Otherwise, the value attribute is synchronized to the property,
+ // so we assign defaultValue to the same thing as the value property
+ // assignment step above.
+ node.defaultValue = _initialValue;
+ }
+ }
+
+ // Normally, we'd just do `node.checked = node.checked` upon initial mount, less this bug
+ // this is needed to work around a chrome bug where setting defaultChecked
+ // will sometimes influence the value of checked (even after detachment).
+ // Reference: https://bugs.chromium.org/p/chromium/issues/detail?id=608416
+ // We need to temporarily unset name to avoid disrupting radio button groups.
+ var name = node.name;
+ if (name !== '') {
+ node.name = '';
+ }
+
+ if (disableInputAttributeSyncing) {
+ // When not syncing the checked attribute, the checked property
+ // never gets assigned. It must be manually set. We don't want
+ // to do this when hydrating so that existing user input isn't
+ // modified
+ if (!isHydrating) {
+ updateChecked(element, props);
+ }
+
+ // Only assign the checked attribute if it is defined. This saves
+ // a DOM write when controlling the checked attribute isn't needed
+ // (text inputs, submit/reset)
+ if (props.hasOwnProperty('defaultChecked')) {
+ node.defaultChecked = !node.defaultChecked;
+ node.defaultChecked = !!props.defaultChecked;
+ }
+ } else {
+ // When syncing the checked attribute, both the checked property and
+ // attribute are assigned at the same time using defaultChecked. This uses:
+ //
+ // 1. The checked React property when present
+ // 2. The defaultChecked React property when present
+ // 3. Otherwise, false
+ node.defaultChecked = !node.defaultChecked;
+ node.defaultChecked = !!node._wrapperState.initialChecked;
+ }
+
+ if (name !== '') {
+ node.name = name;
+ }
+}
+
+function restoreControlledState(element, props) {
+ var node = element;
+ updateWrapper(node, props);
+ updateNamedCousins(node, props);
+}
+
+function updateNamedCousins(rootNode, props) {
+ var name = props.name;
+ if (props.type === 'radio' && name != null) {
+ var queryRoot = rootNode;
+
+ while (queryRoot.parentNode) {
+ queryRoot = queryRoot.parentNode;
+ }
+
+ // If `rootNode.form` was non-null, then we could try `form.elements`,
+ // but that sometimes behaves strangely in IE8. We could also try using
+ // `form.getElementsByName`, but that will only return direct children
+ // and won't include inputs that use the HTML5 `form=` attribute. Since
+ // the input might not even be in a form. It might not even be in the
+ // document. Let's just use the local `querySelectorAll` to ensure we don't
+ // miss anything.
+ var group = queryRoot.querySelectorAll('input[name=' + JSON.stringify('' + name) + '][type="radio"]');
+
+ for (var i = 0; i < group.length; i++) {
+ var otherNode = group[i];
+ if (otherNode === rootNode || otherNode.form !== rootNode.form) {
+ continue;
+ }
+ // This will throw if radio buttons rendered by different copies of React
+ // and the same name are rendered into the same form (same as #1939).
+ // That's probably okay; we don't support it just as we don't support
+ // mixing React radio buttons with non-React ones.
+ var otherProps = getFiberCurrentPropsFromNode$1(otherNode);
+ !otherProps ? invariant(false, 'ReactDOMInput: Mixing React and non-React radio inputs with the same `name` is not supported.') : void 0;
+
+ // We need update the tracked value on the named cousin since the value
+ // was changed but the input saw no event or value set
+ updateValueIfChanged(otherNode);
+
+ // If this is a controlled radio button group, forcing the input that
+ // was previously checked to update will cause it to be come re-checked
+ // as appropriate.
+ updateWrapper(otherNode, otherProps);
+ }
+ }
+}
+
+// In Chrome, assigning defaultValue to certain input types triggers input validation.
+// For number inputs, the display value loses trailing decimal points. For email inputs,
+// Chrome raises "The specified value is not a valid email address".
+//
+// Here we check to see if the defaultValue has actually changed, avoiding these problems
+// when the user is inputting text
+//
+// https://github.com/facebook/react/issues/7253
+function setDefaultValue(node, type, value) {
+ if (
+ // Focused number inputs synchronize on blur. See ChangeEventPlugin.js
+ type !== 'number' || node.ownerDocument.activeElement !== node) {
+ if (value == null) {
+ node.defaultValue = toString(node._wrapperState.initialValue);
+ } else if (node.defaultValue !== toString(value)) {
+ node.defaultValue = toString(value);
+ }
+ }
+}
+
+var eventTypes$1 = {
+ change: {
+ phasedRegistrationNames: {
+ bubbled: 'onChange',
+ captured: 'onChangeCapture'
+ },
+ dependencies: [TOP_BLUR, TOP_CHANGE, TOP_CLICK, TOP_FOCUS, TOP_INPUT, TOP_KEY_DOWN, TOP_KEY_UP, TOP_SELECTION_CHANGE]
+ }
+};
+
+function createAndAccumulateChangeEvent(inst, nativeEvent, target) {
+ var event = SyntheticEvent.getPooled(eventTypes$1.change, inst, nativeEvent, target);
+ event.type = 'change';
+ // Flag this event loop as needing state restore.
+ enqueueStateRestore(target);
+ accumulateTwoPhaseDispatches(event);
+ return event;
+}
+/**
+ * For IE shims
+ */
+var activeElement = null;
+var activeElementInst = null;
+
+/**
+ * SECTION: handle `change` event
+ */
+function shouldUseChangeEvent(elem) {
+ var nodeName = elem.nodeName && elem.nodeName.toLowerCase();
+ return nodeName === 'select' || nodeName === 'input' && elem.type === 'file';
+}
+
+function manualDispatchChangeEvent(nativeEvent) {
+ var event = createAndAccumulateChangeEvent(activeElementInst, nativeEvent, getEventTarget(nativeEvent));
+
+ // If change and propertychange bubbled, we'd just bind to it like all the
+ // other events and have it go through ReactBrowserEventEmitter. Since it
+ // doesn't, we manually listen for the events and so we have to enqueue and
+ // process the abstract event manually.
+ //
+ // Batching is necessary here in order to ensure that all event handlers run
+ // before the next rerender (including event handlers attached to ancestor
+ // elements instead of directly on the input). Without this, controlled
+ // components don't work properly in conjunction with event bubbling because
+ // the component is rerendered and the value reverted before all the event
+ // handlers can run. See https://github.com/facebook/react/issues/708.
+ batchedUpdates(runEventInBatch, event);
+}
+
+function runEventInBatch(event) {
+ runEventsInBatch(event);
+}
+
+function getInstIfValueChanged(targetInst) {
+ var targetNode = getNodeFromInstance$1(targetInst);
+ if (updateValueIfChanged(targetNode)) {
+ return targetInst;
+ }
+}
+
+function getTargetInstForChangeEvent(topLevelType, targetInst) {
+ if (topLevelType === TOP_CHANGE) {
+ return targetInst;
+ }
+}
+
+/**
+ * SECTION: handle `input` event
+ */
+var isInputEventSupported = false;
+if (canUseDOM) {
+ // IE9 claims to support the input event but fails to trigger it when
+ // deleting text, so we ignore its input events.
+ isInputEventSupported = isEventSupported('input') && (!document.documentMode || document.documentMode > 9);
+}
+
+/**
+ * (For IE <=9) Starts tracking propertychange events on the passed-in element
+ * and override the value property so that we can distinguish user events from
+ * value changes in JS.
+ */
+function startWatchingForValueChange(target, targetInst) {
+ activeElement = target;
+ activeElementInst = targetInst;
+ activeElement.attachEvent('onpropertychange', handlePropertyChange);
+}
+
+/**
+ * (For IE <=9) Removes the event listeners from the currently-tracked element,
+ * if any exists.
+ */
+function stopWatchingForValueChange() {
+ if (!activeElement) {
+ return;
+ }
+ activeElement.detachEvent('onpropertychange', handlePropertyChange);
+ activeElement = null;
+ activeElementInst = null;
+}
+
+/**
+ * (For IE <=9) Handles a propertychange event, sending a `change` event if
+ * the value of the active element has changed.
+ */
+function handlePropertyChange(nativeEvent) {
+ if (nativeEvent.propertyName !== 'value') {
+ return;
+ }
+ if (getInstIfValueChanged(activeElementInst)) {
+ manualDispatchChangeEvent(nativeEvent);
+ }
+}
+
+function handleEventsForInputEventPolyfill(topLevelType, target, targetInst) {
+ if (topLevelType === TOP_FOCUS) {
+ // In IE9, propertychange fires for most input events but is buggy and
+ // doesn't fire when text is deleted, but conveniently, selectionchange
+ // appears to fire in all of the remaining cases so we catch those and
+ // forward the event if the value has changed
+ // In either case, we don't want to call the event handler if the value
+ // is changed from JS so we redefine a setter for `.value` that updates
+ // our activeElementValue variable, allowing us to ignore those changes
+ //
+ // stopWatching() should be a noop here but we call it just in case we
+ // missed a blur event somehow.
+ stopWatchingForValueChange();
+ startWatchingForValueChange(target, targetInst);
+ } else if (topLevelType === TOP_BLUR) {
+ stopWatchingForValueChange();
}
}
// For IE8 and IE9.
function getTargetInstForInputEventPolyfill(topLevelType, targetInst) {
- if (topLevelType === 'topSelectionChange' || topLevelType === 'topKeyUp' || topLevelType === 'topKeyDown') {
+ if (topLevelType === TOP_SELECTION_CHANGE || topLevelType === TOP_KEY_UP || topLevelType === TOP_KEY_DOWN) {
// On the selectionchange event, the target is just document which isn't
// helpful for us so just check activeElement instead.
//
@@ -3927,34 +4253,27 @@ function shouldUseClickEvent(elem) {
}
function getTargetInstForClickEvent(topLevelType, targetInst) {
- if (topLevelType === 'topClick') {
+ if (topLevelType === TOP_CLICK) {
return getInstIfValueChanged(targetInst);
}
}
function getTargetInstForInputOrChangeEvent(topLevelType, targetInst) {
- if (topLevelType === 'topInput' || topLevelType === 'topChange') {
+ if (topLevelType === TOP_INPUT || topLevelType === TOP_CHANGE) {
return getInstIfValueChanged(targetInst);
}
}
-function handleControlledInputBlur(inst, node) {
- // TODO: In IE, inst is occasionally null. Why?
- if (inst == null) {
- return;
- }
-
- // Fiber and ReactDOM keep wrapper state in separate places
- var state = inst._wrapperState || node._wrapperState;
+function handleControlledInputBlur(node) {
+ var state = node._wrapperState;
if (!state || !state.controlled || node.type !== 'number') {
return;
}
- // If controlled, assign the value attribute to the current value on blur
- var value = '' + node.value;
- if (node.getAttribute('value') !== value) {
- node.setAttribute('value', value);
+ if (!disableInputAttributeSyncing) {
+ // If controlled, assign the value attribute to the current value on blur
+ setDefaultValue(node, 'number', node.value);
}
}
@@ -3976,7 +4295,8 @@ var ChangeEventPlugin = {
extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
var targetNode = targetInst ? getNodeFromInstance$1(targetInst) : window;
- var getTargetInstFunc, handleEventFunc;
+ var getTargetInstFunc = void 0,
+ handleEventFunc = void 0;
if (shouldUseChangeEvent(targetNode)) {
getTargetInstFunc = getTargetInstForChangeEvent;
} else if (isTextInputElement(targetNode)) {
@@ -4003,8 +4323,8 @@ var ChangeEventPlugin = {
}
// When blurring, set the value attribute for number inputs
- if (topLevelType === 'topBlur') {
- handleControlledInputBlur(targetInst, targetNode);
+ if (topLevelType === TOP_BLUR) {
+ handleControlledInputBlur(targetNode);
}
}
};
@@ -4018,33 +4338,12 @@ var ChangeEventPlugin = {
* `ResponderEventPlugin` must occur before `SimpleEventPlugin` so that
* preventing default on events is convenient in `SimpleEventPlugin` handlers.
*/
-var DOMEventPluginOrder = ['ResponderEventPlugin', 'SimpleEventPlugin', 'TapEventPlugin', 'EnterLeaveEventPlugin', 'ChangeEventPlugin', 'SelectEventPlugin', 'BeforeInputEventPlugin'];
+var DOMEventPluginOrder = ['ResponderEventPlugin', 'SimpleEventPlugin', 'EnterLeaveEventPlugin', 'ChangeEventPlugin', 'SelectEventPlugin', 'BeforeInputEventPlugin'];
-/**
- * @interface UIEvent
- * @see http://www.w3.org/TR/DOM-Level-3-Events/
- */
-var UIEventInterface = {
+var SyntheticUIEvent = SyntheticEvent.extend({
view: null,
detail: null
-};
-
-/**
- * @param {object} dispatchConfig Configuration used to dispatch this event.
- * @param {string} dispatchMarker Marker identifying the event target.
- * @param {object} nativeEvent Native browser event.
- * @extends {SyntheticEvent}
- */
-function SyntheticUIEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
- return SyntheticEvent$1.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
-}
-
-SyntheticEvent$1.augmentClass(SyntheticUIEvent, UIEventInterface);
-
-/**
- * Translation from modifier key to the associated property in the event.
- * @see http://www.w3.org/TR/DOM-Level-3-Events/#keys-Modifiers
- */
+});
var modifierKeyToProp = {
Alt: 'altKey',
@@ -4053,9 +4352,14 @@ var modifierKeyToProp = {
Shift: 'shiftKey'
};
-// IE8 does not implement getModifierState so we simply map it to the only
-// modifier keys exposed by the event itself, does not support Lock-keys.
-// Currently, all major browsers except Chrome seems to support Lock-keys.
+// Older browsers (Safari <= 10, iOS Safari <= 10.2) do not support
+// getModifierState. If getModifierState is not supported, we map it to a set of
+// modifier keys exposed by the event. In this case, Lock-keys are not supported.
+/**
+ * Translation from modifier key to the associated property in the event.
+ * @see http://www.w3.org/TR/DOM-Level-3-Events/#keys-Modifiers
+ */
+
function modifierStateGetter(keyArg) {
var syntheticEvent = this;
var nativeEvent = syntheticEvent.nativeEvent;
@@ -4070,11 +4374,17 @@ function getEventModifierState(nativeEvent) {
return modifierStateGetter;
}
+var previousScreenX = 0;
+var previousScreenY = 0;
+// Use flags to signal movementX/Y has already been set
+var isMovementXSet = false;
+var isMovementYSet = false;
+
/**
* @interface MouseEvent
* @see http://www.w3.org/TR/DOM-Level-3-Events/
*/
-var MouseEventInterface = {
+var SyntheticMouseEvent = SyntheticUIEvent.extend({
screenX: null,
screenY: null,
clientX: null,
@@ -4090,29 +4400,72 @@ var MouseEventInterface = {
buttons: null,
relatedTarget: function (event) {
return event.relatedTarget || (event.fromElement === event.srcElement ? event.toElement : event.fromElement);
- }
-};
+ },
+ movementX: function (event) {
+ if ('movementX' in event) {
+ return event.movementX;
+ }
-/**
- * @param {object} dispatchConfig Configuration used to dispatch this event.
- * @param {string} dispatchMarker Marker identifying the event target.
- * @param {object} nativeEvent Native browser event.
- * @extends {SyntheticUIEvent}
- */
-function SyntheticMouseEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
- return SyntheticUIEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
-}
+ var screenX = previousScreenX;
+ previousScreenX = event.screenX;
-SyntheticUIEvent.augmentClass(SyntheticMouseEvent, MouseEventInterface);
+ if (!isMovementXSet) {
+ isMovementXSet = true;
+ return 0;
+ }
+
+ return event.type === 'mousemove' ? event.screenX - screenX : 0;
+ },
+ movementY: function (event) {
+ if ('movementY' in event) {
+ return event.movementY;
+ }
+
+ var screenY = previousScreenY;
+ previousScreenY = event.screenY;
+
+ if (!isMovementYSet) {
+ isMovementYSet = true;
+ return 0;
+ }
+
+ return event.type === 'mousemove' ? event.screenY - screenY : 0;
+ }
+});
+
+/**
+ * @interface PointerEvent
+ * @see http://www.w3.org/TR/pointerevents/
+ */
+var SyntheticPointerEvent = SyntheticMouseEvent.extend({
+ pointerId: null,
+ width: null,
+ height: null,
+ pressure: null,
+ tangentialPressure: null,
+ tiltX: null,
+ tiltY: null,
+ twist: null,
+ pointerType: null,
+ isPrimary: null
+});
var eventTypes$2 = {
mouseEnter: {
registrationName: 'onMouseEnter',
- dependencies: ['topMouseOut', 'topMouseOver']
+ dependencies: [TOP_MOUSE_OUT, TOP_MOUSE_OVER]
},
mouseLeave: {
registrationName: 'onMouseLeave',
- dependencies: ['topMouseOut', 'topMouseOver']
+ dependencies: [TOP_MOUSE_OUT, TOP_MOUSE_OVER]
+ },
+ pointerEnter: {
+ registrationName: 'onPointerEnter',
+ dependencies: [TOP_POINTER_OUT, TOP_POINTER_OVER]
+ },
+ pointerLeave: {
+ registrationName: 'onPointerLeave',
+ dependencies: [TOP_POINTER_OUT, TOP_POINTER_OVER]
}
};
@@ -4127,15 +4480,19 @@ var EnterLeaveEventPlugin = {
* the `mouseover` top-level event.
*/
extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
- if (topLevelType === 'topMouseOver' && (nativeEvent.relatedTarget || nativeEvent.fromElement)) {
+ var isOverEvent = topLevelType === TOP_MOUSE_OVER || topLevelType === TOP_POINTER_OVER;
+ var isOutEvent = topLevelType === TOP_MOUSE_OUT || topLevelType === TOP_POINTER_OUT;
+
+ if (isOverEvent && (nativeEvent.relatedTarget || nativeEvent.fromElement)) {
return null;
}
- if (topLevelType !== 'topMouseOut' && topLevelType !== 'topMouseOver') {
- // Must not be a mouse in or mouse out - ignoring.
+
+ if (!isOutEvent && !isOverEvent) {
+ // Must not be a mouse or pointer in or out - ignoring.
return null;
}
- var win;
+ var win = void 0;
if (nativeEventTarget.window === nativeEventTarget) {
// `nativeEventTarget` is probably a window object.
win = nativeEventTarget;
@@ -4149,9 +4506,9 @@ var EnterLeaveEventPlugin = {
}
}
- var from;
- var to;
- if (topLevelType === 'topMouseOut') {
+ var from = void 0;
+ var to = void 0;
+ if (isOutEvent) {
from = targetInst;
var related = nativeEvent.relatedTarget || nativeEvent.toElement;
to = related ? getClosestInstanceFromNode(related) : null;
@@ -4166,16 +4523,33 @@ var EnterLeaveEventPlugin = {
return null;
}
+ var eventInterface = void 0,
+ leaveEventType = void 0,
+ enterEventType = void 0,
+ eventTypePrefix = void 0;
+
+ if (topLevelType === TOP_MOUSE_OUT || topLevelType === TOP_MOUSE_OVER) {
+ eventInterface = SyntheticMouseEvent;
+ leaveEventType = eventTypes$2.mouseLeave;
+ enterEventType = eventTypes$2.mouseEnter;
+ eventTypePrefix = 'mouse';
+ } else if (topLevelType === TOP_POINTER_OUT || topLevelType === TOP_POINTER_OVER) {
+ eventInterface = SyntheticPointerEvent;
+ leaveEventType = eventTypes$2.pointerLeave;
+ enterEventType = eventTypes$2.pointerEnter;
+ eventTypePrefix = 'pointer';
+ }
+
var fromNode = from == null ? win : getNodeFromInstance$1(from);
var toNode = to == null ? win : getNodeFromInstance$1(to);
- var leave = SyntheticMouseEvent.getPooled(eventTypes$2.mouseLeave, from, nativeEvent, nativeEventTarget);
- leave.type = 'mouseleave';
+ var leave = eventInterface.getPooled(leaveEventType, from, nativeEvent, nativeEventTarget);
+ leave.type = eventTypePrefix + 'leave';
leave.target = fromNode;
leave.relatedTarget = toNode;
- var enter = SyntheticMouseEvent.getPooled(eventTypes$2.mouseEnter, to, nativeEvent, nativeEventTarget);
- enter.type = 'mouseenter';
+ var enter = eventInterface.getPooled(enterEventType, to, nativeEvent, nativeEventTarget);
+ enter.type = eventTypePrefix + 'enter';
enter.target = toNode;
enter.relatedTarget = fromNode;
@@ -4185,6 +4559,58 @@ var EnterLeaveEventPlugin = {
}
};
+/*eslint-disable no-self-compare */
+
+var hasOwnProperty$1 = Object.prototype.hasOwnProperty;
+
+/**
+ * inlined Object.is polyfill to avoid requiring consumers ship their own
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
+ */
+function is(x, y) {
+ // SameValue algorithm
+ if (x === y) {
+ // Steps 1-5, 7-10
+ // Steps 6.b-6.e: +0 != -0
+ // Added the nonzero y check to make Flow happy, but it is redundant
+ return x !== 0 || y !== 0 || 1 / x === 1 / y;
+ } else {
+ // Step 6.a: NaN == NaN
+ return x !== x && y !== y;
+ }
+}
+
+/**
+ * Performs equality by iterating through keys on an object and returning false
+ * when any key has values which are not strictly equal between the arguments.
+ * Returns true when the values of all keys are strictly equal.
+ */
+function shallowEqual(objA, objB) {
+ if (is(objA, objB)) {
+ return true;
+ }
+
+ if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
+ return false;
+ }
+
+ var keysA = Object.keys(objA);
+ var keysB = Object.keys(objB);
+
+ if (keysA.length !== keysB.length) {
+ return false;
+ }
+
+ // Test for A's keys different from B.
+ for (var i = 0; i < keysA.length; i++) {
+ if (!hasOwnProperty$1.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
/**
* `ReactInstanceMap` maintains a mapping from a public facing stateful
* instance (key) and the internal representation (value). This allows public
@@ -4214,36 +4640,32 @@ function set(key, value) {
key._reactInternalFiber = value;
}
-var ReactInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
+// Don't change these two values. They're used by React Dev Tools.
+var NoEffect = /* */0;
+var PerformedWork = /* */1;
-var ReactCurrentOwner = ReactInternals.ReactCurrentOwner;
-var ReactDebugCurrentFrame = ReactInternals.ReactDebugCurrentFrame;
+// You can change the rest (and add more).
+var Placement = /* */2;
+var Update = /* */4;
+var PlacementAndUpdate = /* */6;
+var Deletion = /* */8;
+var ContentReset = /* */16;
+var Callback = /* */32;
+var DidCapture = /* */64;
+var Ref = /* */128;
+var Snapshot = /* */256;
+var Passive = /* */512;
-function getComponentName(fiber) {
- var type = fiber.type;
+// Passive & Update & Callback & Ref & Snapshot
+var LifecycleEffectMask = /* */932;
- if (typeof type === 'string') {
- return type;
- }
- if (typeof type === 'function') {
- return type.displayName || type.name;
- }
- return null;
-}
+// Union of all host effects
+var HostEffectMask = /* */1023;
-// Don't change these two values:
-var NoEffect = 0; // 0b00000000
-var PerformedWork = 1; // 0b00000001
+var Incomplete = /* */1024;
+var ShouldCapture = /* */2048;
-// You can change the rest (and add more).
-var Placement = 2; // 0b00000010
-var Update = 4; // 0b00000100
-var PlacementAndUpdate = 6; // 0b00000110
-var Deletion = 8; // 0b00001000
-var ContentReset = 16; // 0b00010000
-var Callback = 32; // 0b00100000
-var Err = 64; // 0b01000000
-var Ref = 128; // 0b10000000
+var ReactCurrentOwner$1 = ReactSharedInternals.ReactCurrentOwner;
var MOUNTING = 1;
var MOUNTED = 2;
@@ -4257,15 +4679,15 @@ function isFiberMountedImpl(fiber) {
if ((node.effectTag & Placement) !== NoEffect) {
return MOUNTING;
}
- while (node['return']) {
- node = node['return'];
+ while (node.return) {
+ node = node.return;
if ((node.effectTag & Placement) !== NoEffect) {
return MOUNTING;
}
}
} else {
- while (node['return']) {
- node = node['return'];
+ while (node.return) {
+ node = node.return;
}
}
if (node.tag === HostRoot) {
@@ -4284,11 +4706,11 @@ function isFiberMounted(fiber) {
function isMounted(component) {
{
- var owner = ReactCurrentOwner.current;
+ var owner = ReactCurrentOwner$1.current;
if (owner !== null && owner.tag === ClassComponent) {
var ownerFiber = owner;
var instance = ownerFiber.stateNode;
- warning(instance._warnedAboutRefsInRender, '%s is accessing isMounted inside its render() function. ' + 'render() should be a pure function of props and state. It should ' + 'never access something that requires stale data from the previous ' + 'render, such as refs. Move this logic to componentDidMount and ' + 'componentDidUpdate instead.', getComponentName(ownerFiber) || 'A component');
+ !instance._warnedAboutRefsInRender ? warningWithoutStack$1(false, '%s is accessing isMounted inside its render() function. ' + 'render() should be a pure function of props and state. It should ' + 'never access something that requires stale data from the previous ' + 'render, such as refs. Move this logic to componentDidMount and ' + 'componentDidUpdate instead.', getComponentName(ownerFiber.type) || 'A component') : void 0;
instance._warnedAboutRefsInRender = true;
}
}
@@ -4321,7 +4743,7 @@ function findCurrentFiberUsingSlowPath(fiber) {
var a = fiber;
var b = alternate;
while (true) {
- var parentA = a['return'];
+ var parentA = a.return;
var parentB = parentA ? parentA.alternate : null;
if (!parentA || !parentB) {
// We're at the root.
@@ -4351,7 +4773,7 @@ function findCurrentFiberUsingSlowPath(fiber) {
invariant(false, 'Unable to find node on an unmounted component.');
}
- if (a['return'] !== b['return']) {
+ if (a.return !== b.return) {
// The return pointer of A and the return pointer of B point to different
// fibers. We assume that return pointers never criss-cross, so A must
// belong to the child set of A.return, and B must belong to the child
@@ -4428,7 +4850,7 @@ function findCurrentHostFiber(parent) {
if (node.tag === HostComponent || node.tag === HostText) {
return node;
} else if (node.child) {
- node.child['return'] = node;
+ node.child.return = node;
node = node.child;
continue;
}
@@ -4436,12 +4858,12 @@ function findCurrentHostFiber(parent) {
return null;
}
while (!node.sibling) {
- if (!node['return'] || node['return'] === currentParent) {
+ if (!node.return || node.return === currentParent) {
return null;
}
- node = node['return'];
+ node = node.return;
}
- node.sibling['return'] = node['return'];
+ node.sibling.return = node.return;
node = node.sibling;
}
// Flow needs the return null here, but ESLint complains about it.
@@ -4461,7 +4883,7 @@ function findCurrentHostFiberWithNoPortals(parent) {
if (node.tag === HostComponent || node.tag === HostText) {
return node;
} else if (node.child && node.tag !== HostPortal) {
- node.child['return'] = node;
+ node.child.return = node;
node = node.child;
continue;
}
@@ -4469,12 +4891,12 @@ function findCurrentHostFiberWithNoPortals(parent) {
return null;
}
while (!node.sibling) {
- if (!node['return'] || node['return'] === currentParent) {
+ if (!node.return || node.return === currentParent) {
return null;
}
- node = node['return'];
+ node = node.return;
}
- node.sibling['return'] = node['return'];
+ node.sibling.return = node.return;
node = node.sibling;
}
// Flow needs the return null here, but ESLint complains about it.
@@ -4482,356 +4904,621 @@ function findCurrentHostFiberWithNoPortals(parent) {
return null;
}
-var CALLBACK_BOOKKEEPING_POOL_SIZE = 10;
-var callbackBookkeepingPool = [];
-
-/**
- * Find the deepest React component completely containing the root of the
- * passed-in instance (for use when entire React trees are nested within each
- * other). If React trees are not nested, returns null.
- */
-function findRootContainerNode(inst) {
- // TODO: It may be a good idea to cache this to prevent unnecessary DOM
- // traversal, but caching is difficult to do correctly without using a
- // mutation observer to listen for all DOM changes.
- while (inst['return']) {
- inst = inst['return'];
- }
- if (inst.tag !== HostRoot) {
- // This can happen if we're in a detached tree.
- return null;
- }
- return inst.stateNode.containerInfo;
-}
-
-// Used to store ancestor hierarchy in top level callback
-function getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst) {
- if (callbackBookkeepingPool.length) {
- var instance = callbackBookkeepingPool.pop();
- instance.topLevelType = topLevelType;
- instance.nativeEvent = nativeEvent;
- instance.targetInst = targetInst;
- return instance;
- }
- return {
- topLevelType: topLevelType,
- nativeEvent: nativeEvent,
- targetInst: targetInst,
- ancestors: []
- };
+function addEventBubbleListener(element, eventType, listener) {
+ element.addEventListener(eventType, listener, false);
}
-function releaseTopLevelCallbackBookKeeping(instance) {
- instance.topLevelType = null;
- instance.nativeEvent = null;
- instance.targetInst = null;
- instance.ancestors.length = 0;
- if (callbackBookkeepingPool.length < CALLBACK_BOOKKEEPING_POOL_SIZE) {
- callbackBookkeepingPool.push(instance);
- }
+function addEventCaptureListener(element, eventType, listener) {
+ element.addEventListener(eventType, listener, true);
}
-function handleTopLevelImpl(bookKeeping) {
- var targetInst = bookKeeping.targetInst;
-
- // Loop through the hierarchy, in case there's any nested components.
- // It's important that we build the array of ancestors before calling any
- // event handlers, because event handlers can modify the DOM, leading to
- // inconsistencies with ReactMount's node cache. See #1105.
- var ancestor = targetInst;
- do {
- if (!ancestor) {
- bookKeeping.ancestors.push(ancestor);
- break;
- }
- var root = findRootContainerNode(ancestor);
- if (!root) {
- break;
- }
- bookKeeping.ancestors.push(ancestor);
- ancestor = getClosestInstanceFromNode(root);
- } while (ancestor);
+/**
+ * @interface Event
+ * @see http://www.w3.org/TR/css3-animations/#AnimationEvent-interface
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/AnimationEvent
+ */
+var SyntheticAnimationEvent = SyntheticEvent.extend({
+ animationName: null,
+ elapsedTime: null,
+ pseudoElement: null
+});
- for (var i = 0; i < bookKeeping.ancestors.length; i++) {
- targetInst = bookKeeping.ancestors[i];
- _handleTopLevel(bookKeeping.topLevelType, targetInst, bookKeeping.nativeEvent, getEventTarget(bookKeeping.nativeEvent));
+/**
+ * @interface Event
+ * @see http://www.w3.org/TR/clipboard-apis/
+ */
+var SyntheticClipboardEvent = SyntheticEvent.extend({
+ clipboardData: function (event) {
+ return 'clipboardData' in event ? event.clipboardData : window.clipboardData;
}
-}
-
-// TODO: can we stop exporting these?
-var _enabled = true;
-var _handleTopLevel = void 0;
-
-function setHandleTopLevel(handleTopLevel) {
- _handleTopLevel = handleTopLevel;
-}
-
-function setEnabled(enabled) {
- _enabled = !!enabled;
-}
-
-function isEnabled() {
- return _enabled;
-}
+});
/**
- * Traps top-level events by using event bubbling.
- *
- * @param {string} topLevelType Record from `BrowserEventConstants`.
- * @param {string} handlerBaseName Event name (e.g. "click").
- * @param {object} element Element on which to attach listener.
- * @return {?object} An object with a remove function which will forcefully
- * remove the listener.
- * @internal
+ * @interface FocusEvent
+ * @see http://www.w3.org/TR/DOM-Level-3-Events/
*/
-function trapBubbledEvent(topLevelType, handlerBaseName, element) {
- if (!element) {
- return null;
- }
- return EventListener.listen(element, handlerBaseName, dispatchEvent.bind(null, topLevelType));
-}
+var SyntheticFocusEvent = SyntheticUIEvent.extend({
+ relatedTarget: null
+});
/**
- * Traps a top-level event by using event capturing.
+ * `charCode` represents the actual "character code" and is safe to use with
+ * `String.fromCharCode`. As such, only keys that correspond to printable
+ * characters produce a valid `charCode`, the only exception to this is Enter.
+ * The Tab-key is considered non-printable and does not have a `charCode`,
+ * presumably because it does not produce a tab-character in browsers.
*
- * @param {string} topLevelType Record from `BrowserEventConstants`.
- * @param {string} handlerBaseName Event name (e.g. "click").
- * @param {object} element Element on which to attach listener.
- * @return {?object} An object with a remove function which will forcefully
- * remove the listener.
- * @internal
+ * @param {object} nativeEvent Native browser event.
+ * @return {number} Normalized `charCode` property.
*/
-function trapCapturedEvent(topLevelType, handlerBaseName, element) {
- if (!element) {
- return null;
- }
- return EventListener.capture(element, handlerBaseName, dispatchEvent.bind(null, topLevelType));
-}
+function getEventCharCode(nativeEvent) {
+ var charCode = void 0;
+ var keyCode = nativeEvent.keyCode;
-function dispatchEvent(topLevelType, nativeEvent) {
- if (!_enabled) {
- return;
- }
+ if ('charCode' in nativeEvent) {
+ charCode = nativeEvent.charCode;
- var nativeEventTarget = getEventTarget(nativeEvent);
- var targetInst = getClosestInstanceFromNode(nativeEventTarget);
- if (targetInst !== null && typeof targetInst.tag === 'number' && !isFiberMounted(targetInst)) {
- // If we get an event (ex: img onload) before committing that
- // component's mount, ignore it for now (that is, treat it as if it was an
- // event on a non-React tree). We might also consider queueing events and
- // dispatching them after the mount.
- targetInst = null;
+ // FF does not set `charCode` for the Enter-key, check against `keyCode`.
+ if (charCode === 0 && keyCode === 13) {
+ charCode = 13;
+ }
+ } else {
+ // IE8 does not implement `charCode`, but `keyCode` has the correct value.
+ charCode = keyCode;
}
- var bookKeeping = getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst);
+ // IE and Edge (on Windows) and Chrome / Safari (on Windows and Linux)
+ // report Enter as charCode 10 when ctrl is pressed.
+ if (charCode === 10) {
+ charCode = 13;
+ }
- try {
- // Event queue being processed in the same cycle allows
- // `preventDefault`.
- batchedUpdates(handleTopLevelImpl, bookKeeping);
- } finally {
- releaseTopLevelCallbackBookKeeping(bookKeeping);
+ // Some non-printable keys are reported in `charCode`/`keyCode`, discard them.
+ // Must not discard the (non-)printable Enter-key.
+ if (charCode >= 32 || charCode === 13) {
+ return charCode;
}
-}
-var ReactDOMEventListener = Object.freeze({
- get _enabled () { return _enabled; },
- get _handleTopLevel () { return _handleTopLevel; },
- setHandleTopLevel: setHandleTopLevel,
- setEnabled: setEnabled,
- isEnabled: isEnabled,
- trapBubbledEvent: trapBubbledEvent,
- trapCapturedEvent: trapCapturedEvent,
- dispatchEvent: dispatchEvent
-});
+ return 0;
+}
/**
- * Generate a mapping of standard vendor prefixes using the defined style property and event name.
- *
- * @param {string} styleProp
- * @param {string} eventName
- * @returns {object}
+ * Normalization of deprecated HTML5 `key` values
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Key_names
*/
-function makePrefixMap(styleProp, eventName) {
- var prefixes = {};
-
- prefixes[styleProp.toLowerCase()] = eventName.toLowerCase();
- prefixes['Webkit' + styleProp] = 'webkit' + eventName;
- prefixes['Moz' + styleProp] = 'moz' + eventName;
- prefixes['ms' + styleProp] = 'MS' + eventName;
- prefixes['O' + styleProp] = 'o' + eventName.toLowerCase();
-
- return prefixes;
-}
+var normalizeKey = {
+ Esc: 'Escape',
+ Spacebar: ' ',
+ Left: 'ArrowLeft',
+ Up: 'ArrowUp',
+ Right: 'ArrowRight',
+ Down: 'ArrowDown',
+ Del: 'Delete',
+ Win: 'OS',
+ Menu: 'ContextMenu',
+ Apps: 'ContextMenu',
+ Scroll: 'ScrollLock',
+ MozPrintableKey: 'Unidentified'
+};
/**
- * A list of event names to a configurable list of vendor prefixes.
+ * Translation from legacy `keyCode` to HTML5 `key`
+ * Only special keys supported, all others depend on keyboard layout or browser
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Key_names
*/
-var vendorPrefixes = {
- animationend: makePrefixMap('Animation', 'AnimationEnd'),
- animationiteration: makePrefixMap('Animation', 'AnimationIteration'),
- animationstart: makePrefixMap('Animation', 'AnimationStart'),
- transitionend: makePrefixMap('Transition', 'TransitionEnd')
+var translateToKey = {
+ '8': 'Backspace',
+ '9': 'Tab',
+ '12': 'Clear',
+ '13': 'Enter',
+ '16': 'Shift',
+ '17': 'Control',
+ '18': 'Alt',
+ '19': 'Pause',
+ '20': 'CapsLock',
+ '27': 'Escape',
+ '32': ' ',
+ '33': 'PageUp',
+ '34': 'PageDown',
+ '35': 'End',
+ '36': 'Home',
+ '37': 'ArrowLeft',
+ '38': 'ArrowUp',
+ '39': 'ArrowRight',
+ '40': 'ArrowDown',
+ '45': 'Insert',
+ '46': 'Delete',
+ '112': 'F1',
+ '113': 'F2',
+ '114': 'F3',
+ '115': 'F4',
+ '116': 'F5',
+ '117': 'F6',
+ '118': 'F7',
+ '119': 'F8',
+ '120': 'F9',
+ '121': 'F10',
+ '122': 'F11',
+ '123': 'F12',
+ '144': 'NumLock',
+ '145': 'ScrollLock',
+ '224': 'Meta'
};
/**
- * Event names that have already been detected and prefixed (if applicable).
+ * @param {object} nativeEvent Native browser event.
+ * @return {string} Normalized `key` property.
*/
-var prefixedEventNames = {};
+function getEventKey(nativeEvent) {
+ if (nativeEvent.key) {
+ // Normalize inconsistent values reported by browsers due to
+ // implementations of a working draft specification.
-/**
- * Element to check for prefixes on.
- */
-var style = {};
+ // FireFox implements `key` but returns `MozPrintableKey` for all
+ // printable characters (normalized to `Unidentified`), ignore it.
+ var key = normalizeKey[nativeEvent.key] || nativeEvent.key;
+ if (key !== 'Unidentified') {
+ return key;
+ }
+ }
-/**
- * Bootstrap if a DOM exists.
- */
-if (ExecutionEnvironment.canUseDOM) {
- style = document.createElement('div').style;
+ // Browser does not implement `key`, polyfill as much of it as we can.
+ if (nativeEvent.type === 'keypress') {
+ var charCode = getEventCharCode(nativeEvent);
- // On some platforms, in particular some releases of Android 4.x,
- // the un-prefixed "animation" and "transition" properties are defined on the
- // style object but the events that fire will still be prefixed, so we need
- // to check if the un-prefixed events are usable, and if not remove them from the map.
- if (!('AnimationEvent' in window)) {
- delete vendorPrefixes.animationend.animation;
- delete vendorPrefixes.animationiteration.animation;
- delete vendorPrefixes.animationstart.animation;
+ // The enter-key is technically both printable and non-printable and can
+ // thus be captured by `keypress`, no other non-printable key should.
+ return charCode === 13 ? 'Enter' : String.fromCharCode(charCode);
}
-
- // Same as above
- if (!('TransitionEvent' in window)) {
- delete vendorPrefixes.transitionend.transition;
+ if (nativeEvent.type === 'keydown' || nativeEvent.type === 'keyup') {
+ // While user keyboard layout determines the actual meaning of each
+ // `keyCode` value, almost all function keys have a universal value.
+ return translateToKey[nativeEvent.keyCode] || 'Unidentified';
}
+ return '';
}
/**
- * Attempts to determine the correct vendor prefixed event name.
- *
- * @param {string} eventName
- * @returns {string}
+ * @interface KeyboardEvent
+ * @see http://www.w3.org/TR/DOM-Level-3-Events/
*/
-function getVendorPrefixedEventName(eventName) {
- if (prefixedEventNames[eventName]) {
- return prefixedEventNames[eventName];
- } else if (!vendorPrefixes[eventName]) {
- return eventName;
- }
+var SyntheticKeyboardEvent = SyntheticUIEvent.extend({
+ key: getEventKey,
+ location: null,
+ ctrlKey: null,
+ shiftKey: null,
+ altKey: null,
+ metaKey: null,
+ repeat: null,
+ locale: null,
+ getModifierState: getEventModifierState,
+ // Legacy Interface
+ charCode: function (event) {
+ // `charCode` is the result of a KeyPress event and represents the value of
+ // the actual printable character.
- var prefixMap = vendorPrefixes[eventName];
+ // KeyPress is deprecated, but its replacement is not yet final and not
+ // implemented in any major browser. Only KeyPress has charCode.
+ if (event.type === 'keypress') {
+ return getEventCharCode(event);
+ }
+ return 0;
+ },
+ keyCode: function (event) {
+ // `keyCode` is the result of a KeyDown/Up event and represents the value of
+ // physical keyboard key.
- for (var styleProp in prefixMap) {
- if (prefixMap.hasOwnProperty(styleProp) && styleProp in style) {
- return prefixedEventNames[eventName] = prefixMap[styleProp];
+ // The actual meaning of the value depends on the users' keyboard layout
+ // which cannot be detected. Assuming that it is a US keyboard layout
+ // provides a surprisingly accurate mapping for US and European users.
+ // Due to this, it is left to the user to implement at this time.
+ if (event.type === 'keydown' || event.type === 'keyup') {
+ return event.keyCode;
+ }
+ return 0;
+ },
+ which: function (event) {
+ // `which` is an alias for either `keyCode` or `charCode` depending on the
+ // type of the event.
+ if (event.type === 'keypress') {
+ return getEventCharCode(event);
+ }
+ if (event.type === 'keydown' || event.type === 'keyup') {
+ return event.keyCode;
}
+ return 0;
}
+});
- return '';
-}
+/**
+ * @interface DragEvent
+ * @see http://www.w3.org/TR/DOM-Level-3-Events/
+ */
+var SyntheticDragEvent = SyntheticMouseEvent.extend({
+ dataTransfer: null
+});
/**
- * Types of raw signals from the browser caught at the top level.
- *
- * For events like 'submit' which don't consistently bubble (which we
- * trap at a lower node than `document`), binding at `document` would
- * cause duplicate events so we don't include them here.
+ * @interface TouchEvent
+ * @see http://www.w3.org/TR/touch-events/
*/
-var topLevelTypes$1 = {
- topAbort: 'abort',
- topAnimationEnd: getVendorPrefixedEventName('animationend') || 'animationend',
- topAnimationIteration: getVendorPrefixedEventName('animationiteration') || 'animationiteration',
- topAnimationStart: getVendorPrefixedEventName('animationstart') || 'animationstart',
- topBlur: 'blur',
- topCancel: 'cancel',
- topCanPlay: 'canplay',
- topCanPlayThrough: 'canplaythrough',
- topChange: 'change',
- topClick: 'click',
- topClose: 'close',
- topCompositionEnd: 'compositionend',
- topCompositionStart: 'compositionstart',
- topCompositionUpdate: 'compositionupdate',
- topContextMenu: 'contextmenu',
- topCopy: 'copy',
- topCut: 'cut',
- topDoubleClick: 'dblclick',
- topDrag: 'drag',
- topDragEnd: 'dragend',
- topDragEnter: 'dragenter',
- topDragExit: 'dragexit',
- topDragLeave: 'dragleave',
- topDragOver: 'dragover',
- topDragStart: 'dragstart',
- topDrop: 'drop',
- topDurationChange: 'durationchange',
- topEmptied: 'emptied',
- topEncrypted: 'encrypted',
- topEnded: 'ended',
- topError: 'error',
- topFocus: 'focus',
- topInput: 'input',
- topKeyDown: 'keydown',
- topKeyPress: 'keypress',
- topKeyUp: 'keyup',
- topLoadedData: 'loadeddata',
- topLoad: 'load',
- topLoadedMetadata: 'loadedmetadata',
- topLoadStart: 'loadstart',
- topMouseDown: 'mousedown',
- topMouseMove: 'mousemove',
- topMouseOut: 'mouseout',
- topMouseOver: 'mouseover',
- topMouseUp: 'mouseup',
- topPaste: 'paste',
- topPause: 'pause',
- topPlay: 'play',
- topPlaying: 'playing',
- topProgress: 'progress',
- topRateChange: 'ratechange',
- topScroll: 'scroll',
- topSeeked: 'seeked',
- topSeeking: 'seeking',
- topSelectionChange: 'selectionchange',
- topStalled: 'stalled',
- topSuspend: 'suspend',
- topTextInput: 'textInput',
- topTimeUpdate: 'timeupdate',
- topToggle: 'toggle',
- topTouchCancel: 'touchcancel',
- topTouchEnd: 'touchend',
- topTouchMove: 'touchmove',
- topTouchStart: 'touchstart',
- topTransitionEnd: getVendorPrefixedEventName('transitionend') || 'transitionend',
- topVolumeChange: 'volumechange',
- topWaiting: 'waiting',
- topWheel: 'wheel'
-};
+var SyntheticTouchEvent = SyntheticUIEvent.extend({
+ touches: null,
+ targetTouches: null,
+ changedTouches: null,
+ altKey: null,
+ metaKey: null,
+ ctrlKey: null,
+ shiftKey: null,
+ getModifierState: getEventModifierState
+});
-var BrowserEventConstants = {
- topLevelTypes: topLevelTypes$1
-};
+/**
+ * @interface Event
+ * @see http://www.w3.org/TR/2009/WD-css3-transitions-20090320/#transition-events-
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/TransitionEvent
+ */
+var SyntheticTransitionEvent = SyntheticEvent.extend({
+ propertyName: null,
+ elapsedTime: null,
+ pseudoElement: null
+});
-function runEventQueueInBatch(events) {
- enqueueEvents(events);
- processEventQueue(false);
-}
+/**
+ * @interface WheelEvent
+ * @see http://www.w3.org/TR/DOM-Level-3-Events/
+ */
+var SyntheticWheelEvent = SyntheticMouseEvent.extend({
+ deltaX: function (event) {
+ return 'deltaX' in event ? event.deltaX : // Fallback to `wheelDeltaX` for Webkit and normalize (right is positive).
+ 'wheelDeltaX' in event ? -event.wheelDeltaX : 0;
+ },
+ deltaY: function (event) {
+ return 'deltaY' in event ? event.deltaY : // Fallback to `wheelDeltaY` for Webkit and normalize (down is positive).
+ 'wheelDeltaY' in event ? -event.wheelDeltaY : // Fallback to `wheelDelta` for IE<9 and normalize (down is positive).
+ 'wheelDelta' in event ? -event.wheelDelta : 0;
+ },
+
+ deltaZ: null,
+
+ // Browsers without "deltaMode" is reporting in raw wheel delta where one
+ // notch on the scroll is always +/- 120, roughly equivalent to pixels.
+ // A good approximation of DOM_DELTA_LINE (1) is 5% of viewport size or
+ // ~40 pixels, for DOM_DELTA_SCREEN (2) it is 87.5% of viewport size.
+ deltaMode: null
+});
/**
- * Streams a fired top-level event to `EventPluginHub` where plugins have the
- * opportunity to create `ReactEvent`s to be dispatched.
+ * Turns
+ * ['abort', ...]
+ * into
+ * eventTypes = {
+ * 'abort': {
+ * phasedRegistrationNames: {
+ * bubbled: 'onAbort',
+ * captured: 'onAbortCapture',
+ * },
+ * dependencies: [TOP_ABORT],
+ * },
+ * ...
+ * };
+ * topLevelEventsToDispatchConfig = new Map([
+ * [TOP_ABORT, { sameConfig }],
+ * ]);
*/
-function handleTopLevel(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
- var events = extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
- runEventQueueInBatch(events);
+
+var interactiveEventTypeNames = [[TOP_BLUR, 'blur'], [TOP_CANCEL, 'cancel'], [TOP_CLICK, 'click'], [TOP_CLOSE, 'close'], [TOP_CONTEXT_MENU, 'contextMenu'], [TOP_COPY, 'copy'], [TOP_CUT, 'cut'], [TOP_AUX_CLICK, 'auxClick'], [TOP_DOUBLE_CLICK, 'doubleClick'], [TOP_DRAG_END, 'dragEnd'], [TOP_DRAG_START, 'dragStart'], [TOP_DROP, 'drop'], [TOP_FOCUS, 'focus'], [TOP_INPUT, 'input'], [TOP_INVALID, 'invalid'], [TOP_KEY_DOWN, 'keyDown'], [TOP_KEY_PRESS, 'keyPress'], [TOP_KEY_UP, 'keyUp'], [TOP_MOUSE_DOWN, 'mouseDown'], [TOP_MOUSE_UP, 'mouseUp'], [TOP_PASTE, 'paste'], [TOP_PAUSE, 'pause'], [TOP_PLAY, 'play'], [TOP_POINTER_CANCEL, 'pointerCancel'], [TOP_POINTER_DOWN, 'pointerDown'], [TOP_POINTER_UP, 'pointerUp'], [TOP_RATE_CHANGE, 'rateChange'], [TOP_RESET, 'reset'], [TOP_SEEKED, 'seeked'], [TOP_SUBMIT, 'submit'], [TOP_TOUCH_CANCEL, 'touchCancel'], [TOP_TOUCH_END, 'touchEnd'], [TOP_TOUCH_START, 'touchStart'], [TOP_VOLUME_CHANGE, 'volumeChange']];
+var nonInteractiveEventTypeNames = [[TOP_ABORT, 'abort'], [TOP_ANIMATION_END, 'animationEnd'], [TOP_ANIMATION_ITERATION, 'animationIteration'], [TOP_ANIMATION_START, 'animationStart'], [TOP_CAN_PLAY, 'canPlay'], [TOP_CAN_PLAY_THROUGH, 'canPlayThrough'], [TOP_DRAG, 'drag'], [TOP_DRAG_ENTER, 'dragEnter'], [TOP_DRAG_EXIT, 'dragExit'], [TOP_DRAG_LEAVE, 'dragLeave'], [TOP_DRAG_OVER, 'dragOver'], [TOP_DURATION_CHANGE, 'durationChange'], [TOP_EMPTIED, 'emptied'], [TOP_ENCRYPTED, 'encrypted'], [TOP_ENDED, 'ended'], [TOP_ERROR, 'error'], [TOP_GOT_POINTER_CAPTURE, 'gotPointerCapture'], [TOP_LOAD, 'load'], [TOP_LOADED_DATA, 'loadedData'], [TOP_LOADED_METADATA, 'loadedMetadata'], [TOP_LOAD_START, 'loadStart'], [TOP_LOST_POINTER_CAPTURE, 'lostPointerCapture'], [TOP_MOUSE_MOVE, 'mouseMove'], [TOP_MOUSE_OUT, 'mouseOut'], [TOP_MOUSE_OVER, 'mouseOver'], [TOP_PLAYING, 'playing'], [TOP_POINTER_MOVE, 'pointerMove'], [TOP_POINTER_OUT, 'pointerOut'], [TOP_POINTER_OVER, 'pointerOver'], [TOP_PROGRESS, 'progress'], [TOP_SCROLL, 'scroll'], [TOP_SEEKING, 'seeking'], [TOP_STALLED, 'stalled'], [TOP_SUSPEND, 'suspend'], [TOP_TIME_UPDATE, 'timeUpdate'], [TOP_TOGGLE, 'toggle'], [TOP_TOUCH_MOVE, 'touchMove'], [TOP_TRANSITION_END, 'transitionEnd'], [TOP_WAITING, 'waiting'], [TOP_WHEEL, 'wheel']];
+
+var eventTypes$4 = {};
+var topLevelEventsToDispatchConfig = {};
+
+function addEventTypeNameToConfig(_ref, isInteractive) {
+ var topEvent = _ref[0],
+ event = _ref[1];
+
+ var capitalizedEvent = event[0].toUpperCase() + event.slice(1);
+ var onEvent = 'on' + capitalizedEvent;
+
+ var type = {
+ phasedRegistrationNames: {
+ bubbled: onEvent,
+ captured: onEvent + 'Capture'
+ },
+ dependencies: [topEvent],
+ isInteractive: isInteractive
+ };
+ eventTypes$4[event] = type;
+ topLevelEventsToDispatchConfig[topEvent] = type;
}
-var topLevelTypes = BrowserEventConstants.topLevelTypes;
+interactiveEventTypeNames.forEach(function (eventTuple) {
+ addEventTypeNameToConfig(eventTuple, true);
+});
+nonInteractiveEventTypeNames.forEach(function (eventTuple) {
+ addEventTypeNameToConfig(eventTuple, false);
+});
-/**
- * Summary of `ReactBrowserEventEmitter` event handling:
- *
- * - Top-level delegation is used to trap most native browser events. This
+// Only used in DEV for exhaustiveness validation.
+var knownHTMLTopLevelTypes = [TOP_ABORT, TOP_CANCEL, TOP_CAN_PLAY, TOP_CAN_PLAY_THROUGH, TOP_CLOSE, TOP_DURATION_CHANGE, TOP_EMPTIED, TOP_ENCRYPTED, TOP_ENDED, TOP_ERROR, TOP_INPUT, TOP_INVALID, TOP_LOAD, TOP_LOADED_DATA, TOP_LOADED_METADATA, TOP_LOAD_START, TOP_PAUSE, TOP_PLAY, TOP_PLAYING, TOP_PROGRESS, TOP_RATE_CHANGE, TOP_RESET, TOP_SEEKED, TOP_SEEKING, TOP_STALLED, TOP_SUBMIT, TOP_SUSPEND, TOP_TIME_UPDATE, TOP_TOGGLE, TOP_VOLUME_CHANGE, TOP_WAITING];
+
+var SimpleEventPlugin = {
+ eventTypes: eventTypes$4,
+
+ isInteractiveTopLevelEventType: function (topLevelType) {
+ var config = topLevelEventsToDispatchConfig[topLevelType];
+ return config !== undefined && config.isInteractive === true;
+ },
+
+
+ extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
+ var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
+ if (!dispatchConfig) {
+ return null;
+ }
+ var EventConstructor = void 0;
+ switch (topLevelType) {
+ case TOP_KEY_PRESS:
+ // Firefox creates a keypress event for function keys too. This removes
+ // the unwanted keypress events. Enter is however both printable and
+ // non-printable. One would expect Tab to be as well (but it isn't).
+ if (getEventCharCode(nativeEvent) === 0) {
+ return null;
+ }
+ /* falls through */
+ case TOP_KEY_DOWN:
+ case TOP_KEY_UP:
+ EventConstructor = SyntheticKeyboardEvent;
+ break;
+ case TOP_BLUR:
+ case TOP_FOCUS:
+ EventConstructor = SyntheticFocusEvent;
+ break;
+ case TOP_CLICK:
+ // Firefox creates a click event on right mouse clicks. This removes the
+ // unwanted click events.
+ if (nativeEvent.button === 2) {
+ return null;
+ }
+ /* falls through */
+ case TOP_AUX_CLICK:
+ case TOP_DOUBLE_CLICK:
+ case TOP_MOUSE_DOWN:
+ case TOP_MOUSE_MOVE:
+ case TOP_MOUSE_UP:
+ // TODO: Disabled elements should not respond to mouse events
+ /* falls through */
+ case TOP_MOUSE_OUT:
+ case TOP_MOUSE_OVER:
+ case TOP_CONTEXT_MENU:
+ EventConstructor = SyntheticMouseEvent;
+ break;
+ case TOP_DRAG:
+ case TOP_DRAG_END:
+ case TOP_DRAG_ENTER:
+ case TOP_DRAG_EXIT:
+ case TOP_DRAG_LEAVE:
+ case TOP_DRAG_OVER:
+ case TOP_DRAG_START:
+ case TOP_DROP:
+ EventConstructor = SyntheticDragEvent;
+ break;
+ case TOP_TOUCH_CANCEL:
+ case TOP_TOUCH_END:
+ case TOP_TOUCH_MOVE:
+ case TOP_TOUCH_START:
+ EventConstructor = SyntheticTouchEvent;
+ break;
+ case TOP_ANIMATION_END:
+ case TOP_ANIMATION_ITERATION:
+ case TOP_ANIMATION_START:
+ EventConstructor = SyntheticAnimationEvent;
+ break;
+ case TOP_TRANSITION_END:
+ EventConstructor = SyntheticTransitionEvent;
+ break;
+ case TOP_SCROLL:
+ EventConstructor = SyntheticUIEvent;
+ break;
+ case TOP_WHEEL:
+ EventConstructor = SyntheticWheelEvent;
+ break;
+ case TOP_COPY:
+ case TOP_CUT:
+ case TOP_PASTE:
+ EventConstructor = SyntheticClipboardEvent;
+ break;
+ case TOP_GOT_POINTER_CAPTURE:
+ case TOP_LOST_POINTER_CAPTURE:
+ case TOP_POINTER_CANCEL:
+ case TOP_POINTER_DOWN:
+ case TOP_POINTER_MOVE:
+ case TOP_POINTER_OUT:
+ case TOP_POINTER_OVER:
+ case TOP_POINTER_UP:
+ EventConstructor = SyntheticPointerEvent;
+ break;
+ default:
+ {
+ if (knownHTMLTopLevelTypes.indexOf(topLevelType) === -1) {
+ warningWithoutStack$1(false, 'SimpleEventPlugin: Unhandled event type, `%s`. This warning ' + 'is likely caused by a bug in React. Please file an issue.', topLevelType);
+ }
+ }
+ // HTML Events
+ // @see http://www.w3.org/TR/html5/index.html#events-0
+ EventConstructor = SyntheticEvent;
+ break;
+ }
+ var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget);
+ accumulateTwoPhaseDispatches(event);
+ return event;
+ }
+};
+
+var isInteractiveTopLevelEventType = SimpleEventPlugin.isInteractiveTopLevelEventType;
+
+
+var CALLBACK_BOOKKEEPING_POOL_SIZE = 10;
+var callbackBookkeepingPool = [];
+
+/**
+ * Find the deepest React component completely containing the root of the
+ * passed-in instance (for use when entire React trees are nested within each
+ * other). If React trees are not nested, returns null.
+ */
+function findRootContainerNode(inst) {
+ // TODO: It may be a good idea to cache this to prevent unnecessary DOM
+ // traversal, but caching is difficult to do correctly without using a
+ // mutation observer to listen for all DOM changes.
+ while (inst.return) {
+ inst = inst.return;
+ }
+ if (inst.tag !== HostRoot) {
+ // This can happen if we're in a detached tree.
+ return null;
+ }
+ return inst.stateNode.containerInfo;
+}
+
+// Used to store ancestor hierarchy in top level callback
+function getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst) {
+ if (callbackBookkeepingPool.length) {
+ var instance = callbackBookkeepingPool.pop();
+ instance.topLevelType = topLevelType;
+ instance.nativeEvent = nativeEvent;
+ instance.targetInst = targetInst;
+ return instance;
+ }
+ return {
+ topLevelType: topLevelType,
+ nativeEvent: nativeEvent,
+ targetInst: targetInst,
+ ancestors: []
+ };
+}
+
+function releaseTopLevelCallbackBookKeeping(instance) {
+ instance.topLevelType = null;
+ instance.nativeEvent = null;
+ instance.targetInst = null;
+ instance.ancestors.length = 0;
+ if (callbackBookkeepingPool.length < CALLBACK_BOOKKEEPING_POOL_SIZE) {
+ callbackBookkeepingPool.push(instance);
+ }
+}
+
+function handleTopLevel(bookKeeping) {
+ var targetInst = bookKeeping.targetInst;
+
+ // Loop through the hierarchy, in case there's any nested components.
+ // It's important that we build the array of ancestors before calling any
+ // event handlers, because event handlers can modify the DOM, leading to
+ // inconsistencies with ReactMount's node cache. See #1105.
+ var ancestor = targetInst;
+ do {
+ if (!ancestor) {
+ bookKeeping.ancestors.push(ancestor);
+ break;
+ }
+ var root = findRootContainerNode(ancestor);
+ if (!root) {
+ break;
+ }
+ bookKeeping.ancestors.push(ancestor);
+ ancestor = getClosestInstanceFromNode(root);
+ } while (ancestor);
+
+ for (var i = 0; i < bookKeeping.ancestors.length; i++) {
+ targetInst = bookKeeping.ancestors[i];
+ runExtractedEventsInBatch(bookKeeping.topLevelType, targetInst, bookKeeping.nativeEvent, getEventTarget(bookKeeping.nativeEvent));
+ }
+}
+
+// TODO: can we stop exporting these?
+var _enabled = true;
+
+function setEnabled(enabled) {
+ _enabled = !!enabled;
+}
+
+function isEnabled() {
+ return _enabled;
+}
+
+/**
+ * Traps top-level events by using event bubbling.
+ *
+ * @param {number} topLevelType Number from `TopLevelEventTypes`.
+ * @param {object} element Element on which to attach listener.
+ * @return {?object} An object with a remove function which will forcefully
+ * remove the listener.
+ * @internal
+ */
+function trapBubbledEvent(topLevelType, element) {
+ if (!element) {
+ return null;
+ }
+ var dispatch = isInteractiveTopLevelEventType(topLevelType) ? dispatchInteractiveEvent : dispatchEvent;
+
+ addEventBubbleListener(element, getRawEventName(topLevelType),
+ // Check if interactive and wrap in interactiveUpdates
+ dispatch.bind(null, topLevelType));
+}
+
+/**
+ * Traps a top-level event by using event capturing.
+ *
+ * @param {number} topLevelType Number from `TopLevelEventTypes`.
+ * @param {object} element Element on which to attach listener.
+ * @return {?object} An object with a remove function which will forcefully
+ * remove the listener.
+ * @internal
+ */
+function trapCapturedEvent(topLevelType, element) {
+ if (!element) {
+ return null;
+ }
+ var dispatch = isInteractiveTopLevelEventType(topLevelType) ? dispatchInteractiveEvent : dispatchEvent;
+
+ addEventCaptureListener(element, getRawEventName(topLevelType),
+ // Check if interactive and wrap in interactiveUpdates
+ dispatch.bind(null, topLevelType));
+}
+
+function dispatchInteractiveEvent(topLevelType, nativeEvent) {
+ interactiveUpdates(dispatchEvent, topLevelType, nativeEvent);
+}
+
+function dispatchEvent(topLevelType, nativeEvent) {
+ if (!_enabled) {
+ return;
+ }
+
+ var nativeEventTarget = getEventTarget(nativeEvent);
+ var targetInst = getClosestInstanceFromNode(nativeEventTarget);
+ if (targetInst !== null && typeof targetInst.tag === 'number' && !isFiberMounted(targetInst)) {
+ // If we get an event (ex: img onload) before committing that
+ // component's mount, ignore it for now (that is, treat it as if it was an
+ // event on a non-React tree). We might also consider queueing events and
+ // dispatching them after the mount.
+ targetInst = null;
+ }
+
+ var bookKeeping = getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst);
+
+ try {
+ // Event queue being processed in the same cycle allows
+ // `preventDefault`.
+ batchedUpdates(handleTopLevel, bookKeeping);
+ } finally {
+ releaseTopLevelCallbackBookKeeping(bookKeeping);
+ }
+}
+
+/**
+ * Summary of `ReactBrowserEventEmitter` event handling:
+ *
+ * - Top-level delegation is used to trap most native browser events. This
* may only occur in the main thread and is the responsibility of
* ReactDOMEventListener, which is injected and can therefore support
* pluggable event sources. This is the only work that occurs in the main
@@ -4921,39 +5608,49 @@ function getListeningForDocument(mountAt) {
* they bubble to document.
*
* @param {string} registrationName Name of listener (e.g. `onClick`).
- * @param {object} contentDocumentHandle Document which owns the container
+ * @param {object} mountAt Container where to mount the listener
*/
-function listenTo(registrationName, contentDocumentHandle) {
- var mountAt = contentDocumentHandle;
+function listenTo(registrationName, mountAt) {
var isListening = getListeningForDocument(mountAt);
var dependencies = registrationNameDependencies[registrationName];
for (var i = 0; i < dependencies.length; i++) {
var dependency = dependencies[i];
if (!(isListening.hasOwnProperty(dependency) && isListening[dependency])) {
- if (dependency === 'topScroll') {
- trapCapturedEvent('topScroll', 'scroll', mountAt);
- } else if (dependency === 'topFocus' || dependency === 'topBlur') {
- trapCapturedEvent('topFocus', 'focus', mountAt);
- trapCapturedEvent('topBlur', 'blur', mountAt);
-
- // to make sure blur and focus event listeners are only attached once
- isListening.topBlur = true;
- isListening.topFocus = true;
- } else if (dependency === 'topCancel') {
- if (isEventSupported('cancel', true)) {
- trapCapturedEvent('topCancel', 'cancel', mountAt);
- }
- isListening.topCancel = true;
- } else if (dependency === 'topClose') {
- if (isEventSupported('close', true)) {
- trapCapturedEvent('topClose', 'close', mountAt);
- }
- isListening.topClose = true;
- } else if (topLevelTypes.hasOwnProperty(dependency)) {
- trapBubbledEvent(dependency, topLevelTypes[dependency], mountAt);
+ switch (dependency) {
+ case TOP_SCROLL:
+ trapCapturedEvent(TOP_SCROLL, mountAt);
+ break;
+ case TOP_FOCUS:
+ case TOP_BLUR:
+ trapCapturedEvent(TOP_FOCUS, mountAt);
+ trapCapturedEvent(TOP_BLUR, mountAt);
+ // We set the flag for a single dependency later in this function,
+ // but this ensures we mark both as attached rather than just one.
+ isListening[TOP_BLUR] = true;
+ isListening[TOP_FOCUS] = true;
+ break;
+ case TOP_CANCEL:
+ case TOP_CLOSE:
+ if (isEventSupported(getRawEventName(dependency))) {
+ trapCapturedEvent(dependency, mountAt);
+ }
+ break;
+ case TOP_INVALID:
+ case TOP_SUBMIT:
+ case TOP_RESET:
+ // We listen to them on the target DOM elements.
+ // Some of them bubble so we don't want them to fire twice.
+ break;
+ default:
+ // By default, listen on the top level to all non-media events.
+ // Media events don't bubble so adding the listener wouldn't do anything.
+ var isMediaEvent = mediaEventTypes.indexOf(dependency) !== -1;
+ if (!isMediaEvent) {
+ trapBubbledEvent(dependency, mountAt);
+ }
+ break;
}
-
isListening[dependency] = true;
}
}
@@ -4971,6 +5668,18 @@ function isListeningToAllDependencies(registrationName, mountAt) {
return true;
}
+function getActiveElement(doc) {
+ doc = doc || (typeof document !== 'undefined' ? document : undefined);
+ if (typeof doc === 'undefined') {
+ return null;
+ }
+ try {
+ return doc.activeElement || doc.body;
+ } catch (e) {
+ return doc.body;
+ }
+}
+
/**
* Given any node return the first leaf node without children.
*
@@ -5035,7 +5744,10 @@ function getNodeForCharacterOffset(root, offset) {
* @return {?object}
*/
function getOffsets(outerNode) {
- var selection = window.getSelection && window.getSelection();
+ var ownerDocument = outerNode.ownerDocument;
+
+ var win = ownerDocument && ownerDocument.defaultView || window;
+ var selection = win.getSelection && win.getSelection();
if (!selection || selection.rangeCount === 0) {
return null;
@@ -5043,7 +5755,7 @@ function getOffsets(outerNode) {
var anchorNode = selection.anchorNode,
anchorOffset = selection.anchorOffset,
- focusNode$$1 = selection.focusNode,
+ focusNode = selection.focusNode,
focusOffset = selection.focusOffset;
// In Firefox, anchorNode and focusNode can be "anonymous divs", e.g. the
@@ -5057,13 +5769,13 @@ function getOffsets(outerNode) {
try {
/* eslint-disable no-unused-expressions */
anchorNode.nodeType;
- focusNode$$1.nodeType;
+ focusNode.nodeType;
/* eslint-enable no-unused-expressions */
} catch (e) {
return null;
}
- return getModernOffsetsFromPoints(outerNode, anchorNode, anchorOffset, focusNode$$1, focusOffset);
+ return getModernOffsetsFromPoints(outerNode, anchorNode, anchorOffset, focusNode, focusOffset);
}
/**
@@ -5075,7 +5787,7 @@ function getOffsets(outerNode) {
*
* Exported only for testing.
*/
-function getModernOffsetsFromPoints(outerNode, anchorNode, anchorOffset, focusNode$$1, focusOffset) {
+function getModernOffsetsFromPoints(outerNode, anchorNode, anchorOffset, focusNode, focusOffset) {
var length = 0;
var start = -1;
var end = -1;
@@ -5091,7 +5803,7 @@ function getModernOffsetsFromPoints(outerNode, anchorNode, anchorOffset, focusNo
if (node === anchorNode && (anchorOffset === 0 || node.nodeType === TEXT_NODE)) {
start = length + anchorOffset;
}
- if (node === focusNode$$1 && (focusOffset === 0 || node.nodeType === TEXT_NODE)) {
+ if (node === focusNode && (focusOffset === 0 || node.nodeType === TEXT_NODE)) {
end = length + focusOffset;
}
@@ -5118,7 +5830,7 @@ function getModernOffsetsFromPoints(outerNode, anchorNode, anchorOffset, focusNo
if (parentNode === anchorNode && ++indexWithinAnchor === anchorOffset) {
start = length;
}
- if (parentNode === focusNode$$1 && ++indexWithinFocus === focusOffset) {
+ if (parentNode === focusNode && ++indexWithinFocus === focusOffset) {
end = length;
}
if ((next = node.nextSibling) !== null) {
@@ -5157,12 +5869,18 @@ function getModernOffsetsFromPoints(outerNode, anchorNode, anchorOffset, focusNo
* @param {object} offsets
*/
function setOffsets(node, offsets) {
- if (!window.getSelection) {
+ var doc = node.ownerDocument || document;
+ var win = doc && doc.defaultView || window;
+
+ // Edge fails with "Object expected" in some scenarios.
+ // (For instance: TinyMCE editor used in a list component that supports pasting to add more,
+ // fails when pasting 100+ items)
+ if (!win.getSelection) {
return;
}
- var selection = window.getSelection();
- var length = node[getTextContentAccessor()].length;
+ var selection = win.getSelection();
+ var length = node.textContent.length;
var start = Math.min(offsets.start, length);
var end = offsets.end === undefined ? start : Math.min(offsets.end, length);
@@ -5181,7 +5899,7 @@ function setOffsets(node, offsets) {
if (selection.rangeCount === 1 && selection.anchorNode === startMarker.node && selection.anchorOffset === startMarker.offset && selection.focusNode === endMarker.node && selection.focusOffset === endMarker.offset) {
return;
}
- var range = document.createRange();
+ var range = doc.createRange();
range.setStart(startMarker.node, startMarker.offset);
selection.removeAllRanges();
@@ -5195,24 +5913,67 @@ function setOffsets(node, offsets) {
}
}
+function isTextNode(node) {
+ return node && node.nodeType === TEXT_NODE;
+}
+
+function containsNode(outerNode, innerNode) {
+ if (!outerNode || !innerNode) {
+ return false;
+ } else if (outerNode === innerNode) {
+ return true;
+ } else if (isTextNode(outerNode)) {
+ return false;
+ } else if (isTextNode(innerNode)) {
+ return containsNode(outerNode, innerNode.parentNode);
+ } else if ('contains' in outerNode) {
+ return outerNode.contains(innerNode);
+ } else if (outerNode.compareDocumentPosition) {
+ return !!(outerNode.compareDocumentPosition(innerNode) & 16);
+ } else {
+ return false;
+ }
+}
+
function isInDocument(node) {
- return containsNode(document.documentElement, node);
+ return node && node.ownerDocument && containsNode(node.ownerDocument.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
+function getActiveElementDeep() {
+ var win = window;
+ var element = getActiveElement();
+ while (element instanceof win.HTMLIFrameElement) {
+ // Accessing the contentDocument of a HTMLIframeElement can cause the browser
+ // to throw, e.g. if it has a cross-origin src attribute
+ try {
+ win = element.contentDocument.defaultView;
+ } catch (e) {
+ return element;
+ }
+ element = getActiveElement(win.document);
+ }
+ return element;
+}
+
+/**
+ * @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.
*/
+/**
+ * @hasSelectionCapabilities: we get the element types that support selection
+ * from https://html.spec.whatwg.org/#do-not-apply, looking at `selectionStart`
+ * and `selectionEnd` rows.
+ */
function hasSelectionCapabilities(elem) {
var nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();
- return nodeName && (nodeName === 'input' && elem.type === 'text' || nodeName === 'textarea' || elem.contentEditable === 'true');
+ return nodeName && (nodeName === 'input' && (elem.type === 'text' || elem.type === 'search' || elem.type === 'tel' || elem.type === 'url' || elem.type === 'password') || nodeName === 'textarea' || elem.contentEditable === 'true');
}
function getSelectionInformation() {
- var focusedElem = getActiveElement();
+ var focusedElem = getActiveElementDeep();
return {
focusedElem: focusedElem,
selectionRange: hasSelectionCapabilities(focusedElem) ? getSelection$1(focusedElem) : null
@@ -5225,11 +5986,11 @@ function getSelectionInformation() {
* nodes and place them back in, resulting in focus being lost.
*/
function restoreSelection(priorSelectionInformation) {
- var curFocusedElem = getActiveElement();
+ var curFocusedElem = getActiveElementDeep();
var priorFocusedElem = priorSelectionInformation.focusedElem;
var priorSelectionRange = priorSelectionInformation.selectionRange;
if (curFocusedElem !== priorFocusedElem && isInDocument(priorFocusedElem)) {
- if (hasSelectionCapabilities(priorFocusedElem)) {
+ if (priorSelectionRange !== null && hasSelectionCapabilities(priorFocusedElem)) {
setSelection(priorFocusedElem, priorSelectionRange);
}
@@ -5246,7 +6007,9 @@ function restoreSelection(priorSelectionInformation) {
}
}
- focusNode(priorFocusedElem);
+ if (typeof priorFocusedElem.focus === 'function') {
+ priorFocusedElem.focus();
+ }
for (var i = 0; i < ancestors.length; i++) {
var info = ancestors[i];
@@ -5301,7 +6064,7 @@ function setSelection(input, offsets) {
}
}
-var skipSelectionChangeEvent = ExecutionEnvironment.canUseDOM && 'documentMode' in document && document.documentMode <= 11;
+var skipSelectionChangeEvent = canUseDOM && 'documentMode' in document && document.documentMode <= 11;
var eventTypes$3 = {
select: {
@@ -5309,7 +6072,7 @@ var eventTypes$3 = {
bubbled: 'onSelect',
captured: 'onSelectCapture'
},
- dependencies: ['topBlur', 'topContextMenu', 'topFocus', 'topKeyDown', 'topKeyUp', 'topMouseDown', 'topMouseUp', 'topSelectionChange']
+ dependencies: [TOP_BLUR, TOP_CONTEXT_MENU, TOP_DRAG_END, TOP_FOCUS, TOP_KEY_DOWN, TOP_KEY_UP, TOP_MOUSE_DOWN, TOP_MOUSE_UP, TOP_SELECTION_CHANGE]
}
};
@@ -5333,8 +6096,9 @@ function getSelection(node) {
start: node.selectionStart,
end: node.selectionEnd
};
- } else if (window.getSelection) {
- var selection = window.getSelection();
+ } else {
+ var win = node.ownerDocument && node.ownerDocument.defaultView || window;
+ var selection = win.getSelection();
return {
anchorNode: selection.anchorNode,
anchorOffset: selection.anchorOffset,
@@ -5344,10 +6108,21 @@ function getSelection(node) {
}
}
+/**
+ * Get document associated with the event target.
+ *
+ * @param {object} nativeEventTarget
+ * @return {Document}
+ */
+function getEventTargetDocument(eventTarget) {
+ return eventTarget.window === eventTarget ? eventTarget.document : eventTarget.nodeType === DOCUMENT_NODE ? eventTarget : eventTarget.ownerDocument;
+}
+
/**
* Poll selection to see whether it's changed.
*
* @param {object} nativeEvent
+ * @param {object} nativeEventTarget
* @return {?SyntheticEvent}
*/
function constructSelectEvent(nativeEvent, nativeEventTarget) {
@@ -5355,7 +6130,9 @@ 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.
- if (mouseDown || activeElement$1 == null || activeElement$1 !== getActiveElement()) {
+ var doc = getEventTargetDocument(nativeEventTarget);
+
+ if (mouseDown || activeElement$1 == null || activeElement$1 !== getActiveElement(doc)) {
return null;
}
@@ -5364,7 +6141,7 @@ function constructSelectEvent(nativeEvent, nativeEventTarget) {
if (!lastSelection || !shallowEqual(lastSelection, currentSelection)) {
lastSelection = currentSelection;
- var syntheticEvent = SyntheticEvent$1.getPooled(eventTypes$3.select, activeElementInst$1, nativeEvent, nativeEventTarget);
+ var syntheticEvent = SyntheticEvent.getPooled(eventTypes$3.select, activeElementInst$1, nativeEvent, nativeEventTarget);
syntheticEvent.type = 'select';
syntheticEvent.target = activeElement$1;
@@ -5395,7 +6172,7 @@ var SelectEventPlugin = {
eventTypes: eventTypes$3,
extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
- var doc = nativeEventTarget.window === nativeEventTarget ? nativeEventTarget.document : nativeEventTarget.nodeType === DOCUMENT_NODE ? nativeEventTarget : nativeEventTarget.ownerDocument;
+ var doc = getEventTargetDocument(nativeEventTarget);
// Track whether all listeners exists for this plugin. If none exist, we do
// not extract events. See #3639.
if (!doc || !isListeningToAllDependencies('onSelect', doc)) {
@@ -5406,25 +6183,26 @@ var SelectEventPlugin = {
switch (topLevelType) {
// Track the input node that has focus.
- case 'topFocus':
+ case TOP_FOCUS:
if (isTextInputElement(targetNode) || targetNode.contentEditable === 'true') {
activeElement$1 = targetNode;
activeElementInst$1 = targetInst;
lastSelection = null;
}
break;
- case 'topBlur':
+ case TOP_BLUR:
activeElement$1 = null;
activeElementInst$1 = null;
lastSelection = null;
break;
// Don't fire the event while the user is dragging. This matches the
// semantics of the native select event.
- case 'topMouseDown':
+ case TOP_MOUSE_DOWN:
mouseDown = true;
break;
- case 'topContextMenu':
- case 'topMouseUp':
+ case TOP_CONTEXT_MENU:
+ case TOP_MOUSE_UP:
+ case TOP_DRAG_END:
mouseDown = false;
return constructSelectEvent(nativeEvent, nativeEventTarget);
// Chrome and IE fire non-standard event when selection is changed (and
@@ -5436,13 +6214,13 @@ var SelectEventPlugin = {
// keyup, but we check on keydown as well in the case of holding down a
// key, when multiple keydown events are fired but only one keyup is.
// This is also our approach for IE handling, for the reason above.
- case 'topSelectionChange':
+ case TOP_SELECTION_CHANGE:
if (skipSelectionChangeEvent) {
break;
}
// falls through
- case 'topKeyDown':
- case 'topKeyUp':
+ case TOP_KEY_DOWN:
+ case TOP_KEY_UP:
return constructSelectEvent(nativeEvent, nativeEventTarget);
}
@@ -5451,8874 +6229,988 @@ var SelectEventPlugin = {
};
/**
- * @interface Event
- * @see http://www.w3.org/TR/css3-animations/#AnimationEvent-interface
- * @see https://developer.mozilla.org/en-US/docs/Web/API/AnimationEvent
- */
-var AnimationEventInterface = {
- animationName: null,
- elapsedTime: null,
- pseudoElement: null
-};
-
-/**
- * @param {object} dispatchConfig Configuration used to dispatch this event.
- * @param {string} dispatchMarker Marker identifying the event target.
- * @param {object} nativeEvent Native browser event.
- * @extends {SyntheticEvent}
+ * Inject modules for resolving DOM hierarchy and plugin ordering.
*/
-function SyntheticAnimationEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
- return SyntheticEvent$1.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
-}
-
-SyntheticEvent$1.augmentClass(SyntheticAnimationEvent, AnimationEventInterface);
+injection.injectEventPluginOrder(DOMEventPluginOrder);
+setComponentTree(getFiberCurrentPropsFromNode$1, getInstanceFromNode$1, getNodeFromInstance$1);
/**
- * @interface Event
- * @see http://www.w3.org/TR/clipboard-apis/
+ * Some important event plugins included by default (without having to require
+ * them).
*/
-var ClipboardEventInterface = {
- clipboardData: function (event) {
- return 'clipboardData' in event ? event.clipboardData : window.clipboardData;
- }
-};
+injection.injectEventPluginsByName({
+ SimpleEventPlugin: SimpleEventPlugin,
+ EnterLeaveEventPlugin: EnterLeaveEventPlugin,
+ ChangeEventPlugin: ChangeEventPlugin,
+ SelectEventPlugin: SelectEventPlugin,
+ BeforeInputEventPlugin: BeforeInputEventPlugin
+});
-/**
- * @param {object} dispatchConfig Configuration used to dispatch this event.
- * @param {string} dispatchMarker Marker identifying the event target.
- * @param {object} nativeEvent Native browser event.
- * @extends {SyntheticEvent}
- */
-function SyntheticClipboardEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
- return SyntheticEvent$1.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
-}
+var didWarnSelectedSetOnOption = false;
+var didWarnInvalidChild = false;
-SyntheticEvent$1.augmentClass(SyntheticClipboardEvent, ClipboardEventInterface);
+function flattenChildren(children) {
+ var content = '';
-/**
- * @interface FocusEvent
- * @see http://www.w3.org/TR/DOM-Level-3-Events/
- */
-var FocusEventInterface = {
- relatedTarget: null
-};
+ // Flatten children. We'll warn if they are invalid
+ // during validateProps() which runs for hydration too.
+ // Note that this would throw on non-element objects.
+ // Elements are stringified (which is normally irrelevant
+ // but matters for ).
+ React.Children.forEach(children, function (child) {
+ if (child == null) {
+ return;
+ }
+ content += child;
+ // Note: we don't warn about invalid children here.
+ // Instead, this is done separately below so that
+ // it happens during the hydration codepath too.
+ });
-/**
- * @param {object} dispatchConfig Configuration used to dispatch this event.
- * @param {string} dispatchMarker Marker identifying the event target.
- * @param {object} nativeEvent Native browser event.
- * @extends {SyntheticUIEvent}
- */
-function SyntheticFocusEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
- return SyntheticUIEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
+ return content;
}
-SyntheticUIEvent.augmentClass(SyntheticFocusEvent, FocusEventInterface);
-
/**
- * `charCode` represents the actual "character code" and is safe to use with
- * `String.fromCharCode`. As such, only keys that correspond to printable
- * characters produce a valid `charCode`, the only exception to this is Enter.
- * The Tab-key is considered non-printable and does not have a `charCode`,
- * presumably because it does not produce a tab-character in browsers.
- *
- * @param {object} nativeEvent Native browser event.
- * @return {number} Normalized `charCode` property.
+ * Implements an host component that warns when `selected` is set.
*/
-function getEventCharCode(nativeEvent) {
- var charCode;
- var keyCode = nativeEvent.keyCode;
- if ('charCode' in nativeEvent) {
- charCode = nativeEvent.charCode;
+function validateProps(element, props) {
+ {
+ // This mirrors the codepath above, but runs for hydration too.
+ // Warn about invalid children here so that client and hydration are consistent.
+ // TODO: this seems like it could cause a DEV-only throw for hydration
+ // if children contains a non-element object. We should try to avoid that.
+ if (typeof props.children === 'object' && props.children !== null) {
+ React.Children.forEach(props.children, function (child) {
+ if (child == null) {
+ return;
+ }
+ if (typeof child === 'string' || typeof child === 'number') {
+ return;
+ }
+ if (typeof child.type !== 'string') {
+ return;
+ }
+ if (!didWarnInvalidChild) {
+ didWarnInvalidChild = true;
+ warning$1(false, 'Only strings and numbers are supported as children.');
+ }
+ });
+ }
- // FF does not set `charCode` for the Enter-key, check against `keyCode`.
- if (charCode === 0 && keyCode === 13) {
- charCode = 13;
+ // TODO: Remove support for `selected` in .
+ if (props.selected != null && !didWarnSelectedSetOnOption) {
+ warning$1(false, 'Use the `defaultValue` or `value` props on instead of ' + 'setting `selected` on .');
+ didWarnSelectedSetOnOption = true;
}
- } else {
- // IE8 does not implement `charCode`, but `keyCode` has the correct value.
- charCode = keyCode;
}
+}
- // Some non-printable keys are reported in `charCode`/`keyCode`, discard them.
- // Must not discard the (non-)printable Enter-key.
- if (charCode >= 32 || charCode === 13) {
- return charCode;
+function postMountWrapper$1(element, props) {
+ // value="" should make a value attribute (#6219)
+ if (props.value != null) {
+ element.setAttribute('value', toString(getToStringValue(props.value)));
}
-
- return 0;
}
-/**
- * Normalization of deprecated HTML5 `key` values
- * @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Key_names
- */
-var normalizeKey = {
- Esc: 'Escape',
- Spacebar: ' ',
- Left: 'ArrowLeft',
- Up: 'ArrowUp',
- Right: 'ArrowRight',
- Down: 'ArrowDown',
- Del: 'Delete',
- Win: 'OS',
- Menu: 'ContextMenu',
- Apps: 'ContextMenu',
- Scroll: 'ScrollLock',
- MozPrintableKey: 'Unidentified'
-};
+function getHostProps$1(element, props) {
+ var hostProps = _assign({ children: undefined }, props);
+ var content = flattenChildren(props.children);
-/**
- * Translation from legacy `keyCode` to HTML5 `key`
- * Only special keys supported, all others depend on keyboard layout or browser
- * @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Key_names
- */
-var translateToKey = {
- '8': 'Backspace',
- '9': 'Tab',
- '12': 'Clear',
- '13': 'Enter',
- '16': 'Shift',
- '17': 'Control',
- '18': 'Alt',
- '19': 'Pause',
- '20': 'CapsLock',
- '27': 'Escape',
- '32': ' ',
- '33': 'PageUp',
- '34': 'PageDown',
- '35': 'End',
- '36': 'Home',
- '37': 'ArrowLeft',
- '38': 'ArrowUp',
- '39': 'ArrowRight',
- '40': 'ArrowDown',
- '45': 'Insert',
- '46': 'Delete',
- '112': 'F1',
- '113': 'F2',
- '114': 'F3',
- '115': 'F4',
- '116': 'F5',
- '117': 'F6',
- '118': 'F7',
- '119': 'F8',
- '120': 'F9',
- '121': 'F10',
- '122': 'F11',
- '123': 'F12',
- '144': 'NumLock',
- '145': 'ScrollLock',
- '224': 'Meta'
-};
+ if (content) {
+ hostProps.children = content;
+ }
-/**
- * @param {object} nativeEvent Native browser event.
- * @return {string} Normalized `key` property.
- */
-function getEventKey(nativeEvent) {
- if (nativeEvent.key) {
- // Normalize inconsistent values reported by browsers due to
- // implementations of a working draft specification.
+ return hostProps;
+}
- // FireFox implements `key` but returns `MozPrintableKey` for all
- // printable characters (normalized to `Unidentified`), ignore it.
- var key = normalizeKey[nativeEvent.key] || nativeEvent.key;
- if (key !== 'Unidentified') {
- return key;
- }
- }
+// TODO: direct imports like some-package/src/* are bad. Fix me.
+var didWarnValueDefaultValue$1 = void 0;
- // Browser does not implement `key`, polyfill as much of it as we can.
- if (nativeEvent.type === 'keypress') {
- var charCode = getEventCharCode(nativeEvent);
+{
+ didWarnValueDefaultValue$1 = false;
+}
- // The enter-key is technically both printable and non-printable and can
- // thus be captured by `keypress`, no other non-printable key should.
- return charCode === 13 ? 'Enter' : String.fromCharCode(charCode);
- }
- if (nativeEvent.type === 'keydown' || nativeEvent.type === 'keyup') {
- // While user keyboard layout determines the actual meaning of each
- // `keyCode` value, almost all function keys have a universal value.
- return translateToKey[nativeEvent.keyCode] || 'Unidentified';
+function getDeclarationErrorAddendum() {
+ var ownerName = getCurrentFiberOwnerNameInDevOrNull();
+ if (ownerName) {
+ return '\n\nCheck the render method of `' + ownerName + '`.';
}
return '';
}
+var valuePropNames = ['value', 'defaultValue'];
+
/**
- * @interface KeyboardEvent
- * @see http://www.w3.org/TR/DOM-Level-3-Events/
+ * Validation function for `value` and `defaultValue`.
*/
-var KeyboardEventInterface = {
- key: getEventKey,
- location: null,
- ctrlKey: null,
- shiftKey: null,
- altKey: null,
- metaKey: null,
- repeat: null,
- locale: null,
- getModifierState: getEventModifierState,
- // Legacy Interface
- charCode: function (event) {
- // `charCode` is the result of a KeyPress event and represents the value of
- // the actual printable character.
+function checkSelectPropTypes(props) {
+ ReactControlledValuePropTypes.checkPropTypes('select', props);
- // KeyPress is deprecated, but its replacement is not yet final and not
- // implemented in any major browser. Only KeyPress has charCode.
- if (event.type === 'keypress') {
- return getEventCharCode(event);
+ for (var i = 0; i < valuePropNames.length; i++) {
+ var propName = valuePropNames[i];
+ if (props[propName] == null) {
+ continue;
}
- return 0;
- },
- keyCode: function (event) {
- // `keyCode` is the result of a KeyDown/Up event and represents the value of
- // physical keyboard key.
-
- // The actual meaning of the value depends on the users' keyboard layout
- // which cannot be detected. Assuming that it is a US keyboard layout
- // provides a surprisingly accurate mapping for US and European users.
- // Due to this, it is left to the user to implement at this time.
- if (event.type === 'keydown' || event.type === 'keyup') {
- return event.keyCode;
+ var isArray = Array.isArray(props[propName]);
+ if (props.multiple && !isArray) {
+ warning$1(false, 'The `%s` prop supplied to must be an array if ' + '`multiple` is true.%s', propName, getDeclarationErrorAddendum());
+ } else if (!props.multiple && isArray) {
+ warning$1(false, 'The `%s` prop supplied to must be a scalar ' + 'value if `multiple` is false.%s', propName, getDeclarationErrorAddendum());
}
- return 0;
- },
- which: function (event) {
- // `which` is an alias for either `keyCode` or `charCode` depending on the
- // type of the event.
- if (event.type === 'keypress') {
- return getEventCharCode(event);
+ }
+}
+
+function updateOptions(node, multiple, propValue, setDefaultSelected) {
+ var options = node.options;
+
+ if (multiple) {
+ var selectedValues = propValue;
+ var selectedValue = {};
+ for (var i = 0; i < selectedValues.length; i++) {
+ // Prefix to avoid chaos with special keys.
+ selectedValue['$' + selectedValues[i]] = true;
}
- if (event.type === 'keydown' || event.type === 'keyup') {
- return event.keyCode;
+ for (var _i = 0; _i < options.length; _i++) {
+ var selected = selectedValue.hasOwnProperty('$' + options[_i].value);
+ if (options[_i].selected !== selected) {
+ options[_i].selected = selected;
+ }
+ if (selected && setDefaultSelected) {
+ options[_i].defaultSelected = true;
+ }
+ }
+ } else {
+ // Do not set `select.value` as exact behavior isn't consistent across all
+ // browsers for all cases.
+ var _selectedValue = toString(getToStringValue(propValue));
+ var defaultSelected = null;
+ for (var _i2 = 0; _i2 < options.length; _i2++) {
+ if (options[_i2].value === _selectedValue) {
+ options[_i2].selected = true;
+ if (setDefaultSelected) {
+ options[_i2].defaultSelected = true;
+ }
+ return;
+ }
+ if (defaultSelected === null && !options[_i2].disabled) {
+ defaultSelected = options[_i2];
+ }
+ }
+ if (defaultSelected !== null) {
+ defaultSelected.selected = true;
}
- return 0;
}
-};
-
-/**
- * @param {object} dispatchConfig Configuration used to dispatch this event.
- * @param {string} dispatchMarker Marker identifying the event target.
- * @param {object} nativeEvent Native browser event.
- * @extends {SyntheticUIEvent}
- */
-function SyntheticKeyboardEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
- return SyntheticUIEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
}
-SyntheticUIEvent.augmentClass(SyntheticKeyboardEvent, KeyboardEventInterface);
-
/**
- * @interface DragEvent
- * @see http://www.w3.org/TR/DOM-Level-3-Events/
+ * Implements a host component that allows optionally setting the
+ * props `value` and `defaultValue`. If `multiple` is false, the prop must be a
+ * stringable. If `multiple` is true, the prop must be an array of stringables.
+ *
+ * If `value` is not supplied (or null/undefined), user actions that change the
+ * selected option will trigger updates to the rendered options.
+ *
+ * If it is supplied (and not null/undefined), the rendered options will not
+ * update in response to user actions. Instead, the `value` prop must change in
+ * order for the rendered options to update.
+ *
+ * If `defaultValue` is provided, any options with the supplied values will be
+ * selected.
*/
-var DragEventInterface = {
- dataTransfer: null
-};
-/**
- * @param {object} dispatchConfig Configuration used to dispatch this event.
- * @param {string} dispatchMarker Marker identifying the event target.
- * @param {object} nativeEvent Native browser event.
- * @extends {SyntheticMouseEvent}
- */
-function SyntheticDragEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
- return SyntheticMouseEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
+function getHostProps$2(element, props) {
+ return _assign({}, props, {
+ value: undefined
+ });
}
-SyntheticMouseEvent.augmentClass(SyntheticDragEvent, DragEventInterface);
+function initWrapperState$1(element, props) {
+ var node = element;
+ {
+ checkSelectPropTypes(props);
+ }
-/**
- * @interface TouchEvent
- * @see http://www.w3.org/TR/touch-events/
- */
-var TouchEventInterface = {
- touches: null,
- targetTouches: null,
- changedTouches: null,
- altKey: null,
- metaKey: null,
- ctrlKey: null,
- shiftKey: null,
- getModifierState: getEventModifierState
-};
+ node._wrapperState = {
+ wasMultiple: !!props.multiple
+ };
-/**
- * @param {object} dispatchConfig Configuration used to dispatch this event.
- * @param {string} dispatchMarker Marker identifying the event target.
- * @param {object} nativeEvent Native browser event.
- * @extends {SyntheticUIEvent}
- */
-function SyntheticTouchEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
- return SyntheticUIEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
+ {
+ if (props.value !== undefined && props.defaultValue !== undefined && !didWarnValueDefaultValue$1) {
+ warning$1(false, 'Select elements must be either controlled or uncontrolled ' + '(specify either the value prop, or the defaultValue prop, but not ' + 'both). Decide between using a controlled or uncontrolled select ' + 'element and remove one of these props. More info: ' + 'https://fb.me/react-controlled-components');
+ didWarnValueDefaultValue$1 = true;
+ }
+ }
}
-SyntheticUIEvent.augmentClass(SyntheticTouchEvent, TouchEventInterface);
-
-/**
- * @interface Event
- * @see http://www.w3.org/TR/2009/WD-css3-transitions-20090320/#transition-events-
- * @see https://developer.mozilla.org/en-US/docs/Web/API/TransitionEvent
- */
-var TransitionEventInterface = {
- propertyName: null,
- elapsedTime: null,
- pseudoElement: null
-};
-
-/**
- * @param {object} dispatchConfig Configuration used to dispatch this event.
- * @param {string} dispatchMarker Marker identifying the event target.
- * @param {object} nativeEvent Native browser event.
- * @extends {SyntheticEvent}
- */
-function SyntheticTransitionEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
- return SyntheticEvent$1.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
+function postMountWrapper$2(element, props) {
+ var node = element;
+ node.multiple = !!props.multiple;
+ var value = props.value;
+ if (value != null) {
+ updateOptions(node, !!props.multiple, value, false);
+ } else if (props.defaultValue != null) {
+ updateOptions(node, !!props.multiple, props.defaultValue, true);
+ }
}
-SyntheticEvent$1.augmentClass(SyntheticTransitionEvent, TransitionEventInterface);
+function postUpdateWrapper(element, props) {
+ var node = element;
+ var wasMultiple = node._wrapperState.wasMultiple;
+ node._wrapperState.wasMultiple = !!props.multiple;
-/**
- * @interface WheelEvent
- * @see http://www.w3.org/TR/DOM-Level-3-Events/
- */
-var WheelEventInterface = {
- deltaX: function (event) {
- return 'deltaX' in event ? event.deltaX : // Fallback to `wheelDeltaX` for Webkit and normalize (right is positive).
- 'wheelDeltaX' in event ? -event.wheelDeltaX : 0;
- },
- deltaY: function (event) {
- return 'deltaY' in event ? event.deltaY : // Fallback to `wheelDeltaY` for Webkit and normalize (down is positive).
- 'wheelDeltaY' in event ? -event.wheelDeltaY : // Fallback to `wheelDelta` for IE<9 and normalize (down is positive).
- 'wheelDelta' in event ? -event.wheelDelta : 0;
- },
- deltaZ: null,
+ var value = props.value;
+ if (value != null) {
+ updateOptions(node, !!props.multiple, value, false);
+ } else if (wasMultiple !== !!props.multiple) {
+ // For simplicity, reapply `defaultValue` if `multiple` is toggled.
+ if (props.defaultValue != null) {
+ updateOptions(node, !!props.multiple, props.defaultValue, true);
+ } else {
+ // Revert the select back to its default unselected state.
+ updateOptions(node, !!props.multiple, props.multiple ? [] : '', false);
+ }
+ }
+}
- // Browsers without "deltaMode" is reporting in raw wheel delta where one
- // notch on the scroll is always +/- 120, roughly equivalent to pixels.
- // A good approximation of DOM_DELTA_LINE (1) is 5% of viewport size or
- // ~40 pixels, for DOM_DELTA_SCREEN (2) it is 87.5% of viewport size.
- deltaMode: null
-};
+function restoreControlledState$2(element, props) {
+ var node = element;
+ var value = props.value;
-/**
- * @param {object} dispatchConfig Configuration used to dispatch this event.
- * @param {string} dispatchMarker Marker identifying the event target.
- * @param {object} nativeEvent Native browser event.
- * @extends {SyntheticMouseEvent}
- */
-function SyntheticWheelEvent(dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget) {
- return SyntheticMouseEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
+ if (value != null) {
+ updateOptions(node, !!props.multiple, value, false);
+ }
}
-SyntheticMouseEvent.augmentClass(SyntheticWheelEvent, WheelEventInterface);
+var didWarnValDefaultVal = false;
/**
- * Turns
- * ['abort', ...]
- * into
- * eventTypes = {
- * 'abort': {
- * phasedRegistrationNames: {
- * bubbled: 'onAbort',
- * captured: 'onAbortCapture',
- * },
- * dependencies: ['topAbort'],
- * },
- * ...
- * };
- * topLevelEventsToDispatchConfig = {
- * 'topAbort': { sameConfig }
- * };
+ * Implements a