@@ -37,12 +37,15 @@ export interface CDropdownProps extends HTMLAttributes<HTMLDivElement | HTMLLIEl
37
37
* </CDropdownMenu>
38
38
* </CDropdown>
39
39
*
40
- * @type 'start' | 'end' | { xs: 'start' | 'end' } | { sm: 'start' | 'end' } | { md: 'start' | 'end' } | { lg: 'start' | 'end' } | { xl: 'start' | 'end'} | { xxl: 'start' | 'end'}
40
+ * @type 'start' | 'end' | { xs: 'start' | 'end' } | { sm: 'start' | 'end' } |
41
+ * { md: 'start' | 'end' } | { lg: 'start' | 'end' } | { xl: 'start' | 'end'} |
42
+ * { xxl: 'start' | 'end'}
41
43
*/
42
44
alignment ?: Alignments
43
45
44
46
/**
45
- * Determines the root node component (native HTML element or a custom React component) for the React Dropdown.
47
+ * Determines the root node component (native HTML element or a custom React
48
+ * component) for the React Dropdown.
46
49
*/
47
50
as ?: ElementType
48
51
@@ -65,7 +68,8 @@ export interface CDropdownProps extends HTMLAttributes<HTMLDivElement | HTMLLIEl
65
68
className ?: string
66
69
67
70
/**
68
- * Appends the React Dropdown Menu to a specific element. You can pass an HTML element or a function returning an element. Defaults to `document.body`.
71
+ * Appends the React Dropdown Menu to a specific element. You can pass an HTML
72
+ * element or a function returning an element. Defaults to `document.body`.
69
73
*
70
74
* @example
71
75
* // Append the menu to a custom container
@@ -78,7 +82,8 @@ export interface CDropdownProps extends HTMLAttributes<HTMLDivElement | HTMLLIEl
78
82
container ?: DocumentFragment | Element | ( ( ) => DocumentFragment | Element | null ) | null
79
83
80
84
/**
81
- * Applies a darker color scheme to the React Dropdown Menu, often used within dark navbars.
85
+ * Applies a darker color scheme to the React Dropdown Menu, often used within
86
+ * dark navbars.
82
87
*/
83
88
dark ?: boolean
84
89
@@ -88,7 +93,8 @@ export interface CDropdownProps extends HTMLAttributes<HTMLDivElement | HTMLLIEl
88
93
direction ?: 'center' | 'dropup' | 'dropup-center' | 'dropend' | 'dropstart'
89
94
90
95
/**
91
- * Defines x and y offsets ([x, y]) for the React Dropdown Menu relative to its target.
96
+ * Defines x and y offsets ([x, y]) for the React Dropdown Menu relative to
97
+ * its target.
92
98
*
93
99
* @example
94
100
* // Offset the menu 10px in X and 5px in Y direction
@@ -111,19 +117,23 @@ export interface CDropdownProps extends HTMLAttributes<HTMLDivElement | HTMLLIEl
111
117
onShow ?: ( ) => void
112
118
113
119
/**
114
- * Determines the placement of the React Dropdown Menu after Popper.js modifiers.
120
+ * Determines the placement of the React Dropdown Menu after Popper.js
121
+ * modifiers.
115
122
*
116
123
* @type 'auto' | 'auto-start' | 'auto-end' | 'top-end' | 'top' | 'top-start' | 'bottom-end' | 'bottom' | 'bottom-start' | 'right-start' | 'right' | 'right-end' | 'left-start' | 'left' | 'left-end'
117
124
*/
118
125
placement ?: Placements
119
126
120
127
/**
121
- * Enables or disables dynamic positioning via Popper.js for the React Dropdown Menu.
128
+ * Enables or disables dynamic positioning via Popper.js for the React
129
+ * Dropdown Menu.
122
130
*/
123
131
popper ?: boolean
124
132
125
133
/**
126
- * Provides a custom Popper.js configuration or a function that returns a modified Popper.js configuration for advanced positioning of the React Dropdown Menu. [Read more](https://popper.js.org/docs/v2/constructors/#options)
134
+ * Provides a custom Popper.js configuration or a function that returns a
135
+ * modified Popper.js configuration for advanced positioning of the React
136
+ * Dropdown Menu. [Read more](https://popper.js.org/docs/v2/constructors/#options)
127
137
*
128
138
* @example
129
139
* // Providing a custom popper config
@@ -143,7 +153,8 @@ export interface CDropdownProps extends HTMLAttributes<HTMLDivElement | HTMLLIEl
143
153
popperConfig ?: Partial < Options > | ( ( defaultPopperConfig : Partial < Options > ) => Partial < Options > )
144
154
145
155
/**
146
- * Renders the React Dropdown Menu using a React Portal, allowing it to escape the DOM hierarchy for improved positioning.
156
+ * Renders the React Dropdown Menu using a React Portal, allowing it to escape
157
+ * the DOM hierarchy for improved positioning.
147
158
*
148
159
* @since 4.8.0
149
160
*/
@@ -202,6 +213,7 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps>
202
213
const dropdownMenuRef = useRef < HTMLDivElement | HTMLUListElement > ( null )
203
214
const forkedRef = useForkedRef ( ref , dropdownRef )
204
215
const [ dropdownToggleElement , setDropdownToggleElement ] = useState < HTMLElement | null > ( null )
216
+ const [ pendingKeyDownEvent , setPendingKeyDownEvent ] = useState < KeyboardEvent | null > ( null )
205
217
const [ _visible , setVisible ] = useState ( visible )
206
218
const { initPopper, destroyPopper } = usePopper ( )
207
219
@@ -249,29 +261,14 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps>
249
261
}
250
262
} , [ dropdownToggleElement ] )
251
263
252
- const handleShow = ( ) => {
253
- const toggleElement = dropdownToggleElement
254
- const menuElement = dropdownMenuRef . current
255
-
256
- if ( toggleElement && menuElement ) {
257
- setVisible ( true )
258
-
259
- if ( allowPopperUse ) {
260
- initPopper ( toggleElement , menuElement , computedPopperConfig )
261
- }
262
-
263
- toggleElement . focus ( )
264
- toggleElement . addEventListener ( 'keydown' , handleKeydown )
265
- menuElement . addEventListener ( 'keydown' , handleKeydown )
266
-
267
- window . addEventListener ( 'mouseup' , handleMouseUp )
268
- window . addEventListener ( 'keyup' , handleKeyup )
269
-
270
- onShow ?.( )
264
+ useEffect ( ( ) => {
265
+ if ( pendingKeyDownEvent !== null ) {
266
+ handleKeydown ( pendingKeyDownEvent )
267
+ setPendingKeyDownEvent ( null )
271
268
}
272
- }
269
+ } , [ pendingKeyDownEvent ] )
273
270
274
- const handleHide = ( ) => {
271
+ const handleHide = useCallback ( ( ) => {
275
272
setVisible ( false )
276
273
277
274
const toggleElement = dropdownToggleElement
@@ -288,51 +285,96 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps>
288
285
window . removeEventListener ( 'keyup' , handleKeyup )
289
286
290
287
onHide ?.( )
291
- }
288
+ } , [ dropdownToggleElement , allowPopperUse , destroyPopper , onHide ] )
292
289
293
- const handleKeydown = ( event : KeyboardEvent ) => {
294
- if (
295
- _visible &&
296
- dropdownMenuRef . current &&
297
- ( event . key === 'ArrowDown' || event . key === 'ArrowUp' )
298
- ) {
290
+ const handleKeydown = useCallback ( ( event : KeyboardEvent ) => {
291
+ if ( dropdownMenuRef . current && ( event . key === 'ArrowDown' || event . key === 'ArrowUp' ) ) {
299
292
event . preventDefault ( )
300
293
const target = event . target as HTMLElement
301
- const items : HTMLElement [ ] = Array . from (
302
- dropdownMenuRef . current . querySelectorAll ( '.dropdown-item:not(.disabled):not(:disabled)' )
303
- )
294
+ const items = [
295
+ ...dropdownMenuRef . current . querySelectorAll (
296
+ '.dropdown-item:not(.disabled):not(:disabled)'
297
+ ) ,
298
+ ] as HTMLElement [ ]
304
299
getNextActiveElement ( items , target , event . key === 'ArrowDown' , true ) . focus ( )
305
300
}
306
- }
301
+ } , [ ] )
307
302
308
- const handleKeyup = ( event : KeyboardEvent ) => {
309
- if ( autoClose === false ) {
310
- return
311
- }
303
+ const handleKeyup = useCallback (
304
+ ( event : KeyboardEvent ) => {
305
+ if ( autoClose === false ) {
306
+ return
307
+ }
312
308
313
- if ( event . key === 'Escape' ) {
314
- handleHide ( )
315
- }
316
- }
309
+ if ( event . key === 'Escape' ) {
310
+ handleHide ( )
311
+ dropdownToggleElement ?. focus ( )
312
+ }
313
+ } ,
314
+ [ autoClose , handleHide ]
315
+ )
317
316
318
- const handleMouseUp = ( event : Event ) => {
319
- if ( ! dropdownToggleElement || ! dropdownMenuRef . current ) {
320
- return
321
- }
317
+ const handleMouseUp = useCallback (
318
+ ( event : Event ) => {
319
+ if ( ! dropdownToggleElement || ! dropdownMenuRef . current ) {
320
+ return
321
+ }
322
322
323
- if ( dropdownToggleElement . contains ( event . target as HTMLElement ) ) {
324
- return
325
- }
323
+ if ( dropdownToggleElement . contains ( event . target as HTMLElement ) ) {
324
+ return
325
+ }
326
326
327
- if (
328
- autoClose === true ||
329
- ( autoClose === 'inside' && dropdownMenuRef . current . contains ( event . target as HTMLElement ) ) ||
330
- ( autoClose === 'outside' && ! dropdownMenuRef . current . contains ( event . target as HTMLElement ) )
331
- ) {
332
- setTimeout ( ( ) => handleHide ( ) , 1 )
333
- return
334
- }
335
- }
327
+ if (
328
+ autoClose === true ||
329
+ ( autoClose === 'inside' &&
330
+ dropdownMenuRef . current . contains ( event . target as HTMLElement ) ) ||
331
+ ( autoClose === 'outside' &&
332
+ ! dropdownMenuRef . current . contains ( event . target as HTMLElement ) )
333
+ ) {
334
+ setTimeout ( ( ) => handleHide ( ) , 1 )
335
+ return
336
+ }
337
+ } ,
338
+ [ autoClose , dropdownToggleElement , handleHide ]
339
+ )
340
+
341
+ const handleShow = useCallback (
342
+ ( event ?: KeyboardEvent ) => {
343
+ const toggleElement = dropdownToggleElement
344
+ const menuElement = dropdownMenuRef . current
345
+
346
+ if ( toggleElement && menuElement ) {
347
+ setVisible ( true )
348
+
349
+ if ( allowPopperUse ) {
350
+ initPopper ( toggleElement , menuElement , computedPopperConfig )
351
+ }
352
+
353
+ toggleElement . focus ( )
354
+ toggleElement . addEventListener ( 'keydown' , handleKeydown )
355
+ menuElement . addEventListener ( 'keydown' , handleKeydown )
356
+
357
+ window . addEventListener ( 'mouseup' , handleMouseUp )
358
+ window . addEventListener ( 'keyup' , handleKeyup )
359
+
360
+ if ( event && ( event . key === 'ArrowDown' || event . key === 'ArrowUp' ) ) {
361
+ setPendingKeyDownEvent ( event )
362
+ }
363
+
364
+ onShow ?.( )
365
+ }
366
+ } ,
367
+ [
368
+ dropdownToggleElement ,
369
+ allowPopperUse ,
370
+ initPopper ,
371
+ computedPopperConfig ,
372
+ handleKeydown ,
373
+ handleMouseUp ,
374
+ handleKeyup ,
375
+ onShow ,
376
+ ]
377
+ )
336
378
337
379
const contextValues = {
338
380
alignment,
0 commit comments