@@ -16,6 +16,7 @@ export type TargetType = "click" | "hover";
16
16
export type PopperType = "blur" | "click" | "hover" | "none" ;
17
17
18
18
export interface PopoverProps {
19
+ /// Undefined if uncontrolled. If controlled, true for open, false for closed
19
20
isOpen ?: boolean ;
20
21
popperClassName ?: string ;
21
22
popperContent ?: React . ReactNode ;
@@ -26,6 +27,8 @@ export interface PopoverProps {
26
27
targetType : TargetType ;
27
28
wrapperElementProps ?: any ;
28
29
wrapperElementType ?: string ;
30
+ /// If uncontrolled - a callback handler to notify when to change the state
31
+ /// If controlled - a callback handler to notify when isOpen should be changed
29
32
onOpenChange ?: ( isOpen : boolean ) => void ;
30
33
}
31
34
@@ -55,23 +58,19 @@ export class Popover extends React.Component<PopoverProps, PopoverState> {
55
58
public open ( ) {
56
59
if ( this . props . isOpen != null ) {
57
60
console . warn (
58
- "Popover's open() can only be used when popover control's it's open state, not when `open ` is passed as a prop"
61
+ "Popover's open() can only be used when popover control's it's open state, not when `isOpen ` is passed as a prop"
59
62
) ;
60
63
}
61
- this . setState ( {
62
- isOpen : true
63
- } ) ;
64
+ this . onChangeOpen ( true ) ;
64
65
}
65
66
66
67
public close ( ) {
67
68
if ( this . props . isOpen != null ) {
68
69
console . warn (
69
- "Popover's open() can only be used when popover control's it's open state, not when `open ` is passed as a prop"
70
+ "Popover's open() can only be used when popover control's it's open state, not when `isOpen ` is passed as a prop"
70
71
) ;
71
72
}
72
- this . setState ( {
73
- isOpen : false
74
- } ) ;
73
+ this . onChangeOpen ( false ) ;
75
74
}
76
75
77
76
componentDidUpdate ( prevProps : PopoverProps , prevState : PopoverState ) {
@@ -82,10 +81,7 @@ export class Popover extends React.Component<PopoverProps, PopoverState> {
82
81
83
82
const { onOpenChange } = this . props ;
84
83
if ( onOpenChange != null ) {
85
- if ( this . props . isOpen !== prevProps . isOpen ) {
86
- onOpenChange ( this . props . isOpen || false ) ;
87
- }
88
- if ( this . state . isOpen !== prevState . isOpen ) {
84
+ if ( this . props . isOpen == null && this . state . isOpen !== prevState . isOpen ) {
89
85
onOpenChange ( this . state . isOpen ) ;
90
86
}
91
87
}
@@ -113,23 +109,14 @@ export class Popover extends React.Component<PopoverProps, PopoverState> {
113
109
}
114
110
115
111
private renderTarget ( ) {
116
- const {
117
- popperType,
118
- targetClassName,
119
- targetContent,
120
- targetType
121
- } = this . props ;
112
+ const { targetClassName, targetContent, targetType } = this . props ;
122
113
123
114
switch ( targetType ) {
124
115
case "click" :
125
116
return (
126
117
< TargetClick
127
118
className = { targetClassName }
128
- onClick = { ( ) => {
129
- this . setState ( {
130
- isOpen : ! this . state . isOpen
131
- } ) ;
132
- } }
119
+ onClick = { this . onReverseState }
133
120
ref = { ref => {
134
121
this . targetClickRef = ref ;
135
122
} }
@@ -141,17 +128,7 @@ export class Popover extends React.Component<PopoverProps, PopoverState> {
141
128
return (
142
129
< TargetHover
143
130
className = { targetClassName }
144
- onHoverChange = { ( isHovering : boolean ) => {
145
- if (
146
- popperType === "hover" ||
147
- popperType === "none" ||
148
- isHovering
149
- ) {
150
- this . setState ( {
151
- isOpen : isHovering
152
- } ) ;
153
- }
154
- } }
131
+ onHoverChange = { this . onTargetHoverChange }
155
132
>
156
133
{ targetContent }
157
134
</ TargetHover >
@@ -161,6 +138,14 @@ export class Popover extends React.Component<PopoverProps, PopoverState> {
161
138
}
162
139
}
163
140
141
+ private onTargetHoverChange = ( isHovering : boolean ) => {
142
+ const { popperType } = this . props ;
143
+
144
+ if ( popperType === "hover" || popperType === "none" || isHovering ) {
145
+ this . onChangeOpen ( isHovering ) ;
146
+ }
147
+ } ;
148
+
164
149
private renderPopper ( ) {
165
150
const { popperContent, popperOptions, popperType } = this . props ;
166
151
@@ -173,86 +158,107 @@ export class Popover extends React.Component<PopoverProps, PopoverState> {
173
158
}
174
159
175
160
switch ( popperType ) {
176
- case "click " :
161
+ case "blur " :
177
162
return (
178
- < PopperClick
163
+ < PopperBlur
179
164
className = { className }
180
165
setScheduleUpdate = { this . setScheduleUpdate }
181
166
{ ...popperOptions }
182
- onDismiss = { ( ) => {
183
- this . setState ( {
184
- isOpen : false
185
- } ) ;
186
- } }
167
+ onDismiss = { this . onPopperBlurDismiss }
187
168
>
188
169
{ popperContent }
189
- </ PopperClick >
170
+ </ PopperBlur >
190
171
) ;
191
- case "hover " :
172
+ case "click " :
192
173
return (
193
- < PopperHover
174
+ < PopperClick
194
175
className = { className }
195
176
setScheduleUpdate = { this . setScheduleUpdate }
196
177
{ ...popperOptions }
197
- onHoverChange = { ( isHovering : boolean ) => {
198
- this . setState ( {
199
- isOpen : isHovering
200
- } ) ;
201
- } }
178
+ onDismiss = { this . onClose }
202
179
>
203
180
{ popperContent }
204
- </ PopperHover >
181
+ </ PopperClick >
205
182
) ;
206
- case "none " :
183
+ case "hover " :
207
184
return (
208
185
< PopperHover
209
186
className = { className }
210
187
setScheduleUpdate = { this . setScheduleUpdate }
211
188
{ ...popperOptions }
212
- onHoverChange = { ( isHovering : boolean ) => { } }
189
+ onHoverChange = { this . onChangeOpen }
213
190
>
214
191
{ popperContent }
215
192
</ PopperHover >
216
193
) ;
217
- case "blur " :
194
+ case "none " :
218
195
return (
219
- < PopperBlur
196
+ < PopperHover
220
197
className = { className }
221
198
setScheduleUpdate = { this . setScheduleUpdate }
222
199
{ ...popperOptions }
223
- onDismiss = { ( event : MouseEvent ) => {
224
- if ( ! this . state . isOpen ) {
225
- return ;
226
- }
227
- if ( this . targetClickRef != null ) {
228
- let target = ReactDOM . findDOMNode ( this . targetClickRef ) ;
229
- let isTargetOrChild = false ;
230
- let sourceElement : Element | null = event . srcElement ;
231
- while ( sourceElement != null ) {
232
- if ( target == sourceElement ) {
233
- isTargetOrChild = true ;
234
- break ;
235
- }
236
- sourceElement = sourceElement . parentElement ;
237
- }
238
-
239
- if ( isTargetOrChild ) {
240
- return ;
241
- }
242
- }
243
- this . setState ( {
244
- isOpen : false
245
- } ) ;
246
- } }
200
+ onHoverChange = { ( ) => { } }
247
201
>
248
202
{ popperContent }
249
- </ PopperBlur >
203
+ </ PopperHover >
250
204
) ;
251
205
default :
252
206
throw new Error ( "Target type must be either click or hover" ) ;
253
207
}
254
208
}
255
209
210
+ private onPopperBlurDismiss = ( event : MouseEvent ) => {
211
+ if (
212
+ ( this . state . isOpen === false && this . props . isOpen == null ) ||
213
+ this . props . isOpen === false
214
+ ) {
215
+ return ;
216
+ }
217
+ if ( this . targetClickRef != null ) {
218
+ let target = ReactDOM . findDOMNode ( this . targetClickRef ) ;
219
+ let isTargetOrChild = false ;
220
+ let sourceElement : null | Element = event . srcElement ;
221
+ while ( sourceElement != null ) {
222
+ if ( target == sourceElement ) {
223
+ isTargetOrChild = true ;
224
+ break ;
225
+ }
226
+ sourceElement = sourceElement . parentElement ;
227
+ }
228
+
229
+ if ( isTargetOrChild ) {
230
+ return ;
231
+ }
232
+ }
233
+
234
+ this . onClose ( ) ;
235
+ } ;
236
+
237
+ private onClose = ( ) => {
238
+ this . onChangeOpen ( false ) ;
239
+ } ;
240
+
241
+ private onReverseState = ( ) => {
242
+ if ( this . props . isOpen == null ) {
243
+ this . onChangeOpen ( ! this . state . isOpen ) ;
244
+ } else {
245
+ this . onChangeOpen ( ! this . props . isOpen ) ;
246
+ }
247
+ } ;
248
+
249
+ private onChangeOpen = ( newIsOpen : boolean ) => {
250
+ const { isOpen, onOpenChange } = this . props ;
251
+ if ( isOpen != null && onOpenChange != null ) {
252
+ if ( isOpen != newIsOpen ) {
253
+ onOpenChange ( newIsOpen ) ;
254
+ }
255
+ } else if ( this . state . isOpen != newIsOpen ) {
256
+ this . setState ( {
257
+ isOpen : newIsOpen
258
+ } ) ;
259
+ }
260
+ } ;
261
+
256
262
private setScheduleUpdate = ( ref : null | ( ( ) => void ) ) => {
257
263
if ( ref != null ) {
258
264
this . scheduleUpdate = ref ;
0 commit comments