Skip to content

fix: should display type error when using useModal() #323 #324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 10, 2023
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
70 changes: 18 additions & 52 deletions packages/vue-final-modal/src/Modal.ts
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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