@@ -19,6 +19,7 @@ import {
19
19
BaseSelection ,
20
20
LexicalEditor ,
21
21
LexicalNode ,
22
+ NodeKey ,
22
23
Point ,
23
24
RangeSelection ,
24
25
TextNode ,
@@ -302,134 +303,67 @@ export function $forEachSelectedTextNode(
302
303
fn : ( textNode : TextNode ) => void ,
303
304
) : void {
304
305
const selection = $getSelection ( ) ;
305
- if ( ! $isRangeSelection ( selection ) ) {
306
+ if ( ! selection ) {
306
307
return ;
307
308
}
308
- const selectedNodes = selection . getNodes ( ) ;
309
- const selectedNodesLength = selectedNodes . length ;
310
- const { anchor, focus} = selection ;
311
-
312
- const lastIndex = selectedNodesLength - 1 ;
313
- let firstNode = selectedNodes [ 0 ] ;
314
- let lastNode = selectedNodes [ lastIndex ] ;
315
309
316
- const firstNodeText = firstNode . getTextContent ( ) ;
317
- const firstNodeTextLength = firstNodeText . length ;
318
- const focusOffset = focus . offset ;
319
- let anchorOffset = anchor . offset ;
320
- const isBefore = anchor . isBefore ( focus ) ;
321
- let startOffset = isBefore ? anchorOffset : focusOffset ;
322
- let endOffset = isBefore ? focusOffset : anchorOffset ;
323
- const startType = isBefore ? anchor . type : focus . type ;
324
- const endType = isBefore ? focus . type : anchor . type ;
325
- const endKey = isBefore ? focus . key : anchor . key ;
310
+ const slicedTextNodes = new Map <
311
+ NodeKey ,
312
+ [ startIndex : number , endIndex : number ]
313
+ > ( ) ;
314
+ const getSliceIndices = (
315
+ node : TextNode ,
316
+ ) : [ startIndex : number , endIndex : number ] =>
317
+ slicedTextNodes . get ( node . getKey ( ) ) || [ 0 , node . getTextContentSize ( ) ] ;
326
318
327
- // This is the case where the user only selected the very end of the
328
- // first node so we don't want to include it in the formatting change.
329
- if ( $isTextNode ( firstNode ) && startOffset === firstNodeTextLength ) {
330
- const nextSibling = firstNode . getNextSibling ( ) ;
319
+ if ( $isRangeSelection ( selection ) ) {
320
+ const { anchor, focus} = selection ;
321
+ const isBackwards = focus . isBefore ( anchor ) ;
322
+ const [ startPoint , endPoint ] = isBackwards
323
+ ? [ focus , anchor ]
324
+ : [ anchor , focus ] ;
331
325
332
- if ( $isTextNode ( nextSibling ) ) {
333
- // we basically make the second node the firstNode, changing offsets accordingly
334
- anchorOffset = 0 ;
335
- startOffset = 0 ;
336
- firstNode = nextSibling ;
326
+ if ( startPoint . type === 'text' && startPoint . offset > 0 ) {
327
+ const endIndex = getSliceIndices ( startPoint . getNode ( ) ) [ 1 ] ;
328
+ slicedTextNodes . set ( startPoint . key , [
329
+ Math . min ( startPoint . offset , endIndex ) ,
330
+ endIndex ,
331
+ ] ) ;
337
332
}
338
- }
339
-
340
- // This is the case where we only selected a single node
341
- if ( selectedNodes . length === 1 ) {
342
- if ( $isTextNode ( firstNode ) && firstNode . canHaveFormat ( ) ) {
343
- startOffset =
344
- startType === 'element'
345
- ? 0
346
- : anchorOffset > focusOffset
347
- ? focusOffset
348
- : anchorOffset ;
349
- endOffset =
350
- endType === 'element'
351
- ? firstNodeTextLength
352
- : anchorOffset > focusOffset
353
- ? anchorOffset
354
- : focusOffset ;
355
-
356
- // No actual text is selected, so do nothing.
357
- if ( startOffset === endOffset ) {
358
- return ;
359
- }
360
-
361
- // The entire node is selected or a token/segment, so just format it
362
- if (
363
- $isTokenOrSegmented ( firstNode ) ||
364
- ( startOffset === 0 && endOffset === firstNodeTextLength )
365
- ) {
366
- fn ( firstNode ) ;
367
- firstNode . select ( startOffset , endOffset ) ;
368
- } else {
369
- // The node is partially selected, so split it into two nodes
370
- // and style the selected one.
371
- const splitNodes = firstNode . splitText ( startOffset , endOffset ) ;
372
- const replacement = startOffset === 0 ? splitNodes [ 0 ] : splitNodes [ 1 ] ;
373
- fn ( replacement ) ;
374
- replacement . select ( 0 , endOffset - startOffset ) ;
375
- }
376
- } // multiple nodes selected.
377
- } else {
378
- if (
379
- $isTextNode ( firstNode ) &&
380
- startOffset < firstNode . getTextContentSize ( ) &&
381
- firstNode . canHaveFormat ( )
382
- ) {
383
- if ( startOffset !== 0 && ! $isTokenOrSegmented ( firstNode ) ) {
384
- // the entire first node isn't selected and it isn't a token or segmented, so split it
385
- firstNode = firstNode . splitText ( startOffset ) [ 1 ] ;
386
- startOffset = 0 ;
387
- if ( isBefore ) {
388
- anchor . set ( firstNode . getKey ( ) , startOffset , 'text' ) ;
389
- } else {
390
- focus . set ( firstNode . getKey ( ) , startOffset , 'text' ) ;
391
- }
333
+ if ( endPoint . type === 'text' ) {
334
+ const [ startIndex , size ] = getSliceIndices ( endPoint . getNode ( ) ) ;
335
+ if ( endPoint . offset < size ) {
336
+ slicedTextNodes . set ( endPoint . key , [
337
+ startIndex ,
338
+ Math . max ( startIndex , endPoint . offset ) ,
339
+ ] ) ;
392
340
}
393
-
394
- fn ( firstNode as TextNode ) ;
395
341
}
342
+ }
396
343
397
- if ( $isTextNode ( lastNode ) && lastNode . canHaveFormat ( ) ) {
398
- const lastNodeText = lastNode . getTextContent ( ) ;
399
- const lastNodeTextLength = lastNodeText . length ;
400
-
401
- // The last node might not actually be the end node
402
- //
403
- // If not, assume the last node is fully-selected unless the end offset is
404
- // zero.
405
- if ( lastNode . __key !== endKey && endOffset !== 0 ) {
406
- endOffset = lastNodeTextLength ;
407
- }
408
-
409
- // if the entire last node isn't selected and it isn't a token or segmented, split it
410
- if ( endOffset !== lastNodeTextLength && ! $isTokenOrSegmented ( lastNode ) ) {
411
- [ lastNode ] = lastNode . splitText ( endOffset ) ;
412
- }
413
-
414
- if ( endOffset !== 0 || endType === 'element' ) {
415
- fn ( lastNode as TextNode ) ;
416
- }
344
+ const selectedNodes = selection . getNodes ( ) ;
345
+ for ( const selectedNode of selectedNodes ) {
346
+ if ( ! ( $isTextNode ( selectedNode ) && selectedNode . canHaveFormat ( ) ) ) {
347
+ continue ;
348
+ }
349
+ const [ startOffset , endOffset ] = getSliceIndices ( selectedNode ) ;
350
+ // No actual text is selected, so do nothing.
351
+ if ( endOffset === startOffset ) {
352
+ continue ;
417
353
}
418
354
419
- // style all the text nodes in between
420
- for ( let i = 1 ; i < lastIndex ; i ++ ) {
421
- const selectedNode = selectedNodes [ i ] ;
422
- const selectedNodeKey = selectedNode . getKey ( ) ;
423
-
424
- if (
425
- $isTextNode ( selectedNode ) &&
426
- selectedNode . canHaveFormat ( ) &&
427
- selectedNodeKey !== firstNode . getKey ( ) &&
428
- selectedNodeKey !== lastNode . getKey ( ) &&
429
- ! selectedNode . isToken ( )
430
- ) {
431
- fn ( selectedNode as TextNode ) ;
432
- }
355
+ // The entire node is selected or a token/segment, so just format it
356
+ if (
357
+ $isTokenOrSegmented ( selectedNode ) ||
358
+ ( startOffset === 0 && endOffset === selectedNode . getTextContentSize ( ) )
359
+ ) {
360
+ fn ( selectedNode ) ;
361
+ } else {
362
+ // The node is partially selected, so split it into two or three nodes
363
+ // and style the selected one.
364
+ const splitNodes = selectedNode . splitText ( startOffset , endOffset ) ;
365
+ const replacement = splitNodes [ startOffset === 0 ? 0 : 1 ] ;
366
+ fn ( replacement ) ;
433
367
}
434
368
}
435
369
}
0 commit comments