You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
249 lines
7.4 KiB
249 lines
7.4 KiB
/** |
|
* Copyright 2013-2015, Facebook, Inc. |
|
* All rights reserved. |
|
* |
|
* This source code is licensed under the BSD-style license found in the |
|
* LICENSE file in the root directory of this source tree. An additional grant |
|
* of patent rights can be found in the PATENTS file in the same directory. |
|
* |
|
* @providesModule traverseAllChildren |
|
*/ |
|
|
|
'use strict'; |
|
|
|
var ReactElement = require("./ReactElement"); |
|
var ReactFragment = require("./ReactFragment"); |
|
var ReactInstanceHandles = require("./ReactInstanceHandles"); |
|
|
|
var getIteratorFn = require("./getIteratorFn"); |
|
var invariant = require("./invariant"); |
|
var warning = require("./warning"); |
|
|
|
var SEPARATOR = ReactInstanceHandles.SEPARATOR; |
|
var SUBSEPARATOR = ':'; |
|
|
|
/** |
|
* TODO: Test that a single child and an array with one item have the same key |
|
* pattern. |
|
*/ |
|
|
|
var userProvidedKeyEscaperLookup = { |
|
'=': '=0', |
|
'.': '=1', |
|
':': '=2' |
|
}; |
|
|
|
var userProvidedKeyEscapeRegex = /[=.:]/g; |
|
|
|
var didWarnAboutMaps = false; |
|
|
|
function userProvidedKeyEscaper(match) { |
|
return userProvidedKeyEscaperLookup[match]; |
|
} |
|
|
|
/** |
|
* Generate a key string that identifies a component within a set. |
|
* |
|
* @param {*} component A component that could contain a manual key. |
|
* @param {number} index Index that is used if a manual key is not provided. |
|
* @return {string} |
|
*/ |
|
function getComponentKey(component, index) { |
|
if (component && component.key != null) { |
|
// Explicit key |
|
return wrapUserProvidedKey(component.key); |
|
} |
|
// Implicit key determined by the index in the set |
|
return index.toString(36); |
|
} |
|
|
|
/** |
|
* Escape a component key so that it is safe to use in a reactid. |
|
* |
|
* @param {*} key Component key to be escaped. |
|
* @return {string} An escaped string. |
|
*/ |
|
function escapeUserProvidedKey(text) { |
|
return ('' + text).replace( |
|
userProvidedKeyEscapeRegex, |
|
userProvidedKeyEscaper |
|
); |
|
} |
|
|
|
/** |
|
* Wrap a `key` value explicitly provided by the user to distinguish it from |
|
* implicitly-generated keys generated by a component's index in its parent. |
|
* |
|
* @param {string} key Value of a user-provided `key` attribute |
|
* @return {string} |
|
*/ |
|
function wrapUserProvidedKey(key) { |
|
return '$' + escapeUserProvidedKey(key); |
|
} |
|
|
|
/** |
|
* @param {?*} children Children tree container. |
|
* @param {!string} nameSoFar Name of the key path so far. |
|
* @param {!number} indexSoFar Number of children encountered until this point. |
|
* @param {!function} callback Callback to invoke with each child found. |
|
* @param {?*} traverseContext Used to pass information throughout the traversal |
|
* process. |
|
* @return {!number} The number of children in this subtree. |
|
*/ |
|
function traverseAllChildrenImpl( |
|
children, |
|
nameSoFar, |
|
indexSoFar, |
|
callback, |
|
traverseContext |
|
) { |
|
var type = typeof children; |
|
|
|
if (type === 'undefined' || type === 'boolean') { |
|
// All of the above are perceived as null. |
|
children = null; |
|
} |
|
|
|
if (children === null || |
|
type === 'string' || |
|
type === 'number' || |
|
ReactElement.isValidElement(children)) { |
|
callback( |
|
traverseContext, |
|
children, |
|
// If it's the only child, treat the name as if it was wrapped in an array |
|
// so that it's consistent if the number of children grows. |
|
nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar, |
|
indexSoFar |
|
); |
|
return 1; |
|
} |
|
|
|
var child, nextName, nextIndex; |
|
var subtreeCount = 0; // Count of children found in the current subtree. |
|
|
|
if (Array.isArray(children)) { |
|
for (var i = 0; i < children.length; i++) { |
|
child = children[i]; |
|
nextName = ( |
|
(nameSoFar !== '' ? nameSoFar + SUBSEPARATOR : SEPARATOR) + |
|
getComponentKey(child, i) |
|
); |
|
nextIndex = indexSoFar + subtreeCount; |
|
subtreeCount += traverseAllChildrenImpl( |
|
child, |
|
nextName, |
|
nextIndex, |
|
callback, |
|
traverseContext |
|
); |
|
} |
|
} else { |
|
var iteratorFn = getIteratorFn(children); |
|
if (iteratorFn) { |
|
var iterator = iteratorFn.call(children); |
|
var step; |
|
if (iteratorFn !== children.entries) { |
|
var ii = 0; |
|
while (!(step = iterator.next()).done) { |
|
child = step.value; |
|
nextName = ( |
|
(nameSoFar !== '' ? nameSoFar + SUBSEPARATOR : SEPARATOR) + |
|
getComponentKey(child, ii++) |
|
); |
|
nextIndex = indexSoFar + subtreeCount; |
|
subtreeCount += traverseAllChildrenImpl( |
|
child, |
|
nextName, |
|
nextIndex, |
|
callback, |
|
traverseContext |
|
); |
|
} |
|
} else { |
|
if ("production" !== process.env.NODE_ENV) { |
|
("production" !== process.env.NODE_ENV ? warning( |
|
didWarnAboutMaps, |
|
'Using Maps as children is not yet fully supported. It is an ' + |
|
'experimental feature that might be removed. Convert it to a ' + |
|
'sequence / iterable of keyed ReactElements instead.' |
|
) : null); |
|
didWarnAboutMaps = true; |
|
} |
|
// Iterator will provide entry [k,v] tuples rather than values. |
|
while (!(step = iterator.next()).done) { |
|
var entry = step.value; |
|
if (entry) { |
|
child = entry[1]; |
|
nextName = ( |
|
(nameSoFar !== '' ? nameSoFar + SUBSEPARATOR : SEPARATOR) + |
|
wrapUserProvidedKey(entry[0]) + SUBSEPARATOR + |
|
getComponentKey(child, 0) |
|
); |
|
nextIndex = indexSoFar + subtreeCount; |
|
subtreeCount += traverseAllChildrenImpl( |
|
child, |
|
nextName, |
|
nextIndex, |
|
callback, |
|
traverseContext |
|
); |
|
} |
|
} |
|
} |
|
} else if (type === 'object') { |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
children.nodeType !== 1, |
|
'traverseAllChildren(...): Encountered an invalid child; DOM ' + |
|
'elements are not valid children of React components.' |
|
) : invariant(children.nodeType !== 1)); |
|
var fragment = ReactFragment.extract(children); |
|
for (var key in fragment) { |
|
if (fragment.hasOwnProperty(key)) { |
|
child = fragment[key]; |
|
nextName = ( |
|
(nameSoFar !== '' ? nameSoFar + SUBSEPARATOR : SEPARATOR) + |
|
wrapUserProvidedKey(key) + SUBSEPARATOR + |
|
getComponentKey(child, 0) |
|
); |
|
nextIndex = indexSoFar + subtreeCount; |
|
subtreeCount += traverseAllChildrenImpl( |
|
child, |
|
nextName, |
|
nextIndex, |
|
callback, |
|
traverseContext |
|
); |
|
} |
|
} |
|
} |
|
} |
|
|
|
return subtreeCount; |
|
} |
|
|
|
/** |
|
* Traverses children that are typically specified as `props.children`, but |
|
* might also be specified through attributes: |
|
* |
|
* - `traverseAllChildren(this.props.children, ...)` |
|
* - `traverseAllChildren(this.props.leftPanelChildren, ...)` |
|
* |
|
* The `traverseContext` is an optional argument that is passed through the |
|
* entire traversal. It can be used to store accumulations or anything else that |
|
* the callback might find relevant. |
|
* |
|
* @param {?*} children Children tree object. |
|
* @param {!function} callback To invoke upon traversing each child. |
|
* @param {?*} traverseContext Context for traversal. |
|
* @return {!number} The number of children in this subtree. |
|
*/ |
|
function traverseAllChildren(children, callback, traverseContext) { |
|
if (children == null) { |
|
return 0; |
|
} |
|
|
|
return traverseAllChildrenImpl(children, '', 0, callback, traverseContext); |
|
} |
|
|
|
module.exports = traverseAllChildren;
|
|
|