feat(angular): add standalone modal (#27885)

This commit is contained in:
Liam DeBeasi
2023-08-04 15:45:50 -04:00
committed by GitHub
parent 9f20780d66
commit 37acdf9711
9 changed files with 189 additions and 131 deletions

View File

@@ -20,6 +20,9 @@ export { bindLifecycleEvents, AngularDelegate } from './providers/angular-delega
export type { IonicWindow } from './types/interfaces';
export { NavParams } from './directives/navigation/nav-params';
export { IonModal } from './overlays/modal';
export { IonRouterOutlet, INPUT_BINDER, RoutedComponentInputBinder } from './directives/navigation/router-outlet';
export type { StackEvent } from './directives/navigation/stack-utils';

View File

@@ -0,0 +1,133 @@
import {
ChangeDetectorRef,
ContentChild,
Directive,
ElementRef,
EventEmitter,
NgZone,
TemplateRef,
} from '@angular/core';
import { Components, ModalBreakpointChangeEventDetail } from '@ionic/core';
import { ProxyCmp, proxyOutputs } from '../utils/proxy';
export declare interface IonModal extends Components.IonModal {
/**
* Emitted after the modal has presented.
**/
ionModalDidPresent: EventEmitter<CustomEvent>;
/**
* Emitted before the modal has presented.
*/
ionModalWillPresent: EventEmitter<CustomEvent>;
/**
* Emitted before the modal has dismissed.
*/
ionModalWillDismiss: EventEmitter<CustomEvent>;
/**
* Emitted after the modal has dismissed.
*/
ionModalDidDismiss: EventEmitter<CustomEvent>;
/**
* Emitted after the modal breakpoint has changed.
*/
ionBreakpointDidChange: EventEmitter<CustomEvent<ModalBreakpointChangeEventDetail>>;
/**
* Emitted after the modal has presented. Shorthand for ionModalDidPresent.
*/
didPresent: EventEmitter<CustomEvent>;
/**
* Emitted before the modal has presented. Shorthand for ionModalWillPresent.
*/
willPresent: EventEmitter<CustomEvent>;
/**
* Emitted before the modal has dismissed. Shorthand for ionModalWillDismiss.
*/
willDismiss: EventEmitter<CustomEvent>;
/**
* Emitted after the modal has dismissed. Shorthand for ionModalDidDismiss.
*/
didDismiss: EventEmitter<CustomEvent>;
}
const MODAL_INPUTS = [
'animated',
'keepContentsMounted',
'backdropBreakpoint',
'backdropDismiss',
'breakpoints',
'canDismiss',
'cssClass',
'enterAnimation',
'event',
'handle',
'handleBehavior',
'initialBreakpoint',
'isOpen',
'keyboardClose',
'leaveAnimation',
'mode',
'presentingElement',
'showBackdrop',
'translucent',
'trigger',
];
const MODAL_METHODS = [
'present',
'dismiss',
'onDidDismiss',
'onWillDismiss',
'setCurrentBreakpoint',
'getCurrentBreakpoint',
];
@ProxyCmp({
inputs: MODAL_INPUTS,
methods: MODAL_METHODS,
})
/**
* @Component extends from @Directive
* so by defining the inputs here we
* do not need to re-define them for the
* lazy loaded popover.
*/
@Directive({
selector: 'ion-modal',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: MODAL_INPUTS,
})
// eslint-disable-next-line @angular-eslint/directive-class-suffix
export class IonModal {
// TODO(FW-2827): type
@ContentChild(TemplateRef, { static: false }) template: TemplateRef<any>;
isCmpOpen = false;
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
this.el = r.nativeElement;
this.el.addEventListener('ionMount', () => {
this.isCmpOpen = true;
c.detectChanges();
});
this.el.addEventListener('didDismiss', () => {
this.isCmpOpen = false;
c.detectChanges();
});
proxyOutputs(this, this.el, [
'ionModalDidPresent',
'ionModalWillPresent',
'ionModalWillDismiss',
'ionModalDidDismiss',
'ionBreakpointDidChange',
'didPresent',
'willPresent',
'willDismiss',
'didDismiss',
]);
}
}

View File

