Skip to content

Commit

Permalink
feat(angular, react, vue): add support for autoMountComponent (#25552)
Browse files Browse the repository at this point in the history
  • Loading branch information
liamdebeasi committed Jul 5, 2022
1 parent 0eaacda commit 805dfa0
Show file tree
Hide file tree
Showing 28 changed files with 448 additions and 13 deletions.
6 changes: 5 additions & 1 deletion angular/src/directives/overlays/modal.ts
Expand Up @@ -54,6 +54,7 @@ export declare interface IonModal extends Components.IonModal {
@ProxyCmp({
inputs: [
'animated',
'keepContentsMounted',
'backdropBreakpoint',
'backdropDismiss',
'breakpoints',
Expand All @@ -78,9 +79,12 @@ export declare interface IonModal extends Components.IonModal {
@Component({
selector: 'ion-modal',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<div class="ion-page" *ngIf="isCmpOpen"><ng-container [ngTemplateOutlet]="template"></ng-container></div>`,
template: `<div class="ion-page" *ngIf="isCmpOpen || keepContentsMounted">
<ng-container [ngTemplateOutlet]="template"></ng-container>
</div>`,
inputs: [
'animated',
'keepContentsMounted',
'backdropBreakpoint',
'backdropDismiss',
'breakpoints',
Expand Down
4 changes: 3 additions & 1 deletion angular/src/directives/overlays/popover.ts
Expand Up @@ -51,6 +51,7 @@ export declare interface IonPopover extends Components.IonPopover {
'alignment',
'animated',
'arrow',
'keepContentsMounted',
'backdropDismiss',
'cssClass',
'dismissOnSelect',
Expand All @@ -73,11 +74,12 @@ export declare interface IonPopover extends Components.IonPopover {
@Component({
selector: 'ion-popover',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<ng-container [ngTemplateOutlet]="template" *ngIf="isCmpOpen"></ng-container>`,
template: `<ng-container [ngTemplateOutlet]="template" *ngIf="isCmpOpen || keepContentsMounted"></ng-container>`,
inputs: [
'alignment',
'animated',
'arrow',
'keepContentsMounted',
'backdropDismiss',
'cssClass',
'dismissOnSelect',
Expand Down
60 changes: 60 additions & 0 deletions angular/test/test-app/e2e/src/keep-contents-mounted.spec.ts
@@ -0,0 +1,60 @@
describe('overlays - keepContentsMounted', () => {
describe('modal', () => {
it('should not mount component if false', () => {
cy.visit('/modal-inline');

cy.get('ion-modal ion-content').should('not.exist');
});

it('should mount component if true', () => {
cy.visit('/keep-contents-mounted');

cy.get('ion-modal ion-content').should('exist');
});

it('should keep component mounted after dismissing if true', () => {
cy.visit('/keep-contents-mounted');

cy.get('#open-modal').click();

cy.get('ion-modal ion-content').should('exist');

cy.get('ion-modal ion-button').click();

cy.get('ion-modal')
.should('not.be.visible')
.should('have.class', 'overlay-hidden');

cy.get('ion-modal ion-content').should('exist');
});
})
describe('popover', () => {
it('should not mount component if false', () => {
cy.visit('/popover-inline');

cy.get('ion-popover ion-content').should('not.exist');
});

it('should mount component if true', () => {
cy.visit('/keep-contents-mounted');

cy.get('ion-popover ion-content').should('exist');
});

it('should keep component mounted after dismissing if true', () => {
cy.visit('/keep-contents-mounted');

cy.get('#open-popover').click();

cy.get('ion-popover ion-content').should('exist');

cy.get('ion-popover ion-button').click();

cy.get('ion-popover')
.should('not.be.visible')
.should('have.class', 'overlay-hidden');

cy.get('ion-popover ion-content').should('exist');
});
});
});
7 changes: 7 additions & 0 deletions angular/test/test-app/e2e/src/popover.spec.ts
Expand Up @@ -4,10 +4,17 @@ describe('Popovers: Inline', () => {
});

it('should initially have no items', () => {
cy.get('ion-button').click();

cy.get('ion-popover').should('be.visible');
cy.get('ion-list ion-item').should('not.exist');
});

it('should have items after 1500ms', () => {
cy.get('ion-button').click();

cy.get('ion-popover').should('be.visible');

cy.wait(1500);

cy.get('ion-list ion-item:nth-child(1)').should('have.text', 'A');
Expand Down
1 change: 1 addition & 0 deletions angular/test/test-app/src/app/app-routing.module.ts
Expand Up @@ -31,6 +31,7 @@ const routes: Routes = [
{ path: 'modals', component: ModalComponent },
{ path: 'modal-inline', loadChildren: () => import('./modal-inline').then(m => m.ModalInlineModule) },
{ path: 'view-child', component: ViewChildComponent },
{ path: 'keep-contents-mounted', loadChildren: () => import('./keep-contents-mounted').then(m => m.OverlayAutoMountModule) },
{ path: 'popover-inline', loadChildren: () => import('./popover-inline').then(m => m.PopoverInlineModule) },
{ path: 'providers', component: ProvidersComponent },
{ path: 'router-link', component: RouterLinkComponent },
Expand Down
2 changes: 2 additions & 0 deletions angular/test/test-app/src/app/keep-contents-mounted/index.ts
@@ -0,0 +1,2 @@
export * from './keep-contents-mounted.component';
export * from './keep-contents-mounted.module';
@@ -0,0 +1,16 @@
import { NgModule } from "@angular/core";
import { RouterModule } from "@angular/router";
import { OverlayKeepContentsMounted } from ".";

@NgModule({
imports: [
RouterModule.forChild([
{
path: '',
component: OverlayKeepContentsMounted
}
])
],
exports: [RouterModule]
})
export class OverlayKeepContentsMountedRoutingModule { }
@@ -0,0 +1,22 @@
<ion-content>
<ion-button id="open-modal" (click)="modal.present()">Open Modal</ion-button>
<ion-button id="open-popover" (click)="popover.present()">Open Popover</ion-button>

<ion-modal [keepContentsMounted]="true" #modal>
<ng-template>
<ion-content>
<ion-button (click)="modal.dismiss()">Dismiss</ion-button>
Modal Content
</ion-content>
</ng-template>
</ion-modal>

<ion-popover [keepContentsMounted]="true" #popover>
<ng-template>
<ion-content>
<ion-button (click)="popover.dismiss()">Dismiss</ion-button>
Popover Content
</ion-content>
</ng-template>
</ion-popover>
</ion-content>
@@ -0,0 +1,13 @@
import { Component } from "@angular/core";

/**
* Validates that inline modals correctly mount
* inner components when keepContentsMounted is
* enabled.
*/
@Component({
selector: 'app-keep-contents-mounted',
templateUrl: 'keep-contents-mounted.component.html'
})
export class OverlayKeepContentsMounted {
}
@@ -0,0 +1,12 @@
import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core";
import { IonicModule } from "@ionic/angular";
import { OverlayKeepContentsMountedRoutingModule } from "./keep-contents-mounted-routing.module";
import { OverlayKeepContentsMounted } from "./keep-contents-mounted.component";

@NgModule({
imports: [CommonModule, IonicModule, OverlayKeepContentsMountedRoutingModule],
declarations: [OverlayKeepContentsMounted],
exports: [OverlayKeepContentsMounted]
})
export class OverlayAutoMountModule { }
@@ -1,4 +1,6 @@
<ion-popover [isOpen]="true">
<ion-button (click)="openPopover(popover)">Open Popover</ion-button>

<ion-popover #popover>
<ng-template>
<ion-content>
<ion-list>
Expand Down
@@ -1,4 +1,4 @@
import { AfterViewInit, Component } from "@angular/core";
import { Component } from "@angular/core";

/**
* Validates that inline popovers will correctly display
Expand All @@ -9,11 +9,13 @@ import { AfterViewInit, Component } from "@angular/core";
selector: 'app-popover-inline',
templateUrl: 'popover-inline.component.html'
})
export class PopoverInlineComponent implements AfterViewInit {
export class PopoverInlineComponent {

items: string[] = [];

ngAfterViewInit(): void {
openPopover(popover: HTMLIonPopoverElement) {
popover.present();

setTimeout(() => {
this.items = ['A', 'B', 'C', 'D'];
}, 1000);
Expand Down
2 changes: 2 additions & 0 deletions core/api.txt
Expand Up @@ -776,6 +776,7 @@ ion-modal,prop,handle,boolean | undefined,undefined,false,false
ion-modal,prop,htmlAttributes,undefined | { [key: string]: any; },undefined,false,false
ion-modal,prop,initialBreakpoint,number | undefined,undefined,false,false
ion-modal,prop,isOpen,boolean,false,false,false
ion-modal,prop,keepContentsMounted,boolean,false,false,false
ion-modal,prop,keyboardClose,boolean,true,false,false
ion-modal,prop,leaveAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
ion-modal,prop,mode,"ios" | "md",undefined,false,false
Expand Down Expand Up @@ -895,6 +896,7 @@ ion-popover,prop,enterAnimation,((baseEl: any, opts?: any) => Animation) | undef
ion-popover,prop,event,any,undefined,false,false
ion-popover,prop,htmlAttributes,undefined | { [key: string]: any; },undefined,false,false
ion-popover,prop,isOpen,boolean,false,false,false
ion-popover,prop,keepContentsMounted,boolean,false,false,false
ion-popover,prop,keyboardClose,boolean,true,false,false
ion-popover,prop,leaveAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
ion-popover,prop,mode,"ios" | "md",undefined,false,false
Expand Down
16 changes: 16 additions & 0 deletions core/src/components.d.ts
Expand Up @@ -1571,6 +1571,10 @@ export namespace Components {
* If `true`, the modal will open. If `false`, the modal will close. Use this if you need finer grained control over presentation, otherwise just use the modalController or the `trigger` property. Note: `isOpen` will not automatically be set back to `false` when the modal dismisses. You will need to do that in your code.
*/
"isOpen": boolean;
/**
* If `true`, the component passed into `ion-modal` will automatically be mounted when the modal is created. The component will remain mounted even when the modal is dismissed. However, the component will be destroyed when the modal is destroyed. This property is not reactive and should only be used when initially creating a modal. Note: This feature only applies to inline modals in JavaScript frameworks such as Angular, React, and Vue.
*/
"keepContentsMounted": boolean;
/**
* If `true`, the keyboard will be automatically dismissed when the overlay is presented.
*/
Expand Down Expand Up @@ -1940,6 +1944,10 @@ export namespace Components {
* If `true`, the popover will open. If `false`, the popover will close. Use this if you need finer grained control over presentation, otherwise just use the popoverController or the `trigger` property. Note: `isOpen` will not automatically be set back to `false` when the popover dismisses. You will need to do that in your code.
*/
"isOpen": boolean;
/**
* If `true`, the component passed into `ion-popover` will automatically be mounted when the popover is created. The component will remain mounted even when the popover is dismissed. However, the component will be destroyed when the popover is destroyed. This property is not reactive and should only be used when initially creating a popover. Note: This feature only applies to inline popovers in JavaScript frameworks such as Angular, React, and Vue.
*/
"keepContentsMounted": boolean;
/**
* If `true`, the keyboard will be automatically dismissed when the overlay is presented.
*/
Expand Down Expand Up @@ -5488,6 +5496,10 @@ declare namespace LocalJSX {
* If `true`, the modal will open. If `false`, the modal will close. Use this if you need finer grained control over presentation, otherwise just use the modalController or the `trigger` property. Note: `isOpen` will not automatically be set back to `false` when the modal dismisses. You will need to do that in your code.
*/
"isOpen"?: boolean;
/**
* If `true`, the component passed into `ion-modal` will automatically be mounted when the modal is created. The component will remain mounted even when the modal is dismissed. However, the component will be destroyed when the modal is destroyed. This property is not reactive and should only be used when initially creating a modal. Note: This feature only applies to inline modals in JavaScript frameworks such as Angular, React, and Vue.
*/
"keepContentsMounted"?: boolean;
/**
* If `true`, the keyboard will be automatically dismissed when the overlay is presented.
*/
Expand Down Expand Up @@ -5779,6 +5791,10 @@ declare namespace LocalJSX {
* If `true`, the popover will open. If `false`, the popover will close. Use this if you need finer grained control over presentation, otherwise just use the popoverController or the `trigger` property. Note: `isOpen` will not automatically be set back to `false` when the popover dismisses. You will need to do that in your code.
*/
"isOpen"?: boolean;
/**
* If `true`, the component passed into `ion-popover` will automatically be mounted when the popover is created. The component will remain mounted even when the popover is dismissed. However, the component will be destroyed when the popover is destroyed. This property is not reactive and should only be used when initially creating a popover. Note: This feature only applies to inline popovers in JavaScript frameworks such as Angular, React, and Vue.
*/
"keepContentsMounted"?: boolean;
/**
* If `true`, the keyboard will be automatically dismissed when the overlay is presented.
*/
Expand Down
13 changes: 13 additions & 0 deletions core/src/components/modal/modal.tsx
Expand Up @@ -222,6 +222,19 @@ export class Modal implements ComponentInterface, OverlayInterface {
this.configureTriggerInteraction();
}

/**
* If `true`, the component passed into `ion-modal` will
* automatically be mounted when the modal is created. The
* component will remain mounted even when the modal is dismissed.
* However, the component will be destroyed when the modal is
* destroyed. This property is not reactive and should only be
* used when initially creating a modal.
*
* Note: This feature only applies to inline modals in JavaScript
* frameworks such as Angular, React, and Vue.
*/
@Prop() keepContentsMounted = false;

/**
* TODO (FW-937)
* This needs to default to true in the next
Expand Down
13 changes: 13 additions & 0 deletions core/src/components/popover/popover.tsx
Expand Up @@ -253,6 +253,19 @@ export class Popover implements ComponentInterface, PopoverInterface {
}
}

/**
* If `true`, the component passed into `ion-popover` will
* automatically be mounted when the popover is created. The
* component will remain mounted even when the popover is dismissed.
* However, the component will be destroyed when the popover is
* destroyed. This property is not reactive and should only be
* used when initially creating a popover.
*
* Note: This feature only applies to inline popovers in JavaScript
* frameworks such as Angular, React, and Vue.
*/
@Prop() keepContentsMounted = false;

/**
* Emitted after the popover has presented.
*/
Expand Down
Expand Up @@ -21,6 +21,7 @@ interface IonicReactInternalProps<ElementType> extends React.HTMLAttributes<Elem
onDidPresent?: (event: CustomEvent<OverlayEventDetail>) => void;
onWillDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
onWillPresent?: (event: CustomEvent<OverlayEventDetail>) => void;
keepContentsMounted?: boolean;
}

export const createInlineOverlayComponent = <PropType, ElementType>(
Expand Down Expand Up @@ -128,7 +129,7 @@ export const createInlineOverlayComponent = <PropType, ElementType>(
* so conditionally render the component
* based on the isOpen state.
*/
return createElement(tagName, newProps, (this.state.isOpen) ?
return createElement(tagName, newProps, (this.state.isOpen || this.props.keepContentsMounted) ?
createElement('div', {
id: 'ion-react-wrapper',
ref: this.wrapperRef,
Expand Down

0 comments on commit 805dfa0

Please sign in to comment.