@@ -425,11 +425,10 @@ export function showWords(): void {
425
425
}
426
426
427
427
updateActiveElement ( undefined , true ) ;
428
+ updateWordWrapperClasses ( ) ;
428
429
setTimeout ( ( ) => {
429
430
void Caret . updatePosition ( ) ;
430
431
} , 125 ) ;
431
-
432
- updateWordWrapperClasses ( ) ;
433
432
}
434
433
435
434
export function appendEmptyWordElement ( ) : void {
@@ -596,10 +595,34 @@ function updateWordsMargin(): void {
596
595
if ( Config . tapeMode !== "off" ) {
597
596
void scrollTape ( ) ;
598
597
} else {
599
- setTimeout ( ( ) => {
600
- $ ( "#words" ) . css ( "margin-left" , "unset" ) ;
601
- $ ( "#words .afterNewline" ) . css ( "margin-left" , "unset" ) ;
602
- } , 0 ) ;
598
+ const wordsEl = document . getElementById ( "words" ) as HTMLElement ;
599
+ const afterNewlineEls =
600
+ wordsEl . querySelectorAll < HTMLElement > ( ".afterNewline" ) ;
601
+ const jqWords = $ ( wordsEl ) ;
602
+ if ( Config . smoothLineScroll ) {
603
+ jqWords . stop ( "leftMargin" , true , false ) . animate (
604
+ {
605
+ marginLeft : 0 ,
606
+ } ,
607
+ {
608
+ duration : SlowTimer . get ( ) ? 0 : 125 ,
609
+ queue : "leftMargin" ,
610
+ }
611
+ ) ;
612
+ jqWords . dequeue ( "leftMargin" ) ;
613
+ //for (const afterNewline of wordsEl.querySelectorAll(".afterNewline")) {
614
+ $ ( afterNewlineEls )
615
+ . stop ( true , false )
616
+ . animate ( { marginLeft : 0 } , SlowTimer . get ( ) ? 0 : 125 ) ;
617
+ //}
618
+ } else {
619
+ setTimeout ( ( ) => {
620
+ wordsEl . style . marginLeft = `0` ;
621
+ for ( const afterNewline of afterNewlineEls ) {
622
+ afterNewline . style . marginLeft = `0` ;
623
+ }
624
+ } , 0 ) ;
625
+ }
603
626
}
604
627
}
605
628
@@ -985,6 +1008,9 @@ export async function scrollTape(): Promise<void> {
985
1008
986
1009
await centeringActiveLine ;
987
1010
1011
+ const currentLang = await JSONData . getCurrentLanguage ( Config . language ) ;
1012
+ const isLanguageRTL = currentLang . rightToLeft ;
1013
+
988
1014
// index of the active word in the collection of .word elements
989
1015
const wordElementIndex = TestState . activeWordIndex - activeWordElementOffset ;
990
1016
const wordsWrapperWidth = (
@@ -1062,7 +1088,10 @@ export async function scrollTape(): Promise<void> {
1062
1088
const wordOuterWidth = $ ( child ) . outerWidth ( true ) ?? 0 ;
1063
1089
const forWordLeft = Math . floor ( child . offsetLeft ) ;
1064
1090
const forWordWidth = Math . floor ( child . offsetWidth ) ;
1065
- if ( forWordLeft < 0 - forWordWidth ) {
1091
+ if (
1092
+ ( ! isLanguageRTL && forWordLeft < 0 - forWordWidth ) ||
1093
+ ( isLanguageRTL && forWordLeft > wordsWrapperWidth )
1094
+ ) {
1066
1095
toRemove . push ( child ) ;
1067
1096
widthRemoved += wordOuterWidth ;
1068
1097
wordsToRemoveCount ++ ;
@@ -1076,15 +1105,20 @@ export async function scrollTape(): Promise<void> {
1076
1105
fullLineWidths -= nlCharWidth + wordRightMargin ;
1077
1106
if ( i < activeWordIndex ) wordsWidthBeforeActive = fullLineWidths ;
1078
1107
1079
- if ( fullLineWidths < wordsEl . offsetWidth ) {
1108
+ /** words that are wider than limit can cause a barely visible bottom line shifting,
1109
+ * increase limit if that ever happens, but keep the limit because browsers hate
1110
+ * ridiculously wide margins which may cause the words to not be displayed
1111
+ */
1112
+ const limit = 3 * wordsEl . offsetWidth ;
1113
+ if ( fullLineWidths < limit ) {
1080
1114
afterNewlinesNewMargins . push ( fullLineWidths ) ;
1081
1115
widthRemovedFromLine . push ( widthRemoved ) ;
1082
1116
} else {
1083
- afterNewlinesNewMargins . push ( wordsEl . offsetWidth ) ;
1117
+ afterNewlinesNewMargins . push ( limit ) ;
1084
1118
widthRemovedFromLine . push ( widthRemoved ) ;
1085
1119
if ( i < lastElementIndex ) {
1086
1120
// for the second .afterNewline after active word
1087
- afterNewlinesNewMargins . push ( wordsEl . offsetWidth ) ;
1121
+ afterNewlinesNewMargins . push ( limit ) ;
1088
1122
widthRemovedFromLine . push ( widthRemoved ) ;
1089
1123
}
1090
1124
break ;
@@ -1104,30 +1138,40 @@ export async function scrollTape(): Promise<void> {
1104
1138
currentLineIndent - ( widthRemovedFromLine [ i ] ?? 0 )
1105
1139
} px`;
1106
1140
}
1141
+ if ( isLanguageRTL ) widthRemoved *= - 1 ;
1107
1142
const currentWordsMargin = parseFloat ( wordsEl . style . marginLeft ) || 0 ;
1108
1143
wordsEl . style . marginLeft = `${ currentWordsMargin + widthRemoved } px` ;
1109
1144
}
1110
1145
1111
1146
/* calculate current word width to add to #words margin */
1112
1147
let currentWordWidth = 0 ;
1113
- if ( Config . tapeMode === "letter" ) {
1114
- if ( TestInput . input . current . length > 0 ) {
1115
- const letters = activeWordEl . querySelectorAll ( "letter" ) ;
1116
- for ( let i = 0 ; i < TestInput . input . current . length ; i ++ ) {
1117
- const letter = letters [ i ] as HTMLElement ;
1118
- if (
1119
- ( Config . blindMode || Config . hideExtraLetters ) &&
1120
- letter . classList . contains ( "extra" )
1121
- ) {
1122
- continue ;
1123
- }
1124
- currentWordWidth += $ ( letter ) . outerWidth ( true ) ?? 0 ;
1148
+ const inputLength = TestInput . input . current . length ;
1149
+ if ( Config . tapeMode === "letter" && inputLength > 0 ) {
1150
+ const letters = activeWordEl . querySelectorAll ( "letter" ) ;
1151
+ let lastPositiveLetterWidth = 0 ;
1152
+ for ( let i = 0 ; i < inputLength ; i ++ ) {
1153
+ const letter = letters [ i ] as HTMLElement ;
1154
+ if (
1155
+ ( Config . blindMode || Config . hideExtraLetters ) &&
1156
+ letter . classList . contains ( "extra" )
1157
+ ) {
1158
+ continue ;
1125
1159
}
1160
+ const letterOuterWidth = $ ( letter ) . outerWidth ( true ) ?? 0 ;
1161
+ currentWordWidth += letterOuterWidth ;
1162
+ if ( letterOuterWidth > 0 ) lastPositiveLetterWidth = letterOuterWidth ;
1126
1163
}
1164
+ // if current letter has zero width move the tape to previous positive width letter
1165
+ if ( $ ( letters [ inputLength ] as Element ) . outerWidth ( true ) === 0 )
1166
+ currentWordWidth -= lastPositiveLetterWidth ;
1127
1167
}
1168
+
1128
1169
/* change to new #words & .afterNewline margins */
1129
- const tapeMargin = wordsWrapperWidth * ( Config . tapeMargin / 100 ) ;
1130
- const newMargin = tapeMargin - ( wordsWidthBeforeActive + currentWordWidth ) ;
1170
+ let newMargin =
1171
+ wordsWrapperWidth * ( Config . tapeMargin / 100 ) -
1172
+ wordsWidthBeforeActive -
1173
+ currentWordWidth ;
1174
+ if ( isLanguageRTL ) newMargin = wordRightMargin - newMargin ;
1131
1175
1132
1176
const jqWords = $ ( wordsEl ) ;
1133
1177
if ( Config . smoothLineScroll ) {
0 commit comments