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: {}