@@ -139,7 +139,7 @@ const debouncedZipfCheck = debounce(250, async () => {
139
139
}
140
140
} ) ;
141
141
142
- ConfigEvent . subscribe ( ( eventKey , eventValue , nosave ) => {
142
+ ConfigEvent . subscribe ( async ( eventKey , eventValue , nosave ) => {
143
143
if (
144
144
( eventKey === "language" || eventKey === "funbox" ) &&
145
145
Config . funbox . split ( "#" ) . includes ( "zipf" )
@@ -149,7 +149,7 @@ ConfigEvent.subscribe((eventKey, eventValue, nosave) => {
149
149
if ( eventKey === "fontSize" && ! nosave ) {
150
150
OutOfFocus . hide ( ) ;
151
151
updateWordsWrapperHeight ( true ) ;
152
- updateWordsMargin ( ) ;
152
+ await updateWordsMargin ( ) ;
153
153
void updateWordsInputPosition ( true ) ;
154
154
}
155
155
if (
@@ -166,7 +166,7 @@ ConfigEvent.subscribe((eventKey, eventValue, nosave) => {
166
166
167
167
if ( eventValue === undefined ) return ;
168
168
if ( eventKey === "highlightMode" ) {
169
- if ( ActivePage . get ( ) === "test" ) updateActiveElement ( ) ;
169
+ if ( ActivePage . get ( ) === "test" ) await updateActiveElement ( ) ;
170
170
}
171
171
172
172
if (
@@ -178,7 +178,7 @@ ConfigEvent.subscribe((eventKey, eventValue, nosave) => {
178
178
"hideExtraLetters" ,
179
179
] . includes ( eventKey )
180
180
) {
181
- updateWordWrapperClasses ( ) ;
181
+ await updateWordWrapperClasses ( ) ;
182
182
}
183
183
184
184
if ( eventKey === "showAllLines" ) {
@@ -246,10 +246,10 @@ export function blurWords(): void {
246
246
$ ( "#wordsInput" ) . trigger ( "blur" ) ;
247
247
}
248
248
249
- export function updateActiveElement (
249
+ export async function updateActiveElement (
250
250
backspace ?: boolean ,
251
251
initial = false
252
- ) : void {
252
+ ) : Promise < void > {
253
253
const active = document . querySelector ( "#words .active" ) ;
254
254
if ( ! backspace ) {
255
255
active ?. classList . add ( "typed" ) ;
@@ -277,7 +277,7 @@ export function updateActiveElement(
277
277
void updateWordsInputPosition ( ) ;
278
278
}
279
279
if ( ! initial && Config . tapeMode !== "off" ) {
280
- scrollTape ( ) ;
280
+ await scrollTape ( ) ;
281
281
}
282
282
}
283
283
@@ -354,7 +354,7 @@ function getWordHTML(word: string): string {
354
354
return retval ;
355
355
}
356
356
357
- function updateWordWrapperClasses ( ) : void {
357
+ async function updateWordWrapperClasses ( ) : Promise < void > {
358
358
if ( Config . tapeMode !== "off" ) {
359
359
$ ( "#words" ) . addClass ( "tape" ) ;
360
360
$ ( "#wordsWrapper" ) . addClass ( "tape" ) ;
@@ -400,13 +400,13 @@ function updateWordWrapperClasses(): void {
400
400
401
401
updateWordsWidth ( ) ;
402
402
updateWordsWrapperHeight ( true ) ;
403
- updateWordsMargin ( ) ;
403
+ await updateWordsMargin ( ) ;
404
404
setTimeout ( ( ) => {
405
405
void updateWordsInputPosition ( true ) ;
406
406
} , 250 ) ;
407
407
}
408
408
409
- export function showWords ( ) : void {
409
+ export async function showWords ( ) : Promise < void > {
410
410
$ ( "#words" ) . empty ( ) ;
411
411
412
412
let wordsHTML = "" ;
@@ -420,12 +420,12 @@ export function showWords(): void {
420
420
421
421
$ ( "#words" ) . html ( wordsHTML ) ;
422
422
423
- updateActiveElement ( undefined , true ) ;
423
+ await updateActiveElement ( undefined , true ) ;
424
424
setTimeout ( ( ) => {
425
425
void Caret . updatePosition ( ) ;
426
426
} , 125 ) ;
427
427
428
- updateWordWrapperClasses ( ) ;
428
+ await updateWordWrapperClasses ( ) ;
429
429
}
430
430
431
431
const posUpdateLangList = [ "japanese" , "chinese" , "korean" ] ;
@@ -573,9 +573,9 @@ 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 ( ) ;
578
+ await scrollTape ( ) ;
579
579
} else {
580
580
setTimeout ( ( ) => {
581
581
$ ( "#words" ) . css ( "margin-left" , "unset" ) ;
@@ -929,17 +929,20 @@ export async function updateActiveWordLetters(
929
929
"<div class='beforeNewline'></div><div class='newline'></div><div class='afterNewline'></div>"
930
930
) ;
931
931
if ( Config . tapeMode !== "off" ) {
932
- scrollTape ( ) ;
932
+ await scrollTape ( ) ;
933
933
}
934
934
}
935
935
936
936
let allowWordRemoval = true ;
937
- export function scrollTape ( noRemove = false ) : void {
937
+ export async function scrollTape ( noRemove = false ) : Promise < void > {
938
938
if ( ActivePage . get ( ) !== "test" || resultVisible ) return ;
939
939
940
940
const waitForLineJumpAnimation = lineTransition && ! allowWordRemoval ;
941
941
if ( waitForLineJumpAnimation ) noRemove = true ;
942
942
943
+ const currentLang = await JSONData . getCurrentLanguage ( Config . language ) ;
944
+ const isLanguageRTL = currentLang . rightToLeft ;
945
+
943
946
const wordsWrapperWidth = (
944
947
document . querySelector ( "#wordsWrapper" ) as HTMLElement
945
948
) . offsetWidth ;
@@ -1009,7 +1012,11 @@ export function scrollTape(noRemove = false): void {
1009
1012
const wordOuterWidth = $ ( child ) . outerWidth ( true ) ?? 0 ;
1010
1013
const forWordLeft = Math . floor ( child . offsetLeft ) ;
1011
1014
const forWordWidth = Math . floor ( child . offsetWidth ) ;
1012
- if ( ! noRemove && forWordLeft < 0 - forWordWidth ) {
1015
+ if (
1016
+ ! noRemove &&
1017
+ ( ( ! isLanguageRTL && forWordLeft < 0 - forWordWidth ) ||
1018
+ ( isLanguageRTL && forWordLeft > wordsWrapperWidth ) )
1019
+ ) {
1013
1020
toRemove . push ( child ) ;
1014
1021
widthToRemove += wordOuterWidth ;
1015
1022
wordsToRemoveCount ++ ;
@@ -1037,30 +1044,44 @@ export function scrollTape(noRemove = false): void {
1037
1044
const currentChildMargin = parseInt ( element . style . marginLeft ) || 0 ;
1038
1045
element . style . marginLeft = `${ currentChildMargin - widthToRemove } px` ;
1039
1046
}
1047
+ if ( isLanguageRTL ) widthToRemove *= - 1 ;
1040
1048
const currentWordsMargin = parseInt ( wordsEl . style . marginLeft ) || 0 ;
1041
1049
wordsEl . style . marginLeft = `${ currentWordsMargin + widthToRemove } px` ;
1042
1050
}
1043
1051
1052
+ const inputLength = TestInput . input . current . length ;
1044
1053
let currentWordWidth = 0 ;
1045
- if ( Config . tapeMode === "letter" ) {
1046
- if ( TestInput . input . current . length > 0 ) {
1047
- const letters = activeWordEl . querySelectorAll ( "letter" ) ;
1048
- for ( let i = 0 ; i < TestInput . input . current . length ; i ++ ) {
1049
- const letter = letters [ i ] as HTMLElement ;
1050
- if (
1051
- ( Config . blindMode || Config . hideExtraLetters ) &&
1052
- letter . classList . contains ( "extra" )
1053
- ) {
1054
- continue ;
1055
- }
1056
- currentWordWidth += $ ( letter ) . outerWidth ( true ) ?? 0 ;
1054
+ if ( Config . tapeMode === "letter" && inputLength > 0 ) {
1055
+ const letters = activeWordEl . querySelectorAll ( "letter" ) ;
1056
+ let lastPositiveLetterWidth = 0 ;
1057
+ for ( let i = 0 ; i < inputLength ; i ++ ) {
1058
+ const letter = letters [ i ] as HTMLElement ;
1059
+ if (
1060
+ ( Config . blindMode || Config . hideExtraLetters ) &&
1061
+ letter . classList . contains ( "extra" )
1062
+ ) {
1063
+ continue ;
1057
1064
}
1065
+ const letterOuterWidth = $ ( letter ) . outerWidth ( true ) ?? 0 ;
1066
+ currentWordWidth += letterOuterWidth ;
1067
+ if ( letterOuterWidth > 0 ) lastPositiveLetterWidth = letterOuterWidth ;
1058
1068
}
1059
- }
1060
- let newMargin =
1061
- wordsWrapperWidth / 2 - ( wordsWidthBeforeActive + currentWordWidth ) ;
1069
+ // if current letter has zero width move the tape to previous positive width letter
1070
+ if ( $ ( letters [ inputLength ] as Element ) . outerWidth ( true ) === 0 )
1071
+ currentWordWidth -= lastPositiveLetterWidth ;
1072
+ }
1073
+
1074
+ let newMargin = wordsWrapperWidth / 2 ;
1075
+ if ( isLanguageRTL )
1076
+ newMargin +=
1077
+ wordsWidthBeforeActive +
1078
+ currentWordWidth -
1079
+ wordsEl . offsetWidth +
1080
+ wordRightMargin ;
1081
+ else newMargin -= wordsWidthBeforeActive + currentWordWidth ;
1062
1082
if ( waitForLineJumpAnimation )
1063
1083
newMargin = parseInt ( wordsEl . style . marginLeft ) || 0 ;
1084
+
1064
1085
const jqWords = $ ( wordsEl ) ;
1065
1086
if ( Config . smoothLineScroll ) {
1066
1087
jqWords . stop ( "leftMargin" , true , false ) . animate (
@@ -1070,10 +1091,10 @@ export function scrollTape(noRemove = false): void {
1070
1091
{
1071
1092
duration : SlowTimer . get ( ) ? 0 : 125 ,
1072
1093
queue : "leftMargin" ,
1073
- complete : ( ) => {
1094
+ complete : async ( ) => {
1074
1095
if ( noRemove ) {
1075
- if ( waitForLineJumpAnimation ) scrollTape ( true ) ;
1076
- else scrollTape ( ) ;
1096
+ if ( waitForLineJumpAnimation ) await scrollTape ( true ) ;
1097
+ else await scrollTape ( ) ;
1077
1098
}
1078
1099
} ,
1079
1100
}
@@ -1094,7 +1115,7 @@ export function scrollTape(noRemove = false): void {
1094
1115
linesWidths . forEach ( ( width , index ) => {
1095
1116
( afterNewLineEls [ index ] as HTMLElement ) . style . marginLeft = `${ width } px` ;
1096
1117
} ) ;
1097
- if ( noRemove ) scrollTape ( ) ;
1118
+ if ( noRemove ) await scrollTape ( ) ;
1098
1119
}
1099
1120
}
1100
1121
0 commit comments