mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(angular): add swipe-to-go-back gesture
This commit is contained in:
committed by
Manu MA
parent
bfbbeca389
commit
108691dc44
@@ -5,7 +5,6 @@ export { RadioValueAccessor } from './control-value-accessors/radio-value-access
|
||||
export { SelectValueAccessor } from './control-value-accessors/select-value-accessor';
|
||||
export { TextValueAccessor } from './control-value-accessors/text-value-accessor';
|
||||
|
||||
export { RouterDirection } from './navigation/router-direction';
|
||||
export { IonBackButton } from './navigation/ion-back-button';
|
||||
export { NavDelegate } from './navigation/nav-delegate';
|
||||
export { TabDelegate } from './navigation/tab-delegate';
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { Directive, ElementRef, HostListener, Input, Optional } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { NavController, NavIntent } from '../../providers/nav-controller';
|
||||
|
||||
export type RouterDirection = 'forward' | 'back' | 'root' | 'auto';
|
||||
|
||||
@Directive({
|
||||
selector: 'ion-anchor,ion-button,ion-item'
|
||||
selector: '[routerDirection],ion-anchor,ion-button,ion-item'
|
||||
})
|
||||
export class HrefDelegate {
|
||||
|
||||
@Input() routerDirection: RouterDirection = 'forward';
|
||||
|
||||
@Input()
|
||||
set routerLink(_: any) {
|
||||
this.elementRef.nativeElement.button = true;
|
||||
@@ -21,6 +26,7 @@ export class HrefDelegate {
|
||||
|
||||
constructor(
|
||||
@Optional() private router: Router,
|
||||
private navCtrl: NavController,
|
||||
private elementRef: ElementRef
|
||||
) {}
|
||||
|
||||
@@ -29,7 +35,17 @@ export class HrefDelegate {
|
||||
const url = this.href;
|
||||
if (this.router && url != null && url[0] !== '#' && url.indexOf('://') === -1) {
|
||||
ev.preventDefault();
|
||||
this.navCtrl.setIntent(textToIntent(this.routerDirection));
|
||||
this.router.navigateByUrl(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function textToIntent(direction: RouterDirection) {
|
||||
switch (direction) {
|
||||
case 'forward': return NavIntent.Forward;
|
||||
case 'back': return NavIntent.Back;
|
||||
case 'root': return NavIntent.Root;
|
||||
default: return NavIntent.Auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Attribute, ChangeDetectorRef, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, EventEmitter, Injector, Input, OnDestroy, OnInit, Optional, Output, ViewContainerRef } from '@angular/core';
|
||||
import { Attribute, ChangeDetectorRef, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, EventEmitter, Injector, Input, NgZone, OnDestroy, OnInit, Optional, Output, ViewContainerRef } from '@angular/core';
|
||||
import { ActivatedRoute, ChildrenOutletContexts, OutletContext, PRIMARY_OUTLET, Router } from '@angular/router';
|
||||
import { RouteView, StackController } from './router-controller';
|
||||
import { RouteView, StackController } from './stack-controller';
|
||||
import { NavController } from '../../providers/nav-controller';
|
||||
import { bindLifecycleEvents } from '../../providers/angular-delegate';
|
||||
|
||||
@@ -14,32 +14,47 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
||||
private activatedView: RouteView | null = null;
|
||||
|
||||
private _activatedRoute: ActivatedRoute | null = null;
|
||||
private _swipeGesture?: boolean;
|
||||
private name: string;
|
||||
private stackCtrl: StackController;
|
||||
private nativeEl: HTMLIonRouterOutletElement;
|
||||
private hasStack = false;
|
||||
|
||||
@Output('activate') activateEvents = new EventEmitter<any>();
|
||||
@Output('deactivate') deactivateEvents = new EventEmitter<any>();
|
||||
|
||||
@Input()
|
||||
set animated(animated: boolean) {
|
||||
(this.elementRef.nativeElement as HTMLIonRouterOutletElement).animated = animated;
|
||||
this.nativeEl.animated = animated;
|
||||
}
|
||||
|
||||
@Input()
|
||||
set swipeGesture(swipe: boolean) {
|
||||
this._swipeGesture = swipe;
|
||||
this.nativeEl.swipeHandler = (swipe && this.hasStack) ? {
|
||||
canStart: () => this.stackCtrl.canGoBack(1),
|
||||
onStart: () => this.stackCtrl.startBackTransition(),
|
||||
onEnd: shouldContinue => this.stackCtrl.endBackTransition(shouldContinue)
|
||||
} : undefined;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private parentContexts: ChildrenOutletContexts,
|
||||
private location: ViewContainerRef,
|
||||
private resolver: ComponentFactoryResolver,
|
||||
private elementRef: ElementRef,
|
||||
@Attribute('name') name: string,
|
||||
@Optional() @Attribute('stack') stack: any,
|
||||
private changeDetector: ChangeDetectorRef,
|
||||
private navCtrl: NavController,
|
||||
router: Router
|
||||
elementRef: ElementRef,
|
||||
router: Router,
|
||||
zone: NgZone
|
||||
) {
|
||||
this.nativeEl = elementRef.nativeElement;
|
||||
this.name = name || PRIMARY_OUTLET;
|
||||
parentContexts.onChildOutletCreated(this.name, this as any);
|
||||
const hasStack = stack !== 'false' && stack !== false;
|
||||
this.stackCtrl = new StackController(hasStack, elementRef.nativeElement, router, this.navCtrl);
|
||||
this.hasStack = stack !== 'false' && stack !== false;
|
||||
this.stackCtrl = new StackController(this.hasStack, this.nativeEl, router, this.navCtrl, zone);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@@ -60,9 +75,16 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
||||
this.activateWith(context.route, context.resolver || null);
|
||||
}
|
||||
}
|
||||
this.nativeEl.componentOnReady().then(() => {
|
||||
if (this._swipeGesture === undefined) {
|
||||
this.swipeGesture = this.nativeEl.mode === 'ios';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get isActivated(): boolean { return !!this.activated; }
|
||||
get isActivated(): boolean {
|
||||
return !!this.activated;
|
||||
}
|
||||
|
||||
get component(): object {
|
||||
if (!this.activated) {
|
||||
@@ -151,9 +173,8 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
||||
this.activatedView = enteringView;
|
||||
this.stackCtrl.setActive(enteringView, direction, animated).then(() => {
|
||||
this.activateEvents.emit(cmpRef.instance);
|
||||
emitEvent(this.elementRef.nativeElement);
|
||||
emitEvent(this.nativeEl);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
canGoBack(deep = 1) {
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { Directive, HostListener, Input } from '@angular/core';
|
||||
import { NavController, NavIntent } from '../../providers/nav-controller';
|
||||
|
||||
@Directive({
|
||||
selector: '[routerDirection]',
|
||||
})
|
||||
export class RouterDirection {
|
||||
|
||||
@Input() routerDirection: string;
|
||||
|
||||
constructor(
|
||||
private navCtrl: NavController,
|
||||
) {}
|
||||
|
||||
@HostListener('click')
|
||||
onClick() {
|
||||
this.navCtrl.setIntent(textToIntent(this.routerDirection));
|
||||
}
|
||||
}
|
||||
|
||||
function textToIntent(direction: string) {
|
||||
switch (direction) {
|
||||
case 'forward': return NavIntent.Forward;
|
||||
case 'back': return NavIntent.Back;
|
||||
case 'root': return NavIntent.Root;
|
||||
default: return NavIntent.Auto;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ComponentRef } from '@angular/core';
|
||||
import { ComponentRef, NgZone } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
import { NavController } from '../../providers/nav-controller';
|
||||
@@ -8,12 +8,15 @@ export class StackController {
|
||||
|
||||
private viewsSnapshot: RouteView[] = [];
|
||||
private views: RouteView[] = [];
|
||||
private runningTransition?: Promise<boolean>;
|
||||
private skipTransition = false;
|
||||
|
||||
constructor(
|
||||
private stack: boolean,
|
||||
private containerEl: HTMLIonRouterOutletElement,
|
||||
private router: Router,
|
||||
private navCtrl: NavController,
|
||||
private zone: NgZone,
|
||||
) {}
|
||||
|
||||
createView(enteringRef: ComponentRef<any>, route: ActivatedRoute): RouteView {
|
||||
@@ -22,7 +25,6 @@ export class StackController {
|
||||
element: (enteringRef && enteringRef.location && enteringRef.location.nativeElement) as HTMLElement,
|
||||
url: this.getUrl(route),
|
||||
fullpath: document.location!.pathname,
|
||||
deactivatedId: -1
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,22 +33,43 @@ export class StackController {
|
||||
return this.views.find(vw => vw.url === activatedUrlKey);
|
||||
}
|
||||
|
||||
async setActive(enteringView: RouteView, direction: number, animated: boolean) {
|
||||
const leavingView = this.getActive();
|
||||
this.insertView(enteringView, direction);
|
||||
await this.transition(enteringView, leavingView, direction, animated, this.canGoBack(1), false);
|
||||
this.cleanup();
|
||||
}
|
||||
|
||||
canGoBack(deep: number): boolean {
|
||||
return this.views.length > deep;
|
||||
}
|
||||
|
||||
async setActive(enteringView: RouteView, direction: number, animated: boolean) {
|
||||
const leavingView = this.getActive();
|
||||
this.insertView(enteringView, direction);
|
||||
await this.transition(enteringView, leavingView, direction, animated, this.canGoBack(1));
|
||||
this.cleanup();
|
||||
pop(deep: number) {
|
||||
this.zone.run(() => {
|
||||
const view = this.views[this.views.length - deep - 1];
|
||||
this.navCtrl.navigateBack(view.url);
|
||||
});
|
||||
}
|
||||
|
||||
pop(deep: number) {
|
||||
const view = this.views[this.views.length - deep - 1];
|
||||
this.navCtrl.navigateBack(view.url);
|
||||
startBackTransition() {
|
||||
this.transition(
|
||||
this.views[this.views.length - 2],
|
||||
this.views[this.views.length - 1],
|
||||
-1,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
endBackTransition(shouldComplete: boolean) {
|
||||
if (shouldComplete) {
|
||||
this.skipTransition = true;
|
||||
this.pop(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private insertView(enteringView: RouteView, direction: number) {
|
||||
// no stack
|
||||
if (!this.stack) {
|
||||
@@ -100,8 +123,17 @@ export class StackController {
|
||||
leavingView: RouteView | undefined,
|
||||
direction: number,
|
||||
animated: boolean,
|
||||
showGoBack: boolean
|
||||
showGoBack: boolean,
|
||||
progressAnimation: boolean
|
||||
) {
|
||||
if (this.runningTransition) {
|
||||
await this.runningTransition;
|
||||
this.runningTransition = undefined;
|
||||
}
|
||||
if (this.skipTransition) {
|
||||
this.skipTransition = false;
|
||||
return;
|
||||
}
|
||||
const enteringEl = enteringView ? enteringView.element : undefined;
|
||||
const leavingEl = leavingView ? leavingView.element : undefined;
|
||||
const containerEl = this.containerEl;
|
||||
@@ -112,12 +144,14 @@ export class StackController {
|
||||
}
|
||||
|
||||
await containerEl.componentOnReady();
|
||||
await containerEl.commit(enteringEl, leavingEl, {
|
||||
this.runningTransition = containerEl.commit(enteringEl, leavingEl, {
|
||||
duration: !animated ? 0 : undefined,
|
||||
direction: direction === 1 ? 'forward' : 'back',
|
||||
deepWait: true,
|
||||
showGoBack
|
||||
showGoBack,
|
||||
progressAnimation
|
||||
});
|
||||
await this.runningTransition;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,33 +159,19 @@ export class StackController {
|
||||
const urlTree = this.router.createUrlTree(['.'], { relativeTo: activatedRoute });
|
||||
return this.router.serializeUrl(urlTree);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function destroyView(view: RouteView) {
|
||||
function destroyView(view: RouteView) {
|
||||
if (view) {
|
||||
// TODO lifecycle event
|
||||
view.ref.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export function getLastDeactivatedRef(views: RouteView[]) {
|
||||
if (views.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return views.sort((a, b) => {
|
||||
if (a.deactivatedId > b.deactivatedId) return -1;
|
||||
if (a.deactivatedId < b.deactivatedId) return 1;
|
||||
return 0;
|
||||
})[0].ref;
|
||||
}
|
||||
|
||||
export interface RouteView {
|
||||
url: string;
|
||||
fullpath: string;
|
||||
element: HTMLElement;
|
||||
ref: ComponentRef<any>;
|
||||
deactivatedId: number;
|
||||
savedData?: any;
|
||||
}
|
||||
@@ -97,7 +97,6 @@ const DECLARATIONS = [
|
||||
// navigation
|
||||
c.IonBackButton,
|
||||
c.IonRouterOutlet,
|
||||
c.RouterDirection,
|
||||
c.NavDelegate,
|
||||
c.TabDelegate,
|
||||
c.TabsDelegate,
|
||||
|
||||
17
core/src/components.d.ts
vendored
17
core/src/components.d.ts
vendored
@@ -56,6 +56,7 @@ import {
|
||||
Side,
|
||||
SpinnerTypes,
|
||||
StyleEvent,
|
||||
SwipeGestureHandler,
|
||||
TabBarChangedDetail,
|
||||
TabButtonClickDetail,
|
||||
TabButtonLayout,
|
||||
@@ -403,7 +404,7 @@ export namespace Components {
|
||||
/**
|
||||
* When using a router, it specifies the transition direction when navigating to another page using `href`.
|
||||
*/
|
||||
'routerDirection'?: RouterDirection;
|
||||
'routerDirection': RouterDirection;
|
||||
}
|
||||
interface IonAnchorAttributes extends StencilHTMLAttributes {
|
||||
/**
|
||||
@@ -565,7 +566,7 @@ export namespace Components {
|
||||
/**
|
||||
* When using a router, it specifies the transition direction when navigating to another page using `href`.
|
||||
*/
|
||||
'routerDirection'?: RouterDirection;
|
||||
'routerDirection': RouterDirection;
|
||||
/**
|
||||
* The button shape.
|
||||
*/
|
||||
@@ -1343,7 +1344,7 @@ export namespace Components {
|
||||
/**
|
||||
* When using a router, it specifies the transition direction when navigating to another page using `href`.
|
||||
*/
|
||||
'routerDirection'?: RouterDirection;
|
||||
'routerDirection': RouterDirection;
|
||||
/**
|
||||
* If `true`, the fab button will show when in a fab-list.
|
||||
*/
|
||||
@@ -2021,7 +2022,7 @@ export namespace Components {
|
||||
/**
|
||||
* When using a router, it specifies the transition direction when navigating to another page using `href`.
|
||||
*/
|
||||
'routerDirection'?: RouterDirection;
|
||||
'routerDirection': RouterDirection;
|
||||
/**
|
||||
* The type of the button. Only used when an `onclick` or `button` property is present.
|
||||
*/
|
||||
@@ -3614,11 +3615,9 @@ export namespace Components {
|
||||
'commit': (enteringEl: HTMLElement, leavingEl: HTMLElement | undefined, opts?: RouterOutletOptions | undefined) => Promise<boolean>;
|
||||
'delegate'?: FrameworkDelegate;
|
||||
'getRouteId': () => Promise<RouteID | undefined>;
|
||||
/**
|
||||
* Set the root component for the given navigation stack
|
||||
*/
|
||||
'setRoot': (component: ComponentRef, params?: { [key: string]: any; } | undefined, opts?: RouterOutletOptions | undefined) => Promise<boolean>;
|
||||
'mode': Mode;
|
||||
'setRouteId': (id: string, params: { [key: string]: any; } | undefined, direction: number) => Promise<RouteWrite>;
|
||||
'swipeHandler'?: SwipeGestureHandler;
|
||||
}
|
||||
interface IonRouterOutletAttributes extends StencilHTMLAttributes {
|
||||
/**
|
||||
@@ -3630,9 +3629,11 @@ export namespace Components {
|
||||
*/
|
||||
'animation'?: AnimationBuilder;
|
||||
'delegate'?: FrameworkDelegate;
|
||||
'mode'?: Mode;
|
||||
'onIonNavDidChange'?: (event: CustomEvent<void>) => void;
|
||||
'onIonNavWillChange'?: (event: CustomEvent<void>) => void;
|
||||
'onIonNavWillLoad'?: (event: CustomEvent<void>) => void;
|
||||
'swipeHandler'?: SwipeGestureHandler;
|
||||
}
|
||||
|
||||
interface IonRouter {
|
||||
|
||||
@@ -29,7 +29,7 @@ export class Anchor implements ComponentInterface {
|
||||
* When using a router, it specifies the transition direction when navigating to
|
||||
* another page using `href`.
|
||||
*/
|
||||
@Prop() routerDirection?: RouterDirection;
|
||||
@Prop() routerDirection: RouterDirection = 'forward';
|
||||
|
||||
hostData() {
|
||||
return {
|
||||
|
||||
@@ -8,11 +8,11 @@ The Anchor component is used for navigating to a specified link. Similar to the
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Attribute | Description | Type | Default |
|
||||
| ----------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | ----------- |
|
||||
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
|
||||
| `href` | `href` | Contains a URL or a URL fragment that the hyperlink points to. If this property is set, an anchor tag will be rendered. | `string \| undefined` | `undefined` |
|
||||
| `routerDirection` | `router-direction` | When using a router, it specifies the transition direction when navigating to another page using `href`. | `"back" \| "forward" \| "root" \| undefined` | `undefined` |
|
||||
| Property | Attribute | Description | Type | Default |
|
||||
| ----------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | ----------- |
|
||||
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
|
||||
| `href` | `href` | Contains a URL or a URL fragment that the hyperlink points to. If this property is set, an anchor tag will be rendered. | `string \| undefined` | `undefined` |
|
||||
| `routerDirection` | `router-direction` | When using a router, it specifies the transition direction when navigating to another page using `href`. | `"back" \| "forward" \| "root"` | `'forward'` |
|
||||
|
||||
|
||||
## CSS Custom Properties
|
||||
|
||||
@@ -58,7 +58,7 @@ export class Button implements ComponentInterface {
|
||||
* When using a router, it specifies the transition direction when navigating to
|
||||
* another page using `href`.
|
||||
*/
|
||||
@Prop() routerDirection?: RouterDirection;
|
||||
@Prop() routerDirection: RouterDirection = 'forward';
|
||||
|
||||
/**
|
||||
* Contains a URL or a URL fragment that the hyperlink points to.
|
||||
|
||||
@@ -46,7 +46,7 @@ This attribute specifies the size of the button. Setting this attribute will cha
|
||||
| `fill` | `fill` | Set to `"clear"` for a transparent button, to `"outline"` for a transparent button with a border, or to `"solid"`. The default style is `"solid"` except inside of a toolbar, where the default is `"clear"`. | `"clear" \| "default" \| "outline" \| "solid" \| undefined` | `undefined` |
|
||||
| `href` | `href` | Contains a URL or a URL fragment that the hyperlink points to. If this property is set, an anchor tag will be rendered. | `string \| undefined` | `undefined` |
|
||||
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
|
||||
| `routerDirection` | `router-direction` | When using a router, it specifies the transition direction when navigating to another page using `href`. | `"back" \| "forward" \| "root" \| undefined` | `undefined` |
|
||||
| `routerDirection` | `router-direction` | When using a router, it specifies the transition direction when navigating to another page using `href`. | `"back" \| "forward" \| "root"` | `'forward'` |
|
||||
| `shape` | `shape` | The button shape. | `"round" \| undefined` | `undefined` |
|
||||
| `size` | `size` | The button size. | `"default" \| "large" \| "small" \| undefined` | `undefined` |
|
||||
| `strong` | `strong` | If `true`, activates a button with a heavier font weight. | `boolean` | `false` |
|
||||
|
||||
@@ -50,7 +50,7 @@ export class FabButton implements ComponentInterface {
|
||||
* When using a router, it specifies the transition direction when navigating to
|
||||
* another page using `href`.
|
||||
*/
|
||||
@Prop() routerDirection?: RouterDirection;
|
||||
@Prop() routerDirection: RouterDirection = 'forward';
|
||||
|
||||
/**
|
||||
* If `true`, the fab button will show when in a fab-list.
|
||||
|
||||
@@ -9,17 +9,17 @@ If the FAB button is not wrapped with `<ion-fab>`, it will scroll with the conte
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Attribute | Description | Type | Default |
|
||||
| ----------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | ----------- |
|
||||
| `activated` | `activated` | If `true`, the fab button will be show a close icon. | `boolean` | `false` |
|
||||
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
|
||||
| `disabled` | `disabled` | If `true`, the user cannot interact with the fab button. | `boolean` | `false` |
|
||||
| `href` | `href` | Contains a URL or a URL fragment that the hyperlink points to. If this property is set, an anchor tag will be rendered. | `string \| undefined` | `undefined` |
|
||||
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
|
||||
| `routerDirection` | `router-direction` | When using a router, it specifies the transition direction when navigating to another page using `href`. | `"back" \| "forward" \| "root" \| undefined` | `undefined` |
|
||||
| `show` | `show` | If `true`, the fab button will show when in a fab-list. | `boolean` | `false` |
|
||||
| `translucent` | `translucent` | If `true`, the fab button will be translucent. | `boolean` | `false` |
|
||||
| `type` | `type` | The type of the button. | `"button" \| "reset" \| "submit"` | `'button'` |
|
||||
| Property | Attribute | Description | Type | Default |
|
||||
| ----------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | ----------- |
|
||||
| `activated` | `activated` | If `true`, the fab button will be show a close icon. | `boolean` | `false` |
|
||||
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
|
||||
| `disabled` | `disabled` | If `true`, the user cannot interact with the fab button. | `boolean` | `false` |
|
||||
| `href` | `href` | Contains a URL or a URL fragment that the hyperlink points to. If this property is set, an anchor tag will be rendered. | `string \| undefined` | `undefined` |
|
||||
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
|
||||
| `routerDirection` | `router-direction` | When using a router, it specifies the transition direction when navigating to another page using `href`. | `"back" \| "forward" \| "root"` | `'forward'` |
|
||||
| `show` | `show` | If `true`, the fab button will show when in a fab-list. | `boolean` | `false` |
|
||||
| `translucent` | `translucent` | If `true`, the fab button will be translucent. | `boolean` | `false` |
|
||||
| `type` | `type` | The type of the button. | `"button" \| "reset" \| "submit"` | `'button'` |
|
||||
|
||||
|
||||
## Events
|
||||
|
||||
@@ -68,7 +68,7 @@ export class Item implements ComponentInterface {
|
||||
* When using a router, it specifies the transition direction when navigating to
|
||||
* another page using `href`.
|
||||
*/
|
||||
@Prop() routerDirection?: RouterDirection;
|
||||
@Prop() routerDirection: RouterDirection = 'forward';
|
||||
|
||||
/**
|
||||
* The type of the button. Only used when an `onclick` or `button` property is present.
|
||||
|
||||
@@ -56,18 +56,18 @@ The highlight color changes based on the item state, but all of the states use I
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Attribute | Description | Type | Default |
|
||||
| ----------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | --------------------- |
|
||||
| `button` | `button` | If `true`, a button tag will be rendered and the item will be tappable. | `boolean` | `false` |
|
||||
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
|
||||
| `detailIcon` | `detail-icon` | The icon to use when `detail` is set to `true`. | `string` | `'ios-arrow-forward'` |
|
||||
| `detail` | `detail` | If `true`, a detail arrow will appear on the item. Defaults to `false` unless the `mode` is `ios` and an `href`, `onclick` or `button` property is present. | `boolean \| undefined` | `undefined` |
|
||||
| `disabled` | `disabled` | If `true`, the user cannot interact with the item. | `boolean` | `false` |
|
||||
| `href` | `href` | Contains a URL or a URL fragment that the hyperlink points to. If this property is set, an anchor tag will be rendered. | `string \| undefined` | `undefined` |
|
||||
| `lines` | `lines` | How the bottom border should be displayed on the item. | `"full" \| "inset" \| "none" \| undefined` | `undefined` |
|
||||
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
|
||||
| `routerDirection` | `router-direction` | When using a router, it specifies the transition direction when navigating to another page using `href`. | `"back" \| "forward" \| "root" \| undefined` | `undefined` |
|
||||
| `type` | `type` | The type of the button. Only used when an `onclick` or `button` property is present. | `"button" \| "reset" \| "submit"` | `'button'` |
|
||||
| Property | Attribute | Description | Type | Default |
|
||||
| ----------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | --------------------- |
|
||||
| `button` | `button` | If `true`, a button tag will be rendered and the item will be tappable. | `boolean` | `false` |
|
||||
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
|
||||
| `detailIcon` | `detail-icon` | The icon to use when `detail` is set to `true`. | `string` | `'ios-arrow-forward'` |
|
||||
| `detail` | `detail` | If `true`, a detail arrow will appear on the item. Defaults to `false` unless the `mode` is `ios` and an `href`, `onclick` or `button` property is present. | `boolean \| undefined` | `undefined` |
|
||||
| `disabled` | `disabled` | If `true`, the user cannot interact with the item. | `boolean` | `false` |
|
||||
| `href` | `href` | Contains a URL or a URL fragment that the hyperlink points to. If this property is set, an anchor tag will be rendered. | `string \| undefined` | `undefined` |
|
||||
| `lines` | `lines` | How the bottom border should be displayed on the item. | `"full" \| "inset" \| "none" \| undefined` | `undefined` |
|
||||
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
|
||||
| `routerDirection` | `router-direction` | When using a router, it specifies the transition direction when navigating to another page using `href`. | `"back" \| "forward" \| "root"` | `'forward'` |
|
||||
| `type` | `type` | The type of the button. Only used when an `onclick` or `button` property is present. | `"button" \| "reset" \| "submit"` | `'button'` |
|
||||
|
||||
|
||||
## CSS Custom Properties
|
||||
|
||||
@@ -137,7 +137,6 @@ export class Menu implements ComponentInterface, MenuI {
|
||||
|
||||
/**
|
||||
* Emitted when the menu state is changed.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
@Event() protected ionMenuChange!: EventEmitter<MenuChangeEventDetail>;
|
||||
|
||||
@@ -12,6 +12,12 @@ export interface NavResult {
|
||||
direction?: NavDirection;
|
||||
}
|
||||
|
||||
export interface SwipeGestureHandler {
|
||||
canStart(): boolean;
|
||||
onStart(): void;
|
||||
onEnd(shouldComplete: boolean): void;
|
||||
}
|
||||
|
||||
export interface RouterOutletOptions {
|
||||
animated?: boolean;
|
||||
animationBuilder?: AnimationBuilder;
|
||||
@@ -23,6 +29,7 @@ export interface RouterOutletOptions {
|
||||
mode?: Mode;
|
||||
keyboardClose?: boolean;
|
||||
skipIfBusy?: boolean;
|
||||
progressAnimation?: boolean;
|
||||
}
|
||||
|
||||
export interface NavOptions extends RouterOutletOptions {
|
||||
|
||||
@@ -9,22 +9,3 @@
|
||||
overflow: hidden;
|
||||
z-index: $z-index-page-container;
|
||||
}
|
||||
|
||||
.nav-decor {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host(.show-decor) .nav-decor {
|
||||
@include position(0, 0, 0, 0);
|
||||
|
||||
display: block;
|
||||
|
||||
// when ios pages transition, the leaving page grays out
|
||||
// this is the black square behind all pages so they gray out
|
||||
position: absolute;
|
||||
|
||||
background: #000;
|
||||
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Build, Component, Element, Event, EventEmitter, Method, Prop, QueueApi, Watch } from '@stencil/core';
|
||||
|
||||
import { ViewLifecycle } from '../..';
|
||||
import { Animation, AnimationBuilder, ComponentProps, Config, FrameworkDelegate, Gesture, GestureDetail, Mode, NavComponent, NavOptions, NavOutlet, NavResult, RouteID, RouteWrite, TransitionDoneFn, TransitionInstruction, ViewController } from '../../interface';
|
||||
import { Animation, AnimationBuilder, ComponentProps, Config, FrameworkDelegate, Gesture, Mode, NavComponent, NavOptions, NavOutlet, NavResult, RouteID, RouteWrite, TransitionDoneFn, TransitionInstruction, ViewController } from '../../interface';
|
||||
import { assert } from '../../utils/helpers';
|
||||
import { TransitionOptions, lifecycle, setPageHidden, transition } from '../../utils/transition';
|
||||
|
||||
@@ -15,7 +15,7 @@ import { ViewState, convertToViews, matches } from './view-controller';
|
||||
export class Nav implements NavOutlet {
|
||||
|
||||
private transInstr: TransitionInstruction[] = [];
|
||||
private sbTrns?: Animation;
|
||||
private sbAni?: Animation;
|
||||
private useRouter = false;
|
||||
private isTransitioning = false;
|
||||
private destroyed = false;
|
||||
@@ -111,23 +111,20 @@ export class Nav implements NavOutlet {
|
||||
async componentDidLoad() {
|
||||
this.rootChanged();
|
||||
|
||||
this.gesture = (await import('../../utils/gesture/gesture')).createGesture({
|
||||
el: this.win.document.body,
|
||||
queue: this.queue,
|
||||
gestureName: 'goback-swipe',
|
||||
gesturePriority: 30,
|
||||
threshold: 10,
|
||||
canStart: () => this.canStart(),
|
||||
onStart: () => this.onStart(),
|
||||
onMove: ev => this.onMove(ev),
|
||||
onEnd: ev => this.onEnd(ev),
|
||||
});
|
||||
this.gesture = (await import('../../utils/gesture/swipe-back')).createSwipeBackGesture(
|
||||
this.el,
|
||||
this.queue,
|
||||
this.canStart.bind(this),
|
||||
this.onStart.bind(this),
|
||||
this.onMove.bind(this),
|
||||
this.onEnd.bind(this)
|
||||
);
|
||||
this.swipeGestureChanged();
|
||||
}
|
||||
|
||||
componentDidUnload() {
|
||||
for (const view of this.views) {
|
||||
lifecycle(this.win, view.element!, ViewLifecycle.WillUnload);
|
||||
lifecycle(view.element!, ViewLifecycle.WillUnload);
|
||||
view._destroy();
|
||||
}
|
||||
|
||||
@@ -136,11 +133,7 @@ export class Nav implements NavOutlet {
|
||||
}
|
||||
|
||||
// release swipe back gesture and transition
|
||||
if (this.sbTrns) {
|
||||
this.sbTrns.destroy();
|
||||
}
|
||||
this.transInstr.length = this.views.length = 0;
|
||||
this.sbTrns = undefined;
|
||||
this.destroyed = true;
|
||||
}
|
||||
|
||||
@@ -756,9 +749,9 @@ export class Nav implements NavOutlet {
|
||||
// let's make sure, callbacks are zoned
|
||||
if (destroyQueue && destroyQueue.length > 0) {
|
||||
for (const view of destroyQueue) {
|
||||
lifecycle(this.win, view.element, ViewLifecycle.WillLeave);
|
||||
lifecycle(this.win, view.element, ViewLifecycle.DidLeave);
|
||||
lifecycle(this.win, view.element, ViewLifecycle.WillUnload);
|
||||
lifecycle(view.element, ViewLifecycle.WillLeave);
|
||||
lifecycle(view.element, ViewLifecycle.DidLeave);
|
||||
lifecycle(view.element, ViewLifecycle.WillUnload);
|
||||
}
|
||||
|
||||
// once all lifecycle events has been delivered, we can safely detroy the views
|
||||
@@ -773,19 +766,12 @@ export class Nav implements NavOutlet {
|
||||
leavingView: ViewController | undefined,
|
||||
ti: TransitionInstruction
|
||||
): Promise<NavResult> {
|
||||
if (this.sbTrns) {
|
||||
this.sbTrns.destroy();
|
||||
this.sbTrns = undefined;
|
||||
}
|
||||
|
||||
// we should animate (duration > 0) if the pushed page is not the first one (startup)
|
||||
// or if it is a portal (modal, actionsheet, etc.)
|
||||
const opts = ti.opts!;
|
||||
|
||||
const progressCallback = opts.progressAnimation
|
||||
? (animation: Animation) => {
|
||||
this.sbTrns = animation;
|
||||
}
|
||||
? (ani: Animation | undefined) => this.sbAni = ani
|
||||
: undefined;
|
||||
|
||||
const enteringEl = enteringView.element!;
|
||||
@@ -887,7 +873,7 @@ export class Nav implements NavOutlet {
|
||||
if (i > activeViewIndex) {
|
||||
// this view comes after the active view
|
||||
// let's unload it
|
||||
lifecycle(this.win, element, ViewLifecycle.WillUnload);
|
||||
lifecycle(element, ViewLifecycle.WillUnload);
|
||||
this.destroyView(view);
|
||||
} else if (i < activeViewIndex) {
|
||||
// this view comes before the active view
|
||||
@@ -898,72 +884,40 @@ export class Nav implements NavOutlet {
|
||||
}
|
||||
|
||||
private canStart(): boolean {
|
||||
return !!this.swipeGesture &&
|
||||
return (
|
||||
!!this.swipeGesture &&
|
||||
!this.isTransitioning &&
|
||||
this.canGoBackSync();
|
||||
}
|
||||
|
||||
private onStart() {
|
||||
if (this.isTransitioning || this.transInstr.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// default the direction to "back";
|
||||
const opts: NavOptions = {
|
||||
direction: 'back',
|
||||
progressAnimation: true
|
||||
};
|
||||
|
||||
this.queueTrns(
|
||||
{
|
||||
removeStart: -1,
|
||||
removeCount: 1,
|
||||
opts
|
||||
},
|
||||
undefined
|
||||
this.transInstr.length === 0 &&
|
||||
this.canGoBackSync()
|
||||
);
|
||||
}
|
||||
|
||||
private onMove(detail: GestureDetail) {
|
||||
if (this.sbTrns) {
|
||||
// continue to disable the app while actively dragging
|
||||
this.isTransitioning = true;
|
||||
private onStart() {
|
||||
this.queueTrns({
|
||||
removeStart: -1,
|
||||
removeCount: 1,
|
||||
opts: {
|
||||
direction: 'back',
|
||||
progressAnimation: true
|
||||
}
|
||||
}, undefined);
|
||||
}
|
||||
|
||||
// set the transition animation's progress
|
||||
const delta = detail.deltaX;
|
||||
const stepValue = delta / this.win.innerWidth;
|
||||
// set the transition animation's progress
|
||||
this.sbTrns.progressStep(stepValue);
|
||||
private onMove(stepValue: number) {
|
||||
if (this.sbAni) {
|
||||
this.sbAni.progressStep(stepValue);
|
||||
}
|
||||
}
|
||||
|
||||
private onEnd(detail: GestureDetail) {
|
||||
if (this.sbTrns) {
|
||||
// the swipe back gesture has ended
|
||||
const delta = detail.deltaX;
|
||||
const width = this.win.innerWidth;
|
||||
const stepValue = delta / width;
|
||||
const velocity = detail.velocityX;
|
||||
const z = width / 2.0;
|
||||
const shouldComplete =
|
||||
velocity >= 0 && (velocity > 0.2 || detail.deltaX > z);
|
||||
|
||||
const missing = shouldComplete ? 1 - stepValue : stepValue;
|
||||
const missingDistance = missing * width;
|
||||
let realDur = 0;
|
||||
if (missingDistance > 5) {
|
||||
const dur = missingDistance / Math.abs(velocity);
|
||||
realDur = Math.min(dur, 300);
|
||||
}
|
||||
|
||||
this.sbTrns.progressEnd(shouldComplete, stepValue, realDur);
|
||||
private onEnd(shouldComplete: boolean, stepValue: number, dur: number) {
|
||||
if (this.sbAni) {
|
||||
this.sbAni.progressEnd(shouldComplete, stepValue, dur);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return [
|
||||
this.mode === 'ios' && <div class="nav-decor" />,
|
||||
return (
|
||||
<slot></slot>
|
||||
];
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,13 @@ While RouterOutlet has methods for navigating around, it's recommended to use th
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Attribute | Description | Type | Default |
|
||||
| ----------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | ----------- |
|
||||
| `animated` | `animated` | If `true`, the router-outlet should animate the transition of components. | `boolean` | `true` |
|
||||
| `animation` | -- | By default `ion-nav` animates transition between pages based in the mode (ios or material design). However, this property allows to create custom transition using `AnimateBuilder` functions. | `AnimationBuilder \| undefined` | `undefined` |
|
||||
| `delegate` | -- | | `FrameworkDelegate \| undefined` | `undefined` |
|
||||
| Property | Attribute | Description | Type | Default |
|
||||
| -------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | ----------- |
|
||||
| `animated` | `animated` | If `true`, the router-outlet should animate the transition of components. | `boolean` | `true` |
|
||||
| `animation` | -- | By default `ion-nav` animates transition between pages based in the mode (ios or material design). However, this property allows to create custom transition using `AnimateBuilder` functions. | `AnimationBuilder \| undefined` | `undefined` |
|
||||
| `delegate` | -- | | `FrameworkDelegate \| undefined` | `undefined` |
|
||||
| `mode` | `mode` | | `"ios" \| "md"` | `undefined` |
|
||||
| `swipeHandler` | -- | | `SwipeGestureHandler \| undefined` | `undefined` |
|
||||
|
||||
|
||||
## Methods
|
||||
@@ -50,24 +52,6 @@ Type: `Promise<RouteID | undefined>`
|
||||
|
||||
|
||||
|
||||
### `setRoot(component: ComponentRef, params?: { [key: string]: any; } | undefined, opts?: RouterOutletOption...`
|
||||
|
||||
Set the root component for the given navigation stack
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| ----------- | ------------------------------------------- | ----------- |
|
||||
| `component` | `Function \| HTMLElement \| null \| string` | |
|
||||
| `params` | `undefined \| { [key: string]: any; }` | |
|
||||
| `opts` | `RouterOutletOptions \| undefined` | |
|
||||
|
||||
#### Returns
|
||||
|
||||
Type: `Promise<boolean>`
|
||||
|
||||
|
||||
|
||||
### `setRouteId(id: string, params: { [key: string]: any; } | undefined, direction: number) => Promise<RouteWrite>`
|
||||
|
||||
|
||||
|
||||
@@ -9,22 +9,3 @@
|
||||
overflow: hidden;
|
||||
z-index: $z-index-page-container;
|
||||
}
|
||||
|
||||
.nav-decor {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host(.show-decor) .nav-decor {
|
||||
@include position(0, 0, 0, 0);
|
||||
|
||||
display: block;
|
||||
|
||||
// when ios pages transition, the leaving page grays out
|
||||
// this is the black square behind all pages so they gray out
|
||||
position: absolute;
|
||||
|
||||
background: #000;
|
||||
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Prop, QueueApi } from '@stencil/core';
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Prop, QueueApi, Watch } from '@stencil/core';
|
||||
|
||||
import { AnimationBuilder, ComponentProps, ComponentRef, Config, FrameworkDelegate, Mode, NavOutlet, RouteID, RouteWrite, RouterOutletOptions } from '../../interface';
|
||||
import { Animation, AnimationBuilder, ComponentProps, ComponentRef, Config, FrameworkDelegate, Gesture, Mode, NavOutlet, RouteID, RouteWrite, RouterOutletOptions, SwipeGestureHandler } from '../../interface';
|
||||
import { transition } from '../../utils';
|
||||
import { attachComponent, detachComponent } from '../../utils/framework-delegate';
|
||||
|
||||
@@ -14,8 +14,8 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
|
||||
private activeEl: HTMLElement | undefined;
|
||||
private activeComponent: any;
|
||||
private waitPromise?: Promise<void>;
|
||||
|
||||
mode!: Mode;
|
||||
private gesture?: Gesture;
|
||||
private ani?: Animation;
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
@@ -24,6 +24,9 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
|
||||
@Prop({ context: 'window' }) win!: Window;
|
||||
@Prop({ context: 'queue' }) queue!: QueueApi;
|
||||
|
||||
/** @internal */
|
||||
@Prop() mode!: Mode;
|
||||
|
||||
/** @internal */
|
||||
@Prop() delegate?: FrameworkDelegate;
|
||||
|
||||
@@ -38,50 +41,49 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
|
||||
*/
|
||||
@Prop() animation?: AnimationBuilder;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
/** @internal */
|
||||
@Prop() swipeHandler?: SwipeGestureHandler;
|
||||
@Watch('swipeHandler')
|
||||
swipeHandlerChanged() {
|
||||
if (this.gesture) {
|
||||
this.gesture.setDisabled(this.swipeHandler === undefined);
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@Event() ionNavWillLoad!: EventEmitter<void>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
/** @internal */
|
||||
@Event() ionNavWillChange!: EventEmitter<void>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
/** @internal */
|
||||
@Event() ionNavDidChange!: EventEmitter<void>;
|
||||
|
||||
componentWillLoad() {
|
||||
this.ionNavWillLoad.emit();
|
||||
}
|
||||
|
||||
componentDidUnload() {
|
||||
this.activeEl = this.activeComponent = undefined;
|
||||
async componentDidLoad() {
|
||||
this.gesture = (await import('../../utils/gesture/swipe-back')).createSwipeBackGesture(
|
||||
this.el,
|
||||
this.queue,
|
||||
() => !!this.swipeHandler && this.swipeHandler.canStart(),
|
||||
() => this.swipeHandler && this.swipeHandler.onStart(),
|
||||
step => this.ani && this.ani.progressStep(step),
|
||||
(shouldComplete, step, dur) => {
|
||||
if (this.ani) {
|
||||
this.ani.progressEnd(shouldComplete, step, dur);
|
||||
}
|
||||
if (this.swipeHandler) {
|
||||
this.swipeHandler.onEnd(shouldComplete);
|
||||
}
|
||||
}
|
||||
);
|
||||
this.swipeHandlerChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the root component for the given navigation stack
|
||||
*/
|
||||
@Method()
|
||||
async setRoot(component: ComponentRef, params?: ComponentProps, opts?: RouterOutletOptions): Promise<boolean> {
|
||||
if (this.activeComponent === component) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// attach entering view to DOM
|
||||
const leavingEl = this.activeEl;
|
||||
const enteringEl = await attachComponent(this.delegate, this.el, component, ['ion-page', 'ion-page-invisible'], params);
|
||||
|
||||
this.activeComponent = component;
|
||||
this.activeEl = enteringEl;
|
||||
|
||||
// commit animation
|
||||
await this.commit(enteringEl, leavingEl, opts);
|
||||
await detachComponent(this.delegate, leavingEl);
|
||||
|
||||
return true;
|
||||
componentDidUnload() {
|
||||
this.activeEl = this.activeComponent = undefined;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -121,19 +123,26 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
|
||||
} : undefined;
|
||||
}
|
||||
|
||||
private async lock() {
|
||||
const p = this.waitPromise;
|
||||
let resolve!: () => void;
|
||||
this.waitPromise = new Promise(r => resolve = r);
|
||||
|
||||
if (p !== undefined) {
|
||||
await p;
|
||||
private async setRoot(component: ComponentRef, params?: ComponentProps, opts?: RouterOutletOptions): Promise<boolean> {
|
||||
if (this.activeComponent === component) {
|
||||
return false;
|
||||
}
|
||||
return resolve;
|
||||
|
||||
// attach entering view to DOM
|
||||
const leavingEl = this.activeEl;
|
||||
const enteringEl = await attachComponent(this.delegate, this.el, component, ['ion-page', 'ion-page-invisible'], params);
|
||||
|
||||
this.activeComponent = component;
|
||||
this.activeEl = enteringEl;
|
||||
|
||||
// commit animation
|
||||
await this.commit(enteringEl, leavingEl, opts);
|
||||
await detachComponent(this.delegate, leavingEl);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async transition(enteringEl: HTMLElement, leavingEl: HTMLElement | undefined, opts?: RouterOutletOptions): Promise<boolean> {
|
||||
// isTransitioning acts as a lock to prevent reentering
|
||||
private async transition(enteringEl: HTMLElement, leavingEl: HTMLElement | undefined, opts: RouterOutletOptions = {}): Promise<boolean> {
|
||||
if (leavingEl === enteringEl) {
|
||||
return false;
|
||||
}
|
||||
@@ -141,8 +150,6 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
|
||||
// emit nav will change event
|
||||
this.ionNavWillChange.emit();
|
||||
|
||||
opts = opts || {};
|
||||
|
||||
const { mode, queue, animationCtrl, win, el } = this;
|
||||
const animated = this.animated && this.config.getBoolean('animated', true);
|
||||
const animationBuilder = this.animation || opts.animationBuilder || this.config.get('navAnimation');
|
||||
@@ -157,7 +164,10 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
|
||||
enteringEl,
|
||||
leavingEl,
|
||||
baseEl: el,
|
||||
|
||||
progressCallback: (opts.progressAnimation
|
||||
? ani => this.ani = ani
|
||||
: undefined
|
||||
),
|
||||
...opts
|
||||
});
|
||||
|
||||
@@ -167,10 +177,20 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
|
||||
return true;
|
||||
}
|
||||
|
||||
private async lock() {
|
||||
const p = this.waitPromise;
|
||||
let resolve!: () => void;
|
||||
this.waitPromise = new Promise(r => resolve = r);
|
||||
|
||||
if (p !== undefined) {
|
||||
await p;
|
||||
}
|
||||
return resolve;
|
||||
}
|
||||
|
||||
render() {
|
||||
return [
|
||||
this.mode === 'ios' && <div class="nav-decor"/>,
|
||||
return (
|
||||
<slot></slot>
|
||||
];
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,3 +11,4 @@ export const enum ViewLifecycle {
|
||||
// util functions
|
||||
export * from './utils/platform';
|
||||
export * from './utils/config';
|
||||
export * from './utils/gesture/swipe-back';
|
||||
|
||||
@@ -32,9 +32,7 @@ export function iosTransitionAnimation(AnimationC: Animation, navEl: HTMLElement
|
||||
if (leavingEl && navEl) {
|
||||
const navDecor = new AnimationC();
|
||||
navDecor
|
||||
.addElement(navEl)
|
||||
.beforeAddClass('show-decor')
|
||||
.afterRemoveClass('show-decor');
|
||||
.addElement(navEl);
|
||||
|
||||
rootTransition.add(navDecor);
|
||||
}
|
||||
|
||||
@@ -60,8 +60,8 @@ export class GestureController {
|
||||
this.capturedId = id;
|
||||
requestedStart.clear();
|
||||
|
||||
const event = new CustomEvent('ionGestureCaptured', { detail: gestureName });
|
||||
this.doc.body.dispatchEvent(event);
|
||||
const event = new CustomEvent('ionGestureCaptured', { detail: { gestureName } });
|
||||
this.doc.dispatchEvent(event);
|
||||
return true;
|
||||
}
|
||||
requestedStart.delete(id);
|
||||
@@ -144,14 +144,16 @@ export class GestureController {
|
||||
|
||||
export class GestureDelegate {
|
||||
private ctrl?: GestureController;
|
||||
private priority: number;
|
||||
|
||||
constructor(
|
||||
ctrl: GestureController,
|
||||
private id: number,
|
||||
private name: string,
|
||||
private priority: number,
|
||||
priority: number,
|
||||
private disableScroll: boolean
|
||||
) {
|
||||
this.priority = priority * 1000000 + id;
|
||||
this.ctrl = ctrl;
|
||||
}
|
||||
|
||||
|
||||
56
core/src/utils/gesture/swipe-back.ts
Normal file
56
core/src/utils/gesture/swipe-back.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { QueueApi } from '@stencil/core';
|
||||
|
||||
import { Gesture, GestureDetail, createGesture } from './gesture';
|
||||
|
||||
export function createSwipeBackGesture(
|
||||
el: HTMLElement,
|
||||
queue: QueueApi,
|
||||
canStartHandler: () => boolean,
|
||||
onStartHandler: () => void,
|
||||
onMoveHandler: (step: number) => void,
|
||||
onEndHandler: (shouldComplete: boolean, step: number, dur: number) => void,
|
||||
): Gesture {
|
||||
const win = el.ownerDocument!.defaultView!;
|
||||
function canStart(detail: GestureDetail) {
|
||||
return detail.startX <= 5000 && canStartHandler();
|
||||
}
|
||||
|
||||
function onMove(detail: GestureDetail) {
|
||||
// set the transition animation's progress
|
||||
const delta = detail.deltaX;
|
||||
const stepValue = delta / win.innerWidth;
|
||||
onMoveHandler(stepValue);
|
||||
}
|
||||
|
||||
function onEnd(detail: GestureDetail) {
|
||||
// the swipe back gesture has ended
|
||||
const delta = detail.deltaX;
|
||||
const width = win.innerWidth;
|
||||
const stepValue = delta / width;
|
||||
const velocity = detail.velocityX;
|
||||
const z = width / 2.0;
|
||||
const shouldComplete =
|
||||
velocity >= 0 && (velocity > 0.2 || detail.deltaX > z);
|
||||
|
||||
const missing = shouldComplete ? 1 - stepValue : stepValue;
|
||||
const missingDistance = missing * width;
|
||||
let realDur = 0;
|
||||
if (missingDistance > 5) {
|
||||
const dur = missingDistance / Math.abs(velocity);
|
||||
realDur = Math.min(dur, 300);
|
||||
}
|
||||
onEndHandler(shouldComplete, stepValue, realDur);
|
||||
}
|
||||
|
||||
return createGesture({
|
||||
el,
|
||||
queue,
|
||||
gestureName: 'goback-swipe',
|
||||
gesturePriority: 30,
|
||||
threshold: 10,
|
||||
canStart,
|
||||
onStart: onStartHandler,
|
||||
onMove,
|
||||
onEnd
|
||||
});
|
||||
}
|
||||
@@ -137,15 +137,15 @@ export function startTapClick(doc: Document) {
|
||||
}
|
||||
}
|
||||
|
||||
doc.body.addEventListener('click', onBodyClick, true);
|
||||
doc.body.addEventListener('ionScrollStart', () => {
|
||||
doc.addEventListener('click', onBodyClick, true);
|
||||
doc.addEventListener('ionScrollStart', () => {
|
||||
scrolling = true;
|
||||
cancelActive();
|
||||
});
|
||||
doc.body.addEventListener('ionScrollEnd', () => {
|
||||
doc.addEventListener('ionScrollEnd', () => {
|
||||
scrolling = false;
|
||||
});
|
||||
doc.body.addEventListener('ionGestureCaptured', cancelActive);
|
||||
doc.addEventListener('ionGestureCaptured', cancelActive);
|
||||
|
||||
doc.addEventListener('touchstart', onTouchStart, true);
|
||||
doc.addEventListener('touchcancel', onTouchEnd, true);
|
||||
|
||||
@@ -38,7 +38,7 @@ export function getClassMap(classes: string | string[] | undefined): CssClassMap
|
||||
return map;
|
||||
}
|
||||
|
||||
export async function openURL(win: Window, url: string | undefined | null, ev: Event | undefined | null, direction?: RouterDirection): Promise<boolean> {
|
||||
export async function openURL(win: Window, url: string | undefined | null, ev: Event | undefined | null, direction: RouterDirection): Promise<boolean> {
|
||||
if (url != null && url[0] !== '#' && url.indexOf('://') === -1) {
|
||||
const router = win.document.querySelector('ion-router');
|
||||
if (router) {
|
||||
|
||||
@@ -76,16 +76,19 @@ async function getAnimationBuilder(opts: TransitionOptions): Promise<AnimationBu
|
||||
async function animation(animationBuilder: AnimationBuilder, opts: TransitionOptions): Promise<TransitionResult> {
|
||||
await waitForReady(opts, true);
|
||||
|
||||
const trns = await opts.animationCtrl.create(animationBuilder, opts.baseEl, opts);
|
||||
fireWillEvents(opts.window, opts.enteringEl, opts.leavingEl);
|
||||
await playTransition(trns, opts);
|
||||
const trans = await opts.animationCtrl.create(animationBuilder, opts.baseEl, opts);
|
||||
fireWillEvents(opts.enteringEl, opts.leavingEl);
|
||||
await playTransition(trans, opts);
|
||||
if (opts.progressCallback) {
|
||||
opts.progressCallback(undefined);
|
||||
}
|
||||
|
||||
if (trns.hasCompleted) {
|
||||
fireDidEvents(opts.window, opts.enteringEl, opts.leavingEl);
|
||||
if (trans.hasCompleted) {
|
||||
fireDidEvents(opts.enteringEl, opts.leavingEl);
|
||||
}
|
||||
return {
|
||||
hasCompleted: trns.hasCompleted,
|
||||
animation: trns
|
||||
hasCompleted: trans.hasCompleted,
|
||||
animation: trans
|
||||
};
|
||||
}
|
||||
|
||||
@@ -95,8 +98,8 @@ async function noAnimation(opts: TransitionOptions): Promise<TransitionResult> {
|
||||
|
||||
await waitForReady(opts, false);
|
||||
|
||||
fireWillEvents(opts.window, enteringEl, leavingEl);
|
||||
fireDidEvents(opts.window, enteringEl, leavingEl);
|
||||
fireWillEvents(enteringEl, leavingEl);
|
||||
fireDidEvents(enteringEl, leavingEl);
|
||||
|
||||
return {
|
||||
hasCompleted: true
|
||||
@@ -144,20 +147,19 @@ function playTransition(trans: Animation, opts: TransitionOptions): Promise<Anim
|
||||
return promise;
|
||||
}
|
||||
|
||||
function fireWillEvents(win: Window, enteringEl: HTMLElement | undefined, leavingEl: HTMLElement | undefined) {
|
||||
lifecycle(win, leavingEl, ViewLifecycle.WillLeave);
|
||||
lifecycle(win, enteringEl, ViewLifecycle.WillEnter);
|
||||
function fireWillEvents(enteringEl: HTMLElement | undefined, leavingEl: HTMLElement | undefined) {
|
||||
lifecycle(leavingEl, ViewLifecycle.WillLeave);
|
||||
lifecycle(enteringEl, ViewLifecycle.WillEnter);
|
||||
}
|
||||
|
||||
function fireDidEvents(win: Window, enteringEl: HTMLElement | undefined, leavingEl: HTMLElement | undefined) {
|
||||
lifecycle(win, enteringEl, ViewLifecycle.DidEnter);
|
||||
lifecycle(win, leavingEl, ViewLifecycle.DidLeave);
|
||||
function fireDidEvents(enteringEl: HTMLElement | undefined, leavingEl: HTMLElement | undefined) {
|
||||
lifecycle(enteringEl, ViewLifecycle.DidEnter);
|
||||
lifecycle(leavingEl, ViewLifecycle.DidLeave);
|
||||
}
|
||||
|
||||
export function lifecycle(win: Window, el: HTMLElement | undefined, eventName: ViewLifecycle) {
|
||||
export function lifecycle(el: HTMLElement | undefined, eventName: ViewLifecycle) {
|
||||
if (el) {
|
||||
const CEvent: typeof CustomEvent = (win as any).CustomEvent;
|
||||
const event = new CEvent(eventName, {
|
||||
const event = new CustomEvent(eventName, {
|
||||
bubbles: false,
|
||||
cancelable: false,
|
||||
});
|
||||
@@ -214,7 +216,7 @@ function setZIndex(
|
||||
export interface TransitionOptions extends NavOptions {
|
||||
animationCtrl: HTMLIonAnimationControllerElement;
|
||||
queue: QueueApi;
|
||||
progressCallback?: ((ani: Animation) => void);
|
||||
progressCallback?: ((ani: Animation | undefined) => void);
|
||||
window: Window;
|
||||
baseEl: HTMLElement;
|
||||
enteringEl: HTMLElement;
|
||||
|
||||
Reference in New Issue
Block a user