feat(angular): build for angular 12.0 (#23970)

This commit is contained in:
Mike Hartington
2021-10-15 16:54:59 -04:00
committed by GitHub
parent e3996cfbd5
commit 3451a34ad0
67 changed files with 55358 additions and 29616 deletions

View File

@ -1,32 +1,30 @@
import { Directive, ElementRef, HostListener, Injector } from '@angular/core';
import { Directive, HostListener, ElementRef, Injector } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { ValueAccessor, setIonicClasses } from './value-accessor';
@Directive({
/* tslint:disable-next-line:directive-selector */
selector: 'ion-checkbox,ion-toggle',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: BooleanValueAccessor,
multi: true
}
]
useExisting: BooleanValueAccessorDirective,
multi: true,
},
],
})
export class BooleanValueAccessor extends ValueAccessor {
export class BooleanValueAccessorDirective extends ValueAccessor {
constructor(injector: Injector, el: ElementRef) {
super(injector, el);
}
writeValue(value: any) {
writeValue(value: any): void {
this.el.nativeElement.checked = this.lastValue = value == null ? false : value;
setIonicClasses(this.el);
}
@HostListener('ionChange', ['$event.target'])
_handleIonChange(el: any) {
_handleIonChange(el: any): void {
this.handleChangeEvent(el, el.checked);
}
}

View File

@ -1,32 +1,30 @@
import { Directive, ElementRef, HostListener, Injector } from '@angular/core';
import { Directive, HostListener, ElementRef, Injector } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { ValueAccessor } from './value-accessor';
@Directive({
/* tslint:disable-next-line:directive-selector */
selector: 'ion-input[type=number]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: NumericValueAccessor,
multi: true
}
]
useExisting: NumericValueAccessorDirective,
multi: true,
},
],
})
export class NumericValueAccessor extends ValueAccessor {
export class NumericValueAccessorDirective extends ValueAccessor {
constructor(injector: Injector, el: ElementRef) {
super(injector, el);
}
@HostListener('ionChange', ['$event.target'])
_handleIonChange(el: any) {
_handleIonChange(el: any): void {
this.handleChangeEvent(el, el.value);
}
registerOnChange(fn: (_: number | null) => void) {
super.registerOnChange(value => {
registerOnChange(fn: (_: number | null) => void): void {
super.registerOnChange((value) => {
fn(value === '' ? null : parseFloat(value));
});
}

View File

@ -1,4 +1,4 @@
import { Directive, ElementRef, HostListener, Injector } from '@angular/core';
import { ElementRef, Injector, Directive, HostListener } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { ValueAccessor } from './value-accessor';
@ -9,19 +9,18 @@ import { ValueAccessor } from './value-accessor';
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: RadioValueAccessor,
multi: true
}
]
useExisting: RadioValueAccessorDirective,
multi: true,
},
],
})
export class RadioValueAccessor extends ValueAccessor {
export class RadioValueAccessorDirective extends ValueAccessor {
constructor(injector: Injector, el: ElementRef) {
super(injector, el);
}
@HostListener('ionSelect', ['$event.target'])
_handleIonSelect(el: any) {
_handleIonSelect(el: any): void {
this.handleChangeEvent(el, el.checked);
}
}

View File

@ -1,4 +1,4 @@
import { Directive, ElementRef, HostListener, Injector } from '@angular/core';
import { ElementRef, Injector, Directive, HostListener } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { ValueAccessor } from './value-accessor';
@ -9,19 +9,18 @@ import { ValueAccessor } from './value-accessor';
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: SelectValueAccessor,
multi: true
}
]
useExisting: SelectValueAccessorDirective,
multi: true,
},
],
})
export class SelectValueAccessor extends ValueAccessor {
export class SelectValueAccessorDirective extends ValueAccessor {
constructor(injector: Injector, el: ElementRef) {
super(injector, el);
}
@HostListener('ionChange', ['$event.target'])
_handleChangeEvent(el: any) {
_handleChangeEvent(el: any): void {
this.handleChangeEvent(el, el.value);
}
}

View File

@ -1,4 +1,4 @@
import { Directive, ElementRef, HostListener, Injector } from '@angular/core';
import { ElementRef, Injector, Directive, HostListener } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { ValueAccessor } from './value-accessor';
@ -9,19 +9,18 @@ import { ValueAccessor } from './value-accessor';
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: TextValueAccessor,
multi: true
}
]
useExisting: TextValueAccessorDirective,
multi: true,
},
],
})
export class TextValueAccessor extends ValueAccessor {
export class TextValueAccessorDirective extends ValueAccessor {
constructor(injector: Injector, el: ElementRef) {
super(injector, el);
}
@HostListener('ionChange', ['$event.target'])
_handleInputEvent(el: any) {
_handleInputEvent(el: any): void {
this.handleChangeEvent(el, el.value);
}
}

View File

@ -1,19 +1,23 @@
import { AfterViewInit, ElementRef, HostListener, Injector, OnDestroy, Type } from '@angular/core';
import { AfterViewInit, ElementRef, Injector, OnDestroy, Directive, HostListener } from '@angular/core';
import { ControlValueAccessor, NgControl } from '@angular/forms';
import { Subscription } from 'rxjs';
import { raf } from '../../util/util';
@Directive()
export class ValueAccessor implements ControlValueAccessor, AfterViewInit, OnDestroy {
private onChange: (value: any) => void = () => {/**/};
private onTouched: () => void = () => {/**/};
private onChange: (value: any) => void = () => {
/**/
};
private onTouched: () => void = () => {
/**/
};
protected lastValue: any;
private statusChanges?: Subscription;
constructor(protected injector: Injector, protected el: ElementRef) {}
writeValue(value: any) {
writeValue(value: any): void {
/**
* TODO for Ionic 6:
* Change `value == null ? '' : value;`
@ -25,7 +29,7 @@ export class ValueAccessor implements ControlValueAccessor, AfterViewInit, OnDes
setIonicClasses(this.el);
}
handleChangeEvent(el: HTMLElement, value: any) {
handleChangeEvent(el: HTMLElement, value: any): void {
if (el === this.el.nativeElement) {
if (value !== this.lastValue) {
this.lastValue = value;
@ -36,38 +40,42 @@ export class ValueAccessor implements ControlValueAccessor, AfterViewInit, OnDes
}
@HostListener('ionBlur', ['$event.target'])
_handleBlurEvent(el: any) {
_handleBlurEvent(el: any): void {
if (el === this.el.nativeElement) {
this.onTouched();
setIonicClasses(this.el);
}
}
registerOnChange(fn: (value: any) => void) {
registerOnChange(fn: (value: any) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void) {
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean) {
setDisabledState(isDisabled: boolean): void {
this.el.nativeElement.disabled = isDisabled;
}
ngOnDestroy() {
ngOnDestroy(): void {
if (this.statusChanges) {
this.statusChanges.unsubscribe();
}
}
ngAfterViewInit() {
ngAfterViewInit(): void {
let ngControl;
try {
ngControl = this.injector.get<NgControl>(NgControl as Type<NgControl>);
} catch { /* No FormControl or ngModel binding */ }
ngControl = this.injector.get<NgControl>(NgControl);
} catch {
/* No FormControl or ngModel binding */
}
if (!ngControl) { return; }
if (!ngControl) {
return;
}
// Listen for changes in validity, disabled, or pending states
if (ngControl.statusChanges) {
@ -84,15 +92,15 @@ export class ValueAccessor implements ControlValueAccessor, AfterViewInit, OnDes
* This patches the methods to manually sync
* the classes until this feature is implemented in Angular.
*/
const formControl = ngControl.control;
const formControl = ngControl.control as any;
if (formControl) {
const methodsToPatch = ['markAsTouched', 'markAllAsTouched', 'markAsUntouched', 'markAsDirty', 'markAsPristine'];
methodsToPatch.forEach(method => {
if (formControl[method]) {
const oldFn = formControl[method].bind(formControl);
formControl[method] = (...params) => {
oldFn(...params);
setIonicClasses(this.el);
methodsToPatch.forEach((method) => {
if (formControl.get(method)) {
const oldFn = formControl[method].bind(formControl);
formControl[method] = (...params: any[]) => {
oldFn(...params);
setIonicClasses(this.el);
};
}
});
@ -100,7 +108,7 @@ export class ValueAccessor implements ControlValueAccessor, AfterViewInit, OnDes
}
}
export const setIonicClasses = (element: ElementRef) => {
export const setIonicClasses = (element: ElementRef): void => {
raf(() => {
const input = element.nativeElement as HTMLElement;
const classes = getClasses(input);
@ -127,16 +135,11 @@ const getClasses = (element: HTMLElement) => {
const setClasses = (element: HTMLElement, classes: string[]) => {
const classList = element.classList;
[
'ion-valid',
'ion-invalid',
'ion-touched',
'ion-untouched',
'ion-dirty',
'ion-pristine'
].forEach(c => classList.remove(c));
['ion-valid', 'ion-invalid', 'ion-touched', 'ion-untouched', 'ion-dirty', 'ion-pristine'].forEach((c) =>
classList.remove(c)
);
classes.forEach(c => classList.add(c));
classes.forEach((c) => classList.add(c));
};
const startsWith = (input: string, search: string): boolean => {

View File

@ -1,4 +1,4 @@
import { Directive, HostListener, Optional } from '@angular/core';
import { Directive, HostListener, Input, Optional } from '@angular/core';
import { AnimationBuilder } from '@ionic/core';
import { Config } from '../../providers/config';
@ -8,11 +8,12 @@ import { IonRouterOutlet } from './ion-router-outlet';
@Directive({
selector: 'ion-back-button',
inputs: ['defaultHref', 'routerAnimation'],
})
export class IonBackButtonDelegate {
export class IonBackButtonDelegateDirective {
@Input()
defaultHref: string | undefined | null;
@Input()
routerAnimation?: AnimationBuilder;
constructor(
@ -25,10 +26,10 @@ export class IonBackButtonDelegate {
* @internal
*/
@HostListener('click', ['$event'])
onClick(ev: Event) {
onClick(ev: Event): void {
const defaultHref = this.defaultHref || this.config.get('backButtonDefaultHref');
if (this.routerOutlet && this.routerOutlet.canGoBack()) {
if (this.routerOutlet?.canGoBack()) {
this.navCtrl.setDirection('back', undefined, undefined, this.routerAnimation);
this.routerOutlet.pop();
ev.preventDefault();

View File

@ -1,11 +1,26 @@
import { Location } from '@angular/common';
import { Attribute, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, EventEmitter, Injector, NgZone, OnDestroy, OnInit, Optional, Output, SkipSelf, ViewContainerRef } from '@angular/core';
import { ActivatedRoute, ChildrenOutletContexts, OutletContext, PRIMARY_OUTLET, Router } from '@angular/router';
import {
ComponentFactoryResolver,
ComponentRef,
ElementRef,
Injector,
NgZone,
OnDestroy,
OnInit,
ViewContainerRef,
Attribute,
Directive,
EventEmitter,
Optional,
Output,
SkipSelf,
} from '@angular/core';
import { OutletContext, Router, ActivatedRoute, ChildrenOutletContexts, PRIMARY_OUTLET } from '@angular/router';
import { componentOnReady } from '@ionic/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { Observable, BehaviorSubject } from 'rxjs';
import { distinctUntilChanged, filter, switchMap } from 'rxjs/operators';
import { AnimationBuilder } from '../../';
import { AnimationBuilder } from '../../ionic-core';
import { Config } from '../../providers/config';
import { NavController } from '../../providers/nav-controller';
@ -15,8 +30,10 @@ import { RouteView, getUrl } from './stack-utils';
@Directive({
selector: 'ion-router-outlet',
exportAs: 'outlet',
inputs: ['animated', 'animation', 'swipeGesture']
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['animated', 'animation', 'swipeGesture'],
})
// eslint-disable-next-line @angular-eslint/directive-class-suffix
export class IonRouterOutlet implements OnDestroy, OnInit {
nativeEl: HTMLIonRouterOutletElement;
@ -37,7 +54,9 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
tabsPrefix: string | undefined;
@Output() stackEvents = new EventEmitter<any>();
// eslint-disable-next-line @angular-eslint/no-output-rename
@Output('activate') activateEvents = new EventEmitter<any>();
// eslint-disable-next-line @angular-eslint/no-output-rename
@Output('deactivate') deactivateEvents = new EventEmitter<any>();
set animation(animation: AnimationBuilder) {
@ -51,11 +70,13 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
set swipeGesture(swipe: boolean) {
this._swipeGesture = swipe;
this.nativeEl.swipeHandler = swipe ? {
canStart: () => this.stackCtrl.canGoBack(1) && !this.stackCtrl.hasRunningTask(),
onStart: () => this.stackCtrl.startBackTransition(),
onEnd: shouldContinue => this.stackCtrl.endBackTransition(shouldContinue)
} : undefined;
this.nativeEl.swipeHandler = swipe
? {
canStart: () => this.stackCtrl.canGoBack(1) && !this.stackCtrl.hasRunningTask(),
onStart: () => this.stackCtrl.startBackTransition(),
onEnd: (shouldContinue) => this.stackCtrl.endBackTransition(shouldContinue),
}
: undefined;
}
constructor(
@ -93,12 +114,12 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
// If the outlet was not instantiated at the time the route got activated we need to populate
// the outlet when it is initialized (ie inside a NgIf)
const context = this.getContext();
if (context && context.route) {
if (context?.route) {
this.activateWith(context.route, context.resolver || null);
}
}
new Promise(resolve => componentOnReady(this.nativeEl, resolve)).then(() => {
new Promise((resolve) => componentOnReady(this.nativeEl, resolve)).then(() => {
if (this._swipeGesture === undefined) {
this.swipeGesture = this.config.getBoolean('swipeBackEnabled', (this.nativeEl as any).mode === 'ios');
}
@ -109,7 +130,7 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
return !!this.activated;
}
get component(): object {
get component(): Record<string, unknown> {
if (!this.activated) {
throw new Error('Outlet is not activated');
}
@ -140,13 +161,15 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
/**
* Called when the `RouteReuseStrategy` instructs to re-attach a previously detached subtree
*/
attach(_ref: ComponentRef<any>, _activatedRoute: ActivatedRoute) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
attach(_ref: ComponentRef<any>, _activatedRoute: ActivatedRoute): void {
throw new Error('incompatible reuse strategy');
}
deactivate(): void {
if (this.activated) {
if (this.activatedView) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const context = this.getContext()!;
this.activatedView.savedData = new Map(context.children['contexts']);
@ -172,7 +195,7 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
const contextSnapshot = context.route.snapshot;
this.activatedView.savedExtras.queryParams = contextSnapshot.queryParams;
this.activatedView.savedExtras.fragment = contextSnapshot.fragment;
(this.activatedView.savedExtras.fragment as string | null) = contextSnapshot.fragment;
}
}
const c = this.component;
@ -183,7 +206,7 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
}
}
activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver | null) {
activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver | null): void {
if (this.isActivated) {
throw new Error('Cannot activate an already activated outlet');
}
@ -196,6 +219,7 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
const saved = enteringView.savedData;
if (saved) {
// self-restore
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const context = this.getContext()!;
context.children['contexts'] = saved;
}
@ -203,6 +227,7 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
this.updateActivatedRouteProxy(cmpRef.instance, activatedRoute);
} else {
const snapshot = (activatedRoute as any)._futureSnapshot;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const component = snapshot.routeConfig!.component as any;
resolver = resolver || this.resolver;
@ -230,7 +255,7 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
}
this.activatedView = enteringView;
this.stackCtrl.setActive(enteringView).then(data => {
this.stackCtrl.setActive(enteringView).then((data) => {
this.navCtrl.setTopOutlet(this);
this.activateEvents.emit(cmpRef.instance);
this.stackEvents.emit(data);
@ -313,11 +338,11 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
private proxyObservable(component$: Observable<any>, path: string): Observable<any> {
return component$.pipe(
// First wait until the component instance is pushed
filter(component => !!component),
switchMap(component =>
filter((component) => !!component),
switchMap((component) =>
this.currentActivatedRoute$.pipe(
filter(current => current !== null && current.component === component),
switchMap(current => current && (current.activatedRoute as any)[path]),
filter((current) => current !== null && current.component === component),
switchMap((current) => current && (current.activatedRoute as any)[path]),
distinctUntilChanged()
)
)
@ -344,11 +369,7 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
}
class OutletInjector implements Injector {
constructor(
private route: ActivatedRoute,
private childContexts: ChildrenOutletContexts,
private parent: Injector
) { }
constructor(private route: ActivatedRoute, private childContexts: ChildrenOutletContexts, private parent: Injector) {}
get(token: any, notFoundValue?: any): any {
if (token === ActivatedRoute) {
@ -359,7 +380,6 @@ class OutletInjector implements Injector {
return this.childContexts;
}
// tslint:disable-next-line
return this.parent.get(token, notFoundValue);
}
}

View File

@ -8,54 +8,53 @@ import { StackEvent } from './stack-utils';
@Component({
selector: 'ion-tabs',
template: `
<ng-content select="[slot=top]"></ng-content>
template: ` <ng-content select="[slot=top]"></ng-content>
<div class="tabs-inner">
<ion-router-outlet #outlet tabs="true" (stackEvents)="onPageSelected($event)"></ion-router-outlet>
</div>
<ng-content></ng-content>`,
styles: [`
:host {
display: flex;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
styles: [
`
:host {
display: flex;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
flex-direction: column;
flex-direction: column;
width: 100%;
height: 100%;
width: 100%;
height: 100%;
contain: layout size style;
z-index: $z-index-page-container;
}
.tabs-inner {
position: relative;
contain: layout size style;
z-index: $z-index-page-container;
}
.tabs-inner {
position: relative;
flex: 1;
flex: 1;
contain: layout size style;
}`
]
contain: layout size style;
}
`,
],
})
// eslint-disable-next-line @angular-eslint/component-class-suffix
export class IonTabs {
@ViewChild('outlet', { read: IonRouterOutlet, static: false }) outlet: IonRouterOutlet;
@ContentChild(IonTabBar, { static: false }) tabBar: IonTabBar | undefined;
@Output() ionTabsWillChange = new EventEmitter<{ tab: string }>();
@Output() ionTabsDidChange = new EventEmitter<{ tab: string }>();
constructor(
private navCtrl: NavController,
) { }
constructor(private navCtrl: NavController) {}
/**
* @internal
*/
onPageSelected(detail: StackEvent) {
onPageSelected(detail: StackEvent): void {
const stackId = detail.enteringView.stackId;
if (detail.tabSwitch && stackId !== undefined) {
if (this.tabBar) {
@ -87,9 +86,9 @@ export class IonTabs {
* to the default tabRootUrl
*/
@HostListener('ionTabButtonClick', ['$event'])
select(tabOrEvent: string | CustomEvent) {
select(tabOrEvent: string | CustomEvent): Promise<boolean> | undefined {
const isTabString = typeof tabOrEvent === 'string';
const tab = (isTabString) ? tabOrEvent : (tabOrEvent as CustomEvent).detail.tab;
const tab = isTabString ? tabOrEvent : (tabOrEvent as CustomEvent).detail.tab;
const alreadySelected = this.outlet.getActiveStackId() === tab;
const tabRootUrl = `${this.outlet.tabsPrefix}/${tab}`;
@ -108,12 +107,14 @@ export class IonTabs {
const activeView = this.outlet.getLastRouteView(activeStackId);
// If on root tab, do not navigate to root tab again
if (activeView.url === tabRootUrl) { return; }
if (activeView?.url === tabRootUrl) {
return;
}
const rootView = this.outlet.getRootView(tab);
const navigationExtras = rootView && tabRootUrl === rootView.url && rootView.savedExtras;
return this.navCtrl.navigateRoot(tabRootUrl, {
...(navigationExtras),
...navigationExtras,
animated: true,
animationDirection: 'back',
});
@ -123,11 +124,11 @@ export class IonTabs {
* If there is a lastRoute, goto that, otherwise goto the fallback url of the
* selected tab
*/
const url = lastRoute && lastRoute.url || tabRootUrl;
const navigationExtras = lastRoute && lastRoute.savedExtras;
const url = lastRoute?.url || tabRootUrl;
const navigationExtras = lastRoute?.savedExtras;
return this.navCtrl.navigateRoot(url, {
...(navigationExtras),
...navigationExtras,
animated: true,
animationDirection: 'back',
});

View File

@ -1,15 +1,30 @@
import { ComponentFactoryResolver, Directive, ElementRef, Injector, ViewContainerRef } from '@angular/core';
import { ComponentFactoryResolver, ElementRef, Injector, ViewContainerRef, Directive } from '@angular/core';
import { AngularDelegate } from '../../providers/angular-delegate';
import { ProxyCmp, proxyOutputs } from '../angular-component-lib/utils';
@ProxyCmp({
inputs: ['animated', 'animation', 'root', 'rootParams', 'swipeGesture'],
methods: ['push', 'insert', 'insertPages', 'pop', 'popTo', 'popToRoot', 'removeIndex', 'setRoot', 'setPages', 'getActive', 'getByIndex', 'canGoBack', 'getPrevious']
methods: [
'push',
'insert',
'insertPages',
'pop',
'popTo',
'popToRoot',
'removeIndex',
'setRoot',
'setPages',
'getActive',
'getByIndex',
'canGoBack',
'getPrevious',
],
})
@Directive({
selector: 'ion-nav'
selector: 'ion-nav',
})
// eslint-disable-next-line @angular-eslint/directive-class-suffix
export class NavDelegate {
protected el: HTMLElement;
constructor(
@ -21,6 +36,6 @@ export class NavDelegate {
) {
this.el = ref.nativeElement;
ref.nativeElement.delegate = angularDelegate.create(resolver, injector, location);
proxyOutputs(this, this.el, ['ionNavDidChange' , 'ionNavWillChange' ]);
proxyOutputs(this, this.el, ['ionNavDidChange', 'ionNavWillChange']);
}
}

View File

@ -19,8 +19,7 @@
* ```
*/
export class NavParams {
constructor(public data: {[key: string]: any} = {}) {}
constructor(public data: { [key: string]: any } = {}) {}
/**
* Get the value of a nav-parameter for the current view

View File

@ -1,5 +1,5 @@
import { LocationStrategy } from '@angular/common';
import { Directive, ElementRef, HostListener, Optional } from '@angular/core';
import { ElementRef, OnChanges, OnDestroy, OnInit, Directive, HostListener, Input, Optional } from '@angular/core';
import { Router, RouterLink } from '@angular/router';
import { AnimationBuilder, RouterDirection } from '@ionic/core';
import { Subscription } from 'rxjs';
@ -8,13 +8,14 @@ import { NavController } from '../../providers/nav-controller';
@Directive({
selector: '[routerLink]',
inputs: ['routerDirection', 'routerAnimation']
})
export class RouterLinkDelegate {
export class RouterLinkDelegateDirective implements OnInit, OnChanges, OnDestroy {
private subscription?: Subscription;
@Input()
routerDirection: RouterDirection = 'forward';
@Input()
routerAnimation?: AnimationBuilder;
constructor(
@ -22,18 +23,18 @@ export class RouterLinkDelegate {
private navCtrl: NavController,
private elementRef: ElementRef,
private router: Router,
@Optional() private routerLink?: RouterLink,
) { }
@Optional() private routerLink?: RouterLink
) {}
ngOnInit() {
ngOnInit(): void {
this.updateTargetUrlAndHref();
}
ngOnChanges(): any {
ngOnChanges(): void {
this.updateTargetUrlAndHref();
}
ngOnDestroy(): any {
ngOnDestroy(): void {
if (this.subscription) {
this.subscription.unsubscribe();
}
@ -50,7 +51,7 @@ export class RouterLinkDelegate {
* @internal
*/
@HostListener('click', ['$event'])
onClick(ev: UIEvent) {
onClick(ev: UIEvent): void {
this.navCtrl.setDirection(this.routerDirection, undefined, undefined, this.routerAnimation);
ev.preventDefault();
}

View File

@ -6,10 +6,18 @@ import { AnimationBuilder, RouterDirection } from '@ionic/core';
import { bindLifecycleEvents } from '../../providers/angular-delegate';
import { NavController } from '../../providers/nav-controller';
import { RouteView, StackEvent, computeStackId, destroyView, getUrl, insertView, isTabSwitch, toSegments } from './stack-utils';
import {
RouteView,
StackEvent,
computeStackId,
destroyView,
getUrl,
insertView,
isTabSwitch,
toSegments,
} from './stack-utils';
export class StackController {
private views: RouteView[] = [];
private runningTask?: Promise<any>;
private skipTransition = false;
@ -30,7 +38,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 element = ref?.location?.nativeElement as HTMLElement;
const unlistenEvents = bindLifecycleEvents(this.zone, ref.instance, element);
return {
id: this.nextId++,
@ -44,7 +52,7 @@ export class StackController {
getExistingView(activatedRoute: ActivatedRoute): RouteView | undefined {
const activatedUrlKey = getUrl(this.router, activatedRoute);
const view = this.views.find(vw => vw.url === activatedUrlKey);
const view = this.views.find((vw) => vw.url === activatedUrlKey);
if (view) {
view.ref.changeDetectorRef.reattach();
}
@ -65,17 +73,14 @@ export class StackController {
let currentNavigation;
const router = (this.router as any);
const router = this.router as any;
// Angular >= 7.2.0
if (router.getCurrentNavigation) {
currentNavigation = router.getCurrentNavigation();
// Angular < 7.2.0
} else if (
router.navigations &&
router.navigations.value
) {
} else if (router.navigations?.value) {
currentNavigation = router.navigations.value;
}
@ -86,11 +91,7 @@ export class StackController {
* we remove the last item
* from our views stack
*/
if (
currentNavigation &&
currentNavigation.extras &&
currentNavigation.extras.replaceUrl
) {
if (currentNavigation?.extras?.replaceUrl) {
if (this.views.length > 0) {
this.views.splice(-1, 1);
}
@ -114,12 +115,7 @@ export class StackController {
* provided another animation.
*/
const customAnimation = enteringView.animationBuilder;
if (
animationBuilder === undefined &&
direction === 'back' &&
!tabSwitch &&
customAnimation !== undefined
) {
if (animationBuilder === undefined && direction === 'back' && !tabSwitch && customAnimation !== undefined) {
animationBuilder = customAnimation;
}
@ -148,7 +144,7 @@ export class StackController {
enteringView,
direction,
animation,
tabSwitch
tabSwitch,
}));
});
});
@ -158,7 +154,7 @@ export class StackController {
return this.getStack(stackId).length > deep;
}
pop(deep: number, stackId = this.getActiveStackId()) {
pop(deep: number, stackId = this.getActiveStackId()): Promise<boolean> {
return this.zone.run(() => {
const views = this.getStack(stackId);
if (views.length <= deep) {
@ -170,13 +166,7 @@ export class StackController {
const viewSavedData = view.savedData;
if (viewSavedData) {
const primaryOutlet = viewSavedData.get('primary');
if (
primaryOutlet &&
primaryOutlet.route &&
primaryOutlet.route._routerState &&
primaryOutlet.route._routerState.snapshot &&
primaryOutlet.route._routerState.snapshot.url
) {
if (primaryOutlet?.route?._routerState?.snapshot.url) {
url = primaryOutlet.route._routerState.snapshot.url;
}
}
@ -185,7 +175,7 @@ export class StackController {
});
}
startBackTransition() {
startBackTransition(): Promise<boolean> | Promise<void> {
const leavingView = this.activeView;
if (leavingView) {
const views = this.getStack(leavingView.stackId);
@ -206,7 +196,7 @@ export class StackController {
return Promise.resolve();
}
endBackTransition(shouldComplete: boolean) {
endBackTransition(shouldComplete: boolean): void {
if (shouldComplete) {
this.skipTransition = true;
this.pop(1);
@ -215,7 +205,7 @@ export class StackController {
}
}
getLastUrl(stackId?: string) {
getLastUrl(stackId?: string): RouteView | undefined {
const views = this.getStack(stackId);
return views.length > 0 ? views[views.length - 1] : undefined;
}
@ -223,7 +213,7 @@ export class StackController {
/**
* @internal
*/
getRootUrl(stackId?: string) {
getRootUrl(stackId?: string): RouteView | undefined {
const views = this.getStack(stackId);
return views.length > 0 ? views[0] : undefined;
}
@ -236,7 +226,8 @@ export class StackController {
return this.runningTask !== undefined;
}
destroy() {
destroy(): void {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.containerEl = undefined!;
this.views.forEach(destroyView);
this.activeView = undefined;
@ -244,7 +235,7 @@ export class StackController {
}
private getStack(stackId: string | undefined) {
return this.views.filter(v => v.stackId === stackId);
return this.views.filter((v) => v.stackId === stackId);
}
private insertView(enteringView: RouteView, direction: RouterDirection) {
@ -285,7 +276,7 @@ export class StackController {
direction,
showGoBack,
progressAnimation,
animationBuilder
animationBuilder,
});
}
}
@ -297,15 +288,15 @@ export class StackController {
await this.runningTask;
this.runningTask = undefined;
}
const promise = this.runningTask = task();
promise.finally(() => this.runningTask = undefined);
const promise = (this.runningTask = task());
promise.finally(() => (this.runningTask = undefined));
return promise;
}
}
const cleanupAsync = (activeRoute: RouteView, views: RouteView[], viewsSnapshot: RouteView[], location: Location) => {
if (typeof (requestAnimationFrame as any) === 'function') {
return new Promise<any>(resolve => {
return new Promise<void>((resolve) => {
requestAnimationFrame(() => {
cleanup(activeRoute, views, viewsSnapshot, location);
resolve();
@ -316,11 +307,9 @@ const cleanupAsync = (activeRoute: RouteView, views: RouteView[], viewsSnapshot:
};
const cleanup = (activeRoute: RouteView, views: RouteView[], viewsSnapshot: RouteView[], location: Location) => {
viewsSnapshot
.filter(view => !views.includes(view))
.forEach(destroyView);
viewsSnapshot.filter((view) => !views.includes(view)).forEach(destroyView);
views.forEach(view => {
views.forEach((view) => {
/**
* In the event that a user navigated multiple
* times in rapid succession, we want to make sure

View File

@ -2,7 +2,7 @@ import { ComponentRef } from '@angular/core';
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
import { AnimationBuilder, NavDirection, RouterDirection } from '@ionic/core';
export const insertView = (views: RouteView[], view: RouteView, direction: RouterDirection) => {
export const insertView = (views: RouteView[], view: RouteView, direction: RouterDirection): RouteView[] => {
if (direction === 'root') {
return setRoot(views, view);
} else if (direction === 'forward') {
@ -13,7 +13,7 @@ export const insertView = (views: RouteView[], view: RouteView, direction: Route
};
const setRoot = (views: RouteView[], view: RouteView) => {
views = views.filter(v => v.stackId !== view.stackId);
views = views.filter((v) => v.stackId !== view.stackId);
views.push(view);
return views;
};
@ -21,7 +21,7 @@ const setRoot = (views: RouteView[], view: RouteView) => {
const setForward = (views: RouteView[], view: RouteView) => {
const index = views.indexOf(view);
if (index >= 0) {
views = views.filter(v => v.stackId !== view.stackId || v.id <= view.id);
views = views.filter((v) => v.stackId !== view.stackId || v.id <= view.id);
} else {
views.push(view);
}
@ -31,25 +31,25 @@ const setForward = (views: RouteView[], view: RouteView) => {
const setBack = (views: RouteView[], view: RouteView) => {
const index = views.indexOf(view);
if (index >= 0) {
return views.filter(v => v.stackId !== view.stackId || v.id <= view.id);
return views.filter((v) => v.stackId !== view.stackId || v.id <= view.id);
} else {
return setRoot(views, view);
}
};
export const getUrl = (router: Router, activatedRoute: ActivatedRoute) => {
export const getUrl = (router: Router, activatedRoute: ActivatedRoute): string => {
const urlTree = router.createUrlTree(['.'], { relativeTo: activatedRoute });
return router.serializeUrl(urlTree);
};
export const isTabSwitch = (enteringView: RouteView, leavingView: RouteView | undefined) => {
export const isTabSwitch = (enteringView: RouteView, leavingView: RouteView | undefined): boolean => {
if (!leavingView) {
return true;
}
return enteringView.stackId !== leavingView.stackId;
};
export const computeStackId = (prefixUrl: string[] | undefined, url: string) => {
export const computeStackId = (prefixUrl: string[] | undefined, url: string): string | undefined => {
if (!prefixUrl) {
return undefined;
}
@ -65,14 +65,14 @@ export const computeStackId = (prefixUrl: string[] | undefined, url: string) =>
return undefined;
};
export const toSegments = (path: string) => {
export const toSegments = (path: string): string[] => {
return path
.split('/')
.map(s => s.trim())
.filter(s => s !== '');
.map((s) => s.trim())
.filter((s) => s !== '');
};
export const destroyView = (view: RouteView | undefined) => {
export const destroyView = (view: RouteView | undefined): void => {
if (view) {
// TODO lifecycle event
view.ref.destroy();

View File

@ -1,12 +1,66 @@
/* eslint-disable */
/* tslint:disable */
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ElementRef, EventEmitter, NgZone, TemplateRef } from "@angular/core";
import { ProxyCmp, proxyOutputs } from "../angular-component-lib/utils";
import { Components } from "@ionic/core";
export declare interface IonModal extends Components.IonModal {
}
@ProxyCmp({ inputs: ["animated", "backdropBreakpoint", "backdropDismiss", "breakpoints", "cssClass", "enterAnimation", "event", "handle", "initialBreakpoint", "isOpen", "keyboardClose", "leaveAnimation", "mode", "presentingElement", "showBackdrop", "swipeToClose", "translucent", "trigger"], "methods": ["present", "dismiss", "onDidDismiss", "onWillDismiss"] })
@Component({ selector: "ion-modal", changeDetection: ChangeDetectionStrategy.OnPush, template: `<ng-container [ngTemplateOutlet]="template" *ngIf="isCmpOpen"></ng-container>`, inputs: ["animated", "backdropBreakpoint", "backdropDismiss", "breakpoints", "cssClass", "enterAnimation", "event", "handle", "initialBreakpoint", "isOpen", "keyboardClose", "leaveAnimation", "mode", "presentingElement", "showBackdrop", "swipeToClose", "translucent", "trigger"] })
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ContentChild,
ElementRef,
EventEmitter,
NgZone,
TemplateRef,
} from '@angular/core';
import { ProxyCmp, proxyOutputs } from '../angular-component-lib/utils';
import { Components } from '@ionic/core';
export declare interface IonModal extends Components.IonModal {}
@ProxyCmp({
inputs: [
'animated',
'backdropBreakpoint',
'backdropDismiss',
'breakpoints',
'cssClass',
'enterAnimation',
'event',
'handle',
'initialBreakpoint',
'isOpen',
'keyboardClose',
'leaveAnimation',
'mode',
'presentingElement',
'showBackdrop',
'swipeToClose',
'translucent',
'trigger',
],
methods: ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss'],
})
@Component({
selector: 'ion-modal',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<ng-container [ngTemplateOutlet]="template" *ngIf="isCmpOpen"></ng-container>`,
inputs: [
'animated',
'backdropBreakpoint',
'backdropDismiss',
'breakpoints',
'cssClass',
'enterAnimation',
'event',
'handle',
'initialBreakpoint',
'isOpen',
'keyboardClose',
'leaveAnimation',
'mode',
'presentingElement',
'showBackdrop',
'swipeToClose',
'translucent',
'trigger',
],
})
export class IonModal {
@ContentChild(TemplateRef, { static: false }) template: TemplateRef<any>;
@ -34,6 +88,15 @@ export class IonModal {
c.detectChanges();
});
proxyOutputs(this, this.el, ["ionModalDidPresent", "ionModalWillPresent", "ionModalWillDismiss", "ionModalDidDismiss", "didPresent", "willPresent", "willDismiss", "didDismiss"]);
proxyOutputs(this, this.el, [
'ionModalDidPresent',
'ionModalWillPresent',
'ionModalWillDismiss',
'ionModalDidDismiss',
'didPresent',
'willPresent',
'willDismiss',
'didDismiss',
]);
}
}

View File

@ -1,12 +1,66 @@
/* eslint-disable */
/* tslint:disable */
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ElementRef, EventEmitter, NgZone, TemplateRef } from "@angular/core";
import { ProxyCmp, proxyOutputs } from "../angular-component-lib/utils";
import { Components } from "@ionic/core";
export declare interface IonPopover extends Components.IonPopover {
}
@ProxyCmp({ inputs: ["alignment", "animated", "arrow", "backdropDismiss", "cssClass", "dismissOnSelect", "enterAnimation", "event", "isOpen", "keyboardClose", "leaveAnimation", "mode", "showBackdrop", "translucent", "trigger", "triggerAction", "reference", "size"], "methods": ["present", "dismiss", "onDidDismiss", "onWillDismiss"] })
@Component({ selector: "ion-popover", changeDetection: ChangeDetectionStrategy.OnPush, template: `<ng-container [ngTemplateOutlet]="template" *ngIf="isCmpOpen"></ng-container>`, inputs: ["alignment", "animated", "arrow", "backdropDismiss", "cssClass", "dismissOnSelect", "enterAnimation", "event", "isOpen", "keyboardClose", "leaveAnimation", "mode", "showBackdrop", "translucent", "trigger", "triggerAction", "reference", "size"] })
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ContentChild,
ElementRef,
EventEmitter,
NgZone,
TemplateRef,
} from '@angular/core';
import { ProxyCmp, proxyOutputs } from '../angular-component-lib/utils';
import { Components } from '@ionic/core';
export declare interface IonPopover extends Components.IonPopover {}
@ProxyCmp({
inputs: [
'alignment',
'animated',
'arrow',
'backdropDismiss',
'cssClass',
'dismissOnSelect',
'enterAnimation',
'event',
'isOpen',
'keyboardClose',
'leaveAnimation',
'mode',
'showBackdrop',
'translucent',
'trigger',
'triggerAction',
'reference',
'size',
],
methods: ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss'],
})
@Component({
selector: 'ion-popover',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<ng-container [ngTemplateOutlet]="template" *ngIf="isCmpOpen"></ng-container>`,
inputs: [
'alignment',
'animated',
'arrow',
'backdropDismiss',
'cssClass',
'dismissOnSelect',
'enterAnimation',
'event',
'isOpen',
'keyboardClose',
'leaveAnimation',
'mode',
'showBackdrop',
'translucent',
'trigger',
'triggerAction',
'reference',
'size',
],
})
export class IonPopover {
@ContentChild(TemplateRef, { static: false }) template: TemplateRef<any>;
@ -34,6 +88,15 @@ export class IonPopover {
c.detectChanges();
});
proxyOutputs(this, this.el, ["ionPopoverDidPresent", "ionPopoverWillPresent", "ionPopoverWillDismiss", "ionPopoverDidDismiss", "didPresent", "willPresent", "willDismiss", "didDismiss"]);
proxyOutputs(this, this.el, [
'ionPopoverDidPresent',
'ionPopoverWillPresent',
'ionPopoverWillDismiss',
'ionPopoverDidDismiss',
'didPresent',
'willPresent',
'willDismiss',
'didDismiss',
]);
}
}

View File

@ -205,8 +205,8 @@ export class IonVirtualScroll {
case 'item': return this.itmTmp.templateRef;
case 'header': return this.hdrTmp.templateRef;
case 'footer': return this.ftrTmp.templateRef;
default: throw new Error('template for virtual item was not provided');
}
throw new Error('template for virtual item was not provided');
}
}