/** * 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;