Skip to content

Commit

Permalink
Fix restoring focus to correct element when closing Dialog component (
Browse files Browse the repository at this point in the history
#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
  • Loading branch information
RobinMalfait authored Jul 5, 2024
1 parent da466ec commit 91e9597
Show file tree
Hide file tree
Showing 6 changed files with 36 additions and 4 deletions.
1 change: 1 addition & 0 deletions packages/@headlessui-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix hanging tests when using `anchor` prop ([#3357](https://github.com/tailwindlabs/headlessui/pull/3357))
- Fix `transition` and `focus` prop combination for `PopoverPanel` component ([#3361](https://github.com/tailwindlabs/headlessui/pull/3361))
- Fix outside click in nested portalled `Popover` components ([#3362](https://github.com/tailwindlabs/headlessui/pull/3362))
- Fix restoring focus to correct element when closing `Dialog` component ([#3365](https://github.com/tailwindlabs/headlessui/pull/3365))

## [2.1.1] - 2024-06-26

Expand Down
17 changes: 16 additions & 1 deletion packages/@headlessui-react/src/utils/active-element-history.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { onDocumentReady } from './document-ready'
import { focusableSelector } from './focus-management'

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

history.unshift(e.target)
let focusableElement = e.target as HTMLElement

// Figure out the closest focusable element, this is needed in a situation
// where you click on a non-focusable element inside a focusable element.
//
// E.g.:
//
// ```html
// <button>
// <span>Click me</span>
// </button>
// ```
focusableElement = focusableElement.closest(focusableSelector) as HTMLElement

history.unshift(focusableElement ?? e.target)

// Filter out DOM Nodes that don't exist anymore
history = history.filter((x) => x != null && x.isConnected)
Expand Down
2 changes: 1 addition & 1 deletion packages/@headlessui-react/src/utils/focus-management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getOwnerDocument } from './owner'

// Credit:
// - https://stackoverflow.com/a/30753870
let focusableSelector = [
export let focusableSelector = [
'[contentEditable=true]',
'[tabindex]',
'a[href]',
Expand Down
1 change: 1 addition & 0 deletions packages/@headlessui-vue/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

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

## [1.7.22] - 2024-05-08

Expand Down
17 changes: 16 additions & 1 deletion packages/@headlessui-vue/src/utils/active-element-history.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { onDocumentReady } from './document-ready'
import { focusableSelector } from './focus-management'

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

history.unshift(e.target)
let focusableElement = e.target as HTMLElement

// Figure out the closest focusable element, this is needed in a situation
// where you click on a non-focusable element inside a focusable element.
//
// E.g.:
//
// ```html
// <button>
// <span>Click me</span>
// </button>
// ```
focusableElement = focusableElement.closest(focusableSelector) as HTMLElement

history.unshift(focusableElement ?? e.target)

// Filter out DOM Nodes that don't exist anymore
history = history.filter((x) => x != null && x.isConnected)
Expand Down
2 changes: 1 addition & 1 deletion packages/@headlessui-vue/src/utils/focus-management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getOwnerDocument } from './owner'

// Credit:
// - https://stackoverflow.com/a/30753870
let focusableSelector = [
export let focusableSelector = [
'[contentEditable=true]',
'[tabindex]',
'a[href]',
Expand Down

0 comments on commit 91e9597

Please sign in to comment.