kliping.pro
  • Docs
  • Directory
  • Colors
  • Typeset
Hooks
  • State7
  • Advanced6
  • Lifecycle16
  • Browser21
    • useActiveElement
    • useBattery
    • useClickAnyWhere
    • useClickAway
    • useClipboard
    • useDocumentVisibility
    • useElementSize
    • useEventListener
    • useFps
    • useFullscreen
    • useHash
    • useHover
    • useInViewport
    • useIsMatchMedia
    • useIsOnline
    • useMouse
    • useNetwork
    • useOs
    • useScrollLock
    • useTextSelection
    • useTitle
  • Development1
  • Third-Party4
  • Libraries4

useClickAway

PreviousNext

Menjalankan sesuatu ketika kliknya jatuh di luar sekumpulan elemen.

Ini yang menutup dropdown, popover, dan menu ketika orang menekan tempat lain. Terdengar sepele, tapi ada dua hal yang selalu salah kalau ditulis dadakan dengan document.addEventListener('click', …):

  1. Target yang belum ada. Elemen yang dipantau baru muncul setelah komponennya terpasang. Pendengar yang dipasang lebih dulu akan menganggap setiap klik jatuh "di luar" — termasuk klik yang membuka menunya sendiri. Di sini targetnya boleh berupa ref yang masih kosong, dan elemen yang belum ada dianggap belum siap, bukan dianggap di luar.
  2. Shadow DOM. event.target di dalam shadow root sudah diretarget saat peristiwanya sampai ke document, jadi contains() selalu bilang tidak. Kalau semua targetnya tinggal di dalam shadow root yang sama, pendengarnya dipasang di root itu, bukan di document — sama seperti sumbernya.

Installation ​

pnpm dlx shadcn-vue@latest add @kliping/use-click-away

Perlu @kliping terdaftar di components.json project Anda. Lihat cara menyiapkannya.

Ikut terpasang sendiri: is-browser, target.

Salin dulu is-browser, target — masing-masing memuat sumbernya di halamannya sendiri — lalu tempel berkas ini.

ts
import { getCurrentScope, onScopeDispose, watch } from 'vue'
import { getTargetElement } from '@/lib/target'
import { isBrowser } from '@/lib/is-browser'
import type { BasicTarget } from '@/lib/target'

export function useClickAway<T extends Event = Event>(
  onClickAway: (event: T) => void,
  target: BasicTarget | BasicTarget[],
  eventName: string | string[] = 'click',
) {
  const eventNames = Array.isArray(eventName) ? eventName : [eventName]

  const allTargets = () => {
    const targets = Array.isArray(target) ? target : [target]
    return targets.map((t) => getTargetElement(t))
  }

  const listenerRoot = (elements: (Element | Node | undefined)[]): Document | Node => {
    const present = elements.filter(Boolean) as Element[]
    if (!present.length) return document

    const allInShadow = present.every(
      (el) => typeof el.getRootNode === 'function' && el.getRootNode() instanceof ShadowRoot,
    )
    return allInShadow ? present[0].getRootNode() : document
  }

  let detach: (() => void) | undefined

  const teardown = () => {
    detach?.()
    detach = undefined
  }

  const stopWatch = watch(
    () => allTargets(),
    (elements) => {
      teardown()
      if (!isBrowser) return

      const root = listenerRoot(elements as Element[])

      const handler = (event: Event) => {
        const current = allTargets()

        const inside = current.some((el) => {
          if (!el) return true
          return 'contains' in el && el.contains(event.target as Node)
        })

        if (!inside) onClickAway(event as T)
      }

      for (const name of eventNames) root.addEventListener(name, handler)

      detach = () => {
        for (const name of eventNames) root.removeEventListener(name, handler)
      }
    },
    { immediate: true, flush: 'post' },
  )

  const stop = () => {
    teardown()
    stopWatch()
  }

  if (getCurrentScope()) onScopeDispose(stop)

  return stop
}

API ​

ts
export function useClickAway<T extends Event = Event>(
  onClickAway: (event: T) => void,
  target: BasicTarget | BasicTarget[],
  eventName: string | string[] = 'click',
)
PrevioususeClickAnyWhereNext useClipboard

On This Page

InstallationAPI
Dirilis di bawah lisensi MIT. Ditulis ulang untuk Vue dari shadcn-vue, shadcn-ui-blocks, dan shadcn-hooks. Hak cipta © 2026 Kliping