Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 71 additions & 1 deletion core/src/utils/animation/animation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ interface AnimationOnFinishCallback {
o?: AnimationCallbackOptions;
}

type AnimationOnStopCallback = AnimationOnFinishCallback;

export const createAnimation = (animationId?: string): Animation => {
let _delay: number | undefined;
let _duration: number | undefined;
Expand Down Expand Up @@ -63,6 +65,7 @@ export const createAnimation = (animationId?: string): Animation => {
const id: string | undefined = animationId;
const onFinishCallbacks: AnimationOnFinishCallback[] = [];
const onFinishOneTimeCallbacks: AnimationOnFinishCallback[] = [];
const onStopOneTimeCallbacks: AnimationOnStopCallback[] = [];
const elements: HTMLElement[] = [];
const childAnimations: Animation[] = [];
const stylesheets: HTMLElement[] = [];
Expand Down Expand Up @@ -134,6 +137,35 @@ export const createAnimation = (animationId?: string): Animation => {
return numAnimationsRunning !== 0 && !paused;
};

/**
* @internal
* Remove a callback from a chosen callback array
* @param callbackToRemove: A reference to the callback that should be removed
* @param callbackObjects: An array of callbacks that callbackToRemove should be removed from.
*/
const clearCallback = (
callbackToRemove: AnimationLifecycle,
callbackObjects: AnimationOnFinishCallback[] | AnimationOnStopCallback[]
) => {
const index = callbackObjects.findIndex((callbackObject) => callbackObject.c === callbackToRemove);

if (index > -1) {
callbackObjects.splice(index, 1);
}
};

/**
* @internal
* Add a callback to be fired when an animation is stopped/cancelled.
* @param callback: A reference to the callback that should be fired
* @param opts: Any options associated with this particular callback
*/
const onStop = (callback: AnimationLifecycle, opts?: AnimationCallbackOptions) => {
onStopOneTimeCallbacks.push({ c: callback, o: opts });

return ani;
};

const onFinish = (callback: AnimationLifecycle, opts?: AnimationCallbackOptions) => {
const callbacks = opts?.oneTimeCallback ? onFinishOneTimeCallbacks : onFinishCallbacks;
callbacks.push({ c: callback, o: opts });
Expand Down Expand Up @@ -953,7 +985,34 @@ export const createAnimation = (animationId?: string): Animation => {
shouldCalculateNumAnimations = false;
}

onFinish(() => resolve(), { oneTimeCallback: true });
/**
* When one of these callbacks fires we
* need to clear the other's callback otherwise
* you can potentially get these callbacks
* firing multiple times if the play method
* is subsequently called.
* Example:
* animation.play() (onStop and onFinish callbacks are registered)
* animation.stop() (onStop callback is fired, onFinish is not)
* animation.play() (onStop and onFinish callbacks are registered)
* Total onStop callbacks: 1
* Total onFinish callbacks: 2
*/
const onStopCallback = () => {
clearCallback(onFinishCallback, onFinishOneTimeCallbacks);
resolve();
};
const onFinishCallback = () => {
clearCallback(onStopCallback, onStopOneTimeCallbacks);
resolve();
};

/**
* The play method resolves when an animation
* run either finishes or is cancelled.
*/
onFinish(onFinishCallback, { oneTimeCallback: true });
onStop(onStopCallback, { oneTimeCallback: true });

childAnimations.forEach((animation) => {
animation.play();
Expand All @@ -969,6 +1028,14 @@ export const createAnimation = (animationId?: string): Animation => {
});
};

/**
* Stops an animation and resets it state to the
* beginning. This does not fire any onFinish
* callbacks because the animation did not finish.
* However, since the animation was not destroyed
* (i.e. the animation could run again) we do not
* clear the onFinish callbacks.
*/
const stop = () => {
childAnimations.forEach((animation) => {
animation.stop();
Expand All @@ -980,6 +1047,9 @@ export const createAnimation = (animationId?: string): Animation => {
}

resetFlags();

onStopOneTimeCallbacks.forEach((onStopCallback) => onStopCallback.c(0, ani));
onStopOneTimeCallbacks.length = 0;
};

const from = (property: string, value: any) => {
Expand Down
18 changes: 18 additions & 0 deletions core/src/utils/animation/test/animation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@ import { processKeyframes } from '../animation-utils';
import { getTimeGivenProgression } from '../cubic-bezier';

describe('Animation Class', () => {
describe('play()', () => {
it('should resolve when the animation is cancelled', async () => {
// Tell Jest to expect 1 assertion for async code
expect.assertions(1);
const el = document.createElement('div');
const animation = createAnimation()
.addElement(el)
.fromTo('transform', 'translateX(0px)', 'translateX(100px)')
.duration(100000);

const animationPromise = animation.play();

animation.stop();

// Expect that the promise resolves and returns undefined
expect(animationPromise).resolves.toEqual(undefined);
});
});
describe('isRunning()', () => {
let animation: Animation;
beforeEach(() => {
Expand Down