diff --git a/packages/nc-gui-v2/composables/index.ts b/packages/nc-gui-v2/composables/index.ts index a242896d05..9c6f6a3010 100644 --- a/packages/nc-gui-v2/composables/index.ts +++ b/packages/nc-gui-v2/composables/index.ts @@ -1,4 +1,5 @@ export * from './useApi' +export * from './useDialog' export * from './useGlobal' export * from './useInjectionState' export * from './useSidebar' diff --git a/packages/nc-gui-v2/composables/useDialog/index.ts b/packages/nc-gui-v2/composables/useDialog/index.ts new file mode 100644 index 0000000000..7b3b299fef --- /dev/null +++ b/packages/nc-gui-v2/composables/useDialog/index.ts @@ -0,0 +1,67 @@ +import { render } from '@vue/runtime-dom' +import type { ComponentPublicInstance } from '@vue/runtime-core' +import { createEventHook, h, toReactive, tryOnScopeDispose, useNuxtApp, watch } from '#imports' + +/** + * Programmatically create a component and attach it to the body (or a specific mount target), like a dialog or modal. + */ +export function useDialog( + component: any, + props: NonNullable[1]> = {}, + mountTarget?: Element | ComponentPublicInstance, +) { + const closeHook = createEventHook() + const mountedHook = createEventHook() + + const isMounted = $ref(false) + + const domNode = document.createElement('div') + + /** if specified, append vnode to mount target instead of document.body */ + if (mountTarget) { + if ('$el' in mountTarget) { + mountTarget.$el.appendChild(domNode) + } else { + mountTarget.appendChild(domNode) + } + } else { + document.body.appendChild(domNode) + } + + /** When props change, we want to re-render the element with the new prop values */ + watch( + toReactive(props), + (reactiveProps) => { + const vNode = h(component, reactiveProps) + + vNode.appContext = useNuxtApp().vueApp._context + + render(vNode, domNode) + + if (!isMounted) mountedHook.trigger() + }, + { deep: true, immediate: true }, + ) + + /** When calling scope is disposed, destroy component */ + tryOnScopeDispose(close) + + /** destroy component, can be debounced */ + function close(debounce = 0) { + setTimeout(() => { + render(null, domNode) + + setTimeout(() => { + document.body.removeChild(domNode) + }, 100) + + closeHook.trigger() + }, debounce) + } + + return { + close, + onClose: closeHook.on, + onMounted: mountedHook.on, + } +}