UNPKG

react-addons

Version:

Simple packaging of react addons to avoid fiddly 'react/addons' npm module.

190 lines (175 loc) 6.53 kB
/** * Copyright 2013-2014 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @providesModule traverseAllChildren */ "use strict"; var ReactInstanceHandles = require("./ReactInstanceHandles"); var ReactTextComponent = require("./ReactTextComponent"); var invariant = require("./invariant"); var SEPARATOR = ReactInstanceHandles.SEPARATOR; var SUBSEPARATOR = ':'; /** * TODO: Test that: * 1. `mapChildren` transforms strings and numbers into `ReactTextComponent`. * 2. it('should fail when supplied duplicate key', function() { * 3. That a single child and an array with one item have the same key pattern. * }); */ var userProvidedKeyEscaperLookup = { '=': '=0', '.': '=1', ':': '=2' }; var userProvidedKeyEscapeRegex = /[=.:]/g; 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.props && component.props.key != null) { // Explicit key return wrapUserProvidedKey(component.props.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. */ var traverseAllChildrenImpl = function(children, nameSoFar, indexSoFar, callback, traverseContext) { var subtreeCount = 0; // Count of children found in the current subtree. if (Array.isArray(children)) { for (var i = 0; i < children.length; i++) { var child = children[i]; var nextName = ( nameSoFar + (nameSoFar ? SUBSEPARATOR : SEPARATOR) + getComponentKey(child, i) ); var nextIndex = indexSoFar + subtreeCount; subtreeCount += traverseAllChildrenImpl( child, nextName, nextIndex, callback, traverseContext ); } } else { var type = typeof children; var isOnlyChild = nameSoFar === ''; // 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 var storageName = isOnlyChild ? SEPARATOR + getComponentKey(children, 0) : nameSoFar; if (children == null || type === 'boolean') { // All of the above are perceived as null. callback(traverseContext, null, storageName, indexSoFar); subtreeCount = 1; } else if (children.mountComponentIntoNode) { callback(traverseContext, children, storageName, indexSoFar); subtreeCount = 1; } else { if (type === 'object') { ("production" !== process.env.NODE_ENV ? invariant( !children || children.nodeType !== 1, 'traverseAllChildren(...): Encountered an invalid child; DOM ' + 'elements are not valid children of React components.' ) : invariant(!children || children.nodeType !== 1)); for (var key in children) { if (children.hasOwnProperty(key)) { subtreeCount += traverseAllChildrenImpl( children[key], ( nameSoFar + (nameSoFar ? SUBSEPARATOR : SEPARATOR) + wrapUserProvidedKey(key) + SUBSEPARATOR + getComponentKey(children[key], 0) ), indexSoFar + subtreeCount, callback, traverseContext ); } } } else if (type === 'string') { var normalizedText = new ReactTextComponent(children); callback(traverseContext, normalizedText, storageName, indexSoFar); subtreeCount += 1; } else if (type === 'number') { var normalizedNumber = new ReactTextComponent('' + children); callback(traverseContext, normalizedNumber, storageName, indexSoFar); subtreeCount += 1; } } } 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. */ function traverseAllChildren(children, callback, traverseContext) { if (children !== null && children !== undefined) { traverseAllChildrenImpl(children, '', 0, callback, traverseContext); } } module.exports = traverseAllChildren;