Skip to content

Commit 0d1b2c4

Browse files
committed
[IMPL] useTextSelection hook
1 parent 19bfecd commit 0d1b2c4

7 files changed

Lines changed: 106 additions & 25 deletions

File tree

apps/react-tools-demo/src/components/home/Home.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export default function Home() {
2020
</code>
2121
</p>
2222
</div>
23-
<Link className='get-started' to="/usePrevious">GET STARTED</Link>
23+
<Link className='get-started' onClick={() => document.querySelector('.container')?.scrollTo(0, 0)} to="/usePrevious">GET STARTED</Link>
2424
<div className='features-container'>
2525
<div className='cell'>
2626
<div className='title'>Typescript</div>

apps/react-tools-demo/src/components/hooks/useTextSelection/UseTextSelection.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { useMemo, useRef } from "react";
22
import { useTextSelection } from "../../../../../../packages/react-tools/src"
3+
4+
/**
5+
The component renders a grid with two columns. First column contains _two tag p elements_ with text and secondo column the result of _useTextSelection hook_.
6+
7+
_useTextSelection_ is initialized with a _target_ property that has a ref attached to first column div and _onEnd_ property that has a function that clean selection. When text is selected appears colored rectangles with the coordinates returned from hook.
8+
*/
39
export const UseTextSelection = () => {
410
const ref = useRef<HTMLDivElement>(null);
511
const selection = useTextSelection({ target: ref, onEnd: () => {getSelection()?.removeAllRanges()} });
@@ -48,7 +54,7 @@ export const UseTextSelection = () => {
4854
</div>
4955
{rectangles}
5056
</div>
51-
<div style={{textAlign: "left", padding: "0 1em", overflow: "auto", border: "1px solid lightgray"}}>
57+
<div style={{textAlign: "left", padding: "0 1em", maxHeight: 300, overflow: "auto", border: "1px solid lightgray"}}>
5258
<p><strong>Selection:</strong></p>
5359
<pre>{JSON.stringify(selection, null, 2)}</pre>
5460
</div>

apps/react-tools-demo/src/markdown/isTouchEvent.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ It returns true if the event param is of TouchEvent type.
44
## API
55

66
```tsx
7-
isTouchEvent (event: SyntheticEvent): boolean
7+
isTouchEvent (event: SyntheticEvent | Event): boolean
88
```
99

1010
> ### Params

apps/react-tools-demo/src/markdown/useStateGetReset.md

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,7 @@ Custom useState with get and reset state functions.
66
```tsx
77
const UseStateGetReset = () => {
88
const [stateG, setStateG, getState, resetState] = useStateGetReset({ id: "", name: "", eta: "" });
9-
const ref = useRef(getState);
10-
const [state, setState] = useState({ id: "", name: "", eta: "" });
11-
const update = useUpdate();
12-
if (!isShallowEqual(ref.current, getState)) {
13-
console.log("different");
14-
15-
}
16-
17-
useEffect(() => {
18-
const id = setInterval(() => update(), 1000);
19-
return () => clearInterval(id)
20-
}, [update])
9+
const [state, setState] = useState({ id: "", name: "", eta:"" });
2110

2211
const onChangeGetter = useCallback((e: BaseSyntheticEvent) => {
2312
const state = getState();
Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,96 @@
1-
#
1+
# useTextSelection
2+
Hook to track text selection and its size.
3+
4+
## Usage
5+
6+
```tsx
7+
export const UseTextSelection = () => {
8+
const ref = useRef<HTMLDivElement>(null);
9+
const selection = useTextSelection({ target: ref, onEnd: () => {getSelection()?.removeAllRanges()} });
10+
const rectangles = useMemo(() => {
11+
if (!selection) {
12+
return null;
13+
} else {
14+
const rectangles = [];
15+
rectangles.push(
16+
<div
17+
key="outside-rectangle"
18+
style={{
19+
position: "absolute",
20+
border: ".5px solid red",
21+
top: selection.outsideRectangle.top + "px",
22+
left: selection.outsideRectangle.left + "px",
23+
width: selection.outsideRectangle.width + "px",
24+
height: selection.outsideRectangle.height + "px",
25+
}}
26+
></div>
27+
);
28+
selection.innerRectangles.forEach((el, index) => {
29+
rectangles.push(<div
30+
key={"inner-rectangle-"+index}
31+
style={{
32+
position: "absolute",
33+
border: ".5px solid darkcyan",
34+
top: el.top + "px",
35+
left: el.left + "px",
36+
width: el.width + "px",
37+
height: el.height + "px",
38+
}}
39+
></div>);
40+
})
41+
return rectangles;
42+
}
43+
}, [selection]);
44+
45+
return <div style={{ display: "grid", gridTemplateColumns: "50% 50%", columnGap: 15 }}>
46+
<div ref={ref} style={{ position: "relative", border: "1px solid lightgray" }}>
47+
<div>
48+
<p>Lorem ipsum, dolor sit amet consectetur adipisicing elit. Incidunt repudiandae fugit distinctio molestiae excepturi ex qui, impedit iste odit. Explicabo quis reprehenderit voluptates reiciendis nostrum minima autem temporibus sint doloribus</p>
49+
</div>
50+
<div>
51+
<p>Lorem ipsum, dolor sit amet consectetur adipisicing elit. Incidunt repudiandae fugit distinctio molestiae excepturi ex qui, impedit iste odit. Explicabo quis reprehenderit voluptates reiciendis nostrum minima autem temporibus sint doloribus</p>
52+
</div>
53+
{rectangles}
54+
</div>
55+
<div style={{textAlign: "left", padding: "0 1em", maxHeight: 300, overflow: "auto", border: "1px solid lightgray"}}>
56+
<p><strong>Selection:</strong></p>
57+
<pre>{JSON.stringify(selection, null, 2)}</pre>
58+
</div>
59+
</div>
60+
}
61+
```
62+
63+
> The component renders a grid with two columns. First column contains _two tag p elements_ with text and secondo column the result of _useTextSelection hook_.
64+
>
65+
> _useTextSelection_ is initialized with a _target_ property that has a ref attached to first column div and _onEnd_ property that has a function that clean selection. When text is selected appears colored rectangles with the coordinates returned from hook.
266
367

468
## API
569

670
```tsx
7-
const range = document.createRange();
71+
useTextSelection ({ target, onStart, onChange, onEnd }: { target?: RefObject<HTMLElement> | HTMLElement, onStart?: (evt: Event) => void, onChange?: (evt: Event) => void, onEnd?: (evt: Event) => void } = {}): TextSelection | null
872
```
973

1074
> ### Params
1175
>
12-
>
76+
> - __param__: _Object_
77+
object with selection properties
78+
> - __param.target?__: _RefObject<HTMLElement> | HTMLElement_
79+
element in which allow selection. Default is _document.body_.
80+
> - __param.onStart?__: _(evt: Event) => void_
81+
function to execute when selection starts.
82+
> - __param.onChange?__: _(evt: Event) => void_
83+
function to execute while selection changes.
84+
> - __param.onEnd?__: _(evt: Event) => void_
85+
function to execute while selection ends.
1386
>
1487
1588
> ### Returns
1689
>
17-
>
18-
>
90+
> __TextSelection__: object with: _text_: selected text; _direction_: selection direction; _outsideRectangle_: a __DOMRect__ of selection rectangle; _innerRectangles_: list of __DOMRect__ representing the selection slices.
91+
> - __Object__:
92+
> - __text__ : _string_
93+
> - __direction__ : _"forward"|"backward"_
94+
> - __outsideRectangle__ : _DOMRect_
95+
> - __innerRectangles__ : _DOMRect[]_
1996
>

packages/react-tools/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
- [x] useActiveElement
6565
- [x] useTimeout
6666
- [x] useInterval
67-
- [ ] useTextSelection
67+
- [x] useTextSelection
6868
- [ ] useDocumentVisibility
6969
- [ ] useCoptyToClipboard
7070
- [ ] useColorScheme

packages/react-tools/src/hooks/useTextSelection.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@ import { RefObject, useCallback, useMemo, useRef } from "react";
22
import { TextSelection } from "../models";
33
import { isDeepEqual, useSyncExternalStore } from "..";
44

5+
/**
6+
* **`useTextSelection`**: Hook to track text selection and its size.
7+
* @param {Object} param - object with selection properties
8+
* @param {RefObject<HTMLElement> | HTMLElement} [param.target] - element in which allow selection. Default is _document.body_.
9+
* @param {(evt: Event) => void} [param.onStart] - function to execute when selection starts.
10+
* @param {(evt: Event) => void} [param.onChange] - function to execute while selection changes.
11+
* @param {(evt: Event) => void} [param.onEnd] - function to execute while selection ends.
12+
* @returns {{text: string, direction: "forward"|"backward", outsideRectangle: DOMRect, innerRectangles: DOMRect[]}} TextSelection - object with: _text_: selected text; _direction_: selection direction; _outsideRectangle_: a __DOMRect__ of selection rectangle; _innerRectangles_: list of __DOMRect__ representing the selection slices.
13+
*/
514
export const useTextSelection = ({ target, onStart, onChange, onEnd }: { target?: RefObject<HTMLElement> | HTMLElement, onStart?: (evt: Event) => void, onChange?: (evt: Event) => void, onEnd?: (evt: Event) => void } = {}): TextSelection | null => {
615
const selecting = useRef(false);
716
const selectedText = useRef<string | null>(null);
@@ -40,7 +49,7 @@ export const useTextSelection = ({ target, onStart, onChange, onEnd }: { target?
4049

4150
const pointerUpLeaveTargetHandler = useCallback(() => {
4251
selecting.current = false;
43-
selectedText.current = (getSelection() ?? "").toString() || null;
52+
selectedText.current = (getSelection() ?? "").toString();
4453
onChange && document.removeEventListener("selectionchange", onChange);
4554
}, [onChange]);
4655

@@ -58,7 +67,7 @@ export const useTextSelection = ({ target, onStart, onChange, onEnd }: { target?
5867
? (target as RefObject<HTMLElement>).current
5968
? (target as RefObject<HTMLElement>).current
6069
: target as HTMLElement
61-
: document;
70+
: document.body;
6271
document.addEventListener("pointerdown", pointerDownDocHandler);
6372
document.addEventListener("pointerup", pointerUpLeaveDocHandler);
6473
document.addEventListener("pointerleave", pointerUpLeaveDocHandler);
@@ -84,14 +93,14 @@ export const useTextSelection = ({ target, onStart, onChange, onEnd }: { target?
8493
? (target as RefObject<HTMLElement>).current
8594
? (target as RefObject<HTMLElement>).current
8695
: target as HTMLElement
87-
: document;
96+
: document.body;
8897
let currSelection = selection.current;
8998
return () => {
9099
const currElement = target
91100
? (target as RefObject<HTMLElement>).current
92101
? (target as RefObject<HTMLElement>).current
93102
: target as HTMLElement
94-
: document;
103+
: document.body;
95104
if (element !== currElement || !isDeepEqual(currSelection, selection.current)) {
96105
element = currElement;
97106
currSelection = selection.current;

0 commit comments

Comments
 (0)