Skip to content

Commit

Permalink
fix(useTextSelection)!: listen to selectionchange event (#1194)
Browse files Browse the repository at this point in the history
  • Loading branch information
okxiaoliang4 committed Feb 27, 2022
1 parent 542140c commit bc3da76
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 69 deletions.
25 changes: 16 additions & 9 deletions packages/core/useTextSelection/demo.vue
@@ -1,19 +1,26 @@
<script setup lang="ts">
import { computed } from 'vue-demi'
import { computed, ref } from 'vue-demi'
import { useTextSelection } from './index'
const state = useTextSelection()
const selectedStyle = computed(() => state.value.text ? 'text-primary' : 'text-gray-400')
const position = computed(() => ({ top: state.value.top, right: state.value.right, bottom: state.value.bottom, left: state.value.left }))
const size = computed(() => ({ width: state.value.width, height: state.value.height }))
const demo = ref()
const { rects, text } = useTextSelection()
const selectedStyle = computed(() => text.value ? 'text-primary' : 'text-gray-400')
</script>
<template>
<div>
<div ref="demo">
<p class="font-600 text-blue-600">
You can select any text on the page.
</p>
<p><strong class="w-140px inline-block">Selected Text:</strong> <em :class="selectedStyle">{{ state.text || 'No selected' }}</em></p>
<p><strong class="w-140px inline-block">Selected Position:</strong> {{ JSON.stringify(position) }}</p>
<p><strong class="w-140px inline-block">Selected Size:</strong> {{ JSON.stringify(size) }}</p>
<p>
<strong class="w-140px inline-block">Selected Text:</strong>
<em
:class="selectedStyle"
class="whitespace-pre h-72 overflow-y-auto inline-block"
>{{ text || 'No selected' }}</em>
</p>
<p>
<strong class="w-140px inline-block">Selected rects:</strong>
{{ JSON.stringify(rects) }}
</p>
</div>
</template>
89 changes: 29 additions & 60 deletions packages/core/useTextSelection/index.ts
@@ -1,70 +1,39 @@
import type { MaybeRef } from '@vueuse/shared'
import { ref } from 'vue-demi'
import { computed, ref } from 'vue-demi'
import { useEventListener } from '../useEventListener'
import { defaultWindow } from '../_configurable'

type Rect = Omit<DOMRectReadOnly, 'x'|'y'|'toJSON'>

export interface UseTextSelectionState extends Rect {
text: string
}

const initialRect: Rect = {
top: 0,
left: 0,
bottom: 0,
right: 0,
height: 0,
width: 0,
}

const initialState: UseTextSelectionState = {
text: '',
...initialRect,
}

function getRectFromSelection(selection: Selection | null): Rect {
if (!selection || selection.rangeCount < 1)
return initialRect

const range = selection.getRangeAt(0)
const { height, width, top, left, right, bottom } = range.getBoundingClientRect()
return {
height,
width,
top,
left,
right,
bottom,
function getRangesFromSelection(selection: Selection) {
const rangeCount = selection.rangeCount ?? 0
const ranges = new Array(rangeCount)
for (let i = 0; i < rangeCount; i++) {
const range = selection.getRangeAt(i)
ranges[i] = range
}
return ranges
}

export function useTextSelection(
element?: MaybeRef<HTMLElement | Document | null | undefined>,
) {
const state = ref(initialState)

if (!defaultWindow?.getSelection) return state

const onMouseup = () => {
const text = window.getSelection()?.toString()
if (text) {
const rect = getRectFromSelection(window.getSelection())
state.value = {
...state.value,
...rect,
text,
}
}
/**
* Reactively track user text selection based on [`Window.getSelection`](https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection).
*
* @see https://vueuse.org/useTextSelection
*/
export function useTextSelection() {
const selection = ref<Selection | null>(null)
const text = computed(() => selection.value?.toString() ?? '')
const ranges = computed<Range[]>(() => selection.value ? getRangesFromSelection(selection.value) : [])
const rects = computed(() => ranges.value.map(range => range.getBoundingClientRect()))

function onSelectionChange() {
selection.value = null // trigger computed update
selection.value = window.getSelection()
}
const onMousedown = () => {
state.value.text && (state.value = initialState)
window.getSelection()?.removeAllRanges()
}
useEventListener(element ?? document, 'mouseup', onMouseup)
useEventListener(document, 'mousedown', onMousedown)

return state
useEventListener(document, 'selectionchange', onSelectionChange)
return {
text,
rects,
ranges,
selection,
}
}

export type UseTextSelectionReturn = ReturnType<typeof useTextSelection>

0 comments on commit bc3da76

Please sign in to comment.