fix(caret): caret not resetting position on quick restart (@nadalaba) (#7038)

- quick restarting immediately after some input makes the caret fail to
reset its position sometimes.


https://github.com/user-attachments/assets/d9e1def5-d8f6-4af2-845c-778c89279849

this happens because the reset of caret's position that should happen on
the callback inside `requestAnimationFrame` in `caret.goTo()` (triggered
in `TestUI.updateWordsWrapperClasses()`) is cancelled by a later call to
`caret.goTo()` (triggered by `TestUI.focusWords()`). However the second
call sets the position directly without stopping previous animation
`$('#caret').stop().animate()`. As a result, the earlier animation
(caused by last input) continues after the reset, if the restart was
done fast enough.

- update some previous comments.
This commit is contained in:
Nad Alaba 2025-10-27 14:14:26 +03:00 committed by GitHub
parent 42227666a8
commit a0c9decc3f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 11 additions and 5 deletions

View file

@ -286,6 +286,7 @@ export function restart(options = {} as RestartOptions): void {
TimerProgress.hide();
Replay.pauseReplay();
TestState.setBailedOut(false);
Caret.resetPosition();
PaceCaret.reset();
Monkey.hide();
TestInput.input.setKoreanStatus(false);

View file

@ -102,6 +102,7 @@ export class Caret {
top: number;
width?: number;
}): void {
$(this.element).stop("pos", true, false);
this.element.style.left = `${options.left}px`;
this.element.style.top = `${options.top}px`;
if (options.width !== undefined) {
@ -143,6 +144,8 @@ export class Caret {
}
public handleTapeWordsRemoved(widthRemoved: number): void {
// Removing tape words reduces the absolute value of options.newValue passed to handleTapeScroll().
// To keep the target marginLeft accurate, we reduce the absolute value of cumulative correction by the same amount.
this.cumulativeTapeMarginCorrection += widthRemoved;
}
@ -154,12 +157,14 @@ export class Caret {
this.readyToResetMarginLeft = false;
/**
* If we didn't reset marginLeft, then options.newValue gives the correct caret
* position by adding up the widths of all typed characters. But since we reset
* caret.style.marginLeft during the test, the caret ends up too far left.
* options.newValue is the total width of all previously typed characters, and assuming we didn't
* reset marginLeft or remove any tape words, then it would be the correct marginLeft to go to.
*
* To fix this, we track how much marginLeft we've reset so far (cumulativeTapeMarginCorrection),
* and subtract it from options.newValue to get the correct newMarginLeft.
* Each time marginLeft is reset, the distance between options.newValue and the current
* marginLeft-after-reset increases, making the caret shift too much left (in LTR).
*
* To correct this, we decrease the new target marginLeft by how much we've
* reset the margin so far (cumulativeTapeMarginCorrection).
*/
const newMarginLeft =
options.newValue - this.cumulativeTapeMarginCorrection;