1- import type {
2- EditorPlugin ,
3- IEditor ,
4- PluginEvent ,
5- ReadonlyContentModelDocument ,
6- } from 'roosterjs-content-model-types' ;
1+ import type { EditorPlugin , IEditor , PluginEvent } from 'roosterjs-content-model-types' ;
72import { getNodePositionFromEvent } from '../utils/getNodePositionFromEvent' ;
8- import {
9- getSelectedSegmentsAndParagraphs ,
10- mutateBlock ,
11- createSelectionMarker ,
12- } from 'roosterjs-content-model-dom' ;
13- import { adjustWordSelection } from 'roosterjs-content-model-api' ;
143
154const MAX_TOUCH_MOVE_DISTANCE = 6 ; // the max number of offsets for the touch selection to move
165const POINTER_DETECTION_DELAY = 150 ; // Delay time to wait for selection to be updated and also detect if pointerup is a tap or part of double tap
176const PUNCTUATION_MATCHING_REGEX = / [ . , ; : ! ] / ;
187const SPACE_MATCHING_REGEX = / \s / ;
19- const CARET_CSS_RULE = 'caret-color: transparent' ;
20- const HIDE_CURSOR_CSS_KEY = '_DOMSelectionHideCursor' ;
218
229/**
2310 * Touch plugin to manage touch behaviors
@@ -72,7 +59,7 @@ export class TouchPlugin implements EditorPlugin {
7259 case 'pointerDown' :
7360 this . isDblClicked = false ;
7461 this . isTouchPenPointerEvent = true ;
75- this . editor . setEditorStyle ( HIDE_CURSOR_CSS_KEY , CARET_CSS_RULE ) ;
62+ event . originalEvent . preventDefault ( ) ;
7663
7764 const targetWindow = this . editor . getDocument ( ) ?. defaultView || window ;
7865 if ( this . timer ) {
@@ -84,16 +71,66 @@ export class TouchPlugin implements EditorPlugin {
8471
8572 if ( this . editor ) {
8673 if ( ! this . isDblClicked ) {
87- this . editor . formatContentModel ( model =>
88- this . repositionTouchSelection ( model )
74+ this . editor . focus ( ) ;
75+ const caretPosition = getNodePositionFromEvent (
76+ this . editor ,
77+ event . rawEvent . x ,
78+ event . rawEvent . y
8979 ) ;
80+
81+ const newRange = this . editor . getDocument ( ) . createRange ( ) ;
82+ if ( caretPosition ) {
83+ const { node, offset } = caretPosition ;
84+
85+ // Place cursor at same position of browser handler by default
86+ newRange . setStart ( node , offset ) ;
87+ newRange . setEnd ( node , offset ) ;
88+
89+ const nodeTextContent = node . textContent || '' ;
90+ const charAtSelection = nodeTextContent [ offset ] ;
91+ if (
92+ node . nodeType === Node . TEXT_NODE &&
93+ charAtSelection &&
94+ ! SPACE_MATCHING_REGEX . test ( charAtSelection ) &&
95+ ! PUNCTUATION_MATCHING_REGEX . test ( charAtSelection )
96+ ) {
97+ const { wordStart, wordEnd } = findWordBoundaries (
98+ nodeTextContent ,
99+ offset
100+ ) ;
101+
102+ // Move cursor to the calculated offset
103+ const leftCursorWordLength = offset - wordStart ;
104+ const rightCursorWordLength = wordEnd - offset ;
105+ let movingOffset : number =
106+ leftCursorWordLength >= rightCursorWordLength
107+ ? rightCursorWordLength
108+ : - leftCursorWordLength ;
109+ movingOffset =
110+ Math . abs ( movingOffset ) > MAX_TOUCH_MOVE_DISTANCE
111+ ? 0
112+ : movingOffset ;
113+ const newOffsetPosition = offset + movingOffset ;
114+ if (
115+ movingOffset !== 0 &&
116+ nodeTextContent . length >= newOffsetPosition
117+ ) {
118+ newRange . setStart ( node , newOffsetPosition ) ;
119+ newRange . setEnd ( node , newOffsetPosition ) ;
120+ }
121+ }
122+ }
123+ this . editor . setDOMSelection ( {
124+ type : 'range' ,
125+ range : newRange ,
126+ isReverted : false ,
127+ } ) ;
128+
90129 // reset values
91130 this . isTouchPenPointerEvent = false ;
92131 }
93- this . editor . setEditorStyle ( HIDE_CURSOR_CSS_KEY , null ) ;
94132 }
95133 } , POINTER_DETECTION_DELAY ) ;
96-
97134 break ;
98135 case 'doubleClick' :
99136 if ( this . isTouchPenPointerEvent ) {
@@ -177,82 +214,6 @@ export class TouchPlugin implements EditorPlugin {
177214 break ;
178215 }
179216 }
180-
181- repositionTouchSelection = ( model : ReadonlyContentModelDocument ) => {
182- const segmentAndParagraphs = getSelectedSegmentsAndParagraphs (
183- model ,
184- false /*includingFormatHolder*/ ,
185- true /*includingEntity*/ ,
186- true /*mutate*/
187- ) ;
188-
189- const isCollapsedSelection =
190- segmentAndParagraphs . length >= 1 &&
191- segmentAndParagraphs . every ( x => x [ 0 ] . segmentType == 'SelectionMarker' ) ;
192-
193- // 1. adjust selection to a word if selection is collapsed
194- if ( isCollapsedSelection ) {
195- const para = segmentAndParagraphs [ 0 ] [ 1 ] ;
196- const segments = adjustWordSelection ( model , segmentAndParagraphs [ 0 ] [ 0 ] ) ;
197-
198- if (
199- segments . length > 2 &&
200- segments . some ( x => x . segmentType == 'Text' && ! x . isSelected ) &&
201- para
202- ) {
203- const selectionMarkerIndexInWord = segments . findIndex (
204- segment => segment . segmentType == 'SelectionMarker'
205- ) ;
206- const selectionMarkerIndexInPara = para . segments . findIndex (
207- segment => segment . segmentType == 'SelectionMarker'
208- ) ;
209- const leftSelectionSegmentsInWord = segments [ selectionMarkerIndexInWord - 1 ] ;
210- const rightSelectionSegmentsInWord = segments [ selectionMarkerIndexInWord + 1 ] ;
211- const leftCursorWordLength =
212- leftSelectionSegmentsInWord . segmentType == 'Text'
213- ? leftSelectionSegmentsInWord . text . length
214- : 0 ;
215- const rightCursorWordLength =
216- rightSelectionSegmentsInWord . segmentType == 'Text'
217- ? rightSelectionSegmentsInWord . text . length
218- : 0 ;
219-
220- // Move the cursor to the closest edge of the word if the distance is within threshold = 6
221- if ( rightCursorWordLength > leftCursorWordLength ) {
222- if ( leftCursorWordLength < MAX_TOUCH_MOVE_DISTANCE ) {
223- // Move cursor to beginning of word
224- // Remove old marker
225- mutateBlock ( para ) . segments . splice ( selectionMarkerIndexInPara , 1 ) ;
226-
227- // Add new marker
228- const indexSegmentBeforeMarker = para . segments . findIndex (
229- segment => segment === leftSelectionSegmentsInWord
230- ) ;
231- const marker = createSelectionMarker (
232- segments [ selectionMarkerIndexInPara ] ?. format || para . format
233- ) ;
234- mutateBlock ( para ) . segments . splice ( indexSegmentBeforeMarker , 0 , marker ) ;
235- }
236- } else {
237- // Move cursor to end of word
238- if ( rightCursorWordLength < MAX_TOUCH_MOVE_DISTANCE ) {
239- // Add new marker
240- const indexSegmentAfterMarker = para . segments . findIndex (
241- segment => segment === rightSelectionSegmentsInWord
242- ) ;
243- const marker = createSelectionMarker (
244- segments [ selectionMarkerIndexInPara ] ?. format || para . format
245- ) ;
246- mutateBlock ( para ) . segments . splice ( indexSegmentAfterMarker + 1 , 0 , marker ) ;
247-
248- // Remove old marker
249- mutateBlock ( para ) . segments . splice ( selectionMarkerIndexInPara , 1 ) ;
250- }
251- }
252- }
253- }
254- return true ;
255- } ;
256217}
257218
258219/**
0 commit comments