Skip to content

Commit 609cb12

Browse files
authored
Deactivate focus trap on click outside (#1717)
* Deactivate focus trap on click outside * Disable storyshots
1 parent acc04b2 commit 609cb12

File tree

3 files changed

+58
-1
lines changed

3 files changed

+58
-1
lines changed

packages/components-providers/src/FocusTrap/utils.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,16 @@ export const activateFocusTrap = (container: HTMLElement) => {
105105
node.select()
106106
}
107107
}
108+
109+
// This needs to be done on mousedown and touchstart instead of click
110+
// so that it precedes the focus event.
111+
const checkPointerDown = function (e: MouseEvent | TouchEvent) {
112+
if (!container.contains(e.target as Node)) {
113+
// immediately deactivate the trap
114+
deactivate()
115+
}
116+
}
117+
108118
// In case focus escapes the trap for some strange reason, pull it back in.
109119
const checkFocusIn = (e: FocusEvent) => {
110120
// In Firefox when you Tab out of an iframe the Document is briefly focused.
@@ -135,6 +145,14 @@ export const activateFocusTrap = (container: HTMLElement) => {
135145
}
136146

137147
document.addEventListener('focusin', checkFocusIn, true)
148+
document.addEventListener('mousedown', checkPointerDown, {
149+
capture: true,
150+
passive: false,
151+
})
152+
document.addEventListener('touchstart', checkPointerDown, {
153+
capture: true,
154+
passive: false,
155+
})
138156
document.addEventListener('keydown', checkKey, {
139157
capture: true,
140158
passive: false,
@@ -145,9 +163,11 @@ export const activateFocusTrap = (container: HTMLElement) => {
145163
}, 0)
146164

147165
// Deactivate function
148-
return () => {
166+
const deactivate = () => {
149167
clearTimeout(t)
150168
document.removeEventListener('focusin', checkFocusIn, true)
169+
document.removeEventListener('mousedown', checkPointerDown, true)
170+
document.removeEventListener('touchstart', checkPointerDown, true)
151171
document.removeEventListener('keydown', checkKey, true)
152172

153173
// If focus lands on the body, move it back to where it was before the trap
@@ -161,4 +181,5 @@ export const activateFocusTrap = (container: HTMLElement) => {
161181
elementToFocus.removeAttribute('data-notooltip')
162182
}
163183
}
184+
return deactivate
164185
}

packages/components/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.9.29]
9+
10+
### Fixed
11+
12+
- Clicking outside a focus trap deactivates it
13+
814
## [0.9.28]
915

1016
### Removed

packages/components/src/Dialog/stories/Dialog.story.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,36 @@ withCheckbox.parameters = {
108108
storyshots: { disable: true },
109109
}
110110

111+
export const ClickOutside = () => {
112+
return (
113+
<>
114+
<input
115+
type="text"
116+
style={{
117+
position: 'absolute',
118+
right: '0',
119+
top: '0',
120+
zIndex: 100,
121+
}}
122+
/>
123+
<Dialog
124+
content={
125+
<>
126+
<button>button 1</button>
127+
<button>button 2</button>
128+
</>
129+
}
130+
>
131+
<button>Open Dialog</button>
132+
</Dialog>
133+
</>
134+
)
135+
}
136+
137+
ClickOutside.arguments = {
138+
storyshots: { disable: true },
139+
}
140+
111141
export default {
112142
argTypes: {
113143
placement: {

0 commit comments

Comments
 (0)