preact-material-components
Version:
preact wrapper for "Material Components for the web"
182 lines (144 loc) • 4.86 kB
JavaScript
import { h, Component, cloneElement } from 'preact';
import { getChildMapping, mergeChildMappings } from './TransitionChildMapping';
import { assign, linkRef } from './util';
const identity = i => i;
export class TransitionGroup extends Component {
static defaultProps = {
component: 'span',
childFactory: identity
};
refs = {};
state = {
children: getChildMapping(this.props.children || [])
};
componentWillMount() {
this.currentlyTransitioningKeys = {};
this.keysToEnter = [];
this.keysToLeave = [];
}
componentDidMount() {
let initialChildMapping = this.state.children;
for (let key in initialChildMapping) {
if (initialChildMapping[key]) {
// this.performAppear(getKey(initialChildMapping[key], key));
this.performAppear(key);
}
}
}
componentWillReceiveProps(nextProps) {
let nextChildMapping = getChildMapping(nextProps.children || []);
let prevChildMapping = this.state.children;
this.setState({
children: mergeChildMappings(prevChildMapping, nextChildMapping)
});
let key;
for (key in nextChildMapping) if (nextChildMapping.hasOwnProperty(key)) {
let hasPrev = prevChildMapping && prevChildMapping.hasOwnProperty(key);
if (nextChildMapping[key] && !hasPrev && !this.currentlyTransitioningKeys[key]) {
this.keysToEnter.push(key);
}
}
for (key in prevChildMapping) if (prevChildMapping.hasOwnProperty(key)) {
let hasNext = nextChildMapping && nextChildMapping.hasOwnProperty(key);
if (prevChildMapping[key] && !hasNext && !this.currentlyTransitioningKeys[key]) {
this.keysToLeave.push(key);
}
}
}
componentDidUpdate() {
let keysToEnter = this.keysToEnter;
this.keysToEnter = [];
keysToEnter.forEach(this.performEnter);
let keysToLeave = this.keysToLeave;
this.keysToLeave = [];
keysToLeave.forEach(this.performLeave);
}
performAppear(key) {
this.currentlyTransitioningKeys[key] = true;
let component = this.refs[key];
if (component.componentWillAppear) {
component.componentWillAppear(this._handleDoneAppearing.bind(this, key));
}
else {
this._handleDoneAppearing(key);
}
}
_handleDoneAppearing(key) {
let component = this.refs[key];
if (component.componentDidAppear) {
component.componentDidAppear();
}
delete this.currentlyTransitioningKeys[key];
let currentChildMapping = getChildMapping(this.props.children || []);
if (!currentChildMapping || !currentChildMapping.hasOwnProperty(key)) {
// This was removed before it had fully appeared. Remove it.
this.performLeave(key);
}
}
performEnter = (key) => {
this.currentlyTransitioningKeys[key] = true;
let component = this.refs[key];
if (component.componentWillEnter) {
component.componentWillEnter(this._handleDoneEntering.bind(this, key));
}
else {
this._handleDoneEntering(key);
}
};
_handleDoneEntering(key) {
let component = this.refs[key];
if (component.componentDidEnter) {
component.componentDidEnter();
}
delete this.currentlyTransitioningKeys[key];
let currentChildMapping = getChildMapping(this.props.children || []);
if (!currentChildMapping || !currentChildMapping.hasOwnProperty(key)) {
// This was removed before it had fully entered. Remove it.
this.performLeave(key);
}
}
performLeave = (key) => {
this.currentlyTransitioningKeys[key] = true;
let component = this.refs[key];
if (component.componentWillLeave) {
component.componentWillLeave(this._handleDoneLeaving.bind(this, key));
}
else {
// Note that this is somewhat dangerous b/c it calls setState()
// again, effectively mutating the component before all the work
// is done.
this._handleDoneLeaving(key);
}
};
_handleDoneLeaving(key) {
let component = this.refs[key];
if (component.componentDidLeave) {
component.componentDidLeave();
}
delete this.currentlyTransitioningKeys[key];
let currentChildMapping = getChildMapping(this.props.children || []);
if (currentChildMapping && currentChildMapping.hasOwnProperty(key)) {
// This entered again before it fully left. Add it again.
this.performEnter(key);
}
else {
let children = assign({}, this.state.children);
delete children[key];
this.setState({ children });
}
}
render({ childFactory, transitionLeave, transitionName, transitionAppear, transitionEnter, transitionLeaveTimeout, transitionEnterTimeout, transitionAppearTimeout, component, ...props }, { children }) {
// TODO: we could get rid of the need for the wrapper node
// by cloning a single child
let childrenToRender = [];
for (let key in children) if (children.hasOwnProperty(key)) {
let child = children[key];
if (child) {
let ref = linkRef(this, key),
el = cloneElement(childFactory(child), { ref, key });
childrenToRender.push(el);
}
}
return h(component, props, childrenToRender);
}
}