diff --git a/frontend/src/ts/test/caret.ts b/frontend/src/ts/test/caret.ts index 2f3bdfd19c7c..a8d0aab1b807 100644 --- a/frontend/src/ts/test/caret.ts +++ b/frontend/src/ts/test/caret.ts @@ -4,7 +4,6 @@ import * as TestInput from "./test-input"; import * as SlowTimer from "../states/slow-timer"; import * as TestState from "../test/test-state"; import * as TestWords from "./test-words"; -import { prefersReducedMotion } from "../utils/misc"; import { convertRemToPixels } from "../utils/numbers"; import { splitIntoCharacters, getWordDirection } from "../utils/strings"; import { safeNumber } from "@monkeytype/util/numbers"; @@ -262,25 +261,6 @@ export async function updatePosition(noAnim = false): Promise { jqcaret .stop(true, false) .animate(animation, SlowTimer.get() || noAnim ? 0 : smoothCaretSpeed); - - if (Config.showAllLines) { - const browserHeight = window.innerHeight; - const middlePos = browserHeight / 2 - (jqcaret.outerHeight() as number) / 2; - const contentHeight = document.body.scrollHeight; - - if ( - newTop >= middlePos && - contentHeight > browserHeight && - TestState.isActive - ) { - const newscrolltop = newTop - middlePos / 2; - window.scrollTo({ - left: 0, - top: newscrolltop, - behavior: prefersReducedMotion() ? "instant" : "smooth", - }); - } - } } function updateStyle(): void { diff --git a/frontend/src/ts/test/result.ts b/frontend/src/ts/test/result.ts index 679b63e96e76..99ba676e231c 100644 --- a/frontend/src/ts/test/result.ts +++ b/frontend/src/ts/test/result.ts @@ -1074,7 +1074,11 @@ export async function update( $("#result"), 250, async () => { - $("#result").trigger("focus"); + const result = document.querySelector("#result"); + result?.focus({ + preventScroll: true, + }); + Misc.scrollToCenterOrTop(result); void AdController.renderResult(); TestUI.setResultCalculating(false); $("#words").empty(); diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index 130c308b7919..22ea7d322b15 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -155,13 +155,45 @@ export function reset(): void { } export function focusWords(): void { - $("#wordsInput").trigger("focus"); + const wordsInput = document.querySelector("#wordsInput"); + wordsInput?.blur(); + wordsInput?.focus({ + preventScroll: true, + }); + if (TestState.isActive) { + keepWordsInputInTheCenter(true); + } else { + const typingTest = document.querySelector("#typingTest"); + Misc.scrollToCenterOrTop(typingTest); + } } export function blurWords(): void { $("#wordsInput").trigger("blur"); } +export function keepWordsInputInTheCenter(force = false): void { + const wordsInput = document.querySelector("#wordsInput"); + const wordsWrapper = document.querySelector("#wordsWrapper"); + if (!wordsInput || !wordsWrapper) return; + + const wordsWrapperHeight = wordsWrapper.offsetHeight; + const windowHeight = window.innerHeight; + + // dont do anything if the wrapper can fit on screen + if (wordsWrapperHeight < windowHeight) return; + + const wordsInputRect = wordsInput.getBoundingClientRect(); + const wordsInputBelowCenter = wordsInputRect.top > windowHeight / 2; + + // dont do anything if its above or at the center unless forced + if (!wordsInputBelowCenter && !force) return; + + wordsInput.scrollIntoView({ + block: "center", + }); +} + export function getWordElement(index: number): HTMLElement | null { const el = document.querySelector( `#words .word[data-wordindex='${index}']` @@ -197,9 +229,8 @@ export function updateActiveElement( activeWordTop = newActiveWord.offsetTop; - if (!initial && shouldUpdateWordsInputPosition()) { - void updateWordsInputPosition(); - } + void updateWordsInputPosition(); + if (!initial && Config.tapeMode !== "off") { void scrollTape(); } @@ -448,7 +479,7 @@ function updateWordWrapperClasses(): void { updateWordsWidth(); updateWordsWrapperHeight(true); - updateWordsMargin(updateWordsInputPosition, [true]); + updateWordsMargin(updateWordsInputPosition, []); } export function showWords(): void { @@ -478,65 +509,54 @@ export function appendEmptyWordElement( `
` ); } +let updateWordsInputPositionAnimationFrameId: null | number = null; +export async function updateWordsInputPosition(): Promise { + if (updateWordsInputPositionAnimationFrameId !== null) { + cancelAnimationFrame(updateWordsInputPositionAnimationFrameId); + } + updateWordsInputPositionAnimationFrameId = requestAnimationFrame(async () => { + updateWordsInputPositionAnimationFrameId = null; + if (ActivePage.get() !== "test") return; + const currentLanguage = await JSONData.getCurrentLanguage(Config.language); + const isLanguageRTL = currentLanguage.rightToLeft; -const posUpdateLangList = ["japanese", "chinese", "korean"]; -function shouldUpdateWordsInputPosition(): boolean { - const language = posUpdateLangList.some((l) => Config.language.startsWith(l)); - return language || (Config.mode !== "time" && Config.showAllLines); -} - -export async function updateWordsInputPosition(initial = false): Promise { - if (ActivePage.get() !== "test") return; - - const currentLanguage = await JSONData.getCurrentLanguage(Config.language); - const isLanguageRTL = currentLanguage.rightToLeft; - - const el = document.querySelector("#wordsInput"); + const el = document.querySelector("#wordsInput"); - if (!el) return; + if (!el) return; - const activeWord = getActiveWordElement(); - - if (!activeWord) { - el.style.top = "0px"; - el.style.left = "0px"; - return; - } + const activeWord = getActiveWordElement(); - const computed = window.getComputedStyle(activeWord); - const activeWordMargin = - parseInt(computed.marginTop) + parseInt(computed.marginBottom); + if (!activeWord) { + el.style.top = "0px"; + el.style.left = "0px"; + return; + } - const letterHeight = convertRemToPixels(Config.fontSize); - const targetTop = - activeWord.offsetTop + letterHeight / 2 - el.offsetHeight / 2 + 1; //+1 for half of border + const letterHeight = convertRemToPixels(Config.fontSize); + const targetTop = + activeWord.offsetTop + letterHeight / 2 - el.offsetHeight / 2 + 1; //+1 for half of border - if (Config.tapeMode !== "off") { - el.style.maxWidth = `${100 - Config.tapeMargin}%`; - } else { - el.style.maxWidth = ""; - } - if (activeWord.offsetWidth < letterHeight) { - el.style.width = letterHeight + "px"; - } else { - el.style.width = activeWord.offsetWidth + "px"; - } + if (Config.tapeMode !== "off") { + el.style.maxWidth = `${100 - Config.tapeMargin}%`; + } else { + el.style.maxWidth = ""; + } + if (activeWord.offsetWidth < letterHeight) { + el.style.width = letterHeight + "px"; + } else { + el.style.width = activeWord.offsetWidth + "px"; + } - if ( - initial && - !shouldUpdateWordsInputPosition() && - Config.tapeMode === "off" - ) { - el.style.top = targetTop + letterHeight + activeWordMargin + 4 + "px"; - } else { el.style.top = targetTop + "px"; - } - if (activeWord.offsetWidth < letterHeight && isLanguageRTL) { - el.style.left = activeWord.offsetLeft - letterHeight + "px"; - } else { - el.style.left = Math.max(0, activeWord.offsetLeft) + "px"; - } + if (activeWord.offsetWidth < letterHeight && isLanguageRTL) { + el.style.left = activeWord.offsetLeft - letterHeight + "px"; + } else { + el.style.left = Math.max(0, activeWord.offsetLeft) + "px"; + } + + keepWordsInputInTheCenter(); + }); } let centeringActiveLine: Promise = Promise.resolve(); diff --git a/frontend/src/ts/ui.ts b/frontend/src/ts/ui.ts index ba8ce4a99d5c..3afadcec582f 100644 --- a/frontend/src/ts/ui.ts +++ b/frontend/src/ts/ui.ts @@ -107,9 +107,7 @@ const debouncedEvent = debounce(250, () => { } setTimeout(() => { void TestUI.updateWordsInputPosition(); - if ($("#wordsInput").is(":focus")) { - Caret.show(true); - } + TestUI.focusWords(); }, 250); } }); diff --git a/frontend/src/ts/utils/misc.ts b/frontend/src/ts/utils/misc.ts index 425789de993e..976cf5d7da0d 100644 --- a/frontend/src/ts/utils/misc.ts +++ b/frontend/src/ts/utils/misc.ts @@ -746,4 +746,15 @@ export function isMacLike(): boolean { return isPlatform(/Mac|iPod|iPhone|iPad/); } +export function scrollToCenterOrTop(el: HTMLElement | null): void { + if (!el) return; + + const elementHeight = el.offsetHeight; + const windowHeight = window.innerHeight; + + el.scrollIntoView({ + block: elementHeight < windowHeight ? "center" : "start", + }); +} + // DO NOT ALTER GLOBAL OBJECTSONSTRUCTOR, IT WILL BREAK RESULT HASHES