import type { TColorArg } from '../../color/typedefs'; import type { ObjectEvents } from '../../EventTypeDefs'; import type { TAnimation } from '../../util/animation/animate'; import { animate, animateColor } from '../../util/animation/animate'; import type { AnimationOptions, ArrayAnimationOptions, ColorAnimationOptions, ValueAnimationOptions, } from '../../util/animation/types'; import { StackedObject } from './StackedObject'; export abstract class AnimatableObject< EventSpec extends ObjectEvents = ObjectEvents > extends StackedObject { /** * List of properties to consider for animating colors. * @type String[] */ static colorProperties: string[] = ['fill', 'stroke', 'backgroundColor']; /** * Animates object's properties * @param {Record} animatable map of keys and end values * @param {Partial>} options * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#animation} * @return {Record>} map of animation contexts * * As object — multiple properties * * object.animate({ left: ..., top: ... }); * object.animate({ left: ..., top: ... }, { duration: ... }); */ animate( animatable: Record, options?: Partial> ): Record> { return Object.entries(animatable).reduce((acc, [key, endValue]) => { acc[key] = this._animate(key, endValue, options); return acc; }, {} as Record>); } /** * @private * @param {String} key Property to animate * @param {String} to Value to animate to * @param {Object} [options] Options object */ _animate( key: string, endValue: T, options: Partial> = {} ): TAnimation { const path = key.split('.'); const propIsColor = ( this.constructor as typeof AnimatableObject ).colorProperties.includes(path[path.length - 1]); const { abort, startValue, onChange, onComplete } = options; const animationOptions = { ...options, target: this, // path.reduce... is the current value in case start value isn't provided startValue: startValue ?? path.reduce((deep: any, key) => deep[key], this), endValue, abort: abort?.bind(this), onChange: ( value: number | number[] | string, valueProgress: number, durationProgress: number ) => { path.reduce((deep: Record, key, index) => { if (index === path.length - 1) { deep[key] = value; } return deep[key]; }, this); onChange && // @ts-expect-error generic callback arg0 is wrong onChange(value, valueProgress, durationProgress); }, onComplete: ( value: number | number[] | string, valueProgress: number, durationProgress: number ) => { this.setCoords(); onComplete && // @ts-expect-error generic callback arg0 is wrong onComplete(value, valueProgress, durationProgress); }, } as AnimationOptions; return ( propIsColor ? animateColor(animationOptions as ColorAnimationOptions) : animate( animationOptions as ValueAnimationOptions | ArrayAnimationOptions ) ) as TAnimation; } }