From 4c4c3f9b9ca90a3bf7f083e47ee5c50256e3ab20 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 8 Mar 2023 18:00:54 +0100 Subject: [PATCH] keep track of a `data-headlessui-focus-visible` attribute --- .../src/utils/focus-management.ts | 41 +++++++++++++++++++ .../src/utils/focus-management.ts | 41 +++++++++++++++++++ 2 files changed, 82 insertions(+) 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 }) }