Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(angular, react, vue): add support for autoMountComponent #25552

Merged
merged 15 commits into from
Jul 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion angular/src/directives/overlays/modal.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './keep-contents-mounted.component';
export * from './keep-contents-mounted.module';
Original file line number Diff line number Diff line change
@@ -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 { }
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
@@ -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 { }
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 @@ -1936,6 +1940,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 @@ -5484,6 +5492,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 @@ -5775,6 +5787,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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading