Skip to content

Commit 91e9597

Browse files
authored
Fix restoring focus to correct element when closing Dialog component (#3365)
* resolve focusable element when recording elements Right now, we have to record when a click / mousedown / focus event happens on an element. But when you click on a non-focusable element inside of a focusable element then we record the inner element instead of the outer one. This happens in this scenario: ```html <button> <span>click me</span> </button> ``` This solves it by resolving the closest focusable element (and we fallback to the e.target as a last resort) * update changelog
1 parent da466ec commit 91e9597

File tree

6 files changed

+36
-4
lines changed

6 files changed

+36
-4
lines changed

packages/@headlessui-react/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
- Fix hanging tests when using `anchor` prop ([#3357](https://github.com/tailwindlabs/headlessui/pull/3357))
1616
- Fix `transition` and `focus` prop combination for `PopoverPanel` component ([#3361](https://github.com/tailwindlabs/headlessui/pull/3361))
1717
- Fix outside click in nested portalled `Popover` components ([#3362](https://github.com/tailwindlabs/headlessui/pull/3362))
18+
- Fix restoring focus to correct element when closing `Dialog` component ([#3365](https://github.com/tailwindlabs/headlessui/pull/3365))
1819

1920
## [2.1.1] - 2024-06-26
2021

packages/@headlessui-react/src/utils/active-element-history.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { onDocumentReady } from './document-ready'
2+
import { focusableSelector } from './focus-management'
23

34
export let history: HTMLElement[] = []
45
onDocumentReady(() => {
@@ -7,7 +8,21 @@ onDocumentReady(() => {
78
if (e.target === document.body) return
89
if (history[0] === e.target) return
910

10-
history.unshift(e.target)
11+
let focusableElement = e.target as HTMLElement
12+
13+
// Figure out the closest focusable element, this is needed in a situation
14+
// where you click on a non-focusable element inside a focusable element.
15+
//
16+
// E.g.:
17+
//
18+
// ```html
19+
// <button>
20+
// <span>Click me</span>
21+
// </button>
22+
// ```
23+
focusableElement = focusableElement.closest(focusableSelector) as HTMLElement
24+
25+
history.unshift(focusableElement ?? e.target)
1126

1227
// Filter out DOM Nodes that don't exist anymore
1328
history = history.filter((x) => x != null && x.isConnected)

packages/@headlessui-react/src/utils/focus-management.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { getOwnerDocument } from './owner'
55

66
// Credit:
77
// - https://stackoverflow.com/a/30753870
8-
let focusableSelector = [
8+
export let focusableSelector = [
99
'[contentEditable=true]',
1010
'[tabindex]',
1111
'a[href]',

packages/@headlessui-vue/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
### Fixed
1616

1717
- Cancel outside click behavior on touch devices when scrolling ([#3266](https://github.com/tailwindlabs/headlessui/pull/3266))
18+
- Fix restoring focus to correct element when closing `Dialog` component ([#3365](https://github.com/tailwindlabs/headlessui/pull/3365))
1819

1920
## [1.7.22] - 2024-05-08
2021

packages/@headlessui-vue/src/utils/active-element-history.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { onDocumentReady } from './document-ready'
2+
import { focusableSelector } from './focus-management'
23

34
export let history: HTMLElement[] = []
45
onDocumentReady(() => {
@@ -7,7 +8,21 @@ onDocumentReady(() => {
78
if (e.target === document.body) return
89
if (history[0] === e.target) return
910

10-
history.unshift(e.target)
11+
let focusableElement = e.target as HTMLElement
12+
13+
// Figure out the closest focusable element, this is needed in a situation
14+
// where you click on a non-focusable element inside a focusable element.
15+
//
16+
// E.g.:
17+
//
18+
// ```html
19+
// <button>
20+
// <span>Click me</span>
21+
// </button>
22+
// ```
23+
focusableElement = focusableElement.closest(focusableSelector) as HTMLElement
24+
25+
history.unshift(focusableElement ?? e.target)
1126

1227
// Filter out DOM Nodes that don't exist anymore
1328
history = history.filter((x) => x != null && x.isConnected)

packages/@headlessui-vue/src/utils/focus-management.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { getOwnerDocument } from './owner'
44

55
// Credit:
66
// - https://stackoverflow.com/a/30753870
7-
let focusableSelector = [
7+
export let focusableSelector = [
88
'[contentEditable=true]',
99
'[tabindex]',
1010
'a[href]',

0 commit comments

Comments
 (0)