kliping.pro
  • Docs
  • Explore
  • Builder
  • Directory
  • Colors
  • Typeset
Saved
Hooks
  • Getting Started2
  • 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
  1. Hooks
  2. Browser
  3. useTextSelection

useTextSelection

PreviousNext

Teks yang sedang disorot, beserta letaknya di layar.

Letaknya yang membuat composable ini berguna: dari situ sebuah bilah alat kecil — kutip, salin, sorot — bisa muncul tepat di atas teks yang dipilih.

mousedown mengosongkan pilihan sebelumnya dan mencatat apakah tekanannya bermula di dalam target. Tanpa catatan itu, menyeret dari luar lalu berakhir di dalam akan terbaca sebagai pilihan milik target — padahal bukan. Klik kanan dilewati karena menu konteks tidak menghapus pilihan yang ada.

Installation ​

pnpm dlx kliping@latest add use-text-selection

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, ref, watch } from 'vue'
import { isBrowser } from '@/lib/is-browser'
import { getTargetElement } from '@/lib/target'
import type { BasicTarget } from '@/lib/target'

export interface SelectionRect {
  top: number
  left: number
  bottom: number
  right: number
  height: number
  width: number
}

const EMPTY_RECT: SelectionRect = {
  top: Number.NaN,
  left: Number.NaN,
  bottom: Number.NaN,
  right: Number.NaN,
  height: Number.NaN,
  width: Number.NaN,
}

function rectFromSelection(selection: Selection | null): SelectionRect {
  if (!selection || selection.rangeCount < 1) return EMPTY_RECT

  const { top, left, bottom, right, height, width } = selection
    .getRangeAt(0)
    .getBoundingClientRect()
  return { top, left, bottom, right, height, width }
}

export function useTextSelection(target?: BasicTarget) {
  const text = ref('')
  const rect = ref<SelectionRect>({ ...EMPTY_RECT })

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

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

  const stopWatch = watch(
    () => getTargetElement(target),
    (element) => {
      teardown()
      if (!isBrowser) return

      const el = (element ?? document) as Document | Element

      const onMouseUp = () => {
        if (typeof window.getSelection !== 'function') return

        const selection = window.getSelection()
        const content = selection ? selection.toString() : ''

        if (content && startedInside) {
          text.value = content
          rect.value = rectFromSelection(selection)
        }
      }

      const onMouseDown = (event: MouseEvent) => {
        if (event.button === 2) return
        if (typeof window.getSelection !== 'function') return

        if (text.value) {
          text.value = ''
          rect.value = { ...EMPTY_RECT }
        }

        startedInside = false
        const selection = window.getSelection()
        if (!selection) return

        selection.removeAllRanges()
        startedInside = 'contains' in el ? el.contains(event.target as Node) : true
      }

      el.addEventListener('mouseup', onMouseUp as EventListener)
      document.addEventListener('mousedown', onMouseDown as EventListener)

      detach = () => {
        el.removeEventListener('mouseup', onMouseUp as EventListener)
        document.removeEventListener('mousedown', onMouseDown as EventListener)
      }
    },
    { immediate: true, flush: 'post' },
  )

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

  if (getCurrentScope()) onScopeDispose(stop)

  return {
    /** Selected text; empty when nothing is selected. Reactive. */
    text,
    /** Bounding box of the selection, in viewport pixels; `NaN` fields when empty. Reactive. */
    rect,
  }
}

Requirements ​

RequirementValue
ProjectVue 3.5+, alias @ — disiapkan kliping init
Kliping itemsis-browser, target
Filescomposables/use-text-selection.ts

API Reference ​

ts
export interface SelectionRect {
  top: number
  left: number
  bottom: number
  right: number
  height: number
  width: number
}

export function useTextSelection(target?: BasicTarget)

Parameters ​

ParameterTypeDefaultDescription
target?BasicTarget—Element whose selection is tracked; leave empty for the whole document.

Returns ​

PropertyTypeDescription
textRef<string, string>Selected text; empty when nothing is selected. Reactive.
rectRef<{ top: number; left: number; bottom: number; right: number; height: number; width: number; }, SelectionRect | { top: number; left: number; bottom: number; right: number; height: number; width: number; }>Bounding box of the selection, in viewport pixels; NaN fields when empty. Reactive.

Credits ​

Berasal dari shadcn-hooks (MIT, Copyright (c) 2025 Brendan Dash). Bentuk API-nya dari ahooks lewat shadcn-hooks. Rantai lengkapnya di Credits.

PrevioususeScrollLockNext useTitle

On This Page

InstallationRequirementsAPI ReferenceParametersReturnsCredits

© 2026 Kliping. Free components are open source under the MIT License.

LicenseCreditsRoadmap