From 0397c71b2b6c61cccf3e97790cb7ca7fae1fd88f Mon Sep 17 00:00:00 2001 From: Warinyourself Date: Thu, 11 Jun 2026 23:26:57 +0300 Subject: [PATCH] Replace mousetrap with @vueuse/core useMagicKeys, drop custom debounce --- package-lock.json | 60 ++++++++++++++----- package.json | 3 +- src/App.tsx | 15 ++--- src/components/base/UserInput.tsx | 1 - .../base/settings/SettingsCustom.tsx | 4 +- .../settings/general/SettingsSelectors.tsx | 5 +- src/utils/helper.ts | 8 --- src/utils/hotkeys.ts | 8 +++ 8 files changed, 67 insertions(+), 37 deletions(-) diff --git a/package-lock.json b/package-lock.json index 32888b9..0a508b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "dependencies": { "@lucide/vue": "^1.17.0", - "mousetrap": "^1.6.5", + "@vueuse/core": "^14.3.0", "pinia": "^3.0.4", "vue": "^3.5.32", "vue-i18n": "^11.4.5", @@ -17,7 +17,6 @@ }, "devDependencies": { "@tsconfig/node24": "^24.0.4", - "@types/mousetrap": "^1.6.15", "@types/node": "^24.12.2", "@vitejs/plugin-vue": "^6.0.6", "@vitejs/plugin-vue-jsx": "^5.1.5", @@ -999,13 +998,6 @@ "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==", "license": "MIT" }, - "node_modules/@types/mousetrap": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/@types/mousetrap/-/mousetrap-1.6.15.tgz", - "integrity": "sha512-qL0hyIMNPow317QWW/63RvL1x5MVMV+Ru3NaY9f/CuEpCqrmb7WeuK2071ZY5hczOnm38qExWM2i2WtkXLSqFw==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { "version": "24.13.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.1.tgz", @@ -1016,6 +1008,12 @@ "undici-types": "~7.18.0" } }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, "node_modules/@vitejs/plugin-vue": { "version": "6.0.7", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.7.tgz", @@ -1372,6 +1370,44 @@ } } }, + "node_modules/@vueuse/core": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.3.0.tgz", + "integrity": "sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "14.3.0", + "@vueuse/shared": "14.3.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/metadata": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.3.0.tgz", + "integrity": "sha512-BwxmbAzwAVF50+MW57GXOUEV61nFBGnlBvrTqj49PqWJu3uw7hdu72ztXeZ33RdZtDY6kO+bfCAE1PCn88Tktw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.3.0.tgz", + "integrity": "sha512-bZpge9eSXwa4ToSiqJ7j6KRwhAsneMFoSz3LMWKQDkqimm3D/tbFlrklrs/IOqC8tEcYmXQZJ6N0UrjhBirVCg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -2589,12 +2625,6 @@ "pathe": "^2.0.1" } }, - "node_modules/mousetrap": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz", - "integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==", - "license": "Apache-2.0 WITH LLVM-exception" - }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", diff --git a/package.json b/package.json index 52d94d0..b27ad47 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@lucide/vue": "^1.17.0", - "mousetrap": "^1.6.5", + "@vueuse/core": "^14.3.0", "pinia": "^3.0.4", "vue": "^3.5.32", "vue-i18n": "^11.4.5", @@ -20,7 +20,6 @@ }, "devDependencies": { "@tsconfig/node24": "^24.0.4", - "@types/mousetrap": "^1.6.15", "@types/node": "^24.12.2", "@vitejs/plugin-vue": "^6.0.6", "@vitejs/plugin-vue-jsx": "^5.1.5", diff --git a/src/App.tsx b/src/App.tsx index 4435d80..20ec8a8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,9 @@ import { defineComponent, watch } from 'vue' -import Mousetrap from 'mousetrap' +import { useMagicKeys, useDebounceFn, whenever } from '@vueuse/core' import { useAppStore } from '@/store/app' import { usePageStore } from '@/store/page' -import { debounce, focusInputPassword, setCSSVariable } from '@/utils/helper' -import { hotkeys } from '@/utils/hotkeys' +import { focusInputPassword, setCSSVariable } from '@/utils/helper' +import { hotkeys, toMagicKeyCombo } from '@/utils/hotkeys' export default defineComponent({ name: 'MainApp', @@ -11,15 +11,16 @@ export default defineComponent({ setup() { const appStore = useAppStore() const pageStore = usePageStore() + const keys = useMagicKeys() const initZoom = () => { setCSSVariable('--zoom', appStore.zoom + '' || '1') } const initKeybinds = () => { - hotkeys.forEach(({ keys, callback }) => Mousetrap.bind(keys.join('+'), callback)) + hotkeys.forEach(({ keys: keyList, callback }) => whenever(keys[toMagicKeyCombo(keyList)]!, callback)) - Mousetrap.bind('escape', () => { + whenever(keys.escape!, () => { const isFocusPassword = document.querySelector('#password:focus') as HTMLInputElement if (pageStore.dialog) { pageStore.closeDialog() @@ -32,7 +33,7 @@ export default defineComponent({ } }) - Mousetrap.bind('enter', () => { + whenever(keys.enter!, () => { const isFocusPassword = document.querySelector('#password:focus') if (isFocusPassword) { appStore.login() @@ -48,7 +49,7 @@ export default defineComponent({ watch( () => appStore.getMainSettings, - debounce(() => appStore.syncSettingsWithCache(), 100), + useDebounceFn(() => appStore.syncSettingsWithCache(), 100), { deep: true } ) diff --git a/src/components/base/UserInput.tsx b/src/components/base/UserInput.tsx index a223bcd..824c195 100644 --- a/src/components/base/UserInput.tsx +++ b/src/components/base/UserInput.tsx @@ -31,7 +31,6 @@ export default defineComponent({ name="password" autocomplete="on" autofocus - class="mousetrap" placeholder={t('text.password')} onKeyup={handleKeyup} value={appStore.password} diff --git a/src/components/base/settings/SettingsCustom.tsx b/src/components/base/settings/SettingsCustom.tsx index c9ccf72..ea0d401 100644 --- a/src/components/base/settings/SettingsCustom.tsx +++ b/src/components/base/settings/SettingsCustom.tsx @@ -1,5 +1,6 @@ import { defineComponent, computed, onUpdated, type VNode } from 'vue' import { useI18n } from 'vue-i18n' +import { useDebounceFn } from '@vueuse/core' import { useAppStore } from '@/store/app' import AppSlider from '@/components/app/AppSlider' import AppButton from '@/components/app/AppButton' @@ -7,7 +8,6 @@ import AppSwitch from '@/components/app/AppSwitch' import AppSelector from '@/components/app/AppSelector' import AppColorSelector from '@/components/app/AppColorSelector' import AppPaletteSelector from '@/components/app/AppPaletteSelector' -import { debounce } from '@/utils/helper' import type { AppInputButton, AppInputColor, @@ -24,7 +24,7 @@ export default defineComponent({ const { t } = useI18n() const inputs = computed(() => appStore.activeTheme?.settings || []) - const debouncedSync = debounce(() => appStore.syncStoreWithQuery(), 300) + const debouncedSync = useDebounceFn(() => appStore.syncStoreWithQuery(), 300) onUpdated(() => debouncedSync()) diff --git a/src/components/base/settings/general/SettingsSelectors.tsx b/src/components/base/settings/general/SettingsSelectors.tsx index 2feeae6..ecd2645 100644 --- a/src/components/base/settings/general/SettingsSelectors.tsx +++ b/src/components/base/settings/general/SettingsSelectors.tsx @@ -1,11 +1,12 @@ import { defineComponent, computed } from 'vue' import { useI18n } from 'vue-i18n' +import { useDebounceFn } from '@vueuse/core' import { useAppStore } from '@/store/app' import { usePageStore } from '@/store/page' import { useRouter } from 'vue-router' import AppSelector from '@/components/app/AppSelector' import AppSlider from '@/components/app/AppSlider' -import { debounce, generateDesktopIcons, languageMap, setCSSVariable } from '@/utils/helper' +import { generateDesktopIcons, languageMap, setCSSVariable } from '@/utils/helper' import type { LoginPosition } from '@/models/page' export default defineComponent({ @@ -49,7 +50,7 @@ export default defineComponent({ appStore.saveStateApp({ key: 'desktop', value }) } - const updateZoom = debounce((value: number) => { + const updateZoom = useDebounceFn((value: number) => { setCSSVariable('--zoom', value + '' || '1') appStore.zoom = parseFloat(value + '') }, 100) diff --git a/src/utils/helper.ts b/src/utils/helper.ts index 7e2e1dc..96cb6ba 100644 --- a/src/utils/helper.ts +++ b/src/utils/helper.ts @@ -22,14 +22,6 @@ export function isDifferentRoute(to: RouteLocationRaw) { return router.currentRoute.value.fullPath !== resolve.fullPath } -export function debounce void>(fn: T, wait = 500) { - let timeout: ReturnType - return ((...args: Parameters) => { - clearTimeout(timeout) - timeout = setTimeout(() => fn(...args), wait) - }) as T -} - export function parseQueryValue(value: string) { const isBoolean = ['false', 'true'].includes(value) if (isBoolean) return value === 'true' diff --git a/src/utils/hotkeys.ts b/src/utils/hotkeys.ts index 65599ba..d932fd3 100644 --- a/src/utils/hotkeys.ts +++ b/src/utils/hotkeys.ts @@ -76,3 +76,11 @@ export const hotkeys = [ ] export type hotkeysType = (typeof hotkeys)[number] + +// Converts a display key list (e.g. ['ctrl', 'P']) into a useMagicKeys combo +// (e.g. 'ctrl+shift+p'), since an uppercase letter implies the Shift modifier +export function toMagicKeyCombo(keys: string[]): string { + return keys + .flatMap((key) => (key.length === 1 && key !== key.toLowerCase() ? ['shift', key.toLowerCase()] : [key.toLowerCase()])) + .join('+') +}