Skip to content

Commit

Permalink
fix(pointer): blur activeElement on click outside of focusable (#834)
Browse files Browse the repository at this point in the history
  • Loading branch information
ph-fritsche authored Jan 16, 2022
1 parent d35ca69 commit d64167c
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 13 deletions.
3 changes: 1 addition & 2 deletions src/pointer/pointerPress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import {
ApiLevel,
assertPointerEvents,
findClosest,
firePointerEvent,
focus,
isDisabled,
Expand Down Expand Up @@ -278,7 +277,7 @@ function mousedownDefaultBehavior({
// The closest focusable element is focused when a `mousedown` would have been fired.
// Even if there was no `mousedown` because the element was disabled.
// A `mousedown` that preventsDefault cancels this though.
focus(findClosest(target, isFocusable) ?? target.ownerDocument.body)
focus(target)

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

Expand Down
4 changes: 2 additions & 2 deletions src/utility/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ export async function upload(
.slice(0, input.multiple ? undefined : 1)

// blur fires when the file selector pops up
blur(element)
blur(input)
// focus fires when they make their selection
focus(element)
focus(input)

// do not fire an input event if the file selection does not change
if (
Expand Down
20 changes: 14 additions & 6 deletions src/utils/focus/focus.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import {eventWrapper} from '../misc/eventWrapper'
import {findClosest} from '../misc/findClosest'
import {getActiveElement} from './getActiveElement'
import {isFocusable} from './isFocusable'
import {updateSelectionOnFocus} from './selection'

/**
* Focus closest focusable element.
*/
function focus(element: Element) {
if (!isFocusable(element)) return
const target = findClosest(element, isFocusable)

const isAlreadyActive = getActiveElement(element.ownerDocument) === element
if (isAlreadyActive) return
const activeElement = getActiveElement(element.ownerDocument)
if ((target ?? element.ownerDocument.body) === activeElement) {
return
} else if (target) {
eventWrapper(() => target.focus())
} else {
eventWrapper(() => (activeElement as HTMLElement | null)?.blur())
}

eventWrapper(() => element.focus())

updateSelectionOnFocus(element)
updateSelectionOnFocus(target ?? element.ownerDocument.body)
}

export {focus}
6 changes: 3 additions & 3 deletions src/utils/misc/findClosest.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export function findClosest(
export function findClosest<T extends Element>(
element: Element,
callback: (e: Element) => boolean,
) {
callback: (e: Element) => e is T,
): T | undefined {
let el: Element | null = element
do {
if (callback(el)) {
Expand Down
13 changes: 13 additions & 0 deletions tests/pointer/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ test('move focus to closest focusable element', async () => {
expect(element).toHaveFocus()
})

test('blur when outside of focusable context', async () => {
const {
elements: [focusable, notFocusable],
} = setup(`
<div tabIndex="-1"></div>
<div></div>
`)
focusable.focus()

await userEvent.pointer({keys: '[MouseLeft>]', target: notFocusable})
expect(document.body).toHaveFocus()
})

test('mousedown handlers can prevent moving focus', async () => {
const {element} = setup<HTMLInputElement>(`<input/>`)
element.addEventListener('mousedown', e => e.preventDefault())
Expand Down
2 changes: 2 additions & 0 deletions tests/utility/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ test('relay click/upload on label to file input', async () => {
label[for="element"] - click: primary
input#element[value=""] - click: primary
input#element[value=""] - focusin
input#element[value=""] - focusout
input#element[value=""] - focusin
input#element[value="C:\\\\fakepath\\\\hello.png"] - input
input#element[value="C:\\\\fakepath\\\\hello.png"] - change
`)
Expand Down

0 comments on commit d64167c

Please sign in to comment.