Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(useActiveElement): support shadow roots #2592

Merged
merged 2 commits into from Jan 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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