Skip to content

Commit 206bb7f

Browse files
authored
Resolve initialFocusRef correctly (#1276)
* resolve initialFocusRef correctly If you are passing a Ref to a component, you don't get the underlying DOM node even if you put it on the element manually. The ref will be a ref to the _component_. This means that the initialFocusRef can be a DOM element or a Vue component instance. Resolving it guarantees us to resolve to an HTMLElement or null but not a component. * update changelog
1 parent 79b3015 commit 206bb7f

File tree

3 files changed

+78
-4
lines changed

3 files changed

+78
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6565
- Fix Tree-shaking support ([#1247](https://github.com/tailwindlabs/headlessui/pull/1247))
6666
- Stop propagation on the Popover Button ([#1263](https://github.com/tailwindlabs/headlessui/pull/1263))
6767
- Fix incorrect closing while interacting with third party libraries in `Dialog` component ([#1268](https://github.com/tailwindlabs/headlessui/pull/1268))
68+
- Resolve `initialFocusRef` correctly ([#1276](https://github.com/tailwindlabs/headlessui/pull/1276))
6869

6970
### Added
7071

packages/@headlessui-vue/src/components/dialog/dialog.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,76 @@ describe('Keyboard interactions', () => {
698698
assertActiveElement(document.getElementById('a'))
699699
})
700700
)
701+
702+
it(
703+
'should be possible to tab around when using the initialFocus ref on a component',
704+
suppressConsoleLogs(async () => {
705+
let CustomComponent = defineComponent({
706+
name: 'CustomComponent',
707+
setup() {
708+
return () => h('input')
709+
},
710+
})
711+
712+
renderTemplate({
713+
components: {
714+
CustomComponent,
715+
},
716+
template: `
717+
<div>
718+
<button id="trigger" @click="toggleOpen">
719+
Trigger
720+
</button>
721+
<Dialog :open="isOpen" @close="setIsOpen" :initialFocus="initialFocusRef">
722+
Contents
723+
<TabSentinel id="a" />
724+
<CustomComponent type="text" id="b" ref="initialFocusRef" />
725+
</Dialog>
726+
</div>
727+
`,
728+
setup() {
729+
let isOpen = ref(false)
730+
let initialFocusRef = ref(null)
731+
return {
732+
isOpen,
733+
initialFocusRef,
734+
setIsOpen(value: boolean) {
735+
isOpen.value = value
736+
},
737+
toggleOpen() {
738+
isOpen.value = !isOpen.value
739+
},
740+
}
741+
},
742+
})
743+
744+
assertDialog({ state: DialogState.InvisibleUnmounted })
745+
746+
// Open dialog
747+
await click(document.getElementById('trigger'))
748+
749+
// Verify it is open
750+
assertDialog({
751+
state: DialogState.Visible,
752+
attributes: { id: 'headlessui-dialog-1' },
753+
})
754+
755+
// Verify that the input field is focused
756+
assertActiveElement(document.getElementById('b'))
757+
758+
// Verify that we can tab around
759+
await press(Keys.Tab)
760+
assertActiveElement(document.getElementById('a'))
761+
762+
// Verify that we can tab around
763+
await press(Keys.Tab)
764+
assertActiveElement(document.getElementById('b'))
765+
766+
// Verify that we can tab around
767+
await press(Keys.Tab)
768+
assertActiveElement(document.getElementById('a'))
769+
})
770+
)
701771
})
702772
})
703773

packages/@headlessui-vue/src/hooks/use-focus-trap.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Keys } from '../keyboard'
1212
import { focusElement, focusIn, Focus, FocusResult } from '../utils/focus-management'
1313
import { getOwnerDocument } from '../utils/owner'
1414
import { useEventListener } from './use-event-listener'
15+
import { dom } from '../utils/dom'
1516

1617
export enum Features {
1718
/** No features enabled for the `useFocusTrap` hook. */
@@ -96,10 +97,12 @@ export function useFocusTrap(
9697
let containerElement = container.value
9798
if (!containerElement) return
9899

100+
let initialFocusElement = dom(options.value.initialFocus)
101+
99102
let activeElement = ownerDocument.value?.activeElement as HTMLElement
100103

101-
if (options.value.initialFocus?.value) {
102-
if (options.value.initialFocus?.value === activeElement) {
104+
if (initialFocusElement) {
105+
if (initialFocusElement === activeElement) {
103106
previousActiveElement.value = activeElement
104107
return // Initial focus ref is already the active element
105108
}
@@ -109,8 +112,8 @@ export function useFocusTrap(
109112
}
110113

111114
// Try to focus the initialFocus ref
112-
if (options.value.initialFocus?.value) {
113-
focusElement(options.value.initialFocus.value)
115+
if (initialFocusElement) {
116+
focusElement(initialFocusElement)
114117
} else {
115118
if (focusIn(containerElement, Focus.First) === FocusResult.Error) {
116119
console.warn('There are no focusable elements inside the <FocusTrap />')

0 commit comments

Comments
 (0)