UNPKG

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