Skip to content

Commit 216222b

Browse files
feat: improve patchOptions implement (#310)
* refactor: improve `patchOptions` implement * refactor: reduce code * refactor: respect the original code * refactor: respect the original code * fix: resolve error when original `attrs` is `undefined` * fix: resolve type error * refactor: improve `patchOptions` implement * chore: code review * chore: cleanup * fix: options.component can be optional and use VueFinalModal by default * test: add useModal test cases --------- Co-authored-by: Hunter <[email protected]>
1 parent 4f185d0 commit 216222b

File tree

4 files changed

+102
-35
lines changed

4 files changed

+102
-35
lines changed
Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,42 @@
11
import TestUseModal from './TestUseModal.vue'
2-
import { createVfm } from '~/index'
2+
import { createVfm, useModal } from '~/index'
33

4-
it('renders the VueFinalModal', () => {
5-
cy.mount(TestUseModal, {
6-
global: {
7-
plugins: [createVfm()],
8-
},
4+
describe('Test useModal()', () => {
5+
it('Should be closed by default', () => {
6+
const vfm = createVfm()
7+
cy.mount(TestUseModal, {
8+
props: {
9+
run() {
10+
useModal({
11+
context: vfm,
12+
slots: {
13+
default: 'Hello World!',
14+
},
15+
})
16+
},
17+
},
18+
global: { plugins: [vfm] },
19+
})
20+
cy.contains('Hello World!').should('not.exist')
21+
})
22+
23+
it('Should be opened by given defaultModelValue: true', () => {
24+
const vfm = createVfm()
25+
cy.mount(TestUseModal, {
26+
props: {
27+
run() {
28+
useModal({
29+
context: vfm,
30+
defaultModelValue: true,
31+
slots: {
32+
default: 'Hello World!',
33+
},
34+
})
35+
},
36+
},
37+
global: { plugins: [vfm] },
38+
})
39+
40+
cy.contains('Hello World!')
941
})
10-
cy.contains('Hello World!')
1142
})

packages/vue-final-modal/cypress/components/TestUseModal.vue

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
<script lang="ts" setup>
2-
import { ModalsContainer, VueFinalModal, useModal } from '~/index'
2+
import { ModalsContainer } from '~/index'
33
4-
useModal({
5-
defaultModelValue: true,
6-
component: VueFinalModal,
7-
slots: {
8-
default: 'Hello World!',
9-
},
10-
})
4+
const props = defineProps<{
5+
run: () => void
6+
}>()
7+
8+
props.run()
119
</script>
1210

1311
<template>

packages/vue-final-modal/src/Modal.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { App, CSSProperties, Component, ComponentOptions, ComponentPublicInstance, ComputedRef, ConcreteComponent, Raw, Ref, VNodeProps } from 'vue'
2+
import type { VueFinalModal } from '.'
23

34
export type ComponentProps = ComponentPublicInstance['$props']
45

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

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

17-
interface UseModalOptionsConcreteComponent<P> { component: ConcreteComponent<P>; attrs?: Attrs<P> }
18-
interface UseModalOptionsComponentOptions<P> { component: ComponentOptions<P>; attrs?: Attrs<P> }
19-
interface UseModalOptionsBase { component: Raw<Component>; attrs?: Record<string, any> }
20-
interface ModalSlotOptionsConcreteComponent<P> extends UseModalOptionsConcreteComponent<P> {}
21-
interface ModalSlotOptionsComponentOptions<P> extends UseModalOptionsComponentOptions<P> {}
22-
type ModalSlot = string | Component | UseModalOptionsBase
20+
type SlotAttrs<P> = (RawProps & P) | ({} extends P ? null : never)
21+
interface ModalSlotOptionsConcreteComponent<P> { component: ConcreteComponent<P>; attrs?: SlotAttrs<P> }
22+
interface ModalSlotOptionsComponentOptions<P> { component: ComponentOptions<P>; attrs?: SlotAttrs<P> }
23+
24+
export interface ModalSlotOptions { component: Raw<Component>; attrs?: Record<string, any> }
25+
export type ModalSlot = string | Component | ModalSlotOptions
2326

2427
export type UseModalOptionsSlots = {
25-
slots?: string | Component | {
28+
slots?: {
2629
default: ModalSlot
2730
[key: string]: ModalSlot
2831
}
@@ -31,7 +34,7 @@ export type UseModalOptionsSlots = {
3134
export type UseModalOptions = {
3235
defaultModelValue?: boolean
3336
context?: Vfm
34-
component: Raw<Component>
37+
component?: Raw<Component>
3538
attrs?: Record<string, any>
3639
} & UseModalOptionsSlots
3740

packages/vue-final-modal/src/useApi.ts

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { isString, tryOnUnmounted } from '@vueuse/core'
22
import { computed, inject, markRaw, reactive, useAttrs } from 'vue'
3+
import type { Component, Raw } from 'vue'
34
import VueFinalModal from './components/VueFinalModal/VueFinalModal.vue'
45
import type CoreModal from './components/CoreModal/CoreModal.vue'
56
import { internalVfmSymbol, vfmSymbol } from './injectionSymbols'
6-
import type { ComponentProps, IOverloadedUseModalFn, InternalVfm, UseModalOptions, UseModalOptionsPrivate, UseModalOptionsSlots, UseModalReturnType, Vfm } from './Modal'
7+
8+
import type { ComponentProps, IOverloadedUseModalFn, InternalVfm, ModalSlot, ModalSlotOptions, UseModalOptions, UseModalOptionsPrivate, UseModalReturnType, Vfm } from './Modal'
79

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

23-
function withMarkRaw(options: Partial<UseModalOptions>) {
25+
function withMarkRaw(options: Partial<UseModalOptions>, DefaultComponent: Component = VueFinalModal) {
2426
const { component, slots: innerSlots, ...rest } = options
2527

2628
const slots = typeof innerSlots === 'undefined'
2729
? {}
28-
: Object.fromEntries<UseModalOptionsSlots['slots']>(Object.entries(innerSlots).map(([name, maybeComponent]) => {
30+
: Object.fromEntries<ModalSlot>(Object.entries(innerSlots).map(([name, maybeComponent]) => {
2931
if (isString(maybeComponent))
3032
return [name, maybeComponent] as const
3133

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

4244
return {
4345
...rest,
44-
component: markRaw(component || VueFinalModal),
46+
component: markRaw(component || DefaultComponent),
4547
slots,
4648
}
4749
}
@@ -83,13 +85,21 @@ export const useModal: IOverloadedUseModalFn = function (_options: UseModalOptio
8385
}
8486

8587
function patchOptions(_options: Partial<UseModalOptions>) {
86-
const _patchOptions = withMarkRaw(_options)
87-
if (_patchOptions?.attrs)
88-
Object.assign(options.attrs || {}, _patchOptions.attrs)
89-
if (_patchOptions?.component)
90-
Object.assign(options.component || {}, _patchOptions.component)
91-
if (_patchOptions?.slots)
92-
Object.assign(options.slots || {}, _patchOptions.slots)
88+
const { slots, ...rest } = withMarkRaw(_options, options.component)
89+
90+
// patch options.component and options.attrs
91+
patchComponentOptions(options, rest)
92+
93+
// patch options.slots
94+
if (slots) {
95+
Object.entries(slots).forEach(([name, slot]) => {
96+
const originSlot = options.slots![name]
97+
if (isModalSlotOptions(originSlot) && isModalSlotOptions(slot))
98+
patchComponentOptions(originSlot, slot)
99+
else
100+
options.slots![name] = slot
101+
})
102+
}
93103
}
94104

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

128+
function patchAttrs<T extends Record<string, any>>(attrs: T, newAttrs: Partial<T>): T {
129+
Object.entries(newAttrs).forEach(([key, value]) => {
130+
attrs[key as keyof T] = value
131+
})
132+
133+
return attrs
134+
}
135+
136+
type ComponentOptions = {
137+
component?: Raw<Component>
138+
attrs?: Record<string, any>
139+
}
140+
141+
function patchComponentOptions(options: ComponentOptions, newOptions: ComponentOptions) {
142+
if (newOptions.component)
143+
options.component = newOptions.component
144+
145+
if (newOptions.attrs)
146+
patchAttrs(options.attrs!, newOptions.attrs)
147+
}
148+
149+
function isModalSlotOptions(value: any): value is ModalSlotOptions {
150+
return 'component' in value || 'attrs' in value
151+
}
152+
118153
export function pickModalProps(props: any, modalProps: any) {
119154
return Object.keys(modalProps).reduce((acc, propName) => {
120155
acc[propName] = props[propName]

0 commit comments

Comments
 (0)