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.
250 lines
7.4 KiB
250 lines
7.4 KiB
10 years ago
|
/**
|
||
|
* 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;
|