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

useClipboard

SebelumnyaBerikutnya

Menyalin ke papan klip, dengan penanda tersalin yang padam sendiri.

Clipboard API hanya tersedia di konteks aman (HTTPS atau localhost).

Menyalin ke papan klip, dengan penanda "tersalin" yang padam sendiri.

Jalur lamanya — document.execCommand('copy') lewat <textarea> tersembunyi — sengaja dipertahankan di balik opsi legacy, dan bukan karena peramban tua. navigator.clipboard hanya ada di konteks aman: HTTPS atau localhost. Tombol salin yang bekerja sempurna di mesin sendiri dan diam saja di jaringan kantor lewat HTTP adalah cacat yang membingungkan justru karena tidak melempar apa-apa.

copied padam sendiri setelah copiedDuring, dan timernya dibatalkan bersama scope.

Pemasangan ​

npx shadcn-vue@latest add @kliping/use-clipboard

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

Ikut terpasang sendiri: is-browser, use-event-listener.

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

ts
import { getCurrentScope, onScopeDispose, readonly, ref, toValue } from 'vue'
import type { MaybeRefOrGetter } from 'vue'
import { isBrowser } from '@/lib/is-browser'
import { useEventListener } from '@/composables/use-event-listener'

export interface UseClipboardOptions {
  /** Ikuti isi papan klip saat orang menyalin di tempat lain. Bawaan: false. */
  read?: boolean
  /** Teks bawaan yang disalin kalau `copy()` dipanggil tanpa argumen. */
  source?: MaybeRefOrGetter<string | undefined>
  /** Berapa lama `copied` menyala, dalam milidetik. Bawaan: 1500. */
  copiedDuring?: number
  /** Pakai jalur `execCommand` kalau Clipboard API tidak tersedia. Bawaan: false. */
  legacy?: boolean
}

function salinCaraLama(nilai: string) {
  const kotak = document.createElement('textarea')
  kotak.value = nilai
  kotak.style.position = 'absolute'
  kotak.style.opacity = '0'
  kotak.setAttribute('readonly', '')
  document.body.appendChild(kotak)
  kotak.select()
  document.execCommand('copy')
  kotak.remove()
}

export function useClipboard(options: UseClipboardOptions = {}) {
  const { read = false, source, copiedDuring = 1500, legacy = false } = options

  const adaApi = isBrowser && 'clipboard' in navigator
  const isSupported = ref(adaApi || legacy)

  const text = ref('')
  const copied = ref(false)

  let timer: ReturnType<typeof setTimeout> | undefined

  const bacaPapanKlip = async () => {
    if (adaApi) {
      try {
        text.value = await navigator.clipboard.readText()
        return
      }
      catch {
        // Izin ditolak atau bukan konteks aman — jatuh ke jalur lama.
      }
    }
    text.value = document?.getSelection?.()?.toString() ?? ''
  }

  useEventListener(['copy', 'cut'], bacaPapanKlip, {
    target: isBrowser ? document : undefined,
    passive: true,
    enable: () => isSupported.value && read,
  })

  const copy = async (nilai?: string) => {
    const isi = nilai ?? toValue(source)
    if (!isSupported.value || isi == null)
      return

    let pakaiCaraLama = !adaApi
    if (!pakaiCaraLama) {
      try {
        await navigator.clipboard.writeText(isi)
      }
      catch {
        pakaiCaraLama = true
      }
    }
    if (pakaiCaraLama && legacy)
      salinCaraLama(isi)

    text.value = isi
    copied.value = true

    if (timer !== undefined)
      clearTimeout(timer)

    timer = setTimeout(() => {
      copied.value = false
      timer = undefined
    }, copiedDuring)
  }

  if (getCurrentScope()) {
    onScopeDispose(() => {
      if (timer !== undefined)
        clearTimeout(timer)
    })
  }

  return { isSupported: readonly(isSupported), text, copied: readonly(copied), copy }
}

API ​

ts
export interface UseClipboardOptions {
  /** Ikuti isi papan klip saat orang menyalin di tempat lain. Bawaan: false. */
  read?: boolean
  /** Teks bawaan yang disalin kalau `copy()` dipanggil tanpa argumen. */
  source?: MaybeRefOrGetter<string | undefined>
  /** Berapa lama `copied` menyala, dalam milidetik. Bawaan: 1500. */
  copiedDuring?: number
  /** Pakai jalur `execCommand` kalau Clipboard API tidak tersedia. Bawaan: false. */
  legacy?: boolean
}

export function useClipboard(options: UseClipboardOptions = {})
useClickAwayuseDocumentVisibility

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