From 15725ec7f7069db69ea015f894fd7ee8b7fbc0c4 Mon Sep 17 00:00:00 2001 From: Seth MacPherson Date: Fri, 25 Mar 2022 12:01:05 -0500 Subject: [PATCH 1/3] mimic browser select on focus When calling focusIn if the next node is selectable select all the text. --- .../src/utils/focus-management.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/@headlessui-react/src/utils/focus-management.ts b/packages/@headlessui-react/src/utils/focus-management.ts index 8d0c717ac4..728abf6990 100644 --- a/packages/@headlessui-react/src/utils/focus-management.ts +++ b/packages/@headlessui-react/src/utils/focus-management.ts @@ -103,6 +103,17 @@ export function focusElement(element: HTMLElement | null) { element?.focus({ preventScroll: true }) } +// https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/select +export function isSelectableElement( + element: HTMLElement | null +): element is HTMLInputElement | HTMLTextAreaElement { + return !!element && element.matches(['textarea', 'input'].join(',')) +} + +export function selectElementText(element: HTMLInputElement | HTMLTextAreaElement | null) { + element?.select() +} + export function sortByDomNode( nodes: T[], resolveKey: (item: T) => HTMLElement | null = (i) => i as unknown as HTMLElement | null @@ -172,6 +183,10 @@ export function focusIn(container: HTMLElement | HTMLElement[], focus: Focus) { // Try the focus the next element, might not work if it is "hidden" to the user. next?.focus(focusOptions) + if (isSelectableElement(next)) { + next.select() + } + // Try the next one in line offset += direction } while (next !== ownerDocument.activeElement) From f2626c0ccb9d572b25ccdef8593a3ec9f0a234e8 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 28 Mar 2022 16:14:59 +0200 Subject: [PATCH 2/3] refactor browser `select` behaviour for React and Vue --- .../src/utils/focus-management.ts | 27 +++++++++++-------- .../src/utils/focus-management.ts | 20 ++++++++++++++ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/packages/@headlessui-react/src/utils/focus-management.ts b/packages/@headlessui-react/src/utils/focus-management.ts index 728abf6990..1e31069639 100644 --- a/packages/@headlessui-react/src/utils/focus-management.ts +++ b/packages/@headlessui-react/src/utils/focus-management.ts @@ -104,14 +104,11 @@ export function focusElement(element: HTMLElement | null) { } // https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/select -export function isSelectableElement( - element: HTMLElement | null +let selectableSelector = ['textarea', 'input'].join(',') +function isSelectableElement( + element: Element | null ): element is HTMLInputElement | HTMLTextAreaElement { - return !!element && element.matches(['textarea', 'input'].join(',')) -} - -export function selectElementText(element: HTMLInputElement | HTMLTextAreaElement | null) { - element?.select() + return element?.matches?.(selectableSelector) ?? false } export function sortByDomNode( @@ -183,14 +180,22 @@ export function focusIn(container: HTMLElement | HTMLElement[], focus: Focus) { // Try the focus the next element, might not work if it is "hidden" to the user. next?.focus(focusOptions) - if (isSelectableElement(next)) { - next.select() - } - // Try the next one in line offset += direction } while (next !== ownerDocument.activeElement) + // By default if you to a text input or a textarea, the browser will + // select all the text once the focus is inside these DOM Nodes. However, + // since we are manually moving focus this behaviour is not happening. This + // code will make sure that the text gets selected as-if you did it manually. + // Note: We only do this when going forward / backward. Not for the + // Focus.First or Focus.Last actions. This is similar to the `autoFocus` + // behaviour on an input where the input will get focus but won't be + // selected. + if (focus & (Focus.Next | Focus.Previous) && isSelectableElement(next)) { + next.select() + } + // This is a little weird, but let me try and explain: There are a few scenario's // in chrome for example where a focused `` tag does not get the default focus // styles and sometimes they do. This highly depends on whether you started by diff --git a/packages/@headlessui-vue/src/utils/focus-management.ts b/packages/@headlessui-vue/src/utils/focus-management.ts index c7da796d94..a64e5f88ae 100644 --- a/packages/@headlessui-vue/src/utils/focus-management.ts +++ b/packages/@headlessui-vue/src/utils/focus-management.ts @@ -96,6 +96,14 @@ export function focusElement(element: HTMLElement | null) { element?.focus({ preventScroll: true }) } +// https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/select +let selectableSelector = ['textarea', 'input'].join(',') +function isSelectableElement( + element: Element | null +): element is HTMLInputElement | HTMLTextAreaElement { + return element?.matches?.(selectableSelector) ?? false +} + export function sortByDomNode( nodes: T[], resolveKey: (item: T) => HTMLElement | null = (i) => i as unknown as HTMLElement | null @@ -179,5 +187,17 @@ export function focusIn(container: HTMLElement | HTMLElement[], focus: Focus) { // also add this tabindex. if (!next.hasAttribute('tabindex')) next.setAttribute('tabindex', '0') + // By default if you to a text input or a textarea, the browser will + // select all the text once the focus is inside these DOM Nodes. However, + // since we are manually moving focus this behaviour is not happening. This + // code will make sure that the text gets selected as-if you did it manually. + // Note: We only do this when going forward / backward. Not for the + // Focus.First or Focus.Last actions. This is similar to the `autoFocus` + // behaviour on an input where the input will get focus but won't be + // selected. + if (focus & (Focus.Next | Focus.Previous) && isSelectableElement(next)) { + next.select() + } + return FocusResult.Success } From ac265dc67f8906f9de029c6c0dc097aff40891bf Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 28 Mar 2022 16:16:41 +0200 Subject: [PATCH 3/3] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47daa6f02d..b710e6e7ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix incorrect `active` option in the Listbox/Combobox component ([#1264](https://github.com/tailwindlabs/headlessui/pull/1264)) - Properly merge incoming props ([#1265](https://github.com/tailwindlabs/headlessui/pull/1265)) - Fix incorrect closing while interacting with third party libraries in `Dialog` component ([#1268](https://github.com/tailwindlabs/headlessui/pull/1268)) +- Mimic browser select on focus when navigating via `Tab` ([#1272](https://github.com/tailwindlabs/headlessui/pull/1272)) ### Added @@ -65,6 +66,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix Tree-shaking support ([#1247](https://github.com/tailwindlabs/headlessui/pull/1247)) - Stop propagation on the Popover Button ([#1263](https://github.com/tailwindlabs/headlessui/pull/1263)) - Fix incorrect closing while interacting with third party libraries in `Dialog` component ([#1268](https://github.com/tailwindlabs/headlessui/pull/1268)) +- Mimic browser select on focus when navigating via `Tab` ([#1272](https://github.com/tailwindlabs/headlessui/pull/1272)) ### Added