diff --git a/core/src/components/nav/nav.tsx b/core/src/components/nav/nav.tsx index 56ef070bc4..46e6834fdf 100644 --- a/core/src/components/nav/nav.tsx +++ b/core/src/components/nav/nav.tsx @@ -953,13 +953,14 @@ export class Nav implements NavOutlet { } private onMove(stepValue: number) { + console.log('step', stepValue, this.sbAni); if (this.sbAni) { - this.sbAni.progressStep(stepValue); + (this.sbAni as any).playStep(stepValue); } } private onEnd(shouldComplete: boolean, stepValue: number, dur: number) { - if (this.sbAni) { + if (this.sbAni && this.sbAni.progressEnd) { this.sbAni.progressEnd(shouldComplete, stepValue, dur); } } diff --git a/core/src/utils/animation/animation.ts b/core/src/utils/animation/animation.ts index 561e889a84..a09a121ca2 100644 --- a/core/src/utils/animation/animation.ts +++ b/core/src/utils/animation/animation.ts @@ -12,11 +12,14 @@ export interface Animation { afterRemoveClasses: string[]; afterStylesValue: { [property: string]: any }; + animationFinish(): void; + play(): Animation; playStep(step: number): Animation; pause(): Animation; stop(): Animation; destroy(): Animation; + progressStart(): Animation; from(property: string, value: any): Animation; to(property: string, value: any): Animation; @@ -25,7 +28,7 @@ export interface Animation { addAnimation(animationToADd: Animation | Animation[] | undefined | null): Animation; addTarget(target: string): Animation; - addElement(el: Node | Node[] | NodeList | undefined | null): Animation; + addElement(el: Element | Element[] | Node | Node[] | NodeList | undefined | null): Animation; iterations(iterations: number): Animation; fill(fill: 'auto' | 'none' | 'forwards' | 'backwards' | 'both' | undefined): Animation; direction(direction: 'normal' | 'reverse' | 'alternate' | 'alternate-reverse' | undefined): Animation; @@ -159,6 +162,8 @@ export const createAnimation = (animationNameValue: string | undefined): Animati let webAnimations: any[] = []; let onFinishCallback: any | undefined; + let numAnimationsRunning = 0; + /** * Destroy this animation and all child animations. */ @@ -203,6 +208,8 @@ export const createAnimation = (animationNameValue: string | undefined): Animati element.style.removeProperty('animation-iteration-count'); element.style.removeProperty('animation-delay'); element.style.removeProperty('animation-play-state'); + element.style.removeProperty('animation-fill-mode'); + element.style.removeProperty('animation-direction'); }); } }; @@ -394,7 +401,7 @@ export const createAnimation = (animationNameValue: string | undefined): Animati return generatePublicAPI(); }; - const addElement = (el: Node | Node[] | NodeList | undefined | null): Animation => { + const addElement = (el: Element | Element[] | Node | Node[] | NodeList | undefined | null): Animation => { if (el != null) { const nodeList = el as NodeList; if (nodeList.length >= 0) { @@ -481,77 +488,84 @@ export const createAnimation = (animationNameValue: string | undefined): Animati } }; - const initializeAnimation = () => { - beforeAnimation(); + const animationFinish = () => { + if (numAnimationsRunning === 0) { return; } - if (supportsWebAnimations()) { - elements.forEach((element, i) => { - const animation = element.animate(getKeyframes(), { - delay: getDelay(), - duration: getDuration(), - easing: getEasing(), - iterations: getIterations(), - fill: getFill(), - direction: getDirection() - }); + numAnimationsRunning--; - if (i === 0) { - animation.onfinish = () => { - afterAnimation(); - }; - } + if (numAnimationsRunning === 0) { + afterAnimation(); + } - animation.pause(); + if (parentAnimation) { + parentAnimation.animationFinish(); + } + }; - webAnimations.push(animation); - }); - - } else { - - if (!stylesheet) { + const initializeCSSAnimation = () => { + if (!stylesheet) { stylesheet = createKeyframeStylesheet(generateKeyframeString(_name, _keyframes)); } - const animationDuration = getDuration(); - const animationEasing = getEasing(); - const animationIterationCount = getIterations(); - const animationDelay = getDelay(); - const animationFill = getFill(); - const animationDirection = getDirection(); + elements.forEach(element => { + element.style.setProperty('animation-name', _name || null); + element.style.setProperty('animation-duration', (getDuration() !== undefined) ? `${getDuration()}ms` : null); + element.style.setProperty('animation-timing-function', getEasing() || null); + element.style.setProperty('animation-delay', (getDelay() !== undefined) ? `${getDelay()}ms` : null); + element.style.setProperty('animation-fill-mode', getFill() || null); + element.style.setProperty('animation-direction', getDirection() || null); - elements.forEach(element => { - if (_name !== undefined) { - element.style.setProperty('animation-name', _name); + let iterationsCount = null; + if (getIterations() !== undefined) { + iterationsCount = (getIterations() === Infinity) ? 'infinite' : getIterations()!.toString(); } - if (animationDuration !== undefined) { - element.style.setProperty('animation-duration', `${animationDuration}ms`); - } - - if (animationEasing !== undefined) { - element.style.setProperty('animation-timing-function', animationEasing); - } - - if (animationIterationCount !== undefined) { - element.style.setProperty('animation-iteration-count', (animationIterationCount === Infinity) ? 'infinite' : animationIterationCount.toString()); - } - - if (animationDelay !== undefined) { - element.style.setProperty('animation-delay', `${animationDelay}ms`); - } - - if (animationFill !== undefined) { - element.style.setProperty('animation-fill-mode', animationFill); - } - - if (animationDirection !== undefined) { - element.style.setProperty('animation-direction', animationDirection); - } + element.style.setProperty('animation-directiteration-countion', iterationsCount); }); - animationEnd(elements[0], () => { - afterAnimation(); + if (elements.length > 0) { + animationEnd(elements[0], () => { + animationFinish(); + }); + } + }; + + const initializeWebAnimation = () => { + elements.forEach(element => { + const animation = element.animate(getKeyframes(), { + delay: getDelay(), + duration: getDuration(), + easing: getEasing(), + iterations: getIterations(), + fill: getFill(), + direction: getDirection() }); + + animation.pause(); + + webAnimations.push(animation); + }); + + if (webAnimations.length > 0) { + webAnimations[0].onfinish = () => { + animationFinish(); + }; + } + }; + + const initializeAnimation = () => { + beforeAnimation(); + + numAnimationsRunning = childAnimations.length + 1; + + if (getKeyframes().length === 0) { + animationFinish(); + } else { + if (supportsWebAnimations()) { + initializeWebAnimation(); + } else { + initializeCSSAnimation(); + } } initialized = true; @@ -562,10 +576,7 @@ export const createAnimation = (animationNameValue: string | undefined): Animati animation.playStep(step); }); - if (!initialized) { - initializeAnimation(); - } - + progressStart(); pause(); if (getDuration() !== undefined) { @@ -610,9 +621,7 @@ export const createAnimation = (animationNameValue: string | undefined): Animati animation.play(); }); - if (!initialized) { - initializeAnimation(); - } + progressStart(true); if (supportsWebAnimations()) { webAnimations.forEach(animation => { @@ -663,7 +672,7 @@ export const createAnimation = (animationNameValue: string | undefined): Animati const to = (property: string, value: any): Animation => { const keyframeValues = getKeyframes(); - const lastFrame = keyframeValues[keyframes.length - 1]; + const lastFrame = keyframeValues[keyframeValues.length - 1]; if (lastFrame != null && (lastFrame.offset === undefined || lastFrame.offset === 1)) { lastFrame[property] = value; @@ -686,6 +695,19 @@ export const createAnimation = (animationNameValue: string | undefined): Animati return from(property, fromValue).to(property, toValue); }; + const progressStart = (reset = false): Animation => { + if (initialized && reset) { + initialized = false; + cleanUpElements(); + } + + if (!initialized) { + initializeAnimation(); + } + + return generatePublicAPI(); + }; + const generatePublicAPI = (): Animation => { return { parentAnimation, @@ -697,6 +719,9 @@ export const createAnimation = (animationNameValue: string | undefined): Animati afterAddClasses, afterRemoveClasses, afterStylesValue, + + animationFinish, + from, to, fromTo, @@ -733,6 +758,7 @@ export const createAnimation = (animationNameValue: string | undefined): Animati beforeRemoveClass, beforeAddClass, onFinish, + progressStart }; }; diff --git a/core/src/utils/animation/test/animation.spec.ts b/core/src/utils/animation/test/animation.spec.ts index 6c470038ac..be3de990e9 100644 --- a/core/src/utils/animation/test/animation.spec.ts +++ b/core/src/utils/animation/test/animation.spec.ts @@ -102,7 +102,7 @@ describe('Animation Class', () => { }) }); - describe('keyframes()', () => { + describe('Animation Keyframes', () => { let animation; beforeEach(() => { animation = createAnimation('my-animation'); @@ -275,8 +275,9 @@ describe('Animation Class', () => { it('should get parent easing when child easing is not set', () => { const childAnimation = createAnimation(); - animation.addAnimation(childAnimation); - childAnimation.easing('linear'); + animation + .addAnimation(childAnimation) + .easing('linear'); expect(childAnimation.getEasing()).toEqual('linear'); }); @@ -285,8 +286,9 @@ describe('Animation Class', () => { const childAnimation = createAnimation(); childAnimation.easing('linear'); - animation.addAnimation(childAnimation); - animation.easing('ease-in-out'); + animation + .addAnimation(childAnimation) + .easing('ease-in-out'); expect(childAnimation.getEasing()).toEqual('linear'); }); @@ -297,8 +299,9 @@ describe('Animation Class', () => { it('should get parent duration when child duration is not set', () => { const childAnimation = createAnimation(); - animation.addAnimation(childAnimation); - childAnimation.duration(500); + animation + .addAnimation(childAnimation) + .duration(500); expect(childAnimation.getDuration()).toEqual(500); }); @@ -307,8 +310,9 @@ describe('Animation Class', () => { const childAnimation = createAnimation(); childAnimation.duration(500); - animation.addAnimation(childAnimation); - animation.duration(1000); + animation + .addAnimation(childAnimation) + .duration(1000); expect(childAnimation.getDuration()).toEqual(500); }); @@ -319,8 +323,9 @@ describe('Animation Class', () => { it('should get parent delay when child delay is not set', () => { const childAnimation = createAnimation(); - animation.addAnimation(childAnimation); - childAnimation.delay(500); + animation + .addAnimation(childAnimation) + .delay(500); expect(childAnimation.getDelay()).toEqual(500); }); @@ -329,8 +334,9 @@ describe('Animation Class', () => { const childAnimation = createAnimation(); childAnimation.delay(500); - animation.addAnimation(childAnimation); - animation.delay(1000); + animation + .addAnimation(childAnimation) + .delay(1000); expect(childAnimation.getDelay()).toEqual(500); }); @@ -341,8 +347,9 @@ describe('Animation Class', () => { it('should get parent iterations when child iterations is not set', () => { const childAnimation = createAnimation(); - animation.addAnimation(childAnimation); - childAnimation.iterations(2); + animation + .addAnimation(childAnimation) + .iterations(2); expect(childAnimation.getIterations()).toEqual(2); }); @@ -351,10 +358,59 @@ describe('Animation Class', () => { const childAnimation = createAnimation(); childAnimation.iterations(2); - animation.addAnimation(childAnimation); - animation.iterations(1); + animation + .addAnimation(childAnimation) + .iterations(1); expect(childAnimation.getIterations()).toEqual(2); + }); + + it('should get undefined when fill not set', () => { + expect(animation.getFill()).toEqual(undefined); + }); + + it('should get parent fill when child fill is not set', () => { + const childAnimation = createAnimation(); + animation + .addAnimation(childAnimation) + .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 undefined when direction not set', () => { + expect(animation.getDirection()).toEqual(undefined); + }); + + 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'); }); }) diff --git a/core/src/utils/animation/test/basic/e2e.ts b/core/src/utils/animation/test/basic/e2e.ts new file mode 100644 index 0000000000..ed226ff871 --- /dev/null +++ b/core/src/utils/animation/test/basic/e2e.ts @@ -0,0 +1,28 @@ +import { newE2EPage } from '@stencil/core/testing'; + +import { listenForEvent, waitForFunctionTestContext } from '../../../test/utils'; + +test.only(`animation: basic`, async () => { + const page = await newE2EPage({ url: '/src/utils/animation/test/basic?ionic:_testing=true' }); + const screenshotCompares = []; + + screenshotCompares.push(await page.compareScreenshot()); + + const ANIMATION_FINISHED = 'onIonAnimationFinished'; + const animationFinishedCount: any = { count: 0 }; + await page.exposeFunction(ANIMATION_FINISHED, () => { + animationFinishedCount.count += 1; + }); + + const square = await page.$('.square-a'); + await listenForEvent(page, 'ionAnimationFinished', square, ANIMATION_FINISHED); + + await page.click('.play'); + await page.waitForSelector('.play'); + + await waitForFunctionTestContext((payload: any) => { + return payload.animationFinishedCount.count === 1; + }, { animationFinishedCount }); + + screenshotCompares.push(await page.compareScreenshot()); +}); diff --git a/core/src/utils/animation/test/basic/index.html b/core/src/utils/animation/test/basic/index.html index 0f1ce344d9..1087f977d1 100644 --- a/core/src/utils/animation/test/basic/index.html +++ b/core/src/utils/animation/test/basic/index.html @@ -13,127 +13,42 @@ @@ -147,21 +62,14 @@
-
- Drag along the track to animate the elements -
-
+ Play + Pause + Stop + Destroy +
Hello
- -
- Hello -
- -
- Hello -
diff --git a/core/src/utils/animation/test/chaining/index.html b/core/src/utils/animation/test/chaining/index.html new file mode 100644 index 0000000000..eb1a419b23 --- /dev/null +++ b/core/src/utils/animation/test/chaining/index.html @@ -0,0 +1,227 @@ + + + + + + Animation - Basic + + + + + + + + + + + + + + + Animations + + + + +
+ Play + Pause + Stop + Destroy + +
+
Hello
+
+ +
+
Hello
+
+ +
+
Hello
+
+ +
+
    +
  • Animation A
  • +
+ +
    +
  • Animation B
  • +
+ +
    +
  • Animation C
  • +
      +
    • Animation C sub A
    • +
    • Animation C sub B
    • +
    +
+
+
+
+ + + + + diff --git a/core/src/utils/animation/test/gesture/e2e.ts b/core/src/utils/animation/test/gesture/e2e.ts new file mode 100644 index 0000000000..48558429ee --- /dev/null +++ b/core/src/utils/animation/test/gesture/e2e.ts @@ -0,0 +1,61 @@ +import { newE2EPage } from '@stencil/core/testing'; + +import { checkComponentModeClasses, checkModeClasses } from '../utils'; + +// This test is to loop through all components that should have +// specific classes added and test them +test('component: modes', async () => { + const page = await newE2EPage({ + url: '/src/utils/test/modes?ionic:_testing=true' + }); + + // First test: .button class + // ---------------------------------------------------------------- + // components that need to have the `button` class + // for use in styling by other components (`ion-buttons`) + // e.g. + let tags = ['ion-button', 'ion-back-button', 'ion-menu-button']; + + for (const tag of tags) { + const el = await page.find(tag); + expect(el).toHaveClass('button'); + } + + // Second test: .item class + // ---------------------------------------------------------------- + // components that need to have the `item` class + // for use in styling by other components + // e.g. + tags = ['ion-item', 'ion-item-divider', 'ion-item-group']; + + for (const tag of tags) { + const el = await page.find(tag); + expect(el).toHaveClass('item'); + } + + // Third test: .{component}-{mode} class + // ---------------------------------------------------------------- + // components that need to have their tag name + // + mode as a class for internal styling + // e.g. + tags = ['ion-card-content', 'ion-footer', 'ion-header', 'ion-infinite-scroll-content', 'ion-item-group', 'ion-item-options', 'ion-list', 'ion-picker', 'ion-refresher', 'ion-slides', 'ion-split-pane']; + + for (const tag of tags) { + const el = await page.find(tag); + await checkComponentModeClasses(el); + } + + // Fourth test: .{mode} class + // ---------------------------------------------------------------- + // components that need to have the mode class + // added for external / user styling + // e.g. + tags = ['ion-action-sheet', 'ion-alert', 'ion-anchor', 'ion-app', 'ion-avatar', 'ion-back-button', 'ion-backdrop', 'ion-badge', 'ion-button', 'ion-buttons', 'ion-card-content', 'ion-card-header', 'ion-card-subtitle', 'ion-card-title', 'ion-card', 'ion-checkbox', 'ion-chip', 'ion-col', 'ion-content', 'ion-datetime', 'ion-fab', 'ion-fab-button', 'ion-fab-list', 'ion-footer', 'ion-grid', 'ion-header', 'ion-icon', 'ion-img', 'ion-infinite-scroll', 'ion-infinite-scroll-content', 'ion-input', 'ion-item', 'ion-item-divider', 'ion-item-group', 'ion-item-option', 'ion-item-options', 'ion-item-sliding', 'ion-label', 'ion-list', 'ion-list-header', 'ion-loading', 'ion-modal', 'ion-menu', 'ion-menu-button', 'ion-menu-toggle', 'ion-note', 'ion-picker', 'ion-picker-column', 'ion-popover', 'ion-progress-bar', 'ion-radio', 'ion-radio-group', 'ion-range', 'ion-refresher', 'ion-refresher-content', 'ion-reorder', 'ion-reorder-group', 'ion-ripple-effect', 'ion-row', 'ion-searchbar', 'ion-segment', 'ion-segment-button', 'ion-select', 'ion-select-option', 'ion-select-popover', 'ion-skeleton-text', 'ion-slide', 'ion-slides', 'ion-spinner', 'ion-split-pane', 'ion-tab-bar', 'ion-tab-button', 'ion-text', 'ion-textarea', 'ion-thumbnail', 'ion-title', 'ion-toast', 'ion-toggle', 'ion-toolbar']; + + const globalMode = await page.evaluate(() => document.documentElement.getAttribute('mode')); + for (const tag of tags) { + await page.waitForSelector(tag); + const el = await page.find(tag); + await checkModeClasses(el, globalMode!); + } +}); diff --git a/core/src/utils/animation/test/gesture/index.html b/core/src/utils/animation/test/gesture/index.html new file mode 100644 index 0000000000..b0690daf8b --- /dev/null +++ b/core/src/utils/animation/test/gesture/index.html @@ -0,0 +1,171 @@ + + + + + + Animation - Basic + + + + + + + + + + + + + + + Animations + + + + +
+
+ Drag along the track to animate the elements +
+
+
+ Hello +
+ +
+ Hello +
+ +
+ Hello +
+
+
+ + + + + diff --git a/core/src/utils/animation/test/multiple/index.html b/core/src/utils/animation/test/multiple/index.html index aa75a9b03f..714accb307 100644 --- a/core/src/utils/animation/test/multiple/index.html +++ b/core/src/utils/animation/test/multiple/index.html @@ -17,12 +17,15 @@ const squareA = document.querySelectorAll('.square-a'); const squareB = document.querySelectorAll('.square-b'); const squareC = document.querySelectorAll('.square-c'); + const squareCText = document.querySelectorAll('.square-c .text'); const rootAnimation = createAnimation(); const animationA = createAnimation('animation-a'); const animationB = createAnimation('animation-b'); const animationC = createAnimation('animation-c'); + const animationCSubA = createAnimation('animation-c-sub-a'); + animationA .addElement(squareA) .duration(2000) @@ -39,6 +42,9 @@ }) .afterStyles({ 'background': 'rgba(0, 255, 0, 0.5)' + }) + .onFinish(() => { + console.log('Animation A Done'); }); animationB @@ -57,8 +63,11 @@ }) .afterStyles({ 'background': 'rgba(0, 255, 0, 0.5)' + }) + .onFinish(() => { + console.log('Animation B Done'); }); - + animationC .addElement(squareC) .duration(2000) @@ -75,9 +84,27 @@ }) .afterStyles({ 'background': 'rgba(0, 255, 0, 0.5)' + }) + .onFinish(() => { + console.log('Animation C Done') }); + + animationCSubA + .addElement(squareCText) + .duration(1000) + .delay(3000) + .fromTo('color', 'red', 'blue') + .onFinish(() => { + console.log('Animation CSubA Done'); + }); + + animationC.addAnimation(animationCSubA); - rootAnimation.addAnimation([animationA, animationB, animationC]); + rootAnimation + .addAnimation([animationA, animationB, animationC]) + .onFinish(() => { + console.log('Root Animation Done'); + }); document.querySelector('.play').addEventListener('click', () => { rootAnimation.play(); @@ -126,15 +153,15 @@ Destroy
- Hello +
Hello
- Hello +
Hello
- Hello +
Hello
diff --git a/core/src/utils/transition/index.ts b/core/src/utils/transition/index.ts index 4168455c84..36e13d5100 100644 --- a/core/src/utils/transition/index.ts +++ b/core/src/utils/transition/index.ts @@ -77,18 +77,32 @@ const animation = async (animationBuilder: AnimationBuilder, opts: TransitionOpt await waitForReady(opts, true); const trans = await import('../animation').then(mod => mod.create(animationBuilder, opts.baseEl, opts)); + trans; + fireWillEvents(opts.enteringEl, opts.leavingEl); - await playTransition(trans, opts); + + const animation = ((opts.mode === 'ios') + ? (await iosTransitionAnimation()).newIosTransitionAnimation(opts.baseEl, opts) + : (await mdTransitionAnimation()).newMdTransitionAnimation(opts) as any); + + animation; + + await playTransition(animation, opts); + + animation.hasCompleted = true; + if (opts.progressCallback) { opts.progressCallback(undefined); } - if (trans.hasCompleted) { + if (animation.hasCompleted) { fireDidEvents(opts.enteringEl, opts.leavingEl); } + + console.log('done!'); return { - hasCompleted: trans.hasCompleted, - animation: trans + hasCompleted: animation.hasCompleted, + animation }; }; @@ -126,7 +140,7 @@ const notifyViewReady = async (viewIsReady: undefined | ((enteringEl: HTMLElemen } }; -const playTransition = (trans: Animation, opts: TransitionOptions): Promise => { +const playTransition = async (trans: any, opts: TransitionOptions): Promise => { const progressCallback = opts.progressCallback; const promise = new Promise(resolve => trans.onFinish(resolve)); @@ -135,7 +149,7 @@ const playTransition = (trans: Animation, opts: TransitionOptions): Promise(el: T): ShadowRoot | T => { return el.shadowRoot || el; }; +export const newIosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOptions): Promise => { + try { + const isRTL = (navEl.ownerDocument as any).dir === 'rtl'; + const OFF_RIGHT = isRTL ? '-99.5%' : '99.5%'; + const OFF_LEFT = isRTL ? '33%' : '-33%'; + + const enteringEl = opts.enteringEl; + const leavingEl = opts.leavingEl; + + const backDirection = (opts.direction === 'back'); + const contentEl = enteringEl.querySelector(':scope > ion-content'); + const headerEls = enteringEl.querySelectorAll(':scope > ion-header > *:not(ion-toolbar), :scope > ion-footer > *'); + const enteringToolBarEls = enteringEl.querySelectorAll(':scope > ion-header > ion-toolbar'); + + const rootAnimation = createAnimation('ios-root-animation'); + const enteringContentAnimation = createAnimation('ios-entering-content-animation'); + + rootAnimation + .addElement(enteringEl) + .duration(opts.duration || DURATION) + .easing(opts.easing || EASING) + .beforeRemoveClass('ion-page-invisible'); + + if (leavingEl && navEl) { + const navDecorAnimation = createAnimation('ios-decor-animation'); + navDecorAnimation.addElement(navEl); + rootAnimation.addAnimation(navDecorAnimation); + } + + if (!contentEl && enteringToolBarEls.length === 0 && headerEls.length === 0) { + enteringContentAnimation.addElement(enteringEl.querySelector(':scope > .ion-page, :scope > ion-nav, :scope > ion-tabs')); + } else { + enteringContentAnimation.addElement(contentEl); + enteringContentAnimation.addElement(headerEls); + } + + rootAnimation.addAnimation(enteringContentAnimation); + + if (backDirection) { + enteringContentAnimation + .beforeClearStyles([OPACITY]) + .fromTo('transform', `translateX(${OFF_LEFT})`, `translateX(${CENTER})`) + .fromTo(OPACITY, OFF_OPACITY, 1); + } else { + // entering content, forward direction + enteringContentAnimation + .beforeClearStyles([OPACITY]) + .fromTo('transform', `translateX(${OFF_RIGHT})`, `translateX(${CENTER})`); + } + + enteringToolBarEls.forEach((enteringToolBarEl, i) => { + const enteringToolBar = createAnimation(`ios-entering-toolbar-${i}`); + enteringToolBar.addElement(enteringToolBarEl); + rootAnimation.addAnimation(enteringToolBar); + + const enteringTitle = createAnimation(`ios-entering-toolbar-${i}-title`); + enteringTitle.addElement(enteringToolBarEl.querySelector('ion-title')); + + const enteringToolBarButtons = createAnimation(`ios-entering-toolbar-${i}-buttons`); + enteringToolBarButtons.addElement(enteringToolBarEl.querySelectorAll('ion-buttons,[menuToggle]')); + + const enteringToolBarItems = createAnimation(`ios-entering-toolbar-${i}-items`); + enteringToolBarItems.addElement(enteringToolBarEl.querySelectorAll(':scope > *:not(ion-title):not(ion-buttons):not([menuToggle])')); + + const enteringToolBarBg = createAnimation(`ios-entering-toolbar-${i}-bg`); + enteringToolBarBg.addElement(shadow(enteringToolBarEl).querySelector('.toolbar-background')); + + const enteringBackButton = createAnimation(`ios-entering-toolbar-${i}-back-button`); + const backButtonEl = enteringToolBarEl.querySelector('ion-back-button'); + + if (backButtonEl) { + enteringBackButton.addElement(backButtonEl); + } + + enteringToolBar.addAnimation([enteringTitle, enteringToolBarButtons, enteringToolBarItems, enteringToolBarBg, enteringBackButton]); + enteringTitle.fromTo(OPACITY, 0.01, 1); + enteringToolBarButtons.fromTo(OPACITY, 0.01, 1); + enteringToolBarItems.fromTo(OPACITY, 0.01, 1); + + if (backDirection) { + enteringTitle.fromTo('transform', `translateX(${OFF_LEFT})`, `translateX(${CENTER})`); + + enteringToolBarItems.fromTo('transform', `translateX(${OFF_LEFT})`, `translateX(${CENTER})`); + + // back direction, entering page has a back button + enteringBackButton.fromTo(OPACITY, 0.01, 1); + } else { + // entering toolbar, forward direction + enteringTitle.fromTo('transform', `translateX(${OFF_RIGHT})`, `translateX(${CENTER})`); + + enteringToolBarItems.fromTo('transform', `translateX(${OFF_RIGHT})`, `translateX(${CENTER})`); + + enteringToolBarBg + .beforeClearStyles([OPACITY]) + .fromTo(OPACITY, 0.01, 1); + + // forward direction, entering page has a back button + enteringBackButton.fromTo(OPACITY, 0.01, 1); + + if (backButtonEl) { + const enteringBackBtnText = createAnimation(`ios-entering-toolbar-${i}-back-button-text`); + enteringBackBtnText + .addElement(shadow(backButtonEl).querySelector('.button-text')) + .fromTo(`transform`, (isRTL ? 'translateX(-100px)' : 'translateX(100px)'), 'translateX(0px)'); + + enteringToolBar.addAnimation(enteringBackBtnText); + } + } + }); + + // setup leaving view + if (leavingEl) { + + const leavingContent = createAnimation(`ios-leaving-content-animation`); + leavingContent.addElement(leavingEl.querySelector(':scope > ion-content')); + leavingContent.addElement(leavingEl.querySelectorAll(':scope > ion-header > *:not(ion-toolbar), :scope > ion-footer > *')); + rootAnimation.addAnimation(leavingContent); + + if (backDirection) { + // leaving content, back direction + leavingContent + .beforeClearStyles([OPACITY]) + .fromTo('transform', `translateX(${CENTER})`, (isRTL ? 'translateX(-100%)' : 'translateX(100%)')); + + } else { + // leaving content, forward direction + leavingContent + .fromTo('transform', `translateX(${CENTER})`, `translateX(${OFF_LEFT})`) + .fromTo(OPACITY, 1, OFF_OPACITY); + } + + const leavingToolBarEls = leavingEl.querySelectorAll(':scope > ion-header > ion-toolbar'); + leavingToolBarEls.forEach((leavingToolBarEl, i) => { + const leavingToolBar = createAnimation(`ios-leaving-toolbar-${i}-animation`); + leavingToolBar.addElement(leavingToolBarEl); + + const leavingTitle = createAnimation(`ios-leaving-toolbar-${i}-title-animation`); + leavingTitle.addElement(leavingToolBarEl.querySelector('ion-title')); + + const leavingToolBarButtons = createAnimation(`ios-leaving-toolbar-${i}-buttons-animation`); + leavingToolBarButtons.addElement(leavingToolBarEl.querySelectorAll('ion-buttons,[menuToggle]')); + + const leavingToolBarItems = createAnimation(`ios-leaving-toolbar-${i}-items-animation`); + const leavingToolBarItemEls = leavingToolBarEl.querySelectorAll(':scope > *:not(ion-title):not(ion-buttons):not([menuToggle])'); + if (leavingToolBarItemEls.length > 0) { + leavingToolBarItems.addElement(leavingToolBarItemEls); + } + + const leavingToolBarBg = createAnimation(`ios-leaving-toolbar-${i}-bg-animation`); + leavingToolBarBg.addElement(shadow(leavingToolBarEl).querySelector('.toolbar-background')); + + const leavingBackButton = createAnimation(`ios-leaving-toolbar-${i}-back-button-animation`); + const backButtonEl = leavingToolBarEl.querySelector('ion-back-button'); + if (backButtonEl) { + leavingBackButton.addElement(backButtonEl); + } + + leavingToolBar.addAnimation([leavingTitle, leavingToolBarButtons, leavingToolBarItems, leavingBackButton, leavingToolBarBg]); + rootAnimation.addAnimation(leavingToolBar); + + // fade out leaving toolbar items + leavingBackButton.fromTo(OPACITY, 0.99, 0); + leavingTitle.fromTo(OPACITY, 0.99, 0); + leavingToolBarButtons.fromTo(OPACITY, 0.99, 0); + leavingToolBarItems.fromTo(OPACITY, 0.99, 0); + + if (backDirection) { + // leaving toolbar, back direction + leavingTitle.fromTo('transform', `translateX(${CENTER})`, (isRTL ? 'translateX(-100%)' : 'translateX(100%)')); + leavingToolBarItems.fromTo('transform', `translateX(${CENTER})`, (isRTL ? 'translateX(-100%)' : 'translateX(100%)')); + + // leaving toolbar, back direction, and there's no entering toolbar + // should just slide out, no fading out + leavingToolBarBg + .beforeClearStyles([OPACITY]) + .fromTo(OPACITY, 1, 0.01); + + if (backButtonEl) { + const leavingBackBtnText = createAnimation('ios-leaving-toolbar-${i}-back-button-text'); + leavingBackBtnText.addElement(shadow(backButtonEl).querySelector('.button-text')); + leavingBackBtnText.fromTo('transform', `translateX(${CENTER})`, `translateX(${(isRTL ? -124 : 124) + 'px'})`); + leavingToolBar.addAnimation(leavingBackBtnText); + } + + } else { + // leaving toolbar, forward direction + leavingTitle + .fromTo('transform', `translateX(${CENTER})`, `translateX(${OFF_LEFT})`) + .afterClearStyles([TRANSFORM]); + leavingToolBarItems + .fromTo('transform', `translateX(${CENTER})`, `translateX(${OFF_LEFT})`) + .afterClearStyles([TRANSFORM, OPACITY]); + + leavingBackButton.afterClearStyles([OPACITY]); + leavingTitle.afterClearStyles([OPACITY]); + leavingToolBarButtons.afterClearStyles([OPACITY]); + } + }); + } + + return rootAnimation as any; + } catch (err) { + throw err; + } +}; + export const iosTransitionAnimation = (AnimationC: Animation, navEl: HTMLElement, opts: TransitionOptions): Promise => { const isRTL = (navEl.ownerDocument as any).dir === 'rtl'; diff --git a/core/src/utils/transition/md.transition.ts b/core/src/utils/transition/md.transition.ts index ae7f0cd680..a50a1651b2 100644 --- a/core/src/utils/transition/md.transition.ts +++ b/core/src/utils/transition/md.transition.ts @@ -1,10 +1,65 @@ import { Animation } from '../../interface'; +import { Animation as AnimationNew, createAnimation } from '../animation/animation'; import { TransitionOptions } from '../transition'; const TRANSLATEY = 'translateY'; const OFF_BOTTOM = '40px'; const CENTER = '0px'; +export const newMdTransitionAnimation = (opts: TransitionOptions): AnimationNew => { + try { + const rootAnimation = createAnimation('md-root-animation'); + + const enteringEl = opts.enteringEl; + const leavingEl = opts.leavingEl; + const backDirection = (opts.direction === 'back'); + + const ionPageElement = getIonPageElement(enteringEl); + const enteringToolbarEle = ionPageElement.querySelector('ion-toolbar'); + + rootAnimation + .addElement(ionPageElement) + .beforeRemoveClass('ion-page-invisible') + .fill('both'); + + if (backDirection) { + rootAnimation + .duration(opts.duration || 200) + .easing('cubic-bezier(0.47,0,0.745,0.715)'); + } else { + rootAnimation + .duration(opts.duration || 280) + .easing('cubic-bezier(0.36,0.66,0.04,1)') + .fromTo('transform', `translateY(${OFF_BOTTOM})`, `translateY(${CENTER})`) + .fromTo('opacity', 0.01, 1); + } + + if (enteringToolbarEle) { + const enteringToolBarAnimation = createAnimation('md-entering-toolbar-animation'); + enteringToolBarAnimation.addElement(enteringToolbarEle); + rootAnimation.addAnimation(enteringToolBarAnimation); + } + + if (leavingEl && backDirection) { + rootAnimation + .duration(opts.duration || 200) + .easing('cubic-bezier(0.47,0,0.745,0.715)'); + + const leavingPageAnimation = createAnimation('md-leaving-page-animation'); + leavingPageAnimation + .addElement(getIonPageElement(leavingEl)) + .fromTo('transform', `translateY(${CENTER})`, `translateY(${OFF_BOTTOM})`) + .fromTo('opacity', 1, 0); + + rootAnimation.addAnimation(leavingPageAnimation); + } + + return rootAnimation; + } catch (err) { + throw err; + } +}; + export const mdTransitionAnimation = (AnimationC: Animation, _: HTMLElement, opts: TransitionOptions): Promise => { const enteringEl = opts.enteringEl;