UNPKG

5.17 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2013-present, Facebook, Inc.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 */
8
9'use strict';
10
11var _assign = require('object-assign');
12
13var EventListener = require('fbjs/lib/EventListener');
14var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
15var PooledClass = require('./PooledClass');
16var ReactDOMComponentTree = require('./ReactDOMComponentTree');
17var ReactUpdates = require('./ReactUpdates');
18
19var getEventTarget = require('./getEventTarget');
20var getUnboundedScrollPosition = require('fbjs/lib/getUnboundedScrollPosition');
21
22/**
23 * Find the deepest React component completely containing the root of the
24 * passed-in instance (for use when entire React trees are nested within each
25 * other). If React trees are not nested, returns null.
26 */
27function findParent(inst) {
28 // TODO: It may be a good idea to cache this to prevent unnecessary DOM
29 // traversal, but caching is difficult to do correctly without using a
30 // mutation observer to listen for all DOM changes.
31 while (inst._hostParent) {
32 inst = inst._hostParent;
33 }
34 var rootNode = ReactDOMComponentTree.getNodeFromInstance(inst);
35 var container = rootNode.parentNode;
36 return ReactDOMComponentTree.getClosestInstanceFromNode(container);
37}
38
39// Used to store ancestor hierarchy in top level callback
40function TopLevelCallbackBookKeeping(topLevelType, nativeEvent) {
41 this.topLevelType = topLevelType;
42 this.nativeEvent = nativeEvent;
43 this.ancestors = [];
44}
45_assign(TopLevelCallbackBookKeeping.prototype, {
46 destructor: function () {
47 this.topLevelType = null;
48 this.nativeEvent = null;
49 this.ancestors.length = 0;
50 }
51});
52PooledClass.addPoolingTo(TopLevelCallbackBookKeeping, PooledClass.twoArgumentPooler);
53
54function handleTopLevelImpl(bookKeeping) {
55 var nativeEventTarget = getEventTarget(bookKeeping.nativeEvent);
56 var targetInst = ReactDOMComponentTree.getClosestInstanceFromNode(nativeEventTarget);
57
58 // Loop through the hierarchy, in case there's any nested components.
59 // It's important that we build the array of ancestors before calling any
60 // event handlers, because event handlers can modify the DOM, leading to
61 // inconsistencies with ReactMount's node cache. See #1105.
62 var ancestor = targetInst;
63 do {
64 bookKeeping.ancestors.push(ancestor);
65 ancestor = ancestor && findParent(ancestor);
66 } while (ancestor);
67
68 for (var i = 0; i < bookKeeping.ancestors.length; i++) {
69 targetInst = bookKeeping.ancestors[i];
70 ReactEventListener._handleTopLevel(bookKeeping.topLevelType, targetInst, bookKeeping.nativeEvent, getEventTarget(bookKeeping.nativeEvent));
71 }
72}
73
74function scrollValueMonitor(cb) {
75 var scrollPosition = getUnboundedScrollPosition(window);
76 cb(scrollPosition);
77}
78
79var ReactEventListener = {
80 _enabled: true,
81 _handleTopLevel: null,
82
83 WINDOW_HANDLE: ExecutionEnvironment.canUseDOM ? window : null,
84
85 setHandleTopLevel: function (handleTopLevel) {
86 ReactEventListener._handleTopLevel = handleTopLevel;
87 },
88
89 setEnabled: function (enabled) {
90 ReactEventListener._enabled = !!enabled;
91 },
92
93 isEnabled: function () {
94 return ReactEventListener._enabled;
95 },
96
97 /**
98 * Traps top-level events by using event bubbling.
99 *
100 * @param {string} topLevelType Record from `EventConstants`.
101 * @param {string} handlerBaseName Event name (e.g. "click").
102 * @param {object} element Element on which to attach listener.
103 * @return {?object} An object with a remove function which will forcefully
104 * remove the listener.
105 * @internal
106 */
107 trapBubbledEvent: function (topLevelType, handlerBaseName, element) {
108 if (!element) {
109 return null;
110 }
111 return EventListener.listen(element, handlerBaseName, ReactEventListener.dispatchEvent.bind(null, topLevelType));
112 },
113
114 /**
115 * Traps a top-level event by using event capturing.
116 *
117 * @param {string} topLevelType Record from `EventConstants`.
118 * @param {string} handlerBaseName Event name (e.g. "click").
119 * @param {object} element Element on which to attach listener.
120 * @return {?object} An object with a remove function which will forcefully
121 * remove the listener.
122 * @internal
123 */
124 trapCapturedEvent: function (topLevelType, handlerBaseName, element) {
125 if (!element) {
126 return null;
127 }
128 return EventListener.capture(element, handlerBaseName, ReactEventListener.dispatchEvent.bind(null, topLevelType));
129 },
130
131 monitorScrollValue: function (refresh) {
132 var callback = scrollValueMonitor.bind(null, refresh);
133 EventListener.listen(window, 'scroll', callback);
134 },
135
136 dispatchEvent: function (topLevelType, nativeEvent) {
137 if (!ReactEventListener._enabled) {
138 return;
139 }
140
141 var bookKeeping = TopLevelCallbackBookKeeping.getPooled(topLevelType, nativeEvent);
142 try {
143 // Event queue being processed in the same cycle allows
144 // `preventDefault`.
145 ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
146 } finally {
147 TopLevelCallbackBookKeeping.release(bookKeeping);
148 }
149 }
150};
151
152module.exports = ReactEventListener;
\No newline at end of file