12
12
let focusable: boolean = true ;
13
13
let clickable: boolean = $derived (trigger === " click" );
14
14
let hoverable: boolean = $derived (trigger === " hover" );
15
+
15
16
let popover: HTMLElement | null = $state (null );
16
17
let invoker: HTMLElement | null = null ;
17
18
let referenceElement: HTMLElement | null = null ;
54
55
// throttle
55
56
isTriggered = true ;
56
57
await new Promise ((resolve ) => setTimeout (resolve , triggerDelay ));
57
- if (! isTriggered ) return ;
58
+ if (! isTriggered ) {
59
+ return ;
60
+ }
58
61
59
62
ev .preventDefault ();
60
63
73
76
}
74
77
75
78
async function close_popover(ev : Event ) {
79
+ // For click triggers, don't close on focusout events from inside the popover
80
+ if (trigger === " click" && ev .type === " focusout" ) {
81
+ const relatedTarget = (ev as FocusEvent ).relatedTarget as HTMLElement ;
82
+
83
+ // If focus is moving to somewhere inside the popover, don't close
84
+ if (popover && relatedTarget && popover .contains (relatedTarget )) {
85
+ return ;
86
+ }
87
+
88
+ // If focus is moving to nowhere (like when clicking), don't close for click triggers
89
+ if (! relatedTarget ) {
90
+ return ;
91
+ }
92
+ }
93
+
76
94
isTriggered = false ;
77
95
await new Promise ((resolve ) => setTimeout (resolve , triggerDelay ));
78
- if (isTriggered ) return ;
96
+ if (isTriggered ) {
97
+ return ;
98
+ }
79
99
80
100
// if popover has focus don't close when leaving the invoker
81
- if (ev ?.type === " mouseleave" && popover ?.contains (popover .ownerDocument .activeElement )) return ;
82
- if (ev ?.type === " focusout" && popover ?.contains (popover .ownerDocument .activeElement )) return ;
101
+ if (ev ?.type === " mouseleave" && popover ?.contains (popover .ownerDocument .activeElement )) {
102
+ return ;
103
+ }
104
+ if (ev ?.type === " focusout" && popover ?.contains (popover .ownerDocument .activeElement )) {
105
+ return ;
106
+ }
83
107
84
108
isOpen = false ;
85
109
}
97
121
if (ev .newState === " open" ) {
98
122
autoUpdateDestroy = dom .autoUpdate (referenceElement ?? invoker , popover , updatePopoverPosition );
99
123
popover .ownerDocument .addEventListener (" click" , closeOnClickOutside );
100
- popover .ownerDocument .addEventListener (" keydown" , closeOnEscape ); // ✅ Add this line
124
+ popover .ownerDocument .addEventListener (" keydown" , closeOnEscape );
101
125
} else {
102
126
autoUpdateDestroy ();
103
127
popover .ownerDocument .removeEventListener (" click" , closeOnClickOutside );
104
- popover .ownerDocument .removeEventListener (" keydown" , closeOnEscape ); // ✅ Add this line
128
+ popover .ownerDocument .removeEventListener (" keydown" , closeOnEscape );
105
129
}
106
130
}
107
131
156
180
}
157
181
}
158
182
159
- /**
160
- * Close the popper when clicking outside of it.
161
- * This is necessary to get around a bug in Safari where clicking outside of the open popper does not close it.
162
- */
163
183
function closeOnClickOutside(event : MouseEvent ) {
164
- if (popover && ! event .composedPath ().includes (popover ) && ! triggerEls .some ((el ) => event .composedPath ().includes (el ))) {
184
+ if (! popover ) {
185
+ return ;
186
+ }
187
+
188
+ const clickPath = event .composedPath ();
189
+
190
+ const isClickInsidePopover = clickPath .includes (popover );
191
+ const isClickOnTrigger = triggerEls .some ((el ) => clickPath .includes (el ));
192
+
193
+ // Only close if click is outside both popover and trigger elements
194
+ if (! isClickInsidePopover && ! isClickOnTrigger ) {
165
195
close_popover (event );
166
- // Update isOpen state when clicking outside
167
196
isOpen = false ;
168
197
}
169
198
}
204
233
@prop transition = fade
205
234
@prop children
206
235
@prop ...restProps
207
- -->
236
+ -->
0 commit comments