fix(caret): misalignment issue when switching between carets (@byseif21) (#6642)

### Description

fixes a caret misalignment issue that occurred when switching between
caret styles (e.g., from block/outline/underline to line caret).


#### Bug:

When switching caret styles, the **line caret** could retain the width
from the previous style (e.g., block), causing it to appear **visually
incorrect** until the next input.

#### Root Cause:

* The caret's inline `width` was **not reset** when changing styles, so
the new style inherited the old dimensions.
* `updatePosition()` was called **before** the style was fully applied,
leading to **incorrect positioning**.

---

### Fix

* Added a `subscribe` listener in `caret.ts` for the `"caretStyle"`
event.
* When the caret style changes:

  * The caret's inline width is **reset**:

    ```ts
    caret.style.width = "";
    ```
  * Its position is immediately **recalculated** with:

    ```ts
    updatePosition(true);
    ```

---------

Co-authored-by: Miodec <jack@monkeytype.com>
This commit is contained in:
Seif Soliman 2025-07-01 20:06:43 +03:00 committed by GitHub
parent de447c2cae
commit ce737e35b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -8,6 +8,7 @@ import { prefersReducedMotion } from "../utils/misc";
import { convertRemToPixels } from "../utils/numbers";
import { splitIntoCharacters } from "../utils/strings";
import { safeNumber } from "@monkeytype/util/numbers";
import { subscribe } from "../observables/config-event";
export let caretAnimating = true;
const caret = document.querySelector("#caret") as HTMLElement;
@ -132,6 +133,27 @@ function getTargetPositionLeft(
return result;
}
function getLetterWidth(
currentLetter: HTMLElement | undefined,
activeWordEl: HTMLElement,
wordLength: number,
inputLength: number,
currentWordNodeList: NodeListOf<HTMLElement>
): number {
let letterWidth = currentLetter?.offsetWidth;
if (letterWidth === undefined || wordLength === 0) {
// at word beginning in zen mode current letter is defined "_" but wordLen is 0
letterWidth = getSpaceWidth(activeWordEl);
} else if (letterWidth === 0) {
// current letter is a zero-width character e.g, diacritics)
for (let i = inputLength; i >= 0; i--) {
letterWidth = (currentWordNodeList[i] as HTMLElement)?.offsetWidth;
if (letterWidth) break;
}
}
return letterWidth ?? 0;
}
export async function updatePosition(noAnim = false): Promise<void> {
const caretComputedStyle = window.getComputedStyle(caret);
const caretWidth = parseInt(caretComputedStyle.width) || 0;
@ -176,18 +198,13 @@ export async function updatePosition(noAnim = false): Promise<void> {
newTop = activeWordEl.offsetTop + letterPosTop - caretHeight / 2;
}
let letterWidth = currentLetter?.offsetWidth;
if (letterWidth === undefined || wordLen === 0) {
// at word beginning in zen mode current letter is defined "_" but wordLen is 0
letterWidth = getSpaceWidth(activeWordEl);
} else if (letterWidth === 0) {
// current letter is a zero-width character e.g, diacritics)
for (let i = inputLen; i >= 0; i--) {
letterWidth = (currentWordNodeList[i] as HTMLElement)?.offsetWidth;
if (letterWidth) break;
}
}
const newWidth = fullWidthCaret ? (letterWidth ?? 0) + "px" : "";
const letterWidth = getLetterWidth(
currentLetter,
activeWordEl,
wordLen,
inputLen,
currentWordNodeList
);
const letterPosLeft = getTargetPositionLeft(
fullWidthCaret,
@ -209,10 +226,8 @@ export async function updatePosition(noAnim = false): Promise<void> {
left: newLeft,
};
if (newWidth !== "") {
animation.width = newWidth;
} else {
jqcaret.css("width", "");
if (fullWidthCaret) {
animation.width = `${letterWidth}px`;
}
const smoothCaretSpeed =
@ -250,6 +265,13 @@ export async function updatePosition(noAnim = false): Promise<void> {
}
}
subscribe((eventKey) => {
if (eventKey === "caretStyle") {
caret.style.width = "";
void updatePosition(true);
}
});
export function show(noAnim = false): void {
caret.classList.remove("hidden");
void updatePosition(noAnim);