Files
2023-01-18 16:49:25 -05:00

206 lines
5.5 KiB
TypeScript

import type {
Animation,
AnimationCallbackOptions,
AnimationDirection,
AnimationFill,
AnimationKeyFrames,
AnimationLifecycle,
} from '@ionic/core/components';
import { createAnimation } from '@ionic/core/components';
import type { PropsWithChildren } from 'react';
import React from 'react';
// TODO(FW-2959): types
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[] | PartialPropertyValue;
to?: PartialPropertyValue[] | PartialPropertyValue;
fromTo?: PropertyValue[] | 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<PropsWithChildren<CreateAnimationProps>> {
nodes: Map<number, HTMLElement> = 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 (
// eslint-disable-next-line no-prototype-builtins
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();
}
};