diff --git a/core/src/components/action-sheet/animations/ios.enter.ts b/core/src/components/action-sheet/animations/ios.enter.ts index 2b9f1b6790b..40fd7abda81 100644 --- a/core/src/components/action-sheet/animations/ios.enter.ts +++ b/core/src/components/action-sheet/animations/ios.enter.ts @@ -10,11 +10,11 @@ export const iosEnterAnimation = (baseEl: HTMLElement): IonicAnimation => { const wrapperAnimation = createAnimation(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.01, 0.4); wrapperAnimation - .addElement(baseEl.querySelector('.action-sheet-wrapper')) + .addElement(baseEl.querySelector('.action-sheet-wrapper')!) .fromTo('transform', 'translateY(100%)', 'translateY(0%)'); return baseAnimation diff --git a/core/src/components/action-sheet/animations/ios.leave.ts b/core/src/components/action-sheet/animations/ios.leave.ts index 5021833c7c9..52e4ff45230 100644 --- a/core/src/components/action-sheet/animations/ios.leave.ts +++ b/core/src/components/action-sheet/animations/ios.leave.ts @@ -10,11 +10,11 @@ export const iosLeaveAnimation = (baseEl: HTMLElement): IonicAnimation => { const wrapperAnimation = createAnimation(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.4, 0); wrapperAnimation - .addElement(baseEl.querySelector('.action-sheet-wrapper')) + .addElement(baseEl.querySelector('.action-sheet-wrapper')!) .fromTo('transform', 'translateY(0%)', 'translateY(100%)'); return baseAnimation diff --git a/core/src/components/action-sheet/animations/md.enter.ts b/core/src/components/action-sheet/animations/md.enter.ts index 517c0598e63..3ad476c8aa9 100644 --- a/core/src/components/action-sheet/animations/md.enter.ts +++ b/core/src/components/action-sheet/animations/md.enter.ts @@ -10,11 +10,11 @@ export const mdEnterAnimation = (baseEl: HTMLElement): IonicAnimation => { const wrapperAnimation = createAnimation(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.01, 0.32); wrapperAnimation - .addElement(baseEl.querySelector('.action-sheet-wrapper')) + .addElement(baseEl.querySelector('.action-sheet-wrapper')!) .fromTo('transform', 'translateY(100%)', 'translateY(0%)'); return baseAnimation diff --git a/core/src/components/action-sheet/animations/md.leave.ts b/core/src/components/action-sheet/animations/md.leave.ts index bc5dcb24041..468b79f5010 100644 --- a/core/src/components/action-sheet/animations/md.leave.ts +++ b/core/src/components/action-sheet/animations/md.leave.ts @@ -10,11 +10,11 @@ export const mdLeaveAnimation = (baseEl: HTMLElement): IonicAnimation => { const wrapperAnimation = createAnimation(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.32, 0); wrapperAnimation - .addElement(baseEl.querySelector('.action-sheet-wrapper')) + .addElement(baseEl.querySelector('.action-sheet-wrapper')!) .fromTo('transform', 'translateY(0%)', 'translateY(100%)'); return baseAnimation diff --git a/core/src/components/alert/animations/ios.enter.ts b/core/src/components/alert/animations/ios.enter.ts index 5366ab58aa8..bbc5f8afafa 100644 --- a/core/src/components/alert/animations/ios.enter.ts +++ b/core/src/components/alert/animations/ios.enter.ts @@ -10,14 +10,14 @@ export const iosEnterAnimation = (baseEl: HTMLElement): IonicAnimation => { const wrapperAnimation = createAnimation(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.01, 0.3); wrapperAnimation - .addElement(baseEl.querySelector('.alert-wrapper')) + .addElement(baseEl.querySelector('.alert-wrapper')!) .keyframes([ - { offset: 0, opacity: 0.01, transform: 'scale(1.1)' }, - { offset: 1, opacity: 1, transform: 'scale(1)' } + { offset: 0, opacity: '0.01', transform: 'scale(1.1)' }, + { offset: 1, opacity: '1', transform: 'scale(1)' } ]); return baseAnimation diff --git a/core/src/components/alert/animations/ios.leave.ts b/core/src/components/alert/animations/ios.leave.ts index aa024c7a64c..84a4cdfce33 100644 --- a/core/src/components/alert/animations/ios.leave.ts +++ b/core/src/components/alert/animations/ios.leave.ts @@ -10,11 +10,11 @@ export const iosLeaveAnimation = (baseEl: HTMLElement): IonicAnimation => { const wrapperAnimation = createAnimation(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.3, 0); wrapperAnimation - .addElement(baseEl.querySelector('.alert-wrapper')) + .addElement(baseEl.querySelector('.alert-wrapper')!) .keyframes([ { offset: 0, opacity: 0.99, transform: 'scale(1)' }, { offset: 1, opacity: 0, transform: 'scale(0.9)' } diff --git a/core/src/components/alert/animations/md.enter.ts b/core/src/components/alert/animations/md.enter.ts index fcf2cdb2dfe..d9a6032cd76 100644 --- a/core/src/components/alert/animations/md.enter.ts +++ b/core/src/components/alert/animations/md.enter.ts @@ -10,14 +10,14 @@ export const mdEnterAnimation = (baseEl: HTMLElement): IonicAnimation => { const wrapperAnimation = createAnimation(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.01, 0.32); wrapperAnimation - .addElement(baseEl.querySelector('.alert-wrapper')) + .addElement(baseEl.querySelector('.alert-wrapper')!) .keyframes([ - { offset: 0, opacity: 0.01, transform: 'scale(0.9)' }, - { offset: 1, opacity: 1, transform: 'scale(1)' } + { offset: 0, opacity: '0.01', transform: 'scale(0.9)' }, + { offset: 1, opacity: '1', transform: 'scale(1)' } ]); return baseAnimation diff --git a/core/src/components/alert/animations/md.leave.ts b/core/src/components/alert/animations/md.leave.ts index 6754ca3ede4..2c12c3b3d8f 100644 --- a/core/src/components/alert/animations/md.leave.ts +++ b/core/src/components/alert/animations/md.leave.ts @@ -10,11 +10,11 @@ export const mdLeaveAnimation = (baseEl: HTMLElement): IonicAnimation => { const wrapperAnimation = createAnimation(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.32, 0); wrapperAnimation - .addElement(baseEl.querySelector('.alert-wrapper')) + .addElement(baseEl.querySelector('.alert-wrapper')!) .fromTo('opacity', 0.99, 0); return baseAnimation diff --git a/core/src/components/loading/animations/ios.enter.ts b/core/src/components/loading/animations/ios.enter.ts index c5c31143779..b01dace4284 100644 --- a/core/src/components/loading/animations/ios.enter.ts +++ b/core/src/components/loading/animations/ios.enter.ts @@ -10,11 +10,11 @@ export const iosEnterAnimation = (baseEl: HTMLElement): IonicAnimation => { const wrapperAnimation = createAnimation(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.01, 0.3); wrapperAnimation - .addElement(baseEl.querySelector('.loading-wrapper')) + .addElement(baseEl.querySelector('.loading-wrapper')!) .keyframes([ { offset: 0, opacity: 0.01, transform: 'scale(1.1)' }, { offset: 1, opacity: 1, transform: 'scale(1)' } diff --git a/core/src/components/loading/animations/ios.leave.ts b/core/src/components/loading/animations/ios.leave.ts index c8d22c99b42..a36395c2632 100644 --- a/core/src/components/loading/animations/ios.leave.ts +++ b/core/src/components/loading/animations/ios.leave.ts @@ -10,11 +10,11 @@ export const iosLeaveAnimation = (baseEl: HTMLElement): IonicAnimation => { const wrapperAnimation = createAnimation(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.3, 0); wrapperAnimation - .addElement(baseEl.querySelector('.loading-wrapper')) + .addElement(baseEl.querySelector('.loading-wrapper')!) .keyframes([ { offset: 0, opacity: 0.99, transform: 'scale(1)' }, { offset: 1, opacity: 0, transform: 'scale(0.9)' } diff --git a/core/src/components/loading/animations/md.enter.ts b/core/src/components/loading/animations/md.enter.ts index 6f02750a233..989537c0cd7 100644 --- a/core/src/components/loading/animations/md.enter.ts +++ b/core/src/components/loading/animations/md.enter.ts @@ -10,11 +10,11 @@ export const mdEnterAnimation = (baseEl: HTMLElement): IonicAnimation => { const wrapperAnimation = createAnimation(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.01, 0.32); wrapperAnimation - .addElement(baseEl.querySelector('.loading-wrapper')) + .addElement(baseEl.querySelector('.loading-wrapper')!) .keyframes([ { offset: 0, opacity: 0.01, transform: 'scale(1.1)' }, { offset: 1, opacity: 1, transform: 'scale(1)' } diff --git a/core/src/components/loading/animations/md.leave.ts b/core/src/components/loading/animations/md.leave.ts index fdbe41c85db..ba86ba84473 100644 --- a/core/src/components/loading/animations/md.leave.ts +++ b/core/src/components/loading/animations/md.leave.ts @@ -10,11 +10,11 @@ export const mdLeaveAnimation = (baseEl: HTMLElement): IonicAnimation => { const wrapperAnimation = createAnimation(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.32, 0); wrapperAnimation - .addElement(baseEl.querySelector('.loading-wrapper')) + .addElement(baseEl.querySelector('.loading-wrapper')!) .keyframes([ { offset: 0, opacity: 0.99, transform: 'scale(1)' }, { offset: 1, opacity: 0, transform: 'scale(0.9)' } diff --git a/core/src/components/menu-controller/menu-controller.ts b/core/src/components/menu-controller/menu-controller.ts index 74f4bebfa2a..2b30ebda599 100644 --- a/core/src/components/menu-controller/menu-controller.ts +++ b/core/src/components/menu-controller/menu-controller.ts @@ -150,6 +150,6 @@ export class MenuController { */ @Method() async registerAnimation(name: string, animation: AnimationBuilder | ((menu: MenuI) => IonicAnimation)) { - return menuController.registerAnimation(name, animation); + return menuController.registerAnimation(name, animation as any); } } diff --git a/core/src/components/menu/menu.tsx b/core/src/components/menu/menu.tsx index 7bd482e781b..6db7450c192 100644 --- a/core/src/components/menu/menu.tsx +++ b/core/src/components/menu/menu.tsx @@ -434,10 +434,10 @@ export class Menu implements ComponentInterface, MenuI { this.animation .easing('cubic-bezier(0.4, 0.0, 0.6, 1)') - .onFinish(() => this.afterAnimation(shouldOpen), { - oneTimeCallback: true - }) - .progressEnd(shouldComplete, newStepValue, 300); + .onFinish( + () => this.afterAnimation(shouldOpen), + { oneTimeCallback: true }) + .progressEnd(shouldComplete ? 1 : 0, newStepValue, 300); } private beforeAnimation(shouldOpen: boolean) { diff --git a/core/src/components/modal/animations/ios.enter.ts b/core/src/components/modal/animations/ios.enter.ts index 891926849c8..cb6d135f5dd 100644 --- a/core/src/components/modal/animations/ios.enter.ts +++ b/core/src/components/modal/animations/ios.enter.ts @@ -10,11 +10,11 @@ export const iosEnterAnimation = (baseEl: HTMLElement): IonicAnimation => { const wrapperAnimation = createAnimation(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.01, 0.4); wrapperAnimation - .addElement(baseEl.querySelector('.modal-wrapper')) + .addElement(baseEl.querySelector('.modal-wrapper')!) .beforeStyles({ 'opacity': 1 }) .fromTo('transform', 'translateY(100%)', 'translateY(0%)'); diff --git a/core/src/components/modal/animations/ios.leave.ts b/core/src/components/modal/animations/ios.leave.ts index e90b0dcf953..2728a6f8fb1 100644 --- a/core/src/components/modal/animations/ios.leave.ts +++ b/core/src/components/modal/animations/ios.leave.ts @@ -12,11 +12,11 @@ export const iosLeaveAnimation = (baseEl: HTMLElement): IonicAnimation => { const wrapperElRect = wrapperEl!.getBoundingClientRect(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.4, 0.0); wrapperAnimation - .addElement(wrapperEl) + .addElement(wrapperEl!) .beforeStyles({ 'opacity': 1 }) .fromTo('transform', 'translateY(0%)', `translateY(${(baseEl.ownerDocument as any).defaultView.innerHeight - wrapperElRect.top}px)`); diff --git a/core/src/components/modal/animations/md.enter.ts b/core/src/components/modal/animations/md.enter.ts index f4c84806ef1..15ae0be989b 100644 --- a/core/src/components/modal/animations/md.enter.ts +++ b/core/src/components/modal/animations/md.enter.ts @@ -10,11 +10,11 @@ export const mdEnterAnimation = (baseEl: HTMLElement): IonicAnimation => { const wrapperAnimation = createAnimation(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.01, 0.32); wrapperAnimation - .addElement(baseEl.querySelector('.modal-wrapper')) + .addElement(baseEl.querySelector('.modal-wrapper')!) .keyframes([ { offset: 0, opacity: 0.01, transform: 'translateY(40px)' }, { offset: 1, opacity: 1, transform: 'translateY(0px)' } diff --git a/core/src/components/modal/animations/md.leave.ts b/core/src/components/modal/animations/md.leave.ts index 92fa5e5445e..9e527da2409 100644 --- a/core/src/components/modal/animations/md.leave.ts +++ b/core/src/components/modal/animations/md.leave.ts @@ -8,10 +8,10 @@ export const mdLeaveAnimation = (baseEl: HTMLElement): IonicAnimation => { const baseAnimation = createAnimation(); const backdropAnimation = createAnimation(); const wrapperAnimation = createAnimation(); - const wrapperEl = baseEl.querySelector('.modal-wrapper'); + const wrapperEl = baseEl.querySelector('.modal-wrapper')!; backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.32, 0.0); wrapperAnimation diff --git a/core/src/components/nav/nav.tsx b/core/src/components/nav/nav.tsx index c1b1b08a526..abc22b16b37 100644 --- a/core/src/components/nav/nav.tsx +++ b/core/src/components/nav/nav.tsx @@ -986,7 +986,7 @@ export class Nav implements NavOutlet { newStepValue += getTimeGivenProgression(new Point(0, 0), new Point(0.32, 0.72), new Point(0, 1), new Point(1, 1), stepValue); } - this.sbAni.progressEnd(shouldComplete, newStepValue, dur); + (this.sbAni as IonicAnimation).progressEnd(shouldComplete ? 1 : 0, newStepValue, dur); } } diff --git a/core/src/components/picker/animations/ios.enter.ts b/core/src/components/picker/animations/ios.enter.ts index 1825e1410fe..093d80ec584 100644 --- a/core/src/components/picker/animations/ios.enter.ts +++ b/core/src/components/picker/animations/ios.enter.ts @@ -10,11 +10,11 @@ export const iosEnterAnimation = (baseEl: HTMLElement): IonicAnimation => { const wrapperAnimation = createAnimation(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.01, 0.26); wrapperAnimation - .addElement(baseEl.querySelector('.picker-wrapper')) + .addElement(baseEl.querySelector('.picker-wrapper')!) .fromTo('transform', 'translateY(100%)', 'translateY(0%)'); return baseAnimation diff --git a/core/src/components/picker/animations/ios.leave.ts b/core/src/components/picker/animations/ios.leave.ts index 25e6ce692c3..106df59b171 100644 --- a/core/src/components/picker/animations/ios.leave.ts +++ b/core/src/components/picker/animations/ios.leave.ts @@ -10,11 +10,11 @@ export const iosLeaveAnimation = (baseEl: HTMLElement): IonicAnimation => { const wrapperAnimation = createAnimation(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.26, 0.01); wrapperAnimation - .addElement(baseEl.querySelector('.picker-wrapper')) + .addElement(baseEl.querySelector('.picker-wrapper')!) .fromTo('transform', 'translateY(0%)', 'translateY(100%)'); return baseAnimation diff --git a/core/src/components/popover/animations/ios.enter.ts b/core/src/components/popover/animations/ios.enter.ts index de8b5387054..43a6510ccd8 100644 --- a/core/src/components/popover/animations/ios.enter.ts +++ b/core/src/components/popover/animations/ios.enter.ts @@ -103,11 +103,11 @@ export const iosEnterAnimation = (baseEl: HTMLElement, ev?: Event): IonicAnimati const wrapperAnimation = createAnimation(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.01, 0.08); wrapperAnimation - .addElement(baseEl.querySelector('.popover-wrapper')) + .addElement(baseEl.querySelector('.popover-wrapper')!) .fromTo('opacity', 0.01, 1); return baseAnimation diff --git a/core/src/components/popover/animations/ios.leave.ts b/core/src/components/popover/animations/ios.leave.ts index 82674d10499..8e3bf06bebc 100644 --- a/core/src/components/popover/animations/ios.leave.ts +++ b/core/src/components/popover/animations/ios.leave.ts @@ -10,11 +10,11 @@ export const iosLeaveAnimation = (baseEl: HTMLElement): IonicAnimation => { const wrapperAnimation = createAnimation(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.08, 0); wrapperAnimation - .addElement(baseEl.querySelector('.popover-wrapper')) + .addElement(baseEl.querySelector('.popover-wrapper')!) .fromTo('opacity', 0.99, 0); return baseAnimation diff --git a/core/src/components/popover/animations/md.enter.ts b/core/src/components/popover/animations/md.enter.ts index 7d8dc5bb831..278208b3611 100644 --- a/core/src/components/popover/animations/md.enter.ts +++ b/core/src/components/popover/animations/md.enter.ts @@ -85,11 +85,11 @@ export const mdEnterAnimation = (baseEl: HTMLElement, ev?: Event): IonicAnimatio const viewportAnimation = createAnimation(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.01, 0.32); wrapperAnimation - .addElement(baseEl.querySelector('.popover-wrapper')) + .addElement(baseEl.querySelector('.popover-wrapper')!) .fromTo('opacity', 0.01, 1); contentAnimation @@ -102,7 +102,7 @@ export const mdEnterAnimation = (baseEl: HTMLElement, ev?: Event): IonicAnimatio .fromTo('transform', 'scale(0.001)', 'scale(1)'); viewportAnimation - .addElement(baseEl.querySelector('.popover-viewport')) + .addElement(baseEl.querySelector('.popover-viewport')!) .fromTo('opacity', 0.01, 1); return baseAnimation diff --git a/core/src/components/popover/animations/md.leave.ts b/core/src/components/popover/animations/md.leave.ts index c33259a69a9..08335966d9b 100644 --- a/core/src/components/popover/animations/md.leave.ts +++ b/core/src/components/popover/animations/md.leave.ts @@ -10,11 +10,11 @@ export const mdLeaveAnimation = (baseEl: HTMLElement): IonicAnimation => { const wrapperAnimation = createAnimation(); backdropAnimation - .addElement(baseEl.querySelector('ion-backdrop')) + .addElement(baseEl.querySelector('ion-backdrop')!) .fromTo('opacity', 0.32, 0); wrapperAnimation - .addElement(baseEl.querySelector('.popover-wrapper')) + .addElement(baseEl.querySelector('.popover-wrapper')!) .fromTo('opacity', 0.99, 0); return baseAnimation diff --git a/core/src/components/router-outlet/route-outlet.tsx b/core/src/components/router-outlet/route-outlet.tsx index a84f3e5ed69..9d93140327d 100644 --- a/core/src/components/router-outlet/route-outlet.tsx +++ b/core/src/components/router-outlet/route-outlet.tsx @@ -96,7 +96,7 @@ export class RouterOutlet implements ComponentInterface, NavOutlet { newStepValue += getTimeGivenProgression(new Point(0, 0), new Point(0.32, 0.72), new Point(0, 1), new Point(1, 1), step); } - this.ani.progressEnd(shouldComplete, newStepValue, dur); + (this.ani as IonicAnimation).progressEnd(shouldComplete ? 1 : 0, newStepValue, dur); } } diff --git a/core/src/utils/animation/animation-interface.ts b/core/src/utils/animation/animation-interface.ts index 109c55a21c3..0a15542d29f 100644 --- a/core/src/utils/animation/animation-interface.ts +++ b/core/src/utils/animation/animation-interface.ts @@ -3,69 +3,239 @@ export interface Animation { elements: HTMLElement[]; childAnimations: Animation[]; - animationFinish(): void; - play(): Animation; - playAsync(): Promise; - playSync(): Animation; - pause(): Animation; - stop(): Animation; - destroy(): Animation; - - progressStart(forceLinearEasing: boolean): Animation; - progressStep(step: number): Animation; - progressEnd(shouldComplete: boolean, step: number, dur: number | undefined): Animation; + /** + * Play the animation + * + * If the `sync` options is `true`, the animation will play synchronously. This + * is the equivalent of running the animation + * with a duration of 0ms. + */ + play(opts?: AnimationPlayOptions): Promise; + + /** + * Pauses the animation + */ + pause(): void; + + /** + * Stop the animation and reset + * all elements to their initial state + */ + stop(): void; + + /** + * Destroy the animation and all child animations. + */ + destroy(): void; + + progressStart(forceLinearEasing: boolean): void; + progressStep(step: number): void; + progressEnd(playTo: 0 | 1 | undefined, step: number, dur?: number): void; from(property: string, value: any): Animation; to(property: string, value: any): Animation; fromTo(property: string, fromValue: any, toValue: any): Animation; - keyframes(keyframes: any[]): Animation; - addAnimation(animationToADd: Animation | Animation[] | undefined | null): Animation; - addElement(el: Element | Element[] | Node | Node[] | NodeList | undefined | null): Animation; + /** + * Set the keyframes for the animation. + */ + keyframes(keyframes: AnimationKeyFrames): Animation; + + /** + * Group one or more animations together to be controlled by a parent animation. + */ + addAnimation(animationToAdd: Animation | Animation[]): Animation; + + /** + * Add one or more elements to the animation + */ + addElement(el: Element | Element[] | Node | Node[] | NodeList): Animation; + + /** + * Sets the number of times the animation cycle + * should be played before stopping. + */ iterations(iterations: number): Animation; + + /** + * Sets how the animation applies styles to its + * elements before and after the animation's execution. + */ fill(fill: AnimationFill | undefined): Animation; + + /** + * Sets whether the animation should play forwards, + * backwards, or alternating back and forth. + */ direction(direction: AnimationDirection | undefined): Animation; - duration(duration: number): Animation; - easing(easing: string): Animation; - delay(delay: number): Animation; - parent(animation: Animation): Animation; - update(deep: boolean): Animation; - - getKeyframes(): any[]; - getDirection(): AnimationDirection | undefined; - getFill(): AnimationFill | undefined; - getDelay(): number | undefined; - getIterations(): number | undefined; - getEasing(): string | undefined; - getDuration(): number | undefined; + + /** + * Sets the length of time the animation takes + * to complete one cycle. + */ + duration(duration: number | undefined): Animation; + + /** + * Sets how the animation progresses through the + * duration of each cycle. + */ + easing(easing: string | undefined): Animation; + + /** + * Sets when an animation starts (in milliseconds). + */ + delay(delay: number | undefined): Animation; + + /** + * Get an array of keyframes for the animation. + */ + getKeyframes(): AnimationKeyFrames; + + /** + * Returns the animation's direction. + */ + getDirection(): AnimationDirection; + + /** + * Returns the animation's fill mode. + */ + getFill(): AnimationFill; + + /** + * Gets the animation's delay in milliseconds. + */ + getDelay(): number; + + /** + * Gets the number of iterations the animation will run. + */ + getIterations(): number; + + /** + * Returns the animation's easing. + */ + getEasing(): string; + + /** + * Gets the animation's duration in milliseconds. + */ + getDuration(): number; + + /** + * Returns the raw Web Animations object + * for all elements in an Animation. + * This will return an empty array on + * browsers that do not support + * the Web Animations API. + */ getWebAnimations(): any[]; + /** + * Add a function that performs a + * DOM read to be run after the + * animation end + */ afterAddRead(readFn: () => void): Animation; + + /** + * Add a function that performs a + * DOM write to be run after the + * animation end + */ afterAddWrite(writeFn: () => void): Animation; + + /** + * Clear CSS inline styles from the animation's + * elements after the animation ends. + */ afterClearStyles(propertyNames: string[]): Animation; + + /** + * Set CSS inline styles to the animation's + * elements after the animation ends. + */ afterStyles(styles: { [property: string]: any }): Animation; - afterRemoveClass(className: string | string[] | undefined): Animation; - afterAddClass(className: string | string[] | undefined): Animation; + /** + * Add CSS class to the animation's + * elements after the animation ends. + */ + afterAddClass(className: string | string[]): Animation; + + /** + * Remove CSS class from the animation's + * elements after the animation ends. + */ + afterRemoveClass(className: string | string[]): Animation; + + /** + * Add a function that performs a + * DOM read to be run before the + * animation starts + */ beforeAddRead(readFn: () => void): Animation; + + /** + * Add a function that performs a + * DOM write to be run before the + * animation starts + */ beforeAddWrite(writeFn: () => void): Animation; + + /** + * Clear CSS inline styles from the animation's + * elements before the animation begins. + */ beforeClearStyles(propertyNames: string[]): Animation; + + /** + * Set CSS inline styles to the animation's + * elements before the animation begins. + */ beforeStyles(styles: { [property: string]: any }): Animation; - beforeRemoveClass(className: string | string[] | undefined): Animation; - beforeAddClass(className: string | string[] | undefined): Animation; - onFinish(callback: (didComplete: boolean, animation: Animation) => void, opts?: AnimationOnFinishOptions): Animation; - clearOnFinish(): Animation; -} + /** + * Add a class to the animation's + * elements before the animation starts + */ + beforeAddClass(className: string | string[]): Animation; -export interface AnimationOnFinishCallback { - callback: (didComplete: boolean, animation: Animation) => void; - opts: AnimationOnFinishOptions; + /** + * Remove a class from the animation's + * elements before the animation starts + */ + beforeRemoveClass(className: string | string[]): Animation; + + /** + * Add a callback to be run + * upon the animation ending + */ + onFinish(callback: AnimationLifecycle, opts?: AnimationOnFinishOptions): Animation; + + /** @deprecated */ + playAsync(): Promise; + /** @deprecated */ + playSync(): void; } +export type AnimationLifecycle = (currentStep: 0 | 1, animation: Animation) => void; + export interface AnimationOnFinishOptions { oneTimeCallback: boolean; } +export type AnimationKeyFrames = AnimationKeyFrame[]; + +export interface AnimationKeyFrame extends AnimationStyles { + offset: number; +} + +export type AnimationStyles = Record; + +export interface AnimationPlayOptions { + sync: boolean; +} + +export type AnimationPlayTo = 'start' | 'end'; export type AnimationDirection = 'normal' | 'reverse' | 'alternate' | 'alternate-reverse'; export type AnimationFill = 'auto' | 'none' | 'forwards' | 'backwards' | 'both'; diff --git a/core/src/utils/animation/animation.ts b/core/src/utils/animation/animation.ts index b00cc9a5bb1..35e5451cc14 100644 --- a/core/src/utils/animation/animation.ts +++ b/core/src/utils/animation/animation.ts @@ -2,21 +2,40 @@ import { raf } from '../helpers'; -import { Animation, AnimationDirection, AnimationFill, AnimationOnFinishCallback, AnimationOnFinishOptions } from './animation-interface'; +import { Animation, AnimationDirection, AnimationFill, AnimationKeyFrame, AnimationKeyFrames, AnimationLifecycle, AnimationOnFinishOptions, AnimationPlayOptions } from './animation-interface'; import { addClassToArray, animationEnd, createKeyframeStylesheet, generateKeyframeName, generateKeyframeRules, removeStyleProperty, setStyleProperty } from './animation-utils'; -export const createAnimation = () => { +interface AnimationOnFinishCallback { + c: AnimationLifecycle; + o?: AnimationOnFinishOptions; +} + +interface AnimationInternal extends Animation { + /** + * Sets the parent animation. + */ + parent(animation: Animation): Animation; + + /** + * Updates any existing animations. + */ + update(deep: boolean): Animation; + + animationFinish(): void; +} + +export const createAnimation = (): Animation => { let _delay: number | undefined; let _duration: number | undefined; let _easing: string | undefined; let _iterations: number | undefined; let _fill: AnimationFill | undefined; let _direction: AnimationDirection | undefined; - let _keyframes: any[] = []; + let _keyframes: AnimationKeyFrames = []; let beforeAddClasses: string[] = []; let beforeRemoveClasses: string[] = []; let initialized = false; - let parentAnimation: Animation | undefined; + let parentAnimation: AnimationInternal | undefined; let beforeStylesValue: { [property: string]: any } = {}; let afterAddClasses: string[] = []; let afterRemoveClasses: string[] = []; @@ -32,12 +51,12 @@ export const createAnimation = () => { let finished = false; let shouldCalculateNumAnimations = true; let keyframeName: string | undefined; - let ani: Animation; + let ani: AnimationInternal; const onFinishCallbacks: AnimationOnFinishCallback[] = []; const onFinishOneTimeCallbacks: AnimationOnFinishCallback[] = []; const elements: HTMLElement[] = []; - const childAnimations: Animation[] = []; + const childAnimations: AnimationInternal[] = []; const stylesheets: HTMLElement[] = []; const _beforeAddReadFunctions: any[] = []; const _beforeAddWriteFunctions: any[] = []; @@ -48,20 +67,10 @@ export const createAnimation = () => { const supportsWebAnimations = (typeof (Element as any) === 'function') && (typeof (Element as any).prototype!.animate === 'function') && supportsAnimationEffect; const ANIMATION_END_FALLBACK_PADDING_MS = 100; - /** - * Returns the raw Web Animations object - * for all elements in an Animation. - * This will return an empty array on - * browsers that do not support - * the Web Animations API. - */ const getWebAnimations = () => { return webAnimations; }; - /** - * Destroy the animation and all child animations. - */ const destroy = () => { childAnimations.forEach(childAnimation => { childAnimation.destroy(); @@ -92,20 +101,13 @@ export const createAnimation = () => { cleanUpStyleSheets(); }; - /** - * Add a callback to be run - * upon the animation ending - */ - const onFinish = (callback: any, opts?: AnimationOnFinishOptions) => { + const onFinish = (callback: AnimationLifecycle, opts?: AnimationOnFinishOptions) => { const callbacks = (opts && opts.oneTimeCallback) ? onFinishOneTimeCallbacks : onFinishCallbacks; - callbacks.push({ callback, opts } as AnimationOnFinishCallback); + callbacks.push({ c: callback, o: opts }); return ani; }; - /** - * Clears all callbacks - */ const clearOnFinish = () => { onFinishCallbacks.length = 0; onFinishOneTimeCallbacks.length = 0; @@ -120,7 +122,7 @@ export const createAnimation = () => { */ const cleanUpElements = () => { if (supportsWebAnimations) { - getWebAnimations().forEach(animation => { + webAnimations.forEach(animation => { animation.cancel(); }); @@ -160,64 +162,36 @@ export const createAnimation = () => { stylesheets.length = 0; }; - /** - * Add a function that performs a - * DOM read to be run before the - * animation starts - */ const beforeAddRead = (readFn: () => void) => { _beforeAddReadFunctions.push(readFn); return ani; }; - /** - * Add a function that performs a - * DOM write to be run before the - * animation starts - */ const beforeAddWrite = (writeFn: () => void) => { _beforeAddWriteFunctions.push(writeFn); return ani; }; - /** - * Add a function that performs a - * DOM read to be run after the - * animation end - */ const afterAddRead = (readFn: () => void) => { _afterAddReadFunctions.push(readFn); return ani; }; - /** - * Add a function that performs a - * DOM write to be run after the - * animation end - */ const afterAddWrite = (writeFn: () => void) => { _afterAddWriteFunctions.push(writeFn); return ani; }; - /** - * Add a class to the animation's - * elements before the animation starts - */ const beforeAddClass = (className: string | string[] | undefined) => { beforeAddClasses = addClassToArray(beforeAddClasses, className); return ani; }; - /** - * Remove a class from the animation's - * elements before the animation starts - */ const beforeRemoveClass = (className: string | string[] | undefined) => { beforeRemoveClasses = addClassToArray(beforeRemoveClasses, className); @@ -245,40 +219,24 @@ export const createAnimation = () => { return ani; }; - /** - * Add CSS class to the animation's - * elements after the animation ends. - */ const afterAddClass = (className: string | string[] | undefined) => { afterAddClasses = addClassToArray(afterAddClasses, className); return ani; }; - /** - * Remove CSS class from the animation's - * elements after the animation ends. - */ const afterRemoveClass = (className: string | string[] | undefined) => { afterRemoveClasses = addClassToArray(afterRemoveClasses, className); return ani; }; - /** - * Set CSS inline styles to the animation's - * elements after the animation ends. - */ const afterStyles = (styles: { [property: string]: any } = {}) => { afterStylesValue = styles; return ani; }; - /** - * Clear CSS inline styles from the animation's - * elements after the animation ends. - */ const afterClearStyles = (propertyNames: string[] = []) => { for (const property of propertyNames) { afterStylesValue[property] = ''; @@ -287,9 +245,6 @@ export const createAnimation = () => { return ani; }; - /** - * Returns the animation's fill mode. - */ const getFill = () => { if (_fill !== undefined) { return _fill; } if (parentAnimation) { return parentAnimation.getFill(); } @@ -297,9 +252,6 @@ export const createAnimation = () => { return 'both'; }; - /** - * Returns the animation's direction. - */ const getDirection = () => { if (forceDirectionValue !== undefined) { return forceDirectionValue; } if (_direction !== undefined) { return _direction; } @@ -309,9 +261,6 @@ export const createAnimation = () => { }; - /** - * Returns the animation's easing. - */ const getEasing = () => { if (shouldForceLinearEasing) { return 'linear'; } if (_easing !== undefined) { return _easing; } @@ -320,9 +269,6 @@ export const createAnimation = () => { return 'linear'; }; - /** - * Gets the animation's duration in milliseconds. - */ const getDuration = () => { if (shouldForceSyncPlayback) { return 0; } if (forceDurationValue !== undefined) { return forceDurationValue; } @@ -332,9 +278,6 @@ export const createAnimation = () => { return 0; }; - /** - * Gets the number of iterations the animation will run. - */ const getIterations = () => { if (_iterations !== undefined) { return _iterations; } if (parentAnimation) { return parentAnimation.getIterations(); } @@ -342,9 +285,6 @@ export const createAnimation = () => { return 1; }; - /** - * Gets the animation's delay in milliseconds. - */ const getDelay = () => { if (forceDelayValue !== undefined) { return forceDelayValue; } if (_delay !== undefined) { return _delay; } @@ -353,17 +293,10 @@ export const createAnimation = () => { return 0; }; - /** - * Get an array of keyframes for the animation. - */ const getKeyframes = () => { return _keyframes; }; - /** - * Sets whether the animation should play forwards, - * backwards, or alternating back and forth. - */ const direction = (animationDirection: AnimationDirection) => { _direction = animationDirection; @@ -372,22 +305,14 @@ export const createAnimation = () => { return ani; }; - /** - * Sets how the animation applies styles to its - * elements before and after the animation's execution. - */ const fill = (animationFill: AnimationFill) => { _fill = animationFill; update(true); return ani; - }; - /** - * Sets when an animation starts (in milliseconds). - */ const delay = (animationDelay: number) => { _delay = animationDelay; @@ -396,10 +321,6 @@ export const createAnimation = () => { return ani; }; - /** - * Sets how the animation progresses through the - * duration of each cycle. - */ const easing = (animationEasing: string) => { _easing = animationEasing; @@ -408,10 +329,6 @@ export const createAnimation = () => { return ani; }; - /** - * Sets the length of time the animation takes - * to complete one cycle. - */ const duration = (animationDuration: number) => { /** * CSS Animation Durations of 0ms work fine on Chrome @@ -429,10 +346,6 @@ export const createAnimation = () => { return ani; }; - /** - * Sets the number of times the animation cycle - * should be played before stopping. - */ const iterations = (animationIterations: number) => { _iterations = animationIterations; @@ -441,18 +354,12 @@ export const createAnimation = () => { return ani; }; - /** - * Sets the parent animation. - */ - const parent = (animation: Animation) => { + const parent = (animation: AnimationInternal) => { parentAnimation = animation; return ani; }; - /** - * Add one or more elements to the animation - */ const addElement = (el: Element | Element[] | Node | Node[] | NodeList | undefined | null) => { if (el != null) { @@ -470,31 +377,22 @@ export const createAnimation = () => { return ani; }; - /** - * Group one or more animations together to be controlled by a parent animation. - */ - const addAnimation = (animationToAdd: Animation | Animation[] | undefined | null) => { - if (animationToAdd != null) { - const parentAnim = ani; - const animationsToAdd = animationToAdd as Animation[]; - if (animationsToAdd.length >= 0) { - for (const animation of animationsToAdd) { - animation.parent(parentAnim); + const addAnimation = (animationToAdd: AnimationInternal | AnimationInternal[]) => { + if ((animationToAdd as any) != null) { + if (Array.isArray(animationToAdd)) { + for (const animation of animationToAdd) { + animation.parent(ani); childAnimations.push(animation); } } else { - (animationToAdd as Animation).parent(parentAnim); - childAnimations.push(animationToAdd as Animation); + animationToAdd.parent(ani); + childAnimations.push(animationToAdd); } } - return ani; }; - /** - * Set the keyframes for the animation. - */ - const keyframes = (keyframeValues: any[]) => { + const keyframes = (keyframeValues: AnimationKeyFrames) => { _keyframes = keyframeValues; return ani; @@ -526,7 +424,7 @@ export const createAnimation = () => { const removeClasses = beforeRemoveClasses; const styles = beforeStylesValue; - elements.forEach((el: HTMLElement) => { + elements.forEach(el => { const elementClassList = el.classList; addClasses.forEach(c => elementClassList.add(c)); @@ -575,7 +473,7 @@ export const createAnimation = () => { const removeClasses = afterRemoveClasses; const styles = afterStylesValue; - elements.forEach((el: HTMLElement) => { + elements.forEach(el => { const elementClassList = el.classList; addClasses.forEach(c => elementClassList.add(c)); @@ -598,14 +496,14 @@ export const createAnimation = () => { runAfterWrite(); runAfterStyles(); - const didComplete = willComplete; + const currentStep = willComplete ? 1 : 0; onFinishCallbacks.forEach(onFinishCallback => { - onFinishCallback.callback(didComplete, ani); + return onFinishCallback.c(currentStep, ani); }); onFinishOneTimeCallbacks.forEach(onFinishCallback => { - onFinishCallback.callback(didComplete, ani); + return onFinishCallback.c(currentStep, ani); }); onFinishOneTimeCallbacks.length = 0; @@ -637,16 +535,15 @@ export const createAnimation = () => { const stylesheet = createKeyframeStylesheet(keyframeName, keyframeRules, element); stylesheets.push(stylesheet); - setStyleProperty(element, 'animation-duration', (getDuration() !== undefined) ? `${getDuration()}ms` : null); - setStyleProperty(element, 'animation-timing-function', getEasing() || null); - setStyleProperty(element, 'animation-delay', (getDelay() !== undefined) ? `${getDelay()}ms` : null); - setStyleProperty(element, 'animation-fill-mode', getFill() || null); - setStyleProperty(element, 'animation-direction', getDirection() || null); + setStyleProperty(element, 'animation-duration', `${getDuration()}ms`); + setStyleProperty(element, 'animation-timing-function', getEasing()); + setStyleProperty(element, 'animation-delay', `${getDelay()}ms`); + setStyleProperty(element, 'animation-fill-mode', getFill()); + setStyleProperty(element, 'animation-direction', getDirection()); - const iterationsCount = - (getIterations() !== undefined) ? - (getIterations() === Infinity) ? 'infinite' : getIterations()!.toString() - : null; + const iterationsCount = (getIterations() === Infinity) + ? 'infinite' + : getIterations().toString(); setStyleProperty(element, 'animation-iteration-count', iterationsCount); setStyleProperty(element, 'animation-play-state', 'paused'); @@ -678,7 +575,7 @@ export const createAnimation = () => { webAnimations.push(animation); }); - if (getWebAnimations().length > 0) { + if (webAnimations.length > 0) { webAnimations[0].onfinish = () => { animationFinish(); }; @@ -703,7 +600,7 @@ export const createAnimation = () => { const setAnimationStep = (step: number) => { step = Math.min(Math.max(step, 0), 0.999); if (supportsWebAnimations) { - getWebAnimations().forEach(animation => { + webAnimations.forEach(animation => { animation.currentTime = animation.effect.getComputedTiming().delay + (getDuration()! * step); animation.pause(); }); @@ -722,7 +619,7 @@ export const createAnimation = () => { }; const updateWebAnimation = () => { - getWebAnimations().forEach(animation => { + webAnimations.forEach(animation => { animation.effect.updateTiming({ delay: getDelay(), duration: getDuration(), @@ -738,16 +635,15 @@ export const createAnimation = () => { elements.forEach(element => { raf(() => { setStyleProperty(element, 'animation-name', keyframeName || null); - setStyleProperty(element, 'animation-duration', (getDuration() !== undefined) ? `${getDuration()}ms` : null); - setStyleProperty(element, 'animation-timing-function', getEasing() || null); - setStyleProperty(element, 'animation-delay', (getDelay() !== undefined) ? `${getDelay()}ms` : null); + setStyleProperty(element, 'animation-duration', `${getDuration()}ms`); + setStyleProperty(element, 'animation-timing-function', getEasing()); + setStyleProperty(element, 'animation-delay', `${getDelay()}ms`); setStyleProperty(element, 'animation-fill-mode', getFill() || null); setStyleProperty(element, 'animation-direction', getDirection() || null); - const iterationsCount = - (getIterations() !== undefined) ? - (getIterations() === Infinity) ? 'infinite' : getIterations()!.toString() - : null; + const iterationsCount = (getIterations() === Infinity) + ? 'infinite' + : getIterations().toString(); setStyleProperty(element, 'animation-iteration-count', iterationsCount); @@ -762,9 +658,6 @@ export const createAnimation = () => { }); }; - /** - * Updates any existing animations. - */ const update = (deep = false, toggleAnimationName = true) => { if (deep) { childAnimations.forEach(animation => { @@ -803,19 +696,15 @@ export const createAnimation = () => { childAnimations.forEach(animation => { animation.progressStep(step); }); - - if (getDuration() !== undefined) { - setAnimationStep(step); - } - + setAnimationStep(step); return ani; }; - const progressEnd = (shouldComplete: boolean, step: number, dur: number | undefined) => { + const progressEnd = (playTo: 0 | 1 | undefined, step: number, dur?: number) => { shouldForceLinearEasing = false; childAnimations.forEach(animation => { - animation.progressEnd(shouldComplete, step, dur); + animation.progressEnd(playTo, step, dur); }); if (dur !== undefined) { @@ -824,9 +713,9 @@ export const createAnimation = () => { finished = false; - willComplete = shouldComplete; + willComplete = playTo === 1; - if (!shouldComplete) { + if (!willComplete) { forceDirectionValue = (getDirection() === 'reverse') ? 'normal' : 'reverse'; if (supportsWebAnimations) { @@ -862,7 +751,7 @@ export const createAnimation = () => { const pauseAnimation = () => { if (initialized) { if (supportsWebAnimations) { - getWebAnimations().forEach(animation => { + webAnimations.forEach(animation => { animation.pause(); }); } else { @@ -873,9 +762,6 @@ export const createAnimation = () => { } }; - /** - * Pause the animation. - */ const pause = () => { childAnimations.forEach(animation => { animation.pause(); @@ -886,30 +772,12 @@ export const createAnimation = () => { return ani; }; - /** - * Play the animation asynchronously. - * This returns a promise that resolves - * when the animation has ended. - */ const playAsync = () => { - return new Promise(resolve => { - onFinish(resolve, { oneTimeCallback: true }); - play(); - - return ani; - }); + return play(); }; - /** - * Play the animation synchronously. This - * is the equivalent of running the animation - * with a duration of 0ms. - */ const playSync = () => { - shouldForceSyncPlayback = true; - - onFinish(() => shouldForceSyncPlayback = false, { oneTimeCallback: true }); - play(); + play({ sync: true }); return ani; }; @@ -984,7 +852,7 @@ export const createAnimation = () => { }; const playWebAnimations = () => { - getWebAnimations().forEach(animation => { + webAnimations.forEach(animation => { animation.play(); }); @@ -1001,41 +869,41 @@ export const createAnimation = () => { } }; - /** - * Play the animation - */ - const play = () => { - if (!initialized) { - initializeAnimation(); - } + const play = (opts?: AnimationPlayOptions) => { + return new Promise(resolve => { + if (opts && opts.sync) { + shouldForceSyncPlayback = true; - if (finished) { - resetAnimation(); - finished = false; - } + onFinish(() => shouldForceSyncPlayback = false, { oneTimeCallback: true }); + } + if (!initialized) { + initializeAnimation(); + } - if (shouldCalculateNumAnimations) { - numAnimationsRunning = childAnimations.length + 1; - shouldCalculateNumAnimations = false; - } + if (finished) { + resetAnimation(); + finished = false; + } - childAnimations.forEach(animation => { - animation.play(); - }); + if (shouldCalculateNumAnimations) { + numAnimationsRunning = childAnimations.length + 1; + shouldCalculateNumAnimations = false; + } - if (supportsWebAnimations) { - playWebAnimations(); - } else { - playCSSAnimations(); - } + onFinish(() => resolve(), { oneTimeCallback: true }); - return ani; + childAnimations.forEach(animation => { + animation.play(); + }); + + if (supportsWebAnimations) { + playWebAnimations(); + } else { + playCSSAnimations(); + } + }); }; - /** - * Stop the animation and reset - * all elements to their initial state - */ const stop = () => { childAnimations.forEach(animation => { animation.stop(); @@ -1045,14 +913,12 @@ export const createAnimation = () => { cleanUpElements(); initialized = false; } - - return ani; }; const from = (property: string, value: any) => { - const firstFrame = _keyframes[0]; + const firstFrame = _keyframes[0] as AnimationKeyFrame | undefined; - if (firstFrame != null && (firstFrame.offset === undefined || firstFrame.offset === 0)) { + if (firstFrame !== undefined && firstFrame.offset === 0) { firstFrame[property] = value; } else { _keyframes = [ @@ -1065,9 +931,9 @@ export const createAnimation = () => { }; const to = (property: string, value: any) => { - const lastFrame = _keyframes[_keyframes.length - 1]; + const lastFrame = _keyframes[_keyframes.length - 1] as AnimationKeyFrame | undefined; - if (lastFrame != null && (lastFrame.offset === undefined || lastFrame.offset === 1)) { + if (lastFrame !== undefined && lastFrame.offset === 1) { lastFrame[property] = value; } else { _keyframes = [ @@ -1128,10 +994,9 @@ export const createAnimation = () => { beforeRemoveClass, beforeAddClass, onFinish, - clearOnFinish, progressStart, progressStep, progressEnd - } as Animation; + }; }; diff --git a/core/src/utils/animation/test/animation.spec.ts b/core/src/utils/animation/test/animation.spec.ts index 91b8c8e0ae9..828a382e533 100644 --- a/core/src/utils/animation/test/animation.spec.ts +++ b/core/src/utils/animation/test/animation.spec.ts @@ -1,93 +1,94 @@ import { createAnimation } from '../animation'; import { getTimeGivenProgression, Point } from '../cubic-bezier'; +import { Animation } from '../animation-interface'; describe('Animation Class', () => { - + describe('addElement()', () => { - let animation; + let animation: Animation; beforeEach(() => { animation = createAnimation(); }); - + it('should add 1 element', () => { const el = document.createElement('p'); animation.addElement(el); expect(animation.elements.length).toEqual(1); }); - + it('should add multiple elements', () => { const els = [ document.createElement('p'), document.createElement('p'), document.createElement('p') ]; - + animation.addElement(els); expect(animation.elements.length).toEqual(els.length); }); - + it('should not error when trying to add null or undefined', () => { const el = document.createElement('p'); animation.addElement(el); animation.addElement(null); animation.addElement(undefined); - + expect(animation.elements.length).toEqual(1); }); }); - + describe('addAnimation()', () => { - let animation; + let animation: Animation; beforeEach(() => { animation = createAnimation(); }); - + it('should add 1 animation', () => { const newAnimation = createAnimation(); animation.addAnimation(newAnimation); expect(animation.childAnimations.length).toEqual(1); }); - + it('should add multiple animations', () => { animation.addAnimation([createAnimation(), createAnimation(), createAnimation()]); expect(animation.childAnimations.length).toEqual(3); }); - + it('should not error when trying to add null or undefined', () => { animation.addAnimation(null); animation.addAnimation(undefined); - + expect(animation.childAnimations.length).toEqual(0); }) }); - + describe('Animation Keyframes', () => { - let animation; + let animation: Animation; beforeEach(() => { - animation = createAnimation('my-animation'); + animation = createAnimation(); }); - + it('should generate a keyframe', () => { animation.keyframes([ { transform: 'scale(1)', opacity: 1, offset: 0 }, { transform: 'scale(0.5)', opacity: 0.5, offset: 0.5 }, { transform: 'scale(0)', opacity: 0, offset: 1 } ]); - + expect(animation.getKeyframes().length).toEqual(3); }); - + it('should set the from keyframe properly', () => { animation .from('opacity', 0) .from('background', 'red') .from('color', 'purple'); - + const keyframes = animation.getKeyframes(); expect(keyframes.length).toEqual(1); @@ -98,13 +99,13 @@ describe('Animation Class', () => { offset: 0 }); }); - + it('should set the to keyframe properly', () => { animation .to('opacity', 0) .to('background', 'red') .to('color', 'purple'); - + const keyframes = animation.getKeyframes(); expect(keyframes.length).toEqual(1); expect(keyframes[0]).toEqual({ @@ -114,7 +115,7 @@ describe('Animation Class', () => { offset: 1 }); }); - + it('should mix keyframes and fromTo properly', () => { animation .keyframes([ @@ -123,7 +124,7 @@ describe('Animation Class', () => { { offset: 1, background: 'green' } ]) .fromTo('opacity', 0, 1) - + const keyframes = animation.getKeyframes(); expect(keyframes.length).toEqual(3); expect(keyframes[0]).toEqual({ @@ -131,12 +132,12 @@ describe('Animation Class', () => { background: 'red', offset: 0 }); - + expect(keyframes[1]).toEqual({ background: 'blue', offset: 0.99 }); - + expect(keyframes[2]).toEqual({ opacity: 1, background: 'green', @@ -144,164 +145,164 @@ describe('Animation Class', () => { }); }); }); - + describe('Animation Config Methods', () => { - let animation; + let animation: Animation; beforeEach(() => { animation = createAnimation(); }); - + it('should get "linear" when easing not set', () => { expect(animation.getEasing()).toEqual("linear"); }); - + it('should get parent easing when child easing is not set', () => { const childAnimation = createAnimation(); animation .addAnimation(childAnimation) .easing('ease-in-out'); - + expect(childAnimation.getEasing()).toEqual('ease-in-out'); }); - + it('should get prefer child easing over parent easing', () => { const childAnimation = createAnimation(); childAnimation.easing('linear'); - + animation .addAnimation(childAnimation) .easing('ease-in-out'); - + expect(childAnimation.getEasing()).toEqual('linear'); }); - - it('should get linear easing when forceLinear is set', () => { + + it('should get linear easing when forceLinear is set', () => { animation.easing('ease-in-out'); - + animation.progressStart(true); expect(animation.getEasing()).toEqual('linear'); - + animation.progressEnd(); expect(animation.getEasing()).toEqual('ease-in-out'); }); - + it('should get 0 when duration not set', () => { expect(animation.getDuration()).toEqual(0); }); - + it('should get parent duration when child duration is not set', () => { const childAnimation = createAnimation(); animation .addAnimation(childAnimation) .duration(500); - + expect(childAnimation.getDuration()).toEqual(500); }); - + it('should get prefer child duration over parent duration', () => { const childAnimation = createAnimation(); childAnimation.duration(500); - + animation .addAnimation(childAnimation) .duration(1000); - + expect(childAnimation.getDuration()).toEqual(500); }); - + it('should get 0 when delay not set', () => { expect(animation.getDelay()).toEqual(0); }); - + it('should get parent delay when child delay is not set', () => { const childAnimation = createAnimation(); animation .addAnimation(childAnimation) .delay(500); - + expect(childAnimation.getDelay()).toEqual(500); }); - + it('should get prefer child delay over parent delay', () => { const childAnimation = createAnimation(); childAnimation.delay(500); - + animation .addAnimation(childAnimation) .delay(1000); - + expect(childAnimation.getDelay()).toEqual(500); }); - + it('should get 1 when iterations not set', () => { expect(animation.getIterations()).toEqual(1); }); - + it('should get parent iterations when child iterations is not set', () => { const childAnimation = createAnimation(); animation .addAnimation(childAnimation) .iterations(2); - + expect(childAnimation.getIterations()).toEqual(2); }); - + it('should get prefer child iterations over parent iterations', () => { const childAnimation = createAnimation(); childAnimation.iterations(2); - + animation .addAnimation(childAnimation) .iterations(1); - + expect(childAnimation.getIterations()).toEqual(2); - }); - + }); + it('should get "both" when fill not set', () => { - expect(animation.getFill()).toEqual("both"); + expect(animation.getFill()).toEqual('both'); }); - + it('should get parent fill when child fill is not set', () => { const childAnimation = createAnimation(); animation .addAnimation(childAnimation) - .fill('forwards'); - - expect(childAnimation.getFill()).toEqual('forwards'); + .fill('both'); + + expect(childAnimation.getFill()).toEqual('both'); }); - + it('should get prefer child fill over parent fill', () => { const childAnimation = createAnimation(); childAnimation.fill('none'); - + animation .addAnimation(childAnimation) .fill('forwards'); - + expect(childAnimation.getFill()).toEqual('none'); }); - + it('should get "normal" when direction not set', () => { - expect(animation.getDirection()).toEqual('normal'); + expect(animation.getDirection()).toEqual("normal"); }); - + it('should get parent direction when child direction is not set', () => { const childAnimation = createAnimation(); animation .addAnimation(childAnimation) .direction('alternate'); - + expect(childAnimation.getDirection()).toEqual('alternate'); }); - + it('should get prefer child direction over parent direction', () => { const childAnimation = createAnimation(); childAnimation.direction('alternate-reverse'); - + animation .addAnimation(childAnimation) .direction('normal'); - + expect(childAnimation.getDirection()).toEqual('alternate-reverse'); }); @@ -317,12 +318,12 @@ describe('cubic-bezier conversion', () => { new Point(0, 1), new Point(1, 1) ]; - + shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.5), 0.16); shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.97), 0.56); shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.33), 0.11); }); - + it('cubic-bezier(1, 0, 0.68, 0.28)', () => { const equation = [ new Point(0, 0), @@ -330,12 +331,12 @@ describe('cubic-bezier conversion', () => { new Point(0.68, 0.28), new Point(1, 1) ]; - + shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.08), 0.60); shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.50), 0.84); shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.94), 0.98); }) - + it('cubic-bezier(0.4, 0, 0.6, 1)', () => { const equation = [ new Point(0, 0), @@ -343,12 +344,12 @@ describe('cubic-bezier conversion', () => { new Point(0.6, 1), new Point(1, 1) ]; - + shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.39), 0.43); shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.03), 0.11); shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.89), 0.78); }) - + it('cubic-bezier(0, 0, 0.2, 1)', () => { const equation = [ new Point(0, 0), @@ -356,7 +357,7 @@ describe('cubic-bezier conversion', () => { new Point(0.2, 1), new Point(1, 1) ]; - + shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.95), 0.71); shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.1), 0.03); shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.70), 0.35); diff --git a/core/src/utils/menu-controller/animations/overlay.ts b/core/src/utils/menu-controller/animations/overlay.ts index 61760e57144..e59cdb42981 100644 --- a/core/src/utils/menu-controller/animations/overlay.ts +++ b/core/src/utils/menu-controller/animations/overlay.ts @@ -29,11 +29,11 @@ export const menuOverlayAnimation = (menu: MenuI): IonicAnimation => { } menuAnimation - .addElement(menu.menuInnerEl) + .addElement(menu.menuInnerEl!) .fromTo('transform', `translateX(${closedX})`, `translateX(${openedX})`); backdropAnimation - .addElement(menu.backdropEl) + .addElement(menu.backdropEl!) .fromTo('opacity', 0.01, 0.32); return baseAnimation().addAnimation([menuAnimation, backdropAnimation]); diff --git a/core/src/utils/menu-controller/animations/push.ts b/core/src/utils/menu-controller/animations/push.ts index 2118a1c3c41..0db15fe6b81 100644 --- a/core/src/utils/menu-controller/animations/push.ts +++ b/core/src/utils/menu-controller/animations/push.ts @@ -24,15 +24,15 @@ export const menuPushAnimation = (menu: MenuI): IonicAnimation => { } const menuAnimation = createAnimation() - .addElement(menu.menuInnerEl) + .addElement(menu.menuInnerEl!) .fromTo('transform', `translateX(${menuClosedX})`, 'translateX(0px)'); const contentAnimation = createAnimation() - .addElement(menu.contentEl) + .addElement(menu.contentEl!) .fromTo('transform', 'translateX(0px)', `translateX(${contentOpenedX})`); const backdropAnimation = createAnimation() - .addElement(menu.backdropEl) + .addElement(menu.backdropEl!) .fromTo('opacity', 0.01, 0.32); return baseAnimation().addAnimation([menuAnimation, backdropAnimation, contentAnimation]); diff --git a/core/src/utils/menu-controller/animations/reveal.ts b/core/src/utils/menu-controller/animations/reveal.ts index 2e13d0ac66c..b2e77fb291e 100644 --- a/core/src/utils/menu-controller/animations/reveal.ts +++ b/core/src/utils/menu-controller/animations/reveal.ts @@ -12,7 +12,7 @@ export const menuRevealAnimation = (menu: MenuI): IonicAnimation => { const openedX = (menu.width * (menu.isEndSide ? -1 : 1)) + 'px'; const contentOpen = createAnimation() - .addElement(menu.contentEl) + .addElement(menu.contentEl!) // REVIEW .fromTo('transform', 'translateX(0px)', `translateX(${openedX})`); return baseAnimation().addAnimation(contentOpen); diff --git a/core/src/utils/menu-controller/index.ts b/core/src/utils/menu-controller/index.ts index 96fa34e0585..c9f3aa0b924 100644 --- a/core/src/utils/menu-controller/index.ts +++ b/core/src/utils/menu-controller/index.ts @@ -165,7 +165,7 @@ const createMenuController = () => { return menu._setOpen(shouldOpen, animated); }; - const _createAnimation = (type: string, menuCmp: MenuI): Promise => { + const _createAnimation = (type: string, menuCmp: MenuI) => { const animationBuilder = menuAnimations.get(type) as any; if (!animationBuilder) { throw new Error('animation not registered'); diff --git a/core/src/utils/transition/index.ts b/core/src/utils/transition/index.ts index 7be171418ec..a1fb1b5abaf 100644 --- a/core/src/utils/transition/index.ts +++ b/core/src/utils/transition/index.ts @@ -7,7 +7,10 @@ const iosTransitionAnimation = () => import('./ios.transition'); const mdTransitionAnimation = () => import('./md.transition'); // TODO: Remove when removing AnimationBuilder -export type IonicAnimationInterface = ((navEl: HTMLElement, opts: TransitionOptions) => IonicAnimation) | ((navEl: HTMLElement, opts: TransitionOptions) => Promise); +export type IonicAnimationInterface = ( + ((navEl: HTMLElement, opts: TransitionOptions) => IonicAnimation) | + ((navEl: HTMLElement, opts: TransitionOptions) => Promise) +); export const transition = (opts: TransitionOptions): Promise => { return new Promise((resolve, reject) => { @@ -95,19 +98,16 @@ const animation = async (animationBuilder: IonicAnimationInterface | AnimationBu const didComplete = await playTransition(trans, opts); - // TODO: Remove AnimationBuilder - (trans as any).hasCompleted = didComplete; - if (opts.progressCallback) { opts.progressCallback(undefined); } - if ((trans as any).hasCompleted) { + if (didComplete) { fireDidEvents(opts.enteringEl, opts.leavingEl); } return { - hasCompleted: (trans as any).hasCompleted, + hasCompleted: didComplete, animation: trans }; }; @@ -146,11 +146,19 @@ const notifyViewReady = async (viewIsReady: undefined | ((enteringEl: HTMLElemen } }; -const playTransition = (trans: IonicAnimation | Animation, opts: TransitionOptions): Promise => { +const playTransition = (trans: IonicAnimation | Animation, opts: TransitionOptions): Promise => { const progressCallback = opts.progressCallback; // TODO: Remove AnimationBuilder - const promise = new Promise(resolve => trans.onFinish(resolve)); + const promise = new Promise(resolve => { + trans.onFinish((currentStep: any) => { + if (typeof currentStep === 'number') { + resolve(currentStep === 1); + } else if ((trans as any).hasCompleted !== undefined) { + resolve((trans as Animation).hasCompleted); + } + }); + }); // cool, let's do this, start the transition if (progressCallback) { diff --git a/core/src/utils/transition/ios.transition.ts b/core/src/utils/transition/ios.transition.ts index 7876ea32e70..5099d01f6fe 100644 --- a/core/src/utils/transition/ios.transition.ts +++ b/core/src/utils/transition/ios.transition.ts @@ -214,9 +214,9 @@ export const iosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOptio } if (!contentEl && enteringToolBarEls.length === 0 && headerEls.length === 0) { - enteringContentAnimation.addElement(enteringEl.querySelector(':scope > .ion-page, :scope > ion-nav, :scope > ion-tabs')); + enteringContentAnimation.addElement(enteringEl.querySelector(':scope > .ion-page, :scope > ion-nav, :scope > ion-tabs')!); // REVIEW } else { - enteringContentAnimation.addElement(contentEl); + enteringContentAnimation.addElement(contentEl!); // REVIEW enteringContentAnimation.addElement(headerEls); } @@ -250,12 +250,12 @@ export const iosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOptio .afterStyles({ opacity: '' }); enteringTransitionCover - .addElement(enteringTransitionCoverEl) + .addElement(enteringTransitionCoverEl!) // REVIEW .beforeClearStyles([OPACITY]) .fromTo(OPACITY, 0, 0.1); enteringTransitionShadow - .addElement(enteringTransitionShadowEl) + .addElement(enteringTransitionShadowEl!) // REVIEW .beforeClearStyles([OPACITY]) .fromTo(OPACITY, 0.03, 0.70); @@ -274,7 +274,7 @@ export const iosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOptio rootAnimation.addAnimation(enteringToolBar); const enteringTitle = createAnimation(); - enteringTitle.addElement(enteringToolBarEl.querySelector('ion-title')); + enteringTitle.addElement(enteringToolBarEl.querySelector('ion-title')!); // REVIEW const enteringToolBarButtons = createAnimation(); const buttons = Array.from(enteringToolBarEl.querySelectorAll('ion-buttons,[menuToggle]')); @@ -298,7 +298,7 @@ export const iosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOptio enteringToolBarItems.addElement(enteringToolBarEl.querySelectorAll(':scope > *:not(ion-title):not(ion-buttons):not([menuToggle])')); const enteringToolBarBg = createAnimation(); - enteringToolBarBg.addElement(shadow(enteringToolBarEl).querySelector('.toolbar-background')); + enteringToolBarBg.addElement(shadow(enteringToolBarEl).querySelector('.toolbar-background')!); // REVIEW const enteringBackButton = createAnimation(); const backButtonEl = enteringToolBarEl.querySelector('ion-back-button'); @@ -345,7 +345,7 @@ export const iosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOptio if (backButtonEl && !forward) { const enteringBackBtnText = createAnimation(); enteringBackBtnText - .addElement(shadow(backButtonEl).querySelector('.button-text')) + .addElement(shadow(backButtonEl).querySelector('.button-text')!) // REVIEW .fromTo(`transform`, (isRTL ? 'translateX(-100px)' : 'translateX(100px)'), 'translateX(0px)'); enteringToolBar.addAnimation(enteringBackBtnText); @@ -359,7 +359,7 @@ export const iosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOptio const leavingContent = createAnimation(); const leavingContentEl = leavingEl.querySelector(':scope > ion-content'); - leavingContent.addElement(leavingContentEl); + leavingContent.addElement(leavingContentEl!); // REVIEW leavingContent.addElement(leavingEl.querySelectorAll(':scope > ion-header > *:not(ion-toolbar), :scope > ion-footer > *')); rootAnimation.addAnimation(leavingContent); @@ -393,12 +393,12 @@ export const iosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOptio .afterStyles({ opacity: '' }); leavingTransitionCover - .addElement(leavingTransitionCoverEl) + .addElement(leavingTransitionCoverEl!) // REVIEW .beforeClearStyles([OPACITY]) .fromTo(OPACITY, 0.1, 0); leavingTransitionShadow - .addElement(leavingTransitionShadowEl) + .addElement(leavingTransitionShadowEl!) // REVIEW .beforeClearStyles([OPACITY]) .fromTo(OPACITY, 0.70, 0.03); @@ -413,7 +413,7 @@ export const iosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOptio leavingToolBar.addElement(leavingToolBarEl); const leavingTitle = createAnimation(); - leavingTitle.addElement(leavingToolBarEl.querySelector('ion-title')); + leavingTitle.addElement(leavingToolBarEl.querySelector('ion-title')!); // REVIEW const leavingToolBarButtons = createAnimation(); const buttons = leavingToolBarEl.querySelectorAll('ion-buttons,[menuToggle]'); @@ -435,7 +435,7 @@ export const iosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOptio } const leavingToolBarBg = createAnimation(); - leavingToolBarBg.addElement(shadow(leavingToolBarEl).querySelector('.toolbar-background')); + leavingToolBarBg.addElement(shadow(leavingToolBarEl).querySelector('.toolbar-background')!); // REVIEW const leavingBackButton = createAnimation(); const backButtonEl = leavingToolBarEl.querySelector('ion-back-button'); @@ -472,7 +472,7 @@ export const iosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOptio if (backButtonEl && !backward) { const leavingBackBtnText = createAnimation(); leavingBackBtnText - .addElement(shadow(backButtonEl).querySelector('.button-text')) + .addElement(shadow(backButtonEl).querySelector('.button-text')!) // REVIEW .fromTo('transform', `translateX(${CENTER})`, `translateX(${(isRTL ? -124 : 124) + 'px'})`); leavingToolBar.addAnimation(leavingBackBtnText); }