Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 38 additions & 7 deletions packages/vue-final-modal/cypress/components/TestUseModal.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,42 @@
import TestUseModal from './TestUseModal.vue'
import { createVfm } from '~/index'
import { createVfm, useModal } from '~/index'

it('renders the VueFinalModal', () => {
cy.mount(TestUseModal, {
global: {
plugins: [createVfm()],
},
describe('Test useModal()', () => {
it('Should be closed by default', () => {
const vfm = createVfm()
cy.mount(TestUseModal, {
props: {
run() {
useModal({
context: vfm,
slots: {
default: 'Hello World!',
},
})
},
},
global: { plugins: [vfm] },
})
cy.contains('Hello World!').should('not.exist')
})

it('Should be opened by given defaultModelValue: true', () => {
const vfm = createVfm()
cy.mount(TestUseModal, {
props: {
run() {
useModal({
context: vfm,
defaultModelValue: true,
slots: {
default: 'Hello World!',
},
})
},
},
global: { plugins: [vfm] },
})

cy.contains('Hello World!')
})
cy.contains('Hello World!')
})
14 changes: 6 additions & 8 deletions packages/vue-final-modal/cypress/components/TestUseModal.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
<script lang="ts" setup>
import { ModalsContainer, VueFinalModal, useModal } from '~/index'
import { ModalsContainer } from '~/index'

useModal({
defaultModelValue: true,
component: VueFinalModal,
slots: {
default: 'Hello World!',
},
})
const props = defineProps<{
run: () => void
}>()

props.run()
</script>

<template>
Expand Down
21 changes: 12 additions & 9 deletions packages/vue-final-modal/src/Modal.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { App, CSSProperties, Component, ComponentOptions, ComponentPublicInstance, ComputedRef, ConcreteComponent, Raw, Ref, VNodeProps } from 'vue'
import type { VueFinalModal } from '.'

export type ComponentProps = ComponentPublicInstance['$props']

Expand All @@ -12,17 +13,19 @@ type RawProps = VNodeProps & {
[Symbol.iterator]?: never
} & Record<string, any>

type Attrs<P> = (RawProps & P) | ({} extends P ? null : never)
type VfmAttrs<P> = (RawProps & P) | ({} extends P ? InstanceType<typeof VueFinalModal>['$props'] : never)
interface UseModalOptionsConcreteComponent<P> { component?: ConcreteComponent<P>; attrs?: VfmAttrs<P> }
interface UseModalOptionsComponentOptions<P> { component?: ComponentOptions<P>; attrs?: VfmAttrs<P> }

interface UseModalOptionsConcreteComponent<P> { component: ConcreteComponent<P>; attrs?: Attrs<P> }
interface UseModalOptionsComponentOptions<P> { component: ComponentOptions<P>; attrs?: Attrs<P> }
interface UseModalOptionsBase { component: Raw<Component>; attrs?: Record<string, any> }
interface ModalSlotOptionsConcreteComponent<P> extends UseModalOptionsConcreteComponent<P> {}
interface ModalSlotOptionsComponentOptions<P> extends UseModalOptionsComponentOptions<P> {}
type ModalSlot = string | Component | UseModalOptionsBase
type SlotAttrs<P> = (RawProps & P) | ({} extends P ? null : never)
interface ModalSlotOptionsConcreteComponent<P> { component: ConcreteComponent<P>; attrs?: SlotAttrs<P> }
interface ModalSlotOptionsComponentOptions<P> { component: ComponentOptions<P>; attrs?: SlotAttrs<P> }

export interface ModalSlotOptions { component: Raw<Component>; attrs?: Record<string, any> }
export type ModalSlot = string | Component | ModalSlotOptions

export type UseModalOptionsSlots = {
slots?: string | Component | {
slots?: {
default: ModalSlot
[key: string]: ModalSlot
}
Expand All @@ -31,7 +34,7 @@ export type UseModalOptionsSlots = {
export type UseModalOptions = {
defaultModelValue?: boolean
context?: Vfm
component: Raw<Component>
component?: Raw<Component>
attrs?: Record<string, any>
} & UseModalOptionsSlots

Expand Down
57 changes: 46 additions & 11 deletions packages/vue-final-modal/src/useApi.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { isString, tryOnUnmounted } from '@vueuse/core'
import { computed, inject, markRaw, reactive, useAttrs } from 'vue'
import type { Component, Raw } from 'vue'
import VueFinalModal from './components/VueFinalModal/VueFinalModal.vue'
import type CoreModal from './components/CoreModal/CoreModal.vue'
import { internalVfmSymbol, vfmSymbol } from './injectionSymbols'
import type { ComponentProps, IOverloadedUseModalFn, InternalVfm, UseModalOptions, UseModalOptionsPrivate, UseModalOptionsSlots, UseModalReturnType, Vfm } from './Modal'

import type { ComponentProps, IOverloadedUseModalFn, InternalVfm, ModalSlot, ModalSlotOptions, UseModalOptions, UseModalOptionsPrivate, UseModalReturnType, Vfm } from './Modal'

/**
* Returns the vfm instance. Equivalent to using `$vfm` inside
Expand All @@ -20,12 +22,12 @@ export function useInternalVfm(): InternalVfm {
return inject(internalVfmSymbol)!
}

function withMarkRaw(options: Partial<UseModalOptions>) {
function withMarkRaw(options: Partial<UseModalOptions>, DefaultComponent: Component = VueFinalModal) {
const { component, slots: innerSlots, ...rest } = options

const slots = typeof innerSlots === 'undefined'
? {}
: Object.fromEntries<UseModalOptionsSlots['slots']>(Object.entries(innerSlots).map(([name, maybeComponent]) => {
: Object.fromEntries<ModalSlot>(Object.entries(innerSlots).map(([name, maybeComponent]) => {
if (isString(maybeComponent))
return [name, maybeComponent] as const

Expand All @@ -41,7 +43,7 @@ function withMarkRaw(options: Partial<UseModalOptions>) {

return {
...rest,
component: markRaw(component || VueFinalModal),
component: markRaw(component || DefaultComponent),
slots,
}
}
Expand Down Expand Up @@ -83,13 +85,21 @@ export const useModal: IOverloadedUseModalFn = function (_options: UseModalOptio
}

function patchOptions(_options: Partial<UseModalOptions>) {
const _patchOptions = withMarkRaw(_options)
if (_patchOptions?.attrs)
Object.assign(options.attrs || {}, _patchOptions.attrs)
if (_patchOptions?.component)
Object.assign(options.component || {}, _patchOptions.component)
if (_patchOptions?.slots)
Object.assign(options.slots || {}, _patchOptions.slots)
const { slots, ...rest } = withMarkRaw(_options, options.component)

// patch options.component and options.attrs
patchComponentOptions(options, rest)

// patch options.slots
if (slots) {
Object.entries(slots).forEach(([name, slot]) => {
const originSlot = options.slots![name]
if (isModalSlotOptions(originSlot) && isModalSlotOptions(slot))
patchComponentOptions(originSlot, slot)
else
options.slots![name] = slot
})
}
}

function destroy(): void {
Expand All @@ -115,6 +125,31 @@ export const useModal: IOverloadedUseModalFn = function (_options: UseModalOptio
return modal
}

function patchAttrs<T extends Record<string, any>>(attrs: T, newAttrs: Partial<T>): T {
Object.entries(newAttrs).forEach(([key, value]) => {
attrs[key as keyof T] = value
})

return attrs
}

type ComponentOptions = {
component?: Raw<Component>
attrs?: Record<string, any>
}

function patchComponentOptions(options: ComponentOptions, newOptions: ComponentOptions) {
if (newOptions.component)
options.component = newOptions.component

if (newOptions.attrs)
patchAttrs(options.attrs!, newOptions.attrs)
}

function isModalSlotOptions(value: any): value is ModalSlotOptions {
return 'component' in value || 'attrs' in value
}

export function pickModalProps(props: any, modalProps: any) {
return Object.keys(modalProps).reduce((acc, propName) => {
acc[propName] = props[propName]
Expand Down