mirror of
https://github.com/idanoo/GoScrobble.git
synced 2024-11-25 09:55:15 +00:00
140 lines
4.6 KiB
JavaScript
140 lines
4.6 KiB
JavaScript
|
import { Children, cloneElement, isValidElement } from 'react';
|
||
|
/**
|
||
|
* Given `this.props.children`, return an object mapping key to child.
|
||
|
*
|
||
|
* @param {*} children `this.props.children`
|
||
|
* @return {object} Mapping of key to child
|
||
|
*/
|
||
|
|
||
|
export function getChildMapping(children, mapFn) {
|
||
|
var mapper = function mapper(child) {
|
||
|
return mapFn && isValidElement(child) ? mapFn(child) : child;
|
||
|
};
|
||
|
|
||
|
var result = Object.create(null);
|
||
|
if (children) Children.map(children, function (c) {
|
||
|
return c;
|
||
|
}).forEach(function (child) {
|
||
|
// run the map function here instead so that the key is the computed one
|
||
|
result[child.key] = mapper(child);
|
||
|
});
|
||
|
return result;
|
||
|
}
|
||
|
/**
|
||
|
* When you're adding or removing children some may be added or removed in the
|
||
|
* same render pass. We want to show *both* since we want to simultaneously
|
||
|
* animate elements in and out. This function takes a previous set of keys
|
||
|
* and a new set of keys and merges them with its best guess of the correct
|
||
|
* ordering. In the future we may expose some of the utilities in
|
||
|
* ReactMultiChild to make this easy, but for now React itself does not
|
||
|
* directly have this concept of the union of prevChildren and nextChildren
|
||
|
* so we implement it here.
|
||
|
*
|
||
|
* @param {object} prev prev children as returned from
|
||
|
* `ReactTransitionChildMapping.getChildMapping()`.
|
||
|
* @param {object} next next children as returned from
|
||
|
* `ReactTransitionChildMapping.getChildMapping()`.
|
||
|
* @return {object} a key set that contains all keys in `prev` and all keys
|
||
|
* in `next` in a reasonable order.
|
||
|
*/
|
||
|
|
||
|
export function mergeChildMappings(prev, next) {
|
||
|
prev = prev || {};
|
||
|
next = next || {};
|
||
|
|
||
|
function getValueForKey(key) {
|
||
|
return key in next ? next[key] : prev[key];
|
||
|
} // For each key of `next`, the list of keys to insert before that key in
|
||
|
// the combined list
|
||
|
|
||
|
|
||
|
var nextKeysPending = Object.create(null);
|
||
|
var pendingKeys = [];
|
||
|
|
||
|
for (var prevKey in prev) {
|
||
|
if (prevKey in next) {
|
||
|
if (pendingKeys.length) {
|
||
|
nextKeysPending[prevKey] = pendingKeys;
|
||
|
pendingKeys = [];
|
||
|
}
|
||
|
} else {
|
||
|
pendingKeys.push(prevKey);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var i;
|
||
|
var childMapping = {};
|
||
|
|
||
|
for (var nextKey in next) {
|
||
|
if (nextKeysPending[nextKey]) {
|
||
|
for (i = 0; i < nextKeysPending[nextKey].length; i++) {
|
||
|
var pendingNextKey = nextKeysPending[nextKey][i];
|
||
|
childMapping[nextKeysPending[nextKey][i]] = getValueForKey(pendingNextKey);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
childMapping[nextKey] = getValueForKey(nextKey);
|
||
|
} // Finally, add the keys which didn't appear before any key in `next`
|
||
|
|
||
|
|
||
|
for (i = 0; i < pendingKeys.length; i++) {
|
||
|
childMapping[pendingKeys[i]] = getValueForKey(pendingKeys[i]);
|
||
|
}
|
||
|
|
||
|
return childMapping;
|
||
|
}
|
||
|
|
||
|
function getProp(child, prop, props) {
|
||
|
return props[prop] != null ? props[prop] : child.props[prop];
|
||
|
}
|
||
|
|
||
|
export function getInitialChildMapping(props, onExited) {
|
||
|
return getChildMapping(props.children, function (child) {
|
||
|
return cloneElement(child, {
|
||
|
onExited: onExited.bind(null, child),
|
||
|
in: true,
|
||
|
appear: getProp(child, 'appear', props),
|
||
|
enter: getProp(child, 'enter', props),
|
||
|
exit: getProp(child, 'exit', props)
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
export function getNextChildMapping(nextProps, prevChildMapping, onExited) {
|
||
|
var nextChildMapping = getChildMapping(nextProps.children);
|
||
|
var children = mergeChildMappings(prevChildMapping, nextChildMapping);
|
||
|
Object.keys(children).forEach(function (key) {
|
||
|
var child = children[key];
|
||
|
if (!isValidElement(child)) return;
|
||
|
var hasPrev = (key in prevChildMapping);
|
||
|
var hasNext = (key in nextChildMapping);
|
||
|
var prevChild = prevChildMapping[key];
|
||
|
var isLeaving = isValidElement(prevChild) && !prevChild.props.in; // item is new (entering)
|
||
|
|
||
|
if (hasNext && (!hasPrev || isLeaving)) {
|
||
|
// console.log('entering', key)
|
||
|
children[key] = cloneElement(child, {
|
||
|
onExited: onExited.bind(null, child),
|
||
|
in: true,
|
||
|
exit: getProp(child, 'exit', nextProps),
|
||
|
enter: getProp(child, 'enter', nextProps)
|
||
|
});
|
||
|
} else if (!hasNext && hasPrev && !isLeaving) {
|
||
|
// item is old (exiting)
|
||
|
// console.log('leaving', key)
|
||
|
children[key] = cloneElement(child, {
|
||
|
in: false
|
||
|
});
|
||
|
} else if (hasNext && hasPrev && isValidElement(prevChild)) {
|
||
|
// item hasn't changed transition states
|
||
|
// copy over the last transition props;
|
||
|
// console.log('unchanged', key)
|
||
|
children[key] = cloneElement(child, {
|
||
|
onExited: onExited.bind(null, child),
|
||
|
in: prevChild.props.in,
|
||
|
exit: getProp(child, 'exit', nextProps),
|
||
|
enter: getProp(child, 'enter', nextProps)
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
return children;
|
||
|
}
|