@@ -581,7 +581,7 @@ function updateWordsMargin(): void {
581
581
} else {
582
582
setTimeout ( ( ) => {
583
583
$ ( "#words" ) . css ( "margin-left" , "unset" ) ;
584
- $ ( "#words .afterNewline" ) . css ( "width " , "" ) ;
584
+ $ ( "#words .afterNewline" ) . css ( "margin-left " , "unset " ) ;
585
585
} , 0 ) ;
586
586
}
587
587
}
@@ -938,13 +938,43 @@ export async function updateActiveWordLetters(
938
938
}
939
939
}
940
940
941
+ function getNlCharWidthFromPreviousWord (
942
+ element : Element | HTMLElement ,
943
+ checkIfIncorrect : boolean
944
+ ) : number {
945
+ let lastWordBeforeNewline : Element | null = element ;
946
+ while ( lastWordBeforeNewline ) {
947
+ if ( lastWordBeforeNewline . classList . contains ( "word" ) ) break ;
948
+ lastWordBeforeNewline = lastWordBeforeNewline . previousElementSibling ;
949
+ }
950
+ if ( ! lastWordBeforeNewline ) return 0 ;
951
+
952
+ const letters = lastWordBeforeNewline . querySelectorAll < HTMLElement > ( "letter" ) ;
953
+ for ( const letter of letters ) {
954
+ if ( letter . classList . contains ( "nlChar" ) ) {
955
+ if ( checkIfIncorrect && letter . classList . contains ( "incorrect" ) ) return 0 ;
956
+ const letterComputedStyle = window . getComputedStyle ( letter ) ;
957
+ const letterMargin =
958
+ parseFloat ( letterComputedStyle . marginLeft ) +
959
+ parseFloat ( letterComputedStyle . marginRight ) ;
960
+ return letter . offsetWidth + letterMargin ;
961
+ }
962
+ }
963
+
964
+ return 0 ;
965
+ }
966
+
941
967
let allowWordRemoval = true ;
942
968
export function scrollTape ( noRemove = false ) : void {
943
969
if ( ActivePage . get ( ) !== "test" || resultVisible ) return ;
944
970
945
971
const waitForLineJumpAnimation = lineTransition && ! allowWordRemoval ;
946
- if ( waitForLineJumpAnimation ) noRemove = true ;
972
+ if ( waitForLineJumpAnimation ) {
973
+ setTimeout ( ( ) => scrollTape ( true ) , 50 ) ;
974
+ return ;
975
+ }
947
976
977
+ // index of the active word in the collection of .word elements
948
978
const wordElementIndex = TestState . activeWordIndex - activeWordElementOffset ;
949
979
const wordsWrapperWidth = (
950
980
document . querySelector ( "#wordsWrapper" ) as HTMLElement
@@ -959,20 +989,21 @@ export function scrollTape(noRemove = false): void {
959
989
const afterNewLineEls = wordsEl . getElementsByClassName ( "afterNewline" ) ;
960
990
961
991
let wordsWidthBeforeActive = 0 ;
962
- let fullLinesWidth = 0 ;
963
- let widthRemoved = 0 ;
992
+ let fullLineWidths = 0 ;
964
993
let wordsToRemoveCount = 0 ;
965
994
let leadingNewLine = false ;
966
995
let lastAfterNewLineElement = undefined ;
967
- const afterNewlinesWidths : number [ ] = [ ] ;
996
+ let widthRemoved = 0 ;
997
+ const widthRemovedFromLine : number [ ] = [ ] ;
998
+ const afterNewlinesNewMargins : number [ ] = [ ] ;
968
999
const toRemove : HTMLElement [ ] = [ ] ;
969
1000
970
- // remove leading `.afterNewline` elements
1001
+ /* remove leading `.afterNewline` elements */
971
1002
for ( const child of wordsChildrenArr ) {
972
1003
if ( child . classList . contains ( "word" ) ) {
973
1004
// only last leading `.afterNewline` element pushes `.word`s to right
974
1005
if ( lastAfterNewLineElement ) {
975
- widthRemoved += parseInt ( lastAfterNewLineElement . style . width ) ;
1006
+ widthRemoved += parseFloat ( lastAfterNewLineElement . style . marginLeft ) ;
976
1007
}
977
1008
break ;
978
1009
} else if ( child . classList . contains ( "afterNewline" ) ) {
@@ -981,18 +1012,20 @@ export function scrollTape(noRemove = false): void {
981
1012
lastAfterNewLineElement = child ;
982
1013
}
983
1014
}
984
- // get last element to loop over
985
- const activeWordIndexBetweenWordsChildren =
986
- wordsChildrenArr . indexOf ( activeWordEl ) ;
1015
+
1016
+ /* get last element to loop over */
1017
+ let lastElementIndex : number ;
1018
+ // index of the active word in all #words.children
1019
+ // (which contains .word/.newline/.beforeNewline/.afterNewline elements)
1020
+ const activeWordIndex = wordsChildrenArr . indexOf ( activeWordEl ) ;
987
1021
// this will be 0 or 1
988
1022
const newLinesBeforeActiveWord = wordsChildrenArr
989
- . slice ( 0 , activeWordIndexBetweenWordsChildren )
1023
+ . slice ( 0 , activeWordIndex )
990
1024
. filter ( ( child ) => child . classList . contains ( "afterNewline" ) ) . length ;
991
1025
// the second `.afterNewline` after active word is visible during line jump
992
1026
let lastVisibleAfterNewline = afterNewLineEls [
993
1027
newLinesBeforeActiveWord + 1
994
1028
] as HTMLElement | undefined ;
995
- let lastElementIndex ;
996
1029
if ( lastVisibleAfterNewline ) {
997
1030
lastElementIndex = wordsChildrenArr . indexOf ( lastVisibleAfterNewline ) ;
998
1031
} else {
@@ -1002,14 +1035,15 @@ export function scrollTape(noRemove = false): void {
1002
1035
if ( lastVisibleAfterNewline ) {
1003
1036
lastElementIndex = wordsChildrenArr . indexOf ( lastVisibleAfterNewline ) ;
1004
1037
} else {
1005
- lastElementIndex = activeWordIndexBetweenWordsChildren - 1 ;
1038
+ lastElementIndex = activeWordIndex - 1 ;
1006
1039
}
1007
1040
}
1008
1041
1009
- const wordRightMargin = parseInt (
1042
+ const wordRightMargin = parseFloat (
1010
1043
window . getComputedStyle ( activeWordEl ) . marginRight
1011
1044
) ;
1012
1045
1046
+ /*calculate .afterNewline & #words new margins + determine elements to remove*/
1013
1047
for ( let i = 0 ; i <= lastElementIndex ; i ++ ) {
1014
1048
const child = wordsChildrenArr [ i ] as HTMLElement ;
1015
1049
if ( child . classList . contains ( "word" ) ) {
@@ -1022,36 +1056,46 @@ export function scrollTape(noRemove = false): void {
1022
1056
widthRemoved += wordOuterWidth ;
1023
1057
wordsToRemoveCount ++ ;
1024
1058
} else {
1025
- fullLinesWidth += wordOuterWidth ;
1026
- if ( i < activeWordIndexBetweenWordsChildren )
1027
- wordsWidthBeforeActive = fullLinesWidth ;
1059
+ fullLineWidths += wordOuterWidth ;
1060
+ if ( i < activeWordIndex ) wordsWidthBeforeActive = fullLineWidths ;
1028
1061
}
1029
1062
} else if ( child . classList . contains ( "afterNewline" ) ) {
1030
- if ( ! leadingNewLine ) {
1031
- fullLinesWidth -= wordRightMargin ;
1032
- if ( i < activeWordIndexBetweenWordsChildren )
1033
- wordsWidthBeforeActive = fullLinesWidth ;
1034
- if ( fullLinesWidth > wordsEl . offsetWidth ) {
1035
- afterNewlinesWidths . push ( wordsEl . offsetWidth ) ;
1036
- if ( i < lastElementIndex )
1037
- afterNewlinesWidths . push ( wordsEl . offsetWidth ) ;
1038
- break ;
1039
- } else afterNewlinesWidths . push ( fullLinesWidth ) ;
1063
+ if ( leadingNewLine ) continue ;
1064
+ const nlCharWidth = getNlCharWidthFromPreviousWord ( child , true ) ;
1065
+ fullLineWidths -= nlCharWidth + wordRightMargin ;
1066
+ if ( i < activeWordIndex ) wordsWidthBeforeActive = fullLineWidths ;
1067
+
1068
+ if ( fullLineWidths < wordsEl . offsetWidth ) {
1069
+ afterNewlinesNewMargins . push ( fullLineWidths ) ;
1070
+ widthRemovedFromLine . push ( widthRemoved ) ;
1071
+ } else {
1072
+ afterNewlinesNewMargins . push ( wordsEl . offsetWidth ) ;
1073
+ widthRemovedFromLine . push ( widthRemoved ) ;
1074
+ if ( i < lastElementIndex ) {
1075
+ // for the second .afterNewline after active word
1076
+ afterNewlinesNewMargins . push ( wordsEl . offsetWidth ) ;
1077
+ widthRemovedFromLine . push ( widthRemoved ) ;
1078
+ }
1079
+ break ;
1040
1080
}
1041
1081
}
1042
1082
}
1083
+
1084
+ /* remove overflown elements */
1043
1085
if ( toRemove . length > 0 ) {
1044
1086
activeWordElementOffset += wordsToRemoveCount ;
1045
1087
toRemove . forEach ( ( el ) => el . remove ( ) ) ;
1046
- for ( let i = 0 ; i < afterNewlinesWidths . length ; i ++ ) {
1047
- const element = afterNewLineEls [ i ] as HTMLElement ;
1048
- const currentChildMargin = parseInt ( element . style . width ) || 0 ;
1049
- element . style . width = `${ currentChildMargin - widthRemoved } px` ;
1050
- }
1051
- const currentWordsMargin = parseInt ( wordsEl . style . marginLeft ) || 0 ;
1088
+ widthRemovedFromLine . forEach ( ( width , index ) => {
1089
+ const afterNewlineEl = afterNewLineEls [ index ] as HTMLElement ;
1090
+ const currentLineIndent =
1091
+ parseFloat ( afterNewlineEl . style . marginLeft ) || 0 ;
1092
+ afterNewlineEl . style . marginLeft = `${ currentLineIndent - width } px` ;
1093
+ } ) ;
1094
+ const currentWordsMargin = parseFloat ( wordsEl . style . marginLeft ) || 0 ;
1052
1095
wordsEl . style . marginLeft = `${ currentWordsMargin + widthRemoved } px` ;
1053
1096
}
1054
1097
1098
+ /* calculate current word width to add to #words margin */
1055
1099
let currentWordWidth = 0 ;
1056
1100
if ( Config . tapeMode === "letter" ) {
1057
1101
if ( TestInput . input . current . length > 0 ) {
@@ -1068,11 +1112,10 @@ export function scrollTape(noRemove = false): void {
1068
1112
}
1069
1113
}
1070
1114
}
1071
-
1115
+ /* change to new #words & .afterNewline margins */
1072
1116
const tapeMargin = wordsWrapperWidth * ( Config . tapeMargin / 100 ) ;
1073
- let newMargin = tapeMargin - ( wordsWidthBeforeActive + currentWordWidth ) ;
1074
- if ( waitForLineJumpAnimation )
1075
- newMargin = parseInt ( wordsEl . style . marginLeft ) || 0 ;
1117
+ const newMargin = tapeMargin - ( wordsWidthBeforeActive + currentWordWidth ) ;
1118
+
1076
1119
const jqWords = $ ( wordsEl ) ;
1077
1120
if ( Config . smoothLineScroll ) {
1078
1121
jqWords . stop ( "leftMargin" , true , false ) . animate (
@@ -1083,28 +1126,25 @@ export function scrollTape(noRemove = false): void {
1083
1126
duration : SlowTimer . get ( ) ? 0 : 125 ,
1084
1127
queue : "leftMargin" ,
1085
1128
complete : ( ) => {
1086
- if ( noRemove ) {
1087
- if ( waitForLineJumpAnimation ) scrollTape ( true ) ;
1088
- else scrollTape ( ) ;
1089
- }
1129
+ if ( noRemove ) scrollTape ( ) ;
1090
1130
} ,
1091
1131
}
1092
1132
) ;
1093
1133
jqWords . dequeue ( "leftMargin" ) ;
1094
- afterNewlinesWidths . forEach ( ( width , index ) => {
1134
+ afterNewlinesNewMargins . forEach ( ( margin , index ) => {
1095
1135
$ ( afterNewLineEls [ index ] as Element )
1096
1136
. stop ( true , false )
1097
1137
. animate (
1098
1138
{
1099
- marginLeft : width ,
1139
+ marginLeft : margin ,
1100
1140
} ,
1101
1141
SlowTimer . get ( ) ? 0 : 125
1102
1142
) ;
1103
1143
} ) ;
1104
1144
} else {
1105
1145
wordsEl . style . marginLeft = `${ newMargin } px` ;
1106
- afterNewlinesWidths . forEach ( ( width , index ) => {
1107
- ( afterNewLineEls [ index ] as HTMLElement ) . style . width = `${ width } px` ;
1146
+ afterNewlinesNewMargins . forEach ( ( margin , index ) => {
1147
+ ( afterNewLineEls [ index ] as HTMLElement ) . style . marginLeft = `${ margin } px` ;
1108
1148
} ) ;
1109
1149
if ( noRemove ) scrollTape ( ) ;
1110
1150
}
@@ -1153,16 +1193,21 @@ export function lineJump(currentTop: number, force = false): void {
1153
1193
if ( currentTestLine > 0 || force ) {
1154
1194
const hideBound = currentTop - 10 ;
1155
1195
1156
- const wordIndex = TestState . activeWordIndex - activeWordElementOffset ;
1196
+ // index of the active word in the collection of .word elements
1197
+ const wordElementIndex =
1198
+ TestState . activeWordIndex - activeWordElementOffset ;
1157
1199
const wordsEl = document . getElementById ( "words" ) as HTMLElement ;
1158
1200
const wordsChildrenArr = [ ...wordsEl . children ] ;
1159
1201
const wordElements = wordsEl . querySelectorAll ( ".word" ) ;
1160
- const activeWordEl = wordElements [ wordIndex ] ;
1202
+ const activeWordEl = wordElements [ wordElementIndex ] ;
1161
1203
if ( ! activeWordEl ) return ;
1162
- const activeWordIndexBetweenWordsChildren =
1163
- wordsChildrenArr . indexOf ( activeWordEl ) ;
1204
+
1205
+ // index of the active word in all #words.children
1206
+ // (which contains .word/.newline/.beforeNewline/.afterNewline elements)
1207
+ const activeWordIndex = wordsChildrenArr . indexOf ( activeWordEl ) ;
1208
+
1164
1209
let lastElementToRemove = undefined ;
1165
- for ( let i = 0 ; i < activeWordIndexBetweenWordsChildren ; i ++ ) {
1210
+ for ( let i = 0 ; i < activeWordIndex ; i ++ ) {
1166
1211
const child = wordsChildrenArr [ i ] as HTMLElement ;
1167
1212
if ( child . classList . contains ( "hidden" ) ) continue ;
1168
1213
if ( Math . floor ( child . offsetTop ) < hideBound ) {
@@ -1228,7 +1273,7 @@ export function lineJump(currentTop: number, force = false): void {
1228
1273
currentLinesAnimating = 0 ;
1229
1274
activeWordTop = (
1230
1275
document . querySelectorAll ( "#words .word" ) ?. [
1231
- wordIndex
1276
+ wordElementIndex
1232
1277
] as HTMLElement
1233
1278
) ?. offsetTop ;
1234
1279
activeWordElementOffset +=
0 commit comments