Skip to content

Commit

Permalink
fix: should display type error when using useModal() #323 (#324)
Browse files Browse the repository at this point in the history
* fix: should display type error when using `useModal()` #323

* fix: resolve type issues

---------

Co-authored-by: Alex Liu <dsa1314@gmail.com>
  • Loading branch information
hunterliu1003 and Mini-ghost committed Feb 10, 2023
1 parent 2521c37 commit 2b9f2bb
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 70 deletions.
70 changes: 18 additions & 52 deletions packages/vue-final-modal/src/Modal.ts
@@ -1,91 +1,57 @@
import type { App, CSSProperties, Component, ComponentOptions, ComponentPublicInstance, ComputedRef, ConcreteComponent, Raw, Ref, VNodeProps } from 'vue'
import type { VueFinalModal } from '.'
import type { App, CSSProperties, Component, ComponentPublicInstance, ComputedRef, Raw, Ref, VNodeProps } from 'vue'

export type ComponentProps = ComponentPublicInstance['$props']

export type ModalId = number | string | symbol
export type StyleValue = string | CSSProperties | (string | CSSProperties)[]

type RawProps = VNodeProps & {
export interface Constructor<P = any> {
__isFragment?: never
__isTeleport?: never
__isSuspense?: never
new (...args: any[]): { $props: P }
}

export type RawProps = VNodeProps & {
// used to differ from a single VNode object as children
__v_isVNode?: never
// used to differ from Array children
[Symbol.iterator]?: never
} & Record<string, any>

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> }

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 = {
export type UseModalOptions<P> = {
defaultModelValue?: boolean
context?: Vfm
component?: Constructor<P>
attrs?: (RawProps & P) | ({} extends P ? null : never)
slots?: {
default: ModalSlot
[key: string]: ModalSlot
}
}

export type UseModalOptions = {
defaultModelValue?: boolean
context?: Vfm
component?: Raw<Component>
attrs?: Record<string, any>
} & UseModalOptionsSlots

export interface IOverloadedUseModalFn {
<P>(options: UseModalOptionsConcreteComponent<P> & UseModalOptionsSlots): UseModalReturnType
<P>(options: UseModalOptionsComponentOptions<P> & UseModalOptionsSlots): UseModalReturnType
<P>(options:
| UseModalOptionsConcreteComponent<P> & UseModalOptionsSlots
| UseModalOptionsComponentOptions<P> & UseModalOptionsSlots
| UseModalOptions
): UseModalReturnType
}

interface IOverloadedPatchOptionsFn {
<P>(options: UseModalOptionsConcreteComponent<P> & UseModalOptionsSlots): void
<P>(options: UseModalOptionsComponentOptions<P> & UseModalOptionsSlots): void
<P>(options:
| UseModalOptionsConcreteComponent<P> & UseModalOptionsSlots
| UseModalOptionsComponentOptions<P> & UseModalOptionsSlots
| Omit<UseModalOptions, 'defaultModelValue' | 'context'>
): void
}

interface IOverloadedUseModalSlotFn {
<P>(options: ModalSlotOptionsConcreteComponent<P>): ModalSlot
<P>(options: ModalSlotOptionsComponentOptions<P>): ModalSlot
<P>(options: ModalSlotOptionsConcreteComponent<P> | ModalSlotOptionsComponentOptions<P> | ModalSlot): ModalSlot
}

export const useModalSlot: IOverloadedUseModalSlotFn = (options: ModalSlot): ModalSlot => options

export type UseModalOptionsPrivate = {
id: symbol
modelValue: boolean
resolveOpened: () => void
resolveClosed: () => void
}

export interface UseModalReturnType {
options: UseModalOptions & UseModalOptionsPrivate
export interface UseModalReturnType<P> {
options: UseModalOptions<P> & UseModalOptionsPrivate
open: () => Promise<string>
close: () => Promise<string>
patchOptions: IOverloadedPatchOptionsFn
patchOptions: (options: Partial<Omit<UseModalOptions<P>, 'defaultModelValue' | 'context'>>) => void
destroy: () => void
}

export type Vfm = {
install(app: App): void
modals: ComputedRef<Modal>[]
openedModals: ComputedRef<Modal>[]
dynamicModals: (UseModalOptions & UseModalOptionsPrivate)[]
dynamicModals: (UseModalOptions<any> & UseModalOptionsPrivate)[]
modalsContainers: Ref<symbol[]>
get: (modalId: ModalId) => undefined | ComputedRef<Modal>
toggle: (modalId: ModalId, show?: boolean) => undefined | Promise<string>
Expand Down
Expand Up @@ -11,7 +11,7 @@ const _vfm = useInternalVfm()
const uid = Symbol('ModalsContainer')
const shouldMount = computed(() => uid === vfm.modalsContainers.value?.[0])
const openedDynamicModals: Ref<(UseModalOptions & UseModalOptionsPrivate)[]> = shallowRef([])
const openedDynamicModals: Ref<(UseModalOptions<any> & UseModalOptionsPrivate)[]> = shallowRef([])
function syncOpenDynamicModals() {
openedDynamicModals.value = vfm.dynamicModals.filter(modal => modal.modelValue)
Expand Down
2 changes: 1 addition & 1 deletion packages/vue-final-modal/src/plugin.ts
Expand Up @@ -6,7 +6,7 @@ import type { InternalVfm, Modal, ModalId, UseModalOptions, UseModalOptionsPriva
export function createVfm() {
const modals: ComputedRef<Modal>[] = shallowReactive([])
const openedModals: ComputedRef<Modal>[] = shallowReactive([])
const dynamicModals: (UseModalOptions & UseModalOptionsPrivate)[] = shallowReactive([])
const dynamicModals: (UseModalOptions<any> & UseModalOptionsPrivate)[] = shallowReactive([])
const modalsContainers = ref<symbol[]>([])

const vfm: Vfm = markRaw({
Expand Down
37 changes: 21 additions & 16 deletions packages/vue-final-modal/src/useApi.ts
@@ -1,11 +1,11 @@
import { isString, tryOnUnmounted } from '@vueuse/core'
import { computed, getCurrentInstance, inject, markRaw, reactive, useAttrs } from 'vue'
import type { Component, Raw } from 'vue'
import type { Component } 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, ModalSlot, ModalSlotOptions, UseModalOptions, UseModalOptionsPrivate, UseModalReturnType, Vfm } from './Modal'
import type { ComponentProps, Constructor, InternalVfm, ModalSlot, ModalSlotOptions, RawProps, UseModalOptions, UseModalOptionsPrivate, UseModalReturnType, Vfm } from './Modal'

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

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

const slots = typeof innerSlots === 'undefined'
Expand All @@ -43,23 +43,23 @@ function withMarkRaw(options: Partial<UseModalOptions>, DefaultComponent: Compon

return {
...rest,
component: markRaw(component || DefaultComponent),
component: markRaw(component || DefaultComponent) as Constructor<P>,
slots,
}
}

/**
* Create a dynamic modal.
*/
export const useModal: IOverloadedUseModalFn = function (_options: UseModalOptions): UseModalReturnType {
export function useModal<P = InstanceType<typeof VueFinalModal>['$props']>(_options: UseModalOptions<P>): UseModalReturnType<P> {
const options = reactive({
id: Symbol('useModal'),
modelValue: !!_options?.defaultModelValue,
resolveOpened: () => {},
resolveClosed: () => {},
resolveOpened: () => { },
resolveClosed: () => { },
attrs: {},
...withMarkRaw(_options),
}) as UseModalOptions & UseModalOptionsPrivate
...withMarkRaw<P>(_options),
}) as UseModalOptions<P> & UseModalOptionsPrivate

if (!options.context) {
const currentInstance = getCurrentInstance()
Expand Down Expand Up @@ -89,7 +89,7 @@ export const useModal: IOverloadedUseModalFn = function (_options: UseModalOptio
})
}

function patchOptions(_options: Partial<UseModalOptions>) {
function patchOptions(_options: Partial<Omit<UseModalOptions<P>, 'defaultModelValue' | 'context'>>) {
const { slots, ...rest } = withMarkRaw(_options, options.component)

// patch options.component and options.attrs
Expand Down Expand Up @@ -132,6 +132,13 @@ export const useModal: IOverloadedUseModalFn = function (_options: UseModalOptio
return modal
}

export function useModalSlot<P>(options: {
component: Constructor<P>
attrs?: (RawProps & P) | ({} extends P ? null : never)
}) {
return options
}

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
Expand All @@ -140,12 +147,10 @@ function patchAttrs<T extends Record<string, any>>(attrs: T, newAttrs: Partial<T
return attrs
}

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

function patchComponentOptions(options: ComponentOptions | ModalSlotOptions, newOptions: ComponentOptions | ModalSlotOptions) {
function patchComponentOptions<P>(
options: Omit<UseModalOptions<P>, 'defaultModelValue' | 'context'> | ModalSlotOptions,
newOptions: Partial<Omit<UseModalOptions<P>, 'defaultModelValue' | 'context'>> | ModalSlotOptions,
) {
if (newOptions.component)
options.component = newOptions.component

Expand Down

0 comments on commit 2b9f2bb

Please sign in to comment.