Skip to content

Commit 75694ae

Browse files
committed
feat: nested poppers auto lock
1 parent 3181330 commit 75694ae

File tree

3 files changed

+154
-1
lines changed

3 files changed

+154
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<template>
2+
<div class="example flex justify-center items-center gap-6">
3+
<VMenu placement="bottom-start">
4+
<button class="border border-gray-300 rounded px-4 py-2">
5+
Hover me
6+
</button>
7+
8+
<template #popper>
9+
<div class="px-4 py-1">
10+
Sub menus:
11+
</div>
12+
13+
<VMenu
14+
v-for="n in 5"
15+
:key="n"
16+
placement="right-start"
17+
instant-move
18+
>
19+
<button class="rounded hover:bg-green-100 px-4 py-2">
20+
Sub menu >
21+
</button>
22+
23+
<template #popper>
24+
<VMenu
25+
v-for="n in 5"
26+
:key="n"
27+
placement="right-start"
28+
instant-move
29+
>
30+
<button class="rounded hover:bg-green-100 px-4 py-2">
31+
Option {{ n }} >
32+
</button>
33+
34+
<template #popper>
35+
<div class="px-6 py-2">
36+
Hello
37+
</div>
38+
</template>
39+
</VMenu>
40+
</template>
41+
</VMenu>
42+
</template>
43+
</VMenu>
44+
45+
<VDropdown placement="bottom-start">
46+
<button class="border border-gray-300 rounded px-4 py-2">
47+
Click me
48+
</button>
49+
50+
<template #popper>
51+
<div class="px-4 py-1">
52+
Sub menus:
53+
</div>
54+
55+
<VDropdown
56+
v-for="n in 5"
57+
:key="n"
58+
placement="right-start"
59+
instant-move
60+
>
61+
<button class="rounded hover:bg-green-100 px-4 py-2">
62+
Sub menu >
63+
</button>
64+
65+
<template #popper>
66+
<VDropdown
67+
v-for="n in 5"
68+
:key="n"
69+
placement="right-start"
70+
instant-move
71+
>
72+
<button class="rounded hover:bg-green-100 px-4 py-2">
73+
Option {{ n }} >
74+
</button>
75+
76+
<template #popper>
77+
<div class="px-6 py-2">
78+
Hello
79+
</div>
80+
</template>
81+
</VDropdown>
82+
</template>
83+
</VDropdown>
84+
</template>
85+
</VDropdown>
86+
</div>
87+
</template>

packages/docs/src/guide/component.md

+6
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,12 @@ Close all the poppers in the page with the `all` modifier:
266266
<a v-close-popper.all>Close All</a>
267267
```
268268

269+
## Sub menu
270+
271+
Nesting poppers inside other popper will automatically prevent the parents from hiding when any child is shown:
272+
273+
<SubMenuExample />
274+
269275
## Disable popper
270276

271277
Disabling a popper will prevent it from being shown.

packages/floating-vue/src/components/Popper.ts

+61-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ function defaultPropFactory (prop: string) {
3232
}
3333
}
3434

35+
const PROVIDE_KEY = '__floating-vue__popper'
36+
3537
export default () => ({
3638
name: 'VPopper',
3739

@@ -240,6 +242,18 @@ export default () => ({
240242
},
241243
},
242244

245+
provide () {
246+
return {
247+
[PROVIDE_KEY]: {
248+
parentPopper: this,
249+
},
250+
}
251+
},
252+
253+
inject: {
254+
[PROVIDE_KEY]: { default: null },
255+
},
256+
243257
data () {
244258
return {
245259
isShown: false,
@@ -263,6 +277,7 @@ export default () => ({
263277
},
264278
transformOrigin: null,
265279
},
280+
shownChildren: new Set(),
266281
}
267282
},
268283

@@ -293,6 +308,10 @@ export default () => ({
293308
result: this.positioningDisabled ? null : this.result,
294309
}
295310
},
311+
312+
parentPopper () {
313+
return this[PROVIDE_KEY]?.parentPopper
314+
},
296315
},
297316

298317
watch: {
@@ -369,6 +388,7 @@ export default () => ({
369388

370389
methods: {
371390
show ({ event = null, skipDelay = false, force = false } = {}) {
391+
this.$_pendingHide = false
372392
if (force || !this.disabled) {
373393
this.$_scheduleShow(event, skipDelay)
374394
this.$emit('show')
@@ -384,6 +404,11 @@ export default () => ({
384404

385405
hide ({ event = null, skipDelay = false } = {}) {
386406
if (this.$_hideInProgress) return
407+
if (this.shownChildren.size > 0) {
408+
this.$_pendingHide = true
409+
return
410+
}
411+
this.$_pendingHide = false
387412
this.$_scheduleHide(event, skipDelay)
388413

389414
this.$emit('hide')
@@ -427,6 +452,8 @@ export default () => ({
427452
this.isMounted = false
428453
this.isShown = false
429454

455+
this.$_updateParentShownChildren(false)
456+
430457
this.$_swapTargetAttrs('data-original-title', 'title')
431458

432459
this.$emit('dispose')
@@ -574,10 +601,11 @@ export default () => ({
574601
},
575602

576603
$_scheduleShow (event = null, skipDelay = false) {
604+
this.$_updateParentShownChildren(true)
577605
this.$_hideInProgress = false
578606
clearTimeout(this.$_scheduleTimer)
579607

580-
if (hidingPopper && this.instantMove && hidingPopper.instantMove) {
608+
if (hidingPopper && this.instantMove && hidingPopper.instantMove && hidingPopper !== this.parentPopper) {
581609
hidingPopper.$_applyHide(true)
582610
this.$_applyShow(true)
583611
return
@@ -591,6 +619,11 @@ export default () => ({
591619
},
592620

593621
$_scheduleHide (event = null, skipDelay = false) {
622+
if (this.shownChildren.size > 0) {
623+
this.$_pendingHide = true
624+
return
625+
}
626+
this.$_updateParentShownChildren(false)
594627
this.$_hideInProgress = true
595628
clearTimeout(this.$_scheduleTimer)
596629

@@ -683,6 +716,11 @@ export default () => ({
683716
},
684717

685718
async $_applyHide (skipTransition = false) {
719+
if (this.shownChildren.size > 0) {
720+
this.$_pendingHide = true
721+
this.$_hideInProgress = false
722+
return
723+
}
686724
clearTimeout(this.$_scheduleTimer)
687725

688726
// Already hidden
@@ -847,6 +885,12 @@ export default () => ({
847885
this.$_preventShow = false
848886
}, 300)
849887
}
888+
889+
let parent = this.parentPopper
890+
while (parent) {
891+
parent.$_handleGlobalClose(event, touch)
892+
parent = parent.parentPopper
893+
}
850894
},
851895

852896
$_detachPopperNode () {
@@ -875,6 +919,22 @@ export default () => ({
875919
}
876920
}
877921
},
922+
923+
$_updateParentShownChildren (value) {
924+
let parent = this.parentPopper
925+
while (parent) {
926+
if (value) {
927+
parent.shownChildren.add(this.randomId)
928+
} else {
929+
parent.shownChildren.delete(this.randomId)
930+
931+
if (parent.$_pendingHide) {
932+
parent.hide()
933+
}
934+
}
935+
parent = parent.parentPopper
936+
}
937+
},
878938
},
879939

880940
render () {

0 commit comments

Comments
 (0)