Kliping
KlipingBerandaDokumentasiKomponenBlocksVariasiTemplateDice UIHooks
Memulai
  • Pengenalan
  • Instalasi
  • Kredit
State
  • useBoolean
  • useControllableValue
  • useCounter
  • useDebounce
  • useResetState
  • useThrottle
  • useToggle
Lanjutan
  • useCreation
  • useCustomCompareEffect
  • useLatest
  • useLockFn
  • useMemoizedFn
  • usePrevious
Siklus Hidup
  • useDebounceEffect
  • useDebounceFn
  • useDeepCompareEffect
  • useDeepCompareLayoutEffect
  • useEffectEvent
  • useEffectWithTarget
  • useInterval
  • useIsHydrated
  • useIsomorphicLayoutEffect
  • useMount
  • useThrottleEffect
  • useThrottleFn
  • useTimeout
  • useUnmount
  • useUpdate
  • useUpdateEffect
Peramban
  • useActiveElement
  • useBattery
  • useClickAnyWhere
  • useClickAway
  • useClipboard
  • useDocumentVisibility
  • useElementSize
  • useEventListener
  • useFps
  • useFullscreen
  • useHash
  • useHover
  • useInViewport
  • useIsMatchMedia
  • useIsOnline
  • useMouse
  • useNetwork
  • useOs
  • useScrollLock
  • useTextSelection
  • useTitle
Pengembangan
  • useWhyDidYouUpdate
Paket Luar
  • useMcp
  • useQuery
  • useStickToBottom
  • useSwr
Pustaka
  • isBrowser
  • createContext
  • bacaElemen
  • saatTerpasang

useClickAway

SebelumnyaBerikutnya

Menjalankan sesuatu ketika kliknya jatuh di luar sekumpulan elemen.

Menu tertutup.

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.

Pemasangan ​

npx 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 { bacaElemen } from '@/lib/target'
import { isBrowser } from '@/lib/is-browser'
import type { TargetElemen } from '@/lib/target'

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

  const semuaTarget = () => {
    const daftar = Array.isArray(target) ? target : [target]
    return daftar.map(t => bacaElemen(t))
  }

  /**
   * Tempat pendengarnya dipasang: shadow root kalau seluruh targetnya tinggal
   * di sana, `document` kalau tidak.
   */
  const tempatPasang = (elemen: (Element | Node | undefined)[]): Document | Node => {
    const ada = elemen.filter(Boolean) as Element[]
    if (!ada.length)
      return document

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

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

  const bongkar = () => {
    lepas?.()
    lepas = undefined
  }

  const berhentiPantau = watch(
    () => semuaTarget(),
    (elemen) => {
      bongkar()
      if (!isBrowser)
        return

      const akar = tempatPasang(elemen as Element[])

      const tangani = (event: Event) => {
        const kini = semuaTarget()

        // Elemen yang belum ada berarti belum siap dinilai. Menganggapnya "di
        // luar" akan menutup menu tepat pada klik yang membukanya.
        const didalam = kini.some((el) => {
          if (!el)
            return true
          return 'contains' in el && el.contains(event.target as Node)
        })

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

      for (const nama of daftarNama)
        akar.addEventListener(nama, tangani)

      lepas = () => {
        for (const nama of daftarNama)
          akar.removeEventListener(nama, tangani)
      }
    },
    { immediate: true, flush: 'post' },
  )

  const berhenti = () => {
    bongkar()
    berhentiPantau()
  }

  if (getCurrentScope())
    onScopeDispose(berhenti)

  return berhenti
}

API ​

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

Di halaman ini

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