import React, { Fragment, ReactElement } from 'react'; import { gsap } from 'gsap'; import { PlayState } from './types'; import { getTweenFunction, setPlayState, isEqual, getRefProp, setProps, setInitialPlayState, } from './helper'; import { Context } from './Provider'; import SvgDrawPlugin from './plugins/PlugInSvgDraw'; import CountPlugin from './plugins/PlugInCount'; // @ts-ignore gsap.registerPlugin(SvgDrawPlugin); // @ts-ignore gsap.registerPlugin(CountPlugin); type StaggerFunction = (index: number, target: any, list: any) => number; type StaggerFromValues = 'start' | 'center' | 'edges' | 'random' | 'end'; type EaseFunction = (value: number) => number; export type Stagger = | { amount?: number; each?: number; from?: StaggerFromValues | number | [number, number]; grid?: [number, number] | 'auto'; axis?: 'x' | 'y'; ease?: string | EaseFunction; repeat?: number; yoyo?: boolean; [prop: string]: any; } | number | StaggerFunction; export type TweenProps = { /** One or multiple "refable" components */ children?: React.ReactNode; wrapper?: React.ReactElement; target?: number | string; position?: string | number; from?: any; to?: any; stagger?: Stagger; duration?: number; progress?: number; totalProgress?: number; playState?: PlayState; disabled?: boolean; onlyInvalidateTo?: boolean; [prop: string]: any; }; class Tween extends React.Component { static displayName = 'Tween'; static contextType = Context; tween: any; targets: any[] = []; constructor(props: TweenProps) { super(props); this.addTarget = this.addTarget.bind(this); } componentDidMount() { this.createTween(); // props at mount setProps(this.tween, this.props); setInitialPlayState(this.tween, this.props); this.context.registerConsumer(this); } componentWillUnmount() { if (this.tween) { this.tween.kill(); } } getSnapshotBeforeUpdate() { this.targets = []; return null; } componentDidUpdate(prevProps: TweenProps) { const { children, wrapper, duration, from, to, stagger, progress, totalProgress, playState, disabled, onlyInvalidateTo, onCompleteAll, onCompleteAllParams, onCompleteAllScope, onStartAll, position, target, ...vars } = this.props; // if children change create a new tween // TODO: replace easy length check with fast equal check if (React.Children.count(prevProps.children) !== React.Children.count(children)) { this.createTween(); } if (disabled) { return; } // execute function calls setProps(this.tween, this.props, prevProps); // if "to" props are changed: reinit and restart tween if (!isEqual(to, prevProps.to)) { // is Tween if (!this.tween.getChildren) { this.tween.vars = { ...to, ...vars }; if (onlyInvalidateTo) { var progressTmp = this.tween.progress(); this.tween .progress(0) .invalidate() .progress(progressTmp); } else { this.tween.invalidate(); } } // is Timeline // TODO: not yet ready else { let delay = 0; this.tween.getChildren(false, true, false).forEach((tween: any) => { tween.vars = { ...to, ...vars, ...{ delay } }; tween.invalidate(); // delay += stagger || 0; }); } if (!this.tween.paused()) { this.tween.restart(); } } setPlayState(playState, prevProps.playState, this.tween); } createTween() { if (this.tween) { this.tween.kill(); } if (this.props.children) { this.tween = getTweenFunction(this.targets, this.props, this.context); } else { // why this is needed? this.tween = () => {}; } } getGSAP() { return this.tween; } setGSAP(tween: any) { this.tween = tween; } addTarget(target: any) { // target is null at unmount if (target !== null) { this.targets.push(target); } } getTargets() { return this.targets; } render() { let { children, wrapper } = this.props; const output = ( {React.Children.map(children, child => { return React.cloneElement(child as ReactElement, getRefProp(child, this.addTarget)); })} ); if (wrapper) { return React.cloneElement(wrapper, [], output); } return output; } } export default Tween;