Skip to content

Commit

Permalink
make it work in Vue
Browse files Browse the repository at this point in the history
  • Loading branch information
RobinMalfait committed Sep 9, 2022
1 parent 45082b5 commit d8c469c
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 1 deletion.
50 changes: 49 additions & 1 deletion packages/@headlessui-vue/src/components/popover/popover.test.ts
@@ -1,4 +1,4 @@
import { defineComponent, nextTick, ref, watch, h } from 'vue'
import { defineComponent, nextTick, ref, watch, h, onMounted } from 'vue'
import { createRenderTemplate, render } from '../../test-utils/vue-testing-library'

import { Popover, PopoverGroup, PopoverButton, PopoverPanel, PopoverOverlay } from './popover'
Expand Down Expand Up @@ -1696,6 +1696,54 @@ describe('Keyboard interactions', () => {
})
)

it(
'should focus the Popover.Button when pressing Shift+Tab when we focus inside the Popover.Panel (heuristc based portal)',
suppressConsoleLogs(async () => {
renderTemplate({
template: html`
<Popover>
<PopoverButton>Trigger 1</PopoverButton>
<Teleport v-if="ready" to="#portal">
<PopoverPanel focus>
<a href="/">Link 1</a>
<a href="/">Link 2</a>
</PopoverPanel>
</Teleport>
<button>Before</button>
<div id="portal" />
<button>After</button>
</Popover>
`,
setup() {
let ready = ref(false)
onMounted(() => {
ready.value = true
})
return { ready }
},
})

// Open the popover
await click(getPopoverButton())

// Ensure the popover is open
assertPopoverButton({ state: PopoverState.Visible })

// Ensure the Link 1 is focused
assertActiveElement(getByText('Link 1'))

// Tab out of the Panel
await press(shift(Keys.Tab))

// Ensure the Popover.Button is focused again
assertActiveElement(getPopoverButton())

// Ensure the Popover is closed
assertPopoverButton({ state: PopoverState.InvisibleUnmounted })
assertPopoverPanel({ state: PopoverState.InvisibleUnmounted })
})
)

it(
'should be possible to focus the last item in the PopoverPanel when pressing Shift+Tab on the next PopoverButton',
suppressConsoleLogs(async () => {
Expand Down
21 changes: 21 additions & 0 deletions packages/@headlessui-vue/src/components/popover/popover.ts
Expand Up @@ -118,12 +118,33 @@ export let Popover = defineComponent({
if (!dom(button)) return false
if (!dom(panel)) return false

// We are part of a different "root" tree, so therefore we can consider it portalled. This is a
// heuristic because 3rd party tools could use some form of portal, typically rendered at the
// end of the body but we don't have an actual reference to that.
for (let root of document.querySelectorAll('body > *')) {
if (Number(root?.contains(dom(button))) ^ Number(root?.contains(dom(panel)))) {
return true
}
}

// Use another heuristic to try and calculate wether or not the focusable elements are near
// eachother (aka, following the default focus/tab order from the browser). If they are then it
// doesn't really matter if they are portalled or not because we can follow the default tab
// order. But if they are not, then we can consider it being portalled so that we can ensure
// that tab and shift+tab (hopefully) go to the correct spot.
let elements = getFocusableElements()
let buttonIdx = elements.indexOf(dom(button)!)

let beforeIdx = (buttonIdx + elements.length - 1) % elements.length
let afterIdx = (buttonIdx + 1) % elements.length

let beforeElement = elements[beforeIdx]
let afterElement = elements[afterIdx]

if (!dom(panel)?.contains(beforeElement) && !dom(panel)?.contains(afterElement)) {
return true
}

return false
})

Expand Down

0 comments on commit d8c469c

Please sign in to comment.