Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(useTextSelection)!: listen to
selectionchange
event (#1194)
- Loading branch information
1 parent
542140c
commit bc3da76
Showing
2 changed files
with
45 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> |