@@ -283,7 +283,7 @@ export function updateActiveElement(
283283 void updateWordsInputPosition ( ) ;
284284 }
285285 if ( ! initial && Config . tapeMode !== "off" ) {
286- scrollTape ( ) ;
286+ void scrollTape ( ) ;
287287 }
288288}
289289
@@ -425,11 +425,10 @@ export function showWords(): void {
425425 }
426426
427427 updateActiveElement ( undefined , true ) ;
428+ updateWordWrapperClasses ( ) ;
428429 setTimeout ( ( ) => {
429430 void Caret . updatePosition ( ) ;
430431 } , 125 ) ;
431-
432- updateWordWrapperClasses ( ) ;
433432}
434433
435434export function appendEmptyWordElement ( ) : void {
@@ -575,7 +574,7 @@ export function updateWordsWrapperHeight(force = false): void {
575574
576575function updateWordsMargin ( ) : void {
577576 if ( Config . tapeMode !== "off" ) {
578- scrollTape ( true ) ;
577+ void scrollTape ( true ) ;
579578 } else {
580579 setTimeout ( ( ) => {
581580 $ ( "#words" ) . css ( "margin-left" , "unset" ) ;
@@ -933,7 +932,7 @@ export async function updateActiveWordLetters(
933932 "<div class='beforeNewline'></div><div class='newline'></div><div class='afterNewline'></div>"
934933 ) ;
935934 if ( Config . tapeMode !== "off" ) {
936- scrollTape ( ) ;
935+ await scrollTape ( ) ;
937936 }
938937}
939938
@@ -959,15 +958,18 @@ function getNlCharWidth(
959958}
960959
961960let allowWordRemoval = true ;
962- export function scrollTape ( noRemove = false ) : void {
961+ export async function scrollTape ( noRemove = false ) : Promise < void > {
963962 if ( ActivePage . get ( ) !== "test" || resultVisible ) return ;
964963
965964 const waitForLineJumpAnimation = lineTransition && ! allowWordRemoval ;
966965 if ( waitForLineJumpAnimation ) {
967- setTimeout ( ( ) => scrollTape ( true ) , 50 ) ;
966+ setTimeout ( ( ) => void scrollTape ( true ) , 50 ) ;
968967 return ;
969968 }
970969
970+ const currentLang = await JSONData . getCurrentLanguage ( Config . language ) ;
971+ const isLanguageRTL = currentLang . rightToLeft ;
972+
971973 // index of the active word in the collection of .word elements
972974 const wordElementIndex = TestState . activeWordIndex - activeWordElementOffset ;
973975 const wordsWrapperWidth = (
@@ -1045,7 +1047,11 @@ export function scrollTape(noRemove = false): void {
10451047 const wordOuterWidth = $ ( child ) . outerWidth ( true ) ?? 0 ;
10461048 const forWordLeft = Math . floor ( child . offsetLeft ) ;
10471049 const forWordWidth = Math . floor ( child . offsetWidth ) ;
1048- if ( ! noRemove && forWordLeft < 0 - forWordWidth ) {
1050+ if (
1051+ ! noRemove &&
1052+ ( ( ! isLanguageRTL && forWordLeft < 0 - forWordWidth ) ||
1053+ ( isLanguageRTL && forWordLeft > wordsWrapperWidth ) )
1054+ ) {
10491055 toRemove . push ( child ) ;
10501056 widthRemoved += wordOuterWidth ;
10511057 wordsToRemoveCount ++ ;
@@ -1085,30 +1091,43 @@ export function scrollTape(noRemove = false): void {
10851091 parseFloat ( afterNewlineEl . style . marginLeft ) || 0 ;
10861092 afterNewlineEl . style . marginLeft = `${ currentLineIndent - width } px` ;
10871093 } ) ;
1094+ if ( isLanguageRTL ) widthRemoved *= - 1 ;
10881095 const currentWordsMargin = parseFloat ( wordsEl . style . marginLeft ) || 0 ;
10891096 wordsEl . style . marginLeft = `${ currentWordsMargin + widthRemoved } px` ;
10901097 }
10911098
10921099 /* calculate current word width to add to #words margin */
10931100 let currentWordWidth = 0 ;
1094- if ( Config . tapeMode === "letter" ) {
1095- if ( TestInput . input . current . length > 0 ) {
1096- const letters = activeWordEl . querySelectorAll ( "letter" ) ;
1097- for ( let i = 0 ; i < TestInput . input . current . length ; i ++ ) {
1098- const letter = letters [ i ] as HTMLElement ;
1099- if (
1100- ( Config . blindMode || Config . hideExtraLetters ) &&
1101- letter . classList . contains ( "extra" )
1102- ) {
1103- continue ;
1104- }
1105- currentWordWidth += $ ( letter ) . outerWidth ( true ) ?? 0 ;
1101+ const inputLength = TestInput . input . current . length ;
1102+ if ( Config . tapeMode === "letter" && inputLength > 0 ) {
1103+ const letters = activeWordEl . querySelectorAll ( "letter" ) ;
1104+ let lastPositiveLetterWidth = 0 ;
1105+ for ( let i = 0 ; i < inputLength ; i ++ ) {
1106+ const letter = letters [ i ] as HTMLElement ;
1107+ if (
1108+ ( Config . blindMode || Config . hideExtraLetters ) &&
1109+ letter . classList . contains ( "extra" )
1110+ ) {
1111+ continue ;
11061112 }
1113+ const letterOuterWidth = $ ( letter ) . outerWidth ( true ) ?? 0 ;
1114+ currentWordWidth += letterOuterWidth ;
1115+ if ( letterOuterWidth > 0 ) lastPositiveLetterWidth = letterOuterWidth ;
11071116 }
1117+ // if current letter has zero width move the tape to previous positive width letter
1118+ if ( $ ( letters [ inputLength ] as Element ) . outerWidth ( true ) === 0 )
1119+ currentWordWidth -= lastPositiveLetterWidth ;
11081120 }
1121+
11091122 /* change to new #words & .afterNewline margins */
1110- const tapeMargin = wordsWrapperWidth * ( Config . tapeMargin / 100 ) ;
1111- const newMargin = tapeMargin - ( wordsWidthBeforeActive + currentWordWidth ) ;
1123+ let newMargin = wordsWrapperWidth * ( Config . tapeMargin / 100 ) ;
1124+ if ( isLanguageRTL )
1125+ newMargin +=
1126+ wordsWidthBeforeActive +
1127+ currentWordWidth -
1128+ wordsEl . offsetWidth +
1129+ wordRightMargin ;
1130+ else newMargin -= wordsWidthBeforeActive + currentWordWidth ;
11121131
11131132 const jqWords = $ ( wordsEl ) ;
11141133 if ( Config . smoothLineScroll ) {
@@ -1120,7 +1139,7 @@ export function scrollTape(noRemove = false): void {
11201139 duration : SlowTimer . get ( ) ? 0 : 125 ,
11211140 queue : "leftMargin" ,
11221141 complete : ( ) => {
1123- if ( noRemove ) scrollTape ( ) ;
1142+ if ( noRemove ) void scrollTape ( ) ;
11241143 } ,
11251144 }
11261145 ) ;
@@ -1140,7 +1159,7 @@ export function scrollTape(noRemove = false): void {
11401159 afterNewlinesNewMargins . forEach ( ( margin , index ) => {
11411160 ( afterNewLineEls [ index ] as HTMLElement ) . style . marginLeft = `${ margin } px` ;
11421161 } ) ;
1143- if ( noRemove ) scrollTape ( ) ;
1162+ if ( noRemove ) void scrollTape ( ) ;
11441163 }
11451164}
11461165
0 commit comments