Skip to content

Commit cf37758

Browse files
committed
fix(CDropdown): closes #431; prevents closing the dropdown menu when clicking on the scrollbar
1 parent 76267eb commit cf37758

File tree

4 files changed

+105
-11
lines changed

4 files changed

+105
-11
lines changed

packages/coreui-react/src/components/dropdown/CDropdown.tsx

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps>
281281
toggleElement?.removeEventListener('keydown', handleKeydown)
282282
menuElement?.removeEventListener('keydown', handleKeydown)
283283

284-
window.removeEventListener('mouseup', handleMouseUp)
284+
window.removeEventListener('click', handleClick)
285285
window.removeEventListener('keyup', handleKeyup)
286286

287287
onHide?.()
@@ -314,25 +314,37 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps>
314314
[autoClose, handleHide]
315315
)
316316

317-
const handleMouseUp = useCallback(
318-
(event: Event) => {
317+
const handleClick = useCallback(
318+
(event: MouseEvent) => {
319319
if (!dropdownToggleElement || !dropdownMenuRef.current) {
320320
return
321321
}
322322

323-
if (dropdownToggleElement.contains(event.target as HTMLElement)) {
323+
if ((event as MouseEvent).button === 2) {
324+
return
325+
}
326+
327+
const composedPath = event.composedPath()
328+
const isOnToggle = composedPath.includes(dropdownToggleElement)
329+
const isOnMenu = composedPath.includes(dropdownMenuRef.current)
330+
331+
if (isOnToggle) {
332+
return
333+
}
334+
335+
const target = event.target as HTMLElement | null
336+
const FORM_TAG_RE = /^(input|select|option|textarea|form|button|label)$/i
337+
338+
if (isOnMenu && target && FORM_TAG_RE.test(target.tagName)) {
324339
return
325340
}
326341

327342
if (
328343
autoClose === true ||
329-
(autoClose === 'inside' &&
330-
dropdownMenuRef.current.contains(event.target as HTMLElement)) ||
331-
(autoClose === 'outside' &&
332-
!dropdownMenuRef.current.contains(event.target as HTMLElement))
344+
(autoClose === 'inside' && isOnMenu) ||
345+
(autoClose === 'outside' && !isOnMenu)
333346
) {
334347
setTimeout(() => handleHide(), 1)
335-
return
336348
}
337349
},
338350
[autoClose, dropdownToggleElement, handleHide]
@@ -354,7 +366,7 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps>
354366
toggleElement.addEventListener('keydown', handleKeydown)
355367
menuElement.addEventListener('keydown', handleKeydown)
356368

357-
window.addEventListener('mouseup', handleMouseUp)
369+
window.addEventListener('click', handleClick)
358370
window.addEventListener('keyup', handleKeyup)
359371

360372
if (event && (event.key === 'ArrowDown' || event.key === 'ArrowUp')) {
@@ -369,8 +381,8 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps>
369381
allowPopperUse,
370382
initPopper,
371383
computedPopperConfig,
384+
handleClick,
372385
handleKeydown,
373-
handleMouseUp,
374386
handleKeyup,
375387
onShow,
376388
]
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from 'react'
2+
import { CDropdown, CDropdownItem, CDropdownMenu, CDropdownToggle } from '@coreui/react'
3+
4+
export const DropdownOptionsAutoCloseBehaviorExample = () => {
5+
return (
6+
<div className="d-flex gap-1">
7+
<CDropdown>
8+
<CDropdownToggle color="secondary">Default dropdown</CDropdownToggle>
9+
<CDropdownMenu>
10+
<CDropdownItem href="#">Action</CDropdownItem>
11+
<CDropdownItem href="#">Another action</CDropdownItem>
12+
<CDropdownItem href="#">Something else here</CDropdownItem>
13+
</CDropdownMenu>
14+
</CDropdown>
15+
<CDropdown autoClose="inside">
16+
<CDropdownToggle color="secondary">Clickable inside</CDropdownToggle>
17+
<CDropdownMenu>
18+
<CDropdownItem href="#">Action</CDropdownItem>
19+
<CDropdownItem href="#">Another action</CDropdownItem>
20+
<CDropdownItem href="#">Something else here</CDropdownItem>
21+
</CDropdownMenu>
22+
</CDropdown>
23+
<CDropdown autoClose="outside">
24+
<CDropdownToggle color="secondary">Clickable outside</CDropdownToggle>
25+
<CDropdownMenu>
26+
<CDropdownItem href="#">Action</CDropdownItem>
27+
<CDropdownItem href="#">Another action</CDropdownItem>
28+
<CDropdownItem href="#">Something else here</CDropdownItem>
29+
</CDropdownMenu>
30+
</CDropdown>
31+
<CDropdown autoClose={false}>
32+
<CDropdownToggle color="secondary">Manual close</CDropdownToggle>
33+
<CDropdownMenu>
34+
<CDropdownItem href="#">Action</CDropdownItem>
35+
<CDropdownItem href="#">Another action</CDropdownItem>
36+
<CDropdownItem href="#">Something else here</CDropdownItem>
37+
</CDropdownMenu>
38+
</CDropdown>
39+
</div>
40+
)
41+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from 'react'
2+
import { CDropdown, CDropdownItem, CDropdownMenu, CDropdownToggle } from '@coreui/react'
3+
4+
export const DropdownOptionsExample = () => {
5+
return (
6+
<div className="d-flex gap-1">
7+
<CDropdown offset={[10, 20]}>
8+
<CDropdownToggle color="secondary">Offset</CDropdownToggle>
9+
<CDropdownMenu>
10+
<CDropdownItem href="#">Action</CDropdownItem>
11+
<CDropdownItem href="#">Another action</CDropdownItem>
12+
<CDropdownItem href="#">Something else here</CDropdownItem>
13+
</CDropdownMenu>
14+
</CDropdown>
15+
<CDropdown portal>
16+
<CDropdownToggle color="secondary">Portal</CDropdownToggle>
17+
<CDropdownMenu>
18+
<CDropdownItem href="#">Action</CDropdownItem>
19+
<CDropdownItem href="#">Another action</CDropdownItem>
20+
<CDropdownItem href="#">Something else here</CDropdownItem>
21+
</CDropdownMenu>
22+
</CDropdown>
23+
</div>
24+
)
25+
}

packages/docs/content/components/dropdown/index.mdx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,22 @@ Put a form within a dropdown menu, or make it into a dropdown menu.
172172

173173
<ExampleSnippet component="DropdownMenuContentFormsExample" componentName="React Dropdown" />
174174

175+
## Dropdown options
176+
177+
Use `offset` to displace the dropdown from its default position. The value is a string with two numbers separated by a comma, e.g. `offset={[10, 20]}`. Use `portal` property to render dropdowns in `body` instead of the parent element. This helps to avoid any overflow or z-index issues.
178+
179+
<ExampleSnippet component="DropdownOptionsExample" componentName="React Dropdown" />
180+
181+
### Auto close behavior
182+
183+
By default, dropdowns are closed when clicking outside of the dropdown menu or the toggle button. You can change this behavior with the `autoClose` property. Set `autoClose` to:
184+
185+
- `true` - Close on clicks inside or outside of the React.js dropdown menu.
186+
- `false` - Disable auto-close; close manually by setting the `visible={false}` (also not closed by `Escape`).
187+
- `'inside'` - Close only when clicking inside the React.js dropdown menu.
188+
- `'outside'` - Close only when clicking outside the React.js dropdown menu.
189+
190+
<ExampleSnippet component="DropdownOptionsAutoCloseBehaviorExample" componentName="React Dropdown" />
175191

176192
## API
177193

0 commit comments

Comments
 (0)