11<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'
44import { useEventListener } from ' @vueuse/core'
55import { useKey } from ' ../composables/useKey'
66
@@ -12,43 +12,37 @@ interface SlotProps {
1212 visible: boolean
1313}
1414
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' )
1617
1718export function usePopoverContext() {
1819 return inject (popoverContextKey )!
1920}
20- </script >
21-
22- <script lang="ts" setup>
23- const props = withDefaults (defineProps <Props >(), {
24- wowerlayOptions : () => ({}),
25- })
2621
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' ,
3435}
3536
36- const visible = ref (false )
37-
38- provide (popoverContextKey , { visible })
39-
40- useKey (' esc' , () => {
41- visible .value = false
42- }, { prevent: true , source: visible })
43-
4437const 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
4640
47- const vertical = placement === ' top' || placement === ' bottom'
41+ const vertical = side === ' top' || side === ' bottom'
4842 const transformFunction = vertical ? ' translateY' : ' translateX'
4943
5044 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 ' }) ` ,
5246 opacity: 0 ,
5347 }
5448
@@ -57,13 +51,75 @@ const handleTransition: WowerlayTransitionFn = (type, element, done) => {
5751 opacity: 1 ,
5852 }
5953
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+
6065 const animation = element .animate (type === ' enter' ? [from , to ] : [to , from ], {
6166 duration: 200 ,
6267 easing: ' ease' ,
6368 })
6469
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
6691}
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 })
67123
68124const popoverEl = ref <HTMLElement | null >(null )
69125let lastFocusedElement: Element | null = null
@@ -74,14 +130,15 @@ watch(visible, (value) => {
74130 setTimeout (() => {
75131 popoverEl .value ?.focus ()
76132 })
133+ runPopoverVisibleHooks (props .target as ReferenceElement )
77134 }
78135 else {
79136 setTimeout (() => lastFocusedElement instanceof HTMLElement && lastFocusedElement .focus ())
80137 }
81138})
82139
83140useEventListener (
84- () => props .target ,
141+ () => props .target instanceof HTMLElement ? props . target : null ,
85142 ' click' ,
86143 () => {
87144 visible .value = ! visible .value
@@ -96,10 +153,12 @@ useEventListener(
96153 class =" popover"
97154 tabindex =" -1"
98155 :target =" target"
99- v-bind =" props.wowerlayOptions"
100156 :gap =" 2"
101- noBackground
157+ :backgroundAttrs =" {
158+ style: { zIndex: 1500 },
159+ }"
102160 :transition =" handleTransition"
161+ v-bind =" props.wowerlayOptions"
103162 @update:el =" (el) => popoverEl = el"
104163 >
105164 <slot
@@ -121,7 +180,6 @@ useEventListener(
121180 display : flex ;
122181 flex-direction : column ;
123182 padding : 4px ;
124- --wowerlay-z : 1500 ;
125183
126184 > * + * {
127185 margin-top : 2px ;
0 commit comments