Skip to content

Commit

Permalink
feat(useFocus): support :focus-visible (#3254)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
onmax and antfu committed Jul 30, 2023
1 parent b7e3d7e commit 8032933
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 2 deletions.
17 changes: 17 additions & 0 deletions packages/core/useFocus/index.test.ts
Expand Up @@ -48,6 +48,23 @@ describe('useFocus', () => {
await retry(() => expect(document.activeElement).not.toBe(target.value))
})

it('should only focus when :focus-visible matches with focusVisible=true', () => {
const { focused } = useFocus(target, { focusVisible: true })

let event = new Event('focus')
Object.defineProperty(event, 'target', { value: { matches: () => true }, enumerable: true })
target.value?.dispatchEvent(event)
expect(focused.value).toBeTruthy()

target.value?.dispatchEvent(new Event('blur'))
expect(focused.value).toBeFalsy()

event = new Event('focus')
Object.defineProperty(event, 'target', { value: { matches: () => false }, enumerable: true })
target.value?.dispatchEvent(event)
expect(focused.value).toBeFalsy()
})

describe('when target is missing', () => {
it('should initialize properly', () => {
const { focused } = useFocus(null)
Expand Down
14 changes: 12 additions & 2 deletions packages/core/useFocus/index.ts
Expand Up @@ -12,6 +12,13 @@ export interface UseFocusOptions extends ConfigurableWindow {
* @default false
*/
initialValue?: boolean

/**
* Replicate the :focus-visible behavior of CSS
*
* @default false
*/
focusVisible?: boolean
}

export interface UseFocusReturn {
Expand All @@ -30,12 +37,15 @@ export interface UseFocusReturn {
* @param options
*/
export function useFocus(target: MaybeElementRef, options: UseFocusOptions = {}): UseFocusReturn {
const { initialValue = false } = options
const { initialValue = false, focusVisible = false } = options

const innerFocused = ref(false)
const targetElement = computed(() => unrefElement(target))

useEventListener(targetElement, 'focus', () => innerFocused.value = true)
useEventListener(targetElement, 'focus', (event) => {
if (!focusVisible || (event.target as HTMLElement).matches?.(':focus-visible'))
innerFocused.value = true
})
useEventListener(targetElement, 'blur', () => innerFocused.value = false)

const focused = computed({
Expand Down

0 comments on commit 8032933

Please sign in to comment.