fix(vue): prevent race conditions when opening overlays (#22883)

resolves #22880
This commit is contained in:
Liam DeBeasi
2021-02-23 14:42:38 -05:00
committed by GitHub
parent 26285bbc91
commit 68a9b80053
3 changed files with 67 additions and 14 deletions

View File

@@ -1,4 +1,4 @@
import { defineComponent, h, ref } from 'vue';
import { defineComponent, h, ref, VNode } from 'vue';
export interface OverlayProps {
isOpen?: boolean;
@@ -21,28 +21,61 @@ export const defineOverlayContainer = <Props extends object>(name: string, compo
isOpen && (await present(props))
}
const onVnodeUpdated = async () => {
const isOpen = props.isOpen;
const onVnodeUpdated = async (node: VNode, prevNode: VNode) => {
const isOpen = node.props!.isOpen;
const prevIsOpen = prevNode.props!.isOpen;
/**
* Do not do anything if this prop
* did not change.
*/
if (isOpen === prevIsOpen) return;
if (isOpen) {
await overlay.value?.present() || present(props);
await present(props);
} else {
await overlay.value?.dismiss();
overlay.value = undefined;
await dismiss();
}
}
const onVnodeBeforeUnmount = async () => {
await overlay.value?.dismiss();
await dismiss();
}
const dismiss = async () => {
if (!overlay.value) return;
await overlay.value;
overlay.value = overlay.value.dismiss();
await overlay.value;
overlay.value = undefined;
}
const present = async (props: Readonly<Props>) => {
/**
* Do not open another instance
* if one is already opened.
*/
if (overlay.value) {
await overlay.value;
}
if (overlay.value?.present) {
await overlay.value.present();
return;
}
const component = slots.default && slots.default()[0];
overlay.value = await controller.create({
overlay.value = controller.create({
...props,
component
});
overlay.value = await overlay.value;
eventListeners.forEach(eventListener => {
overlay.value.addEventListener(eventListener.componentEv, () => {
emit(eventListener.frameworkEv);
@@ -59,7 +92,8 @@ export const defineOverlayContainer = <Props extends object>(name: string, compo
style: { display: 'none' },
onVnodeMounted,
onVnodeUpdated,
onVnodeBeforeUnmount
onVnodeBeforeUnmount,
isOpen: props.isOpen
}
);
}

View File

@@ -62,7 +62,9 @@
<br />
<ion-button expand="block" @click="present($event)" id="present-overlay">Present Overlay</ion-button>
<ion-button @click="present($event)" id="present-overlay">Present Overlay</ion-button>
<ion-button @click="changeLoadingProps()" id="change-loading-props">Quickly Change Loading Props</ion-button>
<ion-action-sheet
:is-open="isActionSheetOpen"
@@ -314,7 +316,18 @@ export default defineComponent({
}
}
const changeLoadingProps = () => {
setLoadingRef(true);
setTimeout(() => {
setLoadingRef(false);
setTimeout(() => {
setLoadingRef(true);
}, 10);
}, 10);
}
return {
changeLoadingProps,
overlayProps,
present,
componentType,

View File

@@ -59,7 +59,7 @@ describe('Overlays', () => {
cy.get('ion-toast').should('not.exist');
});
it('it should pass props to modal via controller', () => {
it('should pass props to modal via controller', () => {
cy.get('ion-radio#ion-modal').click();
cy.get('ion-radio#controller').click();
@@ -69,7 +69,7 @@ describe('Overlays', () => {
cy.get('ion-modal ion-content').should('have.text', 'Custom Title');
});
it('it should pass props to modal via component', () => {
it('should pass props to modal via component', () => {
cy.get('ion-radio#ion-modal').click();
cy.get('ion-radio#component').click();
@@ -79,7 +79,7 @@ describe('Overlays', () => {
cy.get('ion-modal ion-content').should('have.text', 'Custom Title');
});
it('it should pass props to popover via controller', () => {
it('should pass props to popover via controller', () => {
cy.get('ion-radio#ion-popover').click();
cy.get('ion-radio#controller').click();
@@ -89,7 +89,7 @@ describe('Overlays', () => {
cy.get('ion-popover ion-content').should('have.text', 'Custom Title');
});
it('it should pass props to popover via component', () => {
it('should pass props to popover via component', () => {
cy.get('ion-radio#ion-popover').click();
cy.get('ion-radio#component').click();
@@ -98,4 +98,10 @@ describe('Overlays', () => {
cy.get('ion-popover ion-content').should('have.text', 'Custom Title');
});
it('should only open one instance at a time when props change quickly on component', () => {
cy.get('#change-loading-props').click();
cy.get('ion-loading').should('have.length', 1);
});
})