Dipantau lewat focus dan blur pada fase capture. Itu bukan detail gaya: keduanya tidak menggelembung, jadi pendengar biasa di window tidak akan pernah mendengarnya sama sekali.
blur diabaikan kalau relatedTarget terisi — artinya fokusnya cuma pindah ke elemen lain, dan focus berikutnya sudah akan melaporkannya. Yang ditangani di sini adalah fokus yang benar-benar keluar dari halaman.
Installation
pnpm dlx shadcn-vue@latest add @kliping/use-active-element Perlu @kliping terdaftar di components.json project Anda. Lihat cara menyiapkannya.
Ikut terpasang sendiri: is-browser, try-on-mounted, use-event-listener.
Salin dulu is-browser, try-on-mounted, target, use-event-listener — masing-masing memuat sumbernya di halamannya sendiri — lalu tempel berkas ini.
ts
import { getCurrentScope, onScopeDispose, readonly, shallowRef } from 'vue'
import { isBrowser } from '@/lib/is-browser'
import { tryOnMounted } from '@/lib/try-on-mounted'
import { useEventListener } from '@/composables/use-event-listener'
export interface UseActiveElementOptions {
/** Descend into open shadow roots. Default: true. */
deep?: boolean
/** Re-check when DOM nodes are removed. Default: false. */
triggerOnRemoval?: boolean
}
function findActiveElement(deep: boolean): Element | null {
if (!isBrowser) return null
let active: Element | null = document.activeElement
if (!deep) return active
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement
return active
}
export function useActiveElement(options: UseActiveElementOptions = {}) {
const { deep = true, triggerOnRemoval = false } = options
const active = shallowRef<Element | null>(null)
const update = () => {
active.value = findActiveElement(deep)
}
useEventListener('focus', update, { capture: true })
useEventListener(
'blur',
(event: FocusEvent) => {
if (event.relatedTarget !== null) return
update()
},
{ capture: true },
)
let observer: MutationObserver | undefined
tryOnMounted(() => {
update()
if (!triggerOnRemoval || typeof MutationObserver === 'undefined') return
observer = new MutationObserver(update)
observer.observe(document, { childList: true, subtree: true })
})
if (getCurrentScope()) onScopeDispose(() => observer?.disconnect())
return readonly(active)
}API
ts
export interface UseActiveElementOptions {
/** Descend into open shadow roots. Default: true. */
deep?: boolean
/** Re-check when DOM nodes are removed. Default: false. */
triggerOnRemoval?: boolean
}
export function useActiveElement(options: UseActiveElementOptions = {})