fix(vue): modal and popover components now correctly pass properties (#23761)

resolves #23698
This commit is contained in:
Liam DeBeasi
2021-08-16 09:09:41 -04:00
committed by GitHub
parent 2995e337c8
commit 578b9062dd
7 changed files with 178 additions and 137 deletions

View File

@ -1,118 +1,185 @@
import { defineComponent, h, ref, VNode } from 'vue';
import { defineComponent, h, ref, VNode, onMounted } from 'vue';
import { defineCustomElement } from '../utils';
export interface OverlayProps {
isOpen?: boolean;
}
export const defineOverlayContainer = <Props extends object>(name: string, customElement: any, componentProps: string[] = [], controller: any) => {
const eventListeners = [
{ componentEv: `${name}-will-present`, frameworkEv: 'willPresent' },
{ componentEv: `${name}-did-present`, frameworkEv: 'didPresent' },
{ componentEv: `${name}-will-dismiss`, frameworkEv: 'willDismiss' },
{ componentEv: `${name}-did-dismiss`, frameworkEv: 'didDismiss' },
];
const EMPTY_PROP = Symbol();
const DEFAULT_EMPTY_PROP = { default: EMPTY_PROP };
const Container = defineComponent<Props & OverlayProps>((props, { slots, emit }) => {
defineCustomElement(name, customElement);
export const defineOverlayContainer = <Props extends object>(name: string, customElement: any, componentProps: string[] = [], controller?: any) => {
const overlay = ref();
const onVnodeMounted = async () => {
const isOpen = props.isOpen;
isOpen && (await present(props))
}
const createControllerComponent = () => {
return defineComponent<Props & OverlayProps>((props, { slots, emit }) => {
const eventListeners = [
{ componentEv: `${name}-will-present`, frameworkEv: 'willPresent' },
{ componentEv: `${name}-did-present`, frameworkEv: 'didPresent' },
{ componentEv: `${name}-will-dismiss`, frameworkEv: 'willDismiss' },
{ componentEv: `${name}-did-dismiss`, frameworkEv: 'didDismiss' },
];
const onVnodeUpdated = async (node: VNode, prevNode: VNode) => {
const isOpen = node.props!.isOpen;
const prevIsOpen = prevNode.props!.isOpen;
defineCustomElement(name, customElement);
/**
* Do not do anything if this prop
* did not change.
*/
if (isOpen === prevIsOpen) return;
const overlay = ref();
const onVnodeMounted = async () => {
const isOpen = props.isOpen;
isOpen && (await present(props))
}
if (isOpen) {
await present(props);
} else {
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 present(props);
} else {
await dismiss();
}
}
const onVnodeBeforeUnmount = async () => {
await dismiss();
}
}
const onVnodeBeforeUnmount = async () => {
await dismiss();
}
const dismiss = async () => {
if (!overlay.value) return;
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;
overlay.value = overlay.value.dismiss();
await overlay.value;
overlay.value = undefined;
}
if (overlay.value?.present) {
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;
}
let restOfProps: any = {};
/**
* We can use Object.entries here
* to avoid the hasOwnProperty check,
* but that would require 2 iterations
* where as this only requires 1.
*/
for (const key in props) {
const value = props[key] as any;
if (props.hasOwnProperty(key) && value !== EMPTY_PROP) {
restOfProps[key] = value;
}
}
/**
* These are getting passed as props.
* Potentially a Vue bug with Web Components?
*/
delete restOfProps.onWillPresent;
delete restOfProps.onDidPresent;
delete restOfProps.onWillDismiss;
delete restOfProps.onDidDismiss;
const component = slots.default && slots.default()[0];
overlay.value = controller.create({
...restOfProps,
component
});
overlay.value = await overlay.value;
eventListeners.forEach(eventListener => {
overlay.value.addEventListener(eventListener.componentEv, () => {
emit(eventListener.frameworkEv);
});
})
await overlay.value.present();
return;
}
/**
* These are getting passed as props.
* Potentially a Vue bug with Web Components?
*/
const restOfProps = { ...(props as any) };
delete restOfProps.onWillPresent;
delete restOfProps.onDidPresent;
delete restOfProps.onWillDismiss;
delete restOfProps.onDidDismiss;
return () => {
return h(
'div',
{
style: { display: 'none' },
onVnodeMounted,
onVnodeUpdated,
onVnodeBeforeUnmount,
isOpen: props.isOpen === true
}
);
}
});
};
const createInlineComponent = () => {
return defineComponent((props, { slots }) => {
defineCustomElement(name, customElement);
const isOpen = ref(false);
const elementRef = ref();
const component = slots.default && slots.default()[0];
overlay.value = controller.create({
...restOfProps,
component
onMounted(() => {
elementRef.value.addEventListener('will-present', () => isOpen.value = true);
elementRef.value.addEventListener('did-dismiss', () => isOpen.value = false);
});
overlay.value = await overlay.value;
return () => {
let restOfProps: any = {};
eventListeners.forEach(eventListener => {
overlay.value.addEventListener(eventListener.componentEv, () => {
emit(eventListener.frameworkEv);
});
})
await overlay.value.present();
}
return () => {
return h(
'div',
{
style: { display: 'none' },
onVnodeMounted,
onVnodeUpdated,
onVnodeBeforeUnmount,
isOpen: props.isOpen
/**
* We can use Object.entries here
* to avoid the hasOwnProperty check,
* but that would require 2 iterations
* where as this only requires 1.
*/
for (const key in props) {
const value = (props as any)[key];
if (props.hasOwnProperty(key) && value !== EMPTY_PROP) {
restOfProps[key] = value;
}
}
);
}
});
return h(
name,
{ ...restOfProps, ref: elementRef },
(isOpen.value) ? slots : undefined
)
}
});
}
const Container = (controller !== undefined) ? createControllerComponent() : createInlineComponent();
Container.displayName = name;
Container.props = [...componentProps, 'isOpen'];
Container.emits = ['willPresent', 'didPresent', 'willDismiss', 'didDismiss'];
Container.props = {
'isOpen': DEFAULT_EMPTY_PROP
};
componentProps.forEach(componentProp => {
Container.props[componentProp] = DEFAULT_EMPTY_PROP;
});
if (controller !== undefined) {
Container.emits = ['willPresent', 'didPresent', 'willDismiss', 'didDismiss'];
}
return Container;
}