@@ -287,8 +287,8 @@ export class Caret {
287287 const word = wordsCache . qs (
288288 `.word[data-wordindex="${ options . wordIndex } "]` ,
289289 ) ;
290- const letters = word ?. qsa ( "letter" ) ?? [ ] ;
291- const wordText = TestWords . words . get ( options . wordIndex ) ;
290+ const wordText = TestWords . words . get ( options . wordIndex ) ?? "" ;
291+ const wordLength = Array . from ( wordText ) . length ;
292292
293293 // caret can be either on the left side of the target letter or the right
294294 // we stick to the left side unless we are on the last letter or beyond
@@ -297,43 +297,32 @@ export class Caret {
297297 // we also clamp the letterIndex to be within the range of actual letters
298298 // anything beyond just goes to the edge of the word
299299 let side : "beforeLetter" | "afterLetter" = "beforeLetter" ;
300- if ( options . letterIndex >= letters . length ) {
301- side = "afterLetter" ;
300+ if ( Config . mode === "zen" ) {
301+ if ( options . letterIndex > 0 ) {
302+ side = "afterLetter" ;
303+ options . letterIndex -= 1 ;
304+ }
305+ } else {
306+ if ( options . letterIndex >= wordLength ) {
307+ side = "afterLetter" ;
302308
303- if ( Config . blindMode || Config . hideExtraLetters ) {
304- options . letterIndex = ( wordText ?. length ?? letters . length ) - 1 ;
305- } else {
306- options . letterIndex = letters . length - 1 ;
309+ if ( Config . blindMode || Config . hideExtraLetters ) {
310+ options . letterIndex = wordLength - 1 ;
311+ } else {
312+ options . letterIndex -= 1 ;
313+ }
307314 }
308315 }
309316
310317 if ( options . letterIndex < 0 ) {
311318 options . letterIndex = 0 ;
312319 }
313320
314- let letter = word ?. qsa ( "letter" ) [ options . letterIndex ] ;
315-
316- if ( word === null || letter === undefined ) {
317- return ;
318- }
319-
320- if ( caretDebug ) {
321- if ( this . id === "paceCaret" ) {
322- for ( const l of document . querySelectorAll ( ".word letter" ) ) {
323- l . classList . remove ( "debugCaretTarget" ) ;
324- l . classList . remove ( "debugCaretTarget2" ) ;
325- l . classList . add ( "debugCaret" ) ;
326- }
327- letter ?. addClass ( "debugCaretTarget" ) ;
328- this . element . addClass ( "debug" ) ;
329- }
330- } else {
331- this . element . removeClass ( "debug" ) ;
332- }
321+ if ( word === null ) return ;
333322
334323 const { left, top, width } = this . getTargetPositionAndWidth ( {
335324 word,
336- letter ,
325+ letterIndex : options . letterIndex ,
337326 wordText,
338327 side,
339328 isLanguageRightToLeft : options . isLanguageRightToLeft ,
@@ -399,59 +388,67 @@ export class Caret {
399388
400389 private getTargetPositionAndWidth ( options : {
401390 word : ElementWithUtils ;
402- letter : ElementWithUtils ;
391+ letterIndex : number ;
403392 wordText : string ;
404393 side : "beforeLetter" | "afterLetter" ;
405394 isLanguageRightToLeft : boolean ;
406395 isDirectionReversed : boolean ;
407396 } ) : { left : number ; top : number ; width : number } {
397+ const letters = options . word ?. qsa ( "letter" ) ;
398+ let letter ;
399+ if ( ! letters ?. length || ! ( letter = letters [ options . letterIndex ] ) ) {
400+ // maybe we should return null here instead of throwing
401+ throw new Error (
402+ "Caret getTargetPositionAndWidth: no letters found in word" ,
403+ ) ;
404+ }
405+
406+ if ( caretDebug ) {
407+ if ( this . id === "paceCaret" ) {
408+ for ( const l of document . querySelectorAll ( ".word letter" ) ) {
409+ l . classList . remove ( "debugCaretTarget" ) ;
410+ l . classList . remove ( "debugCaretTarget2" ) ;
411+ l . classList . add ( "debugCaret" ) ;
412+ }
413+ letter ?. addClass ( "debugCaretTarget" ) ;
414+ this . element . addClass ( "debug" ) ;
415+ }
416+ } else {
417+ this . element . removeClass ( "debug" ) ;
418+ }
419+
408420 // in zen, custom or polyglot mode we need to check per-letter
409421 const checkRtlByLetter =
410422 Config . mode === "zen" ||
411423 Config . mode === "custom" ||
412424 Config . funbox . includes ( "polyglot" ) ;
413425 const [ isWordRTL , isFullMatch ] = isWordRightToLeft (
414- checkRtlByLetter
415- ? ( options . letter . native . textContent ?? "" )
416- : options . wordText ,
426+ checkRtlByLetter ? ( letter . native . textContent ?? "" ) : options . wordText ,
417427 options . isLanguageRightToLeft ,
418428 options . isDirectionReversed ,
419429 ) ;
420430
421431 //if the letter is not visible, use the closest visible letter
422- const isLetterVisible = options . letter . getOffsetWidth ( ) > 0 ;
432+ const isLetterVisible = letter . getOffsetWidth ( ) > 0 ;
423433 if ( ! isLetterVisible ) {
424- const letters = options . word . qsa ( "letter" ) ;
425- if ( letters . length === 0 ) {
426- throw new Error ( "Caret getLeftTopWidth: no letters found in word" ) ;
427- }
428-
429- // ignore letters after the current letter
430- let ignore = true ;
431- for ( let i = letters . length - 1 ; i >= 0 ; i -- ) {
434+ for ( let i = options . letterIndex - 1 ; i >= 0 ; i -- ) {
432435 const loopLetter = letters [ i ] as ElementWithUtils ;
433- if ( loopLetter === options . letter ) {
434- // at the current letter, stop ignoring, continue to the next
435- ignore = false ;
436- continue ;
437- }
438- if ( ignore ) continue ;
439436
440- // found the closest visible letter before the current letter
437+ // find the closest visible letter before the current letter
441438 if ( loopLetter . getOffsetWidth ( ) > 0 ) {
442- options . letter = loopLetter ;
439+ letter = loopLetter ;
443440 break ;
444441 }
445442 }
446443 if ( caretDebug ) {
447- options . letter . addClass ( "debugCaretTarget2" ) ;
444+ letter . addClass ( "debugCaretTarget2" ) ;
448445 }
449446 }
450447
451448 const spaceWidth = getTotalInlineMargin ( options . word . native ) ;
452449 let width = spaceWidth ;
453450 if ( this . isFullWidth ( ) && options . side === "beforeLetter" ) {
454- width = options . letter . getOffsetWidth ( ) ;
451+ width = letter . getOffsetWidth ( ) ;
455452 }
456453
457454 let left = 0 ;
@@ -468,22 +465,22 @@ export class Caret {
468465 if ( this . isFullWidth ( ) ) {
469466 afterLetterCorrection += spaceWidth * - 1 ;
470467 } else {
471- afterLetterCorrection += options . letter . getOffsetWidth ( ) * - 1 ;
468+ afterLetterCorrection += letter . getOffsetWidth ( ) * - 1 ;
472469 }
473470 }
474471 if ( Config . tapeMode === "off" ) {
475472 if ( ! this . isFullWidth ( ) ) {
476- left += options . letter . getOffsetWidth ( ) ;
473+ left += letter . getOffsetWidth ( ) ;
477474 }
478- left += options . letter . getOffsetLeft ( ) ;
475+ left += letter . getOffsetLeft ( ) ;
479476 left += options . word . getOffsetLeft ( ) ;
480477 left += afterLetterCorrection ;
481478 } else if ( Config . tapeMode === "word" ) {
482479 if ( ! this . isFullWidth ( ) ) {
483- left += options . letter . getOffsetWidth ( ) ;
480+ left += letter . getOffsetWidth ( ) ;
484481 }
485482 left += options . word . getOffsetWidth ( ) * - 1 ;
486- left += options . letter . getOffsetLeft ( ) ;
483+ left += letter . getOffsetLeft ( ) ;
487484 left += afterLetterCorrection ;
488485 if ( this . isMainCaret && lockedMainCaretInTape ) {
489486 left += wordsWrapperCache . getOffsetWidth ( ) - tapeOffset ;
@@ -498,7 +495,7 @@ export class Caret {
498495 if ( this . isMainCaret && lockedMainCaretInTape ) {
499496 left += wordsWrapperCache . getOffsetWidth ( ) - tapeOffset ;
500497 } else {
501- left += options . letter . getOffsetLeft ( ) ;
498+ left += letter . getOffsetLeft ( ) ;
502499 left += options . word . getOffsetLeft ( ) ;
503500 left += afterLetterCorrection ;
504501 left += width ;
@@ -507,14 +504,14 @@ export class Caret {
507504 } else {
508505 let afterLetterCorrection = 0 ;
509506 if ( options . side === "afterLetter" ) {
510- afterLetterCorrection += options . letter . getOffsetWidth ( ) ;
507+ afterLetterCorrection += letter . getOffsetWidth ( ) ;
511508 }
512509 if ( Config . tapeMode === "off" ) {
513- left += options . letter . getOffsetLeft ( ) ;
510+ left += letter . getOffsetLeft ( ) ;
514511 left += options . word . getOffsetLeft ( ) ;
515512 left += afterLetterCorrection ;
516513 } else if ( Config . tapeMode === "word" ) {
517- left += options . letter . getOffsetLeft ( ) ;
514+ left += letter . getOffsetLeft ( ) ;
518515 left += afterLetterCorrection ;
519516 if ( this . isMainCaret && lockedMainCaretInTape ) {
520517 left += tapeOffset ;
@@ -525,23 +522,23 @@ export class Caret {
525522 if ( this . isMainCaret && lockedMainCaretInTape ) {
526523 left += tapeOffset ;
527524 } else {
528- left += options . letter . getOffsetLeft ( ) ;
525+ left += letter . getOffsetLeft ( ) ;
529526 left += options . word . getOffsetLeft ( ) ;
530527 left += afterLetterCorrection ;
531528 }
532529 }
533530 }
534531
535532 //top position
536- top += options . letter . getOffsetTop ( ) ;
533+ top += letter . getOffsetTop ( ) ;
537534 top += options . word . getOffsetTop ( ) ;
538535
539536 if ( this . style === "underline" ) {
540537 // if style is underline, add the height of the letter to the top
541- top += options . letter . getOffsetHeight ( ) ;
538+ top += letter . getOffsetHeight ( ) ;
542539 } else {
543540 // else center vertically in the letter
544- top += ( options . letter . getOffsetHeight ( ) - this . getHeight ( ) ) / 2 ;
541+ top += ( letter . getOffsetHeight ( ) - this . getHeight ( ) ) / 2 ;
545542 }
546543
547544 // also center horizontally
0 commit comments