Skip to content

Commit

Permalink
feat(useActiveElement): support shadow roots (#2592)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
43081j and antfu committed Jan 3, 2023
1 parent 7bb0144 commit 07f2031
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 4 deletions.
7 changes: 7 additions & 0 deletions packages/core/_configurable.ts
Expand Up @@ -14,6 +14,13 @@ export interface ConfigurableDocument {
document?: Document
}

export interface ConfigurableDocumentOrShadowRoot {
/*
* Specify a custom `document` instance or a shadow root, e.g. working with iframes or in testing environments.
*/
document?: DocumentOrShadowRoot
}

export interface ConfigurableNavigator {
/*
* Specify a custom `navigator` instance, e.g. working with iframes or in testing environments.
Expand Down
61 changes: 61 additions & 0 deletions packages/core/useActiveElement/index.test.ts
@@ -0,0 +1,61 @@
import { useActiveElement } from '.'

describe('useActiveElement', () => {
let shadowHost: HTMLElement
let input: HTMLInputElement
let shadowInput: HTMLInputElement
let shadowRoot: ShadowRoot

beforeEach(() => {
shadowHost = document.createElement('div')
shadowRoot = shadowHost.attachShadow({ mode: 'open' })
input = document.createElement('input')
shadowInput = input.cloneNode() as HTMLInputElement
shadowRoot.appendChild(shadowInput)
document.body.appendChild(input)
document.body.appendChild(shadowHost)
})

afterEach(() => {
shadowHost.remove()
input.remove()
})

it('should be defined', () => {
expect(useActiveElement).toBeDefined()
})

it('should initialise correctly', () => {
const activeElement = useActiveElement()

expect(activeElement.value).to.equal(document.body)
})

it('should initialise with already-active element', () => {
input.focus()

const activeElement = useActiveElement()

expect(activeElement.value).to.equal(input)
})

it('should accept custom document', () => {
const activeElement = useActiveElement({ document: shadowRoot })

shadowInput.focus()

expect(activeElement.value).to.equal(shadowInput)
})

it('should observe focus/blur events', () => {
const activeElement = useActiveElement()

input.focus()

expect(activeElement.value).to.equal(input)

input.blur()

expect(activeElement.value).to.equal(document.body)
})
})
9 changes: 6 additions & 3 deletions packages/core/useActiveElement/index.ts
@@ -1,19 +1,22 @@
import { computedWithControl } from '@vueuse/shared'
import { useEventListener } from '../useEventListener'
import type { ConfigurableWindow } from '../_configurable'
import type { ConfigurableDocumentOrShadowRoot, ConfigurableWindow } from '../_configurable'
import { defaultWindow } from '../_configurable'

export interface UseActiveElementOptions extends ConfigurableWindow, ConfigurableDocumentOrShadowRoot {}

/**
* Reactive `document.activeElement`
*
* @see https://vueuse.org/useActiveElement
* @param options
*/
export function useActiveElement<T extends HTMLElement>(options: ConfigurableWindow = {}) {
export function useActiveElement<T extends HTMLElement>(options: UseActiveElementOptions = {}) {
const { window = defaultWindow } = options
const document = options.document ?? window?.document
const activeElement = computedWithControl(
() => null,
() => window?.document.activeElement as T | null | undefined,
() => document?.activeElement as T | null | undefined,
)

if (window) {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/useEventListener/index.ts
Expand Up @@ -63,7 +63,7 @@ export function useEventListener<E extends keyof WindowEventMap>(
* @param options
*/
export function useEventListener<E extends keyof DocumentEventMap>(
target: Document,
target: DocumentOrShadowRoot,
event: Arrayable<E>,
listener: Arrayable<(this: Document, ev: DocumentEventMap[E]) => any>,
options?: boolean | AddEventListenerOptions
Expand Down

0 comments on commit 07f2031

Please sign in to comment.