mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
sync with master
This commit is contained in:
@@ -3,6 +3,7 @@ import { applyPolyfills, defineCustomElements } from '@ionic/core/loader';
|
||||
|
||||
import { Config } from './providers/config';
|
||||
import { IonicWindow } from './types/interfaces';
|
||||
import { raf } from './util/util';
|
||||
|
||||
export function appInitialize(config: Config, doc: Document, zone: NgZone) {
|
||||
return (): any => {
|
||||
@@ -23,12 +24,8 @@ export function appInitialize(config: Config, doc: Document, zone: NgZone) {
|
||||
return defineCustomElements(win, {
|
||||
exclude: ['ion-tabs', 'ion-tab'],
|
||||
syncQueue: true,
|
||||
raf,
|
||||
jmp: (h: any) => zone.runOutsideAngular(h),
|
||||
raf: h => {
|
||||
return zone.runOutsideAngular(() => {
|
||||
return (win.__zone_symbol__requestAnimationFrame) ? win.__zone_symbol__requestAnimationFrame(h) : requestAnimationFrame(h);
|
||||
});
|
||||
},
|
||||
ael(elm, eventName, cb, opts) {
|
||||
(elm as any)[aelFn](eventName, cb, opts);
|
||||
},
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ElementRef, HostListener } from '@angular/core';
|
||||
import { ControlValueAccessor } from '@angular/forms';
|
||||
|
||||
import { raf } from '../../util/util';
|
||||
|
||||
export class ValueAccessor implements ControlValueAccessor {
|
||||
|
||||
private onChange: (value: any) => void = () => {/**/};
|
||||
@@ -42,14 +44,16 @@ export class ValueAccessor implements ControlValueAccessor {
|
||||
}
|
||||
|
||||
export function setIonicClasses(element: ElementRef) {
|
||||
const input = element.nativeElement as HTMLElement;
|
||||
const classes = getClasses(input);
|
||||
setClasses(input, classes);
|
||||
raf(() => {
|
||||
const input = element.nativeElement as HTMLElement;
|
||||
const classes = getClasses(input);
|
||||
setClasses(input, classes);
|
||||
|
||||
const item = input.closest('ion-item');
|
||||
if (item) {
|
||||
setClasses(item, classes);
|
||||
}
|
||||
const item = input.closest('ion-item');
|
||||
if (item) {
|
||||
setClasses(item, classes);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getClasses(element: HTMLElement) {
|
||||
|
||||
@@ -205,7 +205,6 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
||||
// Calling `markForCheck` to make sure we will run the change detection when the
|
||||
// `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component.
|
||||
enteringView = this.stackCtrl.createView(this.activated, activatedRoute);
|
||||
enteringView.ref.changeDetectorRef.detectChanges();
|
||||
|
||||
// Store references to the proxy by component
|
||||
this.proxyMap.set(cmpRef.instance, activatedRouteProxy);
|
||||
|
||||
@@ -31,7 +31,7 @@ export class StackController {
|
||||
createView(ref: ComponentRef<any>, activatedRoute: ActivatedRoute): RouteView {
|
||||
const url = getUrl(this.router, activatedRoute);
|
||||
const element = (ref && ref.location && ref.location.nativeElement) as HTMLElement;
|
||||
const unlistenEvents = bindLifecycleEvents(ref.instance, element);
|
||||
const unlistenEvents = bindLifecycleEvents(this.zone, ref.instance, element);
|
||||
return {
|
||||
id: this.nextId++,
|
||||
stackId: computeStackId(this.tabsPrefix, url),
|
||||
@@ -90,16 +90,28 @@ export class StackController {
|
||||
}
|
||||
}
|
||||
|
||||
const reused = this.views.includes(enteringView);
|
||||
const views = this.insertView(enteringView, direction);
|
||||
return this.wait(() => {
|
||||
return this.transition(enteringView, leavingView, animation, this.canGoBack(1), false)
|
||||
.then(() => cleanupAsync(enteringView, views, viewsSnapshot, this.location))
|
||||
.then(() => ({
|
||||
enteringView,
|
||||
direction,
|
||||
animation,
|
||||
tabSwitch
|
||||
}));
|
||||
|
||||
// Trigger change detection before transition starts
|
||||
// This will call ngOnInit() the first time too, just after the view
|
||||
// was attached to the dom, but BEFORE the transition starts
|
||||
if (!reused) {
|
||||
enteringView.ref.changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
// Wait until previous transitions finish
|
||||
return this.zone.runOutsideAngular(() => {
|
||||
return this.wait(() => {
|
||||
return this.transition(enteringView, leavingView, animation, this.canGoBack(1), false)
|
||||
.then(() => cleanupAsync(enteringView, views, viewsSnapshot, this.location))
|
||||
.then(() => ({
|
||||
enteringView,
|
||||
direction,
|
||||
animation,
|
||||
tabSwitch
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -199,11 +211,11 @@ export class StackController {
|
||||
if (enteringView) {
|
||||
enteringView.ref.changeDetectorRef.reattach();
|
||||
}
|
||||
// TODO: disconnect leaving page from change detection to
|
||||
// disconnect leaving page from change detection to
|
||||
// reduce jank during the page transition
|
||||
// if (leavingView) {
|
||||
// leavingView.ref.changeDetectorRef.detach();
|
||||
// }
|
||||
if (leavingView) {
|
||||
leavingView.ref.changeDetectorRef.detach();
|
||||
}
|
||||
const enteringEl = enteringView ? enteringView.element : undefined;
|
||||
const leavingEl = leavingView ? leavingView.element : undefined;
|
||||
const containerEl = this.containerEl;
|
||||
@@ -213,13 +225,13 @@ export class StackController {
|
||||
containerEl.appendChild(enteringEl);
|
||||
}
|
||||
|
||||
return this.zone.runOutsideAngular(() => containerEl.commit(enteringEl, leavingEl, {
|
||||
return containerEl.commit(enteringEl, leavingEl, {
|
||||
deepWait: true,
|
||||
duration: direction === undefined ? 0 : undefined,
|
||||
direction,
|
||||
showGoBack,
|
||||
progressAnimation
|
||||
}));
|
||||
});
|
||||
}
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
@@ -34,10 +34,10 @@ export class AngularFrameworkDelegate implements FrameworkDelegate {
|
||||
) {}
|
||||
|
||||
attachViewToDom(container: any, component: any, params?: any, cssClasses?: string[]): Promise<any> {
|
||||
return new Promise(resolve => {
|
||||
this.zone.run(() => {
|
||||
return this.zone.run(() => {
|
||||
return new Promise(resolve => {
|
||||
const el = attachView(
|
||||
this.resolver, this.injector, this.location, this.appRef,
|
||||
this.zone, this.resolver, this.injector, this.location, this.appRef,
|
||||
this.elRefMap, this.elEventsMap,
|
||||
container, component, params, cssClasses
|
||||
);
|
||||
@@ -47,8 +47,8 @@ export class AngularFrameworkDelegate implements FrameworkDelegate {
|
||||
}
|
||||
|
||||
removeViewFromDom(_container: any, component: any): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
this.zone.run(() => {
|
||||
return this.zone.run(() => {
|
||||
return new Promise(resolve => {
|
||||
const componentRef = this.elRefMap.get(component);
|
||||
if (componentRef) {
|
||||
componentRef.destroy();
|
||||
@@ -66,6 +66,7 @@ export class AngularFrameworkDelegate implements FrameworkDelegate {
|
||||
}
|
||||
|
||||
export function attachView(
|
||||
zone: NgZone,
|
||||
resolver: ComponentFactoryResolver,
|
||||
injector: Injector,
|
||||
location: ViewContainerRef | undefined,
|
||||
@@ -93,7 +94,7 @@ export function attachView(
|
||||
hostElement.classList.add(clazz);
|
||||
}
|
||||
}
|
||||
const unbindEvents = bindLifecycleEvents(instance, hostElement);
|
||||
const unbindEvents = bindLifecycleEvents(zone, instance, hostElement);
|
||||
container.appendChild(hostElement);
|
||||
|
||||
if (!location) {
|
||||
@@ -113,15 +114,17 @@ const LIFECYCLES = [
|
||||
LIFECYCLE_WILL_UNLOAD
|
||||
];
|
||||
|
||||
export function bindLifecycleEvents(instance: any, element: HTMLElement) {
|
||||
const unregisters = LIFECYCLES
|
||||
.filter(eventName => typeof instance[eventName] === 'function')
|
||||
.map(eventName => {
|
||||
const handler = (ev: any) => instance[eventName](ev.detail);
|
||||
element.addEventListener(eventName, handler);
|
||||
return () => element.removeEventListener(eventName, handler);
|
||||
});
|
||||
return () => unregisters.forEach(fn => fn());
|
||||
export function bindLifecycleEvents(zone: NgZone, instance: any, element: HTMLElement) {
|
||||
return zone.run(() => {
|
||||
const unregisters = LIFECYCLES
|
||||
.filter(eventName => typeof instance[eventName] === 'function')
|
||||
.map(eventName => {
|
||||
const handler = (ev: any) => instance[eventName](ev.detail);
|
||||
element.addEventListener(eventName, handler);
|
||||
return () => element.removeEventListener(eventName, handler);
|
||||
});
|
||||
return () => unregisters.forEach(fn => fn());
|
||||
});
|
||||
}
|
||||
|
||||
const NavParamsToken = new InjectionToken<any>('NavParamsToken');
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { HTMLStencilElement } from '../types/interfaces';
|
||||
|
||||
export const raf = (h: any) => {
|
||||
const win = window as any;
|
||||
return (win.__zone_symbol__requestAnimationFrame) ? win.__zone_symbol__requestAnimationFrame(h) : requestAnimationFrame(h);
|
||||
};
|
||||
|
||||
export function proxyMethod(ctrlName: string, doc: Document, methodName: string, ...args: any[]) {
|
||||
const controller = ensureElementInBody(ctrlName, doc);
|
||||
return controller.componentOnReady()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { browser, element, by, protractor } from 'protractor';
|
||||
import { waitTime, testStack, testLifeCycle, handleErrorMessages } from './utils';
|
||||
import { waitTime, testStack, testLifeCycle, handleErrorMessages, getText } from './utils';
|
||||
|
||||
const EC = protractor.ExpectedConditions;
|
||||
|
||||
@@ -23,12 +23,13 @@ describe('router-link params and fragments', () => {
|
||||
|
||||
it('should return to a page with preserved query param and fragment', async () => {
|
||||
await browser.get('/router-link?ionic:_testing=true');
|
||||
await waitTime(30);
|
||||
await element(by.css('#queryParamsFragment')).click();
|
||||
await waitTime(200);
|
||||
await waitTime(400);
|
||||
await element(by.css('#goToPage3')).click();
|
||||
|
||||
browser.wait(EC.urlContains('router-link-page3'), 5000);
|
||||
await waitTime(200);
|
||||
await waitTime(400);
|
||||
|
||||
await element(by.css('#goBackFromPage3')).click();
|
||||
|
||||
@@ -38,6 +39,7 @@ describe('router-link params and fragments', () => {
|
||||
|
||||
it('should preserve query param and fragment with defaultHref string', async () => {
|
||||
await browser.get('/router-link-page3?ionic:_testing=true');
|
||||
await waitTime(30);
|
||||
|
||||
await element(by.css('#goBackFromPage3')).click();
|
||||
|
||||
@@ -71,18 +73,6 @@ describe('router-link', () => {
|
||||
it('should go forward with ion-button[routerLink]', async () => {
|
||||
await element(by.css('#routerLink')).click();
|
||||
await testForward();
|
||||
|
||||
// test go back
|
||||
await element(by.css('ion-back-button')).click();
|
||||
await waitTime(500);
|
||||
|
||||
await testStack('ion-router-outlet', ['app-router-link']);
|
||||
await testLifeCycle('app-router-link', {
|
||||
ionViewWillEnter: 2,
|
||||
ionViewDidEnter: 2,
|
||||
ionViewWillLeave: 1,
|
||||
ionViewDidLeave: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should go forward with a[routerLink]', async () => {
|
||||
@@ -140,18 +130,23 @@ describe('router-link', () => {
|
||||
async function testForward() {
|
||||
await waitTime(2500);
|
||||
await testStack('ion-router-outlet', ['app-router-link', 'app-router-link-page']);
|
||||
await testLifeCycle('app-router-link', {
|
||||
ionViewWillEnter: 1,
|
||||
ionViewDidEnter: 1,
|
||||
ionViewWillLeave: 1,
|
||||
ionViewDidLeave: 1,
|
||||
});
|
||||
await testLifeCycle('app-router-link-page', {
|
||||
ionViewWillEnter: 1,
|
||||
ionViewDidEnter: 1,
|
||||
ionViewWillLeave: 0,
|
||||
ionViewDidLeave: 0,
|
||||
});
|
||||
expect(await getText(`app-router-link-page #canGoBack`)).toEqual('true');
|
||||
|
||||
await browser.navigate().back();
|
||||
await waitTime(100);
|
||||
await testStack('ion-router-outlet', ['app-router-link']);
|
||||
await testLifeCycle('app-router-link', {
|
||||
ionViewWillEnter: 2,
|
||||
ionViewDidEnter: 2,
|
||||
ionViewWillLeave: 1,
|
||||
ionViewDidLeave: 1,
|
||||
});
|
||||
}
|
||||
|
||||
async function testRoot() {
|
||||
@@ -163,6 +158,8 @@ async function testRoot() {
|
||||
ionViewWillLeave: 0,
|
||||
ionViewDidLeave: 0,
|
||||
});
|
||||
expect(await getText(`app-router-link-page #canGoBack`)).toEqual('false');
|
||||
|
||||
await browser.navigate().back();
|
||||
await waitTime(100);
|
||||
await testStack('ion-router-outlet', ['app-router-link']);
|
||||
@@ -183,4 +180,15 @@ async function testBack() {
|
||||
ionViewWillLeave: 0,
|
||||
ionViewDidLeave: 0,
|
||||
});
|
||||
expect(await getText(`app-router-link-page #canGoBack`)).toEqual('false');
|
||||
|
||||
await browser.navigate().back();
|
||||
await waitTime(100);
|
||||
await testStack('ion-router-outlet', ['app-router-link']);
|
||||
await testLifeCycle('app-router-link', {
|
||||
ionViewWillEnter: 1,
|
||||
ionViewDidEnter: 1,
|
||||
ionViewWillLeave: 0,
|
||||
ionViewDidLeave: 0,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -48,11 +48,19 @@ export function handleErrorMessages() {
|
||||
|
||||
export async function testLifeCycle(selector: string, expected: LifeCycleCount) {
|
||||
await waitTime(50);
|
||||
expect(await getText(`${selector} #ngOnInit`)).toEqual('1');
|
||||
expect(await getText(`${selector} #ionViewWillEnter`)).toEqual(expected.ionViewWillEnter.toString());
|
||||
expect(await getText(`${selector} #ionViewDidEnter`)).toEqual(expected.ionViewDidEnter.toString());
|
||||
expect(await getText(`${selector} #ionViewWillLeave`)).toEqual(expected.ionViewWillLeave.toString());
|
||||
expect(await getText(`${selector} #ionViewDidLeave`)).toEqual(expected.ionViewDidLeave.toString());
|
||||
const results = await Promise.all([
|
||||
getText(`${selector} #ngOnInit`),
|
||||
getText(`${selector} #ionViewWillEnter`),
|
||||
getText(`${selector} #ionViewDidEnter`),
|
||||
getText(`${selector} #ionViewWillLeave`),
|
||||
getText(`${selector} #ionViewDidLeave`),
|
||||
]);
|
||||
|
||||
expect(results[0]).toEqual('1');
|
||||
expect(results[1]).toEqual(expected.ionViewWillEnter.toString());
|
||||
expect(results[2]).toEqual(expected.ionViewDidEnter.toString());
|
||||
expect(results[3]).toEqual(expected.ionViewWillLeave.toString());
|
||||
expect(results[4]).toEqual(expected.ionViewDidLeave.toString());
|
||||
}
|
||||
|
||||
export async function testStack(selector: string, expected: string[]) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>
|
||||
Modal test
|
||||
Alert test
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<ion-button (click)="openAlert()" id="action-button">Open Alert</ion-button>
|
||||
<p>Change Detections: <span id="counter">{{counter()}}</span></p>
|
||||
</ion-content>
|
||||
|
||||
@@ -8,10 +8,17 @@ import { NavComponent } from '../nav/nav.component';
|
||||
})
|
||||
export class AlertComponent {
|
||||
|
||||
changes = 0;
|
||||
|
||||
constructor(
|
||||
private alertCtrl: AlertController
|
||||
) { }
|
||||
|
||||
counter() {
|
||||
this.changes++;
|
||||
return Math.floor(this.changes / 2);
|
||||
}
|
||||
|
||||
async openAlert() {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Hello',
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
<ion-content padding>
|
||||
<p>ngOnInit: <span id="ngOnInit">{{onInit}}</span></p>
|
||||
<p>canGoBack: <span id="canGoBack">{{canGoBack}}</span></p>
|
||||
<p>ionViewWillEnter: <span id="ionViewWillEnter">{{willEnter}}</span></p>
|
||||
<p>ionViewDidEnter: <span id="ionViewDidEnter">{{didEnter}}</span></p>
|
||||
<p>ionViewWillLeave: <span id="ionViewWillLeave">{{willLeave}}</span></p>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component, OnInit, NgZone } from '@angular/core';
|
||||
import { IonRouterOutlet } from '@ionic/angular';
|
||||
|
||||
@Component({
|
||||
selector: 'app-router-link-page',
|
||||
@@ -11,9 +12,15 @@ export class RouterLinkPageComponent implements OnInit {
|
||||
didEnter = 0;
|
||||
willLeave = 0;
|
||||
didLeave = 0;
|
||||
canGoBack: boolean = null;
|
||||
|
||||
constructor(
|
||||
private ionRouterOutlet: IonRouterOutlet
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
NgZone.assertInAngularZone();
|
||||
this.canGoBack = this.ionRouterOutlet.canGoBack();
|
||||
this.onInit++;
|
||||
}
|
||||
|
||||
@@ -21,10 +28,16 @@ export class RouterLinkPageComponent implements OnInit {
|
||||
if (this.onInit !== 1) {
|
||||
throw new Error('ngOnInit was not called');
|
||||
}
|
||||
if (this.canGoBack !== this.ionRouterOutlet.canGoBack()) {
|
||||
throw new Error('canGoBack() changed');
|
||||
}
|
||||
NgZone.assertInAngularZone();
|
||||
this.willEnter++;
|
||||
}
|
||||
ionViewDidEnter() {
|
||||
if (this.canGoBack !== this.ionRouterOutlet.canGoBack()) {
|
||||
throw new Error('canGoBack() changed');
|
||||
}
|
||||
NgZone.assertInAngularZone();
|
||||
this.didEnter++;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ export default {
|
||||
format: 'es'
|
||||
},
|
||||
plugins: [
|
||||
resolve({
|
||||
module: true
|
||||
})
|
||||
resolve()
|
||||
]
|
||||
};
|
||||
@@ -5,4 +5,8 @@
|
||||
document.documentElement.setAttribute('dir', 'rtl');
|
||||
}
|
||||
|
||||
window.Ionic = window.Ionic || {};
|
||||
window.Ionic.config = window.Ionic.config || {};
|
||||
window.Ionic.config.experimentalTransitionShadow = true;
|
||||
|
||||
})();
|
||||
@@ -15,15 +15,19 @@ export class App implements ComponentInterface {
|
||||
|
||||
componentDidLoad() {
|
||||
rIC(() => {
|
||||
const isHybrid = isPlatform(window, 'hybrid');
|
||||
if (!config.getBoolean('_testing')) {
|
||||
import('../../utils/tap-click').then(module => module.startTapClick(config));
|
||||
}
|
||||
if (config.getBoolean('statusTap', isPlatform(window, 'hybrid'))) {
|
||||
if (config.getBoolean('statusTap', isHybrid)) {
|
||||
import('../../utils/status-tap').then(module => module.startStatusTap());
|
||||
}
|
||||
if (config.getBoolean('inputShims', needInputShims())) {
|
||||
import('../../utils/input-shims/input-shims').then(module => module.startInputShims(config));
|
||||
}
|
||||
if (config.getBoolean('hardwareBackButton', isHybrid)) {
|
||||
import('../../utils/hardware-back-button').then(module => module.startHardwareBackButton());
|
||||
}
|
||||
import('../../utils/focus-visible').then(module => module.startFocusVisible());
|
||||
|
||||
});
|
||||
|
||||
@@ -118,3 +118,47 @@
|
||||
:host(.content-sizing) .inner-scroll {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.transition-effect {
|
||||
position: absolute;
|
||||
|
||||
/* stylelint-disable property-blacklist */
|
||||
left: -100%;
|
||||
/* stylelint-enable property-blacklist */
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.transition-cover {
|
||||
position: absolute;
|
||||
|
||||
/* stylelint-disable property-blacklist */
|
||||
right: 0;
|
||||
/* stylelint-enable property-blacklist */
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background: black;
|
||||
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.transition-shadow {
|
||||
display: block;
|
||||
position: absolute;
|
||||
|
||||
/* stylelint-disable property-blacklist */
|
||||
right: 0;
|
||||
/* stylelint-enable property-blacklist */
|
||||
|
||||
width: 10px;
|
||||
height: 100%;
|
||||
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAgCAYAAAAIXrg4AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ1IDc5LjE2MzQ5OSwgMjAxOC8wOC8xMy0xNjo0MDoyMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTE3MDgzRkQ5QTkyMTFFOUEwNzQ5MkJFREE1NUY2MjQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTE3MDgzRkU5QTkyMTFFOUEwNzQ5MkJFREE1NUY2MjQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoxMTcwODNGQjlBOTIxMUU5QTA3NDkyQkVEQTU1RjYyNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoxMTcwODNGQzlBOTIxMUU5QTA3NDkyQkVEQTU1RjYyNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmePEuQAAABNSURBVHjaYvz//z8DIxAwMDAwATGMhmFmPDQuOSZks0AMmoJBaQHjkPfB0Lfg/2gQjVow+HPy/yHvg9GiYjQfjMbBqAWjFgy/4hogwADYqwdzxy5BuwAAAABJRU5ErkJggg==);
|
||||
background-repeat: repeat-y;
|
||||
background-size: 10px 16px;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Listen, Method, Prop, h, readTask } from '@stencil/core';
|
||||
|
||||
import { config } from '../../global/config';
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { Color, ScrollBaseDetail, ScrollDetail } from '../../interface';
|
||||
import { isPlatform } from '../../utils/platform';
|
||||
@@ -301,6 +302,8 @@ export class Content implements ComponentInterface {
|
||||
const mode = getIonMode(this);
|
||||
const { scrollX, scrollY, forceOverscroll } = this;
|
||||
|
||||
const transitionShadow = (mode === 'ios' && config.getBoolean('experimentalTransitionShadow', false));
|
||||
|
||||
this.resize();
|
||||
|
||||
return (
|
||||
@@ -328,6 +331,12 @@ export class Content implements ComponentInterface {
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
{transitionShadow ? (
|
||||
<div class="transition-effect">
|
||||
<div class="transition-cover"></div>
|
||||
<div class="transition-shadow"></div>
|
||||
</div>
|
||||
) : null}
|
||||
<slot name="fixed"></slot>
|
||||
</Host>
|
||||
);
|
||||
@@ -370,6 +379,8 @@ const updateScrollDetail = (
|
||||
const prevT = detail.timeStamp;
|
||||
const currentX = el.scrollLeft;
|
||||
const currentY = el.scrollTop;
|
||||
const timeDelta = timestamp - prevT;
|
||||
|
||||
if (shouldStart) {
|
||||
// remember the start positions
|
||||
detail.startTimeStamp = timestamp;
|
||||
@@ -383,7 +394,6 @@ const updateScrollDetail = (
|
||||
detail.deltaX = currentX - detail.startX;
|
||||
detail.deltaY = currentY - detail.startY;
|
||||
|
||||
const timeDelta = timestamp - prevT;
|
||||
if (timeDelta > 0 && timeDelta < 100) {
|
||||
const velocityX = (currentX - prevX) / timeDelta;
|
||||
const velocityY = (currentY - prevY) / timeDelta;
|
||||
|
||||
@@ -154,4 +154,4 @@
|
||||
</ion-app>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
@@ -10,6 +10,9 @@
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script> <script>
|
||||
window.Ionic = window.Ionic || {};
|
||||
window.Ionic.config = window.Ionic.config || {};
|
||||
window.Ionic.config.experimentalTransitionShadow = true;
|
||||
class PageRoot extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this.innerHTML = `
|
||||
|
||||
@@ -193,10 +193,12 @@ export class Textarea implements ComponentInterface {
|
||||
this.ionInputDidLoad.emit();
|
||||
}
|
||||
|
||||
// TODO: performance hit, this cause layout thrashing
|
||||
private runAutoGrow() {
|
||||
const nativeInput = this.nativeInput;
|
||||
if (nativeInput && this.autoGrow) {
|
||||
readTask(() => {
|
||||
nativeInput.style.height = 'inherit';
|
||||
nativeInput.style.height = nativeInput.scrollHeight + 'px';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// TODO: Add validation
|
||||
// TODO: More tests
|
||||
|
||||
let counter = 0;
|
||||
export interface Animation {
|
||||
parentAnimation: Animation | undefined;
|
||||
elements: HTMLElement[];
|
||||
@@ -91,7 +91,8 @@ const animationEnd = (el: HTMLElement | null, callback: (ev?: TransitionEvent) =
|
||||
};
|
||||
|
||||
const supportsWebAnimations = (): boolean => {
|
||||
return !!(window as any).Animation;
|
||||
return false;
|
||||
//return !!(window as any).Animation;
|
||||
};
|
||||
|
||||
const generateKeyframeString = (name: string | undefined, keyframes: any[] = []): string => {
|
||||
@@ -101,25 +102,35 @@ const generateKeyframeString = (name: string | undefined, keyframes: any[] = [])
|
||||
|
||||
keyframes.forEach(keyframe => {
|
||||
const offset = keyframe.offset;
|
||||
delete keyframe.offset;
|
||||
|
||||
const frameString = [];
|
||||
for (const property in keyframe) {
|
||||
if (keyframe.hasOwnProperty(property)) {
|
||||
if (keyframe.hasOwnProperty(property) && property !== 'offset') {
|
||||
frameString.push(`${property}: ${keyframe[property]};`);
|
||||
}
|
||||
}
|
||||
|
||||
keyframeString.push(`${offset * 100}% { ${frameString.join(' ')} }`);
|
||||
});
|
||||
|
||||
keyframeString.push('}')
|
||||
|
||||
return keyframeString.join(' ');
|
||||
};
|
||||
|
||||
const createKeyframeStylesheet = (keyframeString: string): HTMLElement => {
|
||||
const createKeyframeStylesheet = (name: string | undefined, keyframeString: string, element: HTMLElement): HTMLElement | undefined => {
|
||||
const stylesheetId = `ion-${name}`;
|
||||
const stylesheet = document.createElement('style');
|
||||
stylesheet.id = stylesheetId;
|
||||
stylesheet.appendChild(document.createTextNode(keyframeString));
|
||||
document.querySelector('head')!.appendChild(stylesheet);
|
||||
|
||||
const rootNode = (element.getRootNode() as any);
|
||||
const styleContainer = (rootNode.head || rootNode);
|
||||
|
||||
const existingStylesheet = rootNode.querySelector(`#${stylesheetId}`);
|
||||
if (existingStylesheet) { return }
|
||||
|
||||
styleContainer.appendChild(stylesheet);
|
||||
|
||||
return stylesheet;
|
||||
};
|
||||
@@ -148,7 +159,7 @@ export const createAnimation = (animationNameValue: string | undefined): Animati
|
||||
|
||||
let initialized = false;
|
||||
|
||||
let stylesheet: HTMLElement | undefined;
|
||||
let stylesheets: HTMLElement[] | undefined = [];
|
||||
|
||||
let parentAnimation: Animation | undefined;
|
||||
|
||||
@@ -218,10 +229,11 @@ export const createAnimation = (animationNameValue: string | undefined): Animati
|
||||
};
|
||||
|
||||
const cleanUpStyleSheets = () => {
|
||||
if (stylesheet) {
|
||||
stylesheets.forEach(stylesheet => {
|
||||
stylesheet.parentNode!.removeChild(stylesheet);
|
||||
stylesheet = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
stylesheets = [];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -358,6 +370,7 @@ export const createAnimation = (animationNameValue: string | undefined): Animati
|
||||
|
||||
const name = (animationName: string): Animation => {
|
||||
_name = animationName;
|
||||
counter += 1;
|
||||
|
||||
return generatePublicAPI();
|
||||
};
|
||||
@@ -507,26 +520,32 @@ export const createAnimation = (animationNameValue: string | undefined): Animati
|
||||
};
|
||||
|
||||
const initializeCSSAnimation = () => {
|
||||
if (!stylesheet) {
|
||||
stylesheet = createKeyframeStylesheet(generateKeyframeString(_name, _keyframes));
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
|
||||
let iterationsCount = null;
|
||||
if (getIterations() !== undefined) {
|
||||
iterationsCount = (getIterations() === Infinity) ? 'infinite' : getIterations()!.toString();
|
||||
if (_keyframes.length > 0) {
|
||||
|
||||
const stylesheet = createKeyframeStylesheet(_name, generateKeyframeString(_name, _keyframes), element);
|
||||
if (stylesheet) {
|
||||
stylesheets.push(stylesheet);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
let iterationsCount = null;
|
||||
if (getIterations() !== undefined) {
|
||||
iterationsCount = (getIterations() === Infinity) ? 'infinite' : getIterations()!.toString();
|
||||
}
|
||||
|
||||
element.style.setProperty('animation-iteration-countion', iterationsCount);
|
||||
element.style.setProperty('animation-play-state', 'paused');
|
||||
}
|
||||
|
||||
element.style.setProperty('animation-iteration-countion', iterationsCount);
|
||||
element.style.setProperty('animation-play-state', 'paused');
|
||||
});
|
||||
|
||||
|
||||
if (elements.length > 0) {
|
||||
animationEnd(elements[0], () => {
|
||||
@@ -586,7 +605,7 @@ export const createAnimation = (animationNameValue: string | undefined): Animati
|
||||
childAnimations.forEach(animation => {
|
||||
animation.progressStep(step);
|
||||
});
|
||||
|
||||
|
||||
if (getDuration() !== undefined) {
|
||||
if (supportsWebAnimations()) {
|
||||
webAnimations.forEach(animation => {
|
||||
@@ -597,8 +616,10 @@ export const createAnimation = (animationNameValue: string | undefined): Animati
|
||||
const animationDuration = `-${getDuration()! * step}ms`;
|
||||
|
||||
elements.forEach(element => {
|
||||
(element as HTMLElement).style.animationDelay = animationDuration;
|
||||
(element as HTMLElement).style.animationPlayState = 'paused';
|
||||
if (_keyframes.length > 0) {
|
||||
(element as HTMLElement).style.animationDelay = animationDuration;
|
||||
(element as HTMLElement).style.animationPlayState = 'paused';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -625,6 +646,9 @@ export const createAnimation = (animationNameValue: string | undefined): Animati
|
||||
step;
|
||||
|
||||
shouldForceLinearEasing = false;
|
||||
|
||||
// temp
|
||||
//play();
|
||||
|
||||
return generatePublicAPI();
|
||||
};
|
||||
@@ -653,18 +677,19 @@ export const createAnimation = (animationNameValue: string | undefined): Animati
|
||||
childAnimations.forEach(animation => {
|
||||
animation.play();
|
||||
});
|
||||
|
||||
|
||||
if (!initialized) {
|
||||
initializeAnimation();
|
||||
}
|
||||
|
||||
|
||||
if (supportsWebAnimations()) {
|
||||
webAnimations.forEach(animation => {
|
||||
animation.play();
|
||||
});
|
||||
} else {
|
||||
elements.forEach(element => {
|
||||
(element as HTMLElement).style.animationPlayState = 'running';
|
||||
if (_keyframes.length > 0) {
|
||||
(element as HTMLElement).style.animationPlayState = 'running';}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -706,23 +731,25 @@ export const createAnimation = (animationNameValue: string | undefined): Animati
|
||||
};
|
||||
|
||||
const to = (property: string, value: any): Animation => {
|
||||
|
||||
const keyframeValues = getKeyframes();
|
||||
const lastFrame = keyframeValues[keyframeValues.length - 1];
|
||||
|
||||
if (lastFrame != null && (lastFrame.offset === undefined || lastFrame.offset === 1)) {
|
||||
lastFrame[property] = value;
|
||||
|
||||
} else {
|
||||
|
||||
const object: any = {
|
||||
offset: 1
|
||||
};
|
||||
object[property] = value;
|
||||
|
||||
|
||||
_keyframes = [
|
||||
..._keyframes,
|
||||
object
|
||||
];
|
||||
}
|
||||
|
||||
return generatePublicAPI();
|
||||
};
|
||||
|
||||
@@ -785,10 +812,15 @@ export const createAnimation = (animationNameValue: string | undefined): Animati
|
||||
progressEnd
|
||||
};
|
||||
};
|
||||
animationNameValue;
|
||||
|
||||
name(`ion-animation-${counter}`);
|
||||
|
||||
/*
|
||||
if (animationNameValue !== undefined) {
|
||||
name(animationNameValue);
|
||||
}
|
||||
*/
|
||||
|
||||
return generatePublicAPI();
|
||||
};
|
||||
|
||||
97
core/src/utils/animation/test/sandbox/index.html
Normal file
97
core/src/utils/animation/test/sandbox/index.html
Normal file
@@ -0,0 +1,97 @@
|
||||
<!DOCTYPE html>
|
||||
<html dir="ltr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Animation - Basic</title>
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
<script type="module">
|
||||
import { createAnimation } from '../../../../dist/collection/utils/animation/animation.js';
|
||||
|
||||
const cover = document.querySelectorAll('.cover');
|
||||
const square = document.querySelectorAll('.square');
|
||||
|
||||
const rootAnimation = createAnimation();
|
||||
const coverAnimation = createAnimation();
|
||||
const squareAnimation = createAnimation();
|
||||
|
||||
rootAnimation
|
||||
.duration(500)
|
||||
.easing('ease-in-out');
|
||||
|
||||
coverAnimation
|
||||
.addElement(cover)
|
||||
.beforeStyles({ opacity: '1' })
|
||||
.afterStyles({ opacity: '' });
|
||||
|
||||
squareAnimation
|
||||
.addElement(square)
|
||||
.beforeClearStyles(['opacity'])
|
||||
.fromTo('opacity', 0.70, 0.03);
|
||||
|
||||
rootAnimation.addAnimation(coverAnimation);
|
||||
coverAnimation.addAnimation([squareAnimation]);
|
||||
|
||||
let counter = 0;
|
||||
let init = false;
|
||||
document.querySelector('ion-button').addEventListener('click', () => {
|
||||
if (counter === 100) {
|
||||
counter -= 5;
|
||||
} else {
|
||||
counter += 5;
|
||||
}
|
||||
|
||||
if (!init) {
|
||||
rootAnimation.progressStart();
|
||||
init = true;
|
||||
}
|
||||
|
||||
rootAnimation.progressStep(counter / 100);
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.cover {
|
||||
opacity: 0;
|
||||
}
|
||||
.square {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: rgba(0, 0, 255, 0.5);
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
margin-left: 25px;
|
||||
margin-top: 25px;
|
||||
margin-bottom: 25px;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Animations</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<div class="ion-padding">
|
||||
<ion-button>play step</ion-button>
|
||||
<div class="cover">
|
||||
<div class="square">Hello</div>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -165,6 +165,11 @@ export interface IonicConfig {
|
||||
*/
|
||||
pickerLeave?: AnimationBuilder;
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL: Adds a page shadow to transitioning pages on iOS. Disabled by default.
|
||||
*/
|
||||
experimentalTransitionShadow?: boolean;
|
||||
|
||||
// PRIVATE configs
|
||||
keyboardHeight?: number;
|
||||
inputShims?: boolean;
|
||||
|
||||
@@ -2,20 +2,19 @@ import { Animation } from '../../interface';
|
||||
import { Animation as AnimationNew, createAnimation } from '../animation/animation';
|
||||
import { TransitionOptions } from '../transition';
|
||||
|
||||
const DURATION = 500;
|
||||
const EASING = 'cubic-bezier(0.36,0.66,0.04,1)';
|
||||
const OPACITY = 'opacity';
|
||||
const TRANSFORM = 'transform';
|
||||
const TRANSLATEX = 'translateX';
|
||||
const CENTER = '0%';
|
||||
const OFF_OPACITY = 0.8;
|
||||
|
||||
export const shadow = <T extends Element>(el: T): ShadowRoot | T => {
|
||||
return el.shadowRoot || el;
|
||||
};
|
||||
|
||||
export const newIosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOptions): Promise<AnimationNew> => {
|
||||
try {
|
||||
const DURATION = 540;
|
||||
const EASING = 'cubic-bezier(0.32,0.72,0,1)';
|
||||
const OPACITY = 'opacity';
|
||||
const TRANSFORM = 'transform';
|
||||
const CENTER = '0%';
|
||||
const OFF_OPACITY = 0.8;
|
||||
|
||||
const isRTL = (navEl.ownerDocument as any).dir === 'rtl';
|
||||
const OFF_RIGHT = isRTL ? '-99.5%' : '99.5%';
|
||||
const OFF_LEFT = isRTL ? '33%' : '-33%';
|
||||
@@ -61,7 +60,37 @@ export const newIosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOp
|
||||
// entering content, forward direction
|
||||
enteringContentAnimation
|
||||
.beforeClearStyles([OPACITY])
|
||||
.fromTo('transform', `translateX(${OFF_RIGHT})`, `translateX(${CENTER})`);
|
||||
.fromTo('transform', `translateX(${OFF_RIGHT})`, `translateX(${CENTER})`);
|
||||
}
|
||||
|
||||
if (contentEl) {
|
||||
const enteringTransitionEffectEl = shadow(contentEl).querySelector('.transition-effect');
|
||||
if (enteringTransitionEffectEl) {
|
||||
const enteringTransitionCoverEl = enteringTransitionEffectEl.querySelector('.transition-cover');
|
||||
const enteringTransitionShadowEl = enteringTransitionEffectEl.querySelector('.transition-shadow');
|
||||
|
||||
const enteringTransitionEffect = createAnimation('ios-entering-transition-effect');
|
||||
const enteringTransitionCover = createAnimation('ios-entering-transition-cover');
|
||||
const enteringTransitionShadow = createAnimation('ios-entering-transition-shadow');
|
||||
|
||||
enteringTransitionEffect
|
||||
.addElement(enteringTransitionEffectEl)
|
||||
.beforeStyles({ opacity: '1' })
|
||||
.afterStyles({ opacity: '' });
|
||||
|
||||
enteringTransitionCover
|
||||
.addElement(enteringTransitionCoverEl)
|
||||
.beforeClearStyles([OPACITY])
|
||||
.fromTo(OPACITY, 0, 0.1);
|
||||
|
||||
enteringTransitionShadow
|
||||
.addElement(enteringTransitionShadowEl)
|
||||
.beforeClearStyles([OPACITY])
|
||||
.fromTo(OPACITY, 0.03, 0.70);
|
||||
|
||||
enteringTransitionEffect.addAnimation([enteringTransitionCover, enteringTransitionShadow]);
|
||||
enteringContentAnimation.addAnimation([enteringTransitionEffect]);
|
||||
}
|
||||
}
|
||||
|
||||
enteringToolBarEls.forEach((enteringToolBarEl, i) => {
|
||||
@@ -127,8 +156,10 @@ export const newIosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOp
|
||||
// setup leaving view
|
||||
if (leavingEl) {
|
||||
|
||||
const leavingContent = createAnimation(`ios-leaving-content-animation`);
|
||||
leavingContent.addElement(leavingEl.querySelector(':scope > ion-content'));
|
||||
let leavingContent = createAnimation(`ios-leaving-content-animation`);
|
||||
const leavingContentEl = leavingEl.querySelector(':scope > ion-content');
|
||||
|
||||
leavingContent.addElement(leavingContentEl);
|
||||
leavingContent.addElement(leavingEl.querySelectorAll(':scope > ion-header > *:not(ion-toolbar), :scope > ion-footer > *'));
|
||||
rootAnimation.addAnimation(leavingContent);
|
||||
|
||||
@@ -144,6 +175,37 @@ export const newIosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOp
|
||||
.fromTo('transform', `translateX(${CENTER})`, `translateX(${OFF_LEFT})`)
|
||||
.fromTo(OPACITY, 1, OFF_OPACITY);
|
||||
}
|
||||
|
||||
if (leavingContentEl) {
|
||||
const leavingTransitionEffectEl = shadow(leavingContentEl).querySelector('.transition-effect');
|
||||
|
||||
if (leavingTransitionEffectEl) {
|
||||
const leavingTransitionCoverEl = leavingTransitionEffectEl.querySelector('.transition-cover');
|
||||
const leavingTransitionShadowEl = leavingTransitionEffectEl.querySelector('.transition-shadow');
|
||||
|
||||
const leavingTransitionEffect = createAnimation('ios-leaving-transition-effect');
|
||||
const leavingTransitionCover = createAnimation('ios-leaving-transition-cover');
|
||||
const leavingTransitionShadow = createAnimation('ios-leaving-transition-shadow');
|
||||
|
||||
leavingTransitionEffect
|
||||
.addElement(leavingTransitionEffectEl)
|
||||
.beforeStyles({ opacity: '1' })
|
||||
.afterStyles({ opacity: '' });
|
||||
|
||||
leavingTransitionCover
|
||||
.addElement(leavingTransitionCoverEl)
|
||||
.beforeClearStyles([OPACITY])
|
||||
.fromTo(OPACITY, 0.1, 0);
|
||||
|
||||
leavingTransitionShadow
|
||||
.addElement(leavingTransitionShadowEl)
|
||||
.beforeClearStyles([OPACITY])
|
||||
.fromTo(OPACITY, 0.70, 0.03);
|
||||
|
||||
leavingTransitionEffect.addAnimation([leavingTransitionCover, leavingTransitionShadow]);
|
||||
leavingContent.addAnimation([leavingTransitionEffect]);
|
||||
}
|
||||
}
|
||||
|
||||
const leavingToolBarEls = leavingEl.querySelectorAll(':scope > ion-header > ion-toolbar');
|
||||
leavingToolBarEls.forEach((leavingToolBarEl, i) => {
|
||||
@@ -192,7 +254,7 @@ export const newIosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOp
|
||||
.fromTo(OPACITY, 1, 0.01);
|
||||
|
||||
if (backButtonEl) {
|
||||
const leavingBackBtnText = createAnimation('ios-leaving-toolbar-${i}-back-button-text');
|
||||
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);
|
||||
@@ -222,12 +284,25 @@ export const newIosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOp
|
||||
|
||||
export const iosTransitionAnimation = (AnimationC: Animation, navEl: HTMLElement, opts: TransitionOptions): Promise<Animation> => {
|
||||
|
||||
const DURATION = 540;
|
||||
const EASING = 'cubic-bezier(0.32,0.72,0,1)';
|
||||
const OPACITY = 'opacity';
|
||||
const TRANSFORM = 'transform';
|
||||
const TRANSLATEX = 'translateX';
|
||||
const CENTER = '0%';
|
||||
const OFF_OPACITY = 0.8;
|
||||
|
||||
const backDirection = (opts.direction === 'back');
|
||||
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 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 enteringContent = new AnimationC();
|
||||
|
||||
const rootTransition = new AnimationC();
|
||||
rootTransition
|
||||
@@ -244,18 +319,12 @@ export const iosTransitionAnimation = (AnimationC: Animation, navEl: HTMLElement
|
||||
rootTransition.add(navDecor);
|
||||
}
|
||||
|
||||
const backDirection = (opts.direction === 'back');
|
||||
// setting up enter view
|
||||
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 enteringContent = new AnimationC();
|
||||
|
||||
if (!contentEl && enteringToolBarEls.length === 0 && headerEls.length === 0) {
|
||||
enteringContent.addElement(enteringEl.querySelector(':scope > .ion-page, :scope > ion-nav, :scope > ion-tabs'));
|
||||
} else {
|
||||
enteringContent.addElement(contentEl);
|
||||
enteringContent.addElement(headerEls);
|
||||
enteringContent
|
||||
.addElement(contentEl)
|
||||
.addElement(headerEls);
|
||||
}
|
||||
|
||||
rootTransition.add(enteringContent);
|
||||
@@ -270,28 +339,61 @@ export const iosTransitionAnimation = (AnimationC: Animation, navEl: HTMLElement
|
||||
enteringContent
|
||||
.beforeClearStyles([OPACITY])
|
||||
.fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true);
|
||||
|
||||
if (contentEl) {
|
||||
const enteringTransitionEffectEl = shadow(contentEl).querySelector('.transition-effect');
|
||||
|
||||
if (enteringTransitionEffectEl) {
|
||||
const enteringTransitionCoverEl = enteringTransitionEffectEl.querySelector('.transition-cover');
|
||||
const enteringTransitionShadowEl = enteringTransitionEffectEl.querySelector('.transition-shadow');
|
||||
|
||||
const enteringTransitionEffect = new AnimationC();
|
||||
const enteringTransitionCover = new AnimationC();
|
||||
const enteringTransitionShadow = new AnimationC();
|
||||
|
||||
enteringTransitionEffect
|
||||
.addElement(enteringTransitionEffectEl)
|
||||
.beforeStyles({ opacity: '1' })
|
||||
.afterStyles({ opacity: '' });
|
||||
|
||||
enteringTransitionCover
|
||||
.addElement(enteringTransitionCoverEl)
|
||||
.beforeClearStyles([OPACITY])
|
||||
.fromTo(OPACITY, 0, 0.1, true);
|
||||
|
||||
enteringTransitionShadow
|
||||
.addElement(enteringTransitionShadowEl)
|
||||
.beforeClearStyles([OPACITY])
|
||||
.fromTo(OPACITY, 0.70, 0.03, true);
|
||||
|
||||
enteringContent
|
||||
.add(enteringTransitionEffect)
|
||||
.add(enteringTransitionCover)
|
||||
.add(enteringTransitionShadow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enteringToolBarEls.forEach(enteringToolBarEl => {
|
||||
const enteringToolBar = new AnimationC();
|
||||
const enteringTitle = new AnimationC();
|
||||
const enteringToolBarButtons = new AnimationC();
|
||||
const enteringToolBarItems = new AnimationC();
|
||||
const enteringToolBarBg = new AnimationC();
|
||||
const enteringBackButton = new AnimationC();
|
||||
const backButtonEl = enteringToolBarEl.querySelector('ion-back-button');
|
||||
|
||||
enteringToolBar.addElement(enteringToolBarEl);
|
||||
rootTransition.add(enteringToolBar);
|
||||
|
||||
const enteringTitle = new AnimationC();
|
||||
enteringTitle.addElement(enteringToolBarEl.querySelector('ion-title'));
|
||||
|
||||
const enteringToolBarButtons = new AnimationC();
|
||||
enteringToolBarButtons.addElement(enteringToolBarEl.querySelectorAll('ion-buttons,[menuToggle]'));
|
||||
|
||||
const enteringToolBarItems = new AnimationC();
|
||||
enteringToolBarItems.addElement(enteringToolBarEl.querySelectorAll(':scope > *:not(ion-title):not(ion-buttons):not([menuToggle])'));
|
||||
|
||||
const enteringToolBarBg = new AnimationC();
|
||||
enteringToolBarBg.addElement(shadow(enteringToolBarEl).querySelector('.toolbar-background'));
|
||||
|
||||
const enteringBackButton = new AnimationC();
|
||||
const backButtonEl = enteringToolBarEl.querySelector('ion-back-button');
|
||||
|
||||
if (backButtonEl) {
|
||||
enteringBackButton.addElement(backButtonEl);
|
||||
}
|
||||
@@ -340,10 +442,13 @@ export const iosTransitionAnimation = (AnimationC: Animation, navEl: HTMLElement
|
||||
|
||||
// setup leaving view
|
||||
if (leavingEl) {
|
||||
|
||||
const leavingContent = new AnimationC();
|
||||
leavingContent.addElement(leavingEl.querySelector(':scope > ion-content'));
|
||||
leavingContent.addElement(leavingEl.querySelectorAll(':scope > ion-header > *:not(ion-toolbar), :scope > ion-footer > *'));
|
||||
const leavingContentEl = leavingEl.querySelector(':scope > ion-content');
|
||||
|
||||
leavingContent
|
||||
.addElement(leavingContentEl)
|
||||
.addElement(leavingEl.querySelectorAll(':scope > ion-header > *:not(ion-toolbar), :scope > ion-footer > *'));
|
||||
|
||||
rootTransition.add(leavingContent);
|
||||
|
||||
if (backDirection) {
|
||||
@@ -352,6 +457,38 @@ export const iosTransitionAnimation = (AnimationC: Animation, navEl: HTMLElement
|
||||
.beforeClearStyles([OPACITY])
|
||||
.fromTo(TRANSLATEX, CENTER, (isRTL ? '-100%' : '100%'));
|
||||
|
||||
if (leavingContentEl) {
|
||||
const leavingTransitionEffectEl = shadow(leavingContentEl).querySelector('.transition-effect');
|
||||
if (leavingTransitionEffectEl) {
|
||||
const leavingTransitionCoverEl = leavingTransitionEffectEl.querySelector('.transition-cover');
|
||||
const leavingTransitionShadowEl = leavingTransitionEffectEl.querySelector('.transition-shadow');
|
||||
|
||||
const leavingTransitionEffect = new AnimationC();
|
||||
const leavingTransitionCover = new AnimationC();
|
||||
const leavingTransitionShadow = new AnimationC();
|
||||
|
||||
leavingTransitionEffect
|
||||
.addElement(leavingTransitionEffectEl)
|
||||
.beforeStyles({ opacity: '1' })
|
||||
.afterStyles({ opacity: '' });
|
||||
|
||||
leavingTransitionCover
|
||||
.addElement(leavingTransitionCoverEl)
|
||||
.beforeClearStyles([OPACITY])
|
||||
.fromTo(OPACITY, 0.1, 0, true);
|
||||
|
||||
leavingTransitionShadow
|
||||
.addElement(leavingTransitionShadowEl)
|
||||
.beforeClearStyles([OPACITY])
|
||||
.fromTo(OPACITY, 0.70, 0.03, true);
|
||||
|
||||
leavingContent
|
||||
.add(leavingTransitionEffect)
|
||||
.add(leavingTransitionCover)
|
||||
.add(leavingTransitionShadow);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// leaving content, forward direction
|
||||
leavingContent
|
||||
@@ -362,25 +499,26 @@ export const iosTransitionAnimation = (AnimationC: Animation, navEl: HTMLElement
|
||||
const leavingToolBarEls = leavingEl.querySelectorAll(':scope > ion-header > ion-toolbar');
|
||||
leavingToolBarEls.forEach(leavingToolBarEl => {
|
||||
const leavingToolBar = new AnimationC();
|
||||
leavingToolBar.addElement(leavingToolBarEl);
|
||||
|
||||
const leavingTitle = new AnimationC();
|
||||
leavingTitle.addElement(leavingToolBarEl.querySelector('ion-title'));
|
||||
|
||||
const leavingToolBarButtons = new AnimationC();
|
||||
leavingToolBarButtons.addElement(leavingToolBarEl.querySelectorAll('ion-buttons,[menuToggle]'));
|
||||
|
||||
const leavingToolBarItems = new AnimationC();
|
||||
const leavingToolBarItemEls = leavingToolBarEl.querySelectorAll(':scope > *:not(ion-title):not(ion-buttons):not([menuToggle])');
|
||||
const leavingToolBarBg = new AnimationC();
|
||||
const leavingBackButton = new AnimationC();
|
||||
const backButtonEl = leavingToolBarEl.querySelector('ion-back-button');
|
||||
|
||||
leavingToolBar.addElement(leavingToolBarEl);
|
||||
|
||||
leavingTitle.addElement(leavingToolBarEl.querySelector('ion-title'));
|
||||
|
||||
leavingToolBarButtons.addElement(leavingToolBarEl.querySelectorAll('ion-buttons,[menuToggle]'));
|
||||
|
||||
if (leavingToolBarItemEls.length > 0) {
|
||||
leavingToolBarItems.addElement(leavingToolBarItemEls);
|
||||
}
|
||||
|
||||
const leavingToolBarBg = new AnimationC();
|
||||
leavingToolBarBg.addElement(shadow(leavingToolBarEl).querySelector('.toolbar-background'));
|
||||
|
||||
const leavingBackButton = new AnimationC();
|
||||
const backButtonEl = leavingToolBarEl.querySelector('ion-back-button');
|
||||
if (backButtonEl) {
|
||||
leavingBackButton.addElement(backButtonEl);
|
||||
}
|
||||
@@ -423,6 +561,7 @@ export const iosTransitionAnimation = (AnimationC: Animation, navEl: HTMLElement
|
||||
leavingTitle
|
||||
.fromTo(TRANSLATEX, CENTER, OFF_LEFT)
|
||||
.afterClearStyles([TRANSFORM]);
|
||||
|
||||
leavingToolBarItems
|
||||
.fromTo(TRANSLATEX, CENTER, OFF_LEFT)
|
||||
.afterClearStyles([TRANSFORM, OPACITY]);
|
||||
@@ -433,6 +572,7 @@ export const iosTransitionAnimation = (AnimationC: Animation, navEl: HTMLElement
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Return the rootTransition promise
|
||||
return Promise.resolve(rootTransition);
|
||||
};
|
||||
|
||||
@@ -2,12 +2,11 @@ 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 OFF_BOTTOM = '40px';
|
||||
const CENTER = '0px';
|
||||
|
||||
const rootAnimation = createAnimation('md-root-animation');
|
||||
|
||||
const enteringEl = opts.enteringEl;
|
||||
@@ -61,18 +60,21 @@ export const newMdTransitionAnimation = (opts: TransitionOptions): AnimationNew
|
||||
};
|
||||
|
||||
export const mdTransitionAnimation = (AnimationC: Animation, _: HTMLElement, opts: TransitionOptions): Promise<Animation> => {
|
||||
const TRANSLATEY = 'translateY';
|
||||
const OFF_BOTTOM = '40px';
|
||||
const CENTER = '0px';
|
||||
|
||||
const backDirection = (opts.direction === 'back');
|
||||
const enteringEl = opts.enteringEl;
|
||||
const leavingEl = opts.leavingEl;
|
||||
const ionPageElement = getIonPageElement(enteringEl);
|
||||
|
||||
const enteringToolbarEle = ionPageElement.querySelector('ion-toolbar');
|
||||
const rootTransition = new AnimationC();
|
||||
|
||||
rootTransition
|
||||
.addElement(ionPageElement)
|
||||
.beforeRemoveClass('ion-page-invisible');
|
||||
|
||||
const backDirection = (opts.direction === 'back');
|
||||
|
||||
// animate the component itself
|
||||
if (backDirection) {
|
||||
rootTransition
|
||||
@@ -88,7 +90,6 @@ export const mdTransitionAnimation = (AnimationC: Animation, _: HTMLElement, opt
|
||||
}
|
||||
|
||||
// Animate toolbar if it's there
|
||||
const enteringToolbarEle = ionPageElement.querySelector('ion-toolbar');
|
||||
if (enteringToolbarEle) {
|
||||
const enteringToolBar = new AnimationC();
|
||||
enteringToolBar.addElement(enteringToolbarEle);
|
||||
@@ -118,6 +119,7 @@ const getIonPageElement = (element: HTMLElement) => {
|
||||
if (element.classList.contains('ion-page')) {
|
||||
return element;
|
||||
}
|
||||
|
||||
const ionPage = element.querySelector(':scope > .ion-page, :scope > ion-nav, :scope > ion-tabs');
|
||||
if (ionPage) {
|
||||
return ionPage;
|
||||
|
||||
Reference in New Issue
Block a user