Skip to content

Commit 3e16ac8

Browse files
Merge pull request #412 from vue-final/fix/405
Fix/405
2 parents 98a1e6f + eeaa41b commit 3e16ac8

File tree

16 files changed

+700
-366
lines changed

16 files changed

+700
-366
lines changed

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

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { App, CSSProperties, ComputedRef, FunctionalComponent, Raw, Ref } from 'vue'
1+
import type { App, CSSProperties, ComponentInternalInstance, FunctionalComponent, Raw, Ref } from 'vue'
22

33
export type ModalId = number | string | symbol
44
export type StyleValue = string | CSSProperties | (string | CSSProperties)[]
@@ -52,32 +52,22 @@ export interface UseModalReturnType<T extends ComponentType> {
5252

5353
export type Vfm = {
5454
install(app: App): void
55-
modals: ComputedRef<Modal>[]
56-
openedModals: ComputedRef<Modal>[]
57-
openedModalOverlays: ComputedRef<Modal>[]
55+
modals: ComponentInternalInstance[]
56+
openedModals: ComponentInternalInstance[]
57+
openedModalOverlays: ComponentInternalInstance[]
5858
dynamicModals: (UseModalOptions<any> & UseModalOptionsPrivate)[]
5959
modalsContainers: Ref<symbol[]>
60-
get: (modalId: ModalId) => undefined | ComputedRef<Modal>
60+
get: (modalId: ModalId) => undefined | ComponentInternalInstance
6161
toggle: (modalId: ModalId, show?: boolean) => undefined | Promise<string>
6262
open: (modalId: ModalId) => undefined | Promise<string>
6363
close: (modalId: ModalId) => undefined | Promise<string>
6464
closeAll: () => Promise<PromiseSettledResult<string>[]>
6565
}
6666

67-
export type InternalVfm = {
68-
openLastOverlay: () => Promise<void>
69-
moveToLastOpenedModals: (modal: ComputedRef<Modal>) => void
70-
deleteFromOpenedModals: (modal: ComputedRef<Modal>) => void
71-
moveToLastOpenedModalOverlays: (modal: ComputedRef<Modal>) => void
72-
deleteFromOpenedModalOverlays: (modal: ComputedRef<Modal>) => void
73-
deleteFromModals: (modal: ComputedRef<Modal>) => void
74-
resolvedClosed: (index: number) => void
75-
resolvedOpened: (index: number) => void
76-
}
77-
78-
export type Modal = {
79-
modalId?: ModalId
80-
hideOverlay: Ref<boolean | undefined> | undefined
67+
export type ModalExposed = {
68+
modalId?: Ref<undefined | ModalId>
69+
hideOverlay?: Ref<undefined | boolean>
70+
overlayBehavior: Ref<'auto' | 'persist'>
8171
overlayVisible: Ref<boolean>
8272
toggle: (show?: boolean) => Promise<string>
8373
}

packages/vue-final-modal/src/components/ModalsContainer.vue

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
<script setup lang="ts">
22
import { computed, onBeforeUnmount } from 'vue'
33
import { isString } from '~/utils'
4-
import { useInternalVfm, useVfm } from '~/useApi'
4+
import { useVfm } from '~/useApi'
55
66
const { modalsContainers, dynamicModals } = useVfm()
7-
const { resolvedClosed, resolvedOpened } = useInternalVfm()
87
98
const uid = Symbol('ModalsContainer')
109
const shouldMount = computed(() => uid === modalsContainers.value?.[0])
@@ -13,6 +12,16 @@ modalsContainers.value.push(uid)
1312
onBeforeUnmount(() => {
1413
modalsContainers.value = modalsContainers.value.filter(i => i !== uid)
1514
})
15+
16+
function resolvedClosed(index: number) {
17+
dynamicModals[index]?.resolveClosed?.()
18+
if (!dynamicModals[index]?.keepAlive)
19+
dynamicModals.splice(index, 1)
20+
}
21+
22+
function resolvedOpened(index: number) {
23+
dynamicModals[index]?.resolveOpened?.()
24+
}
1625
</script>
1726

1827
<template>

packages/vue-final-modal/src/components/VueFinalModal/VueFinalModal.vue

Lines changed: 60 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { computed, nextTick, onBeforeUnmount, onMounted, ref, toRef, useAttrs, watch } from 'vue'
2+
import { computed, getCurrentInstance, nextTick, onBeforeUnmount, onMounted, ref, toRef, useAttrs, watch } from 'vue'
33
import { vueFinalModalProps } from './VueFinalModalProps'
44
import { useTransition } from './useTransition'
55
import { useToClose } from './useToClose'
@@ -8,10 +8,11 @@ import { useFocusTrap } from './useFocusTrap'
88
import { useLockScroll } from './useBodyScrollLock'
99
import { useZIndex } from './useZIndex'
1010
import { vVisible } from './vVisible'
11-
import { noop, once } from '~/utils'
12-
import { type Modal } from '~/Modal'
11+
import { arrayMoveItemToLast, arrayRemoveItem, noop, once } from '~/utils'
12+
import { type ModalExposed } from '~/Modal'
1313
import { useSwipeToClose } from '~/useSwipeToClose'
14-
import { useInternalVfm, useVfm } from '~/useApi'
14+
import { useVfm } from '~/useApi'
15+
import { getModalExposed } from '~/plugin'
1516
1617
export interface VueFinalModalEmits {
1718
(e: 'update:modelValue', modelValue: boolean): void
@@ -26,30 +27,19 @@ export interface VueFinalModalEmits {
2627
}
2728
2829
const props = defineProps(vueFinalModalProps)
29-
3030
const emit = defineEmits<VueFinalModalEmits>()
31-
3231
const attrs = useAttrs()
3332
34-
defineOptions({
35-
inheritAttrs: false,
36-
})
33+
defineOptions({ inheritAttrs: false })
34+
35+
const instance = getCurrentInstance()
3736
3837
defineSlots<{
3938
'default'(): void
4039
'swipe-banner'(): void
4140
}>()
4241
43-
const { modals, openedModals } = useVfm()
44-
45-
const {
46-
openLastOverlay,
47-
moveToLastOpenedModals,
48-
deleteFromOpenedModals,
49-
moveToLastOpenedModalOverlays,
50-
deleteFromOpenedModalOverlays,
51-
deleteFromModals,
52-
} = useInternalVfm()
42+
const { modals, openedModals, openedModalOverlays } = useVfm()
5343
5444
const vfmRootEl = ref<HTMLDivElement>()
5545
const vfmContentEl = ref<HTMLDivElement>()
@@ -90,7 +80,7 @@ const {
9080
resolveToggle('opened')
9181
},
9282
onLeave() {
93-
deleteFromOpenedModals(getModalInstance())
83+
arrayRemoveItem(openedModals, instance)
9484
resetZIndex()
9585
enableBodyScroll()
9686
emit('closed')
@@ -106,36 +96,16 @@ const {
10696
onTouchStartSwipeBanner,
10797
} = useSwipeToClose(props, { vfmContentEl, modelValueLocal })
10898
109-
const hideOverlay = toRef(props, 'hideOverlay')
110-
const modalInstance = computed<Modal>(() => ({
111-
modalId: props.modalId,
112-
hideOverlay,
113-
overlayVisible,
114-
focus,
115-
toggle(show?: boolean): Promise<string> {
116-
return new Promise((resolve) => {
117-
resolveToggle = once((res: string) => resolve(res))
118-
119-
const value = typeof show === 'boolean' ? show : !modelValueLocal.value
120-
modelValueLocal.value = value
121-
emit('update:modelValue', value)
122-
})
123-
},
124-
}))
125-
126-
function getModalInstance() {
127-
return modalInstance
128-
}
129-
130-
const index = computed(() => openedModals.indexOf(modalInstance))
99+
const index = computed(() => instance ? openedModals.indexOf(instance) : -1)
131100
132101
watch([() => props.zIndexFn, index], () => {
133-
if (visible.value)
134-
refreshZIndex(index.value)
102+
if (!visible.value)
103+
return
104+
refreshZIndex(index.value)
135105
})
136106
137107
onMounted(() => {
138-
modals.push(modalInstance)
108+
arrayMoveItemToLast(modals, instance)
139109
})
140110
141111
if (props.modelValue)
@@ -146,9 +116,8 @@ function open(): boolean {
146116
emit('beforeOpen', { stop: () => shouldStop = true })
147117
if (shouldStop)
148118
return false
149-
moveToLastOpenedModals(modalInstance)
150-
moveToLastOpenedModalOverlays(modalInstance)
151-
refreshZIndex(index.value)
119+
arrayMoveItemToLast(openedModals, instance)
120+
arrayMoveItemToLast(openedModalOverlays, instance)
152121
openLastOverlay()
153122
enterTransition()
154123
return true
@@ -159,7 +128,7 @@ function close(): boolean {
159128
emit('beforeClose', { stop: () => shouldStop = true })
160129
if (shouldStop)
161130
return false
162-
deleteFromOpenedModalOverlays(getModalInstance())
131+
arrayRemoveItem(openedModalOverlays, instance)
163132
openLastOverlay()
164133
blur()
165134
leaveTransition()
@@ -168,12 +137,49 @@ function close(): boolean {
168137
169138
onBeforeUnmount(() => {
170139
enableBodyScroll()
171-
deleteFromModals(modalInstance)
172-
deleteFromOpenedModals(modalInstance)
173-
deleteFromOpenedModalOverlays(modalInstance)
140+
arrayRemoveItem(modals, instance)
141+
arrayRemoveItem(openedModals, instance)
174142
blur()
175143
openLastOverlay()
176144
})
145+
146+
async function openLastOverlay() {
147+
await nextTick()
148+
// Found the modals which has overlay and has `auto` overlayBehavior
149+
const openedModalsOverlaysAuto = openedModalOverlays.filter((modal) => {
150+
const modalExposed = getModalExposed(modal)
151+
return modalExposed?.value.overlayBehavior.value === 'auto' && !modalExposed?.value.hideOverlay?.value
152+
})
153+
// Only keep the last overlay open
154+
openedModalsOverlaysAuto.forEach((modal, index) => {
155+
const modalExposed = getModalExposed(modal)
156+
if (!modalExposed?.value)
157+
return
158+
modalExposed.value.overlayVisible.value = index === openedModalsOverlaysAuto.length - 1
159+
})
160+
}
161+
162+
const modalId = toRef(props, 'modalId')
163+
const hideOverlay = toRef(props, 'hideOverlay')
164+
const overlayBehavior = toRef(props, 'overlayBehavior')
165+
const modalExposed = computed<ModalExposed>(() => ({
166+
modalId,
167+
hideOverlay,
168+
overlayBehavior,
169+
overlayVisible,
170+
toggle(show?: boolean): Promise<string> {
171+
return new Promise((resolve) => {
172+
resolveToggle = once((res: string) => resolve(res))
173+
174+
const value = typeof show === 'boolean' ? show : !modelValueLocal.value
175+
modelValueLocal.value = value
176+
})
177+
},
178+
}))
179+
180+
defineExpose({
181+
modalExposed,
182+
})
177183
</script>
178184

179185
<template>
@@ -193,7 +199,7 @@ onBeforeUnmount(() => {
193199
@mouseup.self="() => onMouseupRoot()"
194200
@mousedown.self="e => onMousedown(e)"
195201
>
196-
<Transition v-if="!hideOverlay" v-bind="overlayTransition" :appear="true" v-on="overlayListeners">
202+
<Transition v-if="!hideOverlay" v-bind="overlayTransition as object" v-on="overlayListeners">
197203
<div
198204
v-if="displayDirective !== 'if' || overlayVisible"
199205
v-show="displayDirective !== 'show' || overlayVisible"
@@ -204,7 +210,7 @@ onBeforeUnmount(() => {
204210
aria-hidden="true"
205211
/>
206212
</Transition>
207-
<Transition v-bind="contentTransition" :appear="true" v-on="contentListeners">
213+
<Transition v-bind="contentTransition as object" v-on="contentListeners">
208214
<div
209215
v-if="displayDirective !== 'if' || contentVisible"
210216
v-show="displayDirective !== 'show' || contentVisible"

packages/vue-final-modal/src/components/VueFinalModal/VueFinalModalProps.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ export const vueFinalModalProps = {
7272
type: Boolean as PropType<boolean>,
7373
default: undefined,
7474
},
75+
/**
76+
* @description Customize the overlay behavior.
77+
*/
78+
overlayBehavior: {
79+
type: String as PropType<'auto' | 'persist'>,
80+
default: 'auto',
81+
validator: (prop: any) => ['auto', 'persist'].includes(prop),
82+
},
7583
/**
7684
* @description Customize the overlay transition.
7785
* @default `undefined`

packages/vue-final-modal/src/components/VueFinalModal/useTransition.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@ export function useTransition(
5858

5959
const contentTransition = computed<TransitionProps>(() => {
6060
if (typeof props.contentTransition === 'string')
61-
return { name: props.contentTransition }
62-
return { ...props.contentTransition }
61+
return { name: props.contentTransition, appear: true }
62+
return { appear: true, ...props.contentTransition }
6363
})
6464

6565
const overlayTransition = computed<TransitionProps>(() => {
6666
if (typeof props.overlayTransition === 'string')
67-
return { name: props.overlayTransition }
68-
return { ...props.overlayTransition }
67+
return { name: props.overlayTransition, appear: true }
68+
return { appear: true, ...props.overlayTransition }
6969
})
7070

7171
const isReadyToBeDestroyed = computed(() =>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { Vfm } from './Modal'
77
export * from './Modal'
88

99
/** Plugin */
10-
export * from './plugin'
10+
export { createVfm, getModalExposed } from './plugin'
1111

1212
/** Components */
1313
export {
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type { InjectionKey } from 'vue'
2-
import type { InternalVfm, Vfm } from './Modal'
2+
import type { Vfm } from './Modal'
33

44
export const vfmSymbol = Symbol(__DEV__ ? 'vfm' : '') as InjectionKey<Vfm>
5-
export const internalVfmSymbol = Symbol(__DEV__ ? 'internalVfm' : '') as InjectionKey<InternalVfm>

0 commit comments

Comments
 (0)