@@ -142,7 +142,7 @@ const debouncedZipfCheck = debounce(250, async () => {
142
142
}
143
143
} ) ;
144
144
145
- ConfigEvent . subscribe ( ( eventKey , eventValue , nosave ) => {
145
+ ConfigEvent . subscribe ( async ( eventKey , eventValue , nosave ) => {
146
146
if (
147
147
( eventKey === "language" || eventKey === "funbox" ) &&
148
148
Config . funbox . split ( "#" ) . includes ( "zipf" )
@@ -151,7 +151,7 @@ ConfigEvent.subscribe((eventKey, eventValue, nosave) => {
151
151
}
152
152
if ( eventKey === "fontSize" && ! nosave ) {
153
153
OutOfFocus . hide ( ) ;
154
- updateWordWrapperClasses ( ) ;
154
+ await updateWordWrapperClasses ( ) ;
155
155
}
156
156
if (
157
157
[ "fontSize" , "fontFamily" , "blindMode" , "hideExtraLetters" ] . includes (
@@ -167,7 +167,7 @@ ConfigEvent.subscribe((eventKey, eventValue, nosave) => {
167
167
168
168
if ( eventValue === undefined ) return ;
169
169
if ( eventKey === "highlightMode" ) {
170
- if ( ActivePage . get ( ) === "test" ) updateActiveElement ( ) ;
170
+ if ( ActivePage . get ( ) === "test" ) await updateActiveElement ( ) ;
171
171
}
172
172
173
173
if (
@@ -179,7 +179,7 @@ ConfigEvent.subscribe((eventKey, eventValue, nosave) => {
179
179
"hideExtraLetters" ,
180
180
] . includes ( eventKey )
181
181
) {
182
- updateWordWrapperClasses ( ) ;
182
+ await updateWordWrapperClasses ( ) ;
183
183
}
184
184
185
185
if ( [ "tapeMode" , "tapeMargin" ] . includes ( eventKey ) ) {
@@ -251,10 +251,10 @@ export function blurWords(): void {
251
251
$ ( "#wordsInput" ) . trigger ( "blur" ) ;
252
252
}
253
253
254
- export function updateActiveElement (
254
+ export async function updateActiveElement (
255
255
backspace ?: boolean ,
256
256
initial = false
257
- ) : void {
257
+ ) : Promise < void > {
258
258
const active = document . querySelector ( "#words .active" ) ;
259
259
if ( ! backspace ) {
260
260
active ?. classList . add ( "typed" ) ;
@@ -282,7 +282,7 @@ export function updateActiveElement(
282
282
void updateWordsInputPosition ( ) ;
283
283
}
284
284
if ( ! initial && Config . tapeMode !== "off" ) {
285
- scrollTape ( ) ;
285
+ await scrollTape ( ) ;
286
286
}
287
287
}
288
288
@@ -358,7 +358,7 @@ function getWordHTML(word: string): string {
358
358
return retval ;
359
359
}
360
360
361
- function updateWordWrapperClasses ( ) : void {
361
+ async function updateWordWrapperClasses ( ) : Promise < void > {
362
362
if ( Config . tapeMode !== "off" ) {
363
363
$ ( "#words" ) . addClass ( "tape" ) ;
364
364
$ ( "#wordsWrapper" ) . addClass ( "tape" ) ;
@@ -404,13 +404,13 @@ function updateWordWrapperClasses(): void {
404
404
405
405
updateWordsWidth ( ) ;
406
406
updateWordsWrapperHeight ( true ) ;
407
- updateWordsMargin ( ) ;
407
+ await updateWordsMargin ( ) ;
408
408
setTimeout ( ( ) => {
409
409
void updateWordsInputPosition ( true ) ;
410
410
} , 250 ) ;
411
411
}
412
412
413
- export function showWords ( ) : void {
413
+ export async function showWords ( ) : Promise < void > {
414
414
$ ( "#words" ) . empty ( ) ;
415
415
416
416
let wordsHTML = "" ;
@@ -424,12 +424,12 @@ export function showWords(): void {
424
424
425
425
$ ( "#words" ) . html ( wordsHTML ) ;
426
426
427
- updateActiveElement ( undefined , true ) ;
427
+ await updateActiveElement ( undefined , true ) ;
428
428
setTimeout ( ( ) => {
429
429
void Caret . updatePosition ( ) ;
430
430
} , 125 ) ;
431
431
432
- updateWordWrapperClasses ( ) ;
432
+ await updateWordWrapperClasses ( ) ;
433
433
}
434
434
435
435
const posUpdateLangList = [ "japanese" , "chinese" , "korean" ] ;
@@ -573,9 +573,9 @@ export function updateWordsWrapperHeight(force = false): void {
573
573
}
574
574
}
575
575
576
- function updateWordsMargin ( ) : void {
576
+ async function updateWordsMargin ( ) : Promise < void > {
577
577
if ( Config . tapeMode !== "off" ) {
578
- scrollTape ( true ) ;
578
+ await scrollTape ( true ) ;
579
579
} else {
580
580
setTimeout ( ( ) => $ ( "#words" ) . css ( "margin-left" , "unset" ) , 0 ) ;
581
581
}
@@ -926,17 +926,20 @@ export async function updateActiveWordLetters(
926
926
"<div class='beforeNewline'></div><div class='newline'></div><div class='afterNewline'></div>"
927
927
) ;
928
928
if ( Config . tapeMode !== "off" ) {
929
- scrollTape ( ) ;
929
+ await scrollTape ( ) ;
930
930
}
931
931
}
932
932
933
933
let allowWordRemoval = true ;
934
- export function scrollTape ( noRemove = false ) : void {
934
+ export async function scrollTape ( noRemove = false ) : Promise < void > {
935
935
if ( ActivePage . get ( ) !== "test" || resultVisible ) return ;
936
936
937
937
const waitForLineJumpAnimation = lineTransition && ! allowWordRemoval ;
938
938
if ( waitForLineJumpAnimation ) noRemove = true ;
939
939
940
+ const currentLang = await JSONData . getCurrentLanguage ( Config . language ) ;
941
+ const isLanguageRTL = currentLang . rightToLeft ;
942
+
940
943
const wordsWrapperWidth = (
941
944
document . querySelector ( "#wordsWrapper" ) as HTMLElement
942
945
) . offsetWidth ;
@@ -1006,7 +1009,11 @@ export function scrollTape(noRemove = false): void {
1006
1009
const wordOuterWidth = $ ( child ) . outerWidth ( true ) ?? 0 ;
1007
1010
const forWordLeft = Math . floor ( child . offsetLeft ) ;
1008
1011
const forWordWidth = Math . floor ( child . offsetWidth ) ;
1009
- if ( ! noRemove && forWordLeft < 0 - forWordWidth ) {
1012
+ if (
1013
+ ! noRemove &&
1014
+ ( ( ! isLanguageRTL && forWordLeft < 0 - forWordWidth ) ||
1015
+ ( isLanguageRTL && forWordLeft > wordsWrapperWidth ) )
1016
+ ) {
1010
1017
toRemove . push ( child ) ;
1011
1018
widthToRemove += wordOuterWidth ;
1012
1019
wordsToRemoveCount ++ ;
@@ -1034,31 +1041,44 @@ export function scrollTape(noRemove = false): void {
1034
1041
const currentChildMargin = parseInt ( element . style . marginLeft ) || 0 ;
1035
1042
element . style . marginLeft = `${ currentChildMargin - widthToRemove } px` ;
1036
1043
}
1044
+ if ( isLanguageRTL ) widthToRemove *= - 1 ;
1037
1045
const currentWordsMargin = parseInt ( wordsEl . style . marginLeft ) || 0 ;
1038
1046
wordsEl . style . marginLeft = `${ currentWordsMargin + widthToRemove } px` ;
1039
1047
}
1040
1048
1049
+ const inputLength = TestInput . input . current . length ;
1041
1050
let currentWordWidth = 0 ;
1042
- if ( Config . tapeMode === "letter" ) {
1043
- if ( TestInput . input . current . length > 0 ) {
1044
- const letters = activeWordEl . querySelectorAll ( "letter" ) ;
1045
- for ( let i = 0 ; i < TestInput . input . current . length ; i ++ ) {
1046
- const letter = letters [ i ] as HTMLElement ;
1047
- if (
1048
- ( Config . blindMode || Config . hideExtraLetters ) &&
1049
- letter . classList . contains ( "extra" )
1050
- ) {
1051
- continue ;
1052
- }
1053
- currentWordWidth += $ ( letter ) . outerWidth ( true ) ?? 0 ;
1051
+ if ( Config . tapeMode === "letter" && inputLength > 0 ) {
1052
+ const letters = activeWordEl . querySelectorAll ( "letter" ) ;
1053
+ let lastPositiveLetterWidth = 0 ;
1054
+ for ( let i = 0 ; i < inputLength ; i ++ ) {
1055
+ const letter = letters [ i ] as HTMLElement ;
1056
+ if (
1057
+ ( Config . blindMode || Config . hideExtraLetters ) &&
1058
+ letter . classList . contains ( "extra" )
1059
+ ) {
1060
+ continue ;
1054
1061
}
1062
+ const letterOuterWidth = $ ( letter ) . outerWidth ( true ) ?? 0 ;
1063
+ currentWordWidth += letterOuterWidth ;
1064
+ if ( letterOuterWidth > 0 ) lastPositiveLetterWidth = letterOuterWidth ;
1055
1065
}
1056
- }
1057
-
1058
- const tapeMargin = wordsWrapperWidth * ( Config . tapeMargin / 100 ) ;
1059
- let newMargin = tapeMargin - ( wordsWidthBeforeActive + currentWordWidth ) ;
1066
+ // if current letter has zero width move the tape to previous positive width letter
1067
+ if ( $ ( letters [ inputLength ] as Element ) . outerWidth ( true ) === 0 )
1068
+ currentWordWidth -= lastPositiveLetterWidth ;
1069
+ }
1070
+
1071
+ let newMargin = wordsWrapperWidth * ( Config . tapeMargin / 100 ) ;
1072
+ if ( isLanguageRTL )
1073
+ newMargin +=
1074
+ wordsWidthBeforeActive +
1075
+ currentWordWidth -
1076
+ wordsEl . offsetWidth +
1077
+ wordRightMargin ;
1078
+ else newMargin -= wordsWidthBeforeActive + currentWordWidth ;
1060
1079
if ( waitForLineJumpAnimation )
1061
1080
newMargin = parseInt ( wordsEl . style . marginLeft ) || 0 ;
1081
+
1062
1082
const jqWords = $ ( wordsEl ) ;
1063
1083
if ( Config . smoothLineScroll ) {
1064
1084
jqWords . stop ( "leftMargin" , true , false ) . animate (
@@ -1068,10 +1088,10 @@ export function scrollTape(noRemove = false): void {
1068
1088
{
1069
1089
duration : SlowTimer . get ( ) ? 0 : 125 ,
1070
1090
queue : "leftMargin" ,
1071
- complete : ( ) => {
1091
+ complete : async ( ) => {
1072
1092
if ( noRemove ) {
1073
- if ( waitForLineJumpAnimation ) scrollTape ( true ) ;
1074
- else scrollTape ( ) ;
1093
+ if ( waitForLineJumpAnimation ) await scrollTape ( true ) ;
1094
+ else await scrollTape ( ) ;
1075
1095
}
1076
1096
} ,
1077
1097
}
@@ -1092,7 +1112,7 @@ export function scrollTape(noRemove = false): void {
1092
1112
linesWidths . forEach ( ( width , index ) => {
1093
1113
( afterNewLineEls [ index ] as HTMLElement ) . style . marginLeft = `${ width } px` ;
1094
1114
} ) ;
1095
- if ( noRemove ) scrollTape ( ) ;
1115
+ if ( noRemove ) await scrollTape ( ) ;
1096
1116
}
1097
1117
}
1098
1118
0 commit comments