Skip to content

Commit

Permalink
Only restore focus to the Menu.Button if necessary when activating …
Browse files Browse the repository at this point in the history
…a `Menu.Option` (#1782)

* only restore focus to the Menu Button if necessary

This will check whether the focus got moved to somewhere else or not
once we activate an item via click or pressing `enter`.

Pressing escape will still move focus to the Menu Button.

* update changelog
  • Loading branch information
RobinMalfait committed Aug 22, 2022
1 parent 486ac80 commit b301f04
Show file tree
Hide file tree
Showing 6 changed files with 34 additions and 4 deletions.
1 change: 1 addition & 0 deletions packages/@headlessui-react/CHANGELOG.md
Expand Up @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Ensure `Disclosure.Panel` is properly linked ([#1747](https://github.com/tailwindlabs/headlessui/pull/1747))
- Only select the active option when using "singular" mode when pressing `<tab>` in the `Combobox` component ([#1750](https://github.com/tailwindlabs/headlessui/pull/1750))
- Improve the types of the `Combobox` component ([#1761](https://github.com/tailwindlabs/headlessui/pull/1761))
- Only restore focus to the `Menu.Button` if necessary when activating a `Menu.Option` ([#1782](https://github.com/tailwindlabs/headlessui/pull/1782))

## Changed

Expand Down
5 changes: 3 additions & 2 deletions packages/@headlessui-react/src/components/menu/menu.tsx
Expand Up @@ -35,6 +35,7 @@ import {
sortByDomNode,
Focus as FocusManagementFocus,
focusFrom,
restoreFocusIfNecessary,
} from '../../utils/focus-management'
import { useOutsideClick } from '../../hooks/use-outside-click'
import { useTreeWalker } from '../../hooks/use-tree-walker'
Expand Down Expand Up @@ -463,7 +464,7 @@ let Items = forwardRefWithAs(function Items<TTag extends ElementType = typeof DE
let { dataRef } = state.items[state.activeItemIndex]
dataRef.current?.domRef.current?.click()
}
disposables().nextFrame(() => state.buttonRef.current?.focus({ preventScroll: true }))
restoreFocusIfNecessary(state.buttonRef.current)
break

case Keys.ArrowDown:
Expand Down Expand Up @@ -615,7 +616,7 @@ let Item = forwardRefWithAs(function Item<TTag extends ElementType = typeof DEFA
let handleClick = useEvent((event: MouseEvent) => {
if (disabled) return event.preventDefault()
dispatch({ type: ActionTypes.CloseMenu })
disposables().nextFrame(() => state.buttonRef.current?.focus({ preventScroll: true }))
restoreFocusIfNecessary(state.buttonRef.current)
})

let handleFocus = useEvent(() => {
Expand Down
13 changes: 13 additions & 0 deletions packages/@headlessui-react/src/utils/focus-management.ts
@@ -1,3 +1,4 @@
import { disposables } from './disposables'
import { match } from './match'
import { getOwnerDocument } from './owner'

Expand Down Expand Up @@ -99,6 +100,18 @@ export function isFocusableElement(
})
}

export function restoreFocusIfNecessary(element: HTMLElement | null) {
let ownerDocument = getOwnerDocument(element)
disposables().nextFrame(() => {
if (
ownerDocument &&
!isFocusableElement(ownerDocument.activeElement as HTMLElement, FocusableMode.Strict)
) {
focusElement(element)
}
})
}

export function focusElement(element: HTMLElement | null) {
element?.focus({ preventScroll: true })
}
Expand Down
1 change: 1 addition & 0 deletions packages/@headlessui-vue/CHANGELOG.md
Expand Up @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Make form components uncontrollable ([#1683](https://github.com/tailwindlabs/headlessui/pull/1683))
- Improve `Combobox` re-opening keyboard issue on mobile ([#1732](https://github.com/tailwindlabs/headlessui/pull/1732))
- Only select the active option when using "singular" mode when pressing `<tab>` in the `Combobox` component ([#1750](https://github.com/tailwindlabs/headlessui/pull/1750))
- Only restore focus to the `MenuButton` if necessary when activating a `MenuOption` ([#1782](https://github.com/tailwindlabs/headlessui/pull/1782))

## [1.6.7] - 2022-07-12

Expand Down
5 changes: 3 additions & 2 deletions packages/@headlessui-vue/src/components/menu/menu.ts
Expand Up @@ -28,6 +28,7 @@ import {
sortByDomNode,
Focus as FocusManagementFocus,
focusFrom,
restoreFocusIfNecessary,
} from '../../utils/focus-management'
import { useOutsideClick } from '../../hooks/use-outside-click'

Expand Down Expand Up @@ -384,7 +385,7 @@ export let MenuItems = defineComponent({
dom(_activeItem.dataRef.domRef)?.click()
}
api.closeMenu()
nextTick(() => dom(api.buttonRef)?.focus({ preventScroll: true }))
restoreFocusIfNecessary(dom(api.buttonRef))
break

case Keys.ArrowDown:
Expand Down Expand Up @@ -531,7 +532,7 @@ export let MenuItem = defineComponent({
function handleClick(event: MouseEvent) {
if (props.disabled) return event.preventDefault()
api.closeMenu()
nextTick(() => dom(api.buttonRef)?.focus({ preventScroll: true }))
restoreFocusIfNecessary(dom(api.buttonRef))
}

function handleFocus() {
Expand Down
13 changes: 13 additions & 0 deletions packages/@headlessui-vue/src/utils/focus-management.ts
@@ -1,3 +1,4 @@
import { nextTick } from 'vue'
import { match } from './match'
import { getOwnerDocument } from './owner'

Expand Down Expand Up @@ -92,6 +93,18 @@ export function isFocusableElement(
})
}

export function restoreFocusIfNecessary(element: HTMLElement | null) {
let ownerDocument = getOwnerDocument(element)
nextTick(() => {
if (
ownerDocument &&
!isFocusableElement(ownerDocument.activeElement as HTMLElement, FocusableMode.Strict)
) {
focusElement(element)
}
})
}

export function focusElement(element: HTMLElement | null) {
element?.focus({ preventScroll: true })
}
Expand Down

0 comments on commit b301f04

Please sign in to comment.