/** * Copyright (c) 2015-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; var React = require('react'); var REACT_ELEMENT_TYPE = (typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element')) || 0xeac7; var emptyFunction = require('fbjs/lib/emptyFunction'); var invariant = require('fbjs/lib/invariant'); var warning = require('fbjs/lib/warning'); var SEPARATOR = '.'; var SUBSEPARATOR = ':'; var didWarnAboutMaps = false; var ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator; var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec. function getIteratorFn(maybeIterable) { var iteratorFn = maybeIterable && ((ITERATOR_SYMBOL && maybeIterable[ITERATOR_SYMBOL]) || maybeIterable[FAUX_ITERATOR_SYMBOL]); if (typeof iteratorFn === 'function') { return iteratorFn; } } function escape(key) { var escapeRegex = /[=:]/g; var escaperLookup = { '=': '=0', ':': '=2' }; var escapedString = ('' + key).replace(escapeRegex, function(match) { return escaperLookup[match]; }); return '$' + escapedString; } function getComponentKey(component, index) { // Do some typechecking here since we call this blindly. We want to ensure // that we don't block potential future ES APIs. if (component && typeof component === 'object' && component.key != null) { // Explicit key return escape(component.key); } // Implicit key determined by the index in the set return index.toString(36); } function traverseAllChildrenImpl( children, nameSoFar, 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' || // The following is inlined from ReactElement. This means we can optimize // some checks. React Fiber also inlines this logic for similar purposes. (type === 'object' && children.$$typeof === REACT_ELEMENT_TYPE) ) { 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 ); return 1; } var child; var nextName; var subtreeCount = 0; // Count of children found in the current subtree. var nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR; if (Array.isArray(children)) { for (var i = 0; i < children.length; i++) { child = children[i]; nextName = nextNamePrefix + getComponentKey(child, i); subtreeCount += traverseAllChildrenImpl( child, nextName, callback, traverseContext ); } } else { var iteratorFn = getIteratorFn(children); if (iteratorFn) { if (process.env.NODE_ENV !== 'production') { // Warn about using Maps as children if (iteratorFn === children.entries) { warning( didWarnAboutMaps, 'Using Maps as children is unsupported and will likely yield ' + 'unexpected results. Convert it to a sequence/iterable of keyed ' + 'ReactElements instead.' ); didWarnAboutMaps = true; } } var iterator = iteratorFn.call(children); var step; var ii = 0; while (!(step = iterator.next()).done) { child = step.value; nextName = nextNamePrefix + getComponentKey(child, ii++); subtreeCount += traverseAllChildrenImpl( child, nextName, callback, traverseContext ); } } else if (type === 'object') { var addendum = ''; if (process.env.NODE_ENV !== 'production') { addendum = ' If you meant to render a collection of children, use an array ' + 'instead or wrap the object using createFragment(object) from the ' + 'React add-ons.'; } var childrenString = '' + children; invariant( false, 'Objects are not valid as a React child (found: %s).%s', childrenString === '[object Object]' ? 'object with keys {' + Object.keys(children).join(', ') + '}' : childrenString, addendum ); } } return subtreeCount; } function traverseAllChildren(children, callback, traverseContext) { if (children == null) { return 0; } return traverseAllChildrenImpl(children, '', callback, traverseContext); } var userProvidedKeyEscapeRegex = /\/+/g; function escapeUserProvidedKey(text) { return ('' + text).replace(userProvidedKeyEscapeRegex, '$&/'); } function cloneAndReplaceKey(oldElement, newKey) { return React.cloneElement( oldElement, {key: newKey}, oldElement.props !== undefined ? oldElement.props.children : undefined ); } var DEFAULT_POOL_SIZE = 10; var DEFAULT_POOLER = oneArgumentPooler; var oneArgumentPooler = function(copyFieldsFrom) { var Klass = this; if (Klass.instancePool.length) { var instance = Klass.instancePool.pop(); Klass.call(instance, copyFieldsFrom); return instance; } else { return new Klass(copyFieldsFrom); } }; var addPoolingTo = function addPoolingTo(CopyConstructor, pooler) { // Casting as any so that flow ignores the actual implementation and trusts // it to match the type we declared var NewKlass = CopyConstructor; NewKlass.instancePool = []; NewKlass.getPooled = pooler || DEFAULT_POOLER; if (!NewKlass.poolSize) { NewKlass.poolSize = DEFAULT_POOL_SIZE; } NewKlass.release = standardReleaser; return NewKlass; }; var standardReleaser = function standardReleaser(instance) { var Klass = this; invariant( instance instanceof Klass, 'Trying to release an instance into a pool of a different type.' ); instance.destructor(); if (Klass.instancePool.length < Klass.poolSize) { Klass.instancePool.push(instance); } }; var fourArgumentPooler = function fourArgumentPooler(a1, a2, a3, a4) { var Klass = this; if (Klass.instancePool.length) { var instance = Klass.instancePool.pop(); Klass.call(instance, a1, a2, a3, a4); return instance; } else { return new Klass(a1, a2, a3, a4); } }; function MapBookKeeping(mapResult, keyPrefix, mapFunction, mapContext) { this.result = mapResult; this.keyPrefix = keyPrefix; this.func = mapFunction; this.context = mapContext; this.count = 0; } MapBookKeeping.prototype.destructor = function() { this.result = null; this.keyPrefix = null; this.func = null; this.context = null; this.count = 0; }; addPoolingTo(MapBookKeeping, fourArgumentPooler); function mapSingleChildIntoContext(bookKeeping, child, childKey) { var result = bookKeeping.result; var keyPrefix = bookKeeping.keyPrefix; var func = bookKeeping.func; var context = bookKeeping.context; var mappedChild = func.call(context, child, bookKeeping.count++); if (Array.isArray(mappedChild)) { mapIntoWithKeyPrefixInternal( mappedChild, result, childKey, emptyFunction.thatReturnsArgument ); } else if (mappedChild != null) { if (React.isValidElement(mappedChild)) { mappedChild = cloneAndReplaceKey( mappedChild, // Keep both the (mapped) and old keys if they differ, just as // traverseAllChildren used to do for objects as children keyPrefix + (mappedChild.key && (!child || child.key !== mappedChild.key) ? escapeUserProvidedKey(mappedChild.key) + '/' : '') + childKey ); } result.push(mappedChild); } } function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) { var escapedPrefix = ''; if (prefix != null) { escapedPrefix = escapeUserProvidedKey(prefix) + '/'; } var traverseContext = MapBookKeeping.getPooled( array, escapedPrefix, func, context ); traverseAllChildren(children, mapSingleChildIntoContext, traverseContext); MapBookKeeping.release(traverseContext); } var numericPropertyRegex = /^\d+$/; var warnedAboutNumeric = false; function createReactFragment(object) { if (typeof object !== 'object' || !object || Array.isArray(object)) { warning( false, 'React.addons.createFragment only accepts a single object. Got: %s', object ); return object; } if (React.isValidElement(object)) { warning( false, 'React.addons.createFragment does not accept a ReactElement ' + 'without a wrapper object.' ); return object; } invariant( object.nodeType !== 1, 'React.addons.createFragment(...): Encountered an invalid child; DOM ' + 'elements are not valid children of React components.' ); var result = []; for (var key in object) { if (process.env.NODE_ENV !== 'production') { if (!warnedAboutNumeric && numericPropertyRegex.test(key)) { warning( false, 'React.addons.createFragment(...): Child objects should have ' + 'non-numeric keys so ordering is preserved.' ); warnedAboutNumeric = true; } } mapIntoWithKeyPrefixInternal( object[key], result, key, emptyFunction.thatReturnsArgument ); } return result; } module.exports = createReactFragment;