1
1
<script lang="ts">
2
- import { type InjectionKey , type Ref , inject , provide , ref , watch } from ' vue'
3
- import { Wowerlay , type WowerlayTransitionFn } from ' wowerlay'
2
+ import { type InjectionKey , type Ref , inject , onScopeDispose , provide , ref , watch } from ' vue'
3
+ import { type AlignedPlacement , type ReferenceElement , type Side , Wowerlay , type WowerlayTransitionFn } from ' wowerlay'
4
4
import { useEventListener } from ' @vueuse/core'
5
5
import { useKey } from ' ../composables/useKey'
6
6
@@ -12,43 +12,37 @@ interface SlotProps {
12
12
visible: boolean
13
13
}
14
14
15
- const popoverContextKey: InjectionKey <PopoverContext > = Symbol (' PopoverContext' )
15
+ // We use plain string on dev mode because hot reloading brokes symbols.
16
+ const popoverContextKey: InjectionKey <PopoverContext > = import .meta .env .DEV ? ' PopoverContext' as any : Symbol (' PopoverContext' )
16
17
17
18
export function usePopoverContext() {
18
19
return inject (popoverContextKey )!
19
20
}
20
- </script >
21
-
22
- <script lang="ts" setup>
23
- const props = withDefaults (defineProps <Props >(), {
24
- wowerlayOptions : () => ({}),
25
- })
26
21
27
- defineSlots <{
28
- default: (props : SlotProps ) => any
29
- }>()
30
-
31
- interface Props {
32
- wowerlayOptions? : Partial <Omit <InstanceType <typeof Wowerlay >[' $props' ], ' visible' | ' target' >>
33
- target? : HTMLElement | null
22
+ const transformOriginMap: Record <AlignedPlacement | Side , string > = {
23
+ ' bottom-end' : ' right top' ,
24
+ ' bottom-start' : ' left top' ,
25
+ ' bottom' : ' center top' ,
26
+ ' left-end' : ' right bottom' ,
27
+ ' left-start' : ' right top' ,
28
+ ' left' : ' right center' ,
29
+ ' right-end' : ' left bottom' ,
30
+ ' right-start' : ' left top' ,
31
+ ' right' : ' left center' ,
32
+ ' top-end' : ' right bottom' ,
33
+ ' top-start' : ' left bottom' ,
34
+ ' top' : ' center bottom' ,
34
35
}
35
36
36
- const visible = ref (false )
37
-
38
- provide (popoverContextKey , { visible })
39
-
40
- useKey (' esc' , () => {
41
- visible .value = false
42
- }, { prevent: true , source: visible })
43
-
44
37
const handleTransition: WowerlayTransitionFn = (type , element , done ) => {
45
- const placement = element .getAttribute (' data-popover-placement' )! .split (' -' )[0 ]
38
+ const placement = element .getAttribute (' data-popover-placement' ) as AlignedPlacement | Side
39
+ const side = placement .split (' -' )[0 ] as Side
46
40
47
- const vertical = placement === ' top' || placement === ' bottom'
41
+ const vertical = side === ' top' || side === ' bottom'
48
42
const transformFunction = vertical ? ' translateY' : ' translateX'
49
43
50
44
const from = {
51
- transform: ` scale(0.97) ${transformFunction }(${placement === ' bottom' || placement === ' right' ? ' -7px ' : ' 7px ' }) ` ,
45
+ transform: ` scale(0.97) ${transformFunction }(${side === ' bottom' || side === ' right' ? ' -3px ' : ' 3px ' }) ` ,
52
46
opacity: 0 ,
53
47
}
54
48
@@ -57,13 +51,75 @@ const handleTransition: WowerlayTransitionFn = (type, element, done) => {
57
51
opacity: 1 ,
58
52
}
59
53
54
+ const oldTransformOrigin = element .style .transformOrigin
55
+ element .style .transformOrigin = transformOriginMap [placement ]
56
+
57
+ if (type === ' leave' ) {
58
+ const background = element .parentElement
59
+ if (background ) {
60
+ background .style .setProperty (' pointer-events' , ' none' )
61
+ element .style .setProperty (' pointer-events' , ' auto' )
62
+ }
63
+ }
64
+
60
65
const animation = element .animate (type === ' enter' ? [from , to ] : [to , from ], {
61
66
duration: 200 ,
62
67
easing: ' ease' ,
63
68
})
64
69
65
- animation .onfinish = done
70
+ animation .onfinish = () => {
71
+ if (type === ' enter' )
72
+ element .style .transformOrigin = oldTransformOrigin
73
+
74
+ done ()
75
+ }
76
+ }
77
+
78
+ const popoverVisibleHooks = new Set <(el : ReferenceElement ) => void >()
79
+
80
+ const runPopoverVisibleHooks = (el : ReferenceElement ) => {
81
+ for (const cb of popoverVisibleHooks )
82
+ cb (el )
83
+ }
84
+
85
+ export const onPopoverVisible = (cb : (el : ReferenceElement ) => void ) => {
86
+ popoverVisibleHooks .add (cb )
87
+ const cleanup = () => popoverVisibleHooks .delete (cb )
88
+ onScopeDispose (cleanup )
89
+
90
+ return cleanup
66
91
}
92
+ </script >
93
+
94
+ <script lang="ts" setup>
95
+ const props = withDefaults (defineProps <Props >(), {
96
+ wowerlayOptions : () => ({}),
97
+ })
98
+
99
+ defineSlots <{
100
+ default: (props : SlotProps ) => any
101
+ }>()
102
+
103
+ interface Props {
104
+ wowerlayOptions? : Partial <Omit <InstanceType <typeof Wowerlay >[' $props' ], ' visible' | ' target' >>
105
+ target? : InstanceType <typeof Wowerlay >[' $props' ][' target' ]
106
+ }
107
+
108
+ const visible = ref (false )
109
+
110
+ defineExpose ({
111
+ show() {
112
+ visible .value = true
113
+ },
114
+ hide() {
115
+ visible .value = false
116
+ },
117
+ })
118
+ provide (popoverContextKey , { visible })
119
+
120
+ useKey (' esc' , () => {
121
+ visible .value = false
122
+ }, { prevent: true , source: visible })
67
123
68
124
const popoverEl = ref <HTMLElement | null >(null )
69
125
let lastFocusedElement: Element | null = null
@@ -74,14 +130,15 @@ watch(visible, (value) => {
74
130
setTimeout (() => {
75
131
popoverEl .value ?.focus ()
76
132
})
133
+ runPopoverVisibleHooks (props .target as ReferenceElement )
77
134
}
78
135
else {
79
136
setTimeout (() => lastFocusedElement instanceof HTMLElement && lastFocusedElement .focus ())
80
137
}
81
138
})
82
139
83
140
useEventListener (
84
- () => props .target ,
141
+ () => props .target instanceof HTMLElement ? props . target : null ,
85
142
' click' ,
86
143
() => {
87
144
visible .value = ! visible .value
@@ -96,10 +153,12 @@ useEventListener(
96
153
class =" popover"
97
154
tabindex =" -1"
98
155
:target =" target"
99
- v-bind =" props.wowerlayOptions"
100
156
:gap =" 2"
101
- noBackground
157
+ :backgroundAttrs =" {
158
+ style: { zIndex: 1500 },
159
+ }"
102
160
:transition =" handleTransition"
161
+ v-bind =" props.wowerlayOptions"
103
162
@update:el =" (el) => popoverEl = el"
104
163
>
105
164
<slot
@@ -121,7 +180,6 @@ useEventListener(
121
180
display : flex ;
122
181
flex-direction : column ;
123
182
padding : 4px ;
124
- --wowerlay-z : 1500 ;
125
183
126
184
> * + * {
127
185
margin-top : 2px ;
0 commit comments