Skip to content

Commit 6d8235e

Browse files
Mimic browser select on focus when navigating via Tab (#1272)
* mimic browser select on focus When calling focusIn if the next node is selectable select all the text. * refactor browser `select` behaviour for React and Vue * update changelog Co-authored-by: Robin Malfait <[email protected]>
1 parent 206bb7f commit 6d8235e

File tree

3 files changed

+42
-0
lines changed

3 files changed

+42
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3232
- Fix incorrect `active` option in the Listbox/Combobox component ([#1264](https://github.com/tailwindlabs/headlessui/pull/1264))
3333
- Properly merge incoming props ([#1265](https://github.com/tailwindlabs/headlessui/pull/1265))
3434
- Fix incorrect closing while interacting with third party libraries in `Dialog` component ([#1268](https://github.com/tailwindlabs/headlessui/pull/1268))
35+
- Mimic browser select on focus when navigating via `Tab` ([#1272](https://github.com/tailwindlabs/headlessui/pull/1272))
3536

3637
### Added
3738

@@ -65,6 +66,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6566
- Fix Tree-shaking support ([#1247](https://github.com/tailwindlabs/headlessui/pull/1247))
6667
- Stop propagation on the Popover Button ([#1263](https://github.com/tailwindlabs/headlessui/pull/1263))
6768
- Fix incorrect closing while interacting with third party libraries in `Dialog` component ([#1268](https://github.com/tailwindlabs/headlessui/pull/1268))
69+
- Mimic browser select on focus when navigating via `Tab` ([#1272](https://github.com/tailwindlabs/headlessui/pull/1272))
6870
- Resolve `initialFocusRef` correctly ([#1276](https://github.com/tailwindlabs/headlessui/pull/1276))
6971

7072
### Added

packages/@headlessui-react/src/utils/focus-management.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,14 @@ export function focusElement(element: HTMLElement | null) {
103103
element?.focus({ preventScroll: true })
104104
}
105105

106+
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/select
107+
let selectableSelector = ['textarea', 'input'].join(',')
108+
function isSelectableElement(
109+
element: Element | null
110+
): element is HTMLInputElement | HTMLTextAreaElement {
111+
return element?.matches?.(selectableSelector) ?? false
112+
}
113+
106114
export function sortByDomNode<T>(
107115
nodes: T[],
108116
resolveKey: (item: T) => HTMLElement | null = (i) => i as unknown as HTMLElement | null
@@ -176,6 +184,18 @@ export function focusIn(container: HTMLElement | HTMLElement[], focus: Focus) {
176184
offset += direction
177185
} while (next !== ownerDocument.activeElement)
178186

187+
// By default if you <Tab> to a text input or a textarea, the browser will
188+
// select all the text once the focus is inside these DOM Nodes. However,
189+
// since we are manually moving focus this behaviour is not happening. This
190+
// code will make sure that the text gets selected as-if you did it manually.
191+
// Note: We only do this when going forward / backward. Not for the
192+
// Focus.First or Focus.Last actions. This is similar to the `autoFocus`
193+
// behaviour on an input where the input will get focus but won't be
194+
// selected.
195+
if (focus & (Focus.Next | Focus.Previous) && isSelectableElement(next)) {
196+
next.select()
197+
}
198+
179199
// This is a little weird, but let me try and explain: There are a few scenario's
180200
// in chrome for example where a focused `<a>` tag does not get the default focus
181201
// styles and sometimes they do. This highly depends on whether you started by

packages/@headlessui-vue/src/utils/focus-management.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,14 @@ export function focusElement(element: HTMLElement | null) {
9696
element?.focus({ preventScroll: true })
9797
}
9898

99+
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/select
100+
let selectableSelector = ['textarea', 'input'].join(',')
101+
function isSelectableElement(
102+
element: Element | null
103+
): element is HTMLInputElement | HTMLTextAreaElement {
104+
return element?.matches?.(selectableSelector) ?? false
105+
}
106+
99107
export function sortByDomNode<T>(
100108
nodes: T[],
101109
resolveKey: (item: T) => HTMLElement | null = (i) => i as unknown as HTMLElement | null
@@ -179,5 +187,17 @@ export function focusIn(container: HTMLElement | HTMLElement[], focus: Focus) {
179187
// also add this tabindex.
180188
if (!next.hasAttribute('tabindex')) next.setAttribute('tabindex', '0')
181189

190+
// By default if you <Tab> to a text input or a textarea, the browser will
191+
// select all the text once the focus is inside these DOM Nodes. However,
192+
// since we are manually moving focus this behaviour is not happening. This
193+
// code will make sure that the text gets selected as-if you did it manually.
194+
// Note: We only do this when going forward / backward. Not for the
195+
// Focus.First or Focus.Last actions. This is similar to the `autoFocus`
196+
// behaviour on an input where the input will get focus but won't be
197+
// selected.
198+
if (focus & (Focus.Next | Focus.Previous) && isSelectableElement(next)) {
199+
next.select()
200+
}
201+
182202
return FocusResult.Success
183203
}

0 commit comments

Comments
 (0)