From a6170aef84135da4493ec6adbc5a47b7e46e662f Mon Sep 17 00:00:00 2001 From: Alex Liu Date: Wed, 18 Jan 2023 15:35:58 +0800 Subject: [PATCH 01/11] refactor: improve `patchOptions` implement --- packages/vue-final-modal/src/useApi.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/vue-final-modal/src/useApi.ts b/packages/vue-final-modal/src/useApi.ts index 17c31454..fd216aa9 100644 --- a/packages/vue-final-modal/src/useApi.ts +++ b/packages/vue-final-modal/src/useApi.ts @@ -49,17 +49,20 @@ function withMarkRaw< } } +function assign (value: T, oldValue: T = {} as T) { + return Object.assign(oldValue || {}, value) +} + /** * Define a dynamic modal. */ function defineModal< ModalProps extends ComponentProps, - DefaultSlotProps extends ComponentProps = {}, + DefaultSlotProps extends ComponentProps, >(_options: UseModalOptions): UseModalReturnType { const options = reactive({ id: Symbol('useModal'), modelValue: !!_options?.defaultModelValue, - attrs: {}, ...withMarkRaw(_options), }) as UseModalOptionsPrivate @@ -86,14 +89,15 @@ function defineModal< }) } - function patchOptions(_options: Partial>) { - 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) + function patchOptions>>(_options: PatchOPtions) { + const markRawPatchOptions = withMarkRaw(_options) + const patchKeys = ['attrs', 'component', 'slots'] as const + + patchKeys.forEach(key => { + if(markRawPatchOptions[key] == null) return + if(key === 'component') return options[key] = markRawPatchOptions[key] + assign(options[key] || {}, markRawPatchOptions[key]) + }) } function destroy(): void { From 3621396c8afb469f56ff02a7cdab61310a18f1c1 Mon Sep 17 00:00:00 2001 From: Alex Liu Date: Wed, 18 Jan 2023 15:39:30 +0800 Subject: [PATCH 02/11] refactor: reduce code --- packages/vue-final-modal/src/useApi.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/vue-final-modal/src/useApi.ts b/packages/vue-final-modal/src/useApi.ts index fd216aa9..fb9f8e9a 100644 --- a/packages/vue-final-modal/src/useApi.ts +++ b/packages/vue-final-modal/src/useApi.ts @@ -49,10 +49,6 @@ function withMarkRaw< } } -function assign (value: T, oldValue: T = {} as T) { - return Object.assign(oldValue || {}, value) -} - /** * Define a dynamic modal. */ @@ -96,7 +92,7 @@ function defineModal< patchKeys.forEach(key => { if(markRawPatchOptions[key] == null) return if(key === 'component') return options[key] = markRawPatchOptions[key] - assign(options[key] || {}, markRawPatchOptions[key]) + Object.assign(options[key] || {}, markRawPatchOptions[key]) }) } From 9cb4c0326dab6f605cb632f91ec3be278bf9e45f Mon Sep 17 00:00:00 2001 From: Alex Liu Date: Wed, 18 Jan 2023 15:41:44 +0800 Subject: [PATCH 03/11] refactor: respect the original code --- packages/vue-final-modal/src/useApi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vue-final-modal/src/useApi.ts b/packages/vue-final-modal/src/useApi.ts index fb9f8e9a..b8a108cd 100644 --- a/packages/vue-final-modal/src/useApi.ts +++ b/packages/vue-final-modal/src/useApi.ts @@ -54,7 +54,7 @@ function withMarkRaw< */ function defineModal< ModalProps extends ComponentProps, - DefaultSlotProps extends ComponentProps, + DefaultSlotProps extends ComponentProps = {}, >(_options: UseModalOptions): UseModalReturnType { const options = reactive({ id: Symbol('useModal'), From 024568fee6a4a4330a6a1ec13e423e7c2e9d5c56 Mon Sep 17 00:00:00 2001 From: Alex Liu Date: Wed, 18 Jan 2023 15:43:02 +0800 Subject: [PATCH 04/11] refactor: respect the original code --- packages/vue-final-modal/src/useApi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vue-final-modal/src/useApi.ts b/packages/vue-final-modal/src/useApi.ts index b8a108cd..f6bea0a0 100644 --- a/packages/vue-final-modal/src/useApi.ts +++ b/packages/vue-final-modal/src/useApi.ts @@ -85,7 +85,7 @@ function defineModal< }) } - function patchOptions>>(_options: PatchOPtions) { + function patchOptions(_options: Partial>) { const markRawPatchOptions = withMarkRaw(_options) const patchKeys = ['attrs', 'component', 'slots'] as const From 2c3185f987e5bd13032b2021f9ca9906dc255381 Mon Sep 17 00:00:00 2001 From: Alex Liu Date: Wed, 18 Jan 2023 15:55:54 +0800 Subject: [PATCH 05/11] fix: resolve error when original `attrs` is `undefined` --- packages/vue-final-modal/src/useApi.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/vue-final-modal/src/useApi.ts b/packages/vue-final-modal/src/useApi.ts index f6bea0a0..1b7e01a6 100644 --- a/packages/vue-final-modal/src/useApi.ts +++ b/packages/vue-final-modal/src/useApi.ts @@ -59,6 +59,7 @@ function defineModal< const options = reactive({ id: Symbol('useModal'), modelValue: !!_options?.defaultModelValue, + attrs: {}, ...withMarkRaw(_options), }) as UseModalOptionsPrivate From 3ab268764454be1c59a679849339dff06f6521e9 Mon Sep 17 00:00:00 2001 From: Alex Liu Date: Thu, 26 Jan 2023 19:03:16 +0800 Subject: [PATCH 06/11] fix: resolve type error --- packages/vue-final-modal/src/Modal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vue-final-modal/src/Modal.ts b/packages/vue-final-modal/src/Modal.ts index 2fd971f1..81d1d3e3 100644 --- a/packages/vue-final-modal/src/Modal.ts +++ b/packages/vue-final-modal/src/Modal.ts @@ -22,7 +22,7 @@ interface ModalSlotOptionsComponentOptions

extends UseModalOptionsComponentOp type ModalSlot = string | Component | UseModalOptionsBase export type UseModalOptionsSlots = { - slots?: string | Component | { + slots?: { default: ModalSlot [key: string]: ModalSlot } From 7b3b18e7bdb738b1852d78e698766efc506b4908 Mon Sep 17 00:00:00 2001 From: Alex Liu Date: Thu, 26 Jan 2023 19:05:14 +0800 Subject: [PATCH 07/11] refactor: improve `patchOptions` implement --- packages/vue-final-modal/src/Modal.ts | 5 +-- packages/vue-final-modal/src/useApi.ts | 48 +++++++++++++++++++------- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/packages/vue-final-modal/src/Modal.ts b/packages/vue-final-modal/src/Modal.ts index 81d1d3e3..79b1496b 100644 --- a/packages/vue-final-modal/src/Modal.ts +++ b/packages/vue-final-modal/src/Modal.ts @@ -16,10 +16,11 @@ type Attrs

= (RawProps & P) | ({} extends P ? null : never) interface UseModalOptionsConcreteComponent

{ component: ConcreteComponent

; attrs?: Attrs

} interface UseModalOptionsComponentOptions

{ component: ComponentOptions

; attrs?: Attrs

} -interface UseModalOptionsBase { component: Raw; attrs?: Record } interface ModalSlotOptionsConcreteComponent

extends UseModalOptionsConcreteComponent

{} interface ModalSlotOptionsComponentOptions

extends UseModalOptionsComponentOptions

{} -type ModalSlot = string | Component | UseModalOptionsBase + +export interface ModalSlotOptions { component: Raw; attrs?: Record } +export type ModalSlot = string | Component | ModalSlotOptions export type UseModalOptionsSlots = { slots?: { diff --git a/packages/vue-final-modal/src/useApi.ts b/packages/vue-final-modal/src/useApi.ts index e5a13609..b32432c5 100644 --- a/packages/vue-final-modal/src/useApi.ts +++ b/packages/vue-final-modal/src/useApi.ts @@ -1,9 +1,11 @@ import { isString, tryOnUnmounted } from '@vueuse/core' import { computed, inject, markRaw, reactive, useAttrs } 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, 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 @@ -20,12 +22,12 @@ export function useInternalVfm(): InternalVfm { return inject(internalVfmSymbol)! } -function withMarkRaw(options: Partial) { +function withMarkRaw(options: Partial, DefaultComponent: Component = VueFinalModal) { const { component, slots: innerSlots, ...rest } = options const slots = typeof innerSlots === 'undefined' ? {} - : Object.fromEntries(Object.entries(innerSlots).map(([name, maybeComponent]) => { + : Object.fromEntries(Object.entries(innerSlots).map(([name, maybeComponent]) => { if (isString(maybeComponent)) return [name, maybeComponent] as const @@ -41,7 +43,7 @@ function withMarkRaw(options: Partial) { return { ...rest, - component: markRaw(component || VueFinalModal), + component: markRaw(component || DefaultComponent), slots, } } @@ -82,18 +84,40 @@ export const useModal: IOverloadedUseModalFn = function (_options: UseModalOptio }) } + function patchAttrs>(attrs: T, newAttrs: Partial): T { + Object.entries(newAttrs).forEach(([key, value]) => { + attrs[key as keyof T] = value + }) + + return attrs + } + + function isModalSlotOptions(value: any): value is ModalSlotOptions { + return 'component' in value || 'attrs' in value + } + function patchOptions(_options: Partial) { - const markRawPatchOptions = withMarkRaw(_options) - const patchKeys = ['attrs', 'component', 'slots'] as const + const { slots, ...rest } = withMarkRaw(_options, options.component) - patchKeys.forEach((key) => { - if (markRawPatchOptions[key] == null) - return + if (rest.component) + options.component = rest.component + + if (rest.attrs) + patchAttrs(options.attrs!, rest.attrs) + + slots && Object.entries(slots).forEach(([name, slot]) => { + const oldSlot = options.slots![name] + if (isModalSlotOptions(oldSlot) && isModalSlotOptions(slot)) { + if (slot.component) + (oldSlot.component = slot.component) - if (key === 'component') - return options[key] = markRawPatchOptions[key] + if (slot.attrs) + patchAttrs(oldSlot.attrs!, slot.attrs) + + return + } - Object.assign(options[key] || {}, markRawPatchOptions[key]) + options.slots![name] = slot }) } From 3fe1b8da1878ef50c60f96b318f8c3f96a6e0bb3 Mon Sep 17 00:00:00 2001 From: Hunter Date: Thu, 26 Jan 2023 19:19:44 +0800 Subject: [PATCH 08/11] chore: code review --- packages/vue-final-modal/src/useApi.ts | 56 ++++++++++++++------------ 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/packages/vue-final-modal/src/useApi.ts b/packages/vue-final-modal/src/useApi.ts index b32432c5..1d4476c1 100644 --- a/packages/vue-final-modal/src/useApi.ts +++ b/packages/vue-final-modal/src/useApi.ts @@ -84,41 +84,33 @@ export const useModal: IOverloadedUseModalFn = function (_options: UseModalOptio }) } - function patchAttrs>(attrs: T, newAttrs: Partial): T { - Object.entries(newAttrs).forEach(([key, value]) => { - attrs[key as keyof T] = value - }) - - return attrs - } - - function isModalSlotOptions(value: any): value is ModalSlotOptions { - return 'component' in value || 'attrs' in value - } - function patchOptions(_options: Partial) { const { slots, ...rest } = withMarkRaw(_options, options.component) + // patch options.component if (rest.component) options.component = rest.component + // patch options.attrs if (rest.attrs) patchAttrs(options.attrs!, rest.attrs) - slots && Object.entries(slots).forEach(([name, slot]) => { - const oldSlot = options.slots![name] - if (isModalSlotOptions(oldSlot) && isModalSlotOptions(slot)) { - if (slot.component) - (oldSlot.component = slot.component) - - if (slot.attrs) - patchAttrs(oldSlot.attrs!, slot.attrs) - - return - } - - options.slots![name] = slot - }) + // patch options.slots + if (slots) { + Object.entries(slots).forEach(([name, slot]) => { + const originSlot = options.slots![name] + if (isModalSlotOptions(originSlot) && isModalSlotOptions(slot)) { + if (slot.component) + (originSlot.component = slot.component) + + if (slot.attrs) + patchAttrs(originSlot.attrs!, slot.attrs) + } + else { + options.slots![name] = slot + } + }) + } } function destroy(): void { @@ -144,6 +136,18 @@ export const useModal: IOverloadedUseModalFn = function (_options: UseModalOptio return modal } +function patchAttrs>(attrs: T, newAttrs: Partial): T { + Object.entries(newAttrs).forEach(([key, value]) => { + attrs[key as keyof T] = value + }) + + return 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] From 93ced4525cc25b53d178242e9bf549185476118d Mon Sep 17 00:00:00 2001 From: Hunter Date: Thu, 26 Jan 2023 19:28:29 +0800 Subject: [PATCH 09/11] chore: cleanup --- packages/vue-final-modal/src/useApi.ts | 36 ++++++++++++++------------ 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/packages/vue-final-modal/src/useApi.ts b/packages/vue-final-modal/src/useApi.ts index 1d4476c1..250f4f13 100644 --- a/packages/vue-final-modal/src/useApi.ts +++ b/packages/vue-final-modal/src/useApi.ts @@ -1,6 +1,6 @@ import { isString, tryOnUnmounted } from '@vueuse/core' import { computed, inject, markRaw, reactive, useAttrs } from 'vue' -import type { Component } 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' @@ -87,28 +87,17 @@ export const useModal: IOverloadedUseModalFn = function (_options: UseModalOptio function patchOptions(_options: Partial) { const { slots, ...rest } = withMarkRaw(_options, options.component) - // patch options.component - if (rest.component) - options.component = rest.component - - // patch options.attrs - if (rest.attrs) - patchAttrs(options.attrs!, rest.attrs) + // 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)) { - if (slot.component) - (originSlot.component = slot.component) - - if (slot.attrs) - patchAttrs(originSlot.attrs!, slot.attrs) - } - else { + if (isModalSlotOptions(originSlot) && isModalSlotOptions(slot)) + patchComponentOptions(originSlot, slot) + else options.slots![name] = slot - } }) } } @@ -144,6 +133,19 @@ function patchAttrs>(attrs: T, newAttrs: Partial + attrs?: Record +} + +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 } From 25a7f87df89f9c138c1235995323b35b24c4a04d Mon Sep 17 00:00:00 2001 From: Hunter Date: Thu, 26 Jan 2023 20:21:09 +0800 Subject: [PATCH 10/11] fix: options.component can be optional and use VueFinalModal by default --- packages/vue-final-modal/src/Modal.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/vue-final-modal/src/Modal.ts b/packages/vue-final-modal/src/Modal.ts index 79b1496b..8065fe7f 100644 --- a/packages/vue-final-modal/src/Modal.ts +++ b/packages/vue-final-modal/src/Modal.ts @@ -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'] @@ -12,12 +13,13 @@ type RawProps = VNodeProps & { [Symbol.iterator]?: never } & Record -type Attrs

= (RawProps & P) | ({} extends P ? null : never) +type VfmAttrs

= (RawProps & P) | ({} extends P ? InstanceType['$props'] : never) +interface UseModalOptionsConcreteComponent

{ component?: ConcreteComponent

; attrs?: VfmAttrs

} +interface UseModalOptionsComponentOptions

{ component?: ComponentOptions

; attrs?: VfmAttrs

} -interface UseModalOptionsConcreteComponent

{ component: ConcreteComponent

; attrs?: Attrs

} -interface UseModalOptionsComponentOptions

{ component: ComponentOptions

; attrs?: Attrs

} -interface ModalSlotOptionsConcreteComponent

extends UseModalOptionsConcreteComponent

{} -interface ModalSlotOptionsComponentOptions

extends UseModalOptionsComponentOptions

{} +type SlotAttrs

= (RawProps & P) | ({} extends P ? null : never) +interface ModalSlotOptionsConcreteComponent

{ component: ConcreteComponent

; attrs?: SlotAttrs

} +interface ModalSlotOptionsComponentOptions

{ component: ComponentOptions

; attrs?: SlotAttrs

} export interface ModalSlotOptions { component: Raw; attrs?: Record } export type ModalSlot = string | Component | ModalSlotOptions @@ -32,7 +34,7 @@ export type UseModalOptionsSlots = { export type UseModalOptions = { defaultModelValue?: boolean context?: Vfm - component: Raw + component?: Raw attrs?: Record } & UseModalOptionsSlots From fb0343d6a244d964989608c843d30a30a444d152 Mon Sep 17 00:00:00 2001 From: Hunter Date: Thu, 26 Jan 2023 20:21:28 +0800 Subject: [PATCH 11/11] test: add useModal test cases --- .../cypress/components/TestUseModal.spec.ts | 45 ++++++++++++++++--- .../cypress/components/TestUseModal.vue | 14 +++--- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/packages/vue-final-modal/cypress/components/TestUseModal.spec.ts b/packages/vue-final-modal/cypress/components/TestUseModal.spec.ts index 4bb1f722..1ab92368 100644 --- a/packages/vue-final-modal/cypress/components/TestUseModal.spec.ts +++ b/packages/vue-final-modal/cypress/components/TestUseModal.spec.ts @@ -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!') }) diff --git a/packages/vue-final-modal/cypress/components/TestUseModal.vue b/packages/vue-final-modal/cypress/components/TestUseModal.vue index 8d5574d4..3b26ad6e 100644 --- a/packages/vue-final-modal/cypress/components/TestUseModal.vue +++ b/packages/vue-final-modal/cypress/components/TestUseModal.vue @@ -1,13 +1,11 @@