From b301f04c7747fcdebc9fcc380dee60c4393c7c2d Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 22 Aug 2022 17:00:15 +0200 Subject: [PATCH] Only restore focus to the `Menu.Button` if necessary when activating 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 --- packages/@headlessui-react/CHANGELOG.md | 1 + .../@headlessui-react/src/components/menu/menu.tsx | 5 +++-- .../@headlessui-react/src/utils/focus-management.ts | 13 +++++++++++++ packages/@headlessui-vue/CHANGELOG.md | 1 + .../@headlessui-vue/src/components/menu/menu.ts | 5 +++-- .../@headlessui-vue/src/utils/focus-management.ts | 13 +++++++++++++ 6 files changed, 34 insertions(+), 4 deletions(-) diff --git a/packages/@headlessui-react/CHANGELOG.md b/packages/@headlessui-react/CHANGELOG.md index 0baafb9..bbfb720 100644 --- a/packages/@headlessui-react/CHANGELOG.md +++ b/packages/@headlessui-react/CHANGELOG.md @@ -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 `` 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 diff --git a/packages/@headlessui-react/src/components/menu/menu.tsx b/packages/@headlessui-react/src/components/menu/menu.tsx index d8e392e..ad6474f 100644 --- a/packages/@headlessui-react/src/components/menu/menu.tsx +++ b/packages/@headlessui-react/src/components/menu/menu.tsx @@ -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' @@ -463,7 +464,7 @@ let Items = forwardRefWithAs(function Items state.buttonRef.current?.focus({ preventScroll: true })) + restoreFocusIfNecessary(state.buttonRef.current) break case Keys.ArrowDown: @@ -615,7 +616,7 @@ let Item = forwardRefWithAs(function Item { if (disabled) return event.preventDefault() dispatch({ type: ActionTypes.CloseMenu }) - disposables().nextFrame(() => state.buttonRef.current?.focus({ preventScroll: true })) + restoreFocusIfNecessary(state.buttonRef.current) }) let handleFocus = useEvent(() => { diff --git a/packages/@headlessui-react/src/utils/focus-management.ts b/packages/@headlessui-react/src/utils/focus-management.ts index 68f18ab..0800fbd 100644 --- a/packages/@headlessui-react/src/utils/focus-management.ts +++ b/packages/@headlessui-react/src/utils/focus-management.ts @@ -1,3 +1,4 @@ +import { disposables } from './disposables' import { match } from './match' import { getOwnerDocument } from './owner' @@ -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 }) } diff --git a/packages/@headlessui-vue/CHANGELOG.md b/packages/@headlessui-vue/CHANGELOG.md index 7702b15..4aec5c1 100644 --- a/packages/@headlessui-vue/CHANGELOG.md +++ b/packages/@headlessui-vue/CHANGELOG.md @@ -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 `` 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 diff --git a/packages/@headlessui-vue/src/components/menu/menu.ts b/packages/@headlessui-vue/src/components/menu/menu.ts index 35ffa90..4b9e8d1 100644 --- a/packages/@headlessui-vue/src/components/menu/menu.ts +++ b/packages/@headlessui-vue/src/components/menu/menu.ts @@ -28,6 +28,7 @@ import { sortByDomNode, Focus as FocusManagementFocus, focusFrom, + restoreFocusIfNecessary, } from '../../utils/focus-management' import { useOutsideClick } from '../../hooks/use-outside-click' @@ -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: @@ -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() { diff --git a/packages/@headlessui-vue/src/utils/focus-management.ts b/packages/@headlessui-vue/src/utils/focus-management.ts index 392ef5e..c8abd58 100644 --- a/packages/@headlessui-vue/src/utils/focus-management.ts +++ b/packages/@headlessui-vue/src/utils/focus-management.ts @@ -1,3 +1,4 @@ +import { nextTick } from 'vue' import { match } from './match' import { getOwnerDocument } from './owner' @@ -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 }) }