347 lines
9.3 KiB
JavaScript
347 lines
9.3 KiB
JavaScript
/**
|
|
* 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;
|