Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81152 views
1
/**
2
* Copyright 2013-2014, 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
* @providesModule ReactInstanceHandles
10
* @typechecks static-only
11
*/
12
13
"use strict";
14
15
var ReactRootIndex = require('ReactRootIndex');
16
17
var invariant = require('invariant');
18
19
var SEPARATOR = '.';
20
var SEPARATOR_LENGTH = SEPARATOR.length;
21
22
/**
23
* Maximum depth of traversals before we consider the possibility of a bad ID.
24
*/
25
var MAX_TREE_DEPTH = 100;
26
27
/**
28
* Creates a DOM ID prefix to use when mounting React components.
29
*
30
* @param {number} index A unique integer
31
* @return {string} React root ID.
32
* @internal
33
*/
34
function getReactRootIDString(index) {
35
return SEPARATOR + index.toString(36);
36
}
37
38
/**
39
* Checks if a character in the supplied ID is a separator or the end.
40
*
41
* @param {string} id A React DOM ID.
42
* @param {number} index Index of the character to check.
43
* @return {boolean} True if the character is a separator or end of the ID.
44
* @private
45
*/
46
function isBoundary(id, index) {
47
return id.charAt(index) === SEPARATOR || index === id.length;
48
}
49
50
/**
51
* Checks if the supplied string is a valid React DOM ID.
52
*
53
* @param {string} id A React DOM ID, maybe.
54
* @return {boolean} True if the string is a valid React DOM ID.
55
* @private
56
*/
57
function isValidID(id) {
58
return id === '' || (
59
id.charAt(0) === SEPARATOR && id.charAt(id.length - 1) !== SEPARATOR
60
);
61
}
62
63
/**
64
* Checks if the first ID is an ancestor of or equal to the second ID.
65
*
66
* @param {string} ancestorID
67
* @param {string} descendantID
68
* @return {boolean} True if `ancestorID` is an ancestor of `descendantID`.
69
* @internal
70
*/
71
function isAncestorIDOf(ancestorID, descendantID) {
72
return (
73
descendantID.indexOf(ancestorID) === 0 &&
74
isBoundary(descendantID, ancestorID.length)
75
);
76
}
77
78
/**
79
* Gets the parent ID of the supplied React DOM ID, `id`.
80
*
81
* @param {string} id ID of a component.
82
* @return {string} ID of the parent, or an empty string.
83
* @private
84
*/
85
function getParentID(id) {
86
return id ? id.substr(0, id.lastIndexOf(SEPARATOR)) : '';
87
}
88
89
/**
90
* Gets the next DOM ID on the tree path from the supplied `ancestorID` to the
91
* supplied `destinationID`. If they are equal, the ID is returned.
92
*
93
* @param {string} ancestorID ID of an ancestor node of `destinationID`.
94
* @param {string} destinationID ID of the destination node.
95
* @return {string} Next ID on the path from `ancestorID` to `destinationID`.
96
* @private
97
*/
98
function getNextDescendantID(ancestorID, destinationID) {
99
invariant(
100
isValidID(ancestorID) && isValidID(destinationID),
101
'getNextDescendantID(%s, %s): Received an invalid React DOM ID.',
102
ancestorID,
103
destinationID
104
);
105
invariant(
106
isAncestorIDOf(ancestorID, destinationID),
107
'getNextDescendantID(...): React has made an invalid assumption about ' +
108
'the DOM hierarchy. Expected `%s` to be an ancestor of `%s`.',
109
ancestorID,
110
destinationID
111
);
112
if (ancestorID === destinationID) {
113
return ancestorID;
114
}
115
// Skip over the ancestor and the immediate separator. Traverse until we hit
116
// another separator or we reach the end of `destinationID`.
117
var start = ancestorID.length + SEPARATOR_LENGTH;
118
for (var i = start; i < destinationID.length; i++) {
119
if (isBoundary(destinationID, i)) {
120
break;
121
}
122
}
123
return destinationID.substr(0, i);
124
}
125
126
/**
127
* Gets the nearest common ancestor ID of two IDs.
128
*
129
* Using this ID scheme, the nearest common ancestor ID is the longest common
130
* prefix of the two IDs that immediately preceded a "marker" in both strings.
131
*
132
* @param {string} oneID
133
* @param {string} twoID
134
* @return {string} Nearest common ancestor ID, or the empty string if none.
135
* @private
136
*/
137
function getFirstCommonAncestorID(oneID, twoID) {
138
var minLength = Math.min(oneID.length, twoID.length);
139
if (minLength === 0) {
140
return '';
141
}
142
var lastCommonMarkerIndex = 0;
143
// Use `<=` to traverse until the "EOL" of the shorter string.
144
for (var i = 0; i <= minLength; i++) {
145
if (isBoundary(oneID, i) && isBoundary(twoID, i)) {
146
lastCommonMarkerIndex = i;
147
} else if (oneID.charAt(i) !== twoID.charAt(i)) {
148
break;
149
}
150
}
151
var longestCommonID = oneID.substr(0, lastCommonMarkerIndex);
152
invariant(
153
isValidID(longestCommonID),
154
'getFirstCommonAncestorID(%s, %s): Expected a valid React DOM ID: %s',
155
oneID,
156
twoID,
157
longestCommonID
158
);
159
return longestCommonID;
160
}
161
162
/**
163
* Traverses the parent path between two IDs (either up or down). The IDs must
164
* not be the same, and there must exist a parent path between them. If the
165
* callback returns `false`, traversal is stopped.
166
*
167
* @param {?string} start ID at which to start traversal.
168
* @param {?string} stop ID at which to end traversal.
169
* @param {function} cb Callback to invoke each ID with.
170
* @param {?boolean} skipFirst Whether or not to skip the first node.
171
* @param {?boolean} skipLast Whether or not to skip the last node.
172
* @private
173
*/
174
function traverseParentPath(start, stop, cb, arg, skipFirst, skipLast) {
175
start = start || '';
176
stop = stop || '';
177
invariant(
178
start !== stop,
179
'traverseParentPath(...): Cannot traverse from and to the same ID, `%s`.',
180
start
181
);
182
var traverseUp = isAncestorIDOf(stop, start);
183
invariant(
184
traverseUp || isAncestorIDOf(start, stop),
185
'traverseParentPath(%s, %s, ...): Cannot traverse from two IDs that do ' +
186
'not have a parent path.',
187
start,
188
stop
189
);
190
// Traverse from `start` to `stop` one depth at a time.
191
var depth = 0;
192
var traverse = traverseUp ? getParentID : getNextDescendantID;
193
for (var id = start; /* until break */; id = traverse(id, stop)) {
194
var ret;
195
if ((!skipFirst || id !== start) && (!skipLast || id !== stop)) {
196
ret = cb(id, traverseUp, arg);
197
}
198
if (ret === false || id === stop) {
199
// Only break //after// visiting `stop`.
200
break;
201
}
202
invariant(
203
depth++ < MAX_TREE_DEPTH,
204
'traverseParentPath(%s, %s, ...): Detected an infinite loop while ' +
205
'traversing the React DOM ID tree. This may be due to malformed IDs: %s',
206
start, stop
207
);
208
}
209
}
210
211
/**
212
* Manages the IDs assigned to DOM representations of React components. This
213
* uses a specific scheme in order to traverse the DOM efficiently (e.g. in
214
* order to simulate events).
215
*
216
* @internal
217
*/
218
var ReactInstanceHandles = {
219
220
/**
221
* Constructs a React root ID
222
* @return {string} A React root ID.
223
*/
224
createReactRootID: function() {
225
return getReactRootIDString(ReactRootIndex.createReactRootIndex());
226
},
227
228
/**
229
* Constructs a React ID by joining a root ID with a name.
230
*
231
* @param {string} rootID Root ID of a parent component.
232
* @param {string} name A component's name (as flattened children).
233
* @return {string} A React ID.
234
* @internal
235
*/
236
createReactID: function(rootID, name) {
237
return rootID + name;
238
},
239
240
/**
241
* Gets the DOM ID of the React component that is the root of the tree that
242
* contains the React component with the supplied DOM ID.
243
*
244
* @param {string} id DOM ID of a React component.
245
* @return {?string} DOM ID of the React component that is the root.
246
* @internal
247
*/
248
getReactRootIDFromNodeID: function(id) {
249
if (id && id.charAt(0) === SEPARATOR && id.length > 1) {
250
var index = id.indexOf(SEPARATOR, 1);
251
return index > -1 ? id.substr(0, index) : id;
252
}
253
return null;
254
},
255
256
/**
257
* Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that
258
* should would receive a `mouseEnter` or `mouseLeave` event.
259
*
260
* NOTE: Does not invoke the callback on the nearest common ancestor because
261
* nothing "entered" or "left" that element.
262
*
263
* @param {string} leaveID ID being left.
264
* @param {string} enterID ID being entered.
265
* @param {function} cb Callback to invoke on each entered/left ID.
266
* @param {*} upArg Argument to invoke the callback with on left IDs.
267
* @param {*} downArg Argument to invoke the callback with on entered IDs.
268
* @internal
269
*/
270
traverseEnterLeave: function(leaveID, enterID, cb, upArg, downArg) {
271
var ancestorID = getFirstCommonAncestorID(leaveID, enterID);
272
if (ancestorID !== leaveID) {
273
traverseParentPath(leaveID, ancestorID, cb, upArg, false, true);
274
}
275
if (ancestorID !== enterID) {
276
traverseParentPath(ancestorID, enterID, cb, downArg, true, false);
277
}
278
},
279
280
/**
281
* Simulates the traversal of a two-phase, capture/bubble event dispatch.
282
*
283
* NOTE: This traversal happens on IDs without touching the DOM.
284
*
285
* @param {string} targetID ID of the target node.
286
* @param {function} cb Callback to invoke.
287
* @param {*} arg Argument to invoke the callback with.
288
* @internal
289
*/
290
traverseTwoPhase: function(targetID, cb, arg) {
291
if (targetID) {
292
traverseParentPath('', targetID, cb, arg, true, false);
293
traverseParentPath(targetID, '', cb, arg, false, true);
294
}
295
},
296
297
/**
298
* Traverse a node ID, calling the supplied `cb` for each ancestor ID. For
299
* example, passing `.0.$row-0.1` would result in `cb` getting called
300
* with `.0`, `.0.$row-0`, and `.0.$row-0.1`.
301
*
302
* NOTE: This traversal happens on IDs without touching the DOM.
303
*
304
* @param {string} targetID ID of the target node.
305
* @param {function} cb Callback to invoke.
306
* @param {*} arg Argument to invoke the callback with.
307
* @internal
308
*/
309
traverseAncestors: function(targetID, cb, arg) {
310
traverseParentPath('', targetID, cb, arg, true, false);
311
},
312
313
/**
314
* Exposed for unit testing.
315
* @private
316
*/
317
_getFirstCommonAncestorID: getFirstCommonAncestorID,
318
319
/**
320
* Exposed for unit testing.
321
* @private
322
*/
323
_getNextDescendantID: getNextDescendantID,
324
325
isAncestorIDOf: isAncestorIDOf,
326
327
SEPARATOR: SEPARATOR
328
329
};
330
331
module.exports = ReactInstanceHandles;
332
333