fix(vue): unmount teleported components (#26647)

Resolves #26644
This commit is contained in:
Sean Perkins
2023-01-20 11:23:27 -05:00
committed by GitHub
parent 22e9ff866f
commit 6b16a0c020
4 changed files with 63 additions and 19 deletions

View File

@ -31,14 +31,7 @@ export const addTeleportedUserComponent = (component: VNode) => {
}; };
export const removeTeleportedUserComponent = (component: VNode) => { export const removeTeleportedUserComponent = (component: VNode) => {
/** userComponents.value = userComponents.value.filter(
* Finds the index of the component in the array and removes it. (cmp) => cmp !== component
* Previously we were using a filter to remove the component from the array, );
* but this was causing a bug where dismissing an overlay and then presenting
* a new overlay, would cause the new overlay to be removed.
*/
const index = userComponents.value.findIndex((cmp) => cmp === component);
if (index !== -1) {
userComponents.value.splice(index, 1);
}
}; };

View File

@ -11,7 +11,9 @@ export const VueDelegate = (
addFn = addTeleportedUserComponent, addFn = addTeleportedUserComponent,
removeFn = removeTeleportedUserComponent removeFn = removeTeleportedUserComponent
): FrameworkDelegate => { ): FrameworkDelegate => {
let Component: VNode | undefined; // `h` doesn't provide a type for the component argument
const refMap = new WeakMap<any, VNode>();
// TODO(FW-2969): types // TODO(FW-2969): types
const attachViewToDom = ( const attachViewToDom = (
parentElement: HTMLElement, parentElement: HTMLElement,
@ -32,15 +34,23 @@ export const VueDelegate = (
classes && div.classList.add(...classes); classes && div.classList.add(...classes);
parentElement.appendChild(div); parentElement.appendChild(div);
Component = h(Teleport, { to: div }, h(component, { ...componentProps })); const hostComponent = h(
Teleport,
{ to: div },
h(component, { ...componentProps })
);
addFn(Component); refMap.set(component, hostComponent);
addFn(hostComponent);
return Promise.resolve(div); return Promise.resolve(div);
}; };
const removeViewFromDom = () => { const removeViewFromDom = (_container: any, component: any) => {
Component && removeFn(Component); const hostComponent = refMap.get(component);
hostComponent && removeFn(hostComponent);
return Promise.resolve(); return Promise.resolve();
}; };

View File

@ -9,7 +9,8 @@
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content class="ion-padding"> <ion-content class="ion-padding">
{{ title }} <ion-label id="title">{{ title }}</ion-label>
<ion-input></ion-input>
</ion-content> </ion-content>
</ion-page> </ion-page>
</template> </template>
@ -23,6 +24,7 @@ import {
IonHeader, IonHeader,
IonTitle, IonTitle,
IonToolbar, IonToolbar,
IonInput,
modalController modalController
} from '@ionic/vue'; } from '@ionic/vue';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
@ -38,7 +40,8 @@ export default defineComponent({
IonContent, IonContent,
IonHeader, IonHeader,
IonTitle, IonTitle,
IonToolbar IonToolbar,
IonInput
}, },
setup() { setup() {
const dismiss = async () => { const dismiss = async () => {

View File

@ -114,7 +114,7 @@ describe('Overlays', () => {
cy.get('ion-button#present-overlay').click(); cy.get('ion-button#present-overlay').click();
cy.get('ion-modal').should('exist'); cy.get('ion-modal').should('exist');
cy.get('ion-modal ion-content').should('have.text', 'Custom Title'); cy.get('ion-modal ion-content #title').should('have.text', 'Custom Title');
}); });
it('should pass props to modal via component', () => { it('should pass props to modal via component', () => {
@ -124,7 +124,7 @@ describe('Overlays', () => {
cy.get('ion-button#present-overlay').click(); cy.get('ion-button#present-overlay').click();
cy.get('ion-modal').should('exist'); cy.get('ion-modal').should('exist');
cy.get('ion-modal ion-content').should('have.text', 'Custom Title'); cy.get('ion-modal ion-content #title').should('have.text', 'Custom Title');
}); });
it('should pass props to popover via controller', () => { it('should pass props to popover via controller', () => {
@ -195,6 +195,44 @@ describe('Overlays', () => {
didDismiss: 2 didDismiss: 2
}); });
}); });
it('should unmount modal via component', () => {
cy.get('ion-radio#ion-modal').click();
cy.get('ion-radio#component').click();
cy.get('ion-button#present-overlay').click();
cy.get('ion-modal').should('exist');
cy.get('ion-modal ion-input').should('have.value', '');
cy.get('ion-modal ion-input').type('1');
cy.get('ion-modal #dismiss').click();
cy.get('ion-button#present-overlay').click();
cy.get('ion-modal').should('exist');
cy.get('ion-modal ion-input').should('have.value', '');
});
it('should unmount modal via controller', () => {
cy.get('ion-radio#ion-modal').click();
cy.get('ion-radio#controller').click();
cy.get('ion-button#present-overlay').click();
cy.get('ion-modal').should('exist');
cy.get('ion-modal ion-input').should('have.value', '');
cy.get('ion-modal ion-input').type('1');
cy.get('ion-modal #dismiss').click();
cy.get('ion-button#present-overlay').click();
cy.get('ion-modal').should('exist');
cy.get('ion-modal ion-input').should('have.value', '');
});
}) })
const testLifecycle = (selector, expected = {}) => { const testLifecycle = (selector, expected = {}) => {