diff --git a/docs/app/components/content/examples/modal/ModalExample.vue b/docs/app/components/content/examples/modal/ModalExample.vue index a409d417..5c0c862e 100644 --- a/docs/app/components/content/examples/modal/ModalExample.vue +++ b/docs/app/components/content/examples/modal/ModalExample.vue @@ -1,23 +1,17 @@ diff --git a/src/runtime/components/Modal.vue b/src/runtime/components/Modal.vue index c6ca5a3f..ada37dd3 100644 --- a/src/runtime/components/Modal.vue +++ b/src/runtime/components/Modal.vue @@ -56,7 +56,9 @@ export interface ModalProps extends DialogRootProps { ui?: Partial } -export interface ModalEmits extends DialogRootEmits {} +export interface ModalEmits extends DialogRootEmits { + 'after:leave': [] +} export interface ModalSlots { default(props: { open: boolean }): any @@ -126,7 +128,7 @@ const ui = computed(() => modal({ - + diff --git a/src/runtime/components/ModalProvider.vue b/src/runtime/components/ModalProvider.vue deleted file mode 100644 index 69799d52..00000000 --- a/src/runtime/components/ModalProvider.vue +++ /dev/null @@ -1,12 +0,0 @@ - - - diff --git a/src/runtime/components/OverlayProvider.vue b/src/runtime/components/OverlayProvider.vue new file mode 100644 index 00000000..868bc2d4 --- /dev/null +++ b/src/runtime/components/OverlayProvider.vue @@ -0,0 +1,26 @@ + + + diff --git a/src/runtime/components/Slideover.vue b/src/runtime/components/Slideover.vue index b06526aa..f935e1e8 100644 --- a/src/runtime/components/Slideover.vue +++ b/src/runtime/components/Slideover.vue @@ -55,7 +55,9 @@ export interface SlideoverProps extends DialogRootProps { ui?: Partial } -export interface SlideoverEmits extends DialogRootEmits {} +export interface SlideoverEmits extends DialogRootEmits { + 'after:leave': [] +} export interface SlideoverSlots { default(props: { open: boolean }): any @@ -126,7 +128,7 @@ const ui = computed(() => slideover({ - + diff --git a/src/runtime/components/SlideoverProvider.vue b/src/runtime/components/SlideoverProvider.vue deleted file mode 100644 index 8b0eafd5..00000000 --- a/src/runtime/components/SlideoverProvider.vue +++ /dev/null @@ -1,12 +0,0 @@ - - - diff --git a/src/runtime/composables/useModal.ts b/src/runtime/composables/useModal.ts deleted file mode 100644 index d0fd8ac8..00000000 --- a/src/runtime/composables/useModal.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { ref, inject } from 'vue' -import type { ShallowRef, Component, InjectionKey } from 'vue' -import type { ComponentProps } from 'vue-component-type-helpers' -import { createSharedComposable } from '@vueuse/core' -import type { ModalProps } from '../types' - -export interface ModalState { - component: Component | string - props: ModalProps -} - -export const modalInjectionKey: InjectionKey> = Symbol('nuxt-ui.modal') - -function _useModal() { - const modalState = inject(modalInjectionKey) - - const isOpen = ref(false) - - function open(component: T, props?: ModalProps & ComponentProps) { - if (!modalState) { - throw new Error('useModal() is called without provider') - } - - modalState.value = { - component, - props: props ?? {} - } - - isOpen.value = true - } - - async function close() { - if (!modalState) return - - isOpen.value = false - } - - function reset() { - if (!modalState) return - - modalState.value = { - component: 'div', - props: {} - } - } - - /** - * Allows updating the modal props - */ - function patch>(props: Partial>) { - if (!modalState) return - - modalState.value = { - ...modalState.value, - props: { - ...modalState.value.props, - ...props - } - } - } - - return { - open, - close, - reset, - patch, - isOpen - } -} - -export const useModal = createSharedComposable(_useModal) diff --git a/src/runtime/composables/useOverlay.ts b/src/runtime/composables/useOverlay.ts new file mode 100644 index 00000000..784e586f --- /dev/null +++ b/src/runtime/composables/useOverlay.ts @@ -0,0 +1,118 @@ +import type { Component } from 'vue' +import { createSharedComposable } from '@vueuse/core' +import type { ComponentProps } from 'vue-component-type-helpers' + +export type OverlayOptions> = { + defaultOpen?: boolean + props?: OverlayAttrs + destroyOnClose?: boolean +} + +type ManagedOverlayOptionsPrivate = { + component?: T + id: symbol + isMounted: boolean + modelValue: boolean + resolvePromise?: (value: unknown) => void +} +export type Overlay = OverlayOptions & ManagedOverlayOptionsPrivate + +interface OverlayInstance { + open: (props?: ComponentProps) => Promise + close: (value?: any) => void + patch: (props: Partial>) => void +} + +function _useOverlay() { + const overlays = shallowReactive([]) + + const create = (component: T, _options?: OverlayOptions>): OverlayInstance => { + const { props: props, defaultOpen, destroyOnClose } = _options || {} + + const options = reactive({ + id: Symbol(import.meta.dev ? 'useOverlay' : ''), + modelValue: !!defaultOpen, + component: markRaw(component!), + isMounted: !!defaultOpen, + destroyOnClose: !!destroyOnClose, + props: props || {} + }) + + overlays.push(options) + + return { + open: (props?: ComponentProps) => open(options.id, props), + close: value => close(options.id, value), + patch: (props: Partial>) => patch(options.id, props) + } + } + + const open = (id: symbol, props?: ComponentProps): Promise => { + const overlay = getOverlay(id) + + // If props are provided, update the overlay's props + if (props) { + patch(overlay.id, props) + } + + overlay.modelValue = true + overlay.isMounted = true + + // Return a new promise that will be resolved when close is called + return new Promise((resolve) => { + overlay.resolvePromise = resolve + }) + } + + const close = (id: symbol, value?: any): void => { + const overlay = getOverlay(id) + + overlay.modelValue = false + + // Resolve the promise if it exists + if (overlay.resolvePromise) { + overlay.resolvePromise(value) + overlay.resolvePromise = undefined + } + } + + const unMount = (id: symbol): void => { + const overlay = getOverlay(id) + + overlay.isMounted = false + + if (overlay.destroyOnClose) { + const index = overlays.findIndex(overlay => overlay.id === id) + overlays.splice(index, 1) + } + } + + const patch = (id: symbol, props: Partial>): void => { + const overlay = getOverlay(id) + + Object.entries(props!).forEach(([key, value]) => { + (overlay.props as any)[key] = value + }) + } + + const getOverlay = (id: symbol): Overlay => { + const overlay = overlays.find(overlay => overlay.id === id) + + if (!overlay) { + throw new Error('Overlay not found') + } + + return overlay + } + + return { + overlays, + open, + close, + create, + patch, + unMount + } +} + +export const useOverlay = createSharedComposable(_useOverlay) diff --git a/src/runtime/composables/useSlideover.ts b/src/runtime/composables/useSlideover.ts deleted file mode 100644 index 97122d98..00000000 --- a/src/runtime/composables/useSlideover.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { ref, inject } from 'vue' -import type { ShallowRef, Component, InjectionKey } from 'vue' -import type { ComponentProps } from 'vue-component-type-helpers' -import { createSharedComposable } from '@vueuse/core' -import type { SlideoverProps } from '../types' - -export interface SlideoverState { - component: Component | string - props: SlideoverProps -} - -export const slideoverInjectionKey: InjectionKey> = Symbol('nuxt-ui.slideover') - -function _useSlideover() { - const slideoverState = inject(slideoverInjectionKey) - - const isOpen = ref(false) - - function open(component: T, props?: SlideoverProps & ComponentProps) { - if (!slideoverState) { - throw new Error('useSlideover() is called without provider') - } - - slideoverState.value = { - component, - props: props ?? {} - } - - isOpen.value = true - } - - async function close() { - if (!slideoverState) return - - isOpen.value = false - } - - function reset() { - if (!slideoverState) return - - slideoverState.value = { - component: 'div', - props: {} - } - } - - /** - * Allows updating the slideover props - */ - function patch>(props: Partial>) { - if (!slideoverState) return - - slideoverState.value = { - ...slideoverState.value, - props: { - ...slideoverState.value.props, - ...props - } - } - } - - return { - open, - close, - reset, - patch, - isOpen - } -} - -export const useSlideover = createSharedComposable(_useSlideover) diff --git a/src/runtime/plugins/modal.ts b/src/runtime/plugins/modal.ts deleted file mode 100644 index 1cb3fdd2..00000000 --- a/src/runtime/plugins/modal.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { shallowRef } from 'vue' -import { defineNuxtPlugin } from '#imports' -// FIXME: https://github.com/nuxt/module-builder/issues/141#issuecomment-2078248248 -import type {} from '#app' -import { modalInjectionKey, type ModalState } from '../composables/useModal' - -export default defineNuxtPlugin((nuxtApp) => { - const modalState = shallowRef({ - component: 'div', - props: {} - }) - nuxtApp.vueApp.provide(modalInjectionKey, modalState) -}) diff --git a/src/runtime/plugins/slideover.ts b/src/runtime/plugins/slideover.ts deleted file mode 100644 index 41078f2f..00000000 --- a/src/runtime/plugins/slideover.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { shallowRef } from 'vue' -import { defineNuxtPlugin } from '#imports' -// FIXME: https://github.com/nuxt/module-builder/issues/141#issuecomment-2078248248 -import type {} from '#app' -import { slideoverInjectionKey, type SlideoverState } from '../composables/useSlideover' - -export default defineNuxtPlugin((nuxtApp) => { - const slideoverState = shallowRef({ - component: 'div', - props: {} - }) - - nuxtApp.vueApp.provide(slideoverInjectionKey, slideoverState) -})