1
1
import { createPopper , type Instance , type Placement , type PositioningStrategy , type VirtualElement } from '@popperjs/core' ;
2
2
import { type MaybeElement , unrefElement } from '@vueuse/core' ;
3
3
import { onMounted , type Ref , ref , watchEffect } from 'vue' ;
4
+ import { useTimeoutManager } from './timeout-manager' ;
4
5
5
6
export interface PopperOptions {
6
7
locked ?: boolean ;
@@ -39,7 +40,7 @@ interface UsePopperReturn {
39
40
popper : Ref < MaybeElement > ;
40
41
popperEnter : Ref < boolean > ;
41
42
reference : Ref < MaybeElement > ;
42
- updatePopper : ( ) => void ;
43
+ updatePopper : ( ) => Promise < void > ;
43
44
}
44
45
45
46
export function usePopper (
@@ -49,65 +50,53 @@ export function usePopper(
49
50
closeDelay : Ref < number > = ref ( 0 ) ,
50
51
virtualReference ?: Ref < Element | VirtualElement > ,
51
52
) : UsePopperReturn {
52
- const reference : Ref < MaybeElement | null > = ref ( null ) ;
53
- const popper : Ref < MaybeElement | null > = ref ( null ) ;
54
- const instance : Ref < Instance | null > = ref ( null ) ;
55
- const open : Ref < boolean > = ref ( false ) ;
56
- const openTimeout : Ref < NodeJS . Timeout | undefined > = ref ( ) ;
57
- const closeTimeout : Ref < NodeJS . Timeout | undefined > = ref ( ) ;
58
- const popperEnter : Ref < boolean > = ref ( false ) ;
59
- const leavePending = ref ( false ) ;
53
+ const reference = ref < MaybeElement | null > ( null ) ;
54
+ const popper = ref < MaybeElement | null > ( null ) ;
55
+ const instance = ref < Instance | null > ( null ) ;
56
+ const open = ref < boolean > ( false ) ;
57
+ const popperEnter = ref < boolean > ( false ) ;
58
+ const leavePending = ref < boolean > ( false ) ;
59
+
60
+ const openTimeoutManager = useTimeoutManager ( ) ;
61
+ const closeTimeoutManager = useTimeoutManager ( ) ;
60
62
61
63
const onPopperLeave = ( ) => {
62
64
set ( popperEnter , false ) ;
63
65
} ;
64
66
65
- const updatePopper = ( ) => {
66
- // todo: see making things async/await has any side-effects
67
- get ( instance ) ?. update ( ) ;
67
+ const updatePopper = async ( ) : Promise < void > => {
68
+ await get ( instance ) ?. update ( ) ;
68
69
} ;
69
70
70
71
const onOpen = ( immediate = false ) => {
71
72
if ( get ( disabled ) )
72
73
return ;
73
74
74
- if ( get ( closeTimeout ) ) {
75
- clearTimeout ( get ( closeTimeout ) ) ;
76
- set ( closeTimeout , undefined ) ;
77
- }
75
+ closeTimeoutManager . clear ( ) ;
78
76
79
77
set ( leavePending , false ) ;
80
- if ( ! get ( openTimeout ) ) {
78
+ if ( ! openTimeoutManager . isActive ( ) ) {
81
79
set ( popperEnter , true ) ;
82
80
83
- const timeout = setTimeout ( ( ) => {
81
+ openTimeoutManager . create ( ( ) => {
84
82
set ( open , true ) ;
85
- set ( openTimeout , undefined ) ;
86
83
} , immediate ? 0 : get ( openDelay ) ) ;
87
-
88
- set ( openTimeout , timeout ) ;
89
84
}
90
85
} ;
91
86
92
87
const onClose = ( immediate = false ) => {
93
88
if ( get ( disabled ) )
94
89
return ;
95
90
96
- if ( get ( openTimeout ) ) {
97
- clearTimeout ( get ( openTimeout ) ) ;
98
- set ( openTimeout , undefined ) ;
99
- }
91
+ openTimeoutManager . clear ( ) ;
100
92
101
- if ( ! get ( closeTimeout ) ) {
102
- const timeout = setTimeout ( ( ) => {
93
+ if ( ! closeTimeoutManager . isActive ( ) ) {
94
+ closeTimeoutManager . create ( ( ) => {
103
95
if ( ! get ( open ) )
104
96
onPopperLeave ( ) ;
105
97
106
98
set ( open , false ) ;
107
- set ( closeTimeout , undefined ) ;
108
99
} , immediate ? 0 : get ( closeDelay ) ) ;
109
-
110
- set ( closeTimeout , timeout ) ;
111
100
}
112
101
} ;
113
102
@@ -176,32 +165,37 @@ export function usePopper(
176
165
} ;
177
166
} ) ;
178
167
179
- function initializePopper ( onInvalidate : ( cleanupFn : ( ) => void ) => void ) {
180
- if ( ! get ( popper ) )
181
- return ;
182
-
183
- if ( ! get ( reference ) && ! get ( virtualReference ) )
184
- return ;
168
+ function getValidElements ( ) : { popperEl : HTMLElement ; referenceEl : Element | VirtualElement } | null {
169
+ if ( ! get ( popper ) || ( ! get ( reference ) && ! get ( virtualReference ) ) ) {
170
+ return null ;
171
+ }
185
172
186
173
const popperEl = unrefElement ( popper ) ;
187
174
const referenceEl = get ( virtualReference ) || unrefElement ( reference ) ;
188
175
189
- if ( ! ( popperEl instanceof HTMLElement ) )
190
- return ;
176
+ if ( ! ( popperEl instanceof HTMLElement ) || ! referenceEl ) {
177
+ return null ;
178
+ }
191
179
192
- if ( ! referenceEl )
180
+ return { popperEl, referenceEl } ;
181
+ }
182
+
183
+ function initializePopper ( onInvalidate : ( cleanupFn : ( ) => void ) => void ) {
184
+ const elements = getValidElements ( ) ;
185
+ if ( ! elements )
193
186
return ;
194
187
188
+ const { popperEl, referenceEl } = elements ;
189
+
195
190
const value = createPopper ( referenceEl , popperEl , get ( popperConfig ) ) ;
196
191
197
192
set ( instance , value ) ;
198
193
onInvalidate ( value . destroy ) ;
199
194
}
200
195
201
196
useResizeObserver ( [ reference , popper ] , async ( ) => {
202
- const instanceVal = get ( instance ) ;
203
- if ( get ( open ) && instanceVal )
204
- await instanceVal . update ( ) ;
197
+ if ( isDefined ( instance ) )
198
+ await get ( instance ) . update ( ) ;
205
199
} ) ;
206
200
207
201
onMounted ( ( ) => {
0 commit comments