import { defineAsyncComponent, getCurrentInstance, h, nextTick, render, createVNode, ref, watch, } from "vue"; import type { Component } from "vue"; type RawSlots = { [name: string]: unknown; $stable?: boolean; }; export interface IModalOptions { modalComponent: Component | any; appendTo?: HTMLElement | string; slots?: RawSlots; [name: string]: unknown; } const getAppendToElement = ( appendTo: IModalOptions["appendTo"] ): HTMLElement => { let appendToEL: HTMLElement | null = document.body; if (appendTo) { if (typeof appendTo === "string") { appendToEL = document.querySelector(appendTo); } if (appendTo instanceof HTMLElement) { appendToEL = appendTo; } if (!(appendToEL instanceof HTMLElement)) { appendToEL = document.body; } } return appendToEL; }; function getProvides(instance: any) { let provides = instance?.provides || {}; if (instance.parent) { provides = { ...provides, ...getProvides(instance.parent) }; } return provides; } export default function useShowModal() { const currentInstance = getCurrentInstance() as any; const provides = getProvides(currentInstance); function showModal(options: IModalOptions) { const container = document.createElement("div"); const isAsync = typeof options.modalComponent === "function"; const innerRef = ref(); const modalComponent = isAsync ? defineAsyncComponent(options.modalComponent) : options.modalComponent; const props: Record = {}; for (const key in options) { if (!["modalComponent", "appendTo", "slots"].includes(key)) props[key] = options[key]; } const vNode = createVNode({ setup() { const instance = getCurrentInstance() as any; if (instance) { instance.provides = { ...instance.provides, ...provides }; } }, render: () => h( modalComponent, { open: true, ...props, ref: innerRef, "onUpdate:open": () => { nextTick(() => { close(); }); }, }, options.slots ), }); render(vNode, container); getAppendToElement(options.appendTo).appendChild(container); function close() { render(null, container); container.parentNode?.removeChild(container); } innerRef.value.$close = close; if (!isAsync) { return innerRef.value; } else { return new Promise((resolve) => { watch( innerRef, () => { resolve(innerRef.value); }, { once: true, } ); }); } } return showModal; }