@@ -73,14 +73,32 @@ function isEventPointWithin(node: MaybeElement, event: Event) {
73
73
)
74
74
}
75
75
76
- function isEventWithinScrollbar ( event : Event , target : HTMLElement ) : boolean {
77
- if ( ! target || ! isPointerEvent ( event ) ) return false
76
+ function isPointInRect ( rect : { x : number ; y : number ; width : number ; height : number } , point : { x : number ; y : number } ) {
77
+ return rect . y <= point . y && point . y <= rect . y + rect . height && rect . x <= point . x && point . x <= rect . x + rect . width
78
+ }
79
+
80
+ function isEventWithinScrollbar ( event : Event , ancestor : HTMLElement ) : boolean {
81
+ if ( ! ancestor || ! isPointerEvent ( event ) ) return false
82
+
83
+ const isScrollableY = ancestor . scrollHeight > ancestor . clientHeight
84
+ const onScrollbarY = isScrollableY && event . clientX > ancestor . offsetLeft + ancestor . clientWidth
85
+
86
+ const isScrollableX = ancestor . scrollWidth > ancestor . clientWidth
87
+ const onScrollbarX = isScrollableX && event . clientY > ancestor . offsetTop + ancestor . clientHeight
78
88
79
- const isScrollableY = target . scrollHeight > target . clientHeight
80
- const onScrollbarY = isScrollableY && event . clientX > target . clientWidth
89
+ const rect = {
90
+ x : ancestor . offsetLeft ,
91
+ y : ancestor . offsetTop ,
92
+ width : ancestor . clientWidth + ( isScrollableY ? 16 : 0 ) ,
93
+ height : ancestor . clientHeight + ( isScrollableX ? 16 : 0 ) ,
94
+ }
95
+
96
+ const point = {
97
+ x : event . clientX ,
98
+ y : event . clientY ,
99
+ }
81
100
82
- const isScrollableX = target . scrollWidth > target . clientWidth
83
- const onScrollbarX = isScrollableX && event . clientY > target . clientHeight
101
+ if ( ! isPointInRect ( rect , point ) ) return false
84
102
85
103
return onScrollbarY || onScrollbarX
86
104
}
@@ -98,17 +116,27 @@ function trackInteractOutsideImpl(node: MaybeElement, options: InteractOutsideOp
98
116
function isEventOutside ( event : Event ) : boolean {
99
117
const target = getEventTarget ( event )
100
118
if ( ! isHTMLElement ( target ) ) return false
119
+
101
120
// ignore disconnected nodes (removed from DOM)
102
121
if ( ! target . isConnected ) return false
122
+
103
123
// ignore nodes that are inside the component
104
124
if ( contains ( node , target ) ) return false
125
+
105
126
// Ex: password manager selection
106
127
if ( isEventPointWithin ( node , event ) ) return false
128
+
107
129
// Ex: page content that is scrollable
108
- if ( isEventWithinScrollbar ( event , target ) ) return false
130
+ const triggerEl = doc . querySelector ( `[aria-controls="${ node ! . id } "]` )
131
+ if ( triggerEl ) {
132
+ const triggerAncestor = getNearestOverflowAncestor ( triggerEl )
133
+ if ( isEventWithinScrollbar ( event , triggerAncestor ) ) return false
134
+ }
135
+
109
136
// Ex: dialog positioner that is scrollable
110
- const scrollParent = getNearestOverflowAncestor ( node ! )
111
- if ( isEventWithinScrollbar ( event , scrollParent ) ) return false
137
+ const nodeAncestor = getNearestOverflowAncestor ( node ! )
138
+ if ( isEventWithinScrollbar ( event , nodeAncestor ) ) return false
139
+
112
140
// Custom exclude function
113
141
return ! exclude ?.( target )
114
142
}
0 commit comments