Skip to content

Commit 86216b3

Browse files
feat: update z-index after state changes (#334)
* feat: update `z-index` after state changes * fix: only refresh zIndex if the modal is visible * fix: watch zIndexFn and index.value * fix: refreshZIndex after moveToLastOpenedModals * test: add useZIndex test cases and simplify tests * test: add test cases for focusTrap * test: add events test cases * test: add test video * fix: do not need to execute refreshZIndex in CoreModal * refactor: tests --------- Co-authored-by: Hunter <[email protected]>
1 parent 61d2604 commit 86216b3

File tree

14 files changed

+271
-64
lines changed

14 files changed

+271
-64
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<script setup lang="ts">
2+
import { ModalsContainer } from '~/index'
3+
</script>
4+
5+
<template>
6+
<div>
7+
<div v-for="i in 1000" :key="i">
8+
id: {{ i }}
9+
</div>
10+
<slot />
11+
12+
<ModalsContainer />
13+
</div>
14+
</template>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script setup lang="ts">
2+
import { ref } from 'vue'
3+
4+
const emit = defineEmits<{
5+
(e: 'submit', payload: {
6+
account: string
7+
password: string
8+
}): void
9+
}>()
10+
11+
const account = ref('')
12+
const password = ref('')
13+
</script>
14+
15+
<template>
16+
<form @submit.prevent="() => emit('submit', { account, password })">
17+
<label>Account:<input v-model="account" class="form-account" type="text"></label>
18+
<label>Password:<input v-model="password" class="form-password" type="password"></label>
19+
20+
<button class="form-submit" type="submit">
21+
Submit
22+
</button>
23+
</form>
24+
</template>

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

Lines changed: 0 additions & 42 deletions
This file was deleted.

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

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import App from './App.vue'
2+
import Form from './Form.vue'
3+
import { VueFinalModal, createVfm, useModal } from '~/index'
4+
5+
describe('Test focusTrap', () => {
6+
it('Props: focusTrap', () => {
7+
const vfm = createVfm()
8+
9+
const firstModal = useModal({
10+
context: vfm,
11+
component: VueFinalModal,
12+
attrs: { contentClass: 'first-modal-content' },
13+
slots: {
14+
default: Form,
15+
},
16+
})
17+
18+
const secondModal = useModal({
19+
context: vfm,
20+
component: VueFinalModal,
21+
attrs: { contentClass: 'second-modal-content' },
22+
slots: {
23+
default: '<p>Hello world!</p>',
24+
},
25+
})
26+
27+
cy.mount(App, {
28+
global: {
29+
plugins: [vfm],
30+
stubs: { transition: false },
31+
},
32+
})
33+
.then(async () => {
34+
await firstModal.open()
35+
cy.focused().as('firstModalFocus')
36+
cy.get('@firstModalFocus').should('have.class', 'first-modal-content')
37+
})
38+
.then(async () => {
39+
cy.get('.form-submit').focus()
40+
cy.focused().as('formSubmitFocus')
41+
cy.get('@formSubmitFocus').should('have.class', 'form-submit')
42+
})
43+
.then(async () => {
44+
await secondModal.open()
45+
cy.focused().as('secondModalFocus')
46+
cy.get('@secondModalFocus').should('have.class', 'second-modal-content')
47+
})
48+
.then(async () => {
49+
await secondModal.close()
50+
cy.focused().as('formSubmitFocus')
51+
cy.get('@formSubmitFocus').should('have.class', 'form-submit')
52+
})
53+
.then(async () => {
54+
await firstModal.close()
55+
await firstModal.open()
56+
cy.focused().as('firstModalFocus')
57+
cy.get('@firstModalFocus').should('have.class', 'first-modal-content')
58+
})
59+
})
60+
})
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import App from './App.vue'
2+
import Form from './Form.vue'
3+
import { createVfm, useModal } from '~/index'
4+
5+
describe('Test useModal()', () => {
6+
it('Should be closed by default', () => {
7+
const vfm = createVfm()
8+
const modal = useModal({
9+
context: vfm,
10+
slots: { default: 'Hello World!' },
11+
})
12+
13+
cy.mount(App, {
14+
global: {
15+
plugins: [vfm],
16+
stubs: { transition: false },
17+
},
18+
}).as('app')
19+
20+
cy.contains('Hello World!').should('not.exist')
21+
cy.get('@app').then(() => modal.open())
22+
cy.contains('Hello World!').should('exist')
23+
})
24+
25+
it('Should be opened by given defaultModelValue: true', () => {
26+
const vfm = createVfm()
27+
useModal({
28+
context: vfm,
29+
defaultModelValue: true,
30+
slots: {
31+
default: 'Hello World!',
32+
},
33+
})
34+
35+
cy.mount(App, {
36+
global: {
37+
plugins: [vfm],
38+
stubs: { transition: false },
39+
},
40+
})
41+
42+
cy.contains('Hello World!')
43+
})
44+
45+
it('Events', () => {
46+
const vfm = createVfm()
47+
48+
const onBeforeOpen = cy.spy().as('onBeforeOpen')
49+
const onOpened = cy.spy().as('onOpened')
50+
const onBeforeClose = cy.spy().as('onBeforeClose')
51+
const onClosed = cy.spy().as('onClosed')
52+
53+
const modal = useModal({
54+
context: vfm,
55+
attrs: {
56+
onBeforeOpen,
57+
onOpened,
58+
onBeforeClose,
59+
onClosed,
60+
},
61+
slots: { default: Form },
62+
})
63+
64+
cy.mount(App, {
65+
global: {
66+
plugins: [vfm],
67+
stubs: { transition: false },
68+
},
69+
}).as('app')
70+
71+
cy.get('@onBeforeOpen').should('have.callCount', 0)
72+
cy.get('@onOpened').should('have.callCount', 0)
73+
cy.get('@app').then(() => modal.open())
74+
cy.get('@onBeforeOpen').should('have.callCount', 1)
75+
cy.get('@onOpened').should('have.callCount', 1)
76+
77+
cy.get('@onBeforeClose').should('have.callCount', 0)
78+
cy.get('@onClosed').should('have.callCount', 0)
79+
cy.get('@app').then(() => modal.close())
80+
cy.get('@onBeforeClose').should('have.callCount', 1)
81+
cy.get('@onClosed').should('have.callCount', 1)
82+
})
83+
})
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import App from './App.vue'
2+
import { VueFinalModal, createVfm, useModal } from '~/index'
3+
4+
describe('Test useZIndex()', () => {
5+
it('Props: zIndexFn()', () => {
6+
const vfm = createVfm()
7+
const firstModal = useModal({
8+
context: vfm,
9+
component: VueFinalModal,
10+
attrs: { class: 'first-modal' },
11+
})
12+
13+
const secondModal = useModal({
14+
context: vfm,
15+
component: VueFinalModal,
16+
attrs: { class: 'second-modal' },
17+
})
18+
19+
const thirdModal = useModal({
20+
context: vfm,
21+
component: VueFinalModal,
22+
attrs: { class: 'third-modal' },
23+
})
24+
25+
cy.mount(App, {
26+
global: {
27+
plugins: [vfm],
28+
stubs: { transition: false },
29+
},
30+
}).as('app')
31+
cy.get('@app').then(() => {
32+
firstModal.open()
33+
})
34+
cy.get('.first-modal').should(($el) => {
35+
expect($el).to.have.css('zIndex', '1000')
36+
})
37+
cy.get('@app').then(() => {
38+
secondModal.open()
39+
})
40+
cy.get('.second-modal').should(($el) => {
41+
expect($el).to.have.css('zIndex', '1002')
42+
})
43+
cy.get('@app').then(() => {
44+
thirdModal.open()
45+
})
46+
cy.get('.third-modal').should(($el) => {
47+
expect($el).to.have.css('zIndex', '1004')
48+
})
49+
cy.get('@app').then(() => {
50+
thirdModal.patchOptions({
51+
attrs: {
52+
zIndexFn: ({ index }) => 1234 + 2 * index,
53+
},
54+
})
55+
})
56+
cy.get('.third-modal').should(($el) => {
57+
expect($el).to.have.css('zIndex', '1238')
58+
})
59+
cy.get('@app').then(() => {
60+
firstModal.close()
61+
})
62+
cy.get('.second-modal').should(($el) => {
63+
expect($el).to.have.css('zIndex', '1000')
64+
})
65+
cy.get('.third-modal').should(($el) => {
66+
expect($el).to.have.css('zIndex', '1236')
67+
})
68+
})
69+
})
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const {
4747
} as any as InternalVfm)
4848
4949
const vfmRootEl = ref<HTMLDivElement>()
50-
const { zIndex, refreshZIndex } = useZIndex(props, { openedModals })
50+
5151
const { focus, focusLast, blur } = useFocusTrap(props, { focusEl: vfmRootEl, openedModals })
5252
const { enableBodyScroll, disableBodyScroll } = useLockScroll(props, { lockScrollEl: vfmRootEl })
5353
const { modelValueLocal } = useModelValue(props, emit)
@@ -114,6 +114,8 @@ const modalInstance = computed<Modal>(() => ({
114114
},
115115
}))
116116
117+
const { zIndex } = useZIndex(props, { openedModals, modalInstance, visible })
118+
117119
onMounted(() => {
118120
modals.push(modalInstance)
119121
})
@@ -127,7 +129,6 @@ watch(modelValueLocal, (value) => {
127129
128130
async function open() {
129131
emitEvent('beforeOpen')
130-
refreshZIndex()
131132
moveToLastOpenedModals(modalInstance)
132133
openLastOverlay()
133134
enterTransition()
Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,32 @@
1-
import type { ComputedRef } from 'vue'
2-
import { ref } from 'vue'
1+
import type { ComputedRef, Ref } from 'vue'
2+
import { computed, ref, watch } from 'vue'
33
import type CoreModal from './CoreModal.vue'
44
import type { Modal } from '~/Modal'
55

66
export function useZIndex(
77
props: InstanceType<typeof CoreModal>['$props'],
8-
options: { openedModals: ComputedRef<Modal>[] },
8+
options: {
9+
openedModals: ComputedRef<Modal>[]
10+
modalInstance: ComputedRef<Modal>
11+
visible: Ref<boolean>
12+
},
913
) {
10-
const { openedModals } = options
14+
const { openedModals, modalInstance, visible } = options
15+
1116
const zIndex = ref<undefined | number>()
1217

18+
const index = computed(() => openedModals.indexOf(modalInstance))
19+
1320
function refreshZIndex() {
14-
zIndex.value = props.zIndexFn?.({ index: openedModals.length })
21+
zIndex.value = props.zIndexFn?.({ index: index.value })
1522
}
1623

24+
watch(() => [props.zIndexFn, index.value], () => {
25+
if (visible.value)
26+
refreshZIndex()
27+
})
28+
1729
return {
1830
zIndex,
19-
refreshZIndex,
2031
}
2132
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ onBeforeUnmount(() => {
5858
:key="modal.id"
5959
v-bind="modal.attrs"
6060
v-model="modal.modelValue"
61-
@closed="withSyncOpenDynamicModals(() => _vfm.resolvedClosed?.(index))"
61+
@closed="() => withSyncOpenDynamicModals(() => _vfm.resolvedClosed?.(index))"
6262
@opened="() => _vfm.resolvedOpened?.(index)"
6363
>
6464
<template v-for="(slot, key) in modal.slots" #[key] :key="key">

0 commit comments

Comments
 (0)