From 1512354363061a8776d448fc8abb1501ed96ef2a Mon Sep 17 00:00:00 2001 From: Kunaaal Date: Wed, 25 Feb 2026 13:55:44 +0530 Subject: [PATCH 1/6] initial setup --- package.json | 3 +- packages/svelte-hotkeys/eslint.config.js | 28 +++ packages/svelte-hotkeys/package.json | 63 +++++ packages/svelte-hotkeys/src/index.ts | 1 + packages/svelte-hotkeys/svelte.config.ts | 10 + packages/svelte-hotkeys/tsconfig.json | 12 + packages/svelte-hotkeys/vite.config.ts | 6 + pnpm-lock.yaml | 289 +++++++++++++++++++++++ 8 files changed, 411 insertions(+), 1 deletion(-) create mode 100644 packages/svelte-hotkeys/eslint.config.js create mode 100644 packages/svelte-hotkeys/package.json create mode 100644 packages/svelte-hotkeys/src/index.ts create mode 100644 packages/svelte-hotkeys/svelte.config.ts create mode 100644 packages/svelte-hotkeys/tsconfig.json create mode 100644 packages/svelte-hotkeys/vite.config.ts diff --git a/package.json b/package.json index 2c58956..c1869ee 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "@tanstack/preact-hotkeys": "workspace:*", "@tanstack/preact-hotkeys-devtools": "workspace:*", "@tanstack/react-hotkeys": "workspace:*", - "@tanstack/react-hotkeys-devtools": "workspace:*" + "@tanstack/react-hotkeys-devtools": "workspace:*", + "@tanstack/svelte-hotkeys": "workspace:*" } } diff --git a/packages/svelte-hotkeys/eslint.config.js b/packages/svelte-hotkeys/eslint.config.js new file mode 100644 index 0000000..141203b --- /dev/null +++ b/packages/svelte-hotkeys/eslint.config.js @@ -0,0 +1,28 @@ +// @ts-check + +import tsParser from '@typescript-eslint/parser' +import pluginSvelte from 'eslint-plugin-svelte' +import rootConfig from '../../eslint.config.js' +import svelteConfig from './svelte.config.js' + +export default [ + ...rootConfig, + ...pluginSvelte.configs['recommended'], + { + files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], + languageOptions: { + parserOptions: { + parser: tsParser, + extraFileExtensions: ['.svelte'], + svelteConfig, + }, + }, + }, + { + rules: { + 'svelte/block-lang': ['error', { script: ['ts'] }], + 'svelte/no-svelte-internal': 'error', + 'svelte/valid-compile': 'off', + }, + }, +] diff --git a/packages/svelte-hotkeys/package.json b/packages/svelte-hotkeys/package.json new file mode 100644 index 0000000..6584569 --- /dev/null +++ b/packages/svelte-hotkeys/package.json @@ -0,0 +1,63 @@ +{ + "name": "@tanstack/svelte-hotkeys", + "version": "0.0.1", + "description": "Svelte adapter for TanStack Hotkeys", + "author": "Kunal Rao", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/TanStack/hotkeys.git", + "directory": "packages/svelte-hotkeys" + }, + "homepage": "https://tanstack.com/hotkeys", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kunalrao" + }, + "keywords": [ + "svelte", + "tanstack", + "keys", + "hotkeys", + "keyboard", + "shortcuts" + ], + "scripts": { + "clean": "premove ./dist ./coverage", + "test:eslint": "eslint ./src", + "test:types": "tsc", + "test:build": "publint --strict", + "build": "svelte-package --input ./src --output ./dist" + }, + "type": "module", + "types": "dist/index.d.ts", + "module": "dist/index.js", + "svelte": "./dist/index.js", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "svelte": "./dist/index.js", + "import": "./dist/index.js" + }, + "./package.json": "./package.json" + }, + "sideEffects": false, + "files": [ + "dist", + "src" + ], + "dependencies": { + "@tanstack/hotkeys": "workspace:*" + }, + "devDependencies": { + "@sveltejs/package": "^2.4.0", + "@sveltejs/vite-plugin-svelte": "^5.1.1", + "eslint-plugin-svelte": "^3.11.0", + "svelte": "^5.39.3", + "svelte-check": "^4.3.1", + "@typescript-eslint/parser": "^8.48.0" + }, + "peerDependencies": { + "svelte": "^5.25.0" + } +} diff --git a/packages/svelte-hotkeys/src/index.ts b/packages/svelte-hotkeys/src/index.ts new file mode 100644 index 0000000..19dfa14 --- /dev/null +++ b/packages/svelte-hotkeys/src/index.ts @@ -0,0 +1 @@ +console.log('Hello, world!') diff --git a/packages/svelte-hotkeys/svelte.config.ts b/packages/svelte-hotkeys/svelte.config.ts new file mode 100644 index 0000000..076d2dc --- /dev/null +++ b/packages/svelte-hotkeys/svelte.config.ts @@ -0,0 +1,10 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +const config = { + preprocess: vitePreprocess(), + compilerOptions: { + runes: true, + }, +} + +export default config diff --git a/packages/svelte-hotkeys/tsconfig.json b/packages/svelte-hotkeys/tsconfig.json new file mode 100644 index 0000000..1849be5 --- /dev/null +++ b/packages/svelte-hotkeys/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "include": [ + "src/**/*.js", + "src/**/*.ts", + "src/**/*.svelte", + "src/**/*.svelte.ts", + "eslint.config.js", + "svelte.config.js", + "vite.config.ts" + ] +} diff --git a/packages/svelte-hotkeys/vite.config.ts b/packages/svelte-hotkeys/vite.config.ts new file mode 100644 index 0000000..3d696d6 --- /dev/null +++ b/packages/svelte-hotkeys/vite.config.ts @@ -0,0 +1,6 @@ +import { svelte } from '@sveltejs/vite-plugin-svelte' +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a663c03..8258eb3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -679,6 +679,31 @@ importers: specifier: ^2.11.10 version: 2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)) + packages/svelte-hotkeys: + dependencies: + '@tanstack/hotkeys': + specifier: workspace:* + version: link:../hotkeys + devDependencies: + '@sveltejs/package': + specifier: ^2.4.0 + version: 2.5.7(svelte@5.53.1)(typescript@5.9.3) + '@sveltejs/vite-plugin-svelte': + specifier: ^5.1.1 + version: 5.1.1(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)) + '@typescript-eslint/parser': + specifier: ^8.48.0 + version: 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) + eslint-plugin-svelte: + specifier: ^3.11.0 + version: 3.15.0(eslint@9.39.3(jiti@2.6.1))(svelte@5.53.1) + svelte: + specifier: ^5.39.3 + version: 5.53.1 + svelte-check: + specifier: ^4.3.1 + version: 4.4.3(picomatch@4.0.3)(svelte@5.53.1)(typescript@5.9.3) + packages: '@adobe/css-tools@4.4.4': @@ -1772,6 +1797,28 @@ packages: peerDependencies: acorn: ^8.9.0 + '@sveltejs/package@2.5.7': + resolution: {integrity: sha512-qqD9xa9H7TDiGFrF6rz7AirOR8k15qDK/9i4MIE8te4vWsv5GEogPks61rrZcLy+yWph+aI6pIj2MdoK3YI8AQ==} + engines: {node: ^16.14 || >=18} + hasBin: true + peerDependencies: + svelte: ^3.44.0 || ^4.0.0 || ^5.0.0-next.1 + + '@sveltejs/vite-plugin-svelte-inspector@4.0.1': + resolution: {integrity: sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^5.0.0 + svelte: ^5.0.0 + vite: ^6.0.0 + + '@sveltejs/vite-plugin-svelte@5.1.1': + resolution: {integrity: sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + svelte: ^5.0.0 + vite: ^6.0.0 + '@svitejs/changesets-changelog-github-compact@1.2.0': resolution: {integrity: sha512-08eKiDAjj4zLug1taXSIJ0kGL5cawjVCyJkBb6EWSg5fEPX6L+Wtr0CH2If4j5KYylz85iaZiFlUItvgJvll5g==} engines: {node: ^14.13.1 || ^16.0.0 || >=18} @@ -2389,6 +2436,14 @@ packages: resolution: {integrity: sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==} engines: {node: '>=20.18.1'} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} @@ -2451,6 +2506,11 @@ packages: css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -2466,6 +2526,9 @@ packages: supports-color: optional: true + dedent-js@1.0.1: + resolution: {integrity: sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==} + deep-equal@2.2.3: resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} engines: {node: '>= 0.4'} @@ -2473,6 +2536,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} @@ -2732,6 +2799,16 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' + eslint-plugin-svelte@3.15.0: + resolution: {integrity: sha512-QKB7zqfuB8aChOfBTComgDptMf2yxiJx7FE04nneCmtQzgTHvY8UJkuh8J2Rz7KB9FFV9aTHX6r7rdYGvG8T9Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.1 || ^9.0.0 || ^10.0.0 + svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + svelte: + optional: true + eslint-plugin-unused-imports@4.4.1: resolution: {integrity: sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ==} peerDependencies: @@ -2963,6 +3040,10 @@ packages: resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + globals@17.3.0: resolution: {integrity: sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==} engines: {node: '>=18'} @@ -3253,6 +3334,10 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + knip@5.85.0: resolution: {integrity: sha512-V2kyON+DZiYdNNdY6GALseiNCwX7dYdpz9Pv85AUn69Gk0UKCts+glOKWfe5KmaMByRjM9q17Mzj/KinTVOyxg==} engines: {node: '>=18.18.0'} @@ -3261,6 +3346,9 @@ packages: '@types/node': '>=18' typescript: '>=5.0.4 <7' + known-css-properties@0.37.0: + resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} + kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} @@ -3268,6 +3356,10 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -3572,6 +3664,34 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + postcss-load-config@3.1.4: + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + postcss-safe-parser@7.0.1: + resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} + engines: {node: '>=18.0'} + peerDependencies: + postcss: ^8.4.31 + + postcss-scss@4.0.9: + resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.4.29 + + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} + engines: {node: '>=4'} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -3664,6 +3784,14 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -3748,6 +3876,9 @@ packages: scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + scule@1.3.0: + resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -3948,6 +4079,29 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + svelte-check@4.4.3: + resolution: {integrity: sha512-4HtdEv2hOoLCEsSXI+RDELk9okP/4sImWa7X02OjMFFOWeSdFF3NFy3vqpw0z+eH9C88J9vxZfUXz/Uv2A1ANw==} + engines: {node: '>= 18.0.0'} + hasBin: true + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: '>=5.0.0' + + svelte-eslint-parser@1.4.1: + resolution: {integrity: sha512-1eqkfQ93goAhjAXxZiu1SaKI9+0/sxp4JIWQwUpsz7ybehRE5L8dNuz7Iry7K22R47p5/+s9EM+38nHV2OlgXA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0, pnpm: 10.24.0} + peerDependencies: + svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + svelte: + optional: true + + svelte2tsx@0.7.51: + resolution: {integrity: sha512-YbVMQi5LtQkVGOMdATTY8v3SMtkNjzYtrVDGaN3Bv+0LQ47tGXu/Oc8ryTkcYuEJWTZFJ8G2+2I8ORcQVGt9Ag==} + peerDependencies: + svelte: ^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0 + typescript: ^4.9.4 || ^5.0.0 + svelte@5.53.1: resolution: {integrity: sha512-WzxFHZhhD23Qzu7JCYdvm1rxvRSzdt9HtHO8TScMBX51bLRFTcJmATVqjqXG+6Ln6hrViGCo9DzwOhAasxwC/w==} engines: {node: '>=18'} @@ -4302,6 +4456,10 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + yaml@2.8.2: resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} engines: {node: '>= 14.6'} @@ -5395,6 +5553,39 @@ snapshots: dependencies: acorn: 8.16.0 + '@sveltejs/package@2.5.7(svelte@5.53.1)(typescript@5.9.3)': + dependencies: + chokidar: 5.0.0 + kleur: 4.1.5 + sade: 1.8.1 + semver: 7.7.4 + svelte: 5.53.1 + svelte2tsx: 0.7.51(svelte@5.53.1)(typescript@5.9.3) + transitivePeerDependencies: + - typescript + + '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))': + dependencies: + '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)) + debug: 4.4.3 + svelte: 5.53.1 + vite: 7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2) + transitivePeerDependencies: + - supports-color + + '@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))': + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)) + debug: 4.4.3 + deepmerge: 4.3.1 + kleur: 4.1.5 + magic-string: 0.30.21 + svelte: 5.53.1 + vite: 7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2) + vitefu: 1.1.1(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)) + transitivePeerDependencies: + - supports-color + '@svitejs/changesets-changelog-github-compact@1.2.0': dependencies: '@changesets/get-github-info': 0.6.0 @@ -6086,6 +6277,14 @@ snapshots: undici: 7.22.0 whatwg-mimetype: 4.0.0 + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + ci-info@3.9.0: {} cli-cursor@3.1.0: @@ -6140,6 +6339,8 @@ snapshots: css.escape@1.5.1: {} + cssesc@3.0.0: {} + csstype@3.2.3: {} dataloader@1.4.0: {} @@ -6148,6 +6349,8 @@ snapshots: dependencies: ms: 2.1.3 + dedent-js@1.0.1: {} + deep-equal@2.2.3: dependencies: array-buffer-byte-length: 1.0.2 @@ -6171,6 +6374,8 @@ snapshots: deep-is@0.1.4: {} + deepmerge@4.3.1: {} + defaults@1.0.4: dependencies: clone: 1.0.4 @@ -6523,6 +6728,24 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-plugin-svelte@3.15.0(eslint@9.39.3(jiti@2.6.1))(svelte@5.53.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1)) + '@jridgewell/sourcemap-codec': 1.5.5 + eslint: 9.39.3(jiti@2.6.1) + esutils: 2.0.3 + globals: 16.5.0 + known-css-properties: 0.37.0 + postcss: 8.5.6 + postcss-load-config: 3.1.4(postcss@8.5.6) + postcss-safe-parser: 7.0.1(postcss@8.5.6) + semver: 7.7.4 + svelte-eslint-parser: 1.4.1(svelte@5.53.1) + optionalDependencies: + svelte: 5.53.1 + transitivePeerDependencies: + - ts-node + eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.56.0(@typescript-eslint/parser@8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1)): dependencies: eslint: 9.39.3(jiti@2.6.1) @@ -6772,6 +6995,8 @@ snapshots: globals@15.15.0: {} + globals@16.5.0: {} + globals@17.3.0: {} globby@11.1.0: @@ -7038,6 +7263,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + kleur@4.1.5: {} + knip@5.85.0(@types/node@25.3.0)(typescript@5.9.3): dependencies: '@nodelib/fs.walk': 1.2.8 @@ -7055,6 +7282,8 @@ snapshots: typescript: 5.9.3 zod: 4.3.6 + known-css-properties@0.37.0: {} + kolorist@1.8.0: {} levn@0.4.1: @@ -7062,6 +7291,8 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lilconfig@2.1.0: {} + lilconfig@3.1.3: {} lines-and-columns@2.0.3: {} @@ -7397,6 +7628,26 @@ snapshots: possible-typed-array-names@1.1.0: {} + postcss-load-config@3.1.4(postcss@8.5.6): + dependencies: + lilconfig: 2.1.0 + yaml: 1.10.2 + optionalDependencies: + postcss: 8.5.6 + + postcss-safe-parser@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-scss@4.0.9(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-selector-parser@7.1.1: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -7475,6 +7726,10 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + readdirp@4.1.2: {} + + readdirp@5.0.0: {} + redent@3.0.0: dependencies: indent-string: 4.0.0 @@ -7593,6 +7848,8 @@ snapshots: scheduler@0.27.0: {} + scule@1.3.0: {} + semver@6.3.1: {} semver@7.7.4: {} @@ -7781,6 +8038,36 @@ snapshots: dependencies: has-flag: 4.0.0 + svelte-check@4.4.3(picomatch@4.0.3)(svelte@5.53.1)(typescript@5.9.3): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + chokidar: 4.0.3 + fdir: 6.5.0(picomatch@4.0.3) + picocolors: 1.1.1 + sade: 1.8.1 + svelte: 5.53.1 + typescript: 5.9.3 + transitivePeerDependencies: + - picomatch + + svelte-eslint-parser@1.4.1(svelte@5.53.1): + dependencies: + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + postcss: 8.5.6 + postcss-scss: 4.0.9(postcss@8.5.6) + postcss-selector-parser: 7.1.1 + optionalDependencies: + svelte: 5.53.1 + + svelte2tsx@0.7.51(svelte@5.53.1)(typescript@5.9.3): + dependencies: + dedent-js: 1.0.1 + scule: 1.3.0 + svelte: 5.53.1 + typescript: 5.9.3 + svelte@5.53.1: dependencies: '@jridgewell/remapping': 2.3.5 @@ -8136,6 +8423,8 @@ snapshots: yallist@3.1.1: {} + yaml@1.10.2: {} + yaml@2.8.2: {} yargs-parser@21.1.1: {} From fbd6b6a4f1481988d04aa7fbe711d57659182947 Mon Sep 17 00:00:00 2001 From: Kunaaal Date: Wed, 25 Feb 2026 14:57:31 +0530 Subject: [PATCH 2/6] feat: Update dependencies and export components in svelte-hotkeys --- packages/svelte-hotkeys/package.json | 3 +- packages/svelte-hotkeys/src/HotkeysCtx.ts | 30 ++++ .../svelte-hotkeys/src/HotkeysProvider.svelte | 16 +++ .../svelte-hotkeys/src/createHotkey.svelte.ts | 134 ++++++++++++++++++ .../src/createHotkeyRecorder.svelte.ts | 93 ++++++++++++ .../src/createHotkeySequence.svelte.ts | 120 ++++++++++++++++ .../src/getHeldKeyCodesMap.svelte.ts | 32 +++++ .../svelte-hotkeys/src/getHeldKeys.svelte.ts | 29 ++++ .../svelte-hotkeys/src/getIsKeyHeld.svelte.ts | 55 +++++++ packages/svelte-hotkeys/src/index.ts | 9 +- pnpm-lock.yaml | 18 +++ 11 files changed, 537 insertions(+), 2 deletions(-) create mode 100644 packages/svelte-hotkeys/src/HotkeysCtx.ts create mode 100644 packages/svelte-hotkeys/src/HotkeysProvider.svelte create mode 100644 packages/svelte-hotkeys/src/createHotkey.svelte.ts create mode 100644 packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts create mode 100644 packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts create mode 100644 packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts create mode 100644 packages/svelte-hotkeys/src/getHeldKeys.svelte.ts create mode 100644 packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts diff --git a/packages/svelte-hotkeys/package.json b/packages/svelte-hotkeys/package.json index 6584569..b0480d3 100644 --- a/packages/svelte-hotkeys/package.json +++ b/packages/svelte-hotkeys/package.json @@ -47,7 +47,8 @@ "src" ], "dependencies": { - "@tanstack/hotkeys": "workspace:*" + "@tanstack/hotkeys": "workspace:*", + "@tanstack/svelte-store": "^0.9.1" }, "devDependencies": { "@sveltejs/package": "^2.4.0", diff --git a/packages/svelte-hotkeys/src/HotkeysCtx.ts b/packages/svelte-hotkeys/src/HotkeysCtx.ts new file mode 100644 index 0000000..519de4e --- /dev/null +++ b/packages/svelte-hotkeys/src/HotkeysCtx.ts @@ -0,0 +1,30 @@ +import { createContext, Snippet } from 'svelte' +import { CreateHotkeyOptions } from './createHotkey.svelte' +import { HotkeyRecorderOptions } from '@tanstack/hotkeys' +import { CreateHotkeySequenceOptions } from './createHotkeySequence.svelte' + +export interface HotkeysProviderOptions { + hotkey?: Partial + hotkeyRecorder?: Partial + hotkeySequence?: Partial +} + +export interface HotkeysProviderProps { + children: Snippet + defaultOptions?: HotkeysProviderOptions +} + +export const DEFAULT_OPTIONS: HotkeysProviderOptions = {} + +interface HotkeysContextValue { + defaultOptions: HotkeysProviderOptions +} + +const [getHotkeysContext, setHotkeysContext] = + createContext() + +export { getHotkeysContext, setHotkeysContext } + +export function getDefaultHotkeysOptions(): HotkeysProviderOptions { + return getHotkeysContext()?.defaultOptions ?? DEFAULT_OPTIONS +} diff --git a/packages/svelte-hotkeys/src/HotkeysProvider.svelte b/packages/svelte-hotkeys/src/HotkeysProvider.svelte new file mode 100644 index 0000000..a113f60 --- /dev/null +++ b/packages/svelte-hotkeys/src/HotkeysProvider.svelte @@ -0,0 +1,16 @@ + + +{@render children()} diff --git a/packages/svelte-hotkeys/src/createHotkey.svelte.ts b/packages/svelte-hotkeys/src/createHotkey.svelte.ts new file mode 100644 index 0000000..34f6d75 --- /dev/null +++ b/packages/svelte-hotkeys/src/createHotkey.svelte.ts @@ -0,0 +1,134 @@ +import { + detectPlatform, + formatHotkey, + getHotkeyManager, + rawHotkeyToParsedHotkey, +} from '@tanstack/hotkeys' +import type { + Hotkey, + HotkeyCallback, + HotkeyOptions, + HotkeyRegistrationHandle, + RegisterableHotkey, +} from '@tanstack/hotkeys' +import { getDefaultHotkeysOptions } from './HotkeysCtx' + +export interface CreateHotkeyOptions extends Omit { + /** + * The DOM element to attach the event listener to. + * Can be a Svelte ref, direct DOM element, or null. + * Defaults to document. + */ + target?: HTMLElement | Document | Window | null +} + +/** + * Svelte function for registering a keyboard hotkey. + * + * Uses the singleton HotkeyManager for efficient event handling. + * The callback receives both the keyboard event and a context object + * containing the hotkey string and parsed hotkey. + * + * This function syncs the callback and options on every render to avoid + * stale closures. This means + * callbacks that reference Svelte state will always have access to + * the latest values. + * + */ + +export function createHotkey( + hotkey: RegisterableHotkey, + callback: HotkeyCallback, + options: CreateHotkeyOptions = {}, +): void { + const mergedOptions = { + ...getDefaultHotkeysOptions().hotkey, + ...options, + } as CreateHotkeyOptions + + const manager = getHotkeyManager() + + // Stable ref for registration handle + let registrationRef = $state(null) + + // Refs to capture current values for use in effect without adding dependencies + let callbackRef = $state(callback) + let optionsRef = $state(mergedOptions) + let managerRef = $state(manager) + + $effect(() => { + callbackRef = callback + optionsRef = mergedOptions + managerRef = manager + }) + + // Track previous target and hotkey to detect changes requiring re-registration + let prevTargetRef = $state(null) + let prevHotkeyRef = $state(null) + + // Normalize to hotkey string + const platform = mergedOptions.platform ?? detectPlatform() + const hotkeyString: Hotkey = + typeof hotkey === 'string' + ? hotkey + : (formatHotkey(rawHotkeyToParsedHotkey(hotkey, platform)) as Hotkey) + + // Extract options without target (target is handled separately) + const { target: _target, ...optionsWithoutTarget } = mergedOptions + + $effect(() => { + // Resolve target inside the effect so refs are already attached after mount + const resolvedTarget = optionsRef?.target + ? optionsRef.target + : typeof document !== 'undefined' + ? document + : null + + // Skip if no valid target (SSR or ref still null) + if (!resolvedTarget) { + return + } + + // Check if we need to re-register (target or hotkey changed) + const targetChanged = + prevTargetRef !== null && prevTargetRef !== resolvedTarget + const hotkeyChanged = + prevHotkeyRef !== null && prevHotkeyRef !== hotkeyString + + // If we have an active registration and target/hotkey changed, unregister first + if (registrationRef?.isActive && (targetChanged || hotkeyChanged)) { + registrationRef.unregister() + registrationRef = null + } + + // Register if needed (no active registration) + // Use refs to access current values without adding them to dependencies + if (!registrationRef || !registrationRef.isActive) { + registrationRef = managerRef.register(hotkeyString, callbackRef, { + ...optionsRef, + target: resolvedTarget, + }) + } + + // Update tracking refs + prevTargetRef = resolvedTarget + prevHotkeyRef = hotkeyString + + // Cleanup on unmount + return () => { + if (registrationRef?.isActive) { + registrationRef.unregister() + registrationRef = null + } + } + }) + + // Sync callback and options on EVERY render (outside useEffect) + // This avoids stale closures - the callback always has access to latest state + $effect(() => { + if (registrationRef?.isActive) { + registrationRef.callback = callbackRef + registrationRef.setOptions(optionsWithoutTarget) + } + }) +} diff --git a/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts b/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts new file mode 100644 index 0000000..958f840 --- /dev/null +++ b/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts @@ -0,0 +1,93 @@ +import { HotkeyRecorder } from '@tanstack/hotkeys' +import type { Hotkey, HotkeyRecorderOptions } from '@tanstack/hotkeys' +import { getDefaultHotkeysOptions } from './HotkeysCtx' +import { onDestroy } from 'svelte' + +export interface SvelteHotkeyRecorder { + /** Whether recording is currently active */ + isRecording: boolean + /** The currently recorded hotkey (for live preview) */ + recordedHotkey: Hotkey | null + /** Start recording a new hotkey */ + startRecording: () => void + /** Stop recording (same as cancel) */ + stopRecording: () => void + /** Cancel recording without saving */ + cancelRecording: () => void +} + +/** + * Svelte function for recording keyboard shortcuts. + * + * This function provides a thin wrapper around the framework-agnostic `HotkeyRecorder` + * class, managing all the complexity of capturing keyboard events, converting them + * to hotkey strings, and handling edge cases like Escape to cancel or Backspace/Delete + * to clear. + * + * @param options - Configuration options for the recorder + * @returns An object with recording state and control functions + * + * @example + * ```svelte + * + * + *
+ * + * {recorder.recordedHotkey && ( + *
Recording: {recorder.recordedHotkey}
+ * )} + *
+ * ``` + */ + +export function createHotkeyRecorder( + options: HotkeyRecorderOptions, +): SvelteHotkeyRecorder { + const mergedOptions = { + ...getDefaultHotkeysOptions().hotkeyRecorder, + ...options, + } as HotkeyRecorderOptions + + let recorderRef = $state(null) + + // Create recorder instance once + if (!recorderRef) { + recorderRef = new HotkeyRecorder(mergedOptions) + } + + // Sync options on every render (same pattern as createHotkey) + // This ensures callbacks always have access to latest values + $effect(() => { + if (recorderRef) { + recorderRef.setOptions(mergedOptions) + } + }) + + let isRecording = $derived(recorderRef?.store.state.isRecording) + let recordedHotkey = $derived(recorderRef?.store.state.recordedHotkey) + + onDestroy(() => { + recorderRef?.destroy() + }) + + return { + isRecording, + recordedHotkey, + startRecording: () => recorderRef?.start(), + stopRecording: () => recorderRef?.stop(), + cancelRecording: () => recorderRef?.cancel(), + } +} diff --git a/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts b/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts new file mode 100644 index 0000000..7f02a0f --- /dev/null +++ b/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts @@ -0,0 +1,120 @@ +import { + formatHotkeySequence, + getSequenceManager, + HotkeyCallback, + HotkeyCallbackContext, + HotkeySequence, + SequenceOptions, + SequenceRegistrationHandle, +} from '@tanstack/hotkeys' +import { getDefaultHotkeysOptions } from './HotkeysCtx' + +export interface CreateHotkeySequenceOptions extends Omit< + SequenceOptions, + 'target' +> { + /** + * The DOM element to attach the event listener to. + * Can be a Svelte ref, direct DOM element, or null. + * Defaults to document. + */ + target?: HTMLElement | Document | Window | null +} + +export function createHotkeySequence( + sequence: HotkeySequence, + callback: HotkeyCallback, + options: CreateHotkeySequenceOptions = {}, +): void { + const mergedOptions = { + ...getDefaultHotkeysOptions().hotkeySequence, + ...options, + } as CreateHotkeySequenceOptions + + let manager = $state(getSequenceManager()) + + // Stable ref for registration handle + let registrationRef = $state(null) + + // Refs to capture current values for use in effect without adding dependencies + let callbackRef = $state(callback) + let optionsRef = $state(mergedOptions) + let managerRef = $state(manager) + + $effect(() => { + callbackRef = callback + optionsRef = mergedOptions + managerRef = manager + }) + + // Track previous target and sequence to detect changes requiring re-registration + let prevTargetRef = $state(null) + let prevSequenceRef = $state(null) + + // Normalize to hotkey sequence string (join with spaces) + let hotkeySequenceString = $derived.by(() => formatHotkeySequence(sequence)) + + // Extract options without target (target is handled separately) + let { target: _target, ...optionsWithoutTarget } = $derived(mergedOptions) + + $effect(() => { + if (sequence.length === 0) { + return + } + + // Resolve target inside the effect so refs are already attached after mount + const resolvedTarget = optionsRef?.target + ? optionsRef.target + : typeof document !== 'undefined' + ? document + : null + + // Skip if no valid target (SSR or ref still null) + if (!resolvedTarget) { + return + } + + // Check if we need to re-register (target or sequence changed) + const targetChanged = + prevTargetRef !== null && prevTargetRef !== resolvedTarget + const sequenceChanged = + prevSequenceRef !== null && prevSequenceRef !== hotkeySequenceString + + // If we have an active registration and target/sequence changed, unregister first + if (registrationRef?.isActive && (targetChanged || sequenceChanged)) { + registrationRef.unregister() + registrationRef = null + } + + // Register if needed (no active registration) + if (!registrationRef || !registrationRef.isActive) { + registrationRef = manager.register(sequence, callback, { + ...optionsRef, + target: resolvedTarget, + }) + } + + // Update tracking refs + prevTargetRef = resolvedTarget + prevSequenceRef = hotkeySequenceString + + // Cleanup on unmount + return () => { + if (registrationRef?.isActive) { + registrationRef.unregister() + registrationRef = null + } + } + }) + + // Sync callback and options on EVERY render + $effect(() => { + if (registrationRef?.isActive) { + registrationRef.callback = ( + event: KeyboardEvent, + context: HotkeyCallbackContext, + ) => callbackRef(event, context) + registrationRef.setOptions(optionsWithoutTarget) + } + }) +} diff --git a/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts b/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts new file mode 100644 index 0000000..d789c93 --- /dev/null +++ b/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts @@ -0,0 +1,32 @@ +import { getKeyStateTracker } from '@tanstack/hotkeys' + +/** + * Svelte function that returns a map of currently held key names to their physical `event.code` values. + * + * This is useful for debugging which physical key was pressed (e.g. distinguishing + * left vs right Shift via "ShiftLeft" / "ShiftRight"). + * + * @returns Record mapping normalized key names to their `event.code` values + * + * ```svelte + * + * + *
+ * {Object.entries(heldKeyCodesMap).map(([key, code]) => ( + * + * {key} {code} + * + * ))} + *
+ * ``` + */ +export function getHeldKeyCodesMap(): Record { + const tracker = getKeyStateTracker() + + const heldKeyCodesMap = $derived(tracker.store.state.heldCodes) + + return heldKeyCodesMap +} diff --git a/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts b/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts new file mode 100644 index 0000000..ce291be --- /dev/null +++ b/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts @@ -0,0 +1,29 @@ +import { getKeyStateTracker } from '@tanstack/hotkeys' + +/** + * Svelte function that returns an array of currently held keyboard keys. + * + * This function uses the global KeyStateTracker and updates whenever keys are pressed + * or released. + * + * @returns Array of currently held key names + * + * @example + * ```svelte + * + *
+ * Currently pressed: {getHeldKeys().join(' + ') || 'None'} + *
+ * ``` + */ +export function getHeldKeys(): Array { + const tracker = getKeyStateTracker() + + const heldKeys = $derived(tracker.store.state.heldKeys) + + return heldKeys +} diff --git a/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts b/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts new file mode 100644 index 0000000..49da330 --- /dev/null +++ b/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts @@ -0,0 +1,55 @@ +import { HeldKey } from '@tanstack/hotkeys' +import { getKeyStateTracker } from '@tanstack/hotkeys' + +/** + * Svelte function that returns whether a specific key is currently being held. + * + * This function uses the global KeyStateTracker and updates whenever keys are pressed + * or released. + * + * @param key - The key to check (e.g., 'Shift', 'Control', 'A') + * @returns True if the key is currently held down + * + * @example + * ```svelte + * + * + *
+ * {isShiftHeld ? 'Shift is pressed!' : 'Press Shift'} + *
+ * ``` + * + * @example + * ```svelte + * + * + *
+ * Ctrl + * Shift + * Alt + *
+ * ``` + */ + +export function getIsKeyHeld(key: HeldKey): boolean { + const tracker = getKeyStateTracker() + const normalizedKey = key.toLowerCase() + + const isKeyHeld = $derived( + tracker.store.state.heldKeys.some( + (heldKey) => heldKey.toLowerCase() === normalizedKey, + ), + ) + + return isKeyHeld +} diff --git a/packages/svelte-hotkeys/src/index.ts b/packages/svelte-hotkeys/src/index.ts index 19dfa14..fc44cb4 100644 --- a/packages/svelte-hotkeys/src/index.ts +++ b/packages/svelte-hotkeys/src/index.ts @@ -1 +1,8 @@ -console.log('Hello, world!') +export * from './createHotkey.svelte' +export * from './createHotkeySequence.svelte' +export * from './createHotkeyRecorder.svelte' +export * from './getHeldKeys.svelte' +export * from './getHeldKeyCodesMap.svelte' +export * from './getIsKeyHeld.svelte' +export * from './HotkeysProvider.svelte' +export * from './HotkeysCtx' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8258eb3..9eaf30f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -684,6 +684,9 @@ importers: '@tanstack/hotkeys': specifier: workspace:* version: link:../hotkeys + '@tanstack/svelte-store': + specifier: ^0.9.1 + version: 0.9.1(svelte@5.53.1) devDependencies: '@sveltejs/package': specifier: ^2.4.0 @@ -1921,9 +1924,17 @@ packages: peerDependencies: solid-js: ^1.6.0 + '@tanstack/store@0.8.1': + resolution: {integrity: sha512-PtOisLjUZPz5VyPRSCGjNOlwTvabdTBQ2K80DpVL1chGVr35WRxfeavAPdNq6pm/t7F8GhoR2qtmkkqtCEtHYw==} + '@tanstack/store@0.9.1': resolution: {integrity: sha512-+qcNkOy0N1qSGsP7omVCW0SDrXtaDcycPqBDE726yryiA5eTDFpjBReaYjghVJwNf1pcPMyzIwTGlYjCSQR0Fg==} + '@tanstack/svelte-store@0.9.1': + resolution: {integrity: sha512-4RYp0CXSB9tjlUZNl29mjraWeRquKzuaW+bGGI4s3kS6BWatgt7BfX4OtoLT8MTBdepW9ARwqHZ3s8YGpfOZkQ==} + peerDependencies: + svelte: ^5.0.0 + '@tanstack/typedoc-config@0.3.3': resolution: {integrity: sha512-wVT2YfKDSpd+4f7fk6UaPIP3a2J7LSovlyVuFF1PH2yQb7gjqehod5zdFiwFyEXgvI9XGuFvvs1OehkKNYcr6A==} engines: {node: '>=18'} @@ -5729,8 +5740,15 @@ snapshots: '@tanstack/store': 0.9.1 solid-js: 1.9.11 + '@tanstack/store@0.8.1': {} + '@tanstack/store@0.9.1': {} + '@tanstack/svelte-store@0.9.1(svelte@5.53.1)': + dependencies: + '@tanstack/store': 0.8.1 + svelte: 5.53.1 + '@tanstack/typedoc-config@0.3.3(typescript@5.9.3)': dependencies: typedoc: 0.28.14(typescript@5.9.3) From 8a025940ca305492d1fe943f608095ca82aaa127 Mon Sep 17 00:00:00 2001 From: Kunaaal Date: Wed, 25 Feb 2026 15:06:07 +0530 Subject: [PATCH 3/6] added jsdoc examples --- .../svelte-hotkeys/src/createHotkey.svelte.ts | 36 +++++++++++++++++++ .../src/createHotkeySequence.svelte.ts | 36 +++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/packages/svelte-hotkeys/src/createHotkey.svelte.ts b/packages/svelte-hotkeys/src/createHotkey.svelte.ts index 34f6d75..294c3d7 100644 --- a/packages/svelte-hotkeys/src/createHotkey.svelte.ts +++ b/packages/svelte-hotkeys/src/createHotkey.svelte.ts @@ -34,6 +34,42 @@ export interface CreateHotkeyOptions extends Omit { * callbacks that reference Svelte state will always have access to * the latest values. * + * @example + * ```svelte + * + * + * + *
+ * .... + *
+ * ``` + * + * @example + * ```svelte + * + * + *
+ * Count: {count} + *
+ * ``` */ export function createHotkey( diff --git a/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts b/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts index 7f02a0f..8dc3c0d 100644 --- a/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts +++ b/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts @@ -21,6 +21,42 @@ export interface CreateHotkeySequenceOptions extends Omit< target?: HTMLElement | Document | Window | null } +/** + * Svelte function for registering a keyboard shortcut sequence (Vim-style). + * + * This hook allows you to register multi-key sequences like 'g g' or 'd d' + * that trigger when the full sequence is pressed within a timeout. + * + * @param sequence - Array of hotkey strings that form the sequence + * @param callback - Function to call when the sequence is completed + * @param options - Options for the sequence behavior + * + * @example + * ```svelte + * + * + *
+ * .... + *
+ * ``` + */ export function createHotkeySequence( sequence: HotkeySequence, callback: HotkeyCallback, From 0e75e7ffda5a7b36b48e3ce53bf94f5397231921 Mon Sep 17 00:00:00 2001 From: Kunaaal Date: Wed, 25 Feb 2026 16:10:17 +0530 Subject: [PATCH 4/6] setup example 1 --- examples/svelte/create-hotkey/.gitignore | 23 +++ examples/svelte/create-hotkey/.npmrc | 1 + examples/svelte/create-hotkey/README.md | 42 +++++ examples/svelte/create-hotkey/package.json | 26 ++++ examples/svelte/create-hotkey/src/app.d.ts | 13 ++ examples/svelte/create-hotkey/src/app.html | 11 ++ .../create-hotkey/src/routes/+layout.svelte | 12 ++ .../create-hotkey/src/routes/+page.svelte | 35 +++++ .../svelte/create-hotkey/static/robots.txt | 3 + .../svelte/create-hotkey/svelte.config.js | 13 ++ examples/svelte/create-hotkey/tsconfig.json | 20 +++ examples/svelte/create-hotkey/vite.config.ts | 6 + packages/svelte-hotkeys/.gitignore | 8 + .../svelte-hotkeys/src/HotkeysProvider.svelte | 14 +- .../svelte-hotkeys/src/createHotkey.svelte.ts | 12 +- .../src/createHotkeyRecorder.svelte.ts | 31 ++-- .../src/createHotkeySequence.svelte.ts | 14 +- .../src/getHeldKeyCodesMap.svelte.ts | 2 +- .../svelte-hotkeys/src/getHeldKeys.svelte.ts | 2 +- .../svelte-hotkeys/src/getIsKeyHeld.svelte.ts | 3 +- pnpm-lock.yaml | 144 ++++++++++++++++++ pnpm-workspace.yaml | 2 + 22 files changed, 396 insertions(+), 41 deletions(-) create mode 100644 examples/svelte/create-hotkey/.gitignore create mode 100644 examples/svelte/create-hotkey/.npmrc create mode 100644 examples/svelte/create-hotkey/README.md create mode 100644 examples/svelte/create-hotkey/package.json create mode 100644 examples/svelte/create-hotkey/src/app.d.ts create mode 100644 examples/svelte/create-hotkey/src/app.html create mode 100644 examples/svelte/create-hotkey/src/routes/+layout.svelte create mode 100644 examples/svelte/create-hotkey/src/routes/+page.svelte create mode 100644 examples/svelte/create-hotkey/static/robots.txt create mode 100644 examples/svelte/create-hotkey/svelte.config.js create mode 100644 examples/svelte/create-hotkey/tsconfig.json create mode 100644 examples/svelte/create-hotkey/vite.config.ts create mode 100644 packages/svelte-hotkeys/.gitignore diff --git a/examples/svelte/create-hotkey/.gitignore b/examples/svelte/create-hotkey/.gitignore new file mode 100644 index 0000000..3b462cb --- /dev/null +++ b/examples/svelte/create-hotkey/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/examples/svelte/create-hotkey/.npmrc b/examples/svelte/create-hotkey/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/examples/svelte/create-hotkey/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/examples/svelte/create-hotkey/README.md b/examples/svelte/create-hotkey/README.md new file mode 100644 index 0000000..57b7713 --- /dev/null +++ b/examples/svelte/create-hotkey/README.md @@ -0,0 +1,42 @@ +# sv + +Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```sh +# create a new project +npx sv create my-app +``` + +To recreate this project with the same configuration: + +```sh +# recreate this project +pnpm dlx sv create --template minimal --types ts --install pnpm create-hotkey +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```sh +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```sh +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. diff --git a/examples/svelte/create-hotkey/package.json b/examples/svelte/create-hotkey/package.json new file mode 100644 index 0000000..7be4fa0 --- /dev/null +++ b/examples/svelte/create-hotkey/package.json @@ -0,0 +1,26 @@ +{ + "name": "create-hotkey", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" + }, + "dependencies": { + "@tanstack/svelte-hotkeys": "workspace:*" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^7.0.0", + "@sveltejs/kit": "^2.50.2", + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "svelte": "^5.51.0", + "svelte-check": "^4.3.6", + "typescript": "^5.9.3", + "vite": "^7.3.1" + } +} diff --git a/examples/svelte/create-hotkey/src/app.d.ts b/examples/svelte/create-hotkey/src/app.d.ts new file mode 100644 index 0000000..da08e6d --- /dev/null +++ b/examples/svelte/create-hotkey/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/examples/svelte/create-hotkey/src/app.html b/examples/svelte/create-hotkey/src/app.html new file mode 100644 index 0000000..f273cc5 --- /dev/null +++ b/examples/svelte/create-hotkey/src/app.html @@ -0,0 +1,11 @@ + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/examples/svelte/create-hotkey/src/routes/+layout.svelte b/examples/svelte/create-hotkey/src/routes/+layout.svelte new file mode 100644 index 0000000..2472ec7 --- /dev/null +++ b/examples/svelte/create-hotkey/src/routes/+layout.svelte @@ -0,0 +1,12 @@ + + + + + + +{@render children()} diff --git a/examples/svelte/create-hotkey/src/routes/+page.svelte b/examples/svelte/create-hotkey/src/routes/+page.svelte new file mode 100644 index 0000000..2e053ae --- /dev/null +++ b/examples/svelte/create-hotkey/src/routes/+page.svelte @@ -0,0 +1,35 @@ + + +
+ +
diff --git a/examples/svelte/create-hotkey/static/robots.txt b/examples/svelte/create-hotkey/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/examples/svelte/create-hotkey/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/examples/svelte/create-hotkey/svelte.config.js b/examples/svelte/create-hotkey/svelte.config.js new file mode 100644 index 0000000..2a5c16e --- /dev/null +++ b/examples/svelte/create-hotkey/svelte.config.js @@ -0,0 +1,13 @@ +import adapter from '@sveltejs/adapter-auto' + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://svelte.dev/docs/kit/adapters for more information about adapters. + adapter: adapter(), + }, +} + +export default config diff --git a/examples/svelte/create-hotkey/tsconfig.json b/examples/svelte/create-hotkey/tsconfig.json new file mode 100644 index 0000000..2c2ed3c --- /dev/null +++ b/examples/svelte/create-hotkey/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "rewriteRelativeImportExtensions": true, + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // To make changes to top-level options such as include and exclude, we recommend extending + // the generated config; see https://svelte.dev/docs/kit/configuration#typescript +} diff --git a/examples/svelte/create-hotkey/vite.config.ts b/examples/svelte/create-hotkey/vite.config.ts new file mode 100644 index 0000000..bbf8c7d --- /dev/null +++ b/examples/svelte/create-hotkey/vite.config.ts @@ -0,0 +1,6 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [sveltekit()] +}); diff --git a/packages/svelte-hotkeys/.gitignore b/packages/svelte-hotkeys/.gitignore new file mode 100644 index 0000000..2fd4600 --- /dev/null +++ b/packages/svelte-hotkeys/.gitignore @@ -0,0 +1,8 @@ +.svelte-kit +node_modules +dist +build +.env +.env.* +!.env.example +!.env.test \ No newline at end of file diff --git a/packages/svelte-hotkeys/src/HotkeysProvider.svelte b/packages/svelte-hotkeys/src/HotkeysProvider.svelte index a113f60..f861aa6 100644 --- a/packages/svelte-hotkeys/src/HotkeysProvider.svelte +++ b/packages/svelte-hotkeys/src/HotkeysProvider.svelte @@ -1,15 +1,15 @@ diff --git a/packages/svelte-hotkeys/src/createHotkey.svelte.ts b/packages/svelte-hotkeys/src/createHotkey.svelte.ts index 294c3d7..a3f32d5 100644 --- a/packages/svelte-hotkeys/src/createHotkey.svelte.ts +++ b/packages/svelte-hotkeys/src/createHotkey.svelte.ts @@ -85,12 +85,12 @@ export function createHotkey( const manager = getHotkeyManager() // Stable ref for registration handle - let registrationRef = $state(null) + let registrationRef: HotkeyRegistrationHandle | null = null // Refs to capture current values for use in effect without adding dependencies - let callbackRef = $state(callback) - let optionsRef = $state(mergedOptions) - let managerRef = $state(manager) + let callbackRef = callback + let optionsRef = mergedOptions + let managerRef = manager $effect(() => { callbackRef = callback @@ -99,8 +99,8 @@ export function createHotkey( }) // Track previous target and hotkey to detect changes requiring re-registration - let prevTargetRef = $state(null) - let prevHotkeyRef = $state(null) + let prevTargetRef: HTMLElement | Document | Window | null = null + let prevHotkeyRef: string | null = null // Normalize to hotkey string const platform = mergedOptions.platform ?? detectPlatform() diff --git a/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts b/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts index 958f840..bd1dacd 100644 --- a/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts +++ b/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts @@ -61,33 +61,30 @@ export function createHotkeyRecorder( ...options, } as HotkeyRecorderOptions - let recorderRef = $state(null) - - // Create recorder instance once - if (!recorderRef) { - recorderRef = new HotkeyRecorder(mergedOptions) - } + const recorder = new HotkeyRecorder(mergedOptions) // Sync options on every render (same pattern as createHotkey) // This ensures callbacks always have access to latest values $effect(() => { - if (recorderRef) { - recorderRef.setOptions(mergedOptions) - } + recorder.setOptions(mergedOptions) }) - let isRecording = $derived(recorderRef?.store.state.isRecording) - let recordedHotkey = $derived(recorderRef?.store.state.recordedHotkey) + let isRecording = $derived.by(() => recorder.store.state.isRecording) + let recordedHotkey = $derived.by(() => recorder.store.state.recordedHotkey) onDestroy(() => { - recorderRef?.destroy() + recorder.destroy() }) return { - isRecording, - recordedHotkey, - startRecording: () => recorderRef?.start(), - stopRecording: () => recorderRef?.stop(), - cancelRecording: () => recorderRef?.cancel(), + get isRecording() { + return isRecording + }, + get recordedHotkey() { + return recordedHotkey + }, + startRecording: () => recorder.start(), + stopRecording: () => recorder.stop(), + cancelRecording: () => recorder.cancel(), } } diff --git a/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts b/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts index 8dc3c0d..6d8a7d5 100644 --- a/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts +++ b/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts @@ -67,15 +67,15 @@ export function createHotkeySequence( ...options, } as CreateHotkeySequenceOptions - let manager = $state(getSequenceManager()) + const manager = getSequenceManager() // Stable ref for registration handle - let registrationRef = $state(null) + let registrationRef: SequenceRegistrationHandle | null = null // Refs to capture current values for use in effect without adding dependencies - let callbackRef = $state(callback) - let optionsRef = $state(mergedOptions) - let managerRef = $state(manager) + let callbackRef = callback + let optionsRef = mergedOptions + let managerRef = manager $effect(() => { callbackRef = callback @@ -84,8 +84,8 @@ export function createHotkeySequence( }) // Track previous target and sequence to detect changes requiring re-registration - let prevTargetRef = $state(null) - let prevSequenceRef = $state(null) + let prevTargetRef: HTMLElement | Document | Window | null = null + let prevSequenceRef: string | null = null // Normalize to hotkey sequence string (join with spaces) let hotkeySequenceString = $derived.by(() => formatHotkeySequence(sequence)) diff --git a/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts b/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts index d789c93..a462b8f 100644 --- a/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts +++ b/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts @@ -26,7 +26,7 @@ import { getKeyStateTracker } from '@tanstack/hotkeys' export function getHeldKeyCodesMap(): Record { const tracker = getKeyStateTracker() - const heldKeyCodesMap = $derived(tracker.store.state.heldCodes) + const heldKeyCodesMap = $derived.by(() => tracker.store.state.heldCodes) return heldKeyCodesMap } diff --git a/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts b/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts index ce291be..4c576b8 100644 --- a/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts +++ b/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts @@ -23,7 +23,7 @@ import { getKeyStateTracker } from '@tanstack/hotkeys' export function getHeldKeys(): Array { const tracker = getKeyStateTracker() - const heldKeys = $derived(tracker.store.state.heldKeys) + const heldKeys = $derived.by(() => tracker.store.state.heldKeys) return heldKeys } diff --git a/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts b/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts index 49da330..58252f1 100644 --- a/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts +++ b/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts @@ -45,11 +45,10 @@ export function getIsKeyHeld(key: HeldKey): boolean { const tracker = getKeyStateTracker() const normalizedKey = key.toLowerCase() - const isKeyHeld = $derived( + const isKeyHeld = $derived.by(() => tracker.store.state.heldKeys.some( (heldKey) => heldKey.toLowerCase() === normalizedKey, ), ) - return isKeyHeld } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9eaf30f..219446d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -507,6 +507,30 @@ importers: specifier: ^2.11.10 version: 2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)) + examples/svelte/create-hotkey: + devDependencies: + '@sveltejs/adapter-auto': + specifier: ^7.0.0 + version: 7.0.1(@sveltejs/kit@2.53.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.53.1)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))) + '@sveltejs/kit': + specifier: ^2.50.2 + version: 2.53.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.53.1)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte': + specifier: ^6.2.4 + version: 6.2.4(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)) + svelte: + specifier: ^5.51.0 + version: 5.53.1 + svelte-check: + specifier: ^4.3.6 + version: 4.4.3(picomatch@4.0.3)(svelte@5.53.1)(typescript@5.9.3) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vite: + specifier: ^7.3.1 + version: 7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2) + packages/hotkeys: dependencies: '@tanstack/store': @@ -1446,6 +1470,9 @@ packages: cpu: [x64] os: [win32] + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@preact/preset-vite@2.10.3': resolution: {integrity: sha512-1SiS+vFItpkNdBs7q585PSAIln0wBeBdcpJYbzPs1qipsb/FssnkUioNXuRsb8ZnU8YEQHr+3v8+/mzWSnTQmg==} peerDependencies: @@ -1800,6 +1827,27 @@ packages: peerDependencies: acorn: ^8.9.0 + '@sveltejs/adapter-auto@7.0.1': + resolution: {integrity: sha512-dvuPm1E7M9NI/+canIQ6KKQDU2AkEefEZ2Dp7cY6uKoPq9Z/PhOXABe526UdW2mN986gjVkuSLkOYIBnS/M2LQ==} + peerDependencies: + '@sveltejs/kit': ^2.0.0 + + '@sveltejs/kit@2.53.1': + resolution: {integrity: sha512-NXsZLvalgI3HrHG6ogoEVzjyV7bSFQNqQeekfU7nNufQFrRyV3EBDfQKEwxx50peu7spZR42JuC1PFhwxuvBrg==} + engines: {node: '>=18.13'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.0.0 + '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: ^5.3.3 + vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + typescript: + optional: true + '@sveltejs/package@2.5.7': resolution: {integrity: sha512-qqD9xa9H7TDiGFrF6rz7AirOR8k15qDK/9i4MIE8te4vWsv5GEogPks61rrZcLy+yWph+aI6pIj2MdoK3YI8AQ==} engines: {node: ^16.14 || >=18} @@ -1815,6 +1863,14 @@ packages: svelte: ^5.0.0 vite: ^6.0.0 + '@sveltejs/vite-plugin-svelte-inspector@5.0.2': + resolution: {integrity: sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==} + engines: {node: ^20.19 || ^22.12 || >=24} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^6.0.0-next.0 + svelte: ^5.0.0 + vite: ^6.3.0 || ^7.0.0 + '@sveltejs/vite-plugin-svelte@5.1.1': resolution: {integrity: sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22} @@ -1822,6 +1878,13 @@ packages: svelte: ^5.0.0 vite: ^6.0.0 + '@sveltejs/vite-plugin-svelte@6.2.4': + resolution: {integrity: sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==} + engines: {node: ^20.19 || ^22.12 || >=24} + peerDependencies: + svelte: ^5.0.0 + vite: ^6.3.0 || ^7.0.0 + '@svitejs/changesets-changelog-github-compact@1.2.0': resolution: {integrity: sha512-08eKiDAjj4zLug1taXSIJ0kGL5cawjVCyJkBb6EWSg5fEPX6L+Wtr0CH2If4j5KYylz85iaZiFlUItvgJvll5g==} engines: {node: ^14.13.1 || ^16.0.0 || >=18} @@ -1996,6 +2059,9 @@ packages: '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} @@ -2503,6 +2569,10 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -3489,6 +3559,10 @@ packages: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -3909,6 +3983,9 @@ packages: resolution: {integrity: sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw==} engines: {node: '>=10'} + set-cookie-parser@3.0.1: + resolution: {integrity: sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -3998,6 +4075,10 @@ packages: simple-code-frame@1.3.0: resolution: {integrity: sha512-MB4pQmETUBlNs62BBeRjIFGeuy/x6gGKh7+eRUemn1rCFhqo7K+4slPqsyizCbcbYLnaYqaoZ2FWsZ/jN06D8w==} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + size-limit@12.0.0: resolution: {integrity: sha512-JBG8dioIs0m2kHOhs9jD6E/tZKD08vmbf2bfqj/rJyNWqJxk/ZcakixjhYtsqdbi+AKVbfPkt3g2RRZiKaizYA==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -4152,6 +4233,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -5297,6 +5382,8 @@ snapshots: '@oxc-resolver/binding-win32-x64-msvc@11.18.0': optional: true + '@polka/url@1.0.0-next.29': {} + '@preact/preset-vite@2.10.3(@babel/core@7.29.0)(preact@10.28.4)(rollup@4.58.0)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))': dependencies: '@babel/core': 7.29.0 @@ -5564,6 +5651,30 @@ snapshots: dependencies: acorn: 8.16.0 + '@sveltejs/adapter-auto@7.0.1(@sveltejs/kit@2.53.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.53.1)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)))': + dependencies: + '@sveltejs/kit': 2.53.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.53.1)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)) + + '@sveltejs/kit@2.53.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.53.1)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))': + dependencies: + '@standard-schema/spec': 1.1.0 + '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)) + '@types/cookie': 0.6.0 + acorn: 8.16.0 + cookie: 0.6.0 + devalue: 5.6.3 + esm-env: 1.2.2 + kleur: 4.1.5 + magic-string: 0.30.21 + mrmime: 2.0.1 + set-cookie-parser: 3.0.1 + sirv: 3.0.2 + svelte: 5.53.1 + vite: 7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2) + optionalDependencies: + typescript: 5.9.3 + '@sveltejs/package@2.5.7(svelte@5.53.1)(typescript@5.9.3)': dependencies: chokidar: 5.0.0 @@ -5584,6 +5695,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))': + dependencies: + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)) + obug: 2.1.1 + svelte: 5.53.1 + vite: 7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2) + '@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))': dependencies: '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)) @@ -5597,6 +5715,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))': + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)) + deepmerge: 4.3.1 + magic-string: 0.30.21 + obug: 2.1.1 + svelte: 5.53.1 + vite: 7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2) + vitefu: 1.1.1(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)) + '@svitejs/changesets-changelog-github-compact@1.2.0': dependencies: '@changesets/get-github-info': 0.6.0 @@ -5840,6 +5968,8 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/cookie@0.6.0': {} + '@types/deep-eql@4.0.2': {} '@types/esrecurse@4.3.1': {} @@ -6339,6 +6469,8 @@ snapshots: convert-source-map@2.0.0: {} + cookie@0.6.0: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -7415,6 +7547,8 @@ snapshots: mri@1.2.0: {} + mrmime@2.0.1: {} + ms@2.1.3: {} nanoid@3.3.11: {} @@ -7878,6 +8012,8 @@ snapshots: seroval@1.5.0: {} + set-cookie-parser@3.0.1: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -7973,6 +8109,12 @@ snapshots: dependencies: kolorist: 1.8.0 + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + size-limit@12.0.0(jiti@2.6.1): dependencies: bytes-iec: 3.1.1 @@ -8134,6 +8276,8 @@ snapshots: dependencies: is-number: 7.0.0 + totalist@3.0.1: {} + tr46@0.0.3: {} tree-kill@1.2.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5add109..2113f35 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,3 +5,5 @@ preferWorkspacePackages: true packages: - 'examples/**/*' - 'packages/*' +onlyBuiltDependencies: + - esbuild From 1f2bb4ef8c86dda73d7f29a18b67803dd129124b Mon Sep 17 00:00:00 2001 From: Kunaaal Date: Wed, 25 Feb 2026 17:02:41 +0530 Subject: [PATCH 5/6] feat: Enhance Svelte hotkeys functionality and update examples --- .../svelte/create-hotkey-sequence/.gitignore | 23 ++++++++++ examples/svelte/create-hotkey-sequence/.npmrc | 1 + .../svelte/create-hotkey-sequence/README.md | 42 +++++++++++++++++++ .../create-hotkey-sequence/package.json | 26 ++++++++++++ .../create-hotkey-sequence/src/app.d.ts | 13 ++++++ .../create-hotkey-sequence/src/app.html | 11 +++++ .../src/routes/+layout.svelte | 12 ++++++ .../src/routes/+page.svelte | 19 +++++++++ .../create-hotkey-sequence/static/robots.txt | 3 ++ .../create-hotkey-sequence/svelte.config.js | 13 ++++++ .../create-hotkey-sequence/tsconfig.json | 20 +++++++++ .../create-hotkey-sequence/vite.config.ts | 6 +++ .../create-hotkey/src/routes/+page.svelte | 7 ++-- packages/svelte-hotkeys/src/HotkeysCtx.ts | 4 ++ .../svelte-hotkeys/src/createHotkey.svelte.ts | 16 +++++-- .../src/createHotkeySequence.svelte.ts | 15 +++++-- .../src/getHeldKeyCodesMap.svelte.ts | 4 +- .../svelte-hotkeys/src/getHeldKeys.svelte.ts | 4 +- .../svelte-hotkeys/src/getIsKeyHeld.svelte.ts | 10 ++--- pnpm-lock.yaml | 32 ++++++++++++++ 20 files changed, 262 insertions(+), 19 deletions(-) create mode 100644 examples/svelte/create-hotkey-sequence/.gitignore create mode 100644 examples/svelte/create-hotkey-sequence/.npmrc create mode 100644 examples/svelte/create-hotkey-sequence/README.md create mode 100644 examples/svelte/create-hotkey-sequence/package.json create mode 100644 examples/svelte/create-hotkey-sequence/src/app.d.ts create mode 100644 examples/svelte/create-hotkey-sequence/src/app.html create mode 100644 examples/svelte/create-hotkey-sequence/src/routes/+layout.svelte create mode 100644 examples/svelte/create-hotkey-sequence/src/routes/+page.svelte create mode 100644 examples/svelte/create-hotkey-sequence/static/robots.txt create mode 100644 examples/svelte/create-hotkey-sequence/svelte.config.js create mode 100644 examples/svelte/create-hotkey-sequence/tsconfig.json create mode 100644 examples/svelte/create-hotkey-sequence/vite.config.ts diff --git a/examples/svelte/create-hotkey-sequence/.gitignore b/examples/svelte/create-hotkey-sequence/.gitignore new file mode 100644 index 0000000..3b462cb --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/examples/svelte/create-hotkey-sequence/.npmrc b/examples/svelte/create-hotkey-sequence/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/examples/svelte/create-hotkey-sequence/README.md b/examples/svelte/create-hotkey-sequence/README.md new file mode 100644 index 0000000..57b7713 --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/README.md @@ -0,0 +1,42 @@ +# sv + +Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```sh +# create a new project +npx sv create my-app +``` + +To recreate this project with the same configuration: + +```sh +# recreate this project +pnpm dlx sv create --template minimal --types ts --install pnpm create-hotkey +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```sh +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```sh +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. diff --git a/examples/svelte/create-hotkey-sequence/package.json b/examples/svelte/create-hotkey-sequence/package.json new file mode 100644 index 0000000..ebdc998 --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/package.json @@ -0,0 +1,26 @@ +{ + "name": "create-hotkey-sequence", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" + }, + "dependencies": { + "@tanstack/svelte-hotkeys": "workspace:*" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^7.0.0", + "@sveltejs/kit": "^2.50.2", + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "svelte": "^5.51.0", + "svelte-check": "^4.3.6", + "typescript": "^5.9.3", + "vite": "^7.3.1" + } +} diff --git a/examples/svelte/create-hotkey-sequence/src/app.d.ts b/examples/svelte/create-hotkey-sequence/src/app.d.ts new file mode 100644 index 0000000..da08e6d --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/examples/svelte/create-hotkey-sequence/src/app.html b/examples/svelte/create-hotkey-sequence/src/app.html new file mode 100644 index 0000000..f273cc5 --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/src/app.html @@ -0,0 +1,11 @@ + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/examples/svelte/create-hotkey-sequence/src/routes/+layout.svelte b/examples/svelte/create-hotkey-sequence/src/routes/+layout.svelte new file mode 100644 index 0000000..2472ec7 --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/src/routes/+layout.svelte @@ -0,0 +1,12 @@ + + + + + + +{@render children()} diff --git a/examples/svelte/create-hotkey-sequence/src/routes/+page.svelte b/examples/svelte/create-hotkey-sequence/src/routes/+page.svelte new file mode 100644 index 0000000..00048d3 --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/src/routes/+page.svelte @@ -0,0 +1,19 @@ + + +
diff --git a/examples/svelte/create-hotkey-sequence/static/robots.txt b/examples/svelte/create-hotkey-sequence/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/examples/svelte/create-hotkey-sequence/svelte.config.js b/examples/svelte/create-hotkey-sequence/svelte.config.js new file mode 100644 index 0000000..2a5c16e --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/svelte.config.js @@ -0,0 +1,13 @@ +import adapter from '@sveltejs/adapter-auto' + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://svelte.dev/docs/kit/adapters for more information about adapters. + adapter: adapter(), + }, +} + +export default config diff --git a/examples/svelte/create-hotkey-sequence/tsconfig.json b/examples/svelte/create-hotkey-sequence/tsconfig.json new file mode 100644 index 0000000..2c2ed3c --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "rewriteRelativeImportExtensions": true, + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // To make changes to top-level options such as include and exclude, we recommend extending + // the generated config; see https://svelte.dev/docs/kit/configuration#typescript +} diff --git a/examples/svelte/create-hotkey-sequence/vite.config.ts b/examples/svelte/create-hotkey-sequence/vite.config.ts new file mode 100644 index 0000000..bbf8c7d --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/vite.config.ts @@ -0,0 +1,6 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [sveltekit()] +}); diff --git a/examples/svelte/create-hotkey/src/routes/+page.svelte b/examples/svelte/create-hotkey/src/routes/+page.svelte index 2e053ae..5fe85a2 100644 --- a/examples/svelte/create-hotkey/src/routes/+page.svelte +++ b/examples/svelte/create-hotkey/src/routes/+page.svelte @@ -15,7 +15,7 @@ divRef.style.backgroundColor = 'red' } }, - { target: divRef }, + { target: () => divRef }, ) createHotkeySequence(['K', 'S'], () => { @@ -28,8 +28,7 @@
- -
+> diff --git a/packages/svelte-hotkeys/src/HotkeysCtx.ts b/packages/svelte-hotkeys/src/HotkeysCtx.ts index 519de4e..a5edcc6 100644 --- a/packages/svelte-hotkeys/src/HotkeysCtx.ts +++ b/packages/svelte-hotkeys/src/HotkeysCtx.ts @@ -3,6 +3,10 @@ import { CreateHotkeyOptions } from './createHotkey.svelte' import { HotkeyRecorderOptions } from '@tanstack/hotkeys' import { CreateHotkeySequenceOptions } from './createHotkeySequence.svelte' +export type Target = ResolvedTarget | (() => ResolvedTarget) + +export type ResolvedTarget = HTMLElement | Document | Window | null + export interface HotkeysProviderOptions { hotkey?: Partial hotkeyRecorder?: Partial diff --git a/packages/svelte-hotkeys/src/createHotkey.svelte.ts b/packages/svelte-hotkeys/src/createHotkey.svelte.ts index a3f32d5..31ec4cf 100644 --- a/packages/svelte-hotkeys/src/createHotkey.svelte.ts +++ b/packages/svelte-hotkeys/src/createHotkey.svelte.ts @@ -11,7 +11,7 @@ import type { HotkeyRegistrationHandle, RegisterableHotkey, } from '@tanstack/hotkeys' -import { getDefaultHotkeysOptions } from './HotkeysCtx' +import { getDefaultHotkeysOptions, ResolvedTarget, Target } from './HotkeysCtx' export interface CreateHotkeyOptions extends Omit { /** @@ -19,7 +19,15 @@ export interface CreateHotkeyOptions extends Omit { * Can be a Svelte ref, direct DOM element, or null. * Defaults to document. */ - target?: HTMLElement | Document | Window | null + target?: Target +} + +function resolveTarget(target: Target): ResolvedTarget { + if (typeof target === 'function') { + return target() ?? null + } + + return target } /** @@ -99,7 +107,7 @@ export function createHotkey( }) // Track previous target and hotkey to detect changes requiring re-registration - let prevTargetRef: HTMLElement | Document | Window | null = null + let prevTargetRef: ResolvedTarget = null let prevHotkeyRef: string | null = null // Normalize to hotkey string @@ -115,7 +123,7 @@ export function createHotkey( $effect(() => { // Resolve target inside the effect so refs are already attached after mount const resolvedTarget = optionsRef?.target - ? optionsRef.target + ? resolveTarget(optionsRef.target) : typeof document !== 'undefined' ? document : null diff --git a/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts b/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts index 6d8a7d5..3e98b57 100644 --- a/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts +++ b/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts @@ -7,7 +7,7 @@ import { SequenceOptions, SequenceRegistrationHandle, } from '@tanstack/hotkeys' -import { getDefaultHotkeysOptions } from './HotkeysCtx' +import { getDefaultHotkeysOptions, ResolvedTarget, Target } from './HotkeysCtx' export interface CreateHotkeySequenceOptions extends Omit< SequenceOptions, @@ -18,7 +18,14 @@ export interface CreateHotkeySequenceOptions extends Omit< * Can be a Svelte ref, direct DOM element, or null. * Defaults to document. */ - target?: HTMLElement | Document | Window | null + target?: Target +} + +function resolveTarget(target: Target): ResolvedTarget { + if (typeof target === 'function') { + return target() ?? null + } + return target } /** @@ -84,7 +91,7 @@ export function createHotkeySequence( }) // Track previous target and sequence to detect changes requiring re-registration - let prevTargetRef: HTMLElement | Document | Window | null = null + let prevTargetRef: ResolvedTarget = null let prevSequenceRef: string | null = null // Normalize to hotkey sequence string (join with spaces) @@ -100,7 +107,7 @@ export function createHotkeySequence( // Resolve target inside the effect so refs are already attached after mount const resolvedTarget = optionsRef?.target - ? optionsRef.target + ? resolveTarget(optionsRef.target) : typeof document !== 'undefined' ? document : null diff --git a/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts b/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts index a462b8f..19285d0 100644 --- a/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts +++ b/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts @@ -1,4 +1,5 @@ import { getKeyStateTracker } from '@tanstack/hotkeys' +import { useStore } from '@tanstack/svelte-store' /** * Svelte function that returns a map of currently held key names to their physical `event.code` values. @@ -26,7 +27,8 @@ import { getKeyStateTracker } from '@tanstack/hotkeys' export function getHeldKeyCodesMap(): Record { const tracker = getKeyStateTracker() - const heldKeyCodesMap = $derived.by(() => tracker.store.state.heldCodes) + const heldKeyCodesMap = useStore(tracker.store, (state) => state.heldCodes) + .current as Record return heldKeyCodesMap } diff --git a/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts b/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts index 4c576b8..d0bd231 100644 --- a/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts +++ b/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts @@ -1,4 +1,5 @@ import { getKeyStateTracker } from '@tanstack/hotkeys' +import { useStore } from '@tanstack/svelte-store' /** * Svelte function that returns an array of currently held keyboard keys. @@ -23,7 +24,8 @@ import { getKeyStateTracker } from '@tanstack/hotkeys' export function getHeldKeys(): Array { const tracker = getKeyStateTracker() - const heldKeys = $derived.by(() => tracker.store.state.heldKeys) + const heldKeys = useStore(tracker.store, (state) => state.heldKeys) + .current as Array return heldKeys } diff --git a/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts b/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts index 58252f1..2f9fa33 100644 --- a/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts +++ b/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts @@ -1,5 +1,6 @@ import { HeldKey } from '@tanstack/hotkeys' import { getKeyStateTracker } from '@tanstack/hotkeys' +import { useStore } from '@tanstack/svelte-store' /** * Svelte function that returns whether a specific key is currently being held. @@ -45,10 +46,9 @@ export function getIsKeyHeld(key: HeldKey): boolean { const tracker = getKeyStateTracker() const normalizedKey = key.toLowerCase() - const isKeyHeld = $derived.by(() => - tracker.store.state.heldKeys.some( - (heldKey) => heldKey.toLowerCase() === normalizedKey, - ), - ) + const isKeyHeld = useStore(tracker.store, (state) => + state.heldKeys.some((heldKey) => heldKey.toLowerCase() === normalizedKey), + ).current as boolean + return isKeyHeld } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 219446d..1526447 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -508,6 +508,38 @@ importers: version: 2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)) examples/svelte/create-hotkey: + dependencies: + '@tanstack/svelte-hotkeys': + specifier: workspace:* + version: link:../../../packages/svelte-hotkeys + devDependencies: + '@sveltejs/adapter-auto': + specifier: ^7.0.0 + version: 7.0.1(@sveltejs/kit@2.53.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.53.1)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2))) + '@sveltejs/kit': + specifier: ^2.50.2 + version: 2.53.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.53.1)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte': + specifier: ^6.2.4 + version: 6.2.4(svelte@5.53.1)(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2)) + svelte: + specifier: ^5.51.0 + version: 5.53.1 + svelte-check: + specifier: ^4.3.6 + version: 4.4.3(picomatch@4.0.3)(svelte@5.53.1)(typescript@5.9.3) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vite: + specifier: ^7.3.1 + version: 7.3.1(@types/node@25.3.0)(jiti@2.6.1)(yaml@2.8.2) + + examples/svelte/create-hotkey-sequence: + dependencies: + '@tanstack/svelte-hotkeys': + specifier: workspace:* + version: link:../../../packages/svelte-hotkeys devDependencies: '@sveltejs/adapter-auto': specifier: ^7.0.0 From 22d5bbef9378e3c1c42a01fae398d8253414239c Mon Sep 17 00:00:00 2001 From: Kunaaal Date: Wed, 25 Feb 2026 22:21:33 +0530 Subject: [PATCH 6/6] refactor: Improve hotkey sequence implementation in Svelte example by adding target option --- .../src/routes/+page.svelte | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/examples/svelte/create-hotkey-sequence/src/routes/+page.svelte b/examples/svelte/create-hotkey-sequence/src/routes/+page.svelte index 00048d3..de308f1 100644 --- a/examples/svelte/create-hotkey-sequence/src/routes/+page.svelte +++ b/examples/svelte/create-hotkey-sequence/src/routes/+page.svelte @@ -3,13 +3,19 @@ let divRef = $state(null) - createHotkeySequence(['K', 'S'], () => { - console.log('K K pressed') + createHotkeySequence( + ['K', 'S'], + () => { + console.log('K K pressed') - if (divRef) { - divRef.style.backgroundColor = 'green' - } - }) + if (divRef) { + divRef.style.backgroundColor = 'green' + } + }, + { + target: () => divRef, + }, + )