use signals for focus and isactive state

This commit is contained in:
Miodec 2026-01-08 16:56:10 +01:00
parent 205a5154df
commit 75a36c9feb
19 changed files with 41 additions and 55 deletions

View file

@ -79,7 +79,7 @@ export function saveFullConfigToLocalStorage(noDbCheck = false): void {
}
function isConfigChangeBlocked(): boolean {
if (TestState.isActive && config.funbox.includes("no_quit")) {
if (TestState.isActive() && config.funbox.includes("no_quit")) {
Notifications.add("No quit funbox is active. Please finish the test.", 0, {
important: true,
});
@ -112,7 +112,7 @@ export function setConfig<T extends keyof Config>(
if (
metadata.changeRequiresRestart &&
TestState.isActive &&
TestState.isActive() &&
config.funbox.includes("no_quit")
) {
Notifications.add("No quit funbox is active. Please finish the test.", 0, {

View file

@ -42,7 +42,7 @@ function init(): void {
}
setInterval(() => {
if (TestState.isActive) {
if (TestState.isActive()) {
return;
}
if (choice === "eg") {

View file

@ -176,7 +176,7 @@ export async function navigate(
}
const noQuit = isFunboxActive("no_quit");
if (TestState.isActive && noQuit) {
if (TestState.isActive() && noQuit) {
Notifications.add("No quit funbox is active. Please finish the test.", 0, {
important: true,
});

View file

@ -7,7 +7,7 @@ import * as TestUI from "../../test/test-ui";
import { isAwaitingNextWord } from "../state";
export function onBeforeDelete(event: InputEvent): void {
if (!TestState.isActive) {
if (!TestState.isActive()) {
event.preventDefault();
return;
}

View file

@ -113,7 +113,7 @@ export async function onInsertText(options: OnInsertTextParams): Promise<void> {
const data = normalizedData ?? options.data;
// start if needed
if (!TestState.isActive) {
if (!TestState.isActive()) {
TestLogic.startTest(now);
}

View file

@ -19,7 +19,7 @@ inputEl.addEventListener("compositionstart", (event) => {
CompositionState.setComposing(true);
CompositionState.setData("");
setLastInsertCompositionTextData("");
if (!TestState.isActive) {
if (!TestState.isActive()) {
TestLogic.startTest(performance.now());
}
});

View file

@ -2,5 +2,3 @@ import { createSignal } from "solid-js";
export const [getWpm, setWpm] = createSignal("0");
export const [getAcc, setAcc] = createSignal("100%");
export const [getBurst, setBurst] = createSignal("0");
export const [getFocus, setFocus] = createSignal(false);
export const [getTestRunning, setTestRunning] = createSignal(false);

View file

@ -39,7 +39,7 @@ const throttledHandleState = debounce(5000, () => {
)?.dispatch("click");
}
bannerAlreadyClosed = false;
} else if (!TestState.isActive) {
} else if (!TestState.isActive()) {
showOfflineBanner();
}
});

View file

@ -2,10 +2,10 @@ import * as Caret from "./caret";
import * as TimerProgress from "./timer-progress";
import * as PageTransition from "../states/page-transition";
import { requestDebouncedAnimationFrame } from "../utils/debounced-animation-frame";
import { setFocus } from "../signals/live-states";
import { createSignal } from "solid-js";
const unfocusPx = 3;
let state = false;
let [state, setState] = createSignal(false);
let cacheReady = false;
let cache: {
@ -13,6 +13,10 @@ let cache: {
cursor?: HTMLElement[];
} = {};
export function isFocused(): boolean {
return state();
}
function initializeCache(): void {
if (cacheReady) return;
@ -43,8 +47,8 @@ export function set(value: boolean, withCursor = false): void {
requestDebouncedAnimationFrame("focus.set", () => {
initializeCache();
if (value && !state) {
state = true;
if (value && !state()) {
setState(true);
// batch DOM operations for better performance
if (cache.focus) {
@ -60,8 +64,8 @@ export function set(value: boolean, withCursor = false): void {
Caret.stopAnimation();
TimerProgress.show();
} else if (!value && state) {
state = false;
} else if (!value && state()) {
setState(false);
if (cache.focus) {
for (const el of cache.focus) {
@ -77,14 +81,12 @@ export function set(value: boolean, withCursor = false): void {
Caret.startAnimation();
TimerProgress.hide();
}
setFocus(state);
});
}
$(document).on("mousemove", function (event) {
if (PageTransition.get()) return;
if (!state) return;
if (!state()) return;
if (
event.originalEvent &&
// To avoid mouse/desk vibration from creating a flashy effect, we'll unfocus @ >5px instead of >0px

View file

@ -2,20 +2,16 @@ import { createMemo } from "solid-js";
import { qsr } from "../utils/dom";
import { LiveCounter } from "./live-counter";
import { render } from "solid-js/web";
import {
getAcc,
getBurst,
getFocus,
getTestRunning,
getWpm,
} from "../signals/live-states";
import { getAcc, getBurst, getWpm } from "../signals/live-states";
import {
getLiveAccStyle,
getLiveBurstStyle,
getLiveSpeedStyle,
} from "../signals/config";
import { isActive } from "./test-state";
import { isFocused } from "./focus";
const isTestRunning = createMemo(() => getTestRunning() && getFocus());
const isTestRunning = createMemo(() => isActive() && isFocused());
const liveWpmText = createMemo(() =>
isTestRunning() && getLiveSpeedStyle() === "text" ? getWpm() : "",

View file

@ -9,7 +9,7 @@ const monkeyEl = document.querySelector("#monkey") as HTMLElement;
const monkeyFastEl = document.querySelector("#monkey .fast") as HTMLElement;
ConfigEvent.subscribe(({ key }) => {
if (key === "monkey" && TestState.isActive) {
if (key === "monkey" && TestState.isActive()) {
if (Config.monkey) {
monkeyEl.classList.remove("hidden");
} else {

View file

@ -133,7 +133,7 @@ export async function update(expectedStepEnd: number): Promise<void> {
const currentSettings = settings;
if (
currentSettings === null ||
!TestState.isActive ||
!TestState.isActive() ||
TestState.resultVisible
) {
return;

View file

@ -151,7 +151,7 @@ export function restart(options = {} as RestartOptions): void {
const animationTime = options.noAnim ? 0 : Misc.applyReducedMotion(125);
const noQuit = isFunboxActive("no_quit");
if (TestState.isActive && noQuit) {
if (TestState.isActive() && noQuit) {
Notifications.add("No quit funbox is active. Please finish the test.", 0, {
important: true,
});
@ -196,7 +196,7 @@ export function restart(options = {} as RestartOptions): void {
}
}
if (TestState.isActive) {
if (TestState.isActive()) {
if (TestState.isRepeated) {
options.withSameWordset = true;
}
@ -221,7 +221,7 @@ export function restart(options = {} as RestartOptions): void {
TestWords.currentQuote !== null &&
Config.language.startsWith(TestWords.currentQuote.language) &&
Config.repeatQuotes === "typing" &&
(TestState.isActive || failReason !== "")
(TestState.isActive() || failReason !== "")
) {
options.withSameWordset = true;
}
@ -854,7 +854,7 @@ function buildCompletedEvent(
}
export async function finish(difficultyFailed = false): Promise<void> {
if (!TestState.isActive) return;
if (!TestState.isActive()) return;
TestUI.setResultCalculating(true);
const now = performance.now();
TestTimer.clear();
@ -1404,7 +1404,7 @@ $(".pageTest").on("click", "#restartTestButton", () => {
ManualRestart.set();
if (TestUI.resultCalculating) return;
if (
TestState.isActive &&
TestState.isActive() &&
Config.repeatQuotes === "typing" &&
Config.mode === "quote"
) {

View file

@ -1,9 +1,10 @@
import { Challenge } from "@monkeytype/schemas/challenges";
import { promiseWithResolvers } from "../utils/misc";
import { createSignal } from "solid-js";
export let isRepeated = false;
export let isPaceRepeat = false;
export let isActive = false;
export let [isActive, setActive] = createSignal(false);
export let activeChallenge: null | Challenge = null;
export let savingEnabled = true;
export let bailedOut = false;
@ -23,10 +24,6 @@ export function setPaceRepeat(tf: boolean): void {
isPaceRepeat = tf;
}
export function setActive(tf: boolean): void {
isActive = tf;
}
export function setActiveChallenge(val: null | Challenge): void {
activeChallenge = val;
}

View file

@ -160,7 +160,7 @@ export function calculateWpmAndRaw(
raw: number;
} {
const testSeconds = calculateTestSeconds(
TestState.isActive ? performance.now() : end,
TestState.isActive() ? performance.now() : end,
);
const chars = countChars(final);
const wpm = Numbers.roundTo2(
@ -256,7 +256,7 @@ function getInputWords(): string[] {
let inputWords = [...TestInput.input.getHistory()];
if (TestState.isActive) {
if (TestState.isActive()) {
inputWords.push(TestInput.input.current);
}
@ -276,7 +276,7 @@ function getTargetWords(): string[] {
: TestWords.words.list),
];
if (TestState.isActive) {
if (TestState.isActive()) {
targetWords.push(
Config.mode === "zen"
? TestInput.input.current

View file

@ -308,7 +308,7 @@ async function _startOld(): Promise<void> {
const drift = Math.abs(interval - delay);
checkIfTimerIsSlow(drift);
timer = setTimeout(function () {
if (!TestState.isActive) {
if (!TestState.isActive()) {
if (timer !== null) clearTimeout(timer);
SlowTimer.clear();
slowTimerCount = 0;

View file

@ -55,12 +55,7 @@ import * as ModesNotice from "../elements/modes-notice";
import * as Last10Average from "../elements/last-10-average";
import * as MemoryFunboxTimer from "./funbox/memory-funbox-timer";
import { qsr } from "../utils/dom";
import {
setAcc,
setBurst,
setTestRunning,
setWpm,
} from "../signals/live-states";
import { setAcc, setBurst, setWpm } from "../signals/live-states";
export const updateHintsPositionDebounced = Misc.debounceUntilResolved(
updateHintsPosition,
@ -88,7 +83,7 @@ export function focusWords(force = false): void {
blurInputElement();
}
focusInputElement(true);
if (TestState.isActive) {
if (TestState.isActive()) {
keepWordsInputInTheCenter(true);
} else {
const typingTest = document.querySelector<HTMLElement>("#typingTest");
@ -1818,7 +1813,6 @@ export function onTestStart(): void {
Focus.set(true);
Monkey.show();
TimerProgress.show();
setTestRunning(true);
TimerProgress.update();
}
@ -1875,7 +1869,6 @@ export function onTestRestart(source: "testPage" | "resultPage"): void {
export function onTestFinish(): void {
Caret.hide();
setTestRunning(false);
TimerProgress.hide();
OutOfFocus.hide();
Monkey.hide();

View file

@ -20,7 +20,7 @@ const textEl = document.querySelector(
const miniEl = document.querySelector("#liveStatsMini .time") as HTMLElement;
export function show(): void {
if (!TestState.isActive) return;
if (!TestState.isActive()) return;
requestDebouncedAnimationFrame("timer-progress.show", () => {
if (Config.mode !== "zen" && Config.timerStyle === "bar") {
animate(barOpacityEl, {
@ -255,7 +255,7 @@ export function update(): void {
}
export function updateStyle(): void {
if (!TestState.isActive) return;
if (!TestState.isActive()) return;
hide();
update();
if (Config.timerStyle === "off") return;

View file

@ -81,7 +81,7 @@ window.addEventListener("beforeunload", (event) => {
) {
//ignore
} else {
if (TestState.isActive) {
if (TestState.isActive()) {
event.preventDefault();
// Included for legacy support, e.g. Chrome/Edge < 119
// oxlint-disable-next-line no-deprecated