Skip to content

Commit

Permalink
fix: fixed generic component type in useModal composable
Browse files Browse the repository at this point in the history
  • Loading branch information
hunterliu1003 committed Nov 29, 2023
1 parent 401d855 commit 8304d63
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 37 deletions.
39 changes: 21 additions & 18 deletions packages/vue-final-modal/src/Modal.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
import type { App, CSSProperties, Component, ComponentPublicInstance, ComputedRef, Raw, Ref, VNodeProps } from 'vue'

export type ComponentProps = ComponentPublicInstance['$props']
import type { App, CSSProperties, ComputedRef, FunctionalComponent, Raw, Ref } from 'vue'

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

export interface Constructor<P = any> {
/** A fake Component Constructor that is only used for extracting `$props` as type `P` */
type 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>
export interface ModalSlotOptions { component: Raw<ComponentType>; attrs?: Record<string, any> }
export type ModalSlot = string | ComponentType | ModalSlotOptions

type ComponentConstructor = (abstract new (...args: any) => any)
/** Including both generic and non-generic vue components */
export type ComponentType = ComponentConstructor | FunctionalComponent<any, any>

export interface ModalSlotOptions { component: Raw<Component>; attrs?: Record<string, any> }
export type ModalSlot = string | Component | ModalSlotOptions
type FunctionalComponentProps<T> = T extends FunctionalComponent<infer P> ? P : Record<any, any>
type NonGenericComponentProps<T> = T extends Constructor<infer P> ? P : Record<any, any>
export type ComponentProps<T extends ComponentType> =
T extends ComponentConstructor
? NonGenericComponentProps<T>
: FunctionalComponentProps<T>

export type UseModalOptions<P> = {
export type UseModalOptions<T extends ComponentType> = {
defaultModelValue?: boolean
keepAlive?: boolean
component?: Constructor<P>
attrs?: (RawProps & P) | ({} extends P ? null : never)
component?: T
attrs?: ComponentProps<T>
slots?: {
[key: string]: ModalSlot
}
Expand All @@ -39,11 +42,11 @@ export type UseModalOptionsPrivate = {
resolveClosed: () => void
}

export interface UseModalReturnType<P> {
options: UseModalOptions<P> & UseModalOptionsPrivate
export interface UseModalReturnType<T extends ComponentType> {
options: UseModalOptions<T> & UseModalOptionsPrivate
open: () => Promise<string>
close: () => Promise<string>
patchOptions: (options: Partial<UseModalOptions<P>>) => void
patchOptions: (options: Partial<UseModalOptions<T>>) => void
destroy: () => void
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ onBeforeUnmount(() => {
:key="modal.id"
v-bind="{
displayDirective: modal?.keepAlive ? 'show' : undefined,
...modal.attrs,
...(typeof modal.attrs === 'object' ? modal.attrs : {}),
}"
v-model="modal.modelValue"
@closed="() => _vfm.resolvedClosed?.(index)"
Expand Down
31 changes: 15 additions & 16 deletions packages/vue-final-modal/src/useApi.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { computed, inject, markRaw, nextTick, reactive, useAttrs } from 'vue'
import { tryOnUnmounted } from '@vueuse/core'
import type { Component } from 'vue'
import VueFinalModal from './components/VueFinalModal/VueFinalModal.vue'
import type CoreModal from './components/CoreModal/CoreModal.vue'
import { internalVfmSymbol } from './injectionSymbols'

import type { ComponentProps, Constructor, InternalVfm, ModalSlot, ModalSlotOptions, RawProps, UseModalOptions, UseModalOptionsPrivate, UseModalReturnType, Vfm } from './Modal'
import type { ComponentProps, ComponentType, InternalVfm, ModalSlot, ModalSlotOptions, UseModalOptions, UseModalOptionsPrivate, UseModalReturnType, Vfm } from './Modal'
import { activeVfm, getActiveVfm } from './plugin'
import { isString } from '~/utils'

Expand Down Expand Up @@ -34,7 +33,7 @@ export function useInternalVfm(): InternalVfm {
return inject(internalVfmSymbol)!
}

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

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

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

/**
* Create a dynamic modal.
*/
export function useModal<P = InstanceType<typeof VueFinalModal>['$props']>(_options: UseModalOptions<P>): UseModalReturnType<P> {
export function useModal<T extends ComponentType = typeof VueFinalModal>(_options: UseModalOptions<T>): UseModalReturnType<T> {
const options = reactive({
id: Symbol('useModal'),
modelValue: !!_options?.defaultModelValue,
resolveOpened: () => { },
resolveClosed: () => { },
attrs: {},
...withMarkRaw<P>(_options),
}) as UseModalOptions<P> & UseModalOptionsPrivate
...withMarkRaw<T>(_options),
}) as UseModalOptions<T> & UseModalOptionsPrivate
tryOnUnmounted(() => {
if (!options?.keepAlive)
destroy()
Expand Down Expand Up @@ -122,7 +121,7 @@ export function useModal<P = InstanceType<typeof VueFinalModal>['$props']>(_opti
})
}

function patchOptions(_options: Partial<UseModalOptions<P>>) {
function patchOptions(_options: Partial<UseModalOptions<T>>) {
const { slots, ...rest } = withMarkRaw(_options, options.component)

if (_options.defaultModelValue !== undefined)
Expand All @@ -147,9 +146,9 @@ export function useModal<P = InstanceType<typeof VueFinalModal>['$props']>(_opti
}
}

function patchComponentOptions<P>(
options: UseModalOptions<P> | ModalSlotOptions,
newOptions: Partial<UseModalOptions<P>> | ModalSlotOptions,
function patchComponentOptions<T extends ComponentType>(
options: UseModalOptions<T> | ModalSlotOptions,
newOptions: Partial<UseModalOptions<T>> | ModalSlotOptions,
) {
if (newOptions.component)
options.component = newOptions.component
Expand Down Expand Up @@ -182,9 +181,9 @@ export function useModal<P = InstanceType<typeof VueFinalModal>['$props']>(_opti
}
}

export function useModalSlot<P>(options: {
component: Constructor<P>
attrs?: (RawProps & P) | ({} extends P ? null : never)
export function useModalSlot<T extends ComponentType>(options: {
component: T
attrs?: ComponentProps<T>
}) {
return options
}
Expand Down Expand Up @@ -217,8 +216,8 @@ export function byPassAllModalEvents(emit?: InstanceType<typeof CoreModal>['$emi
}

export function useVfmAttrs(options: {
props: ComponentProps
modalProps: ComponentProps
props: Record<any, any>
modalProps: Record<any, any>
emit?: any
}) {
const { props, modalProps, emit } = options
Expand Down
3 changes: 1 addition & 2 deletions viteplay/src/components/VueFinalModal/Basic.example.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@ modal.open().then((res) => { console.log('res', res) })
modal.open().then((res) => { console.log('res', res) })
const modal1 = useModal({
keepAlive: true,
component: VueFinalModal,
attrs: {
// 'displayDirective': 'if',
'background': 'interactive',
'lockScroll': false,
'contentStyle': { backgroundColor: '#fff' },
'onUpdate:modelValue': function (val) {
// console.log('onUpdate:modelValue', val)
console.log('onUpdate:modelValue', val)
},
onClosed() { console.log('onClosed') },
onBeforeClose() { console.log('onBeforeClose') },
Expand Down

0 comments on commit 8304d63

Please sign in to comment.