react-pose-15
Version:
A declarative animation library for React 15+
183 lines (153 loc) • 4.64 kB
text/typescript
import * as React from 'react';
import { Props, State } from './types';
import { CurrentPose } from '../PoseElement/types';
import { invariant, warning } from 'hey-listen';
const getKey = (child: React.ReactElement<any>): string => {
invariant(
child && child.key !== null,
'Every child of Transition must be given a unique key'
);
const childKey =
typeof child.key === 'number' ? child.key.toString() : child.key;
return childKey.replace('.$', '');
};
const prependProps = (
element: React.ReactElement<any>,
props: { [key: string]: any; }
) =>
// avoid extra copying in cloneElement
React.createElement(element.type, {
key: element.key,
ref: (element as any).ref,
...props,
...element.props,
})
const handleTransition = (
{
children: incomingChildren,
preEnterPose,
enterPose,
exitPose,
animateOnMount,
enterAfterExit,
flipMove,
onRest,
...propsForChildren
}: Props,
{
displayedChildren,
finishedLeaving,
hasInitialized,
indexedChildren: prevChildren,
scheduleChildRemoval,
}: State
) => {
const targetChildren = makeChildList(incomingChildren as React.ReactNode);
const nextState: Partial<State> = {
displayedChildren: [],
indexedChildren: {},
};
if (process.env.NODE_ENV !== 'production') {
warning(
!propsForChildren.onPoseComplete,
`<Transition/> (or <PoseGroup/>) doesn't accept onPoseComplete prop.`,
);
}
const prevKeys = displayedChildren.map(getKey);
const nextKeys = targetChildren.map(getKey);
const hasPropsForChildren = Object.keys(propsForChildren).length !== 0;
const entering = new Set(
nextKeys.filter(
key => finishedLeaving.hasOwnProperty(key) || prevKeys.indexOf(key) === -1
)
);
entering.forEach(key => delete finishedLeaving[key]);
const leaving: Array<string> = [];
const newlyLeaving: { [key: string]: boolean } = {};
prevKeys.forEach(key => {
if (entering.has(key)) {
return;
}
const isLeaving = finishedLeaving.hasOwnProperty(key);
if (!isLeaving && nextKeys.indexOf(key) !== -1) {
return;
}
leaving.push(key);
if (!isLeaving) {
finishedLeaving[key] = false;
newlyLeaving[key] = true;
}
})
const moving = new Set(
prevKeys.filter((key, i) => {
if (entering.has(key)) {
return false;
}
const nextIndex = nextKeys.indexOf(key);
return nextIndex !== -1 && i !== nextIndex;
})
);
targetChildren.forEach(child => {
const newChildProps: { [key: string]: any; } = {};
if (entering.has(child.key as string)) {
if (hasInitialized || animateOnMount) {
newChildProps.initialPose = preEnterPose;
}
// TODO: Remove _pose and merge with child.props.pose
newChildProps._pose = enterPose;
} else if (moving.has(child.key as string) && flipMove) {
newChildProps._pose = [enterPose, 'flip'];
} else {
newChildProps._pose = enterPose;
}
const newChild = React.cloneElement(child, newChildProps);
nextState.indexedChildren[child.key] = newChild;
nextState.displayedChildren.push(
hasPropsForChildren
? prependProps(newChild, propsForChildren)
: newChild
);
});
leaving.forEach(key => {
const child = prevChildren[key];
const newChild = newlyLeaving[key]
? React.cloneElement(child, {
_pose: exitPose,
onPoseComplete: (pose: CurrentPose) => {
scheduleChildRemoval(key)
const { onPoseComplete } = child.props
onPoseComplete && onPoseComplete(pose)
},
popFromFlow: flipMove,
})
: child;
const insertionIndex = prevKeys.indexOf(key);
// We might have had new items added before this item in the same
// render. So here we find the correct item to anchor to. This is
// a pretty shitty algo. But it is also the one we have
// if (insertionIndex) {
// TODO: Write a shitty algo
// }
nextState.indexedChildren[child.key] = newChild;
nextState.displayedChildren.splice(
insertionIndex,
0,
hasPropsForChildren
? prependProps(newChild, propsForChildren)
: newChild
);
});
return nextState;
};
export default (props: Props, state: State) => ({
hasInitialized: true,
...handleTransition(props, state)
});
const makeChildList = (children: React.ReactNode) => {
const list: Array<React.ReactElement<any>> = [];
React.Children.forEach(
children,
child => child && list.push(child as React.ReactElement<any>)
);
return list;
};