1+ import { RefObject , useCallback , useMemo , useRef } from "react" ;
2+ import { TextSelection } from "../models" ;
3+ import { isDeepEqual , useSyncExternalStore } from ".." ;
4+
5+ /**
6+ * TODO
7+ * non funziona bene, vanno gestiti gli eventi per come qui https://excalidraw.com/#json=7nYktheXKBE2A3aBYxPVx,s3gx5SWbNG-GHk2_VsODRw
8+ * il getTextSelectionDataSet non ritorna i rettangoli se il testo selezionato è di uno stesso elemento ma va a capo.
9+ */
10+ 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 => {
11+ const selectionEnd = useCallback ( ( evt : Event ) => {
12+ const element = target
13+ ? ( target as RefObject < HTMLElement > ) . current
14+ ? ( target as RefObject < HTMLElement > ) . current
15+ : target as HTMLElement
16+ : document ;
17+ onEnd && onEnd ( evt ) ;
18+ onChange && document . removeEventListener ( "selectionchange" , onChange ) ;
19+ element ?. removeEventListener ( "pointerleave" , selectionEndWrap . current ! ) ;
20+ document . removeEventListener ( "pointerup" , selectionEndWrap . current ! ) ;
21+ } , [ onEnd , onChange , target ] ) ;
22+
23+ const selectionEndWrap = useRef < EventListenerOrEventListenerObject > ( ) ;
24+
25+ const selectionStart = useCallback ( ( evt : Event , notif : ( ) => void ) => {
26+ const element = target
27+ ? ( target as RefObject < HTMLElement > ) . current
28+ ? ( target as RefObject < HTMLElement > ) . current
29+ : target as HTMLElement
30+ : document ;
31+ if ( ! element ?. contains ( evt . target as HTMLElement ) ) {
32+ return ;
33+ }
34+ onStart && onStart ( evt ) ;
35+ onChange && document . addEventListener ( "selectionchange" , onChange ) ;
36+ selectionEndWrap . current = ( evt : Event ) => {
37+ selectionEnd ( evt ) ;
38+ notif ( ) ;
39+ }
40+ document . addEventListener ( "pointerup" , selectionEndWrap . current ) ;
41+ element ?. addEventListener ( "pointerleave" , selectionEndWrap . current ) ;
42+ } , [ onStart , onChange , selectionEnd , target ] ) ;
43+
44+ return useSyncExternalStore (
45+ useCallback ( notif => {
46+ const listener = ( evt : Event ) => {
47+ selectionStart ( evt , notif ) ;
48+ }
49+ document . addEventListener ( "selectstart" , listener ) ;
50+
51+ return ( ) => {
52+ document . removeEventListener ( "selectstart" , listener )
53+ }
54+ } , [ selectionStart ] ) ,
55+ useMemo ( ( ) => {
56+ let element = target
57+ ? ( target as RefObject < HTMLElement > ) . current
58+ ? ( target as RefObject < HTMLElement > ) . current
59+ : target as HTMLElement
60+ : document ;
61+ let selection = getTextSelectionDataSet ( element !== document ? element as HTMLElement : undefined ) ;
62+ return ( ) => {
63+ const currElement = target
64+ ? ( target as RefObject < HTMLElement > ) . current
65+ ? ( target as RefObject < HTMLElement > ) . current
66+ : target as HTMLElement
67+ : document ;
68+ const currSelection = getTextSelectionDataSet ( currElement !== document ? currElement as HTMLElement : undefined ) ;
69+ if ( element !== currElement || ! isDeepEqual ( currSelection , selection ) ) {
70+ element = currElement ;
71+ selection = currSelection ;
72+ }
73+ return selection ;
74+ }
75+ } , [ target ] )
76+ ) ;
77+ }
78+
179function getSelectedTextDirection ( selection : Selection ) {
280 const range = document . createRange ( ) ;
381 range . setStart ( selection . anchorNode ! , selection . anchorOffset ) ;
482 range . setEnd ( selection . focusNode ! , selection . focusOffset ) ;
583 return range . collapsed ? 'backward' : 'forward' ;
684}
7- function getTextSelectionDataSet ( ) {
85+ function getTextSelectionDataSet ( parentElement ?: HTMLElement ) : TextSelection | null {
886 const ws = window . getSelection ( ) ;
987 if ( ws === null || ws . toString ( ) . trim ( ) === "" ) {
10- return { text : "" , outsideRectangle : null , innerRectangles : [ ] } ;
88+ return null ;
1189 }
12- const data : { text : string , outsideRectangle : DOMRect | null , innerRectangles : Omit < DOMRect , "toJSON" > [ ] } = {
90+ const parentElementDim = ( parentElement ?? document . body ) . getBoundingClientRect ( ) ;
91+ const selectionDim = ws . getRangeAt ( 0 ) . getBoundingClientRect ( ) ;
92+ const data : TextSelection = {
1393 text : ws . toString ( ) ,
14- outsideRectangle : ws . getRangeAt ( 0 ) . getBoundingClientRect ( ) ,
94+ outsideRectangle : new DOMRect (
95+ selectionDim . x - parentElementDim . x ,
96+ selectionDim . y - parentElementDim . y ,
97+ selectionDim . width ,
98+ selectionDim . height ,
99+ ) ,
15100 innerRectangles : [ ]
16101 }
17102 const direction = getSelectedTextDirection ( ws ) ,
@@ -32,7 +117,7 @@ function getTextSelectionDataSet() {
32117
33118 const range = document . createRange ( ) ;
34119 range . setStart ( text , offset ) ;
35- selectedText = ( text as Node & { data : string } ) . data . toString ( ) . substring ( offset ) ;
120+ selectedText = ( text as Node & { data : string } ) . data . toString ( ) . substring ( offset ) ;
36121 if ( allText . length <= selectedText . length ) {
37122 range . setEnd ( text , offset + allText . length ) ;
38123 ranges . push ( range ) ;
@@ -72,15 +157,6 @@ function getTextSelectionDataSet() {
72157 text = text ! . nextSibling ;
73158 }
74159 }
75- // if (spanText === null) {
76- // container = container!.nextSibling;
77- // for (let i = 0, size = (container! as HTMLElement).children.length; i < size; i++) {
78- // if ((container! as HTMLElement).children[i].nodeName === "SPAN") {
79- // spanText = (container! as HTMLElement).children[i];
80- // break;
81- // }
82- // }
83- // }
84160 const range = document . createRange ( ) ;
85161 selectedText = ( text as Node & { data : string } ) . data . toString ( ) ;
86162 range . setStart ( text , 0 ) ;
@@ -97,18 +173,13 @@ function getTextSelectionDataSet() {
97173
98174 data . innerRectangles = ranges . map ( el => {
99175 const dim = el . getBoundingClientRect ( ) ;
100- return {
101- x : dim . x - ( data . outsideRectangle ? data . outsideRectangle . x : 0 ) ,
102- y : dim . y - ( data . outsideRectangle ? data . outsideRectangle . y : 0 ) ,
103- top : dim . top - ( data . outsideRectangle ? data . outsideRectangle . top : 0 ) ,
104- left : dim . left - ( data . outsideRectangle ? data . outsideRectangle . left : 0 ) ,
105- right : dim . right - ( data . outsideRectangle ? data . outsideRectangle . right : 0 ) ,
106- bottom : dim . bottom - ( data . outsideRectangle ? data . outsideRectangle . bottom : 0 ) ,
107- width : dim . width ,
108- height : dim . height
109- }
176+ return new DOMRect (
177+ dim . x - parentElementDim . x ,
178+ dim . y - parentElementDim . y ,
179+ dim . width ,
180+ dim . height
181+ ) ;
110182 } ) ;
111183
112- window . getSelection ( ) ?. removeAllRanges ( ) ;
113- console . log ( "TEXT_SELECTION_RANGES" , data ) ;
184+ return data ;
114185}
0 commit comments