diff --git a/frontend/package.json b/frontend/package.json
index 7bcd85ef1b08..a7231c169630 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -39,7 +39,10 @@
"@tanstack/pacer-lite": "0.2.1",
"@tanstack/query-db-collection": "1.0.27",
"@tanstack/solid-db": "0.2.10",
+ "@tanstack/solid-devtools": "0.8.0",
"@tanstack/solid-form": "1.28.4",
+ "@tanstack/solid-hotkeys": "0.4.2",
+ "@tanstack/solid-hotkeys-devtools": "0.4.3",
"@tanstack/solid-query": "5.90.23",
"@tanstack/solid-query-devtools": "5.91.3",
"@tanstack/solid-table": "8.21.3",
@@ -60,7 +63,6 @@
"hangul-js": "0.2.6",
"howler": "2.2.3",
"idb": "8.0.3",
- "konami": "1.7.0",
"lz-ts": "1.1.2",
"modern-screenshot": "4.6.8",
"object-hash": "3.0.0",
diff --git a/frontend/src/html/pages/settings.html b/frontend/src/html/pages/settings.html
index a997a27b2599..7049cbd4ee1f 100644
--- a/frontend/src/html/pages/settings.html
+++ b/frontend/src/html/pages/settings.html
@@ -38,13 +38,7 @@
tip: You can also change all these settings quickly using the command line (
- ctrl/cmd
- +
- shift
- +
- p
- or
- esc
+
)
diff --git a/frontend/src/styles/test.scss b/frontend/src/styles/test.scss
index daa6e532be96..9a9e11c6a735 100644
--- a/frontend/src/styles/test.scss
+++ b/frontend/src/styles/test.scss
@@ -1549,6 +1549,7 @@
.textButton {
padding: 0.5em 1em;
+ align-items: center;
&.noInteraction {
pointer-events: none;
}
diff --git a/frontend/src/ts/components/core/DevTools.tsx b/frontend/src/ts/components/core/DevTools.tsx
index 85eb49e91b86..189a0a825a98 100644
--- a/frontend/src/ts/components/core/DevTools.tsx
+++ b/frontend/src/ts/components/core/DevTools.tsx
@@ -3,9 +3,9 @@ import { JSXElement, lazy, onMount, Suspense } from "solid-js";
let DevComponents: (() => JSXElement) | undefined;
if (import.meta.env.DEV) {
- const LazyQueryDevtools = lazy(async () =>
- import("@tanstack/solid-query-devtools").then((m) => ({
- default: m.SolidQueryDevtools,
+ const LazyTanstackDevtools = lazy(async () =>
+ import("./TanstackDevtools").then((m) => ({
+ default: m.TanStackDevtools,
})),
);
const LazyDevOptionsModal = lazy(async () =>
@@ -19,7 +19,7 @@ if (import.meta.env.DEV) {
default: () => {
onMount(() => {
m.attachDevtoolsOverlay({
- defaultOpen: true,
+ defaultOpen: false,
noPadding: true,
});
});
@@ -31,7 +31,7 @@ if (import.meta.env.DEV) {
DevComponents = () => (
-
+
diff --git a/frontend/src/ts/components/core/TanstackDevtools.tsx b/frontend/src/ts/components/core/TanstackDevtools.tsx
new file mode 100644
index 000000000000..3f906ea91313
--- /dev/null
+++ b/frontend/src/ts/components/core/TanstackDevtools.tsx
@@ -0,0 +1,23 @@
+import { TanStackDevtools as TsDevTools } from "@tanstack/solid-devtools";
+import { hotkeysDevtoolsPlugin } from "@tanstack/solid-hotkeys-devtools";
+import { SolidQueryDevtoolsPanel } from "@tanstack/solid-query-devtools";
+import { JSXElement } from "solid-js";
+
+import { queryClient } from "../../queries";
+
+export function TanStackDevtools(): JSXElement {
+ return (
+ ,
+ defaultOpen: true,
+ },
+ hotkeysDevtoolsPlugin(),
+ ]}
+ config={{ defaultOpen: false }}
+ />
+ );
+}
diff --git a/frontend/src/ts/components/hotkeys/CommandlineHotkey.tsx b/frontend/src/ts/components/hotkeys/CommandlineHotkey.tsx
new file mode 100644
index 000000000000..544f5b3b415e
--- /dev/null
+++ b/frontend/src/ts/components/hotkeys/CommandlineHotkey.tsx
@@ -0,0 +1,17 @@
+import { Show } from "solid-js";
+
+import { hotkeys } from "../../states/hotkeys";
+import { isFirefox } from "../../utils/misc";
+import { Kbd } from "./Kbd";
+
+export function CommandlineHotkey() {
+ return (
+ <>
+
+
+ or
+
+
+ >
+ );
+}
diff --git a/frontend/src/ts/components/hotkeys/Kbd.tsx b/frontend/src/ts/components/hotkeys/Kbd.tsx
new file mode 100644
index 000000000000..e9d44600f3c8
--- /dev/null
+++ b/frontend/src/ts/components/hotkeys/Kbd.tsx
@@ -0,0 +1,10 @@
+import { formatWithLabels, Hotkey } from "@tanstack/solid-hotkeys";
+import { JSXElement } from "solid-js";
+
+export function Kbd(props: { hotkey: Hotkey }): JSXElement {
+ return (
+
+ {formatWithLabels(props.hotkey).toLowerCase().replace(/\+/g, " + ")}
+
+ );
+}
diff --git a/frontend/src/ts/components/hotkeys/QuickRestartHotkey.tsx b/frontend/src/ts/components/hotkeys/QuickRestartHotkey.tsx
new file mode 100644
index 000000000000..48702b462bc0
--- /dev/null
+++ b/frontend/src/ts/components/hotkeys/QuickRestartHotkey.tsx
@@ -0,0 +1,16 @@
+import { JSXElement, Show } from "solid-js";
+
+import { getConfig } from "../../config/store";
+import { hotkeys } from "../../states/hotkeys";
+import { Kbd } from "./Kbd";
+
+export function QuickRestartHotkey(): JSXElement {
+ return (
+ tab {">"} enter
+ >
+
+
+ );
+}
diff --git a/frontend/src/ts/components/layout/footer/Keytips.tsx b/frontend/src/ts/components/layout/footer/Keytips.tsx
index 3c703888f71f..4ccc8a98cc41 100644
--- a/frontend/src/ts/components/layout/footer/Keytips.tsx
+++ b/frontend/src/ts/components/layout/footer/Keytips.tsx
@@ -2,42 +2,27 @@ import { JSXElement, Show } from "solid-js";
import { getConfig } from "../../../config/store";
import { getFocus } from "../../../states/core";
-import { Conditional } from "../../common/Conditional";
+import { CommandlineHotkey } from "../../hotkeys/CommandlineHotkey";
+import { QuickRestartHotkey } from "../../hotkeys/QuickRestartHotkey";
export function Keytips(): JSXElement {
- const userAgent = window.navigator.userAgent.toLowerCase();
- const modifierKey =
- userAgent.includes("mac") && !userAgent.includes("firefox")
- ? "cmd"
- : "ctrl";
-
- const commandKey = (): string =>
- getConfig.quickRestart === "esc" ? "tab" : "esc";
-
return (
-
- tab + enter - restart test
- >
- }
- else={
- <>
- {getConfig.quickRestart} - restart test
- >
- }
- />
-
- {commandKey()} or {modifierKey} + shift{" "}
- + p - command line
+
+
+ - restart test
+
+
+
+
+ - command line
+
);
diff --git a/frontend/src/ts/components/layout/header/Logo.tsx b/frontend/src/ts/components/layout/header/Logo.tsx
index 41127879c8c2..e985eb8f8cd5 100644
--- a/frontend/src/ts/components/layout/header/Logo.tsx
+++ b/frontend/src/ts/components/layout/header/Logo.tsx
@@ -1,10 +1,7 @@
import { JSXElement } from "solid-js";
-import {
- restartTestEvent,
- getActivePage,
- getFocus,
-} from "../../../states/core";
+import { restartTestEvent } from "../../../events/test";
+import { getActivePage, getFocus } from "../../../states/core";
import { cn } from "../../../utils/cn";
import { isDevEnvironment } from "../../../utils/env";
diff --git a/frontend/src/ts/components/layout/header/Nav.tsx b/frontend/src/ts/components/layout/header/Nav.tsx
index fbebe1e954d0..641d6981f1e1 100644
--- a/frontend/src/ts/components/layout/header/Nav.tsx
+++ b/frontend/src/ts/components/layout/header/Nav.tsx
@@ -1,17 +1,14 @@
import { useQuery } from "@tanstack/solid-query";
import { createMemo, JSXElement, Show } from "solid-js";
+import { restartTestEvent } from "../../../events/test";
import { createEffectOn } from "../../../hooks/effects";
import {
prefetchAboutPage,
prefetchLeaderboardPage,
} from "../../../queries/prefetch";
import { getServerConfigurationQueryOptions } from "../../../queries/server-configuration";
-import {
- restartTestEvent,
- getActivePage,
- getFocus,
-} from "../../../states/core";
+import { getActivePage, getFocus } from "../../../states/core";
import {
getAccountButtonSpinner,
getAnimatedLevel,
diff --git a/frontend/src/ts/components/mount.tsx b/frontend/src/ts/components/mount.tsx
index 7e96184bfe23..206bc9eeec84 100644
--- a/frontend/src/ts/components/mount.tsx
+++ b/frontend/src/ts/components/mount.tsx
@@ -6,6 +6,7 @@ import { queryClient } from "../queries";
import { qsa } from "../utils/dom";
import { DevTools } from "./core/DevTools";
import { Theme } from "./core/Theme";
+import { CommandlineHotkey } from "./hotkeys/CommandlineHotkey";
import { Footer } from "./layout/footer/Footer";
import { Header } from "./layout/header/Header";
import { Overlays } from "./layout/overlays/Overlays";
@@ -32,6 +33,7 @@ const components: Record JSXElement> = {
theme: () => ,
header: () => ,
devtools: () => ,
+ commandlinehotkey: () => ,
};
function mountToMountpoint(name: string, component: () => JSXElement): void {
diff --git a/frontend/src/ts/components/pages/AboutPage.tsx b/frontend/src/ts/components/pages/AboutPage.tsx
index f14caf161fb8..b365c71dc9f3 100644
--- a/frontend/src/ts/components/pages/AboutPage.tsx
+++ b/frontend/src/ts/components/pages/AboutPage.tsx
@@ -17,6 +17,8 @@ import { Button } from "../common/Button";
import { ChartJs } from "../common/ChartJs";
import { Fa } from "../common/Fa";
import { H2, H3 } from "../common/Headers";
+import { CommandlineHotkey } from "../hotkeys/CommandlineHotkey";
+import { QuickRestartHotkey } from "../hotkeys/QuickRestartHotkey";
export function AboutPage(): JSXElement {
const isOpen = () => getActivePage() === "about";
@@ -201,10 +203,8 @@ export function AboutPage(): JSXElement {
- You can use tab and enter (or just{" "}
- tab if you have quick tab mode enabled) to restart the
- typing test. Open the command line by pressing ctrl/cmd +{" "}
- shift + p or esc - there you can
+ You can use to restart the typing test. Open
+ the command line by pressing - there you can
access all the functionality you need without touching your mouse.
diff --git a/frontend/src/ts/elements/modes-notice.ts b/frontend/src/ts/elements/modes-notice.ts
index 6f670371044c..e763f0f04168 100644
--- a/frontend/src/ts/elements/modes-notice.ts
+++ b/frontend/src/ts/elements/modes-notice.ts
@@ -12,6 +12,7 @@ import Format from "../singletons/format";
import { getActiveFunboxes, getActiveFunboxNames } from "../test/funbox/list";
import { escapeHTML, getMode2 } from "../utils/misc";
import { qsr } from "../utils/dom";
+import { wordsHasNewline, wordsHasTab } from "../states/test";
configEvent.subscribe(({ key }) => {
const configKeys: ConfigEventKey[] = [
@@ -56,28 +57,28 @@ export async function update(): Promise {
);
}
- if (TestWords.hasTab) {
+ if (wordsHasTab()) {
if (Config.quickRestart === "esc") {
testModesNotice.appendHtml(
- `shift + tab to open commandline
`,
+ `shift + tab to open commandline
`,
);
testModesNotice.appendHtml(
- `shift + esc to restart
`,
+ `esc to restart
`,
);
}
if (Config.quickRestart === "tab") {
testModesNotice.appendHtml(
- `shift + tab to restart
`,
+ `shift + tab to restart
`,
);
}
}
if (
- (TestWords.hasNewline || Config.funbox.includes("58008")) &&
+ (wordsHasNewline() || Config.funbox.includes("58008")) &&
Config.quickRestart === "enter"
) {
testModesNotice.appendHtml(
- `shift + enter to restart
`,
+ `shift + enter to restart
`,
);
}
@@ -87,7 +88,7 @@ export async function update(): Promise {
testModesNotice.appendHtml(
`${escapeHTML(
customTextName,
- )} (shift + enter to save progress)
`,
+ )} (shift + enter to save progress)`,
);
}
@@ -99,7 +100,7 @@ export async function update(): Promise {
if (Config.mode === "zen") {
testModesNotice.appendHtml(
- `shift + enter to finish zen
`,
+ `shift + enter to finish zen
`,
);
}
diff --git a/frontend/src/ts/event-handlers/global.ts b/frontend/src/ts/event-handlers/global.ts
index 34262c190d5c..a4bec8b27c3f 100644
--- a/frontend/src/ts/event-handlers/global.ts
+++ b/frontend/src/ts/event-handlers/global.ts
@@ -1,14 +1,10 @@
import * as Misc from "../utils/misc";
import * as PageTransition from "../legacy-states/page-transition";
import { Config } from "../config/store";
-import * as TestWords from "../test/test-words";
-import * as Commandline from "../commandline/commandline";
import { showErrorNotification } from "../states/notifications";
import { getActivePage } from "../states/core";
import { ModifierKeys } from "../constants/modifier-keys";
import { focusWords } from "../test/test-ui";
-import * as TestLogic from "../test/test-logic";
-import { navigate } from "../controllers/route-controller";
import { isInputElementFocused } from "../input/input-element";
import * as TestState from "../test/test-state";
import { isDevEnvironment } from "../utils/env";
@@ -35,52 +31,6 @@ document.addEventListener("keydown", (e) => {
}
}
}
-
- if (
- (e.key === "Escape" && Config.quickRestart !== "esc") ||
- (e.key === "Tab" &&
- Config.quickRestart === "esc" &&
- !TestWords.hasTab &&
- !e.shiftKey) ||
- (e.key === "Tab" &&
- Config.quickRestart === "esc" &&
- TestWords.hasTab &&
- e.shiftKey) ||
- (e.key.toLowerCase() === "p" && (e.metaKey || e.ctrlKey) && e.shiftKey)
- ) {
- const popupVisible = Misc.isAnyPopupVisible();
- if (!popupVisible) {
- e.preventDefault();
- Commandline.show();
- }
- }
-
- if (!isInputElementFocused()) {
- const isInteractiveElement =
- document.activeElement?.tagName === "INPUT" ||
- document.activeElement?.tagName === "TEXTAREA" ||
- document.activeElement?.tagName === "SELECT" ||
- document.activeElement?.tagName === "BUTTON" ||
- document.activeElement?.classList.contains("button") === true ||
- document.activeElement?.classList.contains("textButton") === true;
-
- if (
- (e.key === "Tab" &&
- Config.quickRestart === "tab" &&
- !isInteractiveElement) ||
- (e.key === "Escape" && Config.quickRestart === "esc") ||
- (e.key === "Enter" &&
- Config.quickRestart === "enter" &&
- !isInteractiveElement)
- ) {
- e.preventDefault();
- if (getActivePage() === "test") {
- TestLogic.restart({ isQuickRestart: !e.shiftKey });
- } else {
- void navigate("");
- }
- }
- }
});
//stop space scrolling
diff --git a/frontend/src/ts/events/test.ts b/frontend/src/ts/events/test.ts
new file mode 100644
index 000000000000..9f3519199b41
--- /dev/null
+++ b/frontend/src/ts/events/test.ts
@@ -0,0 +1,5 @@
+import { createEvent } from "../hooks/createEvent";
+
+export const restartTestEvent = createEvent<
+ { isQuickRestart?: boolean } | undefined
+>();
diff --git a/frontend/src/ts/index.ts b/frontend/src/ts/index.ts
index 3dfc3859b2d0..d6b4a96dbe5a 100644
--- a/frontend/src/ts/index.ts
+++ b/frontend/src/ts/index.ts
@@ -46,6 +46,8 @@ import "./ready";
import { setVersion } from "./states/core";
import { loadFromLocalStorage } from "./config/lifecycle";
+import "./input/hotkeys";
+
// Lock Math.random
Object.defineProperty(Math, "random", {
value: Math.random,
diff --git a/frontend/src/ts/input/handlers/before-insert-text.ts b/frontend/src/ts/input/handlers/before-insert-text.ts
index d06b90e35da1..692da300a832 100644
--- a/frontend/src/ts/input/handlers/before-insert-text.ts
+++ b/frontend/src/ts/input/handlers/before-insert-text.ts
@@ -9,6 +9,7 @@ import { getInputElementValue } from "../input-element";
import { isAwaitingNextWord } from "../state";
import { shouldInsertSpaceCharacter } from "../helpers/validation";
import * as SlowTimer from "../../legacy-states/slow-timer";
+import { wordsHasNewline } from "../../states/test";
/**
* Handles logic before inserting text into the input element.
@@ -53,7 +54,7 @@ export function onBeforeInsertText(data: string): boolean {
}
//only allow newlines if the test has newlines or in zen mode
- if (data === "\n" && !TestWords.hasNewline && Config.mode !== "zen") {
+ if (data === "\n" && !wordsHasNewline() && Config.mode !== "zen") {
return true;
}
diff --git a/frontend/src/ts/input/handlers/keydown.ts b/frontend/src/ts/input/handlers/keydown.ts
index d27373dff7ff..af206c3f731f 100644
--- a/frontend/src/ts/input/handlers/keydown.ts
+++ b/frontend/src/ts/input/handlers/keydown.ts
@@ -5,7 +5,6 @@ import { getCharFromEvent } from "../../test/layout-emulator";
import * as Monkey from "../../test/monkey";
import { emulateInsertText } from "./insert-text";
import * as TestState from "../../test/test-state";
-import * as TestWords from "../../test/test-words";
import * as JSONData from "../../utils/json-data";
import {
showNoticeNotification,
@@ -26,16 +25,10 @@ import {
getActiveFunboxNames,
} from "../../test/funbox/list";
import { Keycode } from "../../constants/keys";
+import { wordsHasTab } from "../../states/test";
export async function handleTab(e: KeyboardEvent, now: number): Promise {
- if (Config.quickRestart === "tab") {
- e.preventDefault();
- if ((TestWords.hasTab && e.shiftKey) || !TestWords.hasTab) {
- TestLogic.restart({ isQuickRestart: !e.shiftKey });
- return;
- }
- }
- if (TestWords.hasTab) {
+ if (wordsHasTab()) {
await emulateInsertText({ data: "\t", now });
e.preventDefault();
return;
@@ -80,14 +73,6 @@ export async function handleEnter(
}
}
}
-
- if (Config.quickRestart === "enter") {
- e.preventDefault();
- if ((TestWords.hasNewline && e.shiftKey) || !TestWords.hasNewline) {
- TestLogic.restart({ isQuickRestart: !e.shiftKey });
- return;
- }
- }
}
export async function handleOppositeShift(event: KeyboardEvent): Promise {
@@ -192,10 +177,4 @@ export async function onKeydown(event: KeyboardEvent): Promise {
await handleEnter(event, now);
return;
}
-
- if (event.key === "Escape" && Config.quickRestart === "esc") {
- event.preventDefault();
- TestLogic.restart({ isQuickRestart: !event.shiftKey });
- return;
- }
}
diff --git a/frontend/src/ts/input/hotkeys/commandline.ts b/frontend/src/ts/input/hotkeys/commandline.ts
new file mode 100644
index 000000000000..a34711ecbc48
--- /dev/null
+++ b/frontend/src/ts/input/hotkeys/commandline.ts
@@ -0,0 +1,10 @@
+import { hotkeys } from "../../states/hotkeys";
+import { showModal } from "../../states/modals";
+import { createHotkey } from "./utils";
+
+function openCommandline(): void {
+ showModal("Commandline");
+}
+
+createHotkey(() => hotkeys.commandline, openCommandline);
+createHotkey("Mod+Shift+P", openCommandline);
diff --git a/frontend/src/ts/input/hotkeys/index.ts b/frontend/src/ts/input/hotkeys/index.ts
new file mode 100644
index 000000000000..2dd26876377a
--- /dev/null
+++ b/frontend/src/ts/input/hotkeys/index.ts
@@ -0,0 +1,3 @@
+import "./quickrestart";
+import "./commandline";
+import "./konami";
diff --git a/frontend/src/ts/input/hotkeys/konami.ts b/frontend/src/ts/input/hotkeys/konami.ts
new file mode 100644
index 000000000000..e4c496386913
--- /dev/null
+++ b/frontend/src/ts/input/hotkeys/konami.ts
@@ -0,0 +1,19 @@
+import { createHotkeySequence } from "@tanstack/solid-hotkeys";
+
+createHotkeySequence(
+ [
+ "ArrowUp",
+ "ArrowUp",
+ "ArrowDown",
+ "ArrowDown",
+ "ArrowLeft",
+ "ArrowRight",
+ "ArrowLeft",
+ "ArrowRight",
+ "B",
+ "A",
+ ],
+ () => {
+ window.open("https://keymash.io/", "_blank");
+ },
+);
diff --git a/frontend/src/ts/input/hotkeys/quickrestart.ts b/frontend/src/ts/input/hotkeys/quickrestart.ts
new file mode 100644
index 000000000000..eb0ca51c7869
--- /dev/null
+++ b/frontend/src/ts/input/hotkeys/quickrestart.ts
@@ -0,0 +1,21 @@
+import { isAnyPopupVisible } from "../../utils/misc";
+
+import { navigate } from "../../controllers/route-controller";
+import { restartTestEvent } from "../../events/test";
+import { getActivePage } from "../../states/core";
+import { hotkeys } from "../../states/hotkeys";
+import { createHotkey } from "./utils";
+
+function quickRestart(e: KeyboardEvent): void {
+ if (isAnyPopupVisible()) {
+ return;
+ }
+
+ if (getActivePage() === "test") {
+ restartTestEvent.dispatch({ isQuickRestart: !e.shiftKey });
+ } else {
+ void navigate("");
+ }
+}
+
+createHotkey(() => hotkeys.quickRestart, quickRestart);
diff --git a/frontend/src/ts/input/hotkeys/utils.ts b/frontend/src/ts/input/hotkeys/utils.ts
new file mode 100644
index 000000000000..d306d157bdc2
--- /dev/null
+++ b/frontend/src/ts/input/hotkeys/utils.ts
@@ -0,0 +1,57 @@
+import {
+ Hotkey,
+ HotkeyCallback,
+ HotkeyCallbackContext,
+ createHotkey as registerHotkey,
+} from "@tanstack/solid-hotkeys";
+import { isAnyPopupVisible } from "../../utils/misc";
+import { isInputElementFocused } from "../input-element";
+
+export function createHotkey(
+ hotkey: Hotkey | (() => Hotkey),
+ callback: HotkeyCallback,
+): void {
+ registerHotkey(
+ hotkey,
+ (e, context) => {
+ if (handleHotkeyOnInteractiveElement(e, context)) return;
+ e.stopPropagation();
+ e.preventDefault();
+ callback(e, context);
+ },
+ {
+ ignoreInputs: false, //hotkeys are active on the words input, but not on other interactive elements
+ stopPropagation: false, //we set stopPropagation in the callback if the hotkey executes
+ preventDefault: false, //we set preventDefault in the callback if the hotkey executes
+ requireReset: true,
+ conflictBehavior: "replace",
+ },
+ );
+}
+function isInteractiveElementFocused(): boolean {
+ if (isInputElementFocused()) return false;
+
+ return (
+ document.activeElement?.tagName === "INPUT" ||
+ document.activeElement?.tagName === "TEXTAREA" ||
+ document.activeElement?.tagName === "SELECT" ||
+ document.activeElement?.tagName === "BUTTON" ||
+ document.activeElement?.classList.contains("button") === true ||
+ document.activeElement?.classList.contains("textButton") === true
+ );
+}
+
+function handleHotkeyOnInteractiveElement(
+ e: KeyboardEvent,
+ { hotkey }: HotkeyCallbackContext,
+): boolean {
+ if (
+ (hotkey === "Tab" || hotkey === "Enter") &&
+ isInteractiveElementFocused()
+ ) {
+ return true;
+ } else if (hotkey === "Escape" && isAnyPopupVisible()) {
+ return true;
+ }
+ return false;
+}
diff --git a/frontend/src/ts/pages/settings.ts b/frontend/src/ts/pages/settings.ts
index 3cf3e11f6bc2..62fe8c4f5057 100644
--- a/frontend/src/ts/pages/settings.ts
+++ b/frontend/src/ts/pages/settings.ts
@@ -723,17 +723,6 @@ export async function update(
CustomBackgroundFilter.updateUI();
- const userAgent = window.navigator.userAgent.toLowerCase();
- const modifierKey =
- userAgent.includes("mac") && !userAgent.includes("firefox")
- ? "cmd"
- : "ctrl";
-
- const commandKey = Config.quickRestart === "esc" ? "tab" : "esc";
- qs(".pageSettings .tip")?.setHtml(`
- tip: You can also change all these settings quickly using the
- command line (${commandKey} or ${modifierKey} + shift + p)`);
-
if (
customLayoutFluidSelect !== undefined &&
//checking equal with order, because customLayoutFluid is ordered
diff --git a/frontend/src/ts/ready.ts b/frontend/src/ts/ready.ts
index 9acc9a79e53e..3f3c306a26cb 100644
--- a/frontend/src/ts/ready.ts
+++ b/frontend/src/ts/ready.ts
@@ -1,8 +1,6 @@
import * as Misc from "./utils/misc";
import * as MonkeyPower from "./elements/monkey-power";
import * as MerchBanner from "./elements/merch-banner";
-//@ts-expect-error no types for this package
-import Konami from "konami";
import * as ServerConfiguration from "./ape/server-configuration";
import { configLoadPromise } from "./config/lifecycle";
import { authPromise } from "./firebase";
@@ -32,10 +30,6 @@ onDOMReady(async () => {
MonkeyPower.init();
- // untyped, need to ignore
- // oxlint-disable-next-line no-unsafe-call
- new Konami("https://keymash.io/");
-
if (isDevEnvironment()) {
void navigator.serviceWorker
.getRegistrations()
diff --git a/frontend/src/ts/states/core.ts b/frontend/src/ts/states/core.ts
index 00695db0a8cc..4fdd2803e925 100644
--- a/frontend/src/ts/states/core.ts
+++ b/frontend/src/ts/states/core.ts
@@ -1,5 +1,4 @@
import { createSignal } from "solid-js";
-import { createEvent } from "../hooks/createEvent";
import { PageName } from "../pages/page";
export const [getActivePage, setActivePage] = createSignal("loading");
@@ -37,5 +36,3 @@ export const isLoggedIn = (): boolean => getUserId() !== null;
export const [getSelectedProfileName, setSelectedProfileName] = createSignal<
string | undefined
>(undefined);
-
-export const restartTestEvent = createEvent();
diff --git a/frontend/src/ts/states/hotkeys.ts b/frontend/src/ts/states/hotkeys.ts
new file mode 100644
index 000000000000..978e40f1b494
--- /dev/null
+++ b/frontend/src/ts/states/hotkeys.ts
@@ -0,0 +1,59 @@
+import { Config, QuickRestart } from "@monkeytype/schemas/configs";
+import { Hotkey } from "@tanstack/solid-hotkeys";
+import { createEffect } from "solid-js";
+import { createStore } from "solid-js/store";
+import { getConfig } from "../config/store";
+import { wordsHasNewline, wordsHasTab } from "./test";
+import { getActivePage } from "./core";
+
+const hotkeyMapping: Record = {
+ off: "Meta+Mod+Alt+Shift+F22" as Hotkey, //Dummy
+ esc: "Escape",
+ tab: "Tab",
+ enter: "Enter",
+};
+
+type Hotkeys = {
+ quickRestart: Hotkey;
+ commandline: Hotkey;
+};
+
+export const [hotkeys, setHotkeys] = createStore(
+ calcHotkeys(getConfig, {
+ shiftTab: false,
+ shiftEnter: false,
+ }),
+);
+
+createEffect(() => {
+ const isOnTestPage = getActivePage() === "test";
+ setHotkeys(
+ calcHotkeys(getConfig, {
+ shiftTab: isOnTestPage && wordsHasTab(),
+ shiftEnter: isOnTestPage && wordsHasNewline(),
+ }),
+ );
+});
+
+function calcHotkeys(
+ config: Config,
+ options: { shiftTab: boolean; shiftEnter: boolean },
+): Hotkeys {
+ return {
+ quickRestart: shiftedHotkey(hotkeyMapping[config.quickRestart], options),
+ commandline: shiftedHotkey(
+ getConfig.quickRestart === "esc" ? "Tab" : "Escape",
+ options,
+ ),
+ };
+}
+
+function shiftedHotkey(
+ hotkey: Hotkey,
+ options: { shiftTab: boolean; shiftEnter: boolean },
+): Hotkey {
+ if (hotkey === "Tab" && options.shiftTab) return "Shift+Tab";
+ if (hotkey === "Enter" && options.shiftEnter) return "Shift+Enter";
+
+ return hotkey;
+}
diff --git a/frontend/src/ts/states/test.ts b/frontend/src/ts/states/test.ts
new file mode 100644
index 000000000000..197a99da0dc7
--- /dev/null
+++ b/frontend/src/ts/states/test.ts
@@ -0,0 +1,4 @@
+import { createSignal } from "solid-js";
+
+export const [wordsHasNewline, setWordsHasNewline] = createSignal(false);
+export const [wordsHasTab, setWordsHasTab] = createSignal(false);
diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts
index 8db390113010..c8956eb8e182 100644
--- a/frontend/src/ts/test/test-logic.ts
+++ b/frontend/src/ts/test/test-logic.ts
@@ -26,7 +26,8 @@ import * as TodayTracker from "./today-tracker";
import * as ChallengeContoller from "../controllers/challenge-controller";
import * as QuoteRateModal from "../modals/quote-rate";
import * as Result from "./result";
-import { getActivePage, restartTestEvent } from "../states/core";
+import { getActivePage } from "../states/core";
+import { restartTestEvent } from "../events/test";
import * as TestInput from "./test-input";
import * as TestWords from "./test-words";
import * as WordsGenerator from "./words-generator";
@@ -72,6 +73,7 @@ import { qs } from "../utils/dom";
import { setAccountButtonSpinner } from "../states/header";
import { Config } from "../config/store";
import { setQuoteLengthAll, toggleFunbox, setConfig } from "../config/setters";
+import { setWordsHasNewline, setWordsHasTab } from "../states/test";
let failReason = "";
export async function syncNotSignedInLastResult(uid: string): Promise {
@@ -549,8 +551,8 @@ async function init(): Promise {
}
TestWords.setHasNumbers(hasNumbers);
- TestWords.setHasTab(wordsHaveTab);
- TestWords.setHasNewline(wordsHaveNewline);
+ setWordsHasTab(wordsHaveTab);
+ setWordsHasNewline(wordsHaveNewline);
if (
generatedWords
@@ -1432,7 +1434,7 @@ qs(".pageTest")?.onChild("click", "#restartTestButtonWithSameWordset", () => {
});
});
-restartTestEvent.subscribe(() => restart());
+restartTestEvent.subscribe((event) => restart(event));
qs(".pageTest")?.onChild("click", "#testConfig .mode .textButton", (e) => {
if (TestState.testRestarting) return;
diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts
index 5bd1f63d7463..808cb027c540 100644
--- a/frontend/src/ts/test/test-ui.ts
+++ b/frontend/src/ts/test/test-ui.ts
@@ -69,6 +69,7 @@ import {
} from "../utils/dom";
import { getTheme } from "../states/theme";
import { skipBreakdownEvent } from "../states/header";
+import { wordsHasNewline } from "../states/test";
export const updateHintsPositionDebounced = Misc.debounceUntilResolved(
updateHintsPosition,
@@ -658,7 +659,7 @@ export function updateWordsWrapperHeight(force = false): void {
wordsWrapperEl.setStyle({ height: wrapperHeight + "px" });
} else {
//show 3 lines if tape mode is on and has newlines, otherwise use words height (because of indicate typos: below)
- if (TestWords.hasNewline) {
+ if (wordsHasNewline()) {
wordsWrapperEl.setStyle({ height: wordHeight * 3 + "px" });
} else {
const wordsHeight = wordsEl.getOffsetHeight() ?? wordHeight;
diff --git a/frontend/src/ts/test/test-words.ts b/frontend/src/ts/test/test-words.ts
index 4b87130c9ea5..1f618e0fd0fe 100644
--- a/frontend/src/ts/test/test-words.ts
+++ b/frontend/src/ts/test/test-words.ts
@@ -57,8 +57,6 @@ class Words {
}
export const words = new Words();
-export let hasTab = false;
-export let hasNewline = false;
export let hasNumbers = false;
export let currentQuote = null as QuoteWithTextSplit | null;
@@ -66,14 +64,6 @@ export function setCurrentQuote(rq: QuoteWithTextSplit | null): void {
currentQuote = rq;
}
-export function setHasTab(tf: boolean): void {
- hasTab = tf;
-}
-
-export function setHasNewline(tf: boolean): void {
- hasNewline = tf;
-}
-
export function setHasNumbers(tf: boolean): void {
hasNumbers = tf;
}
diff --git a/frontend/src/ts/utils/misc.ts b/frontend/src/ts/utils/misc.ts
index 631ffe597dd1..7428c0d6fca9 100644
--- a/frontend/src/ts/utils/misc.ts
+++ b/frontend/src/ts/utils/misc.ts
@@ -685,6 +685,11 @@ export function isMacLike(): boolean {
return isPlatform(/Mac|iPod|iPhone|iPad/);
}
+export function isFirefox(): boolean {
+ const userAgent = window.navigator.userAgent.toLowerCase();
+ return userAgent.includes("firefox");
+}
+
export function scrollToCenterOrTop(el: HTMLElement | null): void {
if (!el) return;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 462f6b2ce6b7..e181bef19bec 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -312,9 +312,18 @@ importers:
'@tanstack/solid-db':
specifier: 0.2.10
version: 0.2.10(solid-js@1.9.10)(typescript@6.0.0-beta)
+ '@tanstack/solid-devtools':
+ specifier: 0.8.0
+ version: 0.8.0(csstype@3.2.3)(solid-js@1.9.10)
'@tanstack/solid-form':
specifier: 1.28.4
version: 1.28.4(solid-js@1.9.10)
+ '@tanstack/solid-hotkeys':
+ specifier: 0.4.2
+ version: 0.4.2(solid-js@1.9.10)
+ '@tanstack/solid-hotkeys-devtools':
+ specifier: 0.4.3
+ version: 0.4.3(@tanstack/hotkeys@0.4.2)(@types/react@19.2.14)(csstype@3.2.3)(react@18.3.1)(solid-js@1.9.10)
'@tanstack/solid-query':
specifier: 5.90.23
version: 5.90.23(solid-js@1.9.10)
@@ -375,9 +384,6 @@ importers:
idb:
specifier: 8.0.3
version: 8.0.3
- konami:
- specifier: 1.7.0
- version: 1.7.0
lz-ts:
specifier: 1.1.2
version: 1.1.2
@@ -649,7 +655,7 @@ importers:
version: 6.0.0-beta
vitest:
specifier: 4.1.0
- version: 4.1.0(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@8.0.1(@types/node@24.9.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
+ version: 4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@8.0.1(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
packages/funbox:
dependencies:
@@ -3758,10 +3764,53 @@ packages:
peerDependencies:
typescript: '>=4.7'
+ '@tanstack/devtools-client@0.0.6':
+ resolution: {integrity: sha512-f85ZJXJnDIFOoykG/BFIixuAevJovCvJF391LPs6YjBAPhGYC50NWlx1y4iF/UmK5/cCMx+/JqI5SBOz7FanQQ==}
+ engines: {node: '>=18'}
+
+ '@tanstack/devtools-event-bus@0.4.1':
+ resolution: {integrity: sha512-cNnJ89Q021Zf883rlbBTfsaxTfi2r73/qejGtyTa7ksErF3hyDyAq1aTbo5crK9dAL7zSHh9viKY1BtMls1QOA==}
+ engines: {node: '>=18'}
+
'@tanstack/devtools-event-client@0.4.1':
resolution: {integrity: sha512-GRxmPw4OHZ2oZeIEUkEwt/NDvuEqzEYRAjzUVMs+I0pd4C7k1ySOiuJK2CqF+K/yEAR3YZNkW3ExrpDarh9Vwg==}
engines: {node: '>=18'}
+ '@tanstack/devtools-ui@0.5.1':
+ resolution: {integrity: sha512-T9JjAdqMSnxsVO6AQykD5vhxPF4iFLKtbYxee/bU3OLlk446F5C1220GdCmhDSz7y4lx+m8AvIS0bq6zzvdDUA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ solid-js: '>=1.9.7'
+
+ '@tanstack/devtools-utils@0.4.0':
+ resolution: {integrity: sha512-KsGzYhA8L/fCNgyyMyoUy+TKtx+DjNbzWwqH6wXL48Llzo7kvV9RynYJlaO8Qkzwm+NdHXSgsljQNjQ3CKPpZA==}
+ engines: {node: '>=18'}
+ hasBin: true
+ peerDependencies:
+ '@types/react': '>=17.0.0'
+ preact: '>=10.0.0'
+ react: '>=17.0.0'
+ solid-js: '>=1.9.7'
+ vue: '>=3.2.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ preact:
+ optional: true
+ react:
+ optional: true
+ solid-js:
+ optional: true
+ vue:
+ optional: true
+
+ '@tanstack/devtools@0.11.0':
+ resolution: {integrity: sha512-ARRAnEm0HYjKlB2adC9YyDG3fbq5LVjpxPe6Jz583SanXRM1aKrZIGHIA//oRldX3mWIpM4kB6mCyd+CXCLqhA==}
+ engines: {node: '>=18'}
+ hasBin: true
+ peerDependencies:
+ solid-js: '>=1.9.7'
+
'@tanstack/eslint-plugin-query@5.91.4':
resolution: {integrity: sha512-8a+GAeR7oxJ5laNyYBQ6miPK09Hi18o5Oie/jx8zioXODv/AUFLZQecKabPdpQSLmuDXEBPKFh+W5DKbWlahjQ==}
peerDependencies:
@@ -3774,6 +3823,16 @@ packages:
'@tanstack/form-core@1.28.4':
resolution: {integrity: sha512-2eox5ePrJ6kvA1DXD5QHk/GeGr3VFZ0uYR63UgQOe7bUg6h1JfXaIMqTjZK9sdGyE4oRNqFpoW54H0pZM7nObQ==}
+ '@tanstack/hotkeys-devtools@0.4.3':
+ resolution: {integrity: sha512-PdIQBau5qUHZnDi1jIeIyCBcT/ALW5FvpsWp5GD6PnIhdvseGnMfzLLAvZkc8zrD1eb6pjDRcwpsPY9ITZ0/3w==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@tanstack/hotkeys': 0.4.2
+
+ '@tanstack/hotkeys@0.4.2':
+ resolution: {integrity: sha512-dCCu6Q91wZ2Mz7Vb+tzzpbKH0cSY9JXqJS7ZyouxewNL8oVmI228P9BmP94/1255g5WjPS+njenyrbWVeEQP5Q==}
+ engines: {node: '>=18'}
+
'@tanstack/pacer-lite@0.1.1':
resolution: {integrity: sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w==}
engines: {node: '>=18'}
@@ -3799,11 +3858,29 @@ packages:
peerDependencies:
solid-js: '>=1.9.0'
+ '@tanstack/solid-devtools@0.8.0':
+ resolution: {integrity: sha512-mJn8j6DYvyayyweeLbdaHwjhKFB1lpCU6cN1+KygluLX6etmrBBC/oDMKQTTbnrQk6EbI9Xa9kmyOREroIaXzA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ solid-js: '>=1.9.7'
+
'@tanstack/solid-form@1.28.4':
resolution: {integrity: sha512-ZhA/oQQJzfY0Un5XFiHbfCwuWyShATHIajdx6xjcm34g0xpwoioGyxAPm7pXLpnUAzln0liCkKX+L77rKj3/6A==}
peerDependencies:
solid-js: '>=1.9.9'
+ '@tanstack/solid-hotkeys-devtools@0.4.3':
+ resolution: {integrity: sha512-iP0u4BxkcPQeB2bhWzrO/ZhFrsazB9cAa9xq/6+q4GnC5c/jQQ15yDxp1UEeP4BLVX3kQ4cwpkHG9CVkopYz/A==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ solid-js: '>=1.7.0'
+
+ '@tanstack/solid-hotkeys@0.4.2':
+ resolution: {integrity: sha512-HelOz/mnCZ6Q5n4udwVr/U6o7gEWt8mIKdzxxsMKS42nLK64moTZrO5FLFTsiCgUZS9Vm2N14mQ+6a7ha3Y0yQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ solid-js: '>=1.7.0'
+
'@tanstack/solid-query-devtools@5.91.3':
resolution: {integrity: sha512-xzVwIIxQPbiublZP3RkGp8KVjt8zenv5y1YTSRarP32mLUHJgfdofvjsDvMEhhL/lomz90qa0jCIGsSxoSTyYQ==}
peerDependencies:
@@ -5387,6 +5464,9 @@ packages:
date-fns@3.6.0:
resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
+ dayjs@1.11.20:
+ resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==}
+
debug@2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies:
@@ -6378,6 +6458,11 @@ packages:
engines: {node: '>=0.6.0'}
hasBin: true
+ goober@2.1.18:
+ resolution: {integrity: sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==}
+ peerDependencies:
+ csstype: ^3.0.10
+
google-auth-library@9.12.0:
resolution: {integrity: sha512-5pWjpxJMNJ5UTuhK7QPD5KFPsbosWkX4ajMDeZwXllTtwwqeiIzPWbHIddkLBkkn0mUPboTmukT5rd30Ec9igQ==}
engines: {node: '>=14'}
@@ -7156,9 +7241,6 @@ packages:
known-css-properties@0.30.0:
resolution: {integrity: sha512-VSWXYUnsPu9+WYKkfmJyLKtIvaRJi1kXUqVmBACORXZQxT5oZDsoZ2vQP+bQFDnWtpI/4eq3MLoRMjI2fnLzTQ==}
- konami@1.7.0:
- resolution: {integrity: sha512-lfw/bXyPcOYVP0Vt05PKVYWp/Rx92eUIg48Kb2jkY2Ko00jWMTzk6jt5bRdkVRfvPr9RLTl1DM6vVrjWLXnQyg==}
-
kuler@2.0.0:
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
@@ -13928,8 +14010,65 @@ snapshots:
'@tanstack/pacer-lite': 0.2.1
typescript: 6.0.0-beta
+ '@tanstack/devtools-client@0.0.6':
+ dependencies:
+ '@tanstack/devtools-event-client': 0.4.1
+
+ '@tanstack/devtools-event-bus@0.4.1':
+ dependencies:
+ ws: 8.19.0
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
'@tanstack/devtools-event-client@0.4.1': {}
+ '@tanstack/devtools-ui@0.5.1(csstype@3.2.3)(solid-js@1.9.10)':
+ dependencies:
+ clsx: 2.1.1
+ dayjs: 1.11.20
+ goober: 2.1.18(csstype@3.2.3)
+ solid-js: 1.9.10
+ transitivePeerDependencies:
+ - csstype
+
+ '@tanstack/devtools-ui@0.5.1(csstype@3.2.3)(solid-js@1.9.11)':
+ dependencies:
+ clsx: 2.1.1
+ dayjs: 1.11.20
+ goober: 2.1.18(csstype@3.2.3)
+ solid-js: 1.9.11
+ transitivePeerDependencies:
+ - csstype
+
+ '@tanstack/devtools-utils@0.4.0(@types/react@19.2.14)(react@18.3.1)(solid-js@1.9.10)':
+ optionalDependencies:
+ '@types/react': 19.2.14
+ react: 18.3.1
+ solid-js: 1.9.10
+
+ '@tanstack/devtools-utils@0.4.0(@types/react@19.2.14)(react@18.3.1)(solid-js@1.9.11)':
+ optionalDependencies:
+ '@types/react': 19.2.14
+ react: 18.3.1
+ solid-js: 1.9.11
+
+ '@tanstack/devtools@0.11.0(csstype@3.2.3)(solid-js@1.9.10)':
+ dependencies:
+ '@solid-primitives/event-listener': 2.4.5(solid-js@1.9.10)
+ '@solid-primitives/keyboard': 1.3.5(solid-js@1.9.10)
+ '@solid-primitives/resize-observer': 2.1.5(solid-js@1.9.10)
+ '@tanstack/devtools-client': 0.0.6
+ '@tanstack/devtools-event-bus': 0.4.1
+ '@tanstack/devtools-ui': 0.5.1(csstype@3.2.3)(solid-js@1.9.10)
+ clsx: 2.1.1
+ goober: 2.1.18(csstype@3.2.3)
+ solid-js: 1.9.10
+ transitivePeerDependencies:
+ - bufferutil
+ - csstype
+ - utf-8-validate
+
'@tanstack/eslint-plugin-query@5.91.4(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.0-beta)':
dependencies:
'@typescript-eslint/utils': 8.52.0(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.0-beta)
@@ -13945,6 +14084,25 @@ snapshots:
'@tanstack/pacer-lite': 0.1.1
'@tanstack/store': 0.9.2
+ '@tanstack/hotkeys-devtools@0.4.3(@tanstack/hotkeys@0.4.2)(@types/react@19.2.14)(csstype@3.2.3)(react@18.3.1)':
+ dependencies:
+ '@tanstack/devtools-ui': 0.5.1(csstype@3.2.3)(solid-js@1.9.11)
+ '@tanstack/devtools-utils': 0.4.0(@types/react@19.2.14)(react@18.3.1)(solid-js@1.9.11)
+ '@tanstack/hotkeys': 0.4.2
+ clsx: 2.1.1
+ goober: 2.1.18(csstype@3.2.3)
+ solid-js: 1.9.11
+ transitivePeerDependencies:
+ - '@types/react'
+ - csstype
+ - preact
+ - react
+ - vue
+
+ '@tanstack/hotkeys@0.4.2':
+ dependencies:
+ '@tanstack/store': 0.9.2
+
'@tanstack/pacer-lite@0.1.1': {}
'@tanstack/pacer-lite@0.2.1': {}
@@ -13968,12 +14126,40 @@ snapshots:
transitivePeerDependencies:
- typescript
+ '@tanstack/solid-devtools@0.8.0(csstype@3.2.3)(solid-js@1.9.10)':
+ dependencies:
+ '@tanstack/devtools': 0.11.0(csstype@3.2.3)(solid-js@1.9.10)
+ solid-js: 1.9.10
+ transitivePeerDependencies:
+ - bufferutil
+ - csstype
+ - utf-8-validate
+
'@tanstack/solid-form@1.28.4(solid-js@1.9.10)':
dependencies:
'@tanstack/form-core': 1.28.4
'@tanstack/solid-store': 0.9.2(solid-js@1.9.10)
solid-js: 1.9.10
+ '@tanstack/solid-hotkeys-devtools@0.4.3(@tanstack/hotkeys@0.4.2)(@types/react@19.2.14)(csstype@3.2.3)(react@18.3.1)(solid-js@1.9.10)':
+ dependencies:
+ '@tanstack/devtools-utils': 0.4.0(@types/react@19.2.14)(react@18.3.1)(solid-js@1.9.10)
+ '@tanstack/hotkeys-devtools': 0.4.3(@tanstack/hotkeys@0.4.2)(@types/react@19.2.14)(csstype@3.2.3)(react@18.3.1)
+ solid-js: 1.9.10
+ transitivePeerDependencies:
+ - '@tanstack/hotkeys'
+ - '@types/react'
+ - csstype
+ - preact
+ - react
+ - vue
+
+ '@tanstack/solid-hotkeys@0.4.2(solid-js@1.9.10)':
+ dependencies:
+ '@tanstack/hotkeys': 0.4.2
+ '@tanstack/solid-store': 0.9.2(solid-js@1.9.10)
+ solid-js: 1.9.10
+
'@tanstack/solid-query-devtools@5.91.3(@tanstack/solid-query@5.90.23(solid-js@1.9.10))(solid-js@1.9.10)':
dependencies:
'@tanstack/query-devtools': 5.93.0
@@ -14545,6 +14731,14 @@ snapshots:
optionalDependencies:
vite: 8.0.1(@types/node@20.5.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
+ '@vitest/mocker@4.1.0(vite@8.0.1(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))':
+ dependencies:
+ '@vitest/spy': 4.1.0
+ estree-walker: 3.0.3
+ magic-string: 0.30.21
+ optionalDependencies:
+ vite: 8.0.1(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
+
'@vitest/mocker@4.1.0(vite@8.0.1(@types/node@24.9.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))':
dependencies:
'@vitest/spy': 4.1.0
@@ -15795,6 +15989,8 @@ snapshots:
date-fns@3.6.0: {}
+ dayjs@1.11.20: {}
+
debug@2.6.9:
dependencies:
ms: 2.0.0
@@ -17187,6 +17383,10 @@ snapshots:
dependencies:
minimist: 1.2.8
+ goober@2.1.18(csstype@3.2.3):
+ dependencies:
+ csstype: 3.2.3
+
google-auth-library@9.12.0(encoding@0.1.13):
dependencies:
base64-js: 1.5.1
@@ -18037,8 +18237,6 @@ snapshots:
known-css-properties@0.30.0: {}
- konami@1.7.0: {}
-
kuler@2.0.0: {}
lazystream@1.0.1:
@@ -21687,6 +21885,23 @@ snapshots:
tsx: 4.21.0
yaml: 2.8.2
+ vite@8.0.1(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2):
+ dependencies:
+ lightningcss: 1.32.0
+ picomatch: 4.0.3
+ postcss: 8.5.8
+ rolldown: 1.0.0-rc.10
+ tinyglobby: 0.2.15
+ optionalDependencies:
+ '@types/node': 24.9.1
+ esbuild: 0.25.11
+ fsevents: 2.3.3
+ jiti: 2.6.1
+ sass: 1.98.0
+ terser: 5.46.1
+ tsx: 4.21.0
+ yaml: 2.8.2
+
vite@8.0.1(@types/node@24.9.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2):
dependencies:
lightningcss: 1.32.0
@@ -21818,6 +22033,35 @@ snapshots:
transitivePeerDependencies:
- msw
+ vitest@4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@8.0.1(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)):
+ dependencies:
+ '@vitest/expect': 4.1.0
+ '@vitest/mocker': 4.1.0(vite@8.0.1(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
+ '@vitest/pretty-format': 4.1.0
+ '@vitest/runner': 4.1.0
+ '@vitest/snapshot': 4.1.0
+ '@vitest/spy': 4.1.0
+ '@vitest/utils': 4.1.0
+ es-module-lexer: 2.0.0
+ expect-type: 1.3.0
+ magic-string: 0.30.21
+ obug: 2.1.1
+ pathe: 2.0.3
+ picomatch: 4.0.3
+ std-env: 4.0.0
+ tinybench: 2.9.0
+ tinyexec: 1.0.2
+ tinyglobby: 0.2.15
+ tinyrainbow: 3.0.3
+ vite: 8.0.1(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
+ why-is-node-running: 2.3.0
+ optionalDependencies:
+ '@types/node': 24.9.1
+ happy-dom: 20.0.10
+ jsdom: 27.4.0
+ transitivePeerDependencies:
+ - msw
+
vitest@4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@8.0.1(@types/node@24.9.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)):
dependencies:
'@vitest/expect': 4.1.0