diff --git a/core/src/interface.d.ts b/core/src/interface.d.ts index 39d9df4ed6..804cdc7906 100644 --- a/core/src/interface.d.ts +++ b/core/src/interface.d.ts @@ -31,7 +31,7 @@ export * from './components/toggle/toggle-interface'; export * from './components/virtual-scroll/virtual-scroll-interface'; // Types from utils -export { Animation, AnimationBuilder } from './utils/animation/animation-interface'; +export { Animation, AnimationBuilder, AnimationCallbackOptions, AnimationDirection, AnimationFill, AnimationKeyFrames, AnimationLifecycle } from './utils/animation/animation-interface'; export * from './utils/overlays-interface'; export * from './global/config'; export { Gesture, GestureConfig, GestureDetail } from './utils/gesture'; diff --git a/packages/react/src/components/CreateAnimation.tsx b/packages/react/src/components/CreateAnimation.tsx new file mode 100644 index 0000000000..ceb9a8c2cf --- /dev/null +++ b/packages/react/src/components/CreateAnimation.tsx @@ -0,0 +1,162 @@ +import { Animation, AnimationCallbackOptions, AnimationDirection, AnimationFill, AnimationKeyFrames, AnimationLifecycle, createAnimation } from '@ionic/core'; +import React from 'react'; + +interface PartialPropertyValue { property: string; value: any; } +interface PropertyValue { property: string; fromValue: any; toValue: any; } + +export interface CreateAnimationProps { + delay?: number; + direction?: AnimationDirection; + duration?: number; + easing?: string; + fill?: AnimationFill; + iterations?: number; + id?: string; + + afterAddRead?: () => void; + afterAddWrite?: () => void; + afterClearStyles?: string[]; + afterStyles?: { [property: string]: any }; + afterAddClass?: string | string[]; + afterRemoveClass?: string | string[]; + + beforeAddRead?: () => void; + beforeAddWrite?: () => void; + beforeClearStyles?: string[]; + beforeStyles?: { [property: string]: any }; + beforeAddClass?: string | string[]; + beforeRemoveClass?: string | string[]; + + onFinish?: { callback: AnimationLifecycle; opts?: AnimationCallbackOptions; }; + + keyframes?: AnimationKeyFrames; + from?: PartialPropertyValue; + to?: PartialPropertyValue; + fromTo?: PropertyValue; + + play?: boolean; + pause?: boolean; + stop?: boolean; + destroy?: boolean; + + progressStart?: { forceLinearEasing: boolean, step?: number }; + progressStep?: { step: number }; + progressEnd?: { playTo: 0 | 1 | undefined, step: number, dur?: number }; +} + +export class CreateAnimation extends React.PureComponent { + private nodes: Map = new Map(); + animation: Animation; + + constructor(props: any) { + super(props); + + this.animation = createAnimation(props.id); + } + + setupAnimation(props: any) { + const animation = this.animation; + + if (this.nodes.size > 0) { + animation.addElement(Array.from(this.nodes.values())); + } + + checkConfig(animation, props); + checkPlayback(animation, props); + } + + componentDidMount() { + const props = this.props; + this.setupAnimation(props); + } + + componentDidUpdate(prevProps: any) { + const animation = this.animation; + + const props = this.props; + + checkConfig(animation, props, prevProps); + checkProgress(animation, props, prevProps); + checkPlayback(animation, props, prevProps); + } + + render() { + const { children } = this.props; + return ( + <> + {React.Children.map(children, ((child, id) => React.cloneElement(child as any, { ref: (el: any) => this.nodes.set(id, el) })))} + + ); + } +} + +const checkConfig = (animation: Animation, currentProps: any = {}, prevProps: any = {}) => { + const reservedProps = ['children', 'progressStart', 'progressStep', 'progressEnd', 'pause', 'stop', 'destroy', 'play', 'from', 'to', 'fromTo', 'onFinish']; + for (const key in currentProps) { + if ( + currentProps.hasOwnProperty(key) && + !reservedProps.includes(key) && + currentProps[key] !== prevProps[key] + ) { + (animation as any)[key]((currentProps as any)[key]); + } + } + + const fromValues = currentProps.from; + if (fromValues && fromValues !== prevProps.from) { + const values = (Array.isArray(fromValues)) ? fromValues : [fromValues]; + values.forEach(val => animation.from(val.property, val.value)); + } + + const toValues = currentProps.to; + if (toValues && toValues !== prevProps.to) { + const values = (Array.isArray(toValues)) ? toValues : [toValues]; + values.forEach(val => animation.to(val.property, val.value)); + } + + const fromToValues = currentProps.fromTo; + if (fromToValues && fromToValues !== prevProps.fromTo) { + const values = (Array.isArray(fromToValues)) ? fromToValues : [fromToValues]; + values.forEach(val => animation.fromTo(val.property, val.fromValue, val.toValue)); + } + + const onFinishValues = currentProps.onFinish; + if (onFinishValues && onFinishValues !== prevProps.onFinish) { + const values = (Array.isArray(onFinishValues)) ? onFinishValues : [onFinishValues]; + values.forEach(val => animation.onFinish(val.callback, val.opts)); + } +}; + +const checkProgress = (animation: Animation, currentProps: any = {}, prevProps: any = {}) => { + const { progressStart, progressStep, progressEnd } = currentProps; + + if (progressStart && (prevProps.progressStart?.forceLinearEasing !== progressStart?.forceLinearEasing || prevProps.progressStart?.step !== progressStart?.step)) { + animation.progressStart(progressStart.forceLinearEasing, progressStart.step); + } + + if (progressStep && prevProps.progressStep?.step !== progressStep?.step) { + animation.progressStep(progressStep.step); + } + + if (progressEnd && (prevProps.progressEnd?.playTo !== progressEnd?.playTo || prevProps.progressEnd?.step !== progressEnd?.step || prevProps?.dur !== progressEnd?.dur)) { + animation.progressEnd(progressEnd.playTo, progressEnd.step, progressEnd.dur); + } +}; + +const checkPlayback = (animation: Animation, currentProps: any = {}, prevProps: any = {}) => { + if (!prevProps.play && currentProps.play) { + animation.play(); + } + + if (!prevProps.pause && currentProps.pause) { + animation.pause(); + } + + if (!prevProps.stop && currentProps.stop) { + animation.stop(); + } + + if (!prevProps.destroy && currentProps.destroy) { + animation.destroy(); + } +}; diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index 6eac9a5580..fe04aedc9d 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -28,6 +28,9 @@ export { IonIcon } from './IonIcon'; export { isPlatform, getPlatforms, getConfig } from './utils'; export { RouterDirection } from './hrefprops'; +// Ionic Animations +export { CreateAnimation } from './CreateAnimation'; + // Icons that are used by internal components addIcons({ 'arrow-back-sharp': arrowBackSharp,