mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
feat(angular): add standalone modal (#27885)
This commit is contained in:
@@ -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';
|
||||
|
||||
|
||||
133
packages/angular/common/src/overlays/modal.ts
Normal file
133
packages/angular/common/src/overlays/modal.ts
Normal 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',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export { IonBackButton } from './navigation/back-button';
|
||||
export { IonModal } from './overlays/modal';
|
||||
export { IonRouterOutlet } from './navigation/router-outlet';
|
||||
|
||||
18
packages/angular/standalone/src/overlays/modal.ts
Normal file
18
packages/angular/standalone/src/overlays/modal.ts
Normal 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 {}
|
||||
12
packages/angular/test/base/e2e/src/standalone/modal.spec.ts
Normal file
12
packages/angular/test/base/e2e/src/standalone/modal.spec.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
@@ -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) },
|
||||
]
|
||||
|
||||
@@ -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>
|
||||
@@ -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 {
|
||||
}
|
||||
Reference in New Issue
Block a user