From 1653db65bc6bd0d281606ea54d139c8bc46d7a45 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 22 Aug 2022 16:51:17 +0200 Subject: [PATCH 1/2] 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. --- .../@headlessui-react/src/components/menu/menu.tsx | 5 +++-- .../@headlessui-react/src/utils/focus-management.ts | 13 +++++++++++++ .../@headlessui-vue/src/components/menu/menu.ts | 5 +++-- .../@headlessui-vue/src/utils/focus-management.ts | 13 +++++++++++++ 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/packages/@headlessui-react/src/components/menu/menu.tsx b/packages/@headlessui-react/src/components/menu/menu.tsx index d8e392e637..ad6474f185 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 68f18ab756..0800fbde3e 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/src/components/menu/menu.ts b/packages/@headlessui-vue/src/components/menu/menu.ts index 35ffa90e43..4b9e8d18a8 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 392ef5e634..c8abd5836d 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 }) } From b180bea72fc349485beb81fc161dbb2c8975ee80 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 22 Aug 2022 16:57:07 +0200 Subject: [PATCH 2/2] update changelog --- packages/@headlessui-react/CHANGELOG.md | 1 + packages/@headlessui-vue/CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/@headlessui-react/CHANGELOG.md b/packages/@headlessui-react/CHANGELOG.md index 0baafb9351..bbfb72032f 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-vue/CHANGELOG.md b/packages/@headlessui-vue/CHANGELOG.md index 7702b1549e..4aec5c1605 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