Skip to content

Commit d64167c

Browse files
authored
fix(pointer): blur activeElement on click outside of focusable (#834)
1 parent d35ca69 commit d64167c

File tree

6 files changed

+35
-13
lines changed

6 files changed

+35
-13
lines changed

src/pointer/pointerPress.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import {
44
ApiLevel,
55
assertPointerEvents,
6-
findClosest,
76
firePointerEvent,
87
focus,
98
isDisabled,
@@ -278,7 +277,7 @@ function mousedownDefaultBehavior({
278277
// The closest focusable element is focused when a `mousedown` would have been fired.
279278
// Even if there was no `mousedown` because the element was disabled.
280279
// A `mousedown` that preventsDefault cancels this though.
281-
focus(findClosest(target, isFocusable) ?? target.ownerDocument.body)
280+
focus(target)
282281

283282
// TODO: What happens if a focus event handler interfers?
284283

src/utility/upload.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ export async function upload(
3838
.slice(0, input.multiple ? undefined : 1)
3939

4040
// blur fires when the file selector pops up
41-
blur(element)
41+
blur(input)
4242
// focus fires when they make their selection
43-
focus(element)
43+
focus(input)
4444

4545
// do not fire an input event if the file selection does not change
4646
if (

src/utils/focus/focus.ts

+14-6
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
import {eventWrapper} from '../misc/eventWrapper'
2+
import {findClosest} from '../misc/findClosest'
23
import {getActiveElement} from './getActiveElement'
34
import {isFocusable} from './isFocusable'
45
import {updateSelectionOnFocus} from './selection'
56

7+
/**
8+
* Focus closest focusable element.
9+
*/
610
function focus(element: Element) {
7-
if (!isFocusable(element)) return
11+
const target = findClosest(element, isFocusable)
812

9-
const isAlreadyActive = getActiveElement(element.ownerDocument) === element
10-
if (isAlreadyActive) return
13+
const activeElement = getActiveElement(element.ownerDocument)
14+
if ((target ?? element.ownerDocument.body) === activeElement) {
15+
return
16+
} else if (target) {
17+
eventWrapper(() => target.focus())
18+
} else {
19+
eventWrapper(() => (activeElement as HTMLElement | null)?.blur())
20+
}
1121

12-
eventWrapper(() => element.focus())
13-
14-
updateSelectionOnFocus(element)
22+
updateSelectionOnFocus(target ?? element.ownerDocument.body)
1523
}
1624

1725
export {focus}

src/utils/misc/findClosest.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
export function findClosest(
1+
export function findClosest<T extends Element>(
22
element: Element,
3-
callback: (e: Element) => boolean,
4-
) {
3+
callback: (e: Element) => e is T,
4+
): T | undefined {
55
let el: Element | null = element
66
do {
77
if (callback(el)) {

tests/pointer/select.ts

+13
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,19 @@ test('move focus to closest focusable element', async () => {
3030
expect(element).toHaveFocus()
3131
})
3232

33+
test('blur when outside of focusable context', async () => {
34+
const {
35+
elements: [focusable, notFocusable],
36+
} = setup(`
37+
<div tabIndex="-1"></div>
38+
<div></div>
39+
`)
40+
focusable.focus()
41+
42+
await userEvent.pointer({keys: '[MouseLeft>]', target: notFocusable})
43+
expect(document.body).toHaveFocus()
44+
})
45+
3346
test('mousedown handlers can prevent moving focus', async () => {
3447
const {element} = setup<HTMLInputElement>(`<input/>`)
3548
element.addEventListener('mousedown', e => e.preventDefault())

tests/utility/upload.ts

+2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ test('relay click/upload on label to file input', async () => {
6565
label[for="element"] - click: primary
6666
input#element[value=""] - click: primary
6767
input#element[value=""] - focusin
68+
input#element[value=""] - focusout
69+
input#element[value=""] - focusin
6870
input#element[value="C:\\\\fakepath\\\\hello.png"] - input
6971
input#element[value="C:\\\\fakepath\\\\hello.png"] - change
7072
`)

0 commit comments

Comments
 (0)