Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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)

AltStyle によって変換されたページ (->オリジナル) /