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

This commit is contained in:
Liam DeBeasi
2022-07-05 12:57:16 -04:00
committed by GitHub
parent 0eaacdaf75
commit 805dfa0566
28 changed files with 448 additions and 13 deletions

View File

@ -54,6 +54,7 @@ export declare interface IonModal extends Components.IonModal {
@ProxyCmp({ @ProxyCmp({
inputs: [ inputs: [
'animated', 'animated',
'keepContentsMounted',
'backdropBreakpoint', 'backdropBreakpoint',
'backdropDismiss', 'backdropDismiss',
'breakpoints', 'breakpoints',
@ -78,9 +79,12 @@ export declare interface IonModal extends Components.IonModal {
@Component({ @Component({
selector: 'ion-modal', selector: 'ion-modal',
changeDetection: ChangeDetectionStrategy.OnPush, 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: [ inputs: [
'animated', 'animated',
'keepContentsMounted',
'backdropBreakpoint', 'backdropBreakpoint',
'backdropDismiss', 'backdropDismiss',
'breakpoints', 'breakpoints',

View File

@ -51,6 +51,7 @@ export declare interface IonPopover extends Components.IonPopover {
'alignment', 'alignment',
'animated', 'animated',
'arrow', 'arrow',
'keepContentsMounted',
'backdropDismiss', 'backdropDismiss',
'cssClass', 'cssClass',
'dismissOnSelect', 'dismissOnSelect',
@ -73,11 +74,12 @@ export declare interface IonPopover extends Components.IonPopover {
@Component({ @Component({
selector: 'ion-popover', selector: 'ion-popover',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
template: `<ng-container [ngTemplateOutlet]="template" *ngIf="isCmpOpen"></ng-container>`, template: `<ng-container [ngTemplateOutlet]="template" *ngIf="isCmpOpen || keepContentsMounted"></ng-container>`,
inputs: [ inputs: [
'alignment', 'alignment',
'animated', 'animated',
'arrow', 'arrow',
'keepContentsMounted',
'backdropDismiss', 'backdropDismiss',
'cssClass', 'cssClass',
'dismissOnSelect', 'dismissOnSelect',

View File

@ -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');
});
});
});

View File

@ -4,10 +4,17 @@ describe('Popovers: Inline', () => {
}); });
it('should initially have no items', () => { 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'); cy.get('ion-list ion-item').should('not.exist');
}); });
it('should have items after 1500ms', () => { it('should have items after 1500ms', () => {
cy.get('ion-button').click();
cy.get('ion-popover').should('be.visible');
cy.wait(1500); cy.wait(1500);
cy.get('ion-list ion-item:nth-child(1)').should('have.text', 'A'); cy.get('ion-list ion-item:nth-child(1)').should('have.text', 'A');

View File

@ -31,6 +31,7 @@ const routes: Routes = [
{ path: 'modals', component: ModalComponent }, { path: 'modals', component: ModalComponent },
{ path: 'modal-inline', loadChildren: () => import('./modal-inline').then(m => m.ModalInlineModule) }, { path: 'modal-inline', loadChildren: () => import('./modal-inline').then(m => m.ModalInlineModule) },
{ path: 'view-child', component: ViewChildComponent }, { 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: 'popover-inline', loadChildren: () => import('./popover-inline').then(m => m.PopoverInlineModule) },
{ path: 'providers', component: ProvidersComponent }, { path: 'providers', component: ProvidersComponent },
{ path: 'router-link', component: RouterLinkComponent }, { path: 'router-link', component: RouterLinkComponent },

View File

@ -0,0 +1,2 @@
export * from './keep-contents-mounted.component';
export * from './keep-contents-mounted.module';

View File

@ -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 { }

View File

@ -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>

View File

@ -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 {
}

View File

@ -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 { }

View File

@ -1,4 +1,6 @@
<ion-popover [isOpen]="true"> <ion-button (click)="openPopover(popover)">Open Popover</ion-button>
<ion-popover #popover>
<ng-template> <ng-template>
<ion-content> <ion-content>
<ion-list> <ion-list>

View File

@ -1,4 +1,4 @@
import { AfterViewInit, Component } from "@angular/core"; import { Component } from "@angular/core";
/** /**
* Validates that inline popovers will correctly display * Validates that inline popovers will correctly display
@ -9,11 +9,13 @@ import { AfterViewInit, Component } from "@angular/core";
selector: 'app-popover-inline', selector: 'app-popover-inline',
templateUrl: 'popover-inline.component.html' templateUrl: 'popover-inline.component.html'
}) })
export class PopoverInlineComponent implements AfterViewInit { export class PopoverInlineComponent {
items: string[] = []; items: string[] = [];
ngAfterViewInit(): void { openPopover(popover: HTMLIonPopoverElement) {
popover.present();
setTimeout(() => { setTimeout(() => {
this.items = ['A', 'B', 'C', 'D']; this.items = ['A', 'B', 'C', 'D'];
}, 1000); }, 1000);

View File

@ -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,htmlAttributes,undefined | { [key: string]: any; },undefined,false,false
ion-modal,prop,initialBreakpoint,number | undefined,undefined,false,false ion-modal,prop,initialBreakpoint,number | undefined,undefined,false,false
ion-modal,prop,isOpen,boolean,false,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,keyboardClose,boolean,true,false,false
ion-modal,prop,leaveAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false ion-modal,prop,leaveAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
ion-modal,prop,mode,"ios" | "md",undefined,false,false ion-modal,prop,mode,"ios" | "md",undefined,false,false
@ -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,event,any,undefined,false,false
ion-popover,prop,htmlAttributes,undefined | { [key: string]: 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,isOpen,boolean,false,false,false
ion-popover,prop,keepContentsMounted,boolean,false,false,false
ion-popover,prop,keyboardClose,boolean,true,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,leaveAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
ion-popover,prop,mode,"ios" | "md",undefined,false,false ion-popover,prop,mode,"ios" | "md",undefined,false,false

View File

@ -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. * 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; "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. * If `true`, the keyboard will be automatically dismissed when the overlay is presented.
*/ */
@ -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. * 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; "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. * If `true`, the keyboard will be automatically dismissed when the overlay is presented.
*/ */
@ -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. * 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; "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. * If `true`, the keyboard will be automatically dismissed when the overlay is presented.
*/ */
@ -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. * 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; "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. * If `true`, the keyboard will be automatically dismissed when the overlay is presented.
*/ */

View File

@ -222,6 +222,19 @@ export class Modal implements ComponentInterface, OverlayInterface {
this.configureTriggerInteraction(); 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) * TODO (FW-937)
* This needs to default to true in the next * This needs to default to true in the next

View File

@ -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. * Emitted after the popover has presented.
*/ */

View File

@ -21,6 +21,7 @@ interface IonicReactInternalProps<ElementType> extends React.HTMLAttributes<Elem
onDidPresent?: (event: CustomEvent<OverlayEventDetail>) => void; onDidPresent?: (event: CustomEvent<OverlayEventDetail>) => void;
onWillDismiss?: (event: CustomEvent<OverlayEventDetail>) => void; onWillDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
onWillPresent?: (event: CustomEvent<OverlayEventDetail>) => void; onWillPresent?: (event: CustomEvent<OverlayEventDetail>) => void;
keepContentsMounted?: boolean;
} }
export const createInlineOverlayComponent = <PropType, ElementType>( export const createInlineOverlayComponent = <PropType, ElementType>(
@ -128,7 +129,7 @@ export const createInlineOverlayComponent = <PropType, ElementType>(
* so conditionally render the component * so conditionally render the component
* based on the isOpen state. * based on the isOpen state.
*/ */
return createElement(tagName, newProps, (this.state.isOpen) ? return createElement(tagName, newProps, (this.state.isOpen || this.props.keepContentsMounted) ?
createElement('div', { createElement('div', {
id: 'ion-react-wrapper', id: 'ion-react-wrapper',
ref: this.wrapperRef, ref: this.wrapperRef,

View File

@ -0,0 +1,60 @@
describe('keepContentsMounted', () => {
describe('modal', () => {
it('should not mount component if false', () => {
cy.visit('/overlay-components/modal');
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('/overlay-components/popover');
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');
});
})
});

View File

@ -24,6 +24,7 @@ import './theme/variables.css';
import Main from './pages/Main'; import Main from './pages/Main';
import OverlayHooks from './pages/overlay-hooks/OverlayHooks'; import OverlayHooks from './pages/overlay-hooks/OverlayHooks';
import OverlayComponents from './pages/overlay-components/OverlayComponents'; import OverlayComponents from './pages/overlay-components/OverlayComponents';
import KeepContentsMounted from './pages/overlay-components/KeepContentsMounted';
import Tabs from './pages/Tabs'; import Tabs from './pages/Tabs';
setupIonicReact(); setupIonicReact();
@ -35,6 +36,7 @@ const App: React.FC = () => (
<Route path="/" component={Main} /> <Route path="/" component={Main} />
<Route path="/overlay-hooks" component={OverlayHooks} /> <Route path="/overlay-hooks" component={OverlayHooks} />
<Route path="/overlay-components" component={OverlayComponents} /> <Route path="/overlay-components" component={OverlayComponents} />
<Route path="/keep-contents-mounted" component={KeepContentsMounted} />
<Route path="/tabs" component={Tabs} /> <Route path="/tabs" component={Tabs} />
</IonRouterOutlet> </IonRouterOutlet>
</IonReactRouter> </IonReactRouter>

View File

@ -31,6 +31,11 @@ const Main: React.FC<MainProps> = () => {
<IonLabel>Overlay Components</IonLabel> <IonLabel>Overlay Components</IonLabel>
</IonItem> </IonItem>
</IonList> </IonList>
<IonList>
<IonItem routerLink="/keep-contents-mounted">
<IonLabel>Keep Contents Mounted Overlay Components</IonLabel>
</IonItem>
</IonList>
<IonList> <IonList>
<IonItem routerLink="/tabs"> <IonItem routerLink="/tabs">
<IonLabel>Tabs</IonLabel> <IonLabel>Tabs</IonLabel>

View File

@ -0,0 +1,38 @@
import React, { useState } from 'react';
import {
IonButton,
IonContent,
IonPage,
IonModal,
IonPopover,
} from '@ionic/react';
const KeepContentsMounted: React.FC = () => {
const [showModal, setShowModal] = useState(false);
const [showPopover, setShowPopover] = useState(false);
return (
<IonPage>
<IonContent fullscreen>
<IonButton id="open-modal" onClick={() => setShowModal(true)}>Open Modal</IonButton>
<IonButton id="open-popover" onClick={() => setShowPopover(true)}>Open Popover</IonButton>
<IonModal keepContentsMounted={true} id="default-modal" isOpen={showModal} onDidDismiss={() => setShowPopover(false)}>
<IonContent>
<IonButton onClick={() => setShowModal(false)}>Dismiss</IonButton>
Modal Content
</IonContent>
</IonModal>
<IonPopover keepContentsMounted={true} isOpen={showPopover} onDidDismiss={() => setShowPopover(false)}>
<IonContent>
<IonButton onClick={() => setShowPopover(false)}>Dismiss</IonButton>
Popover Content
</IonContent>
</IonPopover>
</IonContent>
</IonPage>
);
};
export default KeepContentsMounted;

View File

@ -29,7 +29,7 @@ export const IonPicker = /*@__PURE__*/ defineOverlayContainer<JSX.IonPicker>('io
export const IonToast = /*@__PURE__*/ defineOverlayContainer<JSX.IonToast>('ion-toast', defineIonToastCustomElement, ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'position', 'translucent'], toastController); export const IonToast = /*@__PURE__*/ defineOverlayContainer<JSX.IonToast>('ion-toast', defineIonToastCustomElement, ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'position', 'translucent'], toastController);
export const IonModal = /*@__PURE__*/ defineOverlayContainer<JSX.IonModal>('ion-modal', defineIonModalCustomElement, ['animated', 'backdropBreakpoint', 'backdropDismiss', 'breakpoints', 'canDismiss', 'enterAnimation', 'handle', 'htmlAttributes', 'initialBreakpoint', 'isOpen', 'keyboardClose', 'leaveAnimation', 'mode', 'presentingElement', 'showBackdrop', 'swipeToClose', 'trigger']); export const IonModal = /*@__PURE__*/ defineOverlayContainer<JSX.IonModal>('ion-modal', defineIonModalCustomElement, ['animated', 'backdropBreakpoint', 'backdropDismiss', 'breakpoints', 'canDismiss', 'enterAnimation', 'handle', 'htmlAttributes', 'initialBreakpoint', 'isOpen', 'keepContentsMounted', 'keyboardClose', 'leaveAnimation', 'mode', 'presentingElement', 'showBackdrop', 'swipeToClose', 'trigger']);
export const IonPopover = /*@__PURE__*/ defineOverlayContainer<JSX.IonPopover>('ion-popover', defineIonPopoverCustomElement, ['alignment', 'animated', 'arrow', 'backdropDismiss', 'component', 'componentProps', 'dismissOnSelect', 'enterAnimation', 'event', 'htmlAttributes', 'isOpen', 'keyboardClose', 'leaveAnimation', 'mode', 'reference', 'showBackdrop', 'side', 'size', 'translucent', 'trigger', 'triggerAction']); export const IonPopover = /*@__PURE__*/ defineOverlayContainer<JSX.IonPopover>('ion-popover', defineIonPopoverCustomElement, ['alignment', 'animated', 'arrow', 'backdropDismiss', 'component', 'componentProps', 'dismissOnSelect', 'enterAnimation', 'event', 'htmlAttributes', 'isOpen', 'keepContentsMounted', 'keyboardClose', 'leaveAnimation', 'mode', 'reference', 'showBackdrop', 'side', 'size', 'translucent', 'trigger', 'triggerAction']);

View File

@ -162,7 +162,7 @@ export const defineOverlayContainer = <Props extends object>(name: string, defin
return h( return h(
name, name,
{ ...restOfProps, ref: elementRef }, { ...restOfProps, ref: elementRef },
(isOpen.value) ? slots : undefined (isOpen.value || restOfProps.keepContentsMounted) ? slots : undefined
) )
} }
}); });

View File

@ -1,11 +1,16 @@
<template> <template>
<ion-content class="ion-padding"> <ion-content class="ion-padding">
<ion-button id="dismiss" @click="dismiss">Dismiss</ion-button> <br />
<div id="title">
{{ title }} {{ title }}
</div>
</ion-content> </ion-content>
</template> </template>
<script lang="ts"> <script lang="ts">
import { import {
IonButton,
IonContent, IonContent,
popoverController popoverController
} from '@ionic/vue'; } from '@ionic/vue';
@ -16,6 +21,7 @@ export default defineComponent({
title: { type: String, default: 'Default Title' } title: { type: String, default: 'Default Title' }
}, },
components: { components: {
IonButton,
IonContent IonContent
}, },
setup() { setup() {

View File

@ -25,6 +25,10 @@ const routes: Array<RouteRecordRaw> = [
path: '/overlays', path: '/overlays',
component: () => import('@/views/Overlays.vue') component: () => import('@/views/Overlays.vue')
}, },
{
path: '/keep-contents-mounted',
component: () => import('@/views/OverlaysKeepContentsMounted.vue')
},
{ {
path: '/inputs', path: '/inputs',
component: () => import('@/views/Inputs.vue') component: () => import('@/views/Inputs.vue')

View File

@ -0,0 +1,80 @@
<template>
<ion-page data-pageid="overlays-automount">
<ion-header :translucent="true">
<ion-toolbar>
<ion-buttons>
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Overlays - Auto Mount</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding" :fullscreen="true">
<ion-button id="open-auto-mount-modal">Open Auto Mount Modal</ion-button>
<ion-button id="open-auto-mount-popover">Open Auto Mount Popover</ion-button>
<br /><br />
<ion-modal
id="auto-mount-modal"
:keep-contents-mounted="true"
trigger="open-auto-mount-modal"
>
<ModalContent :title="overlayProps.title"></ModalContent>
</ion-modal>
<ion-popover
id="auto-mount-popover"
:keep-contents-mounted="true"
trigger="open-auto-mount-popover"
>
<PopoverContent :title="overlayProps.title"></PopoverContent>
</ion-popover>
</ion-content>
</ion-page>
</template>
<script lang="ts">
import {
IonBackButton,
IonButton,
IonButtons,
IonContent,
IonHeader,
IonPage,
IonTitle,
IonToolbar,
IonModal,
IonPopover,
} from '@ionic/vue';
import { defineComponent } from 'vue';
import ModalContent from '@/components/ModalContent.vue';
import PopoverContent from '@/components/PopoverContent.vue';
export default defineComponent({
components: {
ModalContent,
PopoverContent,
IonBackButton,
IonButton,
IonButtons,
IonContent,
IonHeader,
IonPage,
IonTitle,
IonToolbar,
IonModal,
IonPopover,
},
setup() {
const overlayProps = {
title: 'Custom Title'
}
return {
overlayProps
}
}
});
</script>

View File

@ -0,0 +1,52 @@
describe('overlays - keepContentsMounted', () => {
beforeEach(() => {
cy.viewport(1000, 900);
cy.visit('http://localhost:8080/keep-contents-mounted')
})
describe('modal', () => {
it('should not mount component if false', () => {
cy.get('ion-modal#default-modal ion-content').should('not.exist');
});
it('should mount component if true', () => {
cy.get('ion-modal#auto-mount-modal ion-content').should('exist');
});
it('should keep component mounted after dismissing if true', () => {
cy.get('#open-auto-mount-modal').click();
cy.get('ion-modal#auto-mount-modal ion-content').should('exist');
cy.get('ion-modal#auto-mount-modal #dismiss').click();
cy.get('ion-modal#auto-mount-modal')
.should('not.be.visible')
.should('have.class', 'overlay-hidden');
cy.get('ion-modal#auto-mount-modal ion-content').should('exist');
});
})
describe('popover', () => {
it('should not mount component if false', () => {
cy.get('ion-popover#default-popover ion-content').should('not.exist');
});
it('should mount component if true', () => {
cy.get('ion-popover#auto-mount-popover ion-content').should('exist');
});
it('should keep component mounted after dismissing if true', () => {
cy.get('#open-auto-mount-popover').click();
cy.get('ion-popover#auto-mount-popover ion-content').should('exist');
cy.get('ion-popover#auto-mount-popover #dismiss').click();
cy.get('ion-popover#auto-mount-popover')
.should('not.be.visible')
.should('have.class', 'overlay-hidden');
cy.get('ion-popover#auto-mount-popover ion-content').should('exist');
});
})
})

View File

@ -134,7 +134,7 @@ describe('Overlays', () => {
cy.get('ion-button#present-overlay').click(); cy.get('ion-button#present-overlay').click();
cy.get('ion-popover.ion-popover-controller').should('exist'); cy.get('ion-popover.ion-popover-controller').should('exist');
cy.get('ion-popover.ion-popover-controller ion-content').should('have.text', 'Custom Title'); cy.get('ion-popover.ion-popover-controller ion-content #title').should('have.text', 'Custom Title');
}); });
it('should pass props to popover via component', () => { it('should pass props to popover via component', () => {
@ -144,7 +144,7 @@ describe('Overlays', () => {
cy.get('ion-button#present-overlay').click(); cy.get('ion-button#present-overlay').click();
cy.get('ion-popover').should('exist'); cy.get('ion-popover').should('exist');
cy.get('ion-popover.popover-inline ion-content').should('have.text', 'Custom Title'); cy.get('ion-popover.popover-inline ion-content #title').should('have.text', 'Custom Title');
}); });
it('should only open one instance at a time when props change quickly on component', () => { it('should only open one instance at a time when props change quickly on component', () => {