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
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - 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
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;