diff --git a/frontend/package.json b/frontend/package.json
index 7962b632563e..f21c61bc9744 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 71abff85a54b..317e16f7b67e 100644
--- a/frontend/src/styles/test.scss
+++ b/frontend/src/styles/test.scss
@@ -1424,6 +1424,7 @@
.textButton {
padding: 0.5em 1em;
+ align-items: center;
&.noInteraction {
pointer-events: none;
}
diff --git a/frontend/src/ts/components/common/Kbd.tsx b/frontend/src/ts/components/common/Kbd.tsx
new file mode 100644
index 000000000000..787d39954480
--- /dev/null
+++ b/frontend/src/ts/components/common/Kbd.tsx
@@ -0,0 +1,16 @@
+import { formatWithLabels, Hotkey } from "@tanstack/solid-hotkeys";
+import { JSXElement } from "solid-js";
+
+type Props =
+ | { hotkey: Hotkey; text?: undefined }
+ | { hotkey?: undefined; text: string };
+
+export function Kbd(props: Props): JSXElement {
+ return (
+
+ {props.hotkey
+ ? formatWithLabels(props.hotkey).toLowerCase().replace(/\+/g, " + ")
+ : props.text}
+
+ );
+}
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..82b583b81a0b
--- /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 "../common/Kbd";
+
+export function CommandlineHotkey() {
+ return (
+ <>
+
+
+ or
+
+
+ >
+ );
+}
diff --git a/frontend/src/ts/components/hotkeys/QuickRestartHotkey.tsx b/frontend/src/ts/components/hotkeys/QuickRestartHotkey.tsx
new file mode 100644
index 000000000000..41669ea64d64
--- /dev/null
+++ b/frontend/src/ts/components/hotkeys/QuickRestartHotkey.tsx
@@ -0,0 +1,15 @@
+import { Hotkey } from "@tanstack/solid-hotkeys";
+import { JSXElement } from "solid-js";
+
+import { NoKey } from "../../input/hotkeys/utils";
+import { hotkeys } from "../../states/hotkeys";
+import { Kbd } from "../common/Kbd";
+
+export function QuickRestartHotkey(): JSXElement {
+ const props = (): { hotkey: Hotkey } | { text: string } =>
+ hotkeys.quickRestart !== NoKey
+ ? { hotkey: hotkeys.quickRestart }
+ : { text: "tab > enter" };
+
+ return ;
+}
diff --git a/frontend/src/ts/components/layout/footer/Footer.tsx b/frontend/src/ts/components/layout/footer/Footer.tsx
index 12b2f0265924..3b0484c4dff3 100644
--- a/frontend/src/ts/components/layout/footer/Footer.tsx
+++ b/frontend/src/ts/components/layout/footer/Footer.tsx
@@ -1,7 +1,8 @@
import { JSXElement } from "solid-js";
-import { getFocus, getIsScreenshotting } from "../../../states/core";
+import { getIsScreenshotting } from "../../../states/core";
import { showModal } from "../../../states/modals";
+import { getFocus } from "../../../states/test";
import { cn } from "../../../utils/cn";
import { Button } from "../../common/Button";
import { Keytips } from "./Keytips";
diff --git a/frontend/src/ts/components/layout/footer/Keytips.tsx b/frontend/src/ts/components/layout/footer/Keytips.tsx
index 3c703888f71f..e6be31744bbe 100644
--- a/frontend/src/ts/components/layout/footer/Keytips.tsx
+++ b/frontend/src/ts/components/layout/footer/Keytips.tsx
@@ -1,43 +1,28 @@
import { JSXElement, Show } from "solid-js";
import { getConfig } from "../../../config/store";
-import { getFocus } from "../../../states/core";
-import { Conditional } from "../../common/Conditional";
+import { getFocus } from "../../../states/test";
+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/AccountXpBar.tsx b/frontend/src/ts/components/layout/header/AccountXpBar.tsx
index 06988455bb60..c32a61a92692 100644
--- a/frontend/src/ts/components/layout/header/AccountXpBar.tsx
+++ b/frontend/src/ts/components/layout/header/AccountXpBar.tsx
@@ -11,12 +11,12 @@ import {
import { createEvent } from "../../../hooks/createEvent";
import { createSignalWithSetters } from "../../../hooks/createSignalWithSetters";
import { createEffectOn } from "../../../hooks/effects";
-import { getFocus } from "../../../states/core";
import {
skipBreakdownEvent,
getXpBarData,
setAnimatedLevel,
} from "../../../states/header";
+import { getFocus } from "../../../states/test";
import { getXpDetails } from "../../../utils/levels";
import { sleep } from "../../../utils/misc";
import { Anime, AnimePresence, AnimeShow } from "../../common/anime";
diff --git a/frontend/src/ts/components/layout/header/Header.tsx b/frontend/src/ts/components/layout/header/Header.tsx
index bfc07ae25672..18fca436f364 100644
--- a/frontend/src/ts/components/layout/header/Header.tsx
+++ b/frontend/src/ts/components/layout/header/Header.tsx
@@ -1,6 +1,7 @@
import { JSXElement } from "solid-js";
-import { getFocus, getIsScreenshotting } from "../../../states/core";
+import { getIsScreenshotting } from "../../../states/core";
+import { getFocus } from "../../../states/test";
import { cn } from "../../../utils/cn";
import { Logo } from "./Logo";
import { Nav } from "./Nav";
diff --git a/frontend/src/ts/components/layout/header/Logo.tsx b/frontend/src/ts/components/layout/header/Logo.tsx
index 41127879c8c2..65292a09daec 100644
--- a/frontend/src/ts/components/layout/header/Logo.tsx
+++ b/frontend/src/ts/components/layout/header/Logo.tsx
@@ -1,10 +1,8 @@
import { JSXElement } from "solid-js";
-import {
- restartTestEvent,
- getActivePage,
- getFocus,
-} from "../../../states/core";
+import { restartTestEvent } from "../../../events/test";
+import { getActivePage } from "../../../states/core";
+import { getFocus } from "../../../states/test";
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..cd536ca68133 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 } from "../../../states/core";
import {
getAccountButtonSpinner,
getAnimatedLevel,
@@ -19,6 +16,7 @@ import {
} from "../../../states/header";
import { showModal } from "../../../states/modals";
import { getSnapshot } from "../../../states/snapshot";
+import { getFocus } from "../../../states/test";
import { cn } from "../../../utils/cn";
import { getLevelFromTotalXp } from "../../../utils/levels";
import { AnimeConditional } from "../../common/anime";
diff --git a/frontend/src/ts/components/layout/overlays/Notifications.tsx b/frontend/src/ts/components/layout/overlays/Notifications.tsx
index 811cad8a6cd7..425e6ee8518d 100644
--- a/frontend/src/ts/components/layout/overlays/Notifications.tsx
+++ b/frontend/src/ts/components/layout/overlays/Notifications.tsx
@@ -1,17 +1,14 @@
import { AnimationParams } from "animejs";
import { For, JSXElement } from "solid-js";
-import {
- getFocus,
- getGlobalOffsetTop,
- getIsScreenshotting,
-} from "../../../states/core";
+import { getGlobalOffsetTop, getIsScreenshotting } from "../../../states/core";
import {
Notification,
getNotifications,
removeNotification,
clearAllNotifications,
} from "../../../states/notifications";
+import { getFocus } from "../../../states/test";
import { cn } from "../../../utils/cn";
import { Anime } from "../../common/anime/Anime";
import { AnimePresence } from "../../common/anime/AnimePresence";
diff --git a/frontend/src/ts/components/modals/CustomTestDurationModal.tsx b/frontend/src/ts/components/modals/CustomTestDurationModal.tsx
index 306098271e36..39156334f7ca 100644
--- a/frontend/src/ts/components/modals/CustomTestDurationModal.tsx
+++ b/frontend/src/ts/components/modals/CustomTestDurationModal.tsx
@@ -3,7 +3,7 @@ import { JSXElement } from "solid-js";
import { setConfig } from "../../config/setters";
import { getConfig } from "../../config/store";
-import { restartTestEvent } from "../../states/core";
+import { restartTestEvent } from "../../events/test";
import { hideModalAndClearChain } from "../../states/modals";
import { showNoticeNotification } from "../../states/notifications";
import { AnimatedModal } from "../common/AnimatedModal";
diff --git a/frontend/src/ts/components/modals/CustomTextModal.tsx b/frontend/src/ts/components/modals/CustomTextModal.tsx
index e9df706a54a6..efad688d7f52 100644
--- a/frontend/src/ts/components/modals/CustomTextModal.tsx
+++ b/frontend/src/ts/components/modals/CustomTextModal.tsx
@@ -7,8 +7,8 @@ import type { FaSolidIcon } from "../../types/font-awesome";
import { setConfig } from "../../config/setters";
import { Config } from "../../config/store";
+import { restartTestEvent } from "../../events/test";
import * as CustomTextState from "../../legacy-states/custom-text-name";
-import { restartTestEvent } from "../../states/core";
import { hideModalAndClearChain, showModal } from "../../states/modals";
import {
showNoticeNotification,
diff --git a/frontend/src/ts/components/modals/CustomWordAmountModal.tsx b/frontend/src/ts/components/modals/CustomWordAmountModal.tsx
index 4f277ef0959e..75b87846843e 100644
--- a/frontend/src/ts/components/modals/CustomWordAmountModal.tsx
+++ b/frontend/src/ts/components/modals/CustomWordAmountModal.tsx
@@ -3,7 +3,7 @@ import { JSXElement } from "solid-js";
import { setConfig } from "../../config/setters";
import { getConfig } from "../../config/store";
-import { restartTestEvent } from "../../states/core";
+import { restartTestEvent } from "../../events/test";
import { hideModalAndClearChain } from "../../states/modals";
import { showNoticeNotification } from "../../states/notifications";
import { AnimatedModal } from "../common/AnimatedModal";
diff --git a/frontend/src/ts/components/modals/MobileTestConfigModal.tsx b/frontend/src/ts/components/modals/MobileTestConfigModal.tsx
index 79a8d6b58488..4b3ab07c5b0e 100644
--- a/frontend/src/ts/components/modals/MobileTestConfigModal.tsx
+++ b/frontend/src/ts/components/modals/MobileTestConfigModal.tsx
@@ -8,7 +8,8 @@ import { For, JSXElement, Show } from "solid-js";
import { setConfig, setQuoteLengthAll } from "../../config/setters";
import { getConfig } from "../../config/store";
-import { isLoggedIn, restartTestEvent } from "../../states/core";
+import { restartTestEvent } from "../../events/test";
+import { isLoggedIn } from "../../states/core";
import { showModal } from "../../states/modals";
import { areUnsortedArraysEqual } from "../../utils/arrays";
import { AnimatedModal } from "../common/AnimatedModal";
diff --git a/frontend/src/ts/components/mount.tsx b/frontend/src/ts/components/mount.tsx
index cdcd5667ffa8..9fbcf78c5eb3 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";
@@ -34,6 +35,7 @@ const components: Record JSXElement> = {
header: () => ,
devtools: () => ,
testconfig: () => ,
+ 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/components/pages/test/TestConfig.tsx b/frontend/src/ts/components/pages/test/TestConfig.tsx
index b9e59a340639..e179d2fe73b8 100644
--- a/frontend/src/ts/components/pages/test/TestConfig.tsx
+++ b/frontend/src/ts/components/pages/test/TestConfig.tsx
@@ -2,15 +2,12 @@ import { ComponentProps, For, JSXElement, Show } from "solid-js";
import { setConfig, setQuoteLengthAll } from "../../../config/setters";
import { getConfig } from "../../../config/store";
+import { restartTestEvent } from "../../../events/test";
import { createEffectOn } from "../../../hooks/effects";
import { useRefWithUtils } from "../../../hooks/useRefWithUtils";
-import {
- getFocus,
- getResultVisible,
- isLoggedIn,
- restartTestEvent,
-} from "../../../states/core";
+import { isLoggedIn } from "../../../states/core";
import { showModal } from "../../../states/modals";
+import { getResultVisible, getFocus } from "../../../states/test";
import { FaSolidIcon } from "../../../types/font-awesome";
import { areUnsortedArraysEqual } from "../../../utils/arrays";
import { cn } from "../../../utils/cn";
diff --git a/frontend/src/ts/elements/modes-notice.ts b/frontend/src/ts/elements/modes-notice.ts
index 01dd9c152656..39c5e41679a8 100644
--- a/frontend/src/ts/elements/modes-notice.ts
+++ b/frontend/src/ts/elements/modes-notice.ts
@@ -12,7 +12,11 @@ import Format from "../singletons/format";
import { getActiveFunboxes, getActiveFunboxNames } from "../test/funbox/list";
import { escapeHTML, getMode2 } from "../utils/misc";
import { qsr } from "../utils/dom";
-import { getLoadedChallenge } from "../states/test";
+import {
+ wordsHaveNewline,
+ wordsHaveTab,
+ getLoadedChallenge,
+} from "../states/test";
configEvent.subscribe(({ key }) => {
const configKeys: ConfigEventKey[] = [
@@ -57,28 +61,28 @@ export async function update(): Promise {
);
}
- if (TestWords.hasTab) {
+ if (wordsHaveTab()) {
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")) &&
+ (wordsHaveNewline() || Config.funbox.includes("58008")) &&
Config.quickRestart === "enter"
) {
testModesNotice.appendHtml(
- `shift + enter to restart
`,
+ `shift + enter to restart
`,
);
}
@@ -88,7 +92,7 @@ export async function update(): Promise {
testModesNotice.appendHtml(
`${escapeHTML(
customTextName,
- )} (shift + enter to save progress)
`,
+ )} (shift + enter to save progress)`,
);
}
@@ -101,7 +105,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..9c582fd28e3c 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 { wordsHaveNewline } 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" && !wordsHaveNewline() && 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..569b65d14b02 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 { wordsHaveTab } 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 (wordsHaveTab() && !e.shiftKey) {
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..0494cbbd5d7a
--- /dev/null
+++ b/frontend/src/ts/input/hotkeys/commandline.ts
@@ -0,0 +1,12 @@
+import { hotkeys } from "../../states/hotkeys";
+import { showModal } from "../../states/modals";
+import { isAnyPopupVisible } from "../../utils/misc";
+import { createHotkey } from "./utils";
+
+function openCommandline(): void {
+ if (isAnyPopupVisible()) return;
+ 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..220acaf22d6f
--- /dev/null
+++ b/frontend/src/ts/input/hotkeys/quickrestart.ts
@@ -0,0 +1,23 @@
+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;
+ }
+
+ e.preventDefault();
+
+ 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..daf8a51e89c5
--- /dev/null
+++ b/frontend/src/ts/input/hotkeys/utils.ts
@@ -0,0 +1,70 @@
+import {
+ CreateHotkeyOptions,
+ Hotkey,
+ HotkeyCallback,
+ HotkeyCallbackContext,
+ createHotkey as registerHotkey,
+} from "@tanstack/solid-hotkeys";
+import { isAnyPopupVisible } from "../../utils/misc";
+import { isInputElementFocused } from "../input-element";
+
+export const NoKey = "" as Hotkey;
+
+export function createHotkey(
+ hotkey: Hotkey | (() => Hotkey),
+ callback: HotkeyCallback,
+ options: () => Partial<
+ Omit<
+ CreateHotkeyOptions,
+ "ignoreInputs" | "stopPropagation" | "preventDefault"
+ >
+ > = () => ({}),
+): 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",
+ enabled: (typeof hotkey === "function" ? hotkey() : hotkey) !== NoKey,
+ ...options(),
+ }),
+ );
+}
+
+function isInteractiveElementFocused(): boolean {
+ if (isInputElementFocused()) return false;
+
+ return (
+ document.activeElement?.tagName === "A" ||
+ 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 2fefee6fe99a..c2b3f80f0b2f 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");
@@ -27,7 +26,6 @@ export const [getCommandlineSubgroup, setCommandlineSubgroup] = createSignal<
string | null
>(null);
-export const [getFocus, setFocus] = createSignal(false);
export const [getGlobalOffsetTop, setGlobalOffsetTop] = createSignal(0);
export const [getIsScreenshotting, setIsScreenshotting] = createSignal(false);
@@ -37,7 +35,3 @@ export const isLoggedIn = (): boolean => getUserId() !== null;
export const [getSelectedProfileName, setSelectedProfileName] = createSignal<
string | undefined
>(undefined);
-export const [getResultVisible, setResultVisible] =
- createSignal(false);
-
-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..f1611952be22
--- /dev/null
+++ b/frontend/src/ts/states/hotkeys.ts
@@ -0,0 +1,49 @@
+import { 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 { wordsHaveNewline, wordsHaveTab } from "./test";
+import { getActivePage } from "./core";
+import { NoKey } from "../input/hotkeys/utils";
+
+const quickRestartHotkeyMap: Record = {
+ off: NoKey,
+ esc: "Escape",
+ tab: "Tab",
+ enter: "Enter",
+};
+
+type Hotkeys = {
+ quickRestart: Hotkey;
+ commandline: Hotkey;
+};
+
+export const [hotkeys, setHotkeys] = createStore(updateHotkeys());
+
+createEffect(() => {
+ getActivePage(); // depend on active page
+ setHotkeys(updateHotkeys());
+});
+
+function updateHotkeys(): Hotkeys {
+ const isOnTestPage = getActivePage() === "test";
+ return {
+ quickRestart: shiftHotkey(
+ quickRestartHotkeyMap[getConfig.quickRestart],
+ isOnTestPage && wordsHaveTab(),
+ ),
+ commandline: shiftHotkey(
+ getConfig.quickRestart === "esc" ? "Tab" : "Escape",
+ isOnTestPage && wordsHaveNewline(),
+ ),
+ };
+}
+
+function shiftHotkey(hotkey: Hotkey, shift: boolean): Hotkey {
+ if (shift) {
+ if (hotkey === "Tab") return "Shift+Tab";
+ if (hotkey === "Enter") return "Shift+Enter";
+ }
+ return hotkey;
+}
diff --git a/frontend/src/ts/states/test.ts b/frontend/src/ts/states/test.ts
index 1edc2ac0b853..2549ff79eacc 100644
--- a/frontend/src/ts/states/test.ts
+++ b/frontend/src/ts/states/test.ts
@@ -1,5 +1,11 @@
-import { Challenge } from "@monkeytype/schemas/challenges";
import { createSignal } from "solid-js";
+import { Challenge } from "@monkeytype/schemas/challenges";
+
+export const [wordsHaveNewline, setWordsHaveNewline] = createSignal(false);
+export const [wordsHaveTab, setWordsHaveTab] = createSignal(false);
export const [getLoadedChallenge, setLoadedChallenge] =
createSignal(null);
+export const [getResultVisible, setResultVisible] =
+ createSignal(false);
+export const [getFocus, setFocus] = createSignal(false);
diff --git a/frontend/src/ts/test/focus.ts b/frontend/src/ts/test/focus.ts
index 57f733db6f02..606641c34634 100644
--- a/frontend/src/ts/test/focus.ts
+++ b/frontend/src/ts/test/focus.ts
@@ -5,7 +5,7 @@ import * as LiveAcc from "./live-acc";
import * as TimerProgress from "./timer-progress";
import * as PageTransition from "../legacy-states/page-transition";
import { requestDebouncedAnimationFrame } from "../utils/debounced-animation-frame";
-import { getFocus, setFocus } from "../states/core";
+import { getFocus, setFocus } from "../states/test";
import { qsa, ElementsWithUtils } from "../utils/dom";
const unfocusPx = 3;
diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts
index 8370c8595c81..8d0ecd95d48e 100644
--- a/frontend/src/ts/test/test-logic.ts
+++ b/frontend/src/ts/test/test-logic.ts
@@ -26,11 +26,13 @@ import * as TodayTracker from "./today-tracker";
import * as ChallengeContoller from "../controllers/challenge-controller";
import { clearQuoteStats } from "../states/quote-rate";
import * as Result from "./result";
+import { getActivePage } from "../states/core";
import {
- getActivePage,
- restartTestEvent,
setResultVisible,
-} from "../states/core";
+ setWordsHaveNewline,
+ setWordsHaveTab,
+} from "../states/test";
+import { restartTestEvent } from "../events/test";
import * as TestInput from "./test-input";
import * as TestWords from "./test-words";
import * as WordsGenerator from "./words-generator";
@@ -75,6 +77,7 @@ import { qs } from "../utils/dom";
import { setAccountButtonSpinner } from "../states/header";
import { Config } from "../config/store";
import { setQuoteLengthAll, toggleFunbox, setConfig } from "../config/setters";
+
let failReason = "";
export async function syncNotSignedInLastResult(uid: string): Promise {
@@ -553,8 +556,8 @@ async function init(): Promise {
}
TestWords.setHasNumbers(hasNumbers);
- TestWords.setHasTab(wordsHaveTab);
- TestWords.setHasNewline(wordsHaveNewline);
+ setWordsHaveTab(wordsHaveTab);
+ setWordsHaveNewline(wordsHaveNewline);
if (
generatedWords
@@ -1438,7 +1441,7 @@ qs(".pageTest")?.onChild("click", "#restartTestButtonWithSameWordset", () => {
});
});
-restartTestEvent.subscribe(() => restart());
+restartTestEvent.subscribe((event) => restart(event));
// ===============================
diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts
index 730f65311bc6..f3eda2e605aa 100644
--- a/frontend/src/ts/test/test-ui.ts
+++ b/frontend/src/ts/test/test-ui.ts
@@ -68,6 +68,7 @@ import {
} from "../utils/dom";
import { getTheme } from "../states/theme";
import { skipBreakdownEvent } from "../states/header";
+import { wordsHaveNewline } from "../states/test";
export const updateHintsPositionDebounced = Misc.debounceUntilResolved(
updateHintsPosition,
@@ -657,7 +658,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 (wordsHaveNewline()) {
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 f389feec3e7f..1e40af4d5068 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.2)
+ '@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
@@ -752,7 +758,7 @@ importers:
dependencies:
tsup:
specifier: 8.4.0
- version: 8.4.0(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@6.0.0-beta)(yaml@2.8.2)
+ version: 8.4.0(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.2)
devDependencies:
'@monkeytype/typescript-config':
specifier: workspace:*
@@ -3756,10 +3762,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:
@@ -3772,6 +3821,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'}
@@ -3797,11 +3856,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:
@@ -5382,6 +5459,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:
@@ -6374,6 +6454,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'}
@@ -7148,9 +7233,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==}
@@ -10050,11 +10132,6 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
- typescript@6.0.0-beta:
- resolution: {integrity: sha512-CldZdztDpQRLM1HC6WDQjQkQN5Ub5zRau737a1diGh3lPmb9oRsaWHk1y5iqK0o7+1bNJ0oXfEGRkAogFZBL+Q==}
- engines: {node: '>=14.17'}
- hasBin: true
-
typescript@6.0.2:
resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==}
engines: {node: '>=14.17'}
@@ -11928,7 +12005,7 @@ snapshots:
'@eslint/eslintrc@3.3.3':
dependencies:
- ajv: 6.12.6
+ ajv: 6.14.0
debug: 4.4.3(supports-color@5.5.0)
espree: 10.4.0
globals: 14.0.0
@@ -13929,8 +14006,65 @@ snapshots:
'@tanstack/pacer-lite': 0.2.1
typescript: 6.0.2
+ '@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.2)':
dependencies:
'@typescript-eslint/utils': 8.52.0(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.2)
@@ -13946,6 +14080,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': {}
@@ -13969,12 +14122,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
@@ -15798,6 +15979,8 @@ snapshots:
date-fns@3.6.0: {}
+ dayjs@1.11.20: {}
+
debug@2.6.9:
dependencies:
ms: 2.0.0
@@ -17191,6 +17374,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 +18224,6 @@ snapshots:
known-css-properties@0.30.0: {}
- konami@1.7.0: {}
-
kuler@2.0.0: {}
lazystream@1.0.1:
@@ -21282,33 +21467,6 @@ snapshots:
tsscmp@1.0.6: {}
- tsup@8.4.0(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@6.0.0-beta)(yaml@2.8.2):
- dependencies:
- bundle-require: 5.1.0(esbuild@0.25.11)
- cac: 6.7.14
- chokidar: 4.0.3
- consola: 3.4.0
- debug: 4.4.3(supports-color@5.5.0)
- esbuild: 0.25.11
- joycon: 3.1.1
- picocolors: 1.1.1
- postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(yaml@2.8.2)
- resolve-from: 5.0.0
- rollup: 4.52.5
- source-map: 0.8.0-beta.0
- sucrase: 3.35.0
- tinyexec: 0.3.2
- tinyglobby: 0.2.15
- tree-kill: 1.2.2
- optionalDependencies:
- postcss: 8.5.8
- typescript: 6.0.0-beta
- transitivePeerDependencies:
- - jiti
- - supports-color
- - tsx
- - yaml
-
tsup@8.4.0(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@6.0.2)(yaml@2.8.2):
dependencies:
bundle-require: 5.1.0(esbuild@0.25.11)
@@ -21440,9 +21598,6 @@ snapshots:
typescript@5.9.3: {}
- typescript@6.0.0-beta:
- optional: true
-
typescript@6.0.2: {}
ua-parser-js@0.7.33: {}