UNPKG

61.7 kBJavaScriptView Raw
1/** @license React v16.8.6
2 * react-dom-unstable-native-dependencies.development.js
3 *
4 * Copyright (c) Facebook, Inc. and its affiliates.
5 *
6 * This source code is licensed under the MIT license found in the
7 * LICENSE file in the root directory of this source tree.
8 */
9
10'use strict';
11
12
13
14if (process.env.NODE_ENV !== "production") {
15 (function() {
16'use strict';
17
18var ReactDOM = require('react-dom');
19var _assign = require('object-assign');
20
21/**
22 * Use invariant() to assert state which your program assumes to be true.
23 *
24 * Provide sprintf-style format (only %s is supported) and arguments
25 * to provide information about what broke and what you were
26 * expecting.
27 *
28 * The invariant message will be stripped in production, but the invariant
29 * will remain to ensure logic does not differ in production.
30 */
31
32var validateFormat = function () {};
33
34{
35 validateFormat = function (format) {
36 if (format === undefined) {
37 throw new Error('invariant requires an error message argument');
38 }
39 };
40}
41
42function invariant(condition, format, a, b, c, d, e, f) {
43 validateFormat(format);
44
45 if (!condition) {
46 var error = void 0;
47 if (format === undefined) {
48 error = new Error('Minified exception occurred; use the non-minified dev environment ' + 'for the full error message and additional helpful warnings.');
49 } else {
50 var args = [a, b, c, d, e, f];
51 var argIndex = 0;
52 error = new Error(format.replace(/%s/g, function () {
53 return args[argIndex++];
54 }));
55 error.name = 'Invariant Violation';
56 }
57
58 error.framesToPop = 1; // we don't care about invariant's own frame
59 throw error;
60 }
61}
62
63// Relying on the `invariant()` implementation lets us
64// preserve the format and params in the www builds.
65
66{
67 // In DEV mode, we swap out invokeGuardedCallback for a special version
68 // that plays more nicely with the browser's DevTools. The idea is to preserve
69 // "Pause on exceptions" behavior. Because React wraps all user-provided
70 // functions in invokeGuardedCallback, and the production version of
71 // invokeGuardedCallback uses a try-catch, all user exceptions are treated
72 // like caught exceptions, and the DevTools won't pause unless the developer
73 // takes the extra step of enabling pause on caught exceptions. This is
74 // unintuitive, though, because even though React has caught the error, from
75 // the developer's perspective, the error is uncaught.
76 //
77 // To preserve the expected "Pause on exceptions" behavior, we don't use a
78 // try-catch in DEV. Instead, we synchronously dispatch a fake event to a fake
79 // DOM node, and call the user-provided callback from inside an event handler
80 // for that fake event. If the callback throws, the error is "captured" using
81 // a global event handler. But because the error happens in a different
82 // event loop context, it does not interrupt the normal program flow.
83 // Effectively, this gives us try-catch behavior without actually using
84 // try-catch. Neat!
85
86 // Check that the browser supports the APIs we need to implement our special
87 // DEV version of invokeGuardedCallback
88 if (typeof window !== 'undefined' && typeof window.dispatchEvent === 'function' && typeof document !== 'undefined' && typeof document.createEvent === 'function') {
89 var fakeNode = document.createElement('react');
90
91
92 }
93}
94
95/**
96 * Call a function while guarding against errors that happens within it.
97 * Returns an error if it throws, otherwise null.
98 *
99 * In production, this is implemented using a try-catch. The reason we don't
100 * use a try-catch directly is so that we can swap out a different
101 * implementation in DEV mode.
102 *
103 * @param {String} name of the guard to use for logging or debugging
104 * @param {Function} func The function to invoke
105 * @param {*} context The context to use when calling the function
106 * @param {...*} args Arguments for function
107 */
108
109
110/**
111 * Same as invokeGuardedCallback, but instead of returning an error, it stores
112 * it in a global so it can be rethrown by `rethrowCaughtError` later.
113 * TODO: See if caughtError and rethrowError can be unified.
114 *
115 * @param {String} name of the guard to use for logging or debugging
116 * @param {Function} func The function to invoke
117 * @param {*} context The context to use when calling the function
118 * @param {...*} args Arguments for function
119 */
120
121
122/**
123 * During execution of guarded functions we will capture the first error which
124 * we will rethrow to be handled by the top level error handler.
125 */
126
127/**
128 * Similar to invariant but only logs a warning if the condition is not met.
129 * This can be used to log issues in development environments in critical
130 * paths. Removing the logging code for production environments will keep the
131 * same logic and follow the same code paths.
132 */
133
134var warningWithoutStack = function () {};
135
136{
137 warningWithoutStack = function (condition, format) {
138 for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
139 args[_key - 2] = arguments[_key];
140 }
141
142 if (format === undefined) {
143 throw new Error('`warningWithoutStack(condition, format, ...args)` requires a warning ' + 'message argument');
144 }
145 if (args.length > 8) {
146 // Check before the condition to catch violations early.
147 throw new Error('warningWithoutStack() currently supports at most 8 arguments.');
148 }
149 if (condition) {
150 return;
151 }
152 if (typeof console !== 'undefined') {
153 var argsWithFormat = args.map(function (item) {
154 return '' + item;
155 });
156 argsWithFormat.unshift('Warning: ' + format);
157
158 // We intentionally don't use spread (or .apply) directly because it
159 // breaks IE9: https://github.com/facebook/react/issues/13610
160 Function.prototype.apply.call(console.error, console, argsWithFormat);
161 }
162 try {
163 // --- Welcome to debugging React ---
164 // This error was thrown as a convenience so that you can use this stack
165 // to find the callsite that caused this warning to fire.
166 var argIndex = 0;
167 var message = 'Warning: ' + format.replace(/%s/g, function () {
168 return args[argIndex++];
169 });
170 throw new Error(message);
171 } catch (x) {}
172 };
173}
174
175var warningWithoutStack$1 = warningWithoutStack;
176
177var getFiberCurrentPropsFromNode$1 = null;
178var getInstanceFromNode$1 = null;
179var getNodeFromInstance$1 = null;
180
181function setComponentTree(getFiberCurrentPropsFromNodeImpl, getInstanceFromNodeImpl, getNodeFromInstanceImpl) {
182 getFiberCurrentPropsFromNode$1 = getFiberCurrentPropsFromNodeImpl;
183 getInstanceFromNode$1 = getInstanceFromNodeImpl;
184 getNodeFromInstance$1 = getNodeFromInstanceImpl;
185 {
186 !(getNodeFromInstance$1 && getInstanceFromNode$1) ? warningWithoutStack$1(false, 'EventPluginUtils.setComponentTree(...): Injected ' + 'module is missing getNodeFromInstance or getInstanceFromNode.') : void 0;
187 }
188}
189
190var validateEventDispatches = void 0;
191{
192 validateEventDispatches = function (event) {
193 var dispatchListeners = event._dispatchListeners;
194 var dispatchInstances = event._dispatchInstances;
195
196 var listenersIsArr = Array.isArray(dispatchListeners);
197 var listenersLen = listenersIsArr ? dispatchListeners.length : dispatchListeners ? 1 : 0;
198
199 var instancesIsArr = Array.isArray(dispatchInstances);
200 var instancesLen = instancesIsArr ? dispatchInstances.length : dispatchInstances ? 1 : 0;
201
202 !(instancesIsArr === listenersIsArr && instancesLen === listenersLen) ? warningWithoutStack$1(false, 'EventPluginUtils: Invalid `event`.') : void 0;
203 };
204}
205
206/**
207 * Standard/simple iteration through an event's collected dispatches.
208 */
209
210
211/**
212 * Standard/simple iteration through an event's collected dispatches, but stops
213 * at the first dispatch execution returning true, and returns that id.
214 *
215 * @return {?string} id of the first dispatch execution who's listener returns
216 * true, or null if no listener returned true.
217 */
218function executeDispatchesInOrderStopAtTrueImpl(event) {
219 var dispatchListeners = event._dispatchListeners;
220 var dispatchInstances = event._dispatchInstances;
221 {
222 validateEventDispatches(event);
223 }
224 if (Array.isArray(dispatchListeners)) {
225 for (var i = 0; i < dispatchListeners.length; i++) {
226 if (event.isPropagationStopped()) {
227 break;
228 }
229 // Listeners and Instances are two parallel arrays that are always in sync.
230 if (dispatchListeners[i](event, dispatchInstances[i])) {
231 return dispatchInstances[i];
232 }
233 }
234 } else if (dispatchListeners) {
235 if (dispatchListeners(event, dispatchInstances)) {
236 return dispatchInstances;
237 }
238 }
239 return null;
240}
241
242/**
243 * @see executeDispatchesInOrderStopAtTrueImpl
244 */
245function executeDispatchesInOrderStopAtTrue(event) {
246 var ret = executeDispatchesInOrderStopAtTrueImpl(event);
247 event._dispatchInstances = null;
248 event._dispatchListeners = null;
249 return ret;
250}
251
252/**
253 * Execution of a "direct" dispatch - there must be at most one dispatch
254 * accumulated on the event or it is considered an error. It doesn't really make
255 * sense for an event with multiple dispatches (bubbled) to keep track of the
256 * return values at each dispatch execution, but it does tend to make sense when
257 * dealing with "direct" dispatches.
258 *
259 * @return {*} The return value of executing the single dispatch.
260 */
261function executeDirectDispatch(event) {
262 {
263 validateEventDispatches(event);
264 }
265 var dispatchListener = event._dispatchListeners;
266 var dispatchInstance = event._dispatchInstances;
267 !!Array.isArray(dispatchListener) ? invariant(false, 'executeDirectDispatch(...): Invalid `event`.') : void 0;
268 event.currentTarget = dispatchListener ? getNodeFromInstance$1(dispatchInstance) : null;
269 var res = dispatchListener ? dispatchListener(event) : null;
270 event.currentTarget = null;
271 event._dispatchListeners = null;
272 event._dispatchInstances = null;
273 return res;
274}
275
276/**
277 * @param {SyntheticEvent} event
278 * @return {boolean} True iff number of dispatches accumulated is greater than 0.
279 */
280function hasDispatches(event) {
281 return !!event._dispatchListeners;
282}
283
284// Before we know whether it is function or class
285 // Root of a host tree. Could be nested inside another node.
286 // A subtree. Could be an entry point to a different renderer.
287var HostComponent = 5;
288
289function getParent(inst) {
290 do {
291 inst = inst.return;
292 // TODO: If this is a HostRoot we might want to bail out.
293 // That is depending on if we want nested subtrees (layers) to bubble
294 // events to their parent. We could also go through parentNode on the
295 // host node but that wouldn't work for React Native and doesn't let us
296 // do the portal feature.
297 } while (inst && inst.tag !== HostComponent);
298 if (inst) {
299 return inst;
300 }
301 return null;
302}
303
304/**
305 * Return the lowest common ancestor of A and B, or null if they are in
306 * different trees.
307 */
308function getLowestCommonAncestor(instA, instB) {
309 var depthA = 0;
310 for (var tempA = instA; tempA; tempA = getParent(tempA)) {
311 depthA++;
312 }
313 var depthB = 0;
314 for (var tempB = instB; tempB; tempB = getParent(tempB)) {
315 depthB++;
316 }
317
318 // If A is deeper, crawl up.
319 while (depthA - depthB > 0) {
320 instA = getParent(instA);
321 depthA--;
322 }
323
324 // If B is deeper, crawl up.
325 while (depthB - depthA > 0) {
326 instB = getParent(instB);
327 depthB--;
328 }
329
330 // Walk in lockstep until we find a match.
331 var depth = depthA;
332 while (depth--) {
333 if (instA === instB || instA === instB.alternate) {
334 return instA;
335 }
336 instA = getParent(instA);
337 instB = getParent(instB);
338 }
339 return null;
340}
341
342/**
343 * Return if A is an ancestor of B.
344 */
345function isAncestor(instA, instB) {
346 while (instB) {
347 if (instA === instB || instA === instB.alternate) {
348 return true;
349 }
350 instB = getParent(instB);
351 }
352 return false;
353}
354
355/**
356 * Return the parent instance of the passed-in instance.
357 */
358function getParentInstance(inst) {
359 return getParent(inst);
360}
361
362/**
363 * Simulates the traversal of a two-phase, capture/bubble event dispatch.
364 */
365function traverseTwoPhase(inst, fn, arg) {
366 var path = [];
367 while (inst) {
368 path.push(inst);
369 inst = getParent(inst);
370 }
371 var i = void 0;
372 for (i = path.length; i-- > 0;) {
373 fn(path[i], 'captured', arg);
374 }
375 for (i = 0; i < path.length; i++) {
376 fn(path[i], 'bubbled', arg);
377 }
378}
379
380/**
381 * Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that
382 * should would receive a `mouseEnter` or `mouseLeave` event.
383 *
384 * Does not invoke the callback on the nearest common ancestor because nothing
385 * "entered" or "left" that element.
386 */
387
388/**
389 * Registers plugins so that they can extract and dispatch events.
390 *
391 * @see {EventPluginHub}
392 */
393
394/**
395 * Ordered list of injected plugins.
396 */
397
398
399/**
400 * Mapping from event name to dispatch config
401 */
402
403
404/**
405 * Mapping from registration name to plugin module
406 */
407
408
409/**
410 * Mapping from registration name to event name
411 */
412
413
414/**
415 * Mapping from lowercase registration names to the properly cased version,
416 * used to warn in the case of missing event handlers. Available
417 * only in true.
418 * @type {Object}
419 */
420
421// Trust the developer to only use possibleRegistrationNames in true
422
423/**
424 * Injects an ordering of plugins (by plugin name). This allows the ordering
425 * to be decoupled from injection of the actual plugins so that ordering is
426 * always deterministic regardless of packaging, on-the-fly injection, etc.
427 *
428 * @param {array} InjectedEventPluginOrder
429 * @internal
430 * @see {EventPluginHub.injection.injectEventPluginOrder}
431 */
432
433
434/**
435 * Injects plugins to be used by `EventPluginHub`. The plugin names must be
436 * in the ordering injected by `injectEventPluginOrder`.
437 *
438 * Plugins can be injected as part of page initialization or on-the-fly.
439 *
440 * @param {object} injectedNamesToPlugins Map from names to plugin modules.
441 * @internal
442 * @see {EventPluginHub.injection.injectEventPluginsByName}
443 */
444
445/**
446 * Accumulates items that must not be null or undefined into the first one. This
447 * is used to conserve memory by avoiding array allocations, and thus sacrifices
448 * API cleanness. Since `current` can be null before being passed in and not
449 * null after this function, make sure to assign it back to `current`:
450 *
451 * `a = accumulateInto(a, b);`
452 *
453 * This API should be sparingly used. Try `accumulate` for something cleaner.
454 *
455 * @return {*|array<*>} An accumulation of items.
456 */
457
458function accumulateInto(current, next) {
459 !(next != null) ? invariant(false, 'accumulateInto(...): Accumulated items must not be null or undefined.') : void 0;
460
461 if (current == null) {
462 return next;
463 }
464
465 // Both are not empty. Warning: Never call x.concat(y) when you are not
466 // certain that x is an Array (x could be a string with concat method).
467 if (Array.isArray(current)) {
468 if (Array.isArray(next)) {
469 current.push.apply(current, next);
470 return current;
471 }
472 current.push(next);
473 return current;
474 }
475
476 if (Array.isArray(next)) {
477 // A bit too dangerous to mutate `next`.
478 return [current].concat(next);
479 }
480
481 return [current, next];
482}
483
484/**
485 * @param {array} arr an "accumulation" of items which is either an Array or
486 * a single item. Useful when paired with the `accumulate` module. This is a
487 * simple utility that allows us to reason about a collection of items, but
488 * handling the case when there is exactly one item (and we do not need to
489 * allocate an array).
490 * @param {function} cb Callback invoked with each element or a collection.
491 * @param {?} [scope] Scope used as `this` in a callback.
492 */
493function forEachAccumulated(arr, cb, scope) {
494 if (Array.isArray(arr)) {
495 arr.forEach(cb, scope);
496 } else if (arr) {
497 cb.call(scope, arr);
498 }
499}
500
501function isInteractive(tag) {
502 return tag === 'button' || tag === 'input' || tag === 'select' || tag === 'textarea';
503}
504
505function shouldPreventMouseEvent(name, type, props) {
506 switch (name) {
507 case 'onClick':
508 case 'onClickCapture':
509 case 'onDoubleClick':
510 case 'onDoubleClickCapture':
511 case 'onMouseDown':
512 case 'onMouseDownCapture':
513 case 'onMouseMove':
514 case 'onMouseMoveCapture':
515 case 'onMouseUp':
516 case 'onMouseUpCapture':
517 return !!(props.disabled && isInteractive(type));
518 default:
519 return false;
520 }
521}
522
523/**
524 * This is a unified interface for event plugins to be installed and configured.
525 *
526 * Event plugins can implement the following properties:
527 *
528 * `extractEvents` {function(string, DOMEventTarget, string, object): *}
529 * Required. When a top-level event is fired, this method is expected to
530 * extract synthetic events that will in turn be queued and dispatched.
531 *
532 * `eventTypes` {object}
533 * Optional, plugins that fire events must publish a mapping of registration
534 * names that are used to register listeners. Values of this mapping must
535 * be objects that contain `registrationName` or `phasedRegistrationNames`.
536 *
537 * `executeDispatch` {function(object, function, string)}
538 * Optional, allows plugins to override how an event gets dispatched. By
539 * default, the listener is simply invoked.
540 *
541 * Each plugin that is injected into `EventsPluginHub` is immediately operable.
542 *
543 * @public
544 */
545
546/**
547 * Methods for injecting dependencies.
548 */
549
550
551/**
552 * @param {object} inst The instance, which is the source of events.
553 * @param {string} registrationName Name of listener (e.g. `onClick`).
554 * @return {?function} The stored callback.
555 */
556function getListener(inst, registrationName) {
557 var listener = void 0;
558
559 // TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
560 // live here; needs to be moved to a better place soon
561 var stateNode = inst.stateNode;
562 if (!stateNode) {
563 // Work in progress (ex: onload events in incremental mode).
564 return null;
565 }
566 var props = getFiberCurrentPropsFromNode$1(stateNode);
567 if (!props) {
568 // Work in progress.
569 return null;
570 }
571 listener = props[registrationName];
572 if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
573 return null;
574 }
575 !(!listener || typeof listener === 'function') ? invariant(false, 'Expected `%s` listener to be a function, instead got a value of `%s` type.', registrationName, typeof listener) : void 0;
576 return listener;
577}
578
579/**
580 * Some event types have a notion of different registration names for different
581 * "phases" of propagation. This finds listeners by a given phase.
582 */
583function listenerAtPhase(inst, event, propagationPhase) {
584 var registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase];
585 return getListener(inst, registrationName);
586}
587
588/**
589 * A small set of propagation patterns, each of which will accept a small amount
590 * of information, and generate a set of "dispatch ready event objects" - which
591 * are sets of events that have already been annotated with a set of dispatched
592 * listener functions/ids. The API is designed this way to discourage these
593 * propagation strategies from actually executing the dispatches, since we
594 * always want to collect the entire set of dispatches before executing even a
595 * single one.
596 */
597
598/**
599 * Tags a `SyntheticEvent` with dispatched listeners. Creating this function
600 * here, allows us to not have to bind or create functions for each event.
601 * Mutating the event's members allows us to not have to create a wrapping
602 * "dispatch" object that pairs the event with the listener.
603 */
604function accumulateDirectionalDispatches(inst, phase, event) {
605 {
606 !inst ? warningWithoutStack$1(false, 'Dispatching inst must not be null') : void 0;
607 }
608 var listener = listenerAtPhase(inst, event, phase);
609 if (listener) {
610 event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);
611 event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
612 }
613}
614
615/**
616 * Collect dispatches (must be entirely collected before dispatching - see unit
617 * tests). Lazily allocate the array to conserve memory. We must loop through
618 * each event and perform the traversal for each one. We cannot perform a
619 * single traversal for the entire collection of events because each event may
620 * have a different target.
621 */
622function accumulateTwoPhaseDispatchesSingle(event) {
623 if (event && event.dispatchConfig.phasedRegistrationNames) {
624 traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
625 }
626}
627
628/**
629 * Same as `accumulateTwoPhaseDispatchesSingle`, but skips over the targetID.
630 */
631function accumulateTwoPhaseDispatchesSingleSkipTarget(event) {
632 if (event && event.dispatchConfig.phasedRegistrationNames) {
633 var targetInst = event._targetInst;
634 var parentInst = targetInst ? getParentInstance(targetInst) : null;
635 traverseTwoPhase(parentInst, accumulateDirectionalDispatches, event);
636 }
637}
638
639/**
640 * Accumulates without regard to direction, does not look for phased
641 * registration names. Same as `accumulateDirectDispatchesSingle` but without
642 * requiring that the `dispatchMarker` be the same as the dispatched ID.
643 */
644function accumulateDispatches(inst, ignoredDirection, event) {
645 if (inst && event && event.dispatchConfig.registrationName) {
646 var registrationName = event.dispatchConfig.registrationName;
647 var listener = getListener(inst, registrationName);
648 if (listener) {
649 event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);
650 event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
651 }
652 }
653}
654
655/**
656 * Accumulates dispatches on an `SyntheticEvent`, but only for the
657 * `dispatchMarker`.
658 * @param {SyntheticEvent} event
659 */
660function accumulateDirectDispatchesSingle(event) {
661 if (event && event.dispatchConfig.registrationName) {
662 accumulateDispatches(event._targetInst, null, event);
663 }
664}
665
666function accumulateTwoPhaseDispatches(events) {
667 forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
668}
669
670function accumulateTwoPhaseDispatchesSkipTarget(events) {
671 forEachAccumulated(events, accumulateTwoPhaseDispatchesSingleSkipTarget);
672}
673
674
675
676function accumulateDirectDispatches(events) {
677 forEachAccumulated(events, accumulateDirectDispatchesSingle);
678}
679
680/* eslint valid-typeof: 0 */
681
682var EVENT_POOL_SIZE = 10;
683
684/**
685 * @interface Event
686 * @see http://www.w3.org/TR/DOM-Level-3-Events/
687 */
688var EventInterface = {
689 type: null,
690 target: null,
691 // currentTarget is set when dispatching; no use in copying it here
692 currentTarget: function () {
693 return null;
694 },
695 eventPhase: null,
696 bubbles: null,
697 cancelable: null,
698 timeStamp: function (event) {
699 return event.timeStamp || Date.now();
700 },
701 defaultPrevented: null,
702 isTrusted: null
703};
704
705function functionThatReturnsTrue() {
706 return true;
707}
708
709function functionThatReturnsFalse() {
710 return false;
711}
712
713/**
714 * Synthetic events are dispatched by event plugins, typically in response to a
715 * top-level event delegation handler.
716 *
717 * These systems should generally use pooling to reduce the frequency of garbage
718 * collection. The system should check `isPersistent` to determine whether the
719 * event should be released into the pool after being dispatched. Users that
720 * need a persisted event should invoke `persist`.
721 *
722 * Synthetic events (and subclasses) implement the DOM Level 3 Events API by
723 * normalizing browser quirks. Subclasses do not necessarily have to implement a
724 * DOM interface; custom application-specific events can also subclass this.
725 *
726 * @param {object} dispatchConfig Configuration used to dispatch this event.
727 * @param {*} targetInst Marker identifying the event target.
728 * @param {object} nativeEvent Native browser event.
729 * @param {DOMEventTarget} nativeEventTarget Target node.
730 */
731function SyntheticEvent(dispatchConfig, targetInst, nativeEvent, nativeEventTarget) {
732 {
733 // these have a getter/setter for warnings
734 delete this.nativeEvent;
735 delete this.preventDefault;
736 delete this.stopPropagation;
737 delete this.isDefaultPrevented;
738 delete this.isPropagationStopped;
739 }
740
741 this.dispatchConfig = dispatchConfig;
742 this._targetInst = targetInst;
743 this.nativeEvent = nativeEvent;
744
745 var Interface = this.constructor.Interface;
746 for (var propName in Interface) {
747 if (!Interface.hasOwnProperty(propName)) {
748 continue;
749 }
750 {
751 delete this[propName]; // this has a getter/setter for warnings
752 }
753 var normalize = Interface[propName];
754 if (normalize) {
755 this[propName] = normalize(nativeEvent);
756 } else {
757 if (propName === 'target') {
758 this.target = nativeEventTarget;
759 } else {
760 this[propName] = nativeEvent[propName];
761 }
762 }
763 }
764
765 var defaultPrevented = nativeEvent.defaultPrevented != null ? nativeEvent.defaultPrevented : nativeEvent.returnValue === false;
766 if (defaultPrevented) {
767 this.isDefaultPrevented = functionThatReturnsTrue;
768 } else {
769 this.isDefaultPrevented = functionThatReturnsFalse;
770 }
771 this.isPropagationStopped = functionThatReturnsFalse;
772 return this;
773}
774
775_assign(SyntheticEvent.prototype, {
776 preventDefault: function () {
777 this.defaultPrevented = true;
778 var event = this.nativeEvent;
779 if (!event) {
780 return;
781 }
782
783 if (event.preventDefault) {
784 event.preventDefault();
785 } else if (typeof event.returnValue !== 'unknown') {
786 event.returnValue = false;
787 }
788 this.isDefaultPrevented = functionThatReturnsTrue;
789 },
790
791 stopPropagation: function () {
792 var event = this.nativeEvent;
793 if (!event) {
794 return;
795 }
796
797 if (event.stopPropagation) {
798 event.stopPropagation();
799 } else if (typeof event.cancelBubble !== 'unknown') {
800 // The ChangeEventPlugin registers a "propertychange" event for
801 // IE. This event does not support bubbling or cancelling, and
802 // any references to cancelBubble throw "Member not found". A
803 // typeof check of "unknown" circumvents this issue (and is also
804 // IE specific).
805 event.cancelBubble = true;
806 }
807
808 this.isPropagationStopped = functionThatReturnsTrue;
809 },
810
811 /**
812 * We release all dispatched `SyntheticEvent`s after each event loop, adding
813 * them back into the pool. This allows a way to hold onto a reference that
814 * won't be added back into the pool.
815 */
816 persist: function () {
817 this.isPersistent = functionThatReturnsTrue;
818 },
819
820 /**
821 * Checks if this event should be released back into the pool.
822 *
823 * @return {boolean} True if this should not be released, false otherwise.
824 */
825 isPersistent: functionThatReturnsFalse,
826
827 /**
828 * `PooledClass` looks for `destructor` on each instance it releases.
829 */
830 destructor: function () {
831 var Interface = this.constructor.Interface;
832 for (var propName in Interface) {
833 {
834 Object.defineProperty(this, propName, getPooledWarningPropertyDefinition(propName, Interface[propName]));
835 }
836 }
837 this.dispatchConfig = null;
838 this._targetInst = null;
839 this.nativeEvent = null;
840 this.isDefaultPrevented = functionThatReturnsFalse;
841 this.isPropagationStopped = functionThatReturnsFalse;
842 this._dispatchListeners = null;
843 this._dispatchInstances = null;
844 {
845 Object.defineProperty(this, 'nativeEvent', getPooledWarningPropertyDefinition('nativeEvent', null));
846 Object.defineProperty(this, 'isDefaultPrevented', getPooledWarningPropertyDefinition('isDefaultPrevented', functionThatReturnsFalse));
847 Object.defineProperty(this, 'isPropagationStopped', getPooledWarningPropertyDefinition('isPropagationStopped', functionThatReturnsFalse));
848 Object.defineProperty(this, 'preventDefault', getPooledWarningPropertyDefinition('preventDefault', function () {}));
849 Object.defineProperty(this, 'stopPropagation', getPooledWarningPropertyDefinition('stopPropagation', function () {}));
850 }
851 }
852});
853
854SyntheticEvent.Interface = EventInterface;
855
856/**
857 * Helper to reduce boilerplate when creating subclasses.
858 */
859SyntheticEvent.extend = function (Interface) {
860 var Super = this;
861
862 var E = function () {};
863 E.prototype = Super.prototype;
864 var prototype = new E();
865
866 function Class() {
867 return Super.apply(this, arguments);
868 }
869 _assign(prototype, Class.prototype);
870 Class.prototype = prototype;
871 Class.prototype.constructor = Class;
872
873 Class.Interface = _assign({}, Super.Interface, Interface);
874 Class.extend = Super.extend;
875 addEventPoolingTo(Class);
876
877 return Class;
878};
879
880addEventPoolingTo(SyntheticEvent);
881
882/**
883 * Helper to nullify syntheticEvent instance properties when destructing
884 *
885 * @param {String} propName
886 * @param {?object} getVal
887 * @return {object} defineProperty object
888 */
889function getPooledWarningPropertyDefinition(propName, getVal) {
890 var isFunction = typeof getVal === 'function';
891 return {
892 configurable: true,
893 set: set,
894 get: get
895 };
896
897 function set(val) {
898 var action = isFunction ? 'setting the method' : 'setting the property';
899 warn(action, 'This is effectively a no-op');
900 return val;
901 }
902
903 function get() {
904 var action = isFunction ? 'accessing the method' : 'accessing the property';
905 var result = isFunction ? 'This is a no-op function' : 'This is set to null';
906 warn(action, result);
907 return getVal;
908 }
909
910 function warn(action, result) {
911 var warningCondition = false;
912 !warningCondition ? warningWithoutStack$1(false, "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) : void 0;
913 }
914}
915
916function getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) {
917 var EventConstructor = this;
918 if (EventConstructor.eventPool.length) {
919 var instance = EventConstructor.eventPool.pop();
920 EventConstructor.call(instance, dispatchConfig, targetInst, nativeEvent, nativeInst);
921 return instance;
922 }
923 return new EventConstructor(dispatchConfig, targetInst, nativeEvent, nativeInst);
924}
925
926function releasePooledEvent(event) {
927 var EventConstructor = this;
928 !(event instanceof EventConstructor) ? invariant(false, 'Trying to release an event instance into a pool of a different type.') : void 0;
929 event.destructor();
930 if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {
931 EventConstructor.eventPool.push(event);
932 }
933}
934
935function addEventPoolingTo(EventConstructor) {
936 EventConstructor.eventPool = [];
937 EventConstructor.getPooled = getPooledEvent;
938 EventConstructor.release = releasePooledEvent;
939}
940
941/**
942 * `touchHistory` isn't actually on the native event, but putting it in the
943 * interface will ensure that it is cleaned up when pooled/destroyed. The
944 * `ResponderEventPlugin` will populate it appropriately.
945 */
946var ResponderSyntheticEvent = SyntheticEvent.extend({
947 touchHistory: function (nativeEvent) {
948 return null; // Actually doesn't even look at the native event.
949 }
950});
951
952// Note: ideally these would be imported from DOMTopLevelEventTypes,
953// but our build system currently doesn't let us do that from a fork.
954
955var TOP_TOUCH_START = 'touchstart';
956var TOP_TOUCH_MOVE = 'touchmove';
957var TOP_TOUCH_END = 'touchend';
958var TOP_TOUCH_CANCEL = 'touchcancel';
959var TOP_SCROLL = 'scroll';
960var TOP_SELECTION_CHANGE = 'selectionchange';
961var TOP_MOUSE_DOWN = 'mousedown';
962var TOP_MOUSE_MOVE = 'mousemove';
963var TOP_MOUSE_UP = 'mouseup';
964
965function isStartish(topLevelType) {
966 return topLevelType === TOP_TOUCH_START || topLevelType === TOP_MOUSE_DOWN;
967}
968
969function isMoveish(topLevelType) {
970 return topLevelType === TOP_TOUCH_MOVE || topLevelType === TOP_MOUSE_MOVE;
971}
972
973function isEndish(topLevelType) {
974 return topLevelType === TOP_TOUCH_END || topLevelType === TOP_TOUCH_CANCEL || topLevelType === TOP_MOUSE_UP;
975}
976
977var startDependencies = [TOP_TOUCH_START, TOP_MOUSE_DOWN];
978var moveDependencies = [TOP_TOUCH_MOVE, TOP_MOUSE_MOVE];
979var endDependencies = [TOP_TOUCH_CANCEL, TOP_TOUCH_END, TOP_MOUSE_UP];
980
981/**
982 * Tracks the position and time of each active touch by `touch.identifier`. We
983 * should typically only see IDs in the range of 1-20 because IDs get recycled
984 * when touches end and start again.
985 */
986
987
988var MAX_TOUCH_BANK = 20;
989var touchBank = [];
990var touchHistory = {
991 touchBank: touchBank,
992 numberActiveTouches: 0,
993 // If there is only one active touch, we remember its location. This prevents
994 // us having to loop through all of the touches all the time in the most
995 // common case.
996 indexOfSingleActiveTouch: -1,
997 mostRecentTimeStamp: 0
998};
999
1000function timestampForTouch(touch) {
1001 // The legacy internal implementation provides "timeStamp", which has been
1002 // renamed to "timestamp". Let both work for now while we iron it out
1003 // TODO (evv): rename timeStamp to timestamp in internal code
1004 return touch.timeStamp || touch.timestamp;
1005}
1006
1007/**
1008 * TODO: Instead of making gestures recompute filtered velocity, we could
1009 * include a built in velocity computation that can be reused globally.
1010 */
1011function createTouchRecord(touch) {
1012 return {
1013 touchActive: true,
1014 startPageX: touch.pageX,
1015 startPageY: touch.pageY,
1016 startTimeStamp: timestampForTouch(touch),
1017 currentPageX: touch.pageX,
1018 currentPageY: touch.pageY,
1019 currentTimeStamp: timestampForTouch(touch),
1020 previousPageX: touch.pageX,
1021 previousPageY: touch.pageY,
1022 previousTimeStamp: timestampForTouch(touch)
1023 };
1024}
1025
1026function resetTouchRecord(touchRecord, touch) {
1027 touchRecord.touchActive = true;
1028 touchRecord.startPageX = touch.pageX;
1029 touchRecord.startPageY = touch.pageY;
1030 touchRecord.startTimeStamp = timestampForTouch(touch);
1031 touchRecord.currentPageX = touch.pageX;
1032 touchRecord.currentPageY = touch.pageY;
1033 touchRecord.currentTimeStamp = timestampForTouch(touch);
1034 touchRecord.previousPageX = touch.pageX;
1035 touchRecord.previousPageY = touch.pageY;
1036 touchRecord.previousTimeStamp = timestampForTouch(touch);
1037}
1038
1039function getTouchIdentifier(_ref) {
1040 var identifier = _ref.identifier;
1041
1042 !(identifier != null) ? invariant(false, 'Touch object is missing identifier.') : void 0;
1043 {
1044 !(identifier <= MAX_TOUCH_BANK) ? warningWithoutStack$1(false, 'Touch identifier %s is greater than maximum supported %s which causes ' + 'performance issues backfilling array locations for all of the indices.', identifier, MAX_TOUCH_BANK) : void 0;
1045 }
1046 return identifier;
1047}
1048
1049function recordTouchStart(touch) {
1050 var identifier = getTouchIdentifier(touch);
1051 var touchRecord = touchBank[identifier];
1052 if (touchRecord) {
1053 resetTouchRecord(touchRecord, touch);
1054 } else {
1055 touchBank[identifier] = createTouchRecord(touch);
1056 }
1057 touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
1058}
1059
1060function recordTouchMove(touch) {
1061 var touchRecord = touchBank[getTouchIdentifier(touch)];
1062 if (touchRecord) {
1063 touchRecord.touchActive = true;
1064 touchRecord.previousPageX = touchRecord.currentPageX;
1065 touchRecord.previousPageY = touchRecord.currentPageY;
1066 touchRecord.previousTimeStamp = touchRecord.currentTimeStamp;
1067 touchRecord.currentPageX = touch.pageX;
1068 touchRecord.currentPageY = touch.pageY;
1069 touchRecord.currentTimeStamp = timestampForTouch(touch);
1070 touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
1071 } else {
1072 console.error('Cannot record touch move without a touch start.\n' + 'Touch Move: %s\n', 'Touch Bank: %s', printTouch(touch), printTouchBank());
1073 }
1074}
1075
1076function recordTouchEnd(touch) {
1077 var touchRecord = touchBank[getTouchIdentifier(touch)];
1078 if (touchRecord) {
1079 touchRecord.touchActive = false;
1080 touchRecord.previousPageX = touchRecord.currentPageX;
1081 touchRecord.previousPageY = touchRecord.currentPageY;
1082 touchRecord.previousTimeStamp = touchRecord.currentTimeStamp;
1083 touchRecord.currentPageX = touch.pageX;
1084 touchRecord.currentPageY = touch.pageY;
1085 touchRecord.currentTimeStamp = timestampForTouch(touch);
1086 touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
1087 } else {
1088 console.error('Cannot record touch end without a touch start.\n' + 'Touch End: %s\n', 'Touch Bank: %s', printTouch(touch), printTouchBank());
1089 }
1090}
1091
1092function printTouch(touch) {
1093 return JSON.stringify({
1094 identifier: touch.identifier,
1095 pageX: touch.pageX,
1096 pageY: touch.pageY,
1097 timestamp: timestampForTouch(touch)
1098 });
1099}
1100
1101function printTouchBank() {
1102 var printed = JSON.stringify(touchBank.slice(0, MAX_TOUCH_BANK));
1103 if (touchBank.length > MAX_TOUCH_BANK) {
1104 printed += ' (original size: ' + touchBank.length + ')';
1105 }
1106 return printed;
1107}
1108
1109var ResponderTouchHistoryStore = {
1110 recordTouchTrack: function (topLevelType, nativeEvent) {
1111 if (isMoveish(topLevelType)) {
1112 nativeEvent.changedTouches.forEach(recordTouchMove);
1113 } else if (isStartish(topLevelType)) {
1114 nativeEvent.changedTouches.forEach(recordTouchStart);
1115 touchHistory.numberActiveTouches = nativeEvent.touches.length;
1116 if (touchHistory.numberActiveTouches === 1) {
1117 touchHistory.indexOfSingleActiveTouch = nativeEvent.touches[0].identifier;
1118 }
1119 } else if (isEndish(topLevelType)) {
1120 nativeEvent.changedTouches.forEach(recordTouchEnd);
1121 touchHistory.numberActiveTouches = nativeEvent.touches.length;
1122 if (touchHistory.numberActiveTouches === 1) {
1123 for (var i = 0; i < touchBank.length; i++) {
1124 var touchTrackToCheck = touchBank[i];
1125 if (touchTrackToCheck != null && touchTrackToCheck.touchActive) {
1126 touchHistory.indexOfSingleActiveTouch = i;
1127 break;
1128 }
1129 }
1130 {
1131 var activeRecord = touchBank[touchHistory.indexOfSingleActiveTouch];
1132 !(activeRecord != null && activeRecord.touchActive) ? warningWithoutStack$1(false, 'Cannot find single active touch.') : void 0;
1133 }
1134 }
1135 }
1136 },
1137
1138
1139 touchHistory: touchHistory
1140};
1141
1142/**
1143 * Accumulates items that must not be null or undefined.
1144 *
1145 * This is used to conserve memory by avoiding array allocations.
1146 *
1147 * @return {*|array<*>} An accumulation of items.
1148 */
1149function accumulate(current, next) {
1150 !(next != null) ? invariant(false, 'accumulate(...): Accumulated items must be not be null or undefined.') : void 0;
1151
1152 if (current == null) {
1153 return next;
1154 }
1155
1156 // Both are not empty. Warning: Never call x.concat(y) when you are not
1157 // certain that x is an Array (x could be a string with concat method).
1158 if (Array.isArray(current)) {
1159 return current.concat(next);
1160 }
1161
1162 if (Array.isArray(next)) {
1163 return [current].concat(next);
1164 }
1165
1166 return [current, next];
1167}
1168
1169/**
1170 * Instance of element that should respond to touch/move types of interactions,
1171 * as indicated explicitly by relevant callbacks.
1172 */
1173var responderInst = null;
1174
1175/**
1176 * Count of current touches. A textInput should become responder iff the
1177 * selection changes while there is a touch on the screen.
1178 */
1179var trackedTouchCount = 0;
1180
1181var changeResponder = function (nextResponderInst, blockHostResponder) {
1182 var oldResponderInst = responderInst;
1183 responderInst = nextResponderInst;
1184 if (ResponderEventPlugin.GlobalResponderHandler !== null) {
1185 ResponderEventPlugin.GlobalResponderHandler.onChange(oldResponderInst, nextResponderInst, blockHostResponder);
1186 }
1187};
1188
1189var eventTypes = {
1190 /**
1191 * On a `touchStart`/`mouseDown`, is it desired that this element become the
1192 * responder?
1193 */
1194 startShouldSetResponder: {
1195 phasedRegistrationNames: {
1196 bubbled: 'onStartShouldSetResponder',
1197 captured: 'onStartShouldSetResponderCapture'
1198 },
1199 dependencies: startDependencies
1200 },
1201
1202 /**
1203 * On a `scroll`, is it desired that this element become the responder? This
1204 * is usually not needed, but should be used to retroactively infer that a
1205 * `touchStart` had occurred during momentum scroll. During a momentum scroll,
1206 * a touch start will be immediately followed by a scroll event if the view is
1207 * currently scrolling.
1208 *
1209 * TODO: This shouldn't bubble.
1210 */
1211 scrollShouldSetResponder: {
1212 phasedRegistrationNames: {
1213 bubbled: 'onScrollShouldSetResponder',
1214 captured: 'onScrollShouldSetResponderCapture'
1215 },
1216 dependencies: [TOP_SCROLL]
1217 },
1218
1219 /**
1220 * On text selection change, should this element become the responder? This
1221 * is needed for text inputs or other views with native selection, so the
1222 * JS view can claim the responder.
1223 *
1224 * TODO: This shouldn't bubble.
1225 */
1226 selectionChangeShouldSetResponder: {
1227 phasedRegistrationNames: {
1228 bubbled: 'onSelectionChangeShouldSetResponder',
1229 captured: 'onSelectionChangeShouldSetResponderCapture'
1230 },
1231 dependencies: [TOP_SELECTION_CHANGE]
1232 },
1233
1234 /**
1235 * On a `touchMove`/`mouseMove`, is it desired that this element become the
1236 * responder?
1237 */
1238 moveShouldSetResponder: {
1239 phasedRegistrationNames: {
1240 bubbled: 'onMoveShouldSetResponder',
1241 captured: 'onMoveShouldSetResponderCapture'
1242 },
1243 dependencies: moveDependencies
1244 },
1245
1246 /**
1247 * Direct responder events dispatched directly to responder. Do not bubble.
1248 */
1249 responderStart: {
1250 registrationName: 'onResponderStart',
1251 dependencies: startDependencies
1252 },
1253 responderMove: {
1254 registrationName: 'onResponderMove',
1255 dependencies: moveDependencies
1256 },
1257 responderEnd: {
1258 registrationName: 'onResponderEnd',
1259 dependencies: endDependencies
1260 },
1261 responderRelease: {
1262 registrationName: 'onResponderRelease',
1263 dependencies: endDependencies
1264 },
1265 responderTerminationRequest: {
1266 registrationName: 'onResponderTerminationRequest',
1267 dependencies: []
1268 },
1269 responderGrant: {
1270 registrationName: 'onResponderGrant',
1271 dependencies: []
1272 },
1273 responderReject: {
1274 registrationName: 'onResponderReject',
1275 dependencies: []
1276 },
1277 responderTerminate: {
1278 registrationName: 'onResponderTerminate',
1279 dependencies: []
1280 }
1281};
1282
1283/**
1284 *
1285 * Responder System:
1286 * ----------------
1287 *
1288 * - A global, solitary "interaction lock" on a view.
1289 * - If a node becomes the responder, it should convey visual feedback
1290 * immediately to indicate so, either by highlighting or moving accordingly.
1291 * - To be the responder means, that touches are exclusively important to that
1292 * responder view, and no other view.
1293 * - While touches are still occurring, the responder lock can be transferred to
1294 * a new view, but only to increasingly "higher" views (meaning ancestors of
1295 * the current responder).
1296 *
1297 * Responder being granted:
1298 * ------------------------
1299 *
1300 * - Touch starts, moves, and scrolls can cause an ID to become the responder.
1301 * - We capture/bubble `startShouldSetResponder`/`moveShouldSetResponder` to
1302 * the "appropriate place".
1303 * - If nothing is currently the responder, the "appropriate place" is the
1304 * initiating event's `targetID`.
1305 * - If something *is* already the responder, the "appropriate place" is the
1306 * first common ancestor of the event target and the current `responderInst`.
1307 * - Some negotiation happens: See the timing diagram below.
1308 * - Scrolled views automatically become responder. The reasoning is that a
1309 * platform scroll view that isn't built on top of the responder system has
1310 * began scrolling, and the active responder must now be notified that the
1311 * interaction is no longer locked to it - the system has taken over.
1312 *
1313 * - Responder being released:
1314 * As soon as no more touches that *started* inside of descendants of the
1315 * *current* responderInst, an `onResponderRelease` event is dispatched to the
1316 * current responder, and the responder lock is released.
1317 *
1318 * TODO:
1319 * - on "end", a callback hook for `onResponderEndShouldRemainResponder` that
1320 * determines if the responder lock should remain.
1321 * - If a view shouldn't "remain" the responder, any active touches should by
1322 * default be considered "dead" and do not influence future negotiations or
1323 * bubble paths. It should be as if those touches do not exist.
1324 * -- For multitouch: Usually a translate-z will choose to "remain" responder
1325 * after one out of many touches ended. For translate-y, usually the view
1326 * doesn't wish to "remain" responder after one of many touches end.
1327 * - Consider building this on top of a `stopPropagation` model similar to
1328 * `W3C` events.
1329 * - Ensure that `onResponderTerminate` is called on touch cancels, whether or
1330 * not `onResponderTerminationRequest` returns `true` or `false`.
1331 *
1332 */
1333
1334/* Negotiation Performed
1335 +-----------------------+
1336 / \
1337Process low level events to + Current Responder + wantsResponderID
1338determine who to perform negot-| (if any exists at all) |
1339iation/transition | Otherwise just pass through|
1340-------------------------------+----------------------------+------------------+
1341Bubble to find first ID | |
1342to return true:wantsResponderID| |
1343 | |
1344 +-------------+ | |
1345 | onTouchStart| | |
1346 +------+------+ none | |
1347 | return| |
1348+-----------v-------------+true| +------------------------+ |
1349|onStartShouldSetResponder|----->|onResponderStart (cur) |<-----------+
1350+-----------+-------------+ | +------------------------+ | |
1351 | | | +--------+-------+
1352 | returned true for| false:REJECT +-------->|onResponderReject
1353 | wantsResponderID | | | +----------------+
1354 | (now attempt | +------------------+-----+ |
1355 | handoff) | | onResponder | |
1356 +------------------->| TerminationRequest| |
1357 | +------------------+-----+ |
1358 | | | +----------------+
1359 | true:GRANT +-------->|onResponderGrant|
1360 | | +--------+-------+
1361 | +------------------------+ | |
1362 | | onResponderTerminate |<-----------+
1363 | +------------------+-----+ |
1364 | | | +----------------+
1365 | +-------->|onResponderStart|
1366 | | +----------------+
1367Bubble to find first ID | |
1368to return true:wantsResponderID| |
1369 | |
1370 +-------------+ | |
1371 | onTouchMove | | |
1372 +------+------+ none | |
1373 | return| |
1374+-----------v-------------+true| +------------------------+ |
1375|onMoveShouldSetResponder |----->|onResponderMove (cur) |<-----------+
1376+-----------+-------------+ | +------------------------+ | |
1377 | | | +--------+-------+
1378 | returned true for| false:REJECT +-------->|onResponderRejec|
1379 | wantsResponderID | | | +----------------+
1380 | (now attempt | +------------------+-----+ |
1381 | handoff) | | onResponder | |
1382 +------------------->| TerminationRequest| |
1383 | +------------------+-----+ |
1384 | | | +----------------+
1385 | true:GRANT +-------->|onResponderGrant|
1386 | | +--------+-------+
1387 | +------------------------+ | |
1388 | | onResponderTerminate |<-----------+
1389 | +------------------+-----+ |
1390 | | | +----------------+
1391 | +-------->|onResponderMove |
1392 | | +----------------+
1393 | |
1394 | |
1395 Some active touch started| |
1396 inside current responder | +------------------------+ |
1397 +------------------------->| onResponderEnd | |
1398 | | +------------------------+ |
1399 +---+---------+ | |
1400 | onTouchEnd | | |
1401 +---+---------+ | |
1402 | | +------------------------+ |
1403 +------------------------->| onResponderEnd | |
1404 No active touches started| +-----------+------------+ |
1405 inside current responder | | |
1406 | v |
1407 | +------------------------+ |
1408 | | onResponderRelease | |
1409 | +------------------------+ |
1410 | |
1411 + + */
1412
1413/**
1414 * A note about event ordering in the `EventPluginHub`.
1415 *
1416 * Suppose plugins are injected in the following order:
1417 *
1418 * `[R, S, C]`
1419 *
1420 * To help illustrate the example, assume `S` is `SimpleEventPlugin` (for
1421 * `onClick` etc) and `R` is `ResponderEventPlugin`.
1422 *
1423 * "Deferred-Dispatched Events":
1424 *
1425 * - The current event plugin system will traverse the list of injected plugins,
1426 * in order, and extract events by collecting the plugin's return value of
1427 * `extractEvents()`.
1428 * - These events that are returned from `extractEvents` are "deferred
1429 * dispatched events".
1430 * - When returned from `extractEvents`, deferred-dispatched events contain an
1431 * "accumulation" of deferred dispatches.
1432 * - These deferred dispatches are accumulated/collected before they are
1433 * returned, but processed at a later time by the `EventPluginHub` (hence the
1434 * name deferred).
1435 *
1436 * In the process of returning their deferred-dispatched events, event plugins
1437 * themselves can dispatch events on-demand without returning them from
1438 * `extractEvents`. Plugins might want to do this, so that they can use event
1439 * dispatching as a tool that helps them decide which events should be extracted
1440 * in the first place.
1441 *
1442 * "On-Demand-Dispatched Events":
1443 *
1444 * - On-demand-dispatched events are not returned from `extractEvents`.
1445 * - On-demand-dispatched events are dispatched during the process of returning
1446 * the deferred-dispatched events.
1447 * - They should not have side effects.
1448 * - They should be avoided, and/or eventually be replaced with another
1449 * abstraction that allows event plugins to perform multiple "rounds" of event
1450 * extraction.
1451 *
1452 * Therefore, the sequence of event dispatches becomes:
1453 *
1454 * - `R`s on-demand events (if any) (dispatched by `R` on-demand)
1455 * - `S`s on-demand events (if any) (dispatched by `S` on-demand)
1456 * - `C`s on-demand events (if any) (dispatched by `C` on-demand)
1457 * - `R`s extracted events (if any) (dispatched by `EventPluginHub`)
1458 * - `S`s extracted events (if any) (dispatched by `EventPluginHub`)
1459 * - `C`s extracted events (if any) (dispatched by `EventPluginHub`)
1460 *
1461 * In the case of `ResponderEventPlugin`: If the `startShouldSetResponder`
1462 * on-demand dispatch returns `true` (and some other details are satisfied) the
1463 * `onResponderGrant` deferred dispatched event is returned from
1464 * `extractEvents`. The sequence of dispatch executions in this case
1465 * will appear as follows:
1466 *
1467 * - `startShouldSetResponder` (`ResponderEventPlugin` dispatches on-demand)
1468 * - `touchStartCapture` (`EventPluginHub` dispatches as usual)
1469 * - `touchStart` (`EventPluginHub` dispatches as usual)
1470 * - `responderGrant/Reject` (`EventPluginHub` dispatches as usual)
1471 */
1472
1473function setResponderAndExtractTransfer(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
1474 var shouldSetEventType = isStartish(topLevelType) ? eventTypes.startShouldSetResponder : isMoveish(topLevelType) ? eventTypes.moveShouldSetResponder : topLevelType === TOP_SELECTION_CHANGE ? eventTypes.selectionChangeShouldSetResponder : eventTypes.scrollShouldSetResponder;
1475
1476 // TODO: stop one short of the current responder.
1477 var bubbleShouldSetFrom = !responderInst ? targetInst : getLowestCommonAncestor(responderInst, targetInst);
1478
1479 // When capturing/bubbling the "shouldSet" event, we want to skip the target
1480 // (deepest ID) if it happens to be the current responder. The reasoning:
1481 // It's strange to get an `onMoveShouldSetResponder` when you're *already*
1482 // the responder.
1483 var skipOverBubbleShouldSetFrom = bubbleShouldSetFrom === responderInst;
1484 var shouldSetEvent = ResponderSyntheticEvent.getPooled(shouldSetEventType, bubbleShouldSetFrom, nativeEvent, nativeEventTarget);
1485 shouldSetEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
1486 if (skipOverBubbleShouldSetFrom) {
1487 accumulateTwoPhaseDispatchesSkipTarget(shouldSetEvent);
1488 } else {
1489 accumulateTwoPhaseDispatches(shouldSetEvent);
1490 }
1491 var wantsResponderInst = executeDispatchesInOrderStopAtTrue(shouldSetEvent);
1492 if (!shouldSetEvent.isPersistent()) {
1493 shouldSetEvent.constructor.release(shouldSetEvent);
1494 }
1495
1496 if (!wantsResponderInst || wantsResponderInst === responderInst) {
1497 return null;
1498 }
1499 var extracted = void 0;
1500 var grantEvent = ResponderSyntheticEvent.getPooled(eventTypes.responderGrant, wantsResponderInst, nativeEvent, nativeEventTarget);
1501 grantEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
1502
1503 accumulateDirectDispatches(grantEvent);
1504 var blockHostResponder = executeDirectDispatch(grantEvent) === true;
1505 if (responderInst) {
1506 var terminationRequestEvent = ResponderSyntheticEvent.getPooled(eventTypes.responderTerminationRequest, responderInst, nativeEvent, nativeEventTarget);
1507 terminationRequestEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
1508 accumulateDirectDispatches(terminationRequestEvent);
1509 var shouldSwitch = !hasDispatches(terminationRequestEvent) || executeDirectDispatch(terminationRequestEvent);
1510 if (!terminationRequestEvent.isPersistent()) {
1511 terminationRequestEvent.constructor.release(terminationRequestEvent);
1512 }
1513
1514 if (shouldSwitch) {
1515 var terminateEvent = ResponderSyntheticEvent.getPooled(eventTypes.responderTerminate, responderInst, nativeEvent, nativeEventTarget);
1516 terminateEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
1517 accumulateDirectDispatches(terminateEvent);
1518 extracted = accumulate(extracted, [grantEvent, terminateEvent]);
1519 changeResponder(wantsResponderInst, blockHostResponder);
1520 } else {
1521 var rejectEvent = ResponderSyntheticEvent.getPooled(eventTypes.responderReject, wantsResponderInst, nativeEvent, nativeEventTarget);
1522 rejectEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
1523 accumulateDirectDispatches(rejectEvent);
1524 extracted = accumulate(extracted, rejectEvent);
1525 }
1526 } else {
1527 extracted = accumulate(extracted, grantEvent);
1528 changeResponder(wantsResponderInst, blockHostResponder);
1529 }
1530 return extracted;
1531}
1532
1533/**
1534 * A transfer is a negotiation between a currently set responder and the next
1535 * element to claim responder status. Any start event could trigger a transfer
1536 * of responderInst. Any move event could trigger a transfer.
1537 *
1538 * @param {string} topLevelType Record from `BrowserEventConstants`.
1539 * @return {boolean} True if a transfer of responder could possibly occur.
1540 */
1541function canTriggerTransfer(topLevelType, topLevelInst, nativeEvent) {
1542 return topLevelInst && (
1543 // responderIgnoreScroll: We are trying to migrate away from specifically
1544 // tracking native scroll events here and responderIgnoreScroll indicates we
1545 // will send topTouchCancel to handle canceling touch events instead
1546 topLevelType === TOP_SCROLL && !nativeEvent.responderIgnoreScroll || trackedTouchCount > 0 && topLevelType === TOP_SELECTION_CHANGE || isStartish(topLevelType) || isMoveish(topLevelType));
1547}
1548
1549/**
1550 * Returns whether or not this touch end event makes it such that there are no
1551 * longer any touches that started inside of the current `responderInst`.
1552 *
1553 * @param {NativeEvent} nativeEvent Native touch end event.
1554 * @return {boolean} Whether or not this touch end event ends the responder.
1555 */
1556function noResponderTouches(nativeEvent) {
1557 var touches = nativeEvent.touches;
1558 if (!touches || touches.length === 0) {
1559 return true;
1560 }
1561 for (var i = 0; i < touches.length; i++) {
1562 var activeTouch = touches[i];
1563 var target = activeTouch.target;
1564 if (target !== null && target !== undefined && target !== 0) {
1565 // Is the original touch location inside of the current responder?
1566 var targetInst = getInstanceFromNode$1(target);
1567 if (isAncestor(responderInst, targetInst)) {
1568 return false;
1569 }
1570 }
1571 }
1572 return true;
1573}
1574
1575var ResponderEventPlugin = {
1576 /* For unit testing only */
1577 _getResponder: function () {
1578 return responderInst;
1579 },
1580
1581 eventTypes: eventTypes,
1582
1583 /**
1584 * We must be resilient to `targetInst` being `null` on `touchMove` or
1585 * `touchEnd`. On certain platforms, this means that a native scroll has
1586 * assumed control and the original touch targets are destroyed.
1587 */
1588 extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
1589 if (isStartish(topLevelType)) {
1590 trackedTouchCount += 1;
1591 } else if (isEndish(topLevelType)) {
1592 if (trackedTouchCount >= 0) {
1593 trackedTouchCount -= 1;
1594 } else {
1595 console.error('Ended a touch event which was not counted in `trackedTouchCount`.');
1596 return null;
1597 }
1598 }
1599
1600 ResponderTouchHistoryStore.recordTouchTrack(topLevelType, nativeEvent);
1601
1602 var extracted = canTriggerTransfer(topLevelType, targetInst, nativeEvent) ? setResponderAndExtractTransfer(topLevelType, targetInst, nativeEvent, nativeEventTarget) : null;
1603 // Responder may or may not have transferred on a new touch start/move.
1604 // Regardless, whoever is the responder after any potential transfer, we
1605 // direct all touch start/move/ends to them in the form of
1606 // `onResponderMove/Start/End`. These will be called for *every* additional
1607 // finger that move/start/end, dispatched directly to whoever is the
1608 // current responder at that moment, until the responder is "released".
1609 //
1610 // These multiple individual change touch events are are always bookended
1611 // by `onResponderGrant`, and one of
1612 // (`onResponderRelease/onResponderTerminate`).
1613 var isResponderTouchStart = responderInst && isStartish(topLevelType);
1614 var isResponderTouchMove = responderInst && isMoveish(topLevelType);
1615 var isResponderTouchEnd = responderInst && isEndish(topLevelType);
1616 var incrementalTouch = isResponderTouchStart ? eventTypes.responderStart : isResponderTouchMove ? eventTypes.responderMove : isResponderTouchEnd ? eventTypes.responderEnd : null;
1617
1618 if (incrementalTouch) {
1619 var gesture = ResponderSyntheticEvent.getPooled(incrementalTouch, responderInst, nativeEvent, nativeEventTarget);
1620 gesture.touchHistory = ResponderTouchHistoryStore.touchHistory;
1621 accumulateDirectDispatches(gesture);
1622 extracted = accumulate(extracted, gesture);
1623 }
1624
1625 var isResponderTerminate = responderInst && topLevelType === TOP_TOUCH_CANCEL;
1626 var isResponderRelease = responderInst && !isResponderTerminate && isEndish(topLevelType) && noResponderTouches(nativeEvent);
1627 var finalTouch = isResponderTerminate ? eventTypes.responderTerminate : isResponderRelease ? eventTypes.responderRelease : null;
1628 if (finalTouch) {
1629 var finalEvent = ResponderSyntheticEvent.getPooled(finalTouch, responderInst, nativeEvent, nativeEventTarget);
1630 finalEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
1631 accumulateDirectDispatches(finalEvent);
1632 extracted = accumulate(extracted, finalEvent);
1633 changeResponder(null);
1634 }
1635
1636 return extracted;
1637 },
1638
1639 GlobalResponderHandler: null,
1640
1641 injection: {
1642 /**
1643 * @param {{onChange: (ReactID, ReactID) => void} GlobalResponderHandler
1644 * Object that handles any change in responder. Use this to inject
1645 * integration with an existing touch handling system etc.
1646 */
1647 injectGlobalResponderHandler: function (GlobalResponderHandler) {
1648 ResponderEventPlugin.GlobalResponderHandler = GlobalResponderHandler;
1649 }
1650 }
1651};
1652
1653// Inject react-dom's ComponentTree into this module.
1654// Keep in sync with ReactDOM.js and ReactTestUtils.js:
1655var _ReactDOM$__SECRET_IN = ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Events;
1656var getInstanceFromNode = _ReactDOM$__SECRET_IN[0];
1657var getNodeFromInstance = _ReactDOM$__SECRET_IN[1];
1658var getFiberCurrentPropsFromNode = _ReactDOM$__SECRET_IN[2];
1659var injectEventPluginsByName = _ReactDOM$__SECRET_IN[3];
1660
1661
1662setComponentTree(getFiberCurrentPropsFromNode, getInstanceFromNode, getNodeFromInstance);
1663
1664
1665
1666var ReactDOMUnstableNativeDependencies = Object.freeze({
1667 ResponderEventPlugin: ResponderEventPlugin,
1668 ResponderTouchHistoryStore: ResponderTouchHistoryStore,
1669 injectEventPluginsByName: injectEventPluginsByName
1670});
1671
1672var unstableNativeDependencies = ReactDOMUnstableNativeDependencies;
1673
1674module.exports = unstableNativeDependencies;
1675 })();
1676}