@@ -1,139 +1,11 @@
/* 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, ModalBreakpointChangeEventDetail } from '@ionic/core';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IonModal as IonModalBase } from '@ionic/angular/common';
export declare interface IonModal extends Components.IonModal {
/**
* Emitted after the modal has presented.
**/
ionModalDidPresent: EventEmitter<CustomEvent>;
/**
* Emitted before the modal has presented.
*/
ionModalWillPresent: EventEmitter<CustomEvent>;
/**
* Emitted before the modal has dismissed.
*/
ionModalWillDismiss: EventEmitter<CustomEvent>;
/**
* Emitted after the modal has dismissed.
*/
ionModalDidDismiss: EventEmitter<CustomEvent>;
/**
* Emitted after the modal breakpoint has changed.
*/
ionBreakpointDidChange: EventEmitter<CustomEvent<ModalBreakpointChangeEventDetail>>;
/**
* Emitted after the modal has presented. Shorthand for ionModalWillDismiss.
*/
didPresent: EventEmitter<CustomEvent>;
/**
* Emitted before the modal has presented. Shorthand for ionModalWillPresent.
*/
willPresent: EventEmitter<CustomEvent>;
/**
* Emitted before the modal has dismissed. Shorthand for ionModalWillDismiss.
*/
willDismiss: EventEmitter<CustomEvent>;
/**
* Emitted after the modal has dismissed. Shorthand for ionModalDidDismiss.
*/
didDismiss: EventEmitter<CustomEvent>;
}
@ProxyCmp({
inputs: [
'animated',
'keepContentsMounted',
'backdropBreakpoint',
'backdropDismiss',
'breakpoints',
'canDismiss',
'cssClass',
'enterAnimation',
'event',
'handle',
'handleBehavior',
'initialBreakpoint',
'isOpen',
'keyboardClose',
'leaveAnimation',
'mode',
'presentingElement',
'showBackdrop',
'translucent',
'trigger',
],
methods: ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss', 'setCurrentBreakpoint', 'getCurrentBreakpoint'],
})
@Component({
selector: 'ion-modal',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<div class="ion-delegate-host ion-page" *ngIf="isCmpOpen || keepContentsMounted">
<ng-container [ngTemplateOutlet]="template"></ng-container>
</div>`,
inputs: [
'animated',
'keepContentsMounted',
'backdropBreakpoint',
'backdropDismiss',
'breakpoints',
'canDismiss',
'cssClass',
'enterAnimation',
'event',
'handle',
'handleBehavior',
'initialBreakpoint',
'isOpen',
'keyboardClose',
'leaveAnimation',
'mode',
'presentingElement',
'showBackdrop',
'translucent',
'trigger',
],
})
export class IonModal {
// TODO(FW-2827): type
@ContentChild(TemplateRef, { static: false }) template: TemplateRef<any>;
isCmpOpen: boolean = false;
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
this.el = r.nativeElement;
this.el.addEventListener('ionMount', () => {
this.isCmpOpen = true;
c.detectChanges();
});
this.el.addEventListener('didDismiss', () => {
this.isCmpOpen = false;
c.detectChanges();
});
proxyOutputs(this, this.el, [
'ionModalDidPresent',
'ionModalWillPresent',
'ionModalWillDismiss',
'ionModalDidDismiss',
'ionBreakpointDidChange',
'didPresent',
'willPresent',
'willDismiss',
'didDismiss',
]);
}
}
export class IonModal extends IonModalBase {}

View File

@@ -1,2 +1,3 @@
export { IonBackButton } from './navigation/back-button';
export { IonModal } from './overlays/modal';
export { IonRouterOutlet } from './navigation/router-outlet';

View File

@@ -0,0 +1,18 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IonModal as IonModalBase, ProxyCmp } from '@ionic/angular/common';
import { defineCustomElement } from '@ionic/core/components/ion-modal.js';
@ProxyCmp({
defineCustomElementFn: defineCustomElement,
})
@Component({
selector: 'ion-modal',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<div class="ion-delegate-host ion-page" *ngIf="isCmpOpen || keepContentsMounted">
<ng-container [ngTemplateOutlet]="template"></ng-container>
</div>`,
standalone: true,
imports: [CommonModule],
})
export class IonModal extends IonModalBase {}

View File

@@ -0,0 +1,12 @@
describe('Modals: Inline', () => {
beforeEach(() => {
cy.visit('/standalone/modal');
});
it('should render modal', () => {
cy.get('button#open-modal').click();
cy.get('ion-modal').should('be.visible');
cy.get('ion-modal #modal-content').should('exist');
});
});

View File

@@ -7,6 +7,7 @@ export const routes: Routes = [
component: AppComponent,
children: [
{ path: 'test', loadComponent: () => import('../test/test.component').then(m => m.TestComponent) },
{ path: 'modal', loadComponent: () => import('../modal/modal.component').then(m => m.ModalComponent) },
{ path: 'router-outlet', loadComponent: () => import('../router-outlet/router-outlet.component').then(m => m.RouterOutletComponent) },
{ path: 'back-button', loadComponent: () => import('../back-button/back-button.component').then(m => m.BackButtonComponent) },
]

View File

@@ -0,0 +1,7 @@
<button id="open-modal">Open Modal</button>
<ion-modal trigger="open-modal">
<ng-template>
<div class="ion-padding" id="modal-content">This is my modal content</div>
</ng-template>
</ion-modal>

View File

@@ -0,0 +1,11 @@
import { Component } from '@angular/core';
import { IonModal } from '@ionic/angular/standalone';
@Component({
selector: 'app-test',
templateUrl: './modal.component.html',
standalone: true,
imports: [IonModal]
})
export class ModalComponent {
}