UNPKG

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