Skip to content

Commit

Permalink
Resolve initialFocusRef correctly (#1276)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
RobinMalfait authored Mar 27, 2022
1 parent 79b3015 commit 206bb7f
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,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))
- Resolve `initialFocusRef` correctly ([#1276](https://github.com/tailwindlabs/headlessui/pull/1276))

### Added

Expand Down
70 changes: 70 additions & 0 deletions packages/@headlessui-vue/src/components/dialog/dialog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,76 @@ describe('Keyboard interactions', () => {
assertActiveElement(document.getElementById('a'))
})
)

it(
'should be possible to tab around when using the initialFocus ref on a component',
suppressConsoleLogs(async () => {
let CustomComponent = defineComponent({
name: 'CustomComponent',
setup() {
return () => h('input')
},
})

renderTemplate({
components: {
CustomComponent,
},
template: `
<div>
<button id="trigger" @click="toggleOpen">
Trigger
</button>
<Dialog :open="isOpen" @close="setIsOpen" :initialFocus="initialFocusRef">
Contents
<TabSentinel id="a" />
<CustomComponent type="text" id="b" ref="initialFocusRef" />
</Dialog>
</div>
`,
setup() {
let isOpen = ref(false)
let initialFocusRef = ref(null)
return {
isOpen,
initialFocusRef,
setIsOpen(value: boolean) {
isOpen.value = value
},
toggleOpen() {
isOpen.value = !isOpen.value
},
}
},
})

assertDialog({ state: DialogState.InvisibleUnmounted })

// Open dialog
await click(document.getElementById('trigger'))

// Verify it is open
assertDialog({
state: DialogState.Visible,
attributes: { id: 'headlessui-dialog-1' },
})

// Verify that the input field is focused
assertActiveElement(document.getElementById('b'))

// Verify that we can tab around
await press(Keys.Tab)
assertActiveElement(document.getElementById('a'))

// Verify that we can tab around
await press(Keys.Tab)
assertActiveElement(document.getElementById('b'))

// Verify that we can tab around
await press(Keys.Tab)
assertActiveElement(document.getElementById('a'))
})
)
})
})

Expand Down
11 changes: 7 additions & 4 deletions packages/@headlessui-vue/src/hooks/use-focus-trap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Keys } from '../keyboard'
import { focusElement, focusIn, Focus, FocusResult } from '../utils/focus-management'
import { getOwnerDocument } from '../utils/owner'
import { useEventListener } from './use-event-listener'
import { dom } from '../utils/dom'

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

let initialFocusElement = dom(options.value.initialFocus)

let activeElement = ownerDocument.value?.activeElement as HTMLElement

if (options.value.initialFocus?.value) {
if (options.value.initialFocus?.value === activeElement) {
if (initialFocusElement) {
if (initialFocusElement === activeElement) {
previousActiveElement.value = activeElement
return // Initial focus ref is already the active element
}
Expand All @@ -109,8 +112,8 @@ export function useFocusTrap(
}

// Try to focus the initialFocus ref
if (options.value.initialFocus?.value) {
focusElement(options.value.initialFocus.value)
if (initialFocusElement) {
focusElement(initialFocusElement)
} else {
if (focusIn(containerElement, Focus.First) === FocusResult.Error) {
console.warn('There are no focusable elements inside the <FocusTrap />')
Expand Down

0 comments on commit 206bb7f

Please sign in to comment.