diff --git a/packages/@headlessui-react/src/utils/focus-management.ts b/packages/@headlessui-react/src/utils/focus-management.ts index 33016645b4..18b1a68522 100644 --- a/packages/@headlessui-react/src/utils/focus-management.ts +++ b/packages/@headlessui-react/src/utils/focus-management.ts @@ -116,6 +116,47 @@ export function restoreFocusIfNecessary(element: HTMLElement | null) { }) } +// The method of triggering an action, this is used to determine how we should +// restore focus after an action has been performed. +enum ActivationMethod { + /* If the action was triggered by a keyboard event. */ + Keyboard = 0, + + /* If the action was triggered by a mouse / pointer / ... event.*/ + Mouse = 1, +} + +// We want to be able to set and remove the `data-headlessui-mouse` attribute on the `html` element. +if (typeof window !== 'undefined' && typeof document !== 'undefined') { + document.addEventListener( + 'keydown', + (event) => { + if (event.metaKey || event.altKey || event.ctrlKey) { + return + } + + document.documentElement.dataset.headlessuiFocusVisible = '' + }, + true + ) + + document.addEventListener( + 'click', + (event) => { + // Event originated from an actual mouse click + if (event.detail === ActivationMethod.Mouse) { + delete document.documentElement.dataset.headlessuiFocusVisible + } + + // Event originated from a keyboard event that triggered the `click` event + else if (event.detail === ActivationMethod.Keyboard) { + document.documentElement.dataset.headlessuiFocusVisible = '' + } + }, + true + ) +} + export function focusElement(element: HTMLElement | null) { element?.focus({ preventScroll: true }) } diff --git a/packages/@headlessui-vue/src/utils/focus-management.ts b/packages/@headlessui-vue/src/utils/focus-management.ts index 846f666562..44832cbc5b 100644 --- a/packages/@headlessui-vue/src/utils/focus-management.ts +++ b/packages/@headlessui-vue/src/utils/focus-management.ts @@ -109,6 +109,47 @@ export function restoreFocusIfNecessary(element: HTMLElement | null) { }) } +// The method of triggering an action, this is used to determine how we should +// restore focus after an action has been performed. +enum ActivationMethod { + /* If the action was triggered by a keyboard event. */ + Keyboard = 0, + + /* If the action was triggered by a mouse / pointer / ... event.*/ + Mouse = 1, +} + +// We want to be able to set and remove the `data-headlessui-mouse` attribute on the `html` element. +if (typeof window !== 'undefined' && typeof document !== 'undefined') { + document.addEventListener( + 'keydown', + (event) => { + if (event.metaKey || event.altKey || event.ctrlKey) { + return + } + + document.documentElement.dataset.headlessuiFocusVisible = '' + }, + true + ) + + document.addEventListener( + 'click', + (event) => { + // Event originated from an actual mouse click + if (event.detail === ActivationMethod.Mouse) { + delete document.documentElement.dataset.headlessuiFocusVisible + } + + // Event originated from a keyboard event that triggered the `click` event + else if (event.detail === ActivationMethod.Keyboard) { + document.documentElement.dataset.headlessuiFocusVisible = '' + } + }, + true + ) +} + export function focusElement(element: HTMLElement | null) { element?.focus({ preventScroll: true }) }