From 1512354363061a8776d448fc8abb1501ed96ef2a Mon Sep 17 00:00:00 2001 From: Kunaaal Date: Wed, 25 Feb 2026 13:55:44 +0530 Subject: [PATCH 01/16] 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 02/16] 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 03/16] 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 04/16] 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 05/16] 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 06/16] 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, + }, + )
Date: Thu, 26 Feb 2026 23:10:44 +0530 Subject: [PATCH 07/16] add more examples for svelte --- .../svelte/create-hotkey-recorder/.gitignore | 23 +++++++++++ examples/svelte/create-hotkey-recorder/.npmrc | 1 + .../create-hotkey-recorder/package.json | 26 ++++++++++++ .../create-hotkey-recorder/src/app.d.ts | 13 ++++++ .../create-hotkey-recorder/src/app.html | 11 +++++ .../src/routes/+layout.svelte | 12 ++++++ .../src/routes/+page.svelte | 41 +++++++++++++++++++ .../create-hotkey-recorder/static/robots.txt | 3 ++ .../create-hotkey-recorder/svelte.config.js | 13 ++++++ .../create-hotkey-recorder/tsconfig.json | 20 +++++++++ .../create-hotkey-recorder/vite.config.ts | 6 +++ examples/svelte/get-held-keys/.gitignore | 23 +++++++++++ examples/svelte/get-held-keys/.npmrc | 1 + examples/svelte/get-held-keys/package.json | 26 ++++++++++++ examples/svelte/get-held-keys/src/app.d.ts | 13 ++++++ examples/svelte/get-held-keys/src/app.html | 11 +++++ .../get-held-keys/src/routes/+layout.svelte | 12 ++++++ .../get-held-keys/src/routes/+page.svelte | 29 +++++++++++++ .../svelte/get-held-keys/static/robots.txt | 3 ++ .../svelte/get-held-keys/svelte.config.js | 13 ++++++ examples/svelte/get-held-keys/tsconfig.json | 20 +++++++++ examples/svelte/get-held-keys/vite.config.ts | 6 +++ examples/svelte/get-is-key-held/.gitignore | 23 +++++++++++ examples/svelte/get-is-key-held/.npmrc | 1 + examples/svelte/get-is-key-held/package.json | 26 ++++++++++++ examples/svelte/get-is-key-held/src/app.d.ts | 13 ++++++ examples/svelte/get-is-key-held/src/app.html | 11 +++++ .../get-is-key-held/src/routes/+layout.svelte | 12 ++++++ .../get-is-key-held/src/routes/+page.svelte | 31 ++++++++++++++ .../svelte/get-is-key-held/static/robots.txt | 3 ++ .../svelte/get-is-key-held/svelte.config.js | 13 ++++++ examples/svelte/get-is-key-held/tsconfig.json | 20 +++++++++ .../svelte/get-is-key-held/vite.config.ts | 6 +++ 33 files changed, 485 insertions(+) create mode 100644 examples/svelte/create-hotkey-recorder/.gitignore create mode 100644 examples/svelte/create-hotkey-recorder/.npmrc create mode 100644 examples/svelte/create-hotkey-recorder/package.json create mode 100644 examples/svelte/create-hotkey-recorder/src/app.d.ts create mode 100644 examples/svelte/create-hotkey-recorder/src/app.html create mode 100644 examples/svelte/create-hotkey-recorder/src/routes/+layout.svelte create mode 100644 examples/svelte/create-hotkey-recorder/src/routes/+page.svelte create mode 100644 examples/svelte/create-hotkey-recorder/static/robots.txt create mode 100644 examples/svelte/create-hotkey-recorder/svelte.config.js create mode 100644 examples/svelte/create-hotkey-recorder/tsconfig.json create mode 100644 examples/svelte/create-hotkey-recorder/vite.config.ts create mode 100644 examples/svelte/get-held-keys/.gitignore create mode 100644 examples/svelte/get-held-keys/.npmrc create mode 100644 examples/svelte/get-held-keys/package.json create mode 100644 examples/svelte/get-held-keys/src/app.d.ts create mode 100644 examples/svelte/get-held-keys/src/app.html create mode 100644 examples/svelte/get-held-keys/src/routes/+layout.svelte create mode 100644 examples/svelte/get-held-keys/src/routes/+page.svelte create mode 100644 examples/svelte/get-held-keys/static/robots.txt create mode 100644 examples/svelte/get-held-keys/svelte.config.js create mode 100644 examples/svelte/get-held-keys/tsconfig.json create mode 100644 examples/svelte/get-held-keys/vite.config.ts create mode 100644 examples/svelte/get-is-key-held/.gitignore create mode 100644 examples/svelte/get-is-key-held/.npmrc create mode 100644 examples/svelte/get-is-key-held/package.json create mode 100644 examples/svelte/get-is-key-held/src/app.d.ts create mode 100644 examples/svelte/get-is-key-held/src/app.html create mode 100644 examples/svelte/get-is-key-held/src/routes/+layout.svelte create mode 100644 examples/svelte/get-is-key-held/src/routes/+page.svelte create mode 100644 examples/svelte/get-is-key-held/static/robots.txt create mode 100644 examples/svelte/get-is-key-held/svelte.config.js create mode 100644 examples/svelte/get-is-key-held/tsconfig.json create mode 100644 examples/svelte/get-is-key-held/vite.config.ts diff --git a/examples/svelte/create-hotkey-recorder/.gitignore b/examples/svelte/create-hotkey-recorder/.gitignore new file mode 100644 index 0000000..3b462cb --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/.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-recorder/.npmrc b/examples/svelte/create-hotkey-recorder/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/examples/svelte/create-hotkey-recorder/package.json b/examples/svelte/create-hotkey-recorder/package.json new file mode 100644 index 0000000..b417ce0 --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/package.json @@ -0,0 +1,26 @@ +{ + "name": "create-hotkey-recorder", + "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-recorder/src/app.d.ts b/examples/svelte/create-hotkey-recorder/src/app.d.ts new file mode 100644 index 0000000..da08e6d --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/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-recorder/src/app.html b/examples/svelte/create-hotkey-recorder/src/app.html new file mode 100644 index 0000000..f273cc5 --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/src/app.html @@ -0,0 +1,11 @@ + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/examples/svelte/create-hotkey-recorder/src/routes/+layout.svelte b/examples/svelte/create-hotkey-recorder/src/routes/+layout.svelte new file mode 100644 index 0000000..2472ec7 --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/src/routes/+layout.svelte @@ -0,0 +1,12 @@ + + + + + + +{@render children()} diff --git a/examples/svelte/create-hotkey-recorder/src/routes/+page.svelte b/examples/svelte/create-hotkey-recorder/src/routes/+page.svelte new file mode 100644 index 0000000..25160bf --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/src/routes/+page.svelte @@ -0,0 +1,41 @@ + + +
+

+ {#if recorder.isRecording} + Press a key combination... + {:else if savedHotkey} + Saved: {savedHotkey} + {:else} + No hotkey set + {/if} +

+ + {#if recorder.recordedHotkey} +

Live preview: {recorder.recordedHotkey}

+ {/if} + + + + {#if recorder.isRecording} + + {/if} +
diff --git a/examples/svelte/create-hotkey-recorder/static/robots.txt b/examples/svelte/create-hotkey-recorder/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/examples/svelte/create-hotkey-recorder/svelte.config.js b/examples/svelte/create-hotkey-recorder/svelte.config.js new file mode 100644 index 0000000..2a5c16e --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/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-recorder/tsconfig.json b/examples/svelte/create-hotkey-recorder/tsconfig.json new file mode 100644 index 0000000..2c2ed3c --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/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-recorder/vite.config.ts b/examples/svelte/create-hotkey-recorder/vite.config.ts new file mode 100644 index 0000000..bbf8c7d --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/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/get-held-keys/.gitignore b/examples/svelte/get-held-keys/.gitignore new file mode 100644 index 0000000..3b462cb --- /dev/null +++ b/examples/svelte/get-held-keys/.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/get-held-keys/.npmrc b/examples/svelte/get-held-keys/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/examples/svelte/get-held-keys/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/examples/svelte/get-held-keys/package.json b/examples/svelte/get-held-keys/package.json new file mode 100644 index 0000000..fd67250 --- /dev/null +++ b/examples/svelte/get-held-keys/package.json @@ -0,0 +1,26 @@ +{ + "name": "get-held-keys", + "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/get-held-keys/src/app.d.ts b/examples/svelte/get-held-keys/src/app.d.ts new file mode 100644 index 0000000..da08e6d --- /dev/null +++ b/examples/svelte/get-held-keys/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/get-held-keys/src/app.html b/examples/svelte/get-held-keys/src/app.html new file mode 100644 index 0000000..f273cc5 --- /dev/null +++ b/examples/svelte/get-held-keys/src/app.html @@ -0,0 +1,11 @@ + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/examples/svelte/get-held-keys/src/routes/+layout.svelte b/examples/svelte/get-held-keys/src/routes/+layout.svelte new file mode 100644 index 0000000..2472ec7 --- /dev/null +++ b/examples/svelte/get-held-keys/src/routes/+layout.svelte @@ -0,0 +1,12 @@ + + + + + + +{@render children()} diff --git a/examples/svelte/get-held-keys/src/routes/+page.svelte b/examples/svelte/get-held-keys/src/routes/+page.svelte new file mode 100644 index 0000000..7a4b956 --- /dev/null +++ b/examples/svelte/get-held-keys/src/routes/+page.svelte @@ -0,0 +1,29 @@ + + +
+

Press any keys to see them here:

+ +

+ Held keys: {heldKeys.length > 0 ? heldKeys.join(' + ') : 'None'} +

+ + {#if Object.keys(heldKeyCodesMap).length > 0} +
+ {#each Object.entries(heldKeyCodesMap) as [key, code]} + + {key} ({code}) + + {/each} +
+ {/if} +
diff --git a/examples/svelte/get-held-keys/static/robots.txt b/examples/svelte/get-held-keys/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/examples/svelte/get-held-keys/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/examples/svelte/get-held-keys/svelte.config.js b/examples/svelte/get-held-keys/svelte.config.js new file mode 100644 index 0000000..2a5c16e --- /dev/null +++ b/examples/svelte/get-held-keys/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/get-held-keys/tsconfig.json b/examples/svelte/get-held-keys/tsconfig.json new file mode 100644 index 0000000..2c2ed3c --- /dev/null +++ b/examples/svelte/get-held-keys/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/get-held-keys/vite.config.ts b/examples/svelte/get-held-keys/vite.config.ts new file mode 100644 index 0000000..bbf8c7d --- /dev/null +++ b/examples/svelte/get-held-keys/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/get-is-key-held/.gitignore b/examples/svelte/get-is-key-held/.gitignore new file mode 100644 index 0000000..3b462cb --- /dev/null +++ b/examples/svelte/get-is-key-held/.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/get-is-key-held/.npmrc b/examples/svelte/get-is-key-held/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/examples/svelte/get-is-key-held/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/examples/svelte/get-is-key-held/package.json b/examples/svelte/get-is-key-held/package.json new file mode 100644 index 0000000..dfecd66 --- /dev/null +++ b/examples/svelte/get-is-key-held/package.json @@ -0,0 +1,26 @@ +{ + "name": "get-is-key-held", + "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/get-is-key-held/src/app.d.ts b/examples/svelte/get-is-key-held/src/app.d.ts new file mode 100644 index 0000000..da08e6d --- /dev/null +++ b/examples/svelte/get-is-key-held/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/get-is-key-held/src/app.html b/examples/svelte/get-is-key-held/src/app.html new file mode 100644 index 0000000..f273cc5 --- /dev/null +++ b/examples/svelte/get-is-key-held/src/app.html @@ -0,0 +1,11 @@ + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/examples/svelte/get-is-key-held/src/routes/+layout.svelte b/examples/svelte/get-is-key-held/src/routes/+layout.svelte new file mode 100644 index 0000000..2472ec7 --- /dev/null +++ b/examples/svelte/get-is-key-held/src/routes/+layout.svelte @@ -0,0 +1,12 @@ + + + + + + +{@render children()} diff --git a/examples/svelte/get-is-key-held/src/routes/+page.svelte b/examples/svelte/get-is-key-held/src/routes/+page.svelte new file mode 100644 index 0000000..64a82ac --- /dev/null +++ b/examples/svelte/get-is-key-held/src/routes/+page.svelte @@ -0,0 +1,31 @@ + + +
+

Hold modifier keys to see their state:

+ +
+ + Shift + + + Ctrl + + + Alt + +
+
diff --git a/examples/svelte/get-is-key-held/static/robots.txt b/examples/svelte/get-is-key-held/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/examples/svelte/get-is-key-held/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/examples/svelte/get-is-key-held/svelte.config.js b/examples/svelte/get-is-key-held/svelte.config.js new file mode 100644 index 0000000..2a5c16e --- /dev/null +++ b/examples/svelte/get-is-key-held/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/get-is-key-held/tsconfig.json b/examples/svelte/get-is-key-held/tsconfig.json new file mode 100644 index 0000000..2c2ed3c --- /dev/null +++ b/examples/svelte/get-is-key-held/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/get-is-key-held/vite.config.ts b/examples/svelte/get-is-key-held/vite.config.ts new file mode 100644 index 0000000..bbf8c7d --- /dev/null +++ b/examples/svelte/get-is-key-held/vite.config.ts @@ -0,0 +1,6 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [sveltekit()] +}); From b5572703f305fdbadb7be0ed11f22fb91ab25673 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sun, 8 Mar 2026 14:04:21 -0500 Subject: [PATCH 08/16] fix a bunch of stuff and generate docs --- README.md | 2 +- docs/config.json | 137 +++++ .../svelte/guides/formatting-display.md | 70 +++ .../svelte/guides/hotkey-recording.md | 80 +++ docs/framework/svelte/guides/hotkeys.md | 134 ++++ .../svelte/guides/key-state-tracking.md | 85 +++ docs/framework/svelte/guides/sequences.md | 93 +++ docs/framework/svelte/quick-start.md | 141 +++++ .../reference/functions/createHotkey.md | 81 +++ .../functions/createHotkeyRecorder.md | 59 ++ .../functions/createHotkeySequence.md | 71 +++ .../functions/getDefaultHotkeysOptions.md | 16 + .../reference/functions/getHeldKeyCodesMap.md | 38 ++ .../svelte/reference/functions/getHeldKeys.md | 36 ++ .../reference/functions/getIsKeyHeld.md | 61 ++ docs/framework/svelte/reference/index.md | 37 ++ .../interfaces/CreateHotkeyOptions.md | 26 + .../interfaces/CreateHotkeySequenceOptions.md | 26 + .../interfaces/HotkeysProviderOptions.md | 38 ++ .../interfaces/HotkeysProviderProps.md | 28 + .../interfaces/SvelteHotkeyRecorder.md | 80 +++ .../reference/type-aliases/HotkeysProvider.md | 12 + .../reference/type-aliases/ResolvedTarget.md | 12 + .../svelte/reference/type-aliases/Target.md | 14 + .../reference/variables/DEFAULT_OPTIONS.md | 12 + .../reference/variables/HotkeysProvider.md | 12 + .../reference/variables/getHotkeysContext.md | 16 + .../reference/variables/setHotkeysContext.md | 22 + .../svelte/create-hotkey-recorder/index.html | 14 + .../create-hotkey-recorder/package.json | 19 +- .../create-hotkey-recorder/src/App.svelte | 271 ++++++++ .../src/ShortcutListItem.svelte | 51 ++ .../create-hotkey-recorder/src/app.d.ts | 13 - .../create-hotkey-recorder/src/app.html | 11 - .../create-hotkey-recorder/src/index.css | 256 ++++++++ .../svelte/create-hotkey-recorder/src/main.ts | 5 + .../src/routes/+layout.svelte | 12 - .../src/routes/+page.svelte | 41 -- .../create-hotkey-recorder/svelte.config.js | 12 +- .../create-hotkey-recorder/tsconfig.json | 24 +- .../create-hotkey-recorder/vite.config.ts | 8 +- .../svelte/create-hotkey-sequence/index.html | 14 + .../create-hotkey-sequence/package.json | 19 +- .../create-hotkey-sequence/src/App.svelte | 206 +++++++ .../create-hotkey-sequence/src/app.d.ts | 13 - .../create-hotkey-sequence/src/app.html | 11 - .../create-hotkey-sequence/src/index.css | 162 +++++ .../svelte/create-hotkey-sequence/src/main.ts | 5 + .../src/routes/+layout.svelte | 12 - .../src/routes/+page.svelte | 25 - .../create-hotkey-sequence/svelte.config.js | 12 +- .../create-hotkey-sequence/tsconfig.json | 24 +- .../create-hotkey-sequence/vite.config.ts | 8 +- examples/svelte/create-hotkey/README.md | 42 -- examples/svelte/create-hotkey/index.html | 14 + examples/svelte/create-hotkey/package.json | 20 +- examples/svelte/create-hotkey/src/App.svelte | 581 ++++++++++++++++++ examples/svelte/create-hotkey/src/app.d.ts | 13 - examples/svelte/create-hotkey/src/app.html | 11 - examples/svelte/create-hotkey/src/index.css | 212 +++++++ examples/svelte/create-hotkey/src/main.ts | 5 + .../create-hotkey/src/routes/+layout.svelte | 12 - .../create-hotkey/src/routes/+page.svelte | 34 - .../svelte/create-hotkey/svelte.config.js | 12 +- examples/svelte/create-hotkey/tsconfig.json | 24 +- examples/svelte/create-hotkey/vite.config.ts | 8 +- examples/svelte/get-held-keys/index.html | 14 + examples/svelte/get-held-keys/package.json | 19 +- examples/svelte/get-held-keys/src/App.svelte | 114 ++++ examples/svelte/get-held-keys/src/app.d.ts | 13 - examples/svelte/get-held-keys/src/app.html | 11 - examples/svelte/get-held-keys/src/index.css | 131 ++++ examples/svelte/get-held-keys/src/main.ts | 5 + .../get-held-keys/src/routes/+layout.svelte | 12 - .../get-held-keys/src/routes/+page.svelte | 29 - .../svelte/get-held-keys/svelte.config.js | 12 +- examples/svelte/get-held-keys/tsconfig.json | 24 +- examples/svelte/get-held-keys/vite.config.ts | 8 +- examples/svelte/get-is-key-held/index.html | 14 + examples/svelte/get-is-key-held/package.json | 19 +- .../svelte/get-is-key-held/src/App.svelte | 94 +++ examples/svelte/get-is-key-held/src/app.d.ts | 13 - examples/svelte/get-is-key-held/src/app.html | 11 - examples/svelte/get-is-key-held/src/index.css | 127 ++++ examples/svelte/get-is-key-held/src/main.ts | 5 + .../get-is-key-held/src/routes/+layout.svelte | 12 - .../get-is-key-held/src/routes/+page.svelte | 31 - .../svelte/get-is-key-held/svelte.config.js | 12 +- examples/svelte/get-is-key-held/tsconfig.json | 24 +- .../svelte/get-is-key-held/vite.config.ts | 8 +- package.json | 2 +- packages/angular-hotkeys/README.md | 2 +- packages/hotkeys-devtools/README.md | 2 +- packages/hotkeys/README.md | 2 +- packages/preact-hotkeys-devtools/README.md | 2 +- packages/preact-hotkeys/README.md | 2 +- packages/react-hotkeys-devtools/README.md | 2 +- packages/react-hotkeys/README.md | 2 +- packages/solid-hotkeys-devtools/README.md | 2 +- packages/solid-hotkeys/README.md | 2 +- packages/svelte-hotkeys/README.md | 122 ++++ packages/svelte-hotkeys/eslint.config.js | 5 +- packages/svelte-hotkeys/package.json | 7 +- packages/svelte-hotkeys/src/HotkeysCtx.ts | 9 +- .../svelte-hotkeys/src/createHotkey.svelte.ts | 5 +- .../src/createHotkeyRecorder.svelte.ts | 8 +- .../src/createHotkeySequence.svelte.ts | 16 +- .../src/getHeldKeyCodesMap.svelte.ts | 6 +- .../svelte-hotkeys/src/getHeldKeys.svelte.ts | 3 +- .../svelte-hotkeys/src/getIsKeyHeld.svelte.ts | 4 +- packages/svelte-hotkeys/src/index.ts | 5 +- .../{svelte.config.ts => svelte.config.js} | 0 packages/svelte-hotkeys/tsconfig.docs.json | 9 + packages/svelte-hotkeys/tsconfig.json | 6 +- packages/svelte-hotkeys/vitest.config.ts | 10 + packages/vue-hotkeys-devtools/README.md | 2 +- packages/vue-hotkeys/README.md | 2 +- pnpm-lock.yaml | 190 +----- scripts/generate-docs.ts | 12 + vitest.workspace.ts | 2 + 120 files changed, 4069 insertions(+), 811 deletions(-) create mode 100644 docs/framework/svelte/guides/formatting-display.md create mode 100644 docs/framework/svelte/guides/hotkey-recording.md create mode 100644 docs/framework/svelte/guides/hotkeys.md create mode 100644 docs/framework/svelte/guides/key-state-tracking.md create mode 100644 docs/framework/svelte/guides/sequences.md create mode 100644 docs/framework/svelte/quick-start.md create mode 100644 docs/framework/svelte/reference/functions/createHotkey.md create mode 100644 docs/framework/svelte/reference/functions/createHotkeyRecorder.md create mode 100644 docs/framework/svelte/reference/functions/createHotkeySequence.md create mode 100644 docs/framework/svelte/reference/functions/getDefaultHotkeysOptions.md create mode 100644 docs/framework/svelte/reference/functions/getHeldKeyCodesMap.md create mode 100644 docs/framework/svelte/reference/functions/getHeldKeys.md create mode 100644 docs/framework/svelte/reference/functions/getIsKeyHeld.md create mode 100644 docs/framework/svelte/reference/index.md create mode 100644 docs/framework/svelte/reference/interfaces/CreateHotkeyOptions.md create mode 100644 docs/framework/svelte/reference/interfaces/CreateHotkeySequenceOptions.md create mode 100644 docs/framework/svelte/reference/interfaces/HotkeysProviderOptions.md create mode 100644 docs/framework/svelte/reference/interfaces/HotkeysProviderProps.md create mode 100644 docs/framework/svelte/reference/interfaces/SvelteHotkeyRecorder.md create mode 100644 docs/framework/svelte/reference/type-aliases/HotkeysProvider.md create mode 100644 docs/framework/svelte/reference/type-aliases/ResolvedTarget.md create mode 100644 docs/framework/svelte/reference/type-aliases/Target.md create mode 100644 docs/framework/svelte/reference/variables/DEFAULT_OPTIONS.md create mode 100644 docs/framework/svelte/reference/variables/HotkeysProvider.md create mode 100644 docs/framework/svelte/reference/variables/getHotkeysContext.md create mode 100644 docs/framework/svelte/reference/variables/setHotkeysContext.md create mode 100644 examples/svelte/create-hotkey-recorder/index.html create mode 100644 examples/svelte/create-hotkey-recorder/src/App.svelte create mode 100644 examples/svelte/create-hotkey-recorder/src/ShortcutListItem.svelte delete mode 100644 examples/svelte/create-hotkey-recorder/src/app.d.ts delete mode 100644 examples/svelte/create-hotkey-recorder/src/app.html create mode 100644 examples/svelte/create-hotkey-recorder/src/index.css create mode 100644 examples/svelte/create-hotkey-recorder/src/main.ts delete mode 100644 examples/svelte/create-hotkey-recorder/src/routes/+layout.svelte delete mode 100644 examples/svelte/create-hotkey-recorder/src/routes/+page.svelte create mode 100644 examples/svelte/create-hotkey-sequence/index.html create mode 100644 examples/svelte/create-hotkey-sequence/src/App.svelte delete mode 100644 examples/svelte/create-hotkey-sequence/src/app.d.ts delete mode 100644 examples/svelte/create-hotkey-sequence/src/app.html create mode 100644 examples/svelte/create-hotkey-sequence/src/index.css create mode 100644 examples/svelte/create-hotkey-sequence/src/main.ts delete mode 100644 examples/svelte/create-hotkey-sequence/src/routes/+layout.svelte delete mode 100644 examples/svelte/create-hotkey-sequence/src/routes/+page.svelte delete mode 100644 examples/svelte/create-hotkey/README.md create mode 100644 examples/svelte/create-hotkey/index.html create mode 100644 examples/svelte/create-hotkey/src/App.svelte delete mode 100644 examples/svelte/create-hotkey/src/app.d.ts delete mode 100644 examples/svelte/create-hotkey/src/app.html create mode 100644 examples/svelte/create-hotkey/src/index.css create mode 100644 examples/svelte/create-hotkey/src/main.ts delete mode 100644 examples/svelte/create-hotkey/src/routes/+layout.svelte delete mode 100644 examples/svelte/create-hotkey/src/routes/+page.svelte create mode 100644 examples/svelte/get-held-keys/index.html create mode 100644 examples/svelte/get-held-keys/src/App.svelte delete mode 100644 examples/svelte/get-held-keys/src/app.d.ts delete mode 100644 examples/svelte/get-held-keys/src/app.html create mode 100644 examples/svelte/get-held-keys/src/index.css create mode 100644 examples/svelte/get-held-keys/src/main.ts delete mode 100644 examples/svelte/get-held-keys/src/routes/+layout.svelte delete mode 100644 examples/svelte/get-held-keys/src/routes/+page.svelte create mode 100644 examples/svelte/get-is-key-held/index.html create mode 100644 examples/svelte/get-is-key-held/src/App.svelte delete mode 100644 examples/svelte/get-is-key-held/src/app.d.ts delete mode 100644 examples/svelte/get-is-key-held/src/app.html create mode 100644 examples/svelte/get-is-key-held/src/index.css create mode 100644 examples/svelte/get-is-key-held/src/main.ts delete mode 100644 examples/svelte/get-is-key-held/src/routes/+layout.svelte delete mode 100644 examples/svelte/get-is-key-held/src/routes/+page.svelte create mode 100644 packages/svelte-hotkeys/README.md rename packages/svelte-hotkeys/{svelte.config.ts => svelte.config.js} (100%) create mode 100644 packages/svelte-hotkeys/tsconfig.docs.json create mode 100644 packages/svelte-hotkeys/vitest.config.ts diff --git a/README.md b/README.md index 1c80cd6..8c67395 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/docs/config.json b/docs/config.json index 544aa64..be973df 100644 --- a/docs/config.json +++ b/docs/config.json @@ -67,6 +67,15 @@ "to": "framework/vue/quick-start" } ] + }, + { + "label": "svelte", + "children": [ + { + "label": "Quick Start", + "to": "framework/svelte/quick-start" + } + ] } ] }, @@ -198,6 +207,31 @@ "to": "framework/vue/guides/formatting-display" } ] + }, + { + "label": "svelte", + "children": [ + { + "label": "Hotkeys", + "to": "framework/svelte/guides/hotkeys" + }, + { + "label": "Sequences", + "to": "framework/svelte/guides/sequences" + }, + { + "label": "Hotkey Recording", + "to": "framework/svelte/guides/hotkey-recording" + }, + { + "label": "Key State Tracking", + "to": "framework/svelte/guides/key-state-tracking" + }, + { + "label": "Formatting & Display", + "to": "framework/svelte/guides/formatting-display" + } + ] } ] }, @@ -254,6 +288,15 @@ "to": "framework/vue/reference/index" } ] + }, + { + "label": "svelte", + "children": [ + { + "label": "Svelte APIs", + "to": "framework/svelte/reference/index" + } + ] } ] }, @@ -376,6 +419,23 @@ "to": "framework/vue/reference/interfaces/HotkeysProviderOptions" } ] + }, + { + "label": "svelte", + "children": [ + { + "label": "createHotkey", + "to": "framework/svelte/reference/functions/createHotkey" + }, + { + "label": "CreateHotkeyOptions", + "to": "framework/svelte/reference/interfaces/CreateHotkeyOptions" + }, + { + "label": "HotkeysProviderOptions", + "to": "framework/svelte/reference/interfaces/HotkeysProviderOptions" + } + ] } ] }, @@ -470,6 +530,19 @@ "to": "framework/vue/reference/interfaces/UseHotkeySequenceOptions" } ] + }, + { + "label": "svelte", + "children": [ + { + "label": "createHotkeySequence", + "to": "framework/svelte/reference/functions/createHotkeySequence" + }, + { + "label": "CreateHotkeySequenceOptions", + "to": "framework/svelte/reference/interfaces/CreateHotkeySequenceOptions" + } + ] } ] }, @@ -540,6 +613,15 @@ "to": "framework/vue/reference/functions/useKeyHold" } ] + }, + { + "label": "svelte", + "children": [ + { + "label": "getIsKeyHeld", + "to": "framework/svelte/reference/functions/getIsKeyHeld" + } + ] } ] }, @@ -630,6 +712,23 @@ "to": "framework/vue/reference/functions/useHeldKeyCodes" } ] + }, + { + "label": "svelte", + "children": [ + { + "label": "getHeldKeys", + "to": "framework/svelte/reference/functions/getHeldKeys" + }, + { + "label": "getHeldKeyCodesMap", + "to": "framework/svelte/reference/functions/getHeldKeyCodesMap" + }, + { + "label": "getIsKeyHeld", + "to": "framework/svelte/reference/functions/getIsKeyHeld" + } + ] } ] }, @@ -716,6 +815,19 @@ "to": "framework/vue/reference/interfaces/VueHotkeyRecorder" } ] + }, + { + "label": "svelte", + "children": [ + { + "label": "createHotkeyRecorder", + "to": "framework/svelte/reference/functions/createHotkeyRecorder" + }, + { + "label": "SvelteHotkeyRecorder", + "to": "framework/svelte/reference/interfaces/SvelteHotkeyRecorder" + } + ] } ] }, @@ -906,6 +1018,31 @@ "to": "framework/vue/examples/useKeyhold" } ] + }, + { + "label": "svelte", + "children": [ + { + "label": "createHotkey", + "to": "framework/svelte/examples/create-hotkey" + }, + { + "label": "createHotkeySequence", + "to": "framework/svelte/examples/create-hotkey-sequence" + }, + { + "label": "createHotkeyRecorder", + "to": "framework/svelte/examples/create-hotkey-recorder" + }, + { + "label": "getHeldKeys", + "to": "framework/svelte/examples/get-held-keys" + }, + { + "label": "getIsKeyHeld", + "to": "framework/svelte/examples/get-is-key-held" + } + ] } ] } diff --git a/docs/framework/svelte/guides/formatting-display.md b/docs/framework/svelte/guides/formatting-display.md new file mode 100644 index 0000000..f13d857 --- /dev/null +++ b/docs/framework/svelte/guides/formatting-display.md @@ -0,0 +1,70 @@ +--- +title: Formatting & Display Guide +id: formatting-display +--- + +TanStack Hotkeys includes utilities for turning hotkey strings into display-friendly labels. These utilities are framework-agnostic, but they pair naturally with Svelte templates and reactive UI. + +## `formatForDisplay` + +```ts +import { formatForDisplay } from '@tanstack/svelte-hotkeys' + +formatForDisplay('Mod+S') +formatForDisplay('Mod+Shift+Z') +``` + +## `formatWithLabels` + +```ts +import { formatWithLabels } from '@tanstack/svelte-hotkeys' + +formatWithLabels('Mod+S') +formatWithLabels('Mod+Shift+Z') +``` + +## `formatKeyForDebuggingDisplay` + +```ts +import { formatKeyForDebuggingDisplay } from '@tanstack/svelte-hotkeys' + +formatKeyForDebuggingDisplay('Meta') +formatKeyForDebuggingDisplay('Shift') +``` + +## Using Formatted Hotkeys in Svelte + +### Keyboard Shortcut Badges + +```svelte + + +{formatForDisplay(hotkey)} +``` + +### Menu Items with Hotkeys + +```svelte + + + +``` + +## Validation + +```ts +import { validateHotkey } from '@tanstack/svelte-hotkeys' + +const result = validateHotkey('Alt+A') +``` diff --git a/docs/framework/svelte/guides/hotkey-recording.md b/docs/framework/svelte/guides/hotkey-recording.md new file mode 100644 index 0000000..98472c6 --- /dev/null +++ b/docs/framework/svelte/guides/hotkey-recording.md @@ -0,0 +1,80 @@ +--- +title: Hotkey Recording Guide +id: hotkey-recording +--- + +TanStack Hotkeys provides the `createHotkeyRecorder` function for building shortcut customization UIs in Svelte. + +## Basic Usage + +```svelte + + +
+ + {#if recorder.isRecording} + + {/if} +
+``` + +## Return Value + +- `isRecording`: whether recording is active +- `recordedHotkey`: the most recently recorded hotkey +- `startRecording()`: start listening for key presses +- `stopRecording()`: stop listening and keep the current recording +- `cancelRecording()`: stop listening and discard the in-progress recording + +## Options + +```ts +createHotkeyRecorder({ + onRecord: (hotkey) => {}, + onCancel: () => {}, + onClear: () => {}, +}) +``` + +## Global Default Options via Provider + +```svelte + + + console.log('Recording cancelled'), + }, + }} +> + + +``` + +## Recording Behavior + +- Modifier-only presses do not complete a recording. +- Modifier plus key combinations record the full shortcut. +- Escape cancels recording. +- Backspace and Delete clear the shortcut. +- Recorded values are normalized to portable `Mod` format. diff --git a/docs/framework/svelte/guides/hotkeys.md b/docs/framework/svelte/guides/hotkeys.md new file mode 100644 index 0000000..f54e0f2 --- /dev/null +++ b/docs/framework/svelte/guides/hotkeys.md @@ -0,0 +1,134 @@ +--- +title: Hotkeys Guide +id: hotkeys +--- + +The `createHotkey` function is the primary way to register keyboard shortcuts in Svelte applications. It wraps the singleton `HotkeyManager` with automatic cleanup, support for refs, and reactive option syncing. + +## Basic Usage + +```svelte + +``` + +The callback receives the original `KeyboardEvent` as the first argument and a `HotkeyCallbackContext` as the second: + +```ts +createHotkey('Mod+S', (event, context) => { + console.log(context.hotkey) + console.log(context.parsedHotkey) +}) +``` + +## Default Options + +`createHotkey` uses the same core defaults as the framework-agnostic manager: + +```ts +createHotkey('Mod+S', callback, { + enabled: true, + preventDefault: true, + stopPropagation: true, + eventType: 'keydown', + requireReset: false, + ignoreInputs: undefined, + target: document, + platform: undefined, + conflictBehavior: 'warn', +}) +``` + +## Reactive Options + +Svelte options can be plain values or getter functions for reactive state. + +### `enabled` + +```svelte + +``` + +### `target` + +```svelte + + +
Panel content
+``` + +## Global Default Options via Provider + +```svelte + + + + + +``` + +## Common Options + +### `requireReset` + +```ts +createHotkey('Escape', () => closePanel(), { requireReset: true }) +``` + +### `ignoreInputs` + +```ts +createHotkey('K', () => openSearch()) +createHotkey('Enter', () => submit(), { ignoreInputs: false }) +``` + +### `conflictBehavior` + +```ts +createHotkey('Mod+S', () => save(), { conflictBehavior: 'replace' }) +``` + +### `platform` + +```ts +createHotkey('Mod+S', () => save(), { platform: 'mac' }) +``` + +## Automatic Cleanup + +Hotkeys are automatically unregistered when the owning component unmounts. + +## The Hotkey Manager + +You can always reach for the underlying manager directly: + +```ts +import { getHotkeyManager } from '@tanstack/svelte-hotkeys' + +const manager = getHotkeyManager() +manager.isRegistered('Mod+S') +manager.getRegistrationCount() +``` diff --git a/docs/framework/svelte/guides/key-state-tracking.md b/docs/framework/svelte/guides/key-state-tracking.md new file mode 100644 index 0000000..f6291f8 --- /dev/null +++ b/docs/framework/svelte/guides/key-state-tracking.md @@ -0,0 +1,85 @@ +--- +title: Key State Tracking Guide +id: key-state-tracking +--- + +TanStack Hotkeys provides three Svelte functions for tracking live keyboard state: `getHeldKeys`, `getHeldKeyCodesMap`, and `getIsKeyHeld`. + +## `getHeldKeys` + +```svelte + + +
{$heldKeys.length > 0 ? $heldKeys.join(' + ') : 'No keys held'}
+``` + +## `getHeldKeyCodesMap` + +```svelte + +``` + +## `getIsKeyHeld` + +```svelte + + +Shift +``` + +## Common Patterns + +### Hold-to-Reveal UI + +```svelte + + +{#if isShiftHeld} + +{:else} + +{/if} +``` + +### Debugging Key Display + +```svelte + +``` + +## Under the Hood + +All three functions subscribe to the singleton `KeyStateTracker`: + +```ts +import { getKeyStateTracker } from '@tanstack/svelte-hotkeys' + +const tracker = getKeyStateTracker() +tracker.getHeldKeys() +tracker.isKeyHeld('Shift') +``` diff --git a/docs/framework/svelte/guides/sequences.md b/docs/framework/svelte/guides/sequences.md new file mode 100644 index 0000000..7668a6c --- /dev/null +++ b/docs/framework/svelte/guides/sequences.md @@ -0,0 +1,93 @@ +--- +title: Sequences Guide +id: sequences +--- + +TanStack Hotkeys supports multi-key sequences in Svelte, where keys are pressed one after another rather than simultaneously. + +## Basic Usage + +```svelte + +``` + +## Sequence Options + +```ts +createHotkeySequence(['G', 'G'], callback, { + timeout: 1000, + enabled: true, +}) +``` + +### Reactive `enabled` + +```svelte + +``` + +## Global Default Options via Provider + +```svelte + + + + + +``` + +## Common Patterns + +### Vim-Style Navigation + +```ts +createHotkeySequence(['G', 'G'], () => scrollToTop()) +createHotkeySequence(['G', 'Shift+G'], () => scrollToBottom()) +createHotkeySequence(['D', 'D'], () => deleteLine()) +createHotkeySequence(['D', 'W'], () => deleteWord()) +createHotkeySequence(['C', 'I', 'W'], () => changeInnerWord()) +``` + +### Konami Code + +```ts +createHotkeySequence( + ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'B', 'A'], + () => enableEasterEgg(), + { timeout: 2000 }, +) +``` + +## Under the Hood + +`createHotkeySequence` uses the singleton `SequenceManager`. You can also access it directly: + +```ts +import { + createSequenceMatcher, + getSequenceManager, +} from '@tanstack/svelte-hotkeys' + +const manager = getSequenceManager() +const matcher = createSequenceMatcher(['G', 'G'], { timeout: 1000 }) +``` diff --git a/docs/framework/svelte/quick-start.md b/docs/framework/svelte/quick-start.md new file mode 100644 index 0000000..1a6a857 --- /dev/null +++ b/docs/framework/svelte/quick-start.md @@ -0,0 +1,141 @@ +--- +title: Quick Start +id: quick-start +--- + +## Installation + +Don't have TanStack Hotkeys installed yet? See the [Installation](../../installation) page for instructions. + +## Your First Hotkey + +The `createHotkey` function is the primary way to register keyboard shortcuts in Svelte: + +```svelte + + +
Press Cmd+S (Mac) or Ctrl+S (Windows) to save
+``` + +The `Mod` modifier automatically resolves to `Meta` (Command) on macOS and `Control` on Windows/Linux, so your shortcuts work across platforms without extra logic. + +## Common Patterns + +### Multiple Hotkeys + +```svelte + +``` + +### Scoped Hotkeys with Refs + +```svelte + + +
+

Press Escape while focused here to close

+
+``` + +### Conditional Hotkeys + +```svelte + +``` + +### Multi-Key Sequences + +```svelte + +``` + +### Tracking Held Keys + +```svelte + + +
+ {#if isShiftHeld}Shift mode active{/if} + {#if $heldKeys.length > 0}Keys: {$heldKeys.join('+')}{/if} +
+``` + +### Displaying Hotkeys in the UI + +```svelte + + + +``` + +## Default Options Provider + +Wrap part of your app with `HotkeysProvider` to set default options for all Svelte hotkey functions in that subtree: + +```svelte + + + console.log('Recording cancelled') }, + }} +> + + +``` + +## Next Steps + +- [Hotkeys Guide](./guides/hotkeys) +- [Sequences Guide](./guides/sequences) +- [Hotkey Recording Guide](./guides/hotkey-recording) +- [Key State Tracking Guide](./guides/key-state-tracking) +- [Formatting & Display Guide](./guides/formatting-display) diff --git a/docs/framework/svelte/reference/functions/createHotkey.md b/docs/framework/svelte/reference/functions/createHotkey.md new file mode 100644 index 0000000..09e01fa --- /dev/null +++ b/docs/framework/svelte/reference/functions/createHotkey.md @@ -0,0 +1,81 @@ +--- +id: createHotkey +title: createHotkey +--- + +# Function: createHotkey() + +```ts +function createHotkey( + hotkey, + callback, + options): void; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkey.svelte.ts:84](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkey.svelte.ts#L84) + +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. + +## Parameters + +### hotkey + +`RegisterableHotkey` + +### callback + +`HotkeyCallback` + +### options + +[`CreateHotkeyOptions`](../interfaces/CreateHotkeyOptions.md) = `{}` + +## Returns + +`void` + +## Examples + +```svelte + + + +
+ .... +
+``` + +```svelte + + +
+ Count: {count} +
+``` diff --git a/docs/framework/svelte/reference/functions/createHotkeyRecorder.md b/docs/framework/svelte/reference/functions/createHotkeyRecorder.md new file mode 100644 index 0000000..8df7fe4 --- /dev/null +++ b/docs/framework/svelte/reference/functions/createHotkeyRecorder.md @@ -0,0 +1,59 @@ +--- +id: createHotkeyRecorder +title: createHotkeyRecorder +--- + +# Function: createHotkeyRecorder() + +```ts +function createHotkeyRecorder(options): SvelteHotkeyRecorder; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:56](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L56) + +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. + +## Parameters + +### options + +`HotkeyRecorderOptions` + +Configuration options for the recorder + +## Returns + +[`SvelteHotkeyRecorder`](../interfaces/SvelteHotkeyRecorder.md) + +An object with recording state and control functions + +## Example + +```svelte + + +
+ + {recorder.recordedHotkey && ( +
Recording: {recorder.recordedHotkey}
+ )} +
+``` diff --git a/docs/framework/svelte/reference/functions/createHotkeySequence.md b/docs/framework/svelte/reference/functions/createHotkeySequence.md new file mode 100644 index 0000000..c398913 --- /dev/null +++ b/docs/framework/svelte/reference/functions/createHotkeySequence.md @@ -0,0 +1,71 @@ +--- +id: createHotkeySequence +title: createHotkeySequence +--- + +# Function: createHotkeySequence() + +```ts +function createHotkeySequence( + sequence, + callback, + options): void; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:69](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L69) + +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. + +## Parameters + +### sequence + +`HotkeySequence` + +Array of hotkey strings that form the sequence + +### callback + +`HotkeyCallback` + +Function to call when the sequence is completed + +### options + +[`CreateHotkeySequenceOptions`](../interfaces/CreateHotkeySequenceOptions.md) = `{}` + +Options for the sequence behavior + +## Returns + +`void` + +## Example + +```svelte + + +
+ .... +
+``` diff --git a/docs/framework/svelte/reference/functions/getDefaultHotkeysOptions.md b/docs/framework/svelte/reference/functions/getDefaultHotkeysOptions.md new file mode 100644 index 0000000..0948846 --- /dev/null +++ b/docs/framework/svelte/reference/functions/getDefaultHotkeysOptions.md @@ -0,0 +1,16 @@ +--- +id: getDefaultHotkeysOptions +title: getDefaultHotkeysOptions +--- + +# Function: getDefaultHotkeysOptions() + +```ts +function getDefaultHotkeysOptions(): HotkeysProviderOptions; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:33](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L33) + +## Returns + +[`HotkeysProviderOptions`](../interfaces/HotkeysProviderOptions.md) diff --git a/docs/framework/svelte/reference/functions/getHeldKeyCodesMap.md b/docs/framework/svelte/reference/functions/getHeldKeyCodesMap.md new file mode 100644 index 0000000..967e846 --- /dev/null +++ b/docs/framework/svelte/reference/functions/getHeldKeyCodesMap.md @@ -0,0 +1,38 @@ +--- +id: getHeldKeyCodesMap +title: getHeldKeyCodesMap +--- + +# Function: getHeldKeyCodesMap() + +```ts +function getHeldKeyCodesMap(): Record; +``` + +Defined in: [packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts:27](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts#L27) + +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`\<`string`, `string`\> + +Record mapping normalized key names to their `event.code` values + +```svelte + + +
+ {Object.entries(heldKeyCodesMap).map(([key, code]) => ( + + {key} {code} + + ))} +
+``` diff --git a/docs/framework/svelte/reference/functions/getHeldKeys.md b/docs/framework/svelte/reference/functions/getHeldKeys.md new file mode 100644 index 0000000..f5c1f19 --- /dev/null +++ b/docs/framework/svelte/reference/functions/getHeldKeys.md @@ -0,0 +1,36 @@ +--- +id: getHeldKeys +title: getHeldKeys +--- + +# Function: getHeldKeys() + +```ts +function getHeldKeys(): string[]; +``` + +Defined in: [packages/svelte-hotkeys/src/getHeldKeys.svelte.ts:24](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts#L24) + +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 + +`string`[] + +Array of currently held key names + +## Example + +```svelte + +
+ Currently pressed: {getHeldKeys().join(' + ') || 'None'} +
+``` diff --git a/docs/framework/svelte/reference/functions/getIsKeyHeld.md b/docs/framework/svelte/reference/functions/getIsKeyHeld.md new file mode 100644 index 0000000..f2f0442 --- /dev/null +++ b/docs/framework/svelte/reference/functions/getIsKeyHeld.md @@ -0,0 +1,61 @@ +--- +id: getIsKeyHeld +title: getIsKeyHeld +--- + +# Function: getIsKeyHeld() + +```ts +function getIsKeyHeld(key): boolean; +``` + +Defined in: [packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts:45](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts#L45) + +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. + +## Parameters + +### key + +`HeldKey` + +The key to check (e.g., 'Shift', 'Control', 'A') + +## Returns + +`boolean` + +True if the key is currently held down + +## Examples + +```svelte + + +
+ {isShiftHeld ? 'Shift is pressed!' : 'Press Shift'} +
+``` + +```svelte + + +
+ Ctrl + Shift + Alt +
+``` diff --git a/docs/framework/svelte/reference/index.md b/docs/framework/svelte/reference/index.md new file mode 100644 index 0000000..1ccd2a6 --- /dev/null +++ b/docs/framework/svelte/reference/index.md @@ -0,0 +1,37 @@ +--- +id: "@tanstack/svelte-hotkeys" +title: "@tanstack/svelte-hotkeys" +--- + +# @tanstack/svelte-hotkeys + +## Interfaces + +- [CreateHotkeyOptions](interfaces/CreateHotkeyOptions.md) +- [CreateHotkeySequenceOptions](interfaces/CreateHotkeySequenceOptions.md) +- [HotkeysProviderOptions](interfaces/HotkeysProviderOptions.md) +- [HotkeysProviderProps](interfaces/HotkeysProviderProps.md) +- [SvelteHotkeyRecorder](interfaces/SvelteHotkeyRecorder.md) + +## Type Aliases + +- [HotkeysProvider](type-aliases/HotkeysProvider.md) +- [ResolvedTarget](type-aliases/ResolvedTarget.md) +- [Target](type-aliases/Target.md) + +## Variables + +- [DEFAULT\_OPTIONS](variables/DEFAULT_OPTIONS.md) +- [getHotkeysContext](variables/getHotkeysContext.md) +- [HotkeysProvider](variables/HotkeysProvider.md) +- [setHotkeysContext](variables/setHotkeysContext.md) + +## Functions + +- [createHotkey](functions/createHotkey.md) +- [createHotkeyRecorder](functions/createHotkeyRecorder.md) +- [createHotkeySequence](functions/createHotkeySequence.md) +- [getDefaultHotkeysOptions](functions/getDefaultHotkeysOptions.md) +- [getHeldKeyCodesMap](functions/getHeldKeyCodesMap.md) +- [getHeldKeys](functions/getHeldKeys.md) +- [getIsKeyHeld](functions/getIsKeyHeld.md) diff --git a/docs/framework/svelte/reference/interfaces/CreateHotkeyOptions.md b/docs/framework/svelte/reference/interfaces/CreateHotkeyOptions.md new file mode 100644 index 0000000..99768d1 --- /dev/null +++ b/docs/framework/svelte/reference/interfaces/CreateHotkeyOptions.md @@ -0,0 +1,26 @@ +--- +id: CreateHotkeyOptions +title: CreateHotkeyOptions +--- + +# Interface: CreateHotkeyOptions + +Defined in: [packages/svelte-hotkeys/src/createHotkey.svelte.ts:17](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkey.svelte.ts#L17) + +## Extends + +- `Omit`\<`HotkeyOptions`, `"target"`\> + +## Properties + +### target? + +```ts +optional target: Target; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkey.svelte.ts:23](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkey.svelte.ts#L23) + +The DOM element to attach the event listener to. +Can be a Svelte ref, direct DOM element, or null. +Defaults to document. diff --git a/docs/framework/svelte/reference/interfaces/CreateHotkeySequenceOptions.md b/docs/framework/svelte/reference/interfaces/CreateHotkeySequenceOptions.md new file mode 100644 index 0000000..cf2a32d --- /dev/null +++ b/docs/framework/svelte/reference/interfaces/CreateHotkeySequenceOptions.md @@ -0,0 +1,26 @@ +--- +id: CreateHotkeySequenceOptions +title: CreateHotkeySequenceOptions +--- + +# Interface: CreateHotkeySequenceOptions + +Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:14](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L14) + +## Extends + +- `Omit`\<`SequenceOptions`, `"target"`\> + +## Properties + +### target? + +```ts +optional target: Target; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:23](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L23) + +The DOM element to attach the event listener to. +Can be a Svelte ref, direct DOM element, or null. +Defaults to document. diff --git a/docs/framework/svelte/reference/interfaces/HotkeysProviderOptions.md b/docs/framework/svelte/reference/interfaces/HotkeysProviderOptions.md new file mode 100644 index 0000000..20b2e87 --- /dev/null +++ b/docs/framework/svelte/reference/interfaces/HotkeysProviderOptions.md @@ -0,0 +1,38 @@ +--- +id: HotkeysProviderOptions +title: HotkeysProviderOptions +--- + +# Interface: HotkeysProviderOptions + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:11](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L11) + +## Properties + +### hotkey? + +```ts +optional hotkey: Partial; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:12](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L12) + +*** + +### hotkeyRecorder? + +```ts +optional hotkeyRecorder: Partial; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:13](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L13) + +*** + +### hotkeySequence? + +```ts +optional hotkeySequence: Partial; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:14](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L14) diff --git a/docs/framework/svelte/reference/interfaces/HotkeysProviderProps.md b/docs/framework/svelte/reference/interfaces/HotkeysProviderProps.md new file mode 100644 index 0000000..15ca21a --- /dev/null +++ b/docs/framework/svelte/reference/interfaces/HotkeysProviderProps.md @@ -0,0 +1,28 @@ +--- +id: HotkeysProviderProps +title: HotkeysProviderProps +--- + +# Interface: HotkeysProviderProps + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:17](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L17) + +## Properties + +### children + +```ts +children: Snippet; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:18](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L18) + +*** + +### defaultOptions? + +```ts +optional defaultOptions: HotkeysProviderOptions; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:19](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L19) diff --git a/docs/framework/svelte/reference/interfaces/SvelteHotkeyRecorder.md b/docs/framework/svelte/reference/interfaces/SvelteHotkeyRecorder.md new file mode 100644 index 0000000..777e7fd --- /dev/null +++ b/docs/framework/svelte/reference/interfaces/SvelteHotkeyRecorder.md @@ -0,0 +1,80 @@ +--- +id: SvelteHotkeyRecorder +title: SvelteHotkeyRecorder +--- + +# Interface: SvelteHotkeyRecorder + +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:6](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L6) + +## Properties + +### cancelRecording() + +```ts +cancelRecording: () => void; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:16](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L16) + +Cancel recording without saving + +#### Returns + +`void` + +*** + +### isRecording + +```ts +isRecording: boolean; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:8](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L8) + +Whether recording is currently active + +*** + +### recordedHotkey + +```ts +recordedHotkey: Hotkey | null; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:10](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L10) + +The currently recorded hotkey (for live preview) + +*** + +### startRecording() + +```ts +startRecording: () => void; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:12](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L12) + +Start recording a new hotkey + +#### Returns + +`void` + +*** + +### stopRecording() + +```ts +stopRecording: () => void; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:14](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L14) + +Stop recording (same as cancel) + +#### Returns + +`void` diff --git a/docs/framework/svelte/reference/type-aliases/HotkeysProvider.md b/docs/framework/svelte/reference/type-aliases/HotkeysProvider.md new file mode 100644 index 0000000..a35a88d --- /dev/null +++ b/docs/framework/svelte/reference/type-aliases/HotkeysProvider.md @@ -0,0 +1,12 @@ +--- +id: HotkeysProvider +title: HotkeysProvider +--- + +# Type Alias: HotkeysProvider + +```ts +type HotkeysProvider = SvelteComponent; +``` + +Defined in: node\_modules/.pnpm/svelte@5.53.7/node\_modules/svelte/types/index.d.ts:3195 diff --git a/docs/framework/svelte/reference/type-aliases/ResolvedTarget.md b/docs/framework/svelte/reference/type-aliases/ResolvedTarget.md new file mode 100644 index 0000000..4f8ba32 --- /dev/null +++ b/docs/framework/svelte/reference/type-aliases/ResolvedTarget.md @@ -0,0 +1,12 @@ +--- +id: ResolvedTarget +title: ResolvedTarget +--- + +# Type Alias: ResolvedTarget + +```ts +type ResolvedTarget = HTMLElement | Document | Window | null; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:9](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L9) diff --git a/docs/framework/svelte/reference/type-aliases/Target.md b/docs/framework/svelte/reference/type-aliases/Target.md new file mode 100644 index 0000000..13d0165 --- /dev/null +++ b/docs/framework/svelte/reference/type-aliases/Target.md @@ -0,0 +1,14 @@ +--- +id: Target +title: Target +--- + +# Type Alias: Target + +```ts +type Target = + | ResolvedTarget + | () => ResolvedTarget; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:7](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L7) diff --git a/docs/framework/svelte/reference/variables/DEFAULT_OPTIONS.md b/docs/framework/svelte/reference/variables/DEFAULT_OPTIONS.md new file mode 100644 index 0000000..a526377 --- /dev/null +++ b/docs/framework/svelte/reference/variables/DEFAULT_OPTIONS.md @@ -0,0 +1,12 @@ +--- +id: DEFAULT_OPTIONS +title: DEFAULT_OPTIONS +--- + +# Variable: DEFAULT\_OPTIONS + +```ts +const DEFAULT_OPTIONS: HotkeysProviderOptions = {}; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:22](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L22) diff --git a/docs/framework/svelte/reference/variables/HotkeysProvider.md b/docs/framework/svelte/reference/variables/HotkeysProvider.md new file mode 100644 index 0000000..bc71bdd --- /dev/null +++ b/docs/framework/svelte/reference/variables/HotkeysProvider.md @@ -0,0 +1,12 @@ +--- +id: HotkeysProvider +title: HotkeysProvider +--- + +# Variable: HotkeysProvider + +```ts +const HotkeysProvider: LegacyComponentType; +``` + +Defined in: node\_modules/.pnpm/svelte@5.53.7/node\_modules/svelte/types/index.d.ts:3195 diff --git a/docs/framework/svelte/reference/variables/getHotkeysContext.md b/docs/framework/svelte/reference/variables/getHotkeysContext.md new file mode 100644 index 0000000..55617d0 --- /dev/null +++ b/docs/framework/svelte/reference/variables/getHotkeysContext.md @@ -0,0 +1,16 @@ +--- +id: getHotkeysContext +title: getHotkeysContext +--- + +# Variable: getHotkeysContext() + +```ts +getHotkeysContext: () => HotkeysContextValue | null; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:28](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L28) + +## Returns + +`HotkeysContextValue` \| `null` diff --git a/docs/framework/svelte/reference/variables/setHotkeysContext.md b/docs/framework/svelte/reference/variables/setHotkeysContext.md new file mode 100644 index 0000000..e159996 --- /dev/null +++ b/docs/framework/svelte/reference/variables/setHotkeysContext.md @@ -0,0 +1,22 @@ +--- +id: setHotkeysContext +title: setHotkeysContext +--- + +# Variable: setHotkeysContext() + +```ts +setHotkeysContext: (context) => HotkeysContextValue | null; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:28](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L28) + +## Parameters + +### context + +`HotkeysContextValue` | `null` + +## Returns + +`HotkeysContextValue` \| `null` diff --git a/examples/svelte/create-hotkey-recorder/index.html b/examples/svelte/create-hotkey-recorder/index.html new file mode 100644 index 0000000..1d26e1a --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/index.html @@ -0,0 +1,14 @@ + + + + + + + createHotkeyRecorder - TanStack Hotkeys Svelte Example + + + +
+ + + diff --git a/examples/svelte/create-hotkey-recorder/package.json b/examples/svelte/create-hotkey-recorder/package.json index 3dea7cc..9b4464a 100644 --- a/examples/svelte/create-hotkey-recorder/package.json +++ b/examples/svelte/create-hotkey-recorder/package.json @@ -1,26 +1,17 @@ { - "name": "create-hotkey-recorder", + "name": "@tanstack/hotkeys-example-svelte-create-hotkey-recorder", "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" + "dev": "vite --port=3069" }, "dependencies": { - "@tanstack/svelte-hotkeys": "workspace:*" + "@tanstack/svelte-hotkeys": "workspace:*", + "svelte": "^5.53.7" }, "devDependencies": { - "@sveltejs/adapter-auto": "^7.0.1", - "@sveltejs/kit": "^2.53.4", "@sveltejs/vite-plugin-svelte": "^6.2.4", - "svelte": "^5.53.7", - "svelte-check": "^4.4.5", - "typescript": "^5.9.3", + "typescript": "5.9.3", "vite": "^7.3.1" } } diff --git a/examples/svelte/create-hotkey-recorder/src/App.svelte b/examples/svelte/create-hotkey-recorder/src/App.svelte new file mode 100644 index 0000000..8c83981 --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/src/App.svelte @@ -0,0 +1,271 @@ + + + +
+
+

Keyboard Shortcuts Settings

+

+ Customize your keyboard shortcuts. Click "Edit" to record a new + shortcut, or press Escape to cancel. +

+
+ +
+
+

Shortcuts

+
+ {#each Object.entries(DEFAULT_SHORTCUT_ACTIONS) as [actionId, action]} + handleEdit(actionId)} + onCancel={handleCancel} + /> + {/each} +
+
+ +
+

Demo Actions

+

Try your shortcuts! Actions will trigger when you press them.

+
+
+
Save
+
{saveCount}
+ {formatForDisplay(shortcuts.save || 'Mod+K')} +
+
+
Open
+
{openCount}
+ {formatForDisplay(shortcuts.open || 'Mod+E')} +
+
+
New
+
{newCount}
+ {formatForDisplay(shortcuts.new || 'Mod+G')} +
+
+
Close
+
{closeCount}
+ {formatForDisplay(shortcuts.close || 'Mod+Shift+K')} +
+
+
Undo
+
{undoCount}
+ {formatForDisplay(shortcuts.undo || 'Mod+Shift+E')} +
+
+
Redo
+
{redoCount}
+ {formatForDisplay(shortcuts.redo || 'Mod+Shift+G')} +
+
+
+ + {#if recorder.isRecording} +
+ Recording shortcut... Press any key combination or Escape + to cancel. Press Backspace/Delete to clear the shortcut. +
+ {/if} + +
+

Usage

+
{`import { createHotkey, formatForDisplay } from '@tanstack/svelte-hotkeys'
+
+let shortcuts = $state({
+  save: 'Mod+K',
+  open: 'Mod+E',
+})
+
+// Register shortcuts dynamically
+createHotkey(
+  shortcuts.save,
+  () => handleSave(),
+  { enabled: !isRecording }
+)
+
+// In template:
+// {formatForDisplay(shortcuts.save)}`}
+
+
+
+
diff --git a/examples/svelte/create-hotkey-recorder/src/ShortcutListItem.svelte b/examples/svelte/create-hotkey-recorder/src/ShortcutListItem.svelte new file mode 100644 index 0000000..631c442 --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/src/ShortcutListItem.svelte @@ -0,0 +1,51 @@ + + +
+
+
{actionName}
+
+ {#if isRecording} +
+ {#if heldKeys.length > 0} +
+ {#each heldKeys as key, index} + {#if index > 0} + + + {/if} + {key} + {/each} +
+ {:else} + Press any key combination... + {/if} +
+ {:else if hotkey} + {formatForDisplay(hotkey as Hotkey)} + {:else} + No shortcut + {/if} +
+
+
+ {#if isRecording} + + {:else} + + {/if} +
+
diff --git a/examples/svelte/create-hotkey-recorder/src/app.d.ts b/examples/svelte/create-hotkey-recorder/src/app.d.ts deleted file mode 100644 index da08e6d..0000000 --- a/examples/svelte/create-hotkey-recorder/src/app.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -// 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-recorder/src/app.html b/examples/svelte/create-hotkey-recorder/src/app.html deleted file mode 100644 index f273cc5..0000000 --- a/examples/svelte/create-hotkey-recorder/src/app.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - %sveltekit.head% - - -
%sveltekit.body%
- - diff --git a/examples/svelte/create-hotkey-recorder/src/index.css b/examples/svelte/create-hotkey-recorder/src/index.css new file mode 100644 index 0000000..77dfb2f --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/src/index.css @@ -0,0 +1,256 @@ +* { + box-sizing: border-box; +} +body { + margin: 0; + font-family: + system-ui, + -apple-system, + sans-serif; + background: #f5f5f5; + color: #333; +} +.app { + max-width: 900px; + margin: 0 auto; + padding: 20px; +} +header { + text-align: center; + margin-bottom: 40px; +} +header h1 { + margin: 0 0 10px; + color: #0066cc; +} +header p { + color: #666; + margin: 0; + max-width: 600px; + margin: 0 auto; +} +.demo-section { + background: white; + border-radius: 12px; + padding: 24px; + margin-bottom: 24px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} +.demo-section h2 { + margin: 0 0 16px; + font-size: 20px; +} +.demo-section p { + margin: 0 0 16px; +} +kbd { + background: linear-gradient(180deg, #f8f8f8 0%, #e8e8e8 100%); + border: 1px solid #ccc; + border-bottom-width: 2px; + border-radius: 4px; + padding: 2px 8px; + font-family: monospace; + font-size: 13px; +} +button { + background: #0066cc; + color: white; + border: none; + padding: 8px 16px; + border-radius: 6px; + cursor: pointer; + font-size: 14px; + transition: background 0.2s; +} +button:hover { + background: #0052a3; +} +button:active { + background: #004080; +} +.cancel-button { + background: #dc3545; +} +.cancel-button:hover { + background: #c82333; +} +.edit-button { + background: #28a745; +} +.edit-button:hover { + background: #218838; +} +.code-block { + background: #1e1e1e; + color: #d4d4d4; + padding: 16px; + border-radius: 8px; + overflow-x: auto; + font-size: 13px; + line-height: 1.5; + margin-top: 16px; +} +.info-box { + background: #e3f2fd; + border-radius: 8px; + padding: 12px 16px; + margin: 20px 0; +} +.recording-notice { + background: #fff3cd; + border: 2px solid #ffc107; + animation: pulse 2s ease-in-out infinite; +} +@keyframes pulse { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.8; + } +} + +/* Shortcuts List */ +.shortcuts-list { + display: flex; + flex-direction: column; + gap: 12px; +} +.shortcut-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px; + background: #f8f9fa; + border: 2px solid transparent; + border-radius: 8px; + transition: all 0.2s; +} +.shortcut-item:hover { + background: #f0f0f0; +} +.shortcut-item.recording { + background: #fff3cd; + border-color: #ffc107; + box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.2); + animation: recordingPulse 1.5s ease-in-out infinite; +} +@keyframes recordingPulse { + 0%, + 100% { + box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.2); + } + 50% { + box-shadow: 0 0 0 6px rgba(255, 193, 7, 0.1); + } +} +.shortcut-item-content { + display: flex; + align-items: center; + gap: 24px; + flex: 1; +} +.shortcut-action { + font-weight: 500; + min-width: 80px; + font-size: 15px; +} +.shortcut-hotkey { + display: flex; + align-items: center; + min-height: 32px; +} +.shortcut-hotkey kbd { + font-size: 14px; +} +.no-shortcut { + color: #999; + font-style: italic; + font-size: 14px; +} +.shortcut-actions { + display: flex; + gap: 8px; +} + +/* Recording Indicator */ +.recording-indicator { + display: flex; + align-items: center; + gap: 8px; +} +.recording-text { + color: #856404; + font-style: italic; + font-size: 14px; +} +.held-hotkeys { + display: flex; + align-items: center; + gap: 4px; +} +.held-hotkeys .plus { + color: #856404; + font-size: 16px; + margin: 0 4px; +} +.held-hotkeys kbd { + background: #ffc107; + border-color: #ff9800; + color: #856404; + font-weight: 600; +} + +/* Demo Stats */ +.demo-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 16px; + margin-top: 20px; +} +.stat-item { + display: flex; + flex-direction: column; + align-items: center; + padding: 16px; + background: #f8f9fa; + border-radius: 8px; + gap: 8px; +} +.stat-label { + font-size: 13px; + color: #666; + text-transform: uppercase; + letter-spacing: 0.5px; +} +.stat-value { + font-size: 32px; + font-weight: bold; + color: #0066cc; +} +.stat-item kbd { + margin-top: 4px; +} + +/* Responsive */ +@media (max-width: 600px) { + .shortcut-item { + flex-direction: column; + align-items: flex-start; + gap: 12px; + } + .shortcut-item-content { + width: 100%; + flex-direction: column; + align-items: flex-start; + gap: 8px; + } + .shortcut-actions { + width: 100%; + justify-content: flex-end; + } + .demo-stats { + grid-template-columns: repeat(2, 1fr); + } +} diff --git a/examples/svelte/create-hotkey-recorder/src/main.ts b/examples/svelte/create-hotkey-recorder/src/main.ts new file mode 100644 index 0000000..acece3d --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/src/main.ts @@ -0,0 +1,5 @@ +import { mount } from 'svelte' +import App from './App.svelte' +import './index.css' + +mount(App, { target: document.getElementById('app')! }) diff --git a/examples/svelte/create-hotkey-recorder/src/routes/+layout.svelte b/examples/svelte/create-hotkey-recorder/src/routes/+layout.svelte deleted file mode 100644 index 2472ec7..0000000 --- a/examples/svelte/create-hotkey-recorder/src/routes/+layout.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - -{@render children()} diff --git a/examples/svelte/create-hotkey-recorder/src/routes/+page.svelte b/examples/svelte/create-hotkey-recorder/src/routes/+page.svelte deleted file mode 100644 index 25160bf..0000000 --- a/examples/svelte/create-hotkey-recorder/src/routes/+page.svelte +++ /dev/null @@ -1,41 +0,0 @@ - - -
-

- {#if recorder.isRecording} - Press a key combination... - {:else if savedHotkey} - Saved: {savedHotkey} - {:else} - No hotkey set - {/if} -

- - {#if recorder.recordedHotkey} -

Live preview: {recorder.recordedHotkey}

- {/if} - - - - {#if recorder.isRecording} - - {/if} -
diff --git a/examples/svelte/create-hotkey-recorder/svelte.config.js b/examples/svelte/create-hotkey-recorder/svelte.config.js index 2a5c16e..b30b657 100644 --- a/examples/svelte/create-hotkey-recorder/svelte.config.js +++ b/examples/svelte/create-hotkey-recorder/svelte.config.js @@ -1,12 +1,10 @@ -import adapter from '@sveltejs/adapter-auto' +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' -/** @type {import('@sveltejs/kit').Config} */ +/** @type {import('svelte').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(), + preprocess: vitePreprocess(), + compilerOptions: { + runes: true, }, } diff --git a/examples/svelte/create-hotkey-recorder/tsconfig.json b/examples/svelte/create-hotkey-recorder/tsconfig.json index 2c2ed3c..912a030 100644 --- a/examples/svelte/create-hotkey-recorder/tsconfig.json +++ b/examples/svelte/create-hotkey-recorder/tsconfig.json @@ -1,20 +1,8 @@ { - "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 + "extends": "../../../tsconfig.json", + "compilerOptions": { + "moduleResolution": "bundler" + }, + "include": ["src"], + "exclude": ["eslint.config.js"] } diff --git a/examples/svelte/create-hotkey-recorder/vite.config.ts b/examples/svelte/create-hotkey-recorder/vite.config.ts index bbf8c7d..951a9ba 100644 --- a/examples/svelte/create-hotkey-recorder/vite.config.ts +++ b/examples/svelte/create-hotkey-recorder/vite.config.ts @@ -1,6 +1,6 @@ -import { sveltekit } from '@sveltejs/kit/vite'; -import { defineConfig } from 'vite'; +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' export default defineConfig({ - plugins: [sveltekit()] -}); + plugins: [svelte()], +}) diff --git a/examples/svelte/create-hotkey-sequence/index.html b/examples/svelte/create-hotkey-sequence/index.html new file mode 100644 index 0000000..3e93ebb --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/index.html @@ -0,0 +1,14 @@ + + + + + + + createHotkeySequence - TanStack Hotkeys Svelte Example + + + +
+ + + diff --git a/examples/svelte/create-hotkey-sequence/package.json b/examples/svelte/create-hotkey-sequence/package.json index 9ec8b7d..719201f 100644 --- a/examples/svelte/create-hotkey-sequence/package.json +++ b/examples/svelte/create-hotkey-sequence/package.json @@ -1,26 +1,17 @@ { - "name": "create-hotkey-sequence", + "name": "@tanstack/hotkeys-example-svelte-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" + "dev": "vite --port=3069" }, "dependencies": { - "@tanstack/svelte-hotkeys": "workspace:*" + "@tanstack/svelte-hotkeys": "workspace:*", + "svelte": "^5.53.7" }, "devDependencies": { - "@sveltejs/adapter-auto": "^7.0.1", - "@sveltejs/kit": "^2.53.4", "@sveltejs/vite-plugin-svelte": "^6.2.4", - "svelte": "^5.53.7", - "svelte-check": "^4.4.5", - "typescript": "^5.9.3", + "typescript": "5.9.3", "vite": "^7.3.1" } } diff --git a/examples/svelte/create-hotkey-sequence/src/App.svelte b/examples/svelte/create-hotkey-sequence/src/App.svelte new file mode 100644 index 0000000..a8cc5a5 --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/src/App.svelte @@ -0,0 +1,206 @@ + + + +
+
+

createHotkeySequence

+

+ Register multi-key sequences (like Vim commands). Keys must be pressed + within the timeout window (default: 1000ms). +

+
+ +
+
+

Vim-Style Commands

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SequenceAction
+ g g + Go to top
+ G (Shift+G) + Go to bottom
+ d d + Delete line
+ y y + Yank (copy) line
+ d w + Delete word
+ c i w + Change inner word
+
+ +
+

Fun Sequences

+
+
+

Konami Code (Partial)

+

+ +

+ Use arrow keys within 1.5 seconds +
+
+

Side to Side

+

+ +

+ Arrow keys within 1.5 seconds +
+
+

Spell It Out

+

+ h e l l o +

+ Type "hello" quickly +
+
+
+ + {#if lastSequence} +
+ Triggered: + {lastSequence} +
+ {/if} + +
+

Input handling

+

+ Sequences are not detected when typing in text inputs, textareas, + selects, or contenteditable elements. Button-type inputs ( + type="button", submit, reset) + still receive sequences. Focus the input below and try g + g or h + e + l + l + o — nothing will trigger. Click outside to try again. +

+ +
+ +
+

Usage

+
{`import { createHotkeySequence } from '@tanstack/svelte-hotkeys'
+
+function VimEditor() {
+  // Basic sequence
+  createHotkeySequence(['G', 'G'], () => {
+    scrollToTop()
+  })
+
+  // With custom timeout (1.5 seconds)
+  createHotkeySequence(
+    ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown'],
+    () => activateCheatMode(),
+    { timeout: 1500 }
+  )
+
+  // Three-key sequence
+  createHotkeySequence(['C', 'I', 'W'], () => {
+    changeInnerWord()
+  })
+}`}
+
+ + {#if history.length > 0} +
+

History

+
    + {#each history as item} +
  • {item}
  • + {/each} +
+ +
+ {/if} + +

+ Press Escape to clear history +

+
+
+
diff --git a/examples/svelte/create-hotkey-sequence/src/app.d.ts b/examples/svelte/create-hotkey-sequence/src/app.d.ts deleted file mode 100644 index da08e6d..0000000 --- a/examples/svelte/create-hotkey-sequence/src/app.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -// 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 deleted file mode 100644 index f273cc5..0000000 --- a/examples/svelte/create-hotkey-sequence/src/app.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - %sveltekit.head% - - -
%sveltekit.body%
- - diff --git a/examples/svelte/create-hotkey-sequence/src/index.css b/examples/svelte/create-hotkey-sequence/src/index.css new file mode 100644 index 0000000..8ee4387 --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/src/index.css @@ -0,0 +1,162 @@ +* { + box-sizing: border-box; +} +body { + margin: 0; + font-family: + system-ui, + -apple-system, + sans-serif; + background: #f5f5f5; + color: #333; +} +.app { + max-width: 800px; + margin: 0 auto; + padding: 20px; +} +header { + text-align: center; + margin-bottom: 40px; +} +header h1 { + margin: 0 0 10px; + color: #0066cc; +} +header p { + color: #666; + margin: 0; + max-width: 500px; + margin: 0 auto; +} + +.demo-section { + background: white; + border-radius: 12px; + padding: 24px; + margin-bottom: 24px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} +.demo-section h2 { + margin: 0 0 16px; + font-size: 20px; +} +.demo-section p { + margin: 0 0 12px; +} +kbd { + background: linear-gradient(180deg, #f8f8f8 0%, #e8e8e8 100%); + border: 1px solid #ccc; + border-bottom-width: 2px; + border-radius: 4px; + padding: 2px 8px; + font-family: monospace; + font-size: 13px; + margin-right: 4px; +} +.sequence-table { + width: 100%; + border-collapse: collapse; +} +.sequence-table th, +.sequence-table td { + padding: 12px; + text-align: left; + border-bottom: 1px solid #eee; +} +.sequence-table th { + font-weight: 600; + color: #666; + font-size: 14px; +} +.fun-sequences { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 16px; +} +.sequence-card { + background: #f8f9fa; + border-radius: 8px; + padding: 16px; + text-align: center; +} +.sequence-card h3 { + margin: 0 0 12px; + font-size: 16px; +} +.sequence-card p { + margin: 0 0 8px; +} +.hint { + font-size: 12px; + color: #888; + font-style: italic; +} +.info-box { + background: #e3f2fd; + border-radius: 8px; + padding: 16px 20px; + margin-bottom: 24px; + font-size: 18px; +} +.info-box.success { + background: #e8f5e9; + color: #2e7d32; +} +.code-block { + background: #1e1e1e; + color: #d4d4d4; + padding: 16px; + border-radius: 8px; + overflow-x: auto; + font-size: 13px; + line-height: 1.5; +} +.history-list { + list-style: none; + padding: 0; + margin: 0 0 16px; +} +.history-list li { + padding: 10px 14px; + background: #f0f0f0; + border-radius: 6px; + margin-bottom: 6px; + font-family: monospace; + font-size: 14px; +} +button { + background: #0066cc; + color: white; + border: none; + padding: 10px 20px; + border-radius: 6px; + cursor: pointer; + font-size: 14px; +} +button:hover { + background: #0052a3; +} + +.counter { + font-size: 18px; + font-weight: bold; + color: #0066cc; + margin: 12px 0; +} + +.demo-input { + width: 100%; + max-width: 400px; + padding: 12px 16px; + font-size: 14px; + border: 1px solid #ddd; + border-radius: 6px; + margin-top: 8px; +} + +.demo-input:focus { + outline: 2px solid #0066cc; + outline-offset: 2px; + border-color: #0066cc; +} diff --git a/examples/svelte/create-hotkey-sequence/src/main.ts b/examples/svelte/create-hotkey-sequence/src/main.ts new file mode 100644 index 0000000..acece3d --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/src/main.ts @@ -0,0 +1,5 @@ +import { mount } from 'svelte' +import App from './App.svelte' +import './index.css' + +mount(App, { target: document.getElementById('app')! }) diff --git a/examples/svelte/create-hotkey-sequence/src/routes/+layout.svelte b/examples/svelte/create-hotkey-sequence/src/routes/+layout.svelte deleted file mode 100644 index 2472ec7..0000000 --- a/examples/svelte/create-hotkey-sequence/src/routes/+layout.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - -{@render children()} diff --git a/examples/svelte/create-hotkey-sequence/src/routes/+page.svelte b/examples/svelte/create-hotkey-sequence/src/routes/+page.svelte deleted file mode 100644 index de308f1..0000000 --- a/examples/svelte/create-hotkey-sequence/src/routes/+page.svelte +++ /dev/null @@ -1,25 +0,0 @@ - - -
diff --git a/examples/svelte/create-hotkey-sequence/svelte.config.js b/examples/svelte/create-hotkey-sequence/svelte.config.js index 2a5c16e..b30b657 100644 --- a/examples/svelte/create-hotkey-sequence/svelte.config.js +++ b/examples/svelte/create-hotkey-sequence/svelte.config.js @@ -1,12 +1,10 @@ -import adapter from '@sveltejs/adapter-auto' +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' -/** @type {import('@sveltejs/kit').Config} */ +/** @type {import('svelte').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(), + preprocess: vitePreprocess(), + compilerOptions: { + runes: true, }, } diff --git a/examples/svelte/create-hotkey-sequence/tsconfig.json b/examples/svelte/create-hotkey-sequence/tsconfig.json index 2c2ed3c..912a030 100644 --- a/examples/svelte/create-hotkey-sequence/tsconfig.json +++ b/examples/svelte/create-hotkey-sequence/tsconfig.json @@ -1,20 +1,8 @@ { - "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 + "extends": "../../../tsconfig.json", + "compilerOptions": { + "moduleResolution": "bundler" + }, + "include": ["src"], + "exclude": ["eslint.config.js"] } diff --git a/examples/svelte/create-hotkey-sequence/vite.config.ts b/examples/svelte/create-hotkey-sequence/vite.config.ts index bbf8c7d..951a9ba 100644 --- a/examples/svelte/create-hotkey-sequence/vite.config.ts +++ b/examples/svelte/create-hotkey-sequence/vite.config.ts @@ -1,6 +1,6 @@ -import { sveltekit } from '@sveltejs/kit/vite'; -import { defineConfig } from 'vite'; +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' export default defineConfig({ - plugins: [sveltekit()] -}); + plugins: [svelte()], +}) diff --git a/examples/svelte/create-hotkey/README.md b/examples/svelte/create-hotkey/README.md deleted file mode 100644 index 57b7713..0000000 --- a/examples/svelte/create-hotkey/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# 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/index.html b/examples/svelte/create-hotkey/index.html new file mode 100644 index 0000000..42f0210 --- /dev/null +++ b/examples/svelte/create-hotkey/index.html @@ -0,0 +1,14 @@ + + + + + + + createHotkey - TanStack Hotkeys Svelte Example + + + +
+ + + diff --git a/examples/svelte/create-hotkey/package.json b/examples/svelte/create-hotkey/package.json index e117f52..0e3b948 100644 --- a/examples/svelte/create-hotkey/package.json +++ b/examples/svelte/create-hotkey/package.json @@ -1,26 +1,22 @@ { - "name": "create-hotkey", + "name": "@tanstack/hotkeys-example-svelte-create-hotkey", "private": true, - "version": "0.0.1", "type": "module", "scripts": { - "dev": "vite dev", + "dev": "vite --port=3069", "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" + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "test:types": "tsc" }, "dependencies": { - "@tanstack/svelte-hotkeys": "workspace:*" + "@tanstack/svelte-hotkeys": "workspace:*", + "svelte": "^5.53.7" }, "devDependencies": { - "@sveltejs/adapter-auto": "^7.0.1", - "@sveltejs/kit": "^2.53.4", "@sveltejs/vite-plugin-svelte": "^6.2.4", - "svelte": "^5.53.7", - "svelte-check": "^4.4.5", - "typescript": "^5.9.3", + "typescript": "5.9.3", "vite": "^7.3.1" } } diff --git a/examples/svelte/create-hotkey/src/App.svelte b/examples/svelte/create-hotkey/src/App.svelte new file mode 100644 index 0000000..bab8ca7 --- /dev/null +++ b/examples/svelte/create-hotkey/src/App.svelte @@ -0,0 +1,581 @@ + + + +
+
+

createHotkey

+

+ Register keyboard shortcuts with callback context containing the hotkey + and parsed hotkey information. +

+
+ +
+
+

Basic Hotkey

+

+ Press {formatForDisplay('Mod+S')} to trigger +

+
Save triggered: {saveCount}x
+
{`createHotkey('Mod+S', (_event, { hotkey, parsedHotkey }) => {
+  console.log('Hotkey:', hotkey)
+  console.log('Parsed:', parsedHotkey)
+})`}
+
+ +
+

With requireReset

+

+ Hold {formatForDisplay('Mod+K')} — only increments once until + you release all keys +

+
Increment: {incrementCount}
+

+ This prevents repeated triggering while holding the keys down. Release + all keys to allow re-triggering. +

+
{`createHotkey(
+  'Mod+K',
+  (event, { hotkey }) => {
+    count++
+  },
+  { requireReset: true }
+)`}
+
+ +
+

Conditional Hotkey

+

+ {formatForDisplay('Mod+E')} is currently + {enabled ? 'enabled' : 'disabled'} +

+ +
{`let enabled = $state(true)
+
+createHotkey(
+  'Mod+E',
+  (event, { hotkey }) => {
+    alert('Triggered!')
+  },
+  { enabled }
+)`}
+
+ +
+

Number Key Combinations

+

Common for tab/section switching:

+
+
{formatForDisplay('Mod+1')} → Tab 1
+
{formatForDisplay('Mod+2')} → Tab 2
+
{formatForDisplay('Mod+3')} → Tab 3
+
{formatForDisplay('Mod+4')} → Tab 4
+
{formatForDisplay('Mod+5')} → Tab 5
+
+
Active Tab: {activeTab}
+
{`createHotkey('Mod+1', () => activeTab = 1)
+createHotkey('Mod+2', () => activeTab = 2)`}
+
+ +
+

Navigation Key Combinations

+

Selection and navigation shortcuts:

+
+
{formatForDisplay('Shift+ArrowUp')} — Select up
+
+ {formatForDisplay('Shift+ArrowDown')} — Select down +
+
+ {formatForDisplay('Alt+ArrowLeft')} — Navigate back +
+
+ {formatForDisplay('Alt+ArrowRight')} — Navigate forward +
+
{formatForDisplay('Mod+Home')} — Go to start
+
{formatForDisplay('Mod+End')} — Go to end
+
+ {formatForDisplay('Control+PageUp')} — Previous page +
+
+ {formatForDisplay('Control+PageDown')} — Next page +
+
+
Navigation triggered: {navigationCount}x
+
+ +
+

Function Key Combinations

+

System and application shortcuts:

+
+
{formatForDisplay('Alt+F4')} — Close window
+
{formatForDisplay('Control+F5')} — Hard refresh
+
{formatForDisplay('Mod+F1')} — Help
+
{formatForDisplay('Shift+F10')} — Context menu
+
{formatForDisplay('F12')} — DevTools
+
+
Function keys triggered: {functionKeyCount}x
+
+ +
+

Multi-Modifier Combinations

+

Complex shortcuts with multiple modifiers:

+
+
{formatForDisplay('Mod+Shift+S')} — Save As
+
{formatForDisplay('Mod+Shift+Z')} — Redo
+
+ {formatForDisplay('Control+Alt+A')} — Special action +
+
+ {formatForDisplay('Control+Shift+N')} — New incognito +
+
{formatForDisplay('Mod+Alt+T')} — Toggle theme
+
+ {formatForDisplay('Control+Alt+Shift+X')} — Triple modifier +
+
+
+ Multi-modifier triggered: {multiModifierCount}x +
+
+ +
+

Editing Key Combinations

+

Text editing and form shortcuts:

+
+
{formatForDisplay('Mod+Enter')} — Submit form
+
{formatForDisplay('Shift+Enter')} — New line
+
+ {formatForDisplay('Mod+Backspace')} — Delete word +
+
+ {formatForDisplay('Mod+Delete')} — Delete forward +
+
{formatForDisplay('Control+Tab')} — Next tab
+
{formatForDisplay('Shift+Tab')} — Previous field
+
{formatForDisplay('Mod+Space')} — Toggle
+
+
Editing keys triggered: {editingKeyCount}x
+
+ + {#if lastHotkey} +
+ Last triggered: + {formatForDisplay(lastHotkey)} +
+ {/if} + +

+ Press Escape to reset all counters +

+ +
+

Scoped Keyboard Shortcuts

+

+ Shortcuts can be scoped to specific DOM elements using the + target option. This allows different shortcuts to work in different + parts of your application. +

+ +
+ +
+

Sidebar (Scoped Area)

+

Click here to focus, then try:

+
+
+ {formatForDisplay('Mod+B')} — Trigger sidebar action +
+
{formatForDisplay('Mod+N')} — New item
+
+
+ Sidebar shortcuts: {sidebarShortcutCount}x +
+

+ These shortcuts only work when this sidebar area is focused or + contains focus. +

+
+ +
+

Modal Dialog

+ + {#if modalOpen} + + + {/if} +
+ +
+

Text Editor (Scoped)

+

Focus the editor below and try:

+
+
+ {formatForDisplay('Mod+S')} — Save editor content +
+
{formatForDisplay('Mod+/')} — Add comment
+
{formatForDisplay('Mod+K')} — Clear editor
+
+ +
Editor shortcuts: {editorShortcutCount}x
+

+ These shortcuts only work when the editor is focused. Notice that {formatForDisplay('Mod+S')} + here doesn't conflict with the global + {formatForDisplay('Mod+S')} + shortcut. +

+
+
+ +
{`// Scoped to a ref
+let sidebarRef = $state(null)
+
+createHotkey(
+  'Mod+B',
+  () => console.log('Sidebar shortcut!'),
+  { target: () => sidebarRef }
+)
+
+// Scoped to a modal (only when open)
+let modalRef = $state(null)
+let modalOpen = $state(false)
+
+createHotkey(
+  'Escape',
+  () => modalOpen = false,
+  { target: () => modalRef, enabled: modalOpen }
+)
+
+// Scoped to an editor
+let editorRef = $state(null)
+
+createHotkey(
+  'Mod+S',
+  () => saveEditorContent(),
+  { target: () => editorRef }
+)`}
+
+
+
+
diff --git a/examples/svelte/create-hotkey/src/app.d.ts b/examples/svelte/create-hotkey/src/app.d.ts deleted file mode 100644 index da08e6d..0000000 --- a/examples/svelte/create-hotkey/src/app.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -// 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 deleted file mode 100644 index f273cc5..0000000 --- a/examples/svelte/create-hotkey/src/app.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - %sveltekit.head% - - -
%sveltekit.body%
- - diff --git a/examples/svelte/create-hotkey/src/index.css b/examples/svelte/create-hotkey/src/index.css new file mode 100644 index 0000000..e9f3ca9 --- /dev/null +++ b/examples/svelte/create-hotkey/src/index.css @@ -0,0 +1,212 @@ +* { + box-sizing: border-box; +} +body { + margin: 0; + font-family: + system-ui, + -apple-system, + sans-serif; + background: #f5f5f5; + color: #333; +} +.app { + max-width: 800px; + margin: 0 auto; + padding: 20px; +} +header { + text-align: center; + margin-bottom: 40px; +} +header h1 { + margin: 0 0 10px; + color: #0066cc; +} +header p { + color: #666; + margin: 0; +} +.demo-section { + background: white; + border-radius: 12px; + padding: 24px; + margin-bottom: 24px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} +.demo-section h2 { + margin: 0 0 12px; + font-size: 20px; +} +.demo-section p { + margin: 0 0 12px; +} +kbd { + background: linear-gradient(180deg, #f8f8f8 0%, #e8e8e8 100%); + border: 1px solid #ccc; + border-bottom-width: 2px; + border-radius: 4px; + padding: 2px 8px; + font-family: monospace; + font-size: 13px; +} +.counter { + font-size: 28px; + font-weight: bold; + color: #0066cc; + margin: 16px 0; +} +.hint { + font-size: 13px; + color: #888; + font-style: italic; +} +.info-box { + background: #e3f2fd; + border-radius: 8px; + padding: 12px 16px; + margin: 20px 0; +} +button { + background: #0066cc; + color: white; + border: none; + padding: 10px 20px; + border-radius: 6px; + cursor: pointer; + font-size: 14px; +} +button:hover { + background: #0052a3; +} +.code-block { + background: #1e1e1e; + color: #d4d4d4; + padding: 16px; + border-radius: 8px; + overflow-x: auto; + font-size: 13px; + line-height: 1.5; + margin-top: 16px; +} +.hotkey-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 12px; + margin: 16px 0; +} +.hotkey-grid > div { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + background: #f8f9fa; + border-radius: 6px; + font-size: 14px; +} +.hotkey-grid kbd { + flex-shrink: 0; +} + +/* Scoped shortcuts section */ +.scoped-section { + margin-top: 40px; +} + +.scoped-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 24px; + margin: 24px 0; +} + +.scoped-area { + background: #f8f9fa; + border: 2px dashed #0066cc; + border-radius: 8px; + padding: 20px; + position: relative; +} + +.scoped-area:focus-within { + border-color: #0052a3; + border-style: solid; + background: #f0f7ff; + box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1); +} + +.scoped-area h3 { + margin: 0 0 12px; + font-size: 18px; + color: #0066cc; +} + +.scoped-area .hotkey-list { + margin: 12px 0; +} + +.scoped-area .hotkey-list > div { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 0; + font-size: 14px; +} + +.scoped-editor { + width: 100%; + margin: 12px 0; + padding: 12px; + border: 1px solid #ddd; + border-radius: 6px; + font-family: 'Courier New', monospace; + font-size: 14px; + resize: vertical; + min-height: 120px; +} + +.scoped-editor:focus { + outline: 2px solid #0066cc; + outline-offset: 2px; + border-color: #0066cc; +} + +/* Modal styles */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.modal-content { + background: white; + border-radius: 12px; + padding: 24px; + max-width: 500px; + width: 90%; + max-height: 80vh; + overflow-y: auto; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); +} + +.modal-content:focus { + outline: 3px solid #0066cc; + outline-offset: 2px; +} + +.modal-content h3 { + margin: 0 0 16px; + font-size: 20px; + color: #0066cc; +} + +.modal-content button { + margin-top: 16px; +} diff --git a/examples/svelte/create-hotkey/src/main.ts b/examples/svelte/create-hotkey/src/main.ts new file mode 100644 index 0000000..acece3d --- /dev/null +++ b/examples/svelte/create-hotkey/src/main.ts @@ -0,0 +1,5 @@ +import { mount } from 'svelte' +import App from './App.svelte' +import './index.css' + +mount(App, { target: document.getElementById('app')! }) diff --git a/examples/svelte/create-hotkey/src/routes/+layout.svelte b/examples/svelte/create-hotkey/src/routes/+layout.svelte deleted file mode 100644 index 2472ec7..0000000 --- a/examples/svelte/create-hotkey/src/routes/+layout.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - -{@render children()} diff --git a/examples/svelte/create-hotkey/src/routes/+page.svelte b/examples/svelte/create-hotkey/src/routes/+page.svelte deleted file mode 100644 index 5fe85a2..0000000 --- a/examples/svelte/create-hotkey/src/routes/+page.svelte +++ /dev/null @@ -1,34 +0,0 @@ - - -
diff --git a/examples/svelte/create-hotkey/svelte.config.js b/examples/svelte/create-hotkey/svelte.config.js index 2a5c16e..b30b657 100644 --- a/examples/svelte/create-hotkey/svelte.config.js +++ b/examples/svelte/create-hotkey/svelte.config.js @@ -1,12 +1,10 @@ -import adapter from '@sveltejs/adapter-auto' +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' -/** @type {import('@sveltejs/kit').Config} */ +/** @type {import('svelte').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(), + preprocess: vitePreprocess(), + compilerOptions: { + runes: true, }, } diff --git a/examples/svelte/create-hotkey/tsconfig.json b/examples/svelte/create-hotkey/tsconfig.json index 2c2ed3c..912a030 100644 --- a/examples/svelte/create-hotkey/tsconfig.json +++ b/examples/svelte/create-hotkey/tsconfig.json @@ -1,20 +1,8 @@ { - "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 + "extends": "../../../tsconfig.json", + "compilerOptions": { + "moduleResolution": "bundler" + }, + "include": ["src"], + "exclude": ["eslint.config.js"] } diff --git a/examples/svelte/create-hotkey/vite.config.ts b/examples/svelte/create-hotkey/vite.config.ts index bbf8c7d..951a9ba 100644 --- a/examples/svelte/create-hotkey/vite.config.ts +++ b/examples/svelte/create-hotkey/vite.config.ts @@ -1,6 +1,6 @@ -import { sveltekit } from '@sveltejs/kit/vite'; -import { defineConfig } from 'vite'; +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' export default defineConfig({ - plugins: [sveltekit()] -}); + plugins: [svelte()], +}) diff --git a/examples/svelte/get-held-keys/index.html b/examples/svelte/get-held-keys/index.html new file mode 100644 index 0000000..de0f445 --- /dev/null +++ b/examples/svelte/get-held-keys/index.html @@ -0,0 +1,14 @@ + + + + + + + getHeldKeys - TanStack Hotkeys Svelte Example + + + +
+ + + diff --git a/examples/svelte/get-held-keys/package.json b/examples/svelte/get-held-keys/package.json index e1ef661..740bd73 100644 --- a/examples/svelte/get-held-keys/package.json +++ b/examples/svelte/get-held-keys/package.json @@ -1,26 +1,17 @@ { - "name": "get-held-keys", + "name": "@tanstack/hotkeys-example-svelte-get-held-keys", "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" + "dev": "vite --port=3069" }, "dependencies": { - "@tanstack/svelte-hotkeys": "workspace:*" + "@tanstack/svelte-hotkeys": "workspace:*", + "svelte": "^5.53.7" }, "devDependencies": { - "@sveltejs/adapter-auto": "^7.0.1", - "@sveltejs/kit": "^2.53.4", "@sveltejs/vite-plugin-svelte": "^6.2.4", - "svelte": "^5.53.7", - "svelte-check": "^4.4.5", - "typescript": "^5.9.3", + "typescript": "5.9.3", "vite": "^7.3.1" } } diff --git a/examples/svelte/get-held-keys/src/App.svelte b/examples/svelte/get-held-keys/src/App.svelte new file mode 100644 index 0000000..0952647 --- /dev/null +++ b/examples/svelte/get-held-keys/src/App.svelte @@ -0,0 +1,114 @@ + + + +
+
+

getHeldKeys

+

+ Returns an array of all currently pressed keys. Useful for displaying + key combinations or building custom shortcut recording. +

+
+ +
+
+

Currently Held Keys

+
+ {#if heldKeys.length > 0} + {#each heldKeys as key, index} + {@const code = heldKeyCodesMap[key]} + {#if index > 0} + + + {/if} + + {formatKeyForDebuggingDisplay(key)} + {#if code && code !== key} + + {formatKeyForDebuggingDisplay(code, { + source: 'code', + })} + + {/if} + + {/each} + {:else} + Press any keys... + {/if} +
+
+ Keys held: {heldKeys.length} +
+
+ +
+

Usage

+
{`import { getHeldKeys } from '@tanstack/svelte-hotkeys'
+
+const heldKeys = getHeldKeys()
+
+// In template:
+// Currently pressed: {heldKeys.join(' + ') || 'None'}`}
+
+ +
+

Try These Combinations

+
    +
  • + Hold Shift + Control + A +
  • +
  • Press multiple letter keys at once
  • +
  • Hold modifiers and watch them appear
  • +
  • Release keys one by one
  • +
+
+ +
+

Recent Combinations

+ {#if history.length > 0} +
    + {#each history as combo} +
  • {combo}
  • + {/each} +
+ + {:else} +

Press some key combinations...

+ {/if} +
+ +
+

Use Cases

+
    +
  • Building a keyboard shortcut recorder
  • +
  • Displaying currently held keys to users
  • +
  • Debugging keyboard input
  • +
  • Creating key combination tutorials
  • +
+
+
+
+
diff --git a/examples/svelte/get-held-keys/src/app.d.ts b/examples/svelte/get-held-keys/src/app.d.ts deleted file mode 100644 index da08e6d..0000000 --- a/examples/svelte/get-held-keys/src/app.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -// 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/get-held-keys/src/app.html b/examples/svelte/get-held-keys/src/app.html deleted file mode 100644 index f273cc5..0000000 --- a/examples/svelte/get-held-keys/src/app.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - %sveltekit.head% - - -
%sveltekit.body%
- - diff --git a/examples/svelte/get-held-keys/src/index.css b/examples/svelte/get-held-keys/src/index.css new file mode 100644 index 0000000..5f83d60 --- /dev/null +++ b/examples/svelte/get-held-keys/src/index.css @@ -0,0 +1,131 @@ +* { + box-sizing: border-box; +} +body { + margin: 0; + font-family: + system-ui, + -apple-system, + sans-serif; + background: #f5f5f5; + color: #333; +} +.app { + max-width: 800px; + margin: 0 auto; + padding: 20px; +} +header { + text-align: center; + margin-bottom: 40px; +} +header h1 { + margin: 0 0 10px; + color: #0066cc; +} +header p { + color: #666; + margin: 0; + max-width: 500px; + margin: 0 auto; +} +.demo-section { + background: white; + border-radius: 12px; + padding: 24px; + margin-bottom: 24px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} +.demo-section h2 { + margin: 0 0 16px; + font-size: 20px; +} +.demo-section ul { + margin: 0; + padding-left: 20px; +} +.demo-section li { + margin-bottom: 8px; +} +kbd { + background: linear-gradient(180deg, #f8f8f8 0%, #e8e8e8 100%); + border: 1px solid #ccc; + border-bottom-width: 2px; + border-radius: 4px; + padding: 2px 8px; + font-family: monospace; + font-size: 13px; +} +kbd.large { + font-size: 24px; + padding: 8px 16px; + display: inline-flex; + flex-direction: column; + align-items: center; + gap: 2px; +} +kbd.large .code-label { + display: block; + font-size: 11px; + color: #888; + font-weight: normal; +} +.key-display { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + min-height: 80px; + flex-wrap: wrap; + background: #f8f9fa; + border-radius: 8px; + padding: 20px; +} +.key-display .plus { + font-size: 24px; + color: #666; +} +.placeholder { + color: #999; + font-style: italic; +} +.stats { + text-align: center; + margin-top: 16px; + font-size: 16px; + color: #666; +} +.code-block { + background: #1e1e1e; + color: #d4d4d4; + padding: 16px; + border-radius: 8px; + overflow-x: auto; + font-size: 13px; + line-height: 1.5; +} +.history-list { + list-style: none; + padding: 0; + margin: 0 0 16px; +} +.history-list li { + padding: 8px 12px; + background: #f0f0f0; + border-radius: 4px; + margin-bottom: 4px; + font-family: monospace; + font-size: 14px; +} +button { + background: #0066cc; + color: white; + border: none; + padding: 10px 20px; + border-radius: 6px; + cursor: pointer; + font-size: 14px; +} +button:hover { + background: #0052a3; +} diff --git a/examples/svelte/get-held-keys/src/main.ts b/examples/svelte/get-held-keys/src/main.ts new file mode 100644 index 0000000..acece3d --- /dev/null +++ b/examples/svelte/get-held-keys/src/main.ts @@ -0,0 +1,5 @@ +import { mount } from 'svelte' +import App from './App.svelte' +import './index.css' + +mount(App, { target: document.getElementById('app')! }) diff --git a/examples/svelte/get-held-keys/src/routes/+layout.svelte b/examples/svelte/get-held-keys/src/routes/+layout.svelte deleted file mode 100644 index 2472ec7..0000000 --- a/examples/svelte/get-held-keys/src/routes/+layout.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - -{@render children()} diff --git a/examples/svelte/get-held-keys/src/routes/+page.svelte b/examples/svelte/get-held-keys/src/routes/+page.svelte deleted file mode 100644 index 7a4b956..0000000 --- a/examples/svelte/get-held-keys/src/routes/+page.svelte +++ /dev/null @@ -1,29 +0,0 @@ - - -
-

Press any keys to see them here:

- -

- Held keys: {heldKeys.length > 0 ? heldKeys.join(' + ') : 'None'} -

- - {#if Object.keys(heldKeyCodesMap).length > 0} -
- {#each Object.entries(heldKeyCodesMap) as [key, code]} - - {key} ({code}) - - {/each} -
- {/if} -
diff --git a/examples/svelte/get-held-keys/svelte.config.js b/examples/svelte/get-held-keys/svelte.config.js index 2a5c16e..b30b657 100644 --- a/examples/svelte/get-held-keys/svelte.config.js +++ b/examples/svelte/get-held-keys/svelte.config.js @@ -1,12 +1,10 @@ -import adapter from '@sveltejs/adapter-auto' +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' -/** @type {import('@sveltejs/kit').Config} */ +/** @type {import('svelte').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(), + preprocess: vitePreprocess(), + compilerOptions: { + runes: true, }, } diff --git a/examples/svelte/get-held-keys/tsconfig.json b/examples/svelte/get-held-keys/tsconfig.json index 2c2ed3c..912a030 100644 --- a/examples/svelte/get-held-keys/tsconfig.json +++ b/examples/svelte/get-held-keys/tsconfig.json @@ -1,20 +1,8 @@ { - "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 + "extends": "../../../tsconfig.json", + "compilerOptions": { + "moduleResolution": "bundler" + }, + "include": ["src"], + "exclude": ["eslint.config.js"] } diff --git a/examples/svelte/get-held-keys/vite.config.ts b/examples/svelte/get-held-keys/vite.config.ts index bbf8c7d..951a9ba 100644 --- a/examples/svelte/get-held-keys/vite.config.ts +++ b/examples/svelte/get-held-keys/vite.config.ts @@ -1,6 +1,6 @@ -import { sveltekit } from '@sveltejs/kit/vite'; -import { defineConfig } from 'vite'; +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' export default defineConfig({ - plugins: [sveltekit()] -}); + plugins: [svelte()], +}) diff --git a/examples/svelte/get-is-key-held/index.html b/examples/svelte/get-is-key-held/index.html new file mode 100644 index 0000000..83c4b3b --- /dev/null +++ b/examples/svelte/get-is-key-held/index.html @@ -0,0 +1,14 @@ + + + + + + + getIsKeyHeld - TanStack Hotkeys Svelte Example + + + +
+ + + diff --git a/examples/svelte/get-is-key-held/package.json b/examples/svelte/get-is-key-held/package.json index d7fe7b2..7727ef9 100644 --- a/examples/svelte/get-is-key-held/package.json +++ b/examples/svelte/get-is-key-held/package.json @@ -1,26 +1,17 @@ { - "name": "get-is-key-held", + "name": "@tanstack/hotkeys-example-svelte-get-is-key-held", "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" + "dev": "vite --port=3069" }, "dependencies": { - "@tanstack/svelte-hotkeys": "workspace:*" + "@tanstack/svelte-hotkeys": "workspace:*", + "svelte": "^5.53.7" }, "devDependencies": { - "@sveltejs/adapter-auto": "^7.0.1", - "@sveltejs/kit": "^2.53.4", "@sveltejs/vite-plugin-svelte": "^6.2.4", - "svelte": "^5.53.7", - "svelte-check": "^4.4.5", - "typescript": "^5.9.3", + "typescript": "5.9.3", "vite": "^7.3.1" } } diff --git a/examples/svelte/get-is-key-held/src/App.svelte b/examples/svelte/get-is-key-held/src/App.svelte new file mode 100644 index 0000000..75464ac --- /dev/null +++ b/examples/svelte/get-is-key-held/src/App.svelte @@ -0,0 +1,94 @@ + + + +
+
+

getIsKeyHeld

+

+ Returns a boolean indicating if a specific key is currently held. + Optimized to only re-render when that specific key changes. +

+
+ +
+
+

Modifier Key States

+
+
+ Shift + + {isShiftHeld ? 'HELD' : 'Released'} + +
+
+ Control + + {isControlHeld ? 'HELD' : 'Released'} + +
+
+ Alt / Option + {isAltHeld ? 'HELD' : 'Released'} +
+
+ Meta (⌘ / ⊞) + {isMetaHeld ? 'HELD' : 'Released'} +
+
+
+ +
+

Space Bar Demo

+
+ {isSpaceHeld ? '🚀 SPACE HELD!' : 'Hold Space Bar'} +
+
+ +
+

Usage

+
{`import { getIsKeyHeld } from '@tanstack/svelte-hotkeys'
+
+const isShiftHeld = getIsKeyHeld('Shift')
+
+// In template:
+// 
+// {isShiftHeld ? 'Shift is pressed!' : 'Press Shift'} +//
`}
+
+ +
+

Conditional UI Example

+

+ Hold Shift to reveal the secret message: +

+
+ {#if isShiftHeld} + 🎉 The secret password is: tanstack-hotkeys-rocks! + {:else} + •••••••••••••••••••••••••• + {/if} +
+
+ +
+

Use Cases

+
    +
  • Show different UI based on modifier state
  • +
  • Enable "power user" mode while holding a key
  • +
  • Hold-to-reveal sensitive information
  • +
  • Drag-and-drop with modifier behaviors
  • +
  • Show additional options on hover + modifier
  • +
+
+
+
+
diff --git a/examples/svelte/get-is-key-held/src/app.d.ts b/examples/svelte/get-is-key-held/src/app.d.ts deleted file mode 100644 index da08e6d..0000000 --- a/examples/svelte/get-is-key-held/src/app.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -// 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/get-is-key-held/src/app.html b/examples/svelte/get-is-key-held/src/app.html deleted file mode 100644 index f273cc5..0000000 --- a/examples/svelte/get-is-key-held/src/app.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - %sveltekit.head% - - -
%sveltekit.body%
- - diff --git a/examples/svelte/get-is-key-held/src/index.css b/examples/svelte/get-is-key-held/src/index.css new file mode 100644 index 0000000..0cbb5ae --- /dev/null +++ b/examples/svelte/get-is-key-held/src/index.css @@ -0,0 +1,127 @@ +* { + box-sizing: border-box; +} +body { + margin: 0; + font-family: + system-ui, + -apple-system, + sans-serif; + background: #f5f5f5; + color: #333; +} +.app { + max-width: 800px; + margin: 0 auto; + padding: 20px; +} +header { + text-align: center; + margin-bottom: 40px; +} +header h1 { + margin: 0 0 10px; + color: #0066cc; +} +header p { + color: #666; + margin: 0; + max-width: 500px; + margin: 0 auto; +} +.demo-section { + background: white; + border-radius: 12px; + padding: 24px; + margin-bottom: 24px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} +.demo-section h2 { + margin: 0 0 16px; + font-size: 20px; +} +.demo-section p { + margin: 0 0 12px; +} +.demo-section ul { + margin: 0; + padding-left: 20px; +} +.demo-section li { + margin-bottom: 8px; +} +kbd { + background: linear-gradient(180deg, #f8f8f8 0%, #e8e8e8 100%); + border: 1px solid #ccc; + border-bottom-width: 2px; + border-radius: 4px; + padding: 2px 8px; + font-family: monospace; + font-size: 13px; +} +.modifier-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 16px; +} +.modifier-indicator { + background: #f0f0f0; + border: 2px solid #ddd; + border-radius: 12px; + padding: 20px; + text-align: center; + transition: all 0.15s ease; +} +.modifier-indicator.active { + background: #4caf50; + border-color: #388e3c; + color: white; + transform: scale(1.02); +} +.modifier-indicator .key-name { + display: block; + font-weight: bold; + font-size: 18px; + margin-bottom: 8px; +} +.modifier-indicator .status { + font-size: 14px; + opacity: 0.8; +} +.space-indicator { + background: #f0f0f0; + border: 3px solid #ddd; + border-radius: 16px; + padding: 40px; + text-align: center; + font-size: 24px; + transition: all 0.15s ease; +} +.space-indicator.active { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-color: #5a67d8; + color: white; + transform: scale(1.02); +} +.secret-box { + background: #f0f0f0; + border-radius: 8px; + padding: 20px; + text-align: center; + font-family: monospace; + font-size: 16px; + transition: all 0.3s ease; +} +.secret-box.revealed { + background: #e8f5e9; + color: #2e7d32; +} +.code-block { + background: #1e1e1e; + color: #d4d4d4; + padding: 16px; + border-radius: 8px; + overflow-x: auto; + font-size: 13px; + line-height: 1.5; +} diff --git a/examples/svelte/get-is-key-held/src/main.ts b/examples/svelte/get-is-key-held/src/main.ts new file mode 100644 index 0000000..acece3d --- /dev/null +++ b/examples/svelte/get-is-key-held/src/main.ts @@ -0,0 +1,5 @@ +import { mount } from 'svelte' +import App from './App.svelte' +import './index.css' + +mount(App, { target: document.getElementById('app')! }) diff --git a/examples/svelte/get-is-key-held/src/routes/+layout.svelte b/examples/svelte/get-is-key-held/src/routes/+layout.svelte deleted file mode 100644 index 2472ec7..0000000 --- a/examples/svelte/get-is-key-held/src/routes/+layout.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - -{@render children()} diff --git a/examples/svelte/get-is-key-held/src/routes/+page.svelte b/examples/svelte/get-is-key-held/src/routes/+page.svelte deleted file mode 100644 index 64a82ac..0000000 --- a/examples/svelte/get-is-key-held/src/routes/+page.svelte +++ /dev/null @@ -1,31 +0,0 @@ - - -
-

Hold modifier keys to see their state:

- -
- - Shift - - - Ctrl - - - Alt - -
-
diff --git a/examples/svelte/get-is-key-held/svelte.config.js b/examples/svelte/get-is-key-held/svelte.config.js index 2a5c16e..b30b657 100644 --- a/examples/svelte/get-is-key-held/svelte.config.js +++ b/examples/svelte/get-is-key-held/svelte.config.js @@ -1,12 +1,10 @@ -import adapter from '@sveltejs/adapter-auto' +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' -/** @type {import('@sveltejs/kit').Config} */ +/** @type {import('svelte').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(), + preprocess: vitePreprocess(), + compilerOptions: { + runes: true, }, } diff --git a/examples/svelte/get-is-key-held/tsconfig.json b/examples/svelte/get-is-key-held/tsconfig.json index 2c2ed3c..912a030 100644 --- a/examples/svelte/get-is-key-held/tsconfig.json +++ b/examples/svelte/get-is-key-held/tsconfig.json @@ -1,20 +1,8 @@ { - "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 + "extends": "../../../tsconfig.json", + "compilerOptions": { + "moduleResolution": "bundler" + }, + "include": ["src"], + "exclude": ["eslint.config.js"] } diff --git a/examples/svelte/get-is-key-held/vite.config.ts b/examples/svelte/get-is-key-held/vite.config.ts index bbf8c7d..951a9ba 100644 --- a/examples/svelte/get-is-key-held/vite.config.ts +++ b/examples/svelte/get-is-key-held/vite.config.ts @@ -1,6 +1,6 @@ -import { sveltekit } from '@sveltejs/kit/vite'; -import { defineConfig } from 'vite'; +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' export default defineConfig({ - plugins: [sveltekit()] -}); + plugins: [svelte()], +}) diff --git a/package.json b/package.json index 813a36d..4376185 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "clean": "find . -name 'dist' -type d -prune -exec rm -rf {} +", "clean:node_modules": "find . -name 'node_modules' -type d -prune -exec rm -rf {} +", "clean:all": "pnpm run clean && pnpm run clean:node_modules", - "copy:readme": "cp README.md packages/hotkeys/README.md && cp README.md packages/hotkeys-devtools/README.md && cp README.md packages/angular-hotkeys/README.md && cp README.md packages/react-hotkeys/README.md && cp README.md packages/react-hotkeys-devtools/README.md && cp README.md packages/preact-hotkeys/README.md && cp README.md packages/preact-hotkeys-devtools/README.md && cp README.md packages/solid-hotkeys/README.md && cp README.md packages/solid-hotkeys-devtools/README.md && cp README.md packages/vue-hotkeys/README.md && cp README.md packages/vue-hotkeys-devtools/README.md", + "copy:readme": "cp README.md packages/hotkeys/README.md && cp README.md packages/hotkeys-devtools/README.md && cp README.md packages/angular-hotkeys/README.md && cp README.md packages/react-hotkeys/README.md && cp README.md packages/react-hotkeys-devtools/README.md && cp README.md packages/preact-hotkeys/README.md && cp README.md packages/preact-hotkeys-devtools/README.md && cp README.md packages/solid-hotkeys/README.md && cp README.md packages/solid-hotkeys-devtools/README.md && cp README.md packages/vue-hotkeys/README.md && cp README.md packages/vue-hotkeys-devtools/README.md && cp README.md packages/svelte-hotkeys/README.md", "dev": "pnpm run watch", "format": "prettier --experimental-cli --ignore-unknown '**/*' --write", "generate-docs": "node scripts/generate-docs.ts && pnpm run copy:readme", diff --git a/packages/angular-hotkeys/README.md b/packages/angular-hotkeys/README.md index 1c80cd6..8c67395 100644 --- a/packages/angular-hotkeys/README.md +++ b/packages/angular-hotkeys/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/packages/hotkeys-devtools/README.md b/packages/hotkeys-devtools/README.md index 1c80cd6..8c67395 100644 --- a/packages/hotkeys-devtools/README.md +++ b/packages/hotkeys-devtools/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/packages/hotkeys/README.md b/packages/hotkeys/README.md index 1c80cd6..8c67395 100644 --- a/packages/hotkeys/README.md +++ b/packages/hotkeys/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/packages/preact-hotkeys-devtools/README.md b/packages/preact-hotkeys-devtools/README.md index 1c80cd6..8c67395 100644 --- a/packages/preact-hotkeys-devtools/README.md +++ b/packages/preact-hotkeys-devtools/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/packages/preact-hotkeys/README.md b/packages/preact-hotkeys/README.md index 1c80cd6..8c67395 100644 --- a/packages/preact-hotkeys/README.md +++ b/packages/preact-hotkeys/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/packages/react-hotkeys-devtools/README.md b/packages/react-hotkeys-devtools/README.md index 1c80cd6..8c67395 100644 --- a/packages/react-hotkeys-devtools/README.md +++ b/packages/react-hotkeys-devtools/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/packages/react-hotkeys/README.md b/packages/react-hotkeys/README.md index 1c80cd6..8c67395 100644 --- a/packages/react-hotkeys/README.md +++ b/packages/react-hotkeys/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/packages/solid-hotkeys-devtools/README.md b/packages/solid-hotkeys-devtools/README.md index 1c80cd6..8c67395 100644 --- a/packages/solid-hotkeys-devtools/README.md +++ b/packages/solid-hotkeys-devtools/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/packages/solid-hotkeys/README.md b/packages/solid-hotkeys/README.md index 1c80cd6..8c67395 100644 --- a/packages/solid-hotkeys/README.md +++ b/packages/solid-hotkeys/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/packages/svelte-hotkeys/README.md b/packages/svelte-hotkeys/README.md new file mode 100644 index 0000000..8c67395 --- /dev/null +++ b/packages/svelte-hotkeys/README.md @@ -0,0 +1,122 @@ +
+ TanStack Hotkeys +
+ +
+ + + + + +
+ +### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/) + +
+ +# TanStack Hotkeys + +> [!NOTE] +> TanStack Hotkeys is pre-alpha (prototyping phase). We are actively developing the library and are open to feedback and contributions. + +Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objects, a cross-platform `Mod` key, a singleton Hotkey Manager, and utilities for cheatsheet UIs—built to stay SSR-friendly. + +- Type-safe bindings — template strings (`Mod+Shift+S`, `Escape`) or parsed objects for full control +- Flexible options — `keydown`/`keyup`, `preventDefault`, `stopPropagation`, conditional enabled, `requireReset` +- Cross-platform Mod — maps to Cmd on macOS and Ctrl on Windows/Linux +- Batteries included — validation + matching, sequences (Vim-style), key-state tracking, recorder UI helpers, framework adapters, and devtools + +### Read the docs → + +
+ +> [!NOTE] +> You may know **TanStack Hotkeys** by our adapter names, too! +> +> - [**React Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/react/react-hotkeys) +> - [**Preact Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/preact/preact-hotkeys) +> - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) +> - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) +> - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) + +## Get Involved + +- We welcome issues and pull requests! +- Participate in [GitHub discussions](https://github.com/TanStack/hotkeys/discussions) +- Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ) +- See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions + +## Partners + +
+ + + + + + +
+ + + + + CodeRabbit + + + + + + + + Cloudflare + + +
+ +
+Keys & you? +

+We're looking for TanStack Hotkeys Partners to join our mission! Partner with us to push the boundaries of TanStack Hotkeys and build amazing things together. +

+LET'S CHAT +
+ +
+ +## Explore the TanStack Ecosystem + +- TanStack Config – Tooling for JS/TS packages +- TanStack DB – Reactive sync client store +- TanStack DevTools – Unified devtools panel +- TanStack Form – Type‑safe form state +- TanStack Hotkeys – Type‑safe keyboard shortcuts +- TanStack Query – Async state & caching +- TanStack Ranger – Range & slider primitives +- TanStack Router – Type‑safe routing, caching & URL state +- TanStack Start – Full‑stack SSR & streaming +- TanStack Store – Reactive data store +- TanStack Table – Headless datagrids +- TanStack Virtual – Virtualized rendering + +… and more at TanStack.com » diff --git a/packages/svelte-hotkeys/eslint.config.js b/packages/svelte-hotkeys/eslint.config.js index 141203b..6e628b9 100644 --- a/packages/svelte-hotkeys/eslint.config.js +++ b/packages/svelte-hotkeys/eslint.config.js @@ -5,7 +5,8 @@ import pluginSvelte from 'eslint-plugin-svelte' import rootConfig from '../../eslint.config.js' import svelteConfig from './svelte.config.js' -export default [ +/** @type {import('eslint').Linter.Config[]} */ +const config = [ ...rootConfig, ...pluginSvelte.configs['recommended'], { @@ -26,3 +27,5 @@ export default [ }, }, ] + +export default config diff --git a/packages/svelte-hotkeys/package.json b/packages/svelte-hotkeys/package.json index 6a14d56..799fb51 100644 --- a/packages/svelte-hotkeys/package.json +++ b/packages/svelte-hotkeys/package.json @@ -25,6 +25,8 @@ "scripts": { "clean": "premove ./dist ./coverage", "test:eslint": "eslint ./src", + "test:lib": "vitest --passWithNoTests", + "test:lib:dev": "pnpm test:lib --watch", "test:types": "tsc", "test:build": "publint --strict", "build": "svelte-package --input ./src --output ./dist" @@ -53,10 +55,9 @@ "devDependencies": { "@sveltejs/package": "^2.5.7", "@sveltejs/vite-plugin-svelte": "^6.2.4", + "@typescript-eslint/parser": "^8.56.1", "eslint-plugin-svelte": "^3.15.0", - "svelte": "^5.53.7", - "svelte-check": "^4.4.5", - "@typescript-eslint/parser": "^8.56.1" + "svelte": "^5.53.7" }, "peerDependencies": { "svelte": "^5.25.0" diff --git a/packages/svelte-hotkeys/src/HotkeysCtx.ts b/packages/svelte-hotkeys/src/HotkeysCtx.ts index a5edcc6..1951927 100644 --- a/packages/svelte-hotkeys/src/HotkeysCtx.ts +++ b/packages/svelte-hotkeys/src/HotkeysCtx.ts @@ -1,7 +1,8 @@ -import { createContext, Snippet } from 'svelte' -import { CreateHotkeyOptions } from './createHotkey.svelte' -import { HotkeyRecorderOptions } from '@tanstack/hotkeys' -import { CreateHotkeySequenceOptions } from './createHotkeySequence.svelte' +import { createContext } from 'svelte' +import type { HotkeyRecorderOptions } from '@tanstack/hotkeys' +import type { CreateHotkeyOptions } from './createHotkey.svelte' +import type { Snippet } from 'svelte' +import type { CreateHotkeySequenceOptions } from './createHotkeySequence.svelte' export type Target = ResolvedTarget | (() => ResolvedTarget) diff --git a/packages/svelte-hotkeys/src/createHotkey.svelte.ts b/packages/svelte-hotkeys/src/createHotkey.svelte.ts index 31ec4cf..1d968bf 100644 --- a/packages/svelte-hotkeys/src/createHotkey.svelte.ts +++ b/packages/svelte-hotkeys/src/createHotkey.svelte.ts @@ -4,6 +4,8 @@ import { getHotkeyManager, rawHotkeyToParsedHotkey, } from '@tanstack/hotkeys' +import { getDefaultHotkeysOptions } from './HotkeysCtx' +import type { ResolvedTarget, Target } from './HotkeysCtx' import type { Hotkey, HotkeyCallback, @@ -11,7 +13,6 @@ import type { HotkeyRegistrationHandle, RegisterableHotkey, } from '@tanstack/hotkeys' -import { getDefaultHotkeysOptions, ResolvedTarget, Target } from './HotkeysCtx' export interface CreateHotkeyOptions extends Omit { /** @@ -122,7 +123,7 @@ export function createHotkey( $effect(() => { // Resolve target inside the effect so refs are already attached after mount - const resolvedTarget = optionsRef?.target + const resolvedTarget = optionsRef.target ? resolveTarget(optionsRef.target) : typeof document !== 'undefined' ? document diff --git a/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts b/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts index bd1dacd..05f4061 100644 --- a/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts +++ b/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts @@ -1,7 +1,7 @@ import { HotkeyRecorder } from '@tanstack/hotkeys' -import type { Hotkey, HotkeyRecorderOptions } from '@tanstack/hotkeys' -import { getDefaultHotkeysOptions } from './HotkeysCtx' import { onDestroy } from 'svelte' +import { getDefaultHotkeysOptions } from './HotkeysCtx' +import type { Hotkey, HotkeyRecorderOptions } from '@tanstack/hotkeys' export interface SvelteHotkeyRecorder { /** Whether recording is currently active */ @@ -69,8 +69,8 @@ export function createHotkeyRecorder( recorder.setOptions(mergedOptions) }) - let isRecording = $derived.by(() => recorder.store.state.isRecording) - let recordedHotkey = $derived.by(() => recorder.store.state.recordedHotkey) + const isRecording = $derived.by(() => recorder.store.state.isRecording) + const recordedHotkey = $derived.by(() => recorder.store.state.recordedHotkey) onDestroy(() => { recorder.destroy() diff --git a/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts b/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts index 3e98b57..31cb059 100644 --- a/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts +++ b/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts @@ -1,13 +1,13 @@ -import { - formatHotkeySequence, - getSequenceManager, +import { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys' +import { getDefaultHotkeysOptions } from './HotkeysCtx' +import type { HotkeyCallback, HotkeyCallbackContext, HotkeySequence, SequenceOptions, SequenceRegistrationHandle, } from '@tanstack/hotkeys' -import { getDefaultHotkeysOptions, ResolvedTarget, Target } from './HotkeysCtx' +import type { ResolvedTarget, Target } from './HotkeysCtx' export interface CreateHotkeySequenceOptions extends Omit< SequenceOptions, @@ -82,12 +82,10 @@ export function createHotkeySequence( // Refs to capture current values for use in effect without adding dependencies let callbackRef = callback let optionsRef = mergedOptions - let managerRef = manager $effect(() => { callbackRef = callback optionsRef = mergedOptions - managerRef = manager }) // Track previous target and sequence to detect changes requiring re-registration @@ -95,10 +93,10 @@ export function createHotkeySequence( let prevSequenceRef: string | null = null // Normalize to hotkey sequence string (join with spaces) - let hotkeySequenceString = $derived.by(() => formatHotkeySequence(sequence)) + const hotkeySequenceString = $derived.by(() => formatHotkeySequence(sequence)) // Extract options without target (target is handled separately) - let { target: _target, ...optionsWithoutTarget } = $derived(mergedOptions) + const { target: _target, ...optionsWithoutTarget } = $derived(mergedOptions) $effect(() => { if (sequence.length === 0) { @@ -106,7 +104,7 @@ export function createHotkeySequence( } // Resolve target inside the effect so refs are already attached after mount - const resolvedTarget = optionsRef?.target + const resolvedTarget = optionsRef.target ? resolveTarget(optionsRef.target) : typeof document !== 'undefined' ? document diff --git a/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts b/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts index 19285d0..0095361 100644 --- a/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts +++ b/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts @@ -27,8 +27,10 @@ import { useStore } from '@tanstack/svelte-store' export function getHeldKeyCodesMap(): Record { const tracker = getKeyStateTracker() - const heldKeyCodesMap = useStore(tracker.store, (state) => state.heldCodes) - .current as Record + const heldKeyCodesMap = useStore( + tracker.store, + (state) => state.heldCodes, + ).current return heldKeyCodesMap } diff --git a/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts b/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts index d0bd231..78d651c 100644 --- a/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts +++ b/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts @@ -24,8 +24,7 @@ import { useStore } from '@tanstack/svelte-store' export function getHeldKeys(): Array { const tracker = getKeyStateTracker() - const heldKeys = useStore(tracker.store, (state) => state.heldKeys) - .current as Array + const heldKeys = useStore(tracker.store, (state) => state.heldKeys).current return heldKeys } diff --git a/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts b/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts index 2f9fa33..27b6814 100644 --- a/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts +++ b/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts @@ -1,6 +1,6 @@ -import { HeldKey } from '@tanstack/hotkeys' import { getKeyStateTracker } from '@tanstack/hotkeys' import { useStore } from '@tanstack/svelte-store' +import type { HeldKey } from '@tanstack/hotkeys' /** * Svelte function that returns whether a specific key is currently being held. @@ -48,7 +48,7 @@ export function getIsKeyHeld(key: HeldKey): boolean { const isKeyHeld = useStore(tracker.store, (state) => state.heldKeys.some((heldKey) => heldKey.toLowerCase() === normalizedKey), - ).current as boolean + ).current return isKeyHeld } diff --git a/packages/svelte-hotkeys/src/index.ts b/packages/svelte-hotkeys/src/index.ts index fc44cb4..0919778 100644 --- a/packages/svelte-hotkeys/src/index.ts +++ b/packages/svelte-hotkeys/src/index.ts @@ -1,8 +1,11 @@ +// Re-export everything from the core package +export * from '@tanstack/hotkeys' + 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 { default as HotkeysProvider } from './HotkeysProvider.svelte' export * from './HotkeysCtx' diff --git a/packages/svelte-hotkeys/svelte.config.ts b/packages/svelte-hotkeys/svelte.config.js similarity index 100% rename from packages/svelte-hotkeys/svelte.config.ts rename to packages/svelte-hotkeys/svelte.config.js diff --git a/packages/svelte-hotkeys/tsconfig.docs.json b/packages/svelte-hotkeys/tsconfig.docs.json new file mode 100644 index 0000000..08866d6 --- /dev/null +++ b/packages/svelte-hotkeys/tsconfig.docs.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "paths": { + "@tanstack/hotkeys": ["../hotkeys/src"] + } + }, + "include": ["src"] +} diff --git a/packages/svelte-hotkeys/tsconfig.json b/packages/svelte-hotkeys/tsconfig.json index 1849be5..07b3300 100644 --- a/packages/svelte-hotkeys/tsconfig.json +++ b/packages/svelte-hotkeys/tsconfig.json @@ -5,8 +5,8 @@ "src/**/*.ts", "src/**/*.svelte", "src/**/*.svelte.ts", - "eslint.config.js", "svelte.config.js", - "vite.config.ts" - ] + "vitest.config.ts" + ], + "exclude": ["eslint.config.js"] } diff --git a/packages/svelte-hotkeys/vitest.config.ts b/packages/svelte-hotkeys/vitest.config.ts new file mode 100644 index 0000000..9d327db --- /dev/null +++ b/packages/svelte-hotkeys/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +export default defineConfig({ + plugins: [svelte()], + test: { + environment: 'happy-dom', + globals: true, + }, +}) diff --git a/packages/vue-hotkeys-devtools/README.md b/packages/vue-hotkeys-devtools/README.md index 1c80cd6..8c67395 100644 --- a/packages/vue-hotkeys-devtools/README.md +++ b/packages/vue-hotkeys-devtools/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/packages/vue-hotkeys/README.md b/packages/vue-hotkeys/README.md index 1c80cd6..8c67395 100644 --- a/packages/vue-hotkeys/README.md +++ b/packages/vue-hotkeys/README.md @@ -58,7 +58,7 @@ Type-safe keyboard shortcuts for the web. Template-string bindings, parsed objec > - [**Solid Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/solid/reference) > - [**Angular Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/angular/reference) > - [**Vue Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/vue/reference) -> - Svelte Hotkeys – needs a contributor! +> - [**Svelte Hotkeys**](https://tanstack.com/hotkeys/latest/docs/framework/svelte/reference) ## Get Involved diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e18cdaf..8b1eaab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -862,24 +862,15 @@ importers: '@tanstack/svelte-hotkeys': specifier: workspace:* version: link:../../../packages/svelte-hotkeys + svelte: + specifier: ^5.53.7 + version: 5.53.7 devDependencies: - '@sveltejs/adapter-auto': - specifier: ^7.0.1 - version: 7.0.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))) - '@sveltejs/kit': - specifier: ^2.53.4 - version: 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) '@sveltejs/vite-plugin-svelte': specifier: ^6.2.4 version: 6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) - svelte: - specifier: ^5.53.7 - version: 5.53.7 - svelte-check: - specifier: ^4.4.5 - version: 4.4.5(picomatch@4.0.3)(svelte@5.53.7)(typescript@5.9.3) typescript: - specifier: ^5.9.3 + specifier: 5.9.3 version: 5.9.3 vite: specifier: ^7.3.1 @@ -890,24 +881,15 @@ importers: '@tanstack/svelte-hotkeys': specifier: workspace:* version: link:../../../packages/svelte-hotkeys + svelte: + specifier: ^5.53.7 + version: 5.53.7 devDependencies: - '@sveltejs/adapter-auto': - specifier: ^7.0.1 - version: 7.0.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))) - '@sveltejs/kit': - specifier: ^2.53.4 - version: 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) '@sveltejs/vite-plugin-svelte': specifier: ^6.2.4 version: 6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) - svelte: - specifier: ^5.53.7 - version: 5.53.7 - svelte-check: - specifier: ^4.4.5 - version: 4.4.5(picomatch@4.0.3)(svelte@5.53.7)(typescript@5.9.3) typescript: - specifier: ^5.9.3 + specifier: 5.9.3 version: 5.9.3 vite: specifier: ^7.3.1 @@ -918,24 +900,15 @@ importers: '@tanstack/svelte-hotkeys': specifier: workspace:* version: link:../../../packages/svelte-hotkeys + svelte: + specifier: ^5.53.7 + version: 5.53.7 devDependencies: - '@sveltejs/adapter-auto': - specifier: ^7.0.1 - version: 7.0.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))) - '@sveltejs/kit': - specifier: ^2.53.4 - version: 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) '@sveltejs/vite-plugin-svelte': specifier: ^6.2.4 version: 6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) - svelte: - specifier: ^5.53.7 - version: 5.53.7 - svelte-check: - specifier: ^4.4.5 - version: 4.4.5(picomatch@4.0.3)(svelte@5.53.7)(typescript@5.9.3) typescript: - specifier: ^5.9.3 + specifier: 5.9.3 version: 5.9.3 vite: specifier: ^7.3.1 @@ -946,24 +919,15 @@ importers: '@tanstack/svelte-hotkeys': specifier: workspace:* version: link:../../../packages/svelte-hotkeys + svelte: + specifier: ^5.53.7 + version: 5.53.7 devDependencies: - '@sveltejs/adapter-auto': - specifier: ^7.0.1 - version: 7.0.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))) - '@sveltejs/kit': - specifier: ^2.53.4 - version: 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) '@sveltejs/vite-plugin-svelte': specifier: ^6.2.4 version: 6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) - svelte: - specifier: ^5.53.7 - version: 5.53.7 - svelte-check: - specifier: ^4.4.5 - version: 4.4.5(picomatch@4.0.3)(svelte@5.53.7)(typescript@5.9.3) typescript: - specifier: ^5.9.3 + specifier: 5.9.3 version: 5.9.3 vite: specifier: ^7.3.1 @@ -974,24 +938,15 @@ importers: '@tanstack/svelte-hotkeys': specifier: workspace:* version: link:../../../packages/svelte-hotkeys + svelte: + specifier: ^5.53.7 + version: 5.53.7 devDependencies: - '@sveltejs/adapter-auto': - specifier: ^7.0.1 - version: 7.0.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))) - '@sveltejs/kit': - specifier: ^2.53.4 - version: 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) '@sveltejs/vite-plugin-svelte': specifier: ^6.2.4 version: 6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) - svelte: - specifier: ^5.53.7 - version: 5.53.7 - svelte-check: - specifier: ^4.4.5 - version: 4.4.5(picomatch@4.0.3)(svelte@5.53.7)(typescript@5.9.3) typescript: - specifier: ^5.9.3 + specifier: 5.9.3 version: 5.9.3 vite: specifier: ^7.3.1 @@ -1340,9 +1295,6 @@ importers: svelte: specifier: ^5.53.7 version: 5.53.7 - svelte-check: - specifier: ^4.4.5 - version: 4.4.5(picomatch@4.0.3)(svelte@5.53.7)(typescript@5.9.3) packages/vue-hotkeys: dependencies: @@ -3440,9 +3392,6 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@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: @@ -3932,27 +3881,6 @@ 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.4': - resolution: {integrity: sha512-iAIPEahFgDJJyvz8g0jP08KvqnM6JvdW8YfsygZ+pMeMvyM2zssWMltcsotETvjSZ82G3VlitgDtBIvpQSZrTA==} - 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} @@ -4175,9 +4103,6 @@ packages: '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} - '@types/cookie@0.6.0': - resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - '@types/cors@2.8.19': resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} @@ -5130,10 +5055,6 @@ packages: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} - cookie@0.6.0: - resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} - engines: {node: '>= 0.6'} - cookie@0.7.2: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} @@ -7780,9 +7701,6 @@ packages: resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} engines: {node: '>= 18'} - 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'} @@ -7887,10 +7805,6 @@ 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.1: resolution: {integrity: sha512-vuFj+6lDOoBJQu6OLhcMQv7jnbXjuoEn4WsQHlSLOV/8EFfOka/tfjtLQ/rZig5Gagi3R0GnU/0kd4EY/y2etg==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -8097,14 +8011,6 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svelte-check@4.4.5: - resolution: {integrity: sha512-1bSwIRCvvmSHrlK52fOlZmVtUZgil43jNL/2H18pRpa+eQjzGt6e3zayxhp1S7GajPFKNM/2PMCG+DZFHlG9fw==} - 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} @@ -8197,10 +8103,6 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - totalist@3.0.1: - resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} - engines: {node: '>=6'} - tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -11138,8 +11040,6 @@ snapshots: '@pkgjs/parseargs@0.11.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.57.1)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))': dependencies: '@babel/core': 7.29.0 @@ -11502,30 +11402,6 @@ snapshots: dependencies: acorn: 8.15.0 - '@sveltejs/adapter-auto@7.0.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)))': - dependencies: - '@sveltejs/kit': 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) - - '@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))': - dependencies: - '@standard-schema/spec': 1.1.0 - '@sveltejs/acorn-typescript': 1.0.9(acorn@8.15.0) - '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2)) - '@types/cookie': 0.6.0 - acorn: 8.15.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.7 - vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2) - optionalDependencies: - typescript: 5.9.3 - '@sveltejs/package@2.5.7(svelte@5.53.7)(typescript@5.9.3)': dependencies: chokidar: 5.0.0 @@ -11821,8 +11697,6 @@ snapshots: dependencies: '@types/node': 25.3.5 - '@types/cookie@0.6.0': {} - '@types/cors@2.8.19': dependencies: '@types/node': 25.3.5 @@ -12905,8 +12779,6 @@ snapshots: cookie-signature@1.2.2: {} - cookie@0.6.0: {} - cookie@0.7.2: {} copy-anything@2.0.6: @@ -15904,8 +15776,6 @@ snapshots: transitivePeerDependencies: - supports-color - set-cookie-parser@3.0.1: {} - set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -16020,12 +15890,6 @@ 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.1(jiti@2.6.1): dependencies: bytes-iec: 3.1.1 @@ -16264,18 +16128,6 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@4.4.5(picomatch@4.0.3)(svelte@5.53.7)(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.7 - typescript: 5.9.3 - transitivePeerDependencies: - - picomatch - svelte-eslint-parser@1.4.1(svelte@5.53.7): dependencies: eslint-scope: 8.4.0 @@ -16376,8 +16228,6 @@ snapshots: toidentifier@1.0.1: {} - totalist@3.0.1: {} - tr46@0.0.3: {} tree-dump@1.1.0(tslib@2.8.1): diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts index 0c9ebdf..8047ed2 100644 --- a/scripts/generate-docs.ts +++ b/scripts/generate-docs.ts @@ -70,6 +70,18 @@ await generateReferenceDocs({ outputDir: resolve(__dirname, '../docs/framework/vue/reference'), exclude: ['packages/hotkeys/**/*'], }, + { + name: 'svelte-hotkeys', + entryPoints: [ + resolve(__dirname, '../packages/svelte-hotkeys/src/index.ts'), + ], + tsconfig: resolve( + __dirname, + '../packages/svelte-hotkeys/tsconfig.docs.json', + ), + outputDir: resolve(__dirname, '../docs/framework/svelte/reference'), + exclude: ['packages/hotkeys/**/*'], + }, ], }) diff --git a/vitest.workspace.ts b/vitest.workspace.ts index 5d00262..0e0f245 100644 --- a/vitest.workspace.ts +++ b/vitest.workspace.ts @@ -12,6 +12,8 @@ export default defineConfig({ './packages/solid-hotkeys-devtools/vitest.config.ts', './packages/solid-hotkeys/vitest.config.ts', './packages/angular-hotkeys/vitest.config.ts', + './packages/vue-hotkeys/vitest.config.ts', + './packages/svelte-hotkeys/vitest.config.ts', ], }, }) From 73c276c756bf61036a4691821f6a241ee1583551 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 8 Mar 2026 19:42:18 +0000 Subject: [PATCH 09/16] ci: apply automated fixes --- .../svelte/reference/functions/createHotkeySequence.md | 2 +- .../reference/interfaces/CreateHotkeySequenceOptions.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/framework/svelte/reference/functions/createHotkeySequence.md b/docs/framework/svelte/reference/functions/createHotkeySequence.md index c398913..dc4a82f 100644 --- a/docs/framework/svelte/reference/functions/createHotkeySequence.md +++ b/docs/framework/svelte/reference/functions/createHotkeySequence.md @@ -12,7 +12,7 @@ function createHotkeySequence( options): void; ``` -Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:69](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L69) +Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:67](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L67) Svelte function for registering a keyboard shortcut sequence (Vim-style). diff --git a/docs/framework/svelte/reference/interfaces/CreateHotkeySequenceOptions.md b/docs/framework/svelte/reference/interfaces/CreateHotkeySequenceOptions.md index cf2a32d..319dfc9 100644 --- a/docs/framework/svelte/reference/interfaces/CreateHotkeySequenceOptions.md +++ b/docs/framework/svelte/reference/interfaces/CreateHotkeySequenceOptions.md @@ -5,7 +5,7 @@ title: CreateHotkeySequenceOptions # Interface: CreateHotkeySequenceOptions -Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:14](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L14) +Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:12](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L12) ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:14](http optional target: Target; ``` -Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:23](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L23) +Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:21](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L21) The DOM element to attach the event listener to. Can be a Svelte ref, direct DOM element, or null. From 04ab182b74b2ccd6609ba476558a16558002233d Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Mon, 9 Mar 2026 08:16:22 -0500 Subject: [PATCH 10/16] fix provider errors in examples --- .../create-hotkey-recorder/src/App.svelte | 146 +++--- .../create-hotkey-recorder/src/Root.svelte | 8 + .../svelte/create-hotkey-recorder/src/main.ts | 4 +- .../create-hotkey-sequence/src/App.svelte | 269 +++++----- .../create-hotkey-sequence/src/Root.svelte | 8 + .../svelte/create-hotkey-sequence/src/main.ts | 4 +- examples/svelte/create-hotkey/src/App.svelte | 480 +++++++++--------- examples/svelte/create-hotkey/src/Root.svelte | 8 + examples/svelte/create-hotkey/src/main.ts | 4 +- examples/svelte/get-held-keys/src/App.svelte | 155 +++--- examples/svelte/get-held-keys/src/Root.svelte | 8 + examples/svelte/get-held-keys/src/main.ts | 4 +- .../svelte/get-is-key-held/src/App.svelte | 142 +++--- .../svelte/get-is-key-held/src/Root.svelte | 8 + examples/svelte/get-is-key-held/src/main.ts | 4 +- 15 files changed, 637 insertions(+), 615 deletions(-) create mode 100644 examples/svelte/create-hotkey-recorder/src/Root.svelte create mode 100644 examples/svelte/create-hotkey-sequence/src/Root.svelte create mode 100644 examples/svelte/create-hotkey/src/Root.svelte create mode 100644 examples/svelte/get-held-keys/src/Root.svelte create mode 100644 examples/svelte/get-is-key-held/src/Root.svelte diff --git a/examples/svelte/create-hotkey-recorder/src/App.svelte b/examples/svelte/create-hotkey-recorder/src/App.svelte index 8c83981..611e2ef 100644 --- a/examples/svelte/create-hotkey-recorder/src/App.svelte +++ b/examples/svelte/create-hotkey-recorder/src/App.svelte @@ -3,7 +3,6 @@ createHotkey, createHotkeyRecorder, formatForDisplay, - HotkeysProvider, } from '@tanstack/svelte-hotkeys' import type { Hotkey } from '@tanstack/svelte-hotkeys' import ShortcutListItem from './ShortcutListItem.svelte' @@ -175,81 +174,79 @@ } - -
-
-

Keyboard Shortcuts Settings

-

- Customize your keyboard shortcuts. Click "Edit" to record a new - shortcut, or press Escape to cancel. -

-
+
+
+

Keyboard Shortcuts Settings

+

+ Customize your keyboard shortcuts. Click "Edit" to record a new shortcut, + or press Escape to cancel. +

+
-
-
-

Shortcuts

-
- {#each Object.entries(DEFAULT_SHORTCUT_ACTIONS) as [actionId, action]} - handleEdit(actionId)} - onCancel={handleCancel} - /> - {/each} -
-
+
+
+

Shortcuts

+
+ {#each Object.entries(DEFAULT_SHORTCUT_ACTIONS) as [actionId, action]} + handleEdit(actionId)} + onCancel={handleCancel} + /> + {/each} +
+
-
-

Demo Actions

-

Try your shortcuts! Actions will trigger when you press them.

-
-
-
Save
-
{saveCount}
- {formatForDisplay(shortcuts.save || 'Mod+K')} -
-
-
Open
-
{openCount}
- {formatForDisplay(shortcuts.open || 'Mod+E')} -
-
-
New
-
{newCount}
- {formatForDisplay(shortcuts.new || 'Mod+G')} -
-
-
Close
-
{closeCount}
- {formatForDisplay(shortcuts.close || 'Mod+Shift+K')} -
-
-
Undo
-
{undoCount}
- {formatForDisplay(shortcuts.undo || 'Mod+Shift+E')} -
-
-
Redo
-
{redoCount}
- {formatForDisplay(shortcuts.redo || 'Mod+Shift+G')} -
+
+

Demo Actions

+

Try your shortcuts! Actions will trigger when you press them.

+
+
+
Save
+
{saveCount}
+ {formatForDisplay(shortcuts.save || 'Mod+K')}
-
- - {#if recorder.isRecording} -
- Recording shortcut... Press any key combination or Escape - to cancel. Press Backspace/Delete to clear the shortcut. +
+
Open
+
{openCount}
+ {formatForDisplay(shortcuts.open || 'Mod+E')} +
+
+
New
+
{newCount}
+ {formatForDisplay(shortcuts.new || 'Mod+G')} +
+
+
Close
+
{closeCount}
+ {formatForDisplay(shortcuts.close || 'Mod+Shift+K')}
- {/if} +
+
Undo
+
{undoCount}
+ {formatForDisplay(shortcuts.undo || 'Mod+Shift+E')} +
+
+
Redo
+
{redoCount}
+ {formatForDisplay(shortcuts.redo || 'Mod+Shift+G')} +
+
+
+ + {#if recorder.isRecording} +
+ Recording shortcut... Press any key combination or Escape + to cancel. Press Backspace/Delete to clear the shortcut. +
+ {/if} -
-

Usage

-
{`import { createHotkey, formatForDisplay } from '@tanstack/svelte-hotkeys'
+    
+

Usage

+
{`import { createHotkey, formatForDisplay } from '@tanstack/svelte-hotkeys'
 
 let shortcuts = $state({
   save: 'Mod+K',
@@ -265,7 +262,6 @@ createHotkey(
 
 // In template:
 // {formatForDisplay(shortcuts.save)}`}
-
-
-
- + + +
diff --git a/examples/svelte/create-hotkey-recorder/src/Root.svelte b/examples/svelte/create-hotkey-recorder/src/Root.svelte new file mode 100644 index 0000000..2aa9ee8 --- /dev/null +++ b/examples/svelte/create-hotkey-recorder/src/Root.svelte @@ -0,0 +1,8 @@ + + + + + diff --git a/examples/svelte/create-hotkey-recorder/src/main.ts b/examples/svelte/create-hotkey-recorder/src/main.ts index acece3d..9357900 100644 --- a/examples/svelte/create-hotkey-recorder/src/main.ts +++ b/examples/svelte/create-hotkey-recorder/src/main.ts @@ -1,5 +1,5 @@ import { mount } from 'svelte' -import App from './App.svelte' +import Root from './Root.svelte' import './index.css' -mount(App, { target: document.getElementById('app')! }) +mount(Root, { target: document.getElementById('app')! }) diff --git a/examples/svelte/create-hotkey-sequence/src/App.svelte b/examples/svelte/create-hotkey-sequence/src/App.svelte index a8cc5a5..a0ea73b 100644 --- a/examples/svelte/create-hotkey-sequence/src/App.svelte +++ b/examples/svelte/create-hotkey-sequence/src/App.svelte @@ -3,7 +3,6 @@ createHotkey, createHotkeySequence, formatForDisplay, - HotkeysProvider, } from '@tanstack/svelte-hotkeys' let lastSequence = $state(null) @@ -46,125 +45,124 @@ }) - -
-
-

createHotkeySequence

-

- Register multi-key sequences (like Vim commands). Keys must be pressed - within the timeout window (default: 1000ms). -

-
- -
-
-

Vim-Style Commands

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SequenceAction
- g g - Go to top
- G (Shift+G) - Go to bottom
- d d - Delete line
- y y - Yank (copy) line
- d w - Delete word
- c i w - Change inner word
-
- -
-

Fun Sequences

-
-
-

Konami Code (Partial)

-

- -

- Use arrow keys within 1.5 seconds -
-
-

Side to Side

-

- -

- Arrow keys within 1.5 seconds -
-
-

Spell It Out

-

- h e l l o -

- Type "hello" quickly -
+
+
+

createHotkeySequence

+

+ Register multi-key sequences (like Vim commands). Keys must be pressed + within the timeout window (default: 1000ms). +

+
+ +
+
+

Vim-Style Commands

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SequenceAction
+ g g + Go to top
+ G (Shift+G) + Go to bottom
+ d d + Delete line
+ y y + Yank (copy) line
+ d w + Delete word
+ c i w + Change inner word
+
+ +
+

Fun Sequences

+
+
+

Konami Code (Partial)

+

+ +

+ Use arrow keys within 1.5 seconds
-
- - {#if lastSequence} -
- Triggered: - {lastSequence} +
+

Side to Side

+

+ +

+ Arrow keys within 1.5 seconds
- {/if} - -
-

Input handling

-

- Sequences are not detected when typing in text inputs, textareas, - selects, or contenteditable elements. Button-type inputs ( - type="button", submit, reset) - still receive sequences. Focus the input below and try g - g or h - e - l - l - o — nothing will trigger. Click outside to try again. -

- -
- -
-

Usage

-
{`import { createHotkeySequence } from '@tanstack/svelte-hotkeys'
+        
+

Spell It Out

+

+ h e l l o +

+ Type "hello" quickly +
+
+
+ + {#if lastSequence} +
+ Triggered: + {lastSequence} +
+ {/if} + +
+

Input handling

+

+ Sequences are not detected when typing in text inputs, textareas, + selects, or contenteditable elements. Button-type inputs ( + type="button", submit, reset) + still receive sequences. Focus the input below and try g + g or h + e + l + l + o — nothing will trigger. Click outside to try again. +

+ +
+ +
+

Usage

+
{`import { createHotkeySequence } from '@tanstack/svelte-hotkeys'
 
 function VimEditor() {
   // Basic sequence
@@ -184,23 +182,22 @@ function VimEditor() {
     changeInnerWord()
   })
 }`}
+
+ + {#if history.length > 0} +
+

History

+
    + {#each history as item} +
  • {item}
  • + {/each} +
+
+ {/if} - {#if history.length > 0} -
-

History

-
    - {#each history as item} -
  • {item}
  • - {/each} -
- -
- {/if} - -

- Press Escape to clear history -

-
-
-
+

+ Press Escape to clear history +

+ +
diff --git a/examples/svelte/create-hotkey-sequence/src/Root.svelte b/examples/svelte/create-hotkey-sequence/src/Root.svelte new file mode 100644 index 0000000..2aa9ee8 --- /dev/null +++ b/examples/svelte/create-hotkey-sequence/src/Root.svelte @@ -0,0 +1,8 @@ + + + + + diff --git a/examples/svelte/create-hotkey-sequence/src/main.ts b/examples/svelte/create-hotkey-sequence/src/main.ts index acece3d..9357900 100644 --- a/examples/svelte/create-hotkey-sequence/src/main.ts +++ b/examples/svelte/create-hotkey-sequence/src/main.ts @@ -1,5 +1,5 @@ import { mount } from 'svelte' -import App from './App.svelte' +import Root from './Root.svelte' import './index.css' -mount(App, { target: document.getElementById('app')! }) +mount(Root, { target: document.getElementById('app')! }) diff --git a/examples/svelte/create-hotkey/src/App.svelte b/examples/svelte/create-hotkey/src/App.svelte index bab8ca7..fb9a459 100644 --- a/examples/svelte/create-hotkey/src/App.svelte +++ b/examples/svelte/create-hotkey/src/App.svelte @@ -1,9 +1,5 @@ - -
-
-

createHotkey

+
+
+

createHotkey

+

+ Register keyboard shortcuts with callback context containing the hotkey + and parsed hotkey information. +

+
+ +
+
+

Basic Hotkey

- Register keyboard shortcuts with callback context containing the hotkey - and parsed hotkey information. + Press {formatForDisplay('Mod+S')} to trigger

-
- -
-
-

Basic Hotkey

-

- Press {formatForDisplay('Mod+S')} to trigger -

-
Save triggered: {saveCount}x
-
{`createHotkey('Mod+S', (_event, { hotkey, parsedHotkey }) => {
+      
Save triggered: {saveCount}x
+
{`createHotkey('Mod+S', (_event, { hotkey, parsedHotkey }) => {
   console.log('Hotkey:', hotkey)
   console.log('Parsed:', parsedHotkey)
 })`}
-
- -
-

With requireReset

-

- Hold {formatForDisplay('Mod+K')} — only increments once until - you release all keys -

-
Increment: {incrementCount}
-

- This prevents repeated triggering while holding the keys down. Release - all keys to allow re-triggering. -

-
{`createHotkey(
+    
+ +
+

With requireReset

+

+ Hold {formatForDisplay('Mod+K')} — only increments once until you + release all keys +

+
Increment: {incrementCount}
+

+ This prevents repeated triggering while holding the keys down. Release + all keys to allow re-triggering. +

+
{`createHotkey(
   'Mod+K',
   (event, { hotkey }) => {
     count++
   },
   { requireReset: true }
 )`}
-
- -
-

Conditional Hotkey

-

- {formatForDisplay('Mod+E')} is currently - {enabled ? 'enabled' : 'disabled'} -

- -
{`let enabled = $state(true)
+    
+ +
+

Conditional Hotkey

+

+ {formatForDisplay('Mod+E')} is currently + {enabled ? 'enabled' : 'disabled'} +

+ +
{`let enabled = $state(true)
 
 createHotkey(
   'Mod+E',
@@ -334,221 +329,221 @@ createHotkey(
   },
   { enabled }
 )`}
-
- -
-

Number Key Combinations

-

Common for tab/section switching:

-
-
{formatForDisplay('Mod+1')} → Tab 1
-
{formatForDisplay('Mod+2')} → Tab 2
-
{formatForDisplay('Mod+3')} → Tab 3
-
{formatForDisplay('Mod+4')} → Tab 4
-
{formatForDisplay('Mod+5')} → Tab 5
-
-
Active Tab: {activeTab}
-
{`createHotkey('Mod+1', () => activeTab = 1)
+    
+ +
+

Number Key Combinations

+

Common for tab/section switching:

+
+
{formatForDisplay('Mod+1')} → Tab 1
+
{formatForDisplay('Mod+2')} → Tab 2
+
{formatForDisplay('Mod+3')} → Tab 3
+
{formatForDisplay('Mod+4')} → Tab 4
+
{formatForDisplay('Mod+5')} → Tab 5
+
+
Active Tab: {activeTab}
+
{`createHotkey('Mod+1', () => activeTab = 1)
 createHotkey('Mod+2', () => activeTab = 2)`}
-
- -
-

Navigation Key Combinations

-

Selection and navigation shortcuts:

-
-
{formatForDisplay('Shift+ArrowUp')} — Select up
-
- {formatForDisplay('Shift+ArrowDown')} — Select down -
-
- {formatForDisplay('Alt+ArrowLeft')} — Navigate back -
-
- {formatForDisplay('Alt+ArrowRight')} — Navigate forward -
-
{formatForDisplay('Mod+Home')} — Go to start
-
{formatForDisplay('Mod+End')} — Go to end
-
- {formatForDisplay('Control+PageUp')} — Previous page -
-
- {formatForDisplay('Control+PageDown')} — Next page -
+
+ +
+

Navigation Key Combinations

+

Selection and navigation shortcuts:

+
+
{formatForDisplay('Shift+ArrowUp')} — Select up
+
+ {formatForDisplay('Shift+ArrowDown')} — Select down
-
Navigation triggered: {navigationCount}x
-
- -
-

Function Key Combinations

-

System and application shortcuts:

-
-
{formatForDisplay('Alt+F4')} — Close window
-
{formatForDisplay('Control+F5')} — Hard refresh
-
{formatForDisplay('Mod+F1')} — Help
-
{formatForDisplay('Shift+F10')} — Context menu
-
{formatForDisplay('F12')} — DevTools
+
+ {formatForDisplay('Alt+ArrowLeft')} — Navigate back
-
Function keys triggered: {functionKeyCount}x
-
- -
-

Multi-Modifier Combinations

-

Complex shortcuts with multiple modifiers:

-
-
{formatForDisplay('Mod+Shift+S')} — Save As
-
{formatForDisplay('Mod+Shift+Z')} — Redo
-
- {formatForDisplay('Control+Alt+A')} — Special action -
-
- {formatForDisplay('Control+Shift+N')} — New incognito -
-
{formatForDisplay('Mod+Alt+T')} — Toggle theme
-
- {formatForDisplay('Control+Alt+Shift+X')} — Triple modifier -
+
+ {formatForDisplay('Alt+ArrowRight')} — Navigate forward
-
- Multi-modifier triggered: {multiModifierCount}x +
{formatForDisplay('Mod+Home')} — Go to start
+
{formatForDisplay('Mod+End')} — Go to end
+
+ {formatForDisplay('Control+PageUp')} — Previous page
-
- -
-

Editing Key Combinations

-

Text editing and form shortcuts:

-
-
{formatForDisplay('Mod+Enter')} — Submit form
-
{formatForDisplay('Shift+Enter')} — New line
-
- {formatForDisplay('Mod+Backspace')} — Delete word -
-
- {formatForDisplay('Mod+Delete')} — Delete forward -
-
{formatForDisplay('Control+Tab')} — Next tab
-
{formatForDisplay('Shift+Tab')} — Previous field
-
{formatForDisplay('Mod+Space')} — Toggle
+
+ {formatForDisplay('Control+PageDown')} — Next page
-
Editing keys triggered: {editingKeyCount}x
-
- - {#if lastHotkey} -
- Last triggered: - {formatForDisplay(lastHotkey)} +
+
Navigation triggered: {navigationCount}x
+ + +
+

Function Key Combinations

+

System and application shortcuts:

+
+
{formatForDisplay('Alt+F4')} — Close window
+
{formatForDisplay('Control+F5')} — Hard refresh
+
{formatForDisplay('Mod+F1')} — Help
+
{formatForDisplay('Shift+F10')} — Context menu
+
{formatForDisplay('F12')} — DevTools
+
+
Function keys triggered: {functionKeyCount}x
+
+ +
+

Multi-Modifier Combinations

+

Complex shortcuts with multiple modifiers:

+
+
{formatForDisplay('Mod+Shift+S')} — Save As
+
{formatForDisplay('Mod+Shift+Z')} — Redo
+
+ {formatForDisplay('Control+Alt+A')} — Special action
- {/if} - -

- Press Escape to reset all counters +

+ {formatForDisplay('Control+Shift+N')} — New incognito +
+
{formatForDisplay('Mod+Alt+T')} — Toggle theme
+
+ {formatForDisplay('Control+Alt+Shift+X')} — Triple modifier +
+
+
+ Multi-modifier triggered: {multiModifierCount}x +
+
+ +
+

Editing Key Combinations

+

Text editing and form shortcuts:

+
+
{formatForDisplay('Mod+Enter')} — Submit form
+
{formatForDisplay('Shift+Enter')} — New line
+
+ {formatForDisplay('Mod+Backspace')} — Delete word +
+
+ {formatForDisplay('Mod+Delete')} — Delete forward +
+
{formatForDisplay('Control+Tab')} — Next tab
+
{formatForDisplay('Shift+Tab')} — Previous field
+
{formatForDisplay('Mod+Space')} — Toggle
+
+
Editing keys triggered: {editingKeyCount}x
+
+ + {#if lastHotkey} +
+ Last triggered: + {formatForDisplay(lastHotkey)} +
+ {/if} + +

+ Press Escape to reset all counters +

+ +
+

Scoped Keyboard Shortcuts

+

+ Shortcuts can be scoped to specific DOM elements using the + target option. This allows different shortcuts to work in different + parts of your application.

-
-

Scoped Keyboard Shortcuts

-

- Shortcuts can be scoped to specific DOM elements using the - target option. This allows different shortcuts to work in different - parts of your application. -

- -
- -
-

Sidebar (Scoped Area)

-

Click here to focus, then try:

-
-
- {formatForDisplay('Mod+B')} — Trigger sidebar action -
-
{formatForDisplay('Mod+N')} — New item
+
+ +
+

Sidebar (Scoped Area)

+

Click here to focus, then try:

+
+
+ {formatForDisplay('Mod+B')} — Trigger sidebar action
-
- Sidebar shortcuts: {sidebarShortcutCount}x -
-

- These shortcuts only work when this sidebar area is focused or - contains focus. -

+
{formatForDisplay('Mod+N')} — New item
+
+
+ Sidebar shortcuts: {sidebarShortcutCount}x
+

+ These shortcuts only work when this sidebar area is focused or + contains focus. +

+
-
-

Modal Dialog

- - {#if modalOpen} +
+

Modal Dialog

+ + {#if modalOpen} + +
-
-
-
+ + + diff --git a/examples/svelte/create-hotkey/src/Root.svelte b/examples/svelte/create-hotkey/src/Root.svelte new file mode 100644 index 0000000..2aa9ee8 --- /dev/null +++ b/examples/svelte/create-hotkey/src/Root.svelte @@ -0,0 +1,8 @@ + + + + + diff --git a/examples/svelte/create-hotkey/src/main.ts b/examples/svelte/create-hotkey/src/main.ts index acece3d..9357900 100644 --- a/examples/svelte/create-hotkey/src/main.ts +++ b/examples/svelte/create-hotkey/src/main.ts @@ -1,5 +1,5 @@ import { mount } from 'svelte' -import App from './App.svelte' +import Root from './Root.svelte' import './index.css' -mount(App, { target: document.getElementById('app')! }) +mount(Root, { target: document.getElementById('app')! }) diff --git a/examples/svelte/get-held-keys/src/App.svelte b/examples/svelte/get-held-keys/src/App.svelte index 0952647..761a610 100644 --- a/examples/svelte/get-held-keys/src/App.svelte +++ b/examples/svelte/get-held-keys/src/App.svelte @@ -3,7 +3,6 @@ formatKeyForDebuggingDisplay, getHeldKeys, getHeldKeyCodesMap, - HotkeysProvider, } from '@tanstack/svelte-hotkeys' const heldKeys = getHeldKeys() @@ -23,92 +22,90 @@ }) - -
-
-

getHeldKeys

-

- Returns an array of all currently pressed keys. Useful for displaying - key combinations or building custom shortcut recording. -

-
+
+
+

getHeldKeys

+

+ Returns an array of all currently pressed keys. Useful for displaying key + combinations or building custom shortcut recording. +

+
-
-
-

Currently Held Keys

-
- {#if heldKeys.length > 0} - {#each heldKeys as key, index} - {@const code = heldKeyCodesMap[key]} - {#if index > 0} - + +
+
+

Currently Held Keys

+
+ {#if heldKeys.length > 0} + {#each heldKeys as key, index} + {@const code = heldKeyCodesMap[key]} + {#if index > 0} + + + {/if} + + {formatKeyForDebuggingDisplay(key)} + {#if code && code !== key} + + {formatKeyForDebuggingDisplay(code, { + source: 'code', + })} + {/if} - - {formatKeyForDebuggingDisplay(key)} - {#if code && code !== key} - - {formatKeyForDebuggingDisplay(code, { - source: 'code', - })} - - {/if} - - {/each} - {:else} - Press any keys... - {/if} -
-
- Keys held: {heldKeys.length} -
-
+ + {/each} + {:else} + Press any keys... + {/if} +
+
+ Keys held: {heldKeys.length} +
+
-
-

Usage

-
{`import { getHeldKeys } from '@tanstack/svelte-hotkeys'
+    
+

Usage

+
{`import { getHeldKeys } from '@tanstack/svelte-hotkeys'
 
 const heldKeys = getHeldKeys()
 
 // In template:
 // Currently pressed: {heldKeys.join(' + ') || 'None'}`}
-
+
-
-

Try These Combinations

-
    -
  • - Hold Shift + Control + A -
  • -
  • Press multiple letter keys at once
  • -
  • Hold modifiers and watch them appear
  • -
  • Release keys one by one
  • -
-
+
+

Try These Combinations

+
    +
  • + Hold Shift + Control + A +
  • +
  • Press multiple letter keys at once
  • +
  • Hold modifiers and watch them appear
  • +
  • Release keys one by one
  • +
+
-
-

Recent Combinations

- {#if history.length > 0} -
    - {#each history as combo} -
  • {combo}
  • - {/each} -
- - {:else} -

Press some key combinations...

- {/if} -
- -
-

Use Cases

-
    -
  • Building a keyboard shortcut recorder
  • -
  • Displaying currently held keys to users
  • -
  • Debugging keyboard input
  • -
  • Creating key combination tutorials
  • +
    +

    Recent Combinations

    + {#if history.length > 0} +
      + {#each history as combo} +
    • {combo}
    • + {/each}
    -
    -
-
- + + {:else} +

Press some key combinations...

+ {/if} + + +
+

Use Cases

+
    +
  • Building a keyboard shortcut recorder
  • +
  • Displaying currently held keys to users
  • +
  • Debugging keyboard input
  • +
  • Creating key combination tutorials
  • +
+
+ +
diff --git a/examples/svelte/get-held-keys/src/Root.svelte b/examples/svelte/get-held-keys/src/Root.svelte new file mode 100644 index 0000000..2aa9ee8 --- /dev/null +++ b/examples/svelte/get-held-keys/src/Root.svelte @@ -0,0 +1,8 @@ + + + + + diff --git a/examples/svelte/get-held-keys/src/main.ts b/examples/svelte/get-held-keys/src/main.ts index acece3d..9357900 100644 --- a/examples/svelte/get-held-keys/src/main.ts +++ b/examples/svelte/get-held-keys/src/main.ts @@ -1,5 +1,5 @@ import { mount } from 'svelte' -import App from './App.svelte' +import Root from './Root.svelte' import './index.css' -mount(App, { target: document.getElementById('app')! }) +mount(Root, { target: document.getElementById('app')! }) diff --git a/examples/svelte/get-is-key-held/src/App.svelte b/examples/svelte/get-is-key-held/src/App.svelte index 75464ac..8a73eb3 100644 --- a/examples/svelte/get-is-key-held/src/App.svelte +++ b/examples/svelte/get-is-key-held/src/App.svelte @@ -1,5 +1,5 @@ - -
-
-

getIsKeyHeld

-

- Returns a boolean indicating if a specific key is currently held. - Optimized to only re-render when that specific key changes. -

-
+
+
+

getIsKeyHeld

+

+ Returns a boolean indicating if a specific key is currently held. + Optimized to only re-render when that specific key changes. +

+
-
-
-

Modifier Key States

-
-
- Shift - - {isShiftHeld ? 'HELD' : 'Released'} - -
-
- Control - - {isControlHeld ? 'HELD' : 'Released'} - -
-
- Alt / Option - {isAltHeld ? 'HELD' : 'Released'} -
-
- Meta (⌘ / ⊞) - {isMetaHeld ? 'HELD' : 'Released'} -
+
+
+

Modifier Key States

+
+
+ Shift + + {isShiftHeld ? 'HELD' : 'Released'} +
-
- -
-

Space Bar Demo

-
- {isSpaceHeld ? '🚀 SPACE HELD!' : 'Hold Space Bar'} +
+ Control + + {isControlHeld ? 'HELD' : 'Released'} + +
+
+ Alt / Option + {isAltHeld ? 'HELD' : 'Released'} +
+
+ Meta (⌘ / ⊞) + {isMetaHeld ? 'HELD' : 'Released'}
-
+
+
-
-

Usage

-
{`import { getIsKeyHeld } from '@tanstack/svelte-hotkeys'
+    
+

Space Bar Demo

+
+ {isSpaceHeld ? '🚀 SPACE HELD!' : 'Hold Space Bar'} +
+
+ +
+

Usage

+
{`import { getIsKeyHeld } from '@tanstack/svelte-hotkeys'
 
 const isShiftHeld = getIsKeyHeld('Shift')
 
@@ -63,32 +62,31 @@ const isShiftHeld = getIsKeyHeld('Shift')
 // 
// {isShiftHeld ? 'Shift is pressed!' : 'Press Shift'} //
`}
-
+
-
-

Conditional UI Example

-

- Hold Shift to reveal the secret message: -

-
- {#if isShiftHeld} - 🎉 The secret password is: tanstack-hotkeys-rocks! - {:else} - •••••••••••••••••••••••••• - {/if} -
-
+
+

Conditional UI Example

+

+ Hold Shift to reveal the secret message: +

+
+ {#if isShiftHeld} + 🎉 The secret password is: tanstack-hotkeys-rocks! + {:else} + •••••••••••••••••••••••••• + {/if} +
+
-
-

Use Cases

-
    -
  • Show different UI based on modifier state
  • -
  • Enable "power user" mode while holding a key
  • -
  • Hold-to-reveal sensitive information
  • -
  • Drag-and-drop with modifier behaviors
  • -
  • Show additional options on hover + modifier
  • -
-
-
-
- +
+

Use Cases

+
    +
  • Show different UI based on modifier state
  • +
  • Enable "power user" mode while holding a key
  • +
  • Hold-to-reveal sensitive information
  • +
  • Drag-and-drop with modifier behaviors
  • +
  • Show additional options on hover + modifier
  • +
+
+ +
diff --git a/examples/svelte/get-is-key-held/src/Root.svelte b/examples/svelte/get-is-key-held/src/Root.svelte new file mode 100644 index 0000000..2aa9ee8 --- /dev/null +++ b/examples/svelte/get-is-key-held/src/Root.svelte @@ -0,0 +1,8 @@ + + + + + diff --git a/examples/svelte/get-is-key-held/src/main.ts b/examples/svelte/get-is-key-held/src/main.ts index acece3d..9357900 100644 --- a/examples/svelte/get-is-key-held/src/main.ts +++ b/examples/svelte/get-is-key-held/src/main.ts @@ -1,5 +1,5 @@ import { mount } from 'svelte' -import App from './App.svelte' +import Root from './Root.svelte' import './index.css' -mount(App, { target: document.getElementById('app')! }) +mount(Root, { target: document.getElementById('app')! }) From 3509cb7a028b0e85b0f83999ad1101785038ee74 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Mon, 9 Mar 2026 08:46:44 -0500 Subject: [PATCH 11/16] get examples to actually work --- .../create-hotkey-recorder/src/App.svelte | 90 ++++++++----------- .../src/ShortcutListItem.svelte | 5 +- examples/svelte/create-hotkey/src/App.svelte | 16 ++-- examples/svelte/get-held-keys/src/App.svelte | 9 +- .../svelte/get-is-key-held/src/App.svelte | 19 ++-- .../svelte-hotkeys/src/createHotkey.svelte.ts | 64 ++++++------- .../src/createHotkeyRecorder.svelte.ts | 12 ++- .../src/getHeldKeyCodesMap.svelte.ts | 21 +++-- .../svelte-hotkeys/src/getHeldKeys.svelte.ts | 18 ++-- .../svelte-hotkeys/src/getIsKeyHeld.svelte.ts | 23 +++-- 10 files changed, 137 insertions(+), 140 deletions(-) diff --git a/examples/svelte/create-hotkey-recorder/src/App.svelte b/examples/svelte/create-hotkey-recorder/src/App.svelte index 611e2ef..bfce624 100644 --- a/examples/svelte/create-hotkey-recorder/src/App.svelte +++ b/examples/svelte/create-hotkey-recorder/src/App.svelte @@ -41,13 +41,14 @@ }, } - let shortcuts = $state>(() => { - const defaults: Record = {} - for (const [id, action] of Object.entries(DEFAULT_SHORTCUT_ACTIONS)) { - defaults[id] = action.defaultHotkey - } - return defaults - }) + let shortcuts = $state>( + Object.fromEntries( + Object.entries(DEFAULT_SHORTCUT_ACTIONS).map(([id, action]) => [ + id, + action.defaultHotkey, + ]), + ), + ) let saveCount = $state(0) let openCount = $state(0) @@ -82,85 +83,70 @@ }, }) - const isRecording = recorder.isRecording - - const saveHotkey: Hotkey = - shortcuts.save || DEFAULT_SHORTCUT_ACTIONS.save.defaultHotkey - const openHotkey: Hotkey = - shortcuts.open || DEFAULT_SHORTCUT_ACTIONS.open.defaultHotkey - const newHotkey: Hotkey = - shortcuts.new || DEFAULT_SHORTCUT_ACTIONS.new.defaultHotkey - const closeHotkey: Hotkey = - shortcuts.close || DEFAULT_SHORTCUT_ACTIONS.close.defaultHotkey - const undoHotkey: Hotkey = - shortcuts.undo || DEFAULT_SHORTCUT_ACTIONS.undo.defaultHotkey - const redoHotkey: Hotkey = - shortcuts.redo || DEFAULT_SHORTCUT_ACTIONS.redo.defaultHotkey - createHotkey( - saveHotkey, + () => shortcuts.save || DEFAULT_SHORTCUT_ACTIONS.save.defaultHotkey, () => { - console.log('Save triggered:', saveHotkey) + console.log('Save triggered:', shortcuts.save) saveCount++ }, - { - enabled: !isRecording && shortcuts.save !== '', - }, + () => ({ + enabled: !recorder.isRecording && shortcuts.save !== '', + }), ) createHotkey( - openHotkey, + () => shortcuts.open || DEFAULT_SHORTCUT_ACTIONS.open.defaultHotkey, () => { - console.log('Open triggered:', openHotkey) + console.log('Open triggered:', shortcuts.open) openCount++ }, - { - enabled: !isRecording && shortcuts.open !== '', - }, + () => ({ + enabled: !recorder.isRecording && shortcuts.open !== '', + }), ) createHotkey( - newHotkey, + () => shortcuts.new || DEFAULT_SHORTCUT_ACTIONS.new.defaultHotkey, () => { - console.log('New triggered:', newHotkey) + console.log('New triggered:', shortcuts.new) newCount++ }, - { - enabled: !isRecording && shortcuts.new !== '', - }, + () => ({ + enabled: !recorder.isRecording && shortcuts.new !== '', + }), ) createHotkey( - closeHotkey, + () => shortcuts.close || DEFAULT_SHORTCUT_ACTIONS.close.defaultHotkey, () => { - console.log('Close triggered:', closeHotkey) + console.log('Close triggered:', shortcuts.close) closeCount++ }, - { - enabled: !isRecording && shortcuts.close !== '', - }, + () => ({ + enabled: !recorder.isRecording && shortcuts.close !== '', + }), ) createHotkey( - undoHotkey, + () => shortcuts.undo || DEFAULT_SHORTCUT_ACTIONS.undo.defaultHotkey, () => { - console.log('Undo triggered:', undoHotkey) + console.log('Undo triggered:', shortcuts.undo) undoCount++ }, - { - enabled: !isRecording && shortcuts.undo !== '', - }, + () => ({ + enabled: !recorder.isRecording && shortcuts.undo !== '', + }), ) createHotkey( - redoHotkey, + () => shortcuts.redo || DEFAULT_SHORTCUT_ACTIONS.redo.defaultHotkey, () => { - console.log('Redo triggered:', redoHotkey) + console.log('Redo triggered:', shortcuts.redo) redoCount++ }, - { - enabled: !isRecording && shortcuts.redo !== '', - }, + () => ({ + enabled: !recorder.isRecording && shortcuts.redo !== '', + }), ) function handleEdit(actionId: string) { diff --git a/examples/svelte/create-hotkey-recorder/src/ShortcutListItem.svelte b/examples/svelte/create-hotkey-recorder/src/ShortcutListItem.svelte index 631c442..611574a 100644 --- a/examples/svelte/create-hotkey-recorder/src/ShortcutListItem.svelte +++ b/examples/svelte/create-hotkey-recorder/src/ShortcutListItem.svelte @@ -12,10 +12,11 @@ let { actionName, hotkey, isRecording, onEdit, onCancel }: Props = $props() - const heldKeys = getHeldKeys() + const heldKeysRef = getHeldKeys() + const heldKeys = $derived(heldKeysRef.current) -
+
{actionName}
diff --git a/examples/svelte/create-hotkey/src/App.svelte b/examples/svelte/create-hotkey/src/App.svelte index fb9a459..934eedf 100644 --- a/examples/svelte/create-hotkey/src/App.svelte +++ b/examples/svelte/create-hotkey/src/App.svelte @@ -49,7 +49,7 @@ lastHotkey = 'Mod+E' alert('This hotkey can be toggled!') }, - { enabled: () => enabled }, + () => ({ enabled }), ) createHotkey('Mod+1', () => { @@ -203,7 +203,7 @@ 'Sidebar shortcut triggered! This only works when the sidebar area is focused.', ) }, - { target: () => sidebarRef }, + () => ({ target: sidebarRef }), ) createHotkey( @@ -212,7 +212,7 @@ lastHotkey = 'Mod+N' sidebarShortcutCount++ }, - { target: () => sidebarRef }, + () => ({ target: sidebarRef }), ) createHotkey( @@ -222,7 +222,7 @@ modalShortcutCount++ modalOpen = false }, - { target: () => modalRef, enabled: () => modalOpen }, + () => ({ target: modalRef, enabled: modalOpen }), ) createHotkey( @@ -232,7 +232,7 @@ modalShortcutCount++ alert('Modal submit shortcut!') }, - { target: () => modalRef, enabled: () => modalOpen }, + () => ({ target: modalRef, enabled: modalOpen }), ) createHotkey( @@ -244,7 +244,7 @@ `Editor content saved: "${editorContent.substring(0, 50)}${editorContent.length > 50 ? '...' : ''}"`, ) }, - { target: () => editorRef }, + () => ({ target: editorRef }), ) createHotkey( @@ -254,7 +254,7 @@ editorShortcutCount++ editorContent += '\n// Comment added via shortcut' }, - { target: () => editorRef }, + () => ({ target: editorRef }), ) createHotkey( @@ -264,7 +264,7 @@ editorShortcutCount++ editorContent = '' }, - { target: () => editorRef }, + () => ({ target: editorRef }), ) diff --git a/examples/svelte/get-held-keys/src/App.svelte b/examples/svelte/get-held-keys/src/App.svelte index 761a610..5834c0b 100644 --- a/examples/svelte/get-held-keys/src/App.svelte +++ b/examples/svelte/get-held-keys/src/App.svelte @@ -5,8 +5,10 @@ getHeldKeyCodesMap, } from '@tanstack/svelte-hotkeys' - const heldKeys = getHeldKeys() - const heldKeyCodesMap = getHeldKeyCodesMap() + const heldKeysRef = getHeldKeys() + const heldKeyCodesMapRef = getHeldKeyCodesMap() + const heldKeys = $derived(heldKeysRef.current) + const heldKeyCodesMap = $derived(heldKeyCodesMapRef.current) let history = $state>([]) @@ -66,7 +68,8 @@
{`import { getHeldKeys } from '@tanstack/svelte-hotkeys'
 
-const heldKeys = getHeldKeys()
+const heldKeysRef = getHeldKeys()
+const heldKeys = $derived(heldKeysRef.current)
 
 // In template:
 // Currently pressed: {heldKeys.join(' + ') || 'None'}`}
diff --git a/examples/svelte/get-is-key-held/src/App.svelte b/examples/svelte/get-is-key-held/src/App.svelte index 8a73eb3..3c7ab6a 100644 --- a/examples/svelte/get-is-key-held/src/App.svelte +++ b/examples/svelte/get-is-key-held/src/App.svelte @@ -1,11 +1,17 @@
@@ -56,7 +62,8 @@
{`import { getIsKeyHeld } from '@tanstack/svelte-hotkeys'
 
-const isShiftHeld = getIsKeyHeld('Shift')
+const isShiftHeldRef = getIsKeyHeld('Shift')
+const isShiftHeld = $derived(isShiftHeldRef.current)
 
 // In template:
 // 
diff --git a/packages/svelte-hotkeys/src/createHotkey.svelte.ts b/packages/svelte-hotkeys/src/createHotkey.svelte.ts index 1d968bf..679ee85 100644 --- a/packages/svelte-hotkeys/src/createHotkey.svelte.ts +++ b/packages/svelte-hotkeys/src/createHotkey.svelte.ts @@ -82,15 +82,10 @@ function resolveTarget(target: Target): ResolvedTarget { */ export function createHotkey( - hotkey: RegisterableHotkey, + hotkey: RegisterableHotkey | (() => RegisterableHotkey), callback: HotkeyCallback, - options: CreateHotkeyOptions = {}, + options: CreateHotkeyOptions | (() => CreateHotkeyOptions) = {}, ): void { - const mergedOptions = { - ...getDefaultHotkeysOptions().hotkey, - ...options, - } as CreateHotkeyOptions - const manager = getHotkeyManager() // Stable ref for registration handle @@ -98,12 +93,10 @@ export function createHotkey( // Refs to capture current values for use in effect without adding dependencies let callbackRef = callback - let optionsRef = mergedOptions let managerRef = manager $effect(() => { callbackRef = callback - optionsRef = mergedOptions managerRef = manager }) @@ -111,20 +104,28 @@ export function createHotkey( let prevTargetRef: ResolvedTarget = null let prevHotkeyRef: string | null = 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 reactive values (support getters for dynamic shortcuts) + const resolvedHotkey = typeof hotkey === 'function' ? hotkey() : hotkey + const resolvedOptions = typeof options === 'function' ? options() : options + + const mergedOptions = { + ...getDefaultHotkeysOptions().hotkey, + ...resolvedOptions, + } as CreateHotkeyOptions + + // Normalize to hotkey string + const platform = mergedOptions.platform ?? detectPlatform() + const hotkeyString: Hotkey = + typeof resolvedHotkey === 'string' + ? resolvedHotkey + : (formatHotkey( + rawHotkeyToParsedHotkey(resolvedHotkey, platform), + ) as Hotkey) + // Resolve target inside the effect so refs are already attached after mount - const resolvedTarget = optionsRef.target - ? resolveTarget(optionsRef.target) + const resolvedTarget = mergedOptions.target + ? resolveTarget(mergedOptions.target) : typeof document !== 'undefined' ? document : null @@ -134,6 +135,9 @@ export function createHotkey( return } + // Extract options without target (target is handled separately) + const { target: _target, ...optionsWithoutTarget } = mergedOptions + // Check if we need to re-register (target or hotkey changed) const targetChanged = prevTargetRef !== null && prevTargetRef !== resolvedTarget @@ -147,14 +151,19 @@ export function createHotkey( } // 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, + ...optionsWithoutTarget, target: resolvedTarget, }) } + // Update callback and options + if (registrationRef.isActive) { + registrationRef.callback = callbackRef + registrationRef.setOptions(optionsWithoutTarget) + } + // Update tracking refs prevTargetRef = resolvedTarget prevHotkeyRef = hotkeyString @@ -167,13 +176,4 @@ export function createHotkey( } } }) - - // 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 index 05f4061..a1aafb9 100644 --- a/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts +++ b/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts @@ -1,4 +1,5 @@ import { HotkeyRecorder } from '@tanstack/hotkeys' +import { useStore } from '@tanstack/svelte-store' import { onDestroy } from 'svelte' import { getDefaultHotkeysOptions } from './HotkeysCtx' import type { Hotkey, HotkeyRecorderOptions } from '@tanstack/hotkeys' @@ -69,8 +70,11 @@ export function createHotkeyRecorder( recorder.setOptions(mergedOptions) }) - const isRecording = $derived.by(() => recorder.store.state.isRecording) - const recordedHotkey = $derived.by(() => recorder.store.state.recordedHotkey) + const isRecordingRef = useStore(recorder.store, (state) => state.isRecording) + const recordedHotkeyRef = useStore( + recorder.store, + (state) => state.recordedHotkey, + ) onDestroy(() => { recorder.destroy() @@ -78,10 +82,10 @@ export function createHotkeyRecorder( return { get isRecording() { - return isRecording + return isRecordingRef.current }, get recordedHotkey() { - return recordedHotkey + return recordedHotkeyRef.current }, startRecording: () => recorder.start(), stopRecording: () => recorder.stop(), diff --git a/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts b/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts index 0095361..6230871 100644 --- a/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts +++ b/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts @@ -2,17 +2,20 @@ 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. + * Svelte function that returns a reactive reference to the 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"). + * Use `$derived(getHeldKeyCodesMap().current)` for reactive access in templates. * - * @returns Record mapping normalized key names to their `event.code` values + * @returns Object with `current` property containing the held key codes map * * ```svelte * * *
@@ -24,13 +27,9 @@ import { useStore } from '@tanstack/svelte-store' *
* ``` */ -export function getHeldKeyCodesMap(): Record { +export function getHeldKeyCodesMap(): { + readonly current: Record +} { const tracker = getKeyStateTracker() - - const heldKeyCodesMap = useStore( - tracker.store, - (state) => state.heldCodes, - ).current - - return heldKeyCodesMap + return useStore(tracker.store, (state) => state.heldCodes) } diff --git a/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts b/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts index 78d651c..c91d65a 100644 --- a/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts +++ b/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts @@ -2,29 +2,27 @@ import { getKeyStateTracker } from '@tanstack/hotkeys' import { useStore } from '@tanstack/svelte-store' /** - * Svelte function that returns an array of currently held keyboard keys. + * Svelte function that returns a reactive reference to currently held keyboard keys. * * This function uses the global KeyStateTracker and updates whenever keys are pressed - * or released. + * or released. Use `$derived(getHeldKeys().current)` for reactive access in templates. * - * @returns Array of currently held key names + * @returns Object with `current` property containing the array of held key names * * @example * ```svelte * *
- * Currently pressed: {getHeldKeys().join(' + ') || 'None'} + * Currently pressed: {heldKeys.join(' + ') || 'None'} *
* ``` */ -export function getHeldKeys(): Array { +export function getHeldKeys(): { readonly current: Array } { const tracker = getKeyStateTracker() - - const heldKeys = useStore(tracker.store, (state) => state.heldKeys).current - - return heldKeys + return useStore(tracker.store, (state) => state.heldKeys) } diff --git a/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts b/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts index 27b6814..ec9a89a 100644 --- a/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts +++ b/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts @@ -3,20 +3,21 @@ import { useStore } from '@tanstack/svelte-store' import type { HeldKey } from '@tanstack/hotkeys' /** - * Svelte function that returns whether a specific key is currently being held. + * Svelte function that returns a reactive reference to whether a specific key is currently being held. * * This function uses the global KeyStateTracker and updates whenever keys are pressed - * or released. + * or released. Use `$derived(getIsKeyHeld('Shift').current)` for reactive access in templates. * * @param key - The key to check (e.g., 'Shift', 'Control', 'A') - * @returns True if the key is currently held down + * @returns Object with `current` property - true if the key is currently held down * * @example * ```svelte * * *
@@ -29,9 +30,9 @@ import type { HeldKey } from '@tanstack/hotkeys' * * *
@@ -42,13 +43,11 @@ import type { HeldKey } from '@tanstack/hotkeys' * ``` */ -export function getIsKeyHeld(key: HeldKey): boolean { +export function getIsKeyHeld(key: HeldKey): { readonly current: boolean } { const tracker = getKeyStateTracker() const normalizedKey = key.toLowerCase() - const isKeyHeld = useStore(tracker.store, (state) => + return useStore(tracker.store, (state) => state.heldKeys.some((heldKey) => heldKey.toLowerCase() === normalizedKey), - ).current - - return isKeyHeld + ) } From 5ac287a2afbe80c337ce7269904c5bbd6880a792 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 13:50:06 +0000 Subject: [PATCH 12/16] ci: apply automated fixes --- .../reference/functions/createHotkey.md | 4 +-- .../functions/createHotkeyRecorder.md | 2 +- .../reference/functions/getHeldKeyCodesMap.md | 21 ++++++++++----- .../svelte/reference/functions/getHeldKeys.md | 23 ++++++++++------ .../reference/functions/getIsKeyHeld.md | 27 ++++++++++++------- .../interfaces/SvelteHotkeyRecorder.md | 12 ++++----- 6 files changed, 56 insertions(+), 33 deletions(-) diff --git a/docs/framework/svelte/reference/functions/createHotkey.md b/docs/framework/svelte/reference/functions/createHotkey.md index 09e01fa..2a8872b 100644 --- a/docs/framework/svelte/reference/functions/createHotkey.md +++ b/docs/framework/svelte/reference/functions/createHotkey.md @@ -29,7 +29,7 @@ the latest values. ### hotkey -`RegisterableHotkey` +`RegisterableHotkey` | () => `RegisterableHotkey` ### callback @@ -37,7 +37,7 @@ the latest values. ### options -[`CreateHotkeyOptions`](../interfaces/CreateHotkeyOptions.md) = `{}` +[`CreateHotkeyOptions`](../interfaces/CreateHotkeyOptions.md) | () => [`CreateHotkeyOptions`](../interfaces/CreateHotkeyOptions.md) ## Returns diff --git a/docs/framework/svelte/reference/functions/createHotkeyRecorder.md b/docs/framework/svelte/reference/functions/createHotkeyRecorder.md index 8df7fe4..6340934 100644 --- a/docs/framework/svelte/reference/functions/createHotkeyRecorder.md +++ b/docs/framework/svelte/reference/functions/createHotkeyRecorder.md @@ -9,7 +9,7 @@ title: createHotkeyRecorder function createHotkeyRecorder(options): SvelteHotkeyRecorder; ``` -Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:56](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L56) +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:57](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L57) Svelte function for recording keyboard shortcuts. diff --git a/docs/framework/svelte/reference/functions/getHeldKeyCodesMap.md b/docs/framework/svelte/reference/functions/getHeldKeyCodesMap.md index 967e846..4f20b4a 100644 --- a/docs/framework/svelte/reference/functions/getHeldKeyCodesMap.md +++ b/docs/framework/svelte/reference/functions/getHeldKeyCodesMap.md @@ -6,26 +6,29 @@ title: getHeldKeyCodesMap # Function: getHeldKeyCodesMap() ```ts -function getHeldKeyCodesMap(): Record; +function getHeldKeyCodesMap(): object; ``` -Defined in: [packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts:27](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts#L27) +Defined in: [packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts:30](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts#L30) -Svelte function that returns a map of currently held key names to their physical `event.code` values. +Svelte function that returns a reactive reference to the 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"). +Use `$derived(getHeldKeyCodesMap().current)` for reactive access in templates. ## Returns -`Record`\<`string`, `string`\> +`object` -Record mapping normalized key names to their `event.code` values +Object with `current` property containing the held key codes map ```svelte
@@ -36,3 +39,9 @@ Record mapping normalized key names to their `event.code` values ))}
``` + +### current + +```ts +readonly current: Record; +``` diff --git a/docs/framework/svelte/reference/functions/getHeldKeys.md b/docs/framework/svelte/reference/functions/getHeldKeys.md index f5c1f19..1f17926 100644 --- a/docs/framework/svelte/reference/functions/getHeldKeys.md +++ b/docs/framework/svelte/reference/functions/getHeldKeys.md @@ -6,21 +6,27 @@ title: getHeldKeys # Function: getHeldKeys() ```ts -function getHeldKeys(): string[]; +function getHeldKeys(): object; ``` -Defined in: [packages/svelte-hotkeys/src/getHeldKeys.svelte.ts:24](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts#L24) +Defined in: [packages/svelte-hotkeys/src/getHeldKeys.svelte.ts:25](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts#L25) -Svelte function that returns an array of currently held keyboard keys. +Svelte function that returns a reactive reference to currently held keyboard keys. This function uses the global KeyStateTracker and updates whenever keys are pressed -or released. +or released. Use `$derived(getHeldKeys().current)` for reactive access in templates. ## Returns -`string`[] +`object` -Array of currently held key names +Object with `current` property containing the array of held key names + +### current + +```ts +readonly current: string[]; +``` ## Example @@ -28,9 +34,10 @@ Array of currently held key names
- Currently pressed: {getHeldKeys().join(' + ') || 'None'} + Currently pressed: {heldKeys.join(' + ') || 'None'}
``` diff --git a/docs/framework/svelte/reference/functions/getIsKeyHeld.md b/docs/framework/svelte/reference/functions/getIsKeyHeld.md index f2f0442..415b3b2 100644 --- a/docs/framework/svelte/reference/functions/getIsKeyHeld.md +++ b/docs/framework/svelte/reference/functions/getIsKeyHeld.md @@ -6,15 +6,15 @@ title: getIsKeyHeld # Function: getIsKeyHeld() ```ts -function getIsKeyHeld(key): boolean; +function getIsKeyHeld(key): object; ``` -Defined in: [packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts:45](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts#L45) +Defined in: [packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts:46](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts#L46) -Svelte function that returns whether a specific key is currently being held. +Svelte function that returns a reactive reference to whether a specific key is currently being held. This function uses the global KeyStateTracker and updates whenever keys are pressed -or released. +or released. Use `$derived(getIsKeyHeld('Shift').current)` for reactive access in templates. ## Parameters @@ -26,9 +26,15 @@ The key to check (e.g., 'Shift', 'Control', 'A') ## Returns -`boolean` +`object` -True if the key is currently held down +Object with `current` property - true if the key is currently held down + +### current + +```ts +readonly current: boolean; +``` ## Examples @@ -36,7 +42,8 @@ True if the key is currently held down
@@ -48,9 +55,9 @@ True if the key is currently held down
diff --git a/docs/framework/svelte/reference/interfaces/SvelteHotkeyRecorder.md b/docs/framework/svelte/reference/interfaces/SvelteHotkeyRecorder.md index 777e7fd..fb39159 100644 --- a/docs/framework/svelte/reference/interfaces/SvelteHotkeyRecorder.md +++ b/docs/framework/svelte/reference/interfaces/SvelteHotkeyRecorder.md @@ -5,7 +5,7 @@ title: SvelteHotkeyRecorder # Interface: SvelteHotkeyRecorder -Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:6](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L6) +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:7](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L7) ## Properties @@ -15,7 +15,7 @@ Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:6](https cancelRecording: () => void; ``` -Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:16](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L16) +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:17](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L17) Cancel recording without saving @@ -31,7 +31,7 @@ Cancel recording without saving isRecording: boolean; ``` -Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:8](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L8) +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:9](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L9) Whether recording is currently active @@ -43,7 +43,7 @@ Whether recording is currently active recordedHotkey: Hotkey | null; ``` -Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:10](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L10) +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:11](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L11) The currently recorded hotkey (for live preview) @@ -55,7 +55,7 @@ The currently recorded hotkey (for live preview) startRecording: () => void; ``` -Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:12](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L12) +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:13](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L13) Start recording a new hotkey @@ -71,7 +71,7 @@ Start recording a new hotkey stopRecording: () => void; ``` -Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:14](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L14) +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:15](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L15) Stop recording (same as cancel) From 80f8a4e541942635feb9ffe610e01d3fa9c4dc96 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Tue, 10 Mar 2026 09:17:05 -0500 Subject: [PATCH 13/16] refactor svelte adapter --- .../svelte/guides/hotkey-recording.md | 30 ++- docs/framework/svelte/guides/hotkeys.md | 76 +++---- .../svelte/guides/key-state-tracking.md | 19 +- docs/framework/svelte/guides/sequences.md | 44 ++-- docs/framework/svelte/quick-start.md | 91 +++++--- .../reference/functions/createHotkey.md | 46 +--- .../functions/createHotkeyAttachment.md | 53 +++++ .../functions/createHotkeyRecorder.md | 41 +--- .../functions/createHotkeySequence.md | 17 +- .../createHotkeySequenceAttachment.md | 51 +++++ .../functions/getDefaultHotkeysOptions.md | 2 +- .../reference/functions/getHeldKeyCodesMap.md | 30 +-- .../svelte/reference/functions/getHeldKeys.md | 23 +- .../reference/functions/getHotkeysContext.md | 16 ++ .../reference/functions/getIsKeyHeld.md | 35 ++- .../reference/functions/setHotkeysContext.md | 22 ++ docs/framework/svelte/reference/index.md | 11 +- .../interfaces/CreateHotkeyOptions.md | 16 +- .../interfaces/CreateHotkeySequenceOptions.md | 14 -- .../interfaces/HotkeysProviderOptions.md | 8 +- .../interfaces/HotkeysProviderProps.md | 6 +- .../interfaces/SvelteHeldKeyCodesMap.md | 18 ++ .../interfaces/SvelteHeldKeyState.md | 18 ++ .../reference/interfaces/SvelteHeldKeys.md | 18 ++ .../interfaces/SvelteHotkeyRecorder.md | 16 +- .../reference/type-aliases/ResolvedTarget.md | 12 -- .../svelte/reference/type-aliases/Target.md | 14 -- .../reference/variables/DEFAULT_OPTIONS.md | 2 +- .../reference/variables/getHotkeysContext.md | 16 -- .../reference/variables/setHotkeysContext.md | 22 -- .../create-hotkey-recorder/src/App.svelte | 17 +- .../create-hotkey-recorder/src/Root.svelte | 5 +- .../src/ShortcutListItem.svelte | 7 +- .../create-hotkey-sequence/src/App.svelte | 7 +- .../create-hotkey-sequence/src/Root.svelte | 5 +- examples/svelte/create-hotkey/src/App.svelte | 130 +++++------ examples/svelte/create-hotkey/src/Root.svelte | 5 +- examples/svelte/get-held-keys/src/App.svelte | 23 +- examples/svelte/get-held-keys/src/Root.svelte | 5 +- .../svelte/get-is-key-held/src/App.svelte | 47 ++-- .../svelte/get-is-key-held/src/Root.svelte | 5 +- packages/svelte-hotkeys/package.json | 3 +- packages/svelte-hotkeys/src/HotkeysCtx.ts | 34 ++- .../svelte-hotkeys/src/HotkeysProvider.svelte | 8 +- .../svelte-hotkeys/src/createHotkey.svelte.ts | 203 ++++++------------ .../src/createHotkeyRecorder.svelte.ts | 88 +++++--- .../src/createHotkeySequence.svelte.ts | 175 +++++++-------- .../src/getHeldKeyCodesMap.svelte.ts | 41 ++-- .../svelte-hotkeys/src/getHeldKeys.svelte.ts | 32 ++- .../svelte-hotkeys/src/getIsKeyHeld.svelte.ts | 55 +++-- packages/svelte-hotkeys/src/index.ts | 8 +- .../svelte-hotkeys/src/internal.svelte.ts | 26 +++ .../svelte-hotkeys/tests/createHotkey.test.ts | 9 + pnpm-lock.yaml | 23 +- 54 files changed, 892 insertions(+), 856 deletions(-) create mode 100644 docs/framework/svelte/reference/functions/createHotkeyAttachment.md create mode 100644 docs/framework/svelte/reference/functions/createHotkeySequenceAttachment.md create mode 100644 docs/framework/svelte/reference/functions/getHotkeysContext.md create mode 100644 docs/framework/svelte/reference/functions/setHotkeysContext.md create mode 100644 docs/framework/svelte/reference/interfaces/SvelteHeldKeyCodesMap.md create mode 100644 docs/framework/svelte/reference/interfaces/SvelteHeldKeyState.md create mode 100644 docs/framework/svelte/reference/interfaces/SvelteHeldKeys.md delete mode 100644 docs/framework/svelte/reference/type-aliases/ResolvedTarget.md delete mode 100644 docs/framework/svelte/reference/type-aliases/Target.md delete mode 100644 docs/framework/svelte/reference/variables/getHotkeysContext.md delete mode 100644 docs/framework/svelte/reference/variables/setHotkeysContext.md create mode 100644 packages/svelte-hotkeys/src/internal.svelte.ts create mode 100644 packages/svelte-hotkeys/tests/createHotkey.test.ts diff --git a/docs/framework/svelte/guides/hotkey-recording.md b/docs/framework/svelte/guides/hotkey-recording.md index 98472c6..a143859 100644 --- a/docs/framework/svelte/guides/hotkey-recording.md +++ b/docs/framework/svelte/guides/hotkey-recording.md @@ -9,7 +9,10 @@ TanStack Hotkeys provides the `createHotkeyRecorder` function for building short ```svelte
- {:else} @@ -70,6 +72,17 @@ TanStack Hotkeys provides three Svelte functions for tracking live keyboard stat const heldKeys = getHeldKeys() const heldCodes = getHeldKeyCodesMap() + +
+ {#each heldKeys.keys as key} + + {formatKeyForDebuggingDisplay(key)}: + {heldCodes.codes[key] + ? formatKeyForDebuggingDisplay(heldCodes.codes[key], { source: 'code' }) + : 'unknown'} + + {/each} +
``` ## Under the Hood diff --git a/docs/framework/svelte/guides/sequences.md b/docs/framework/svelte/guides/sequences.md index 7668a6c..2211644 100644 --- a/docs/framework/svelte/guides/sequences.md +++ b/docs/framework/svelte/guides/sequences.md @@ -5,7 +5,7 @@ id: sequences TanStack Hotkeys supports multi-key sequences in Svelte, where keys are pressed one after another rather than simultaneously. -## Basic Usage +## Global sequences ```svelte ``` -## Sequence Options +## Scoped sequences + +Use `createHotkeySequenceAttachment` when a sequence should only be active while a specific element owns focus. + +```svelte + + +
+ Focus here, then press g then g +
+``` + +## Sequence options ```ts createHotkeySequence(['G', 'G'], callback, { @@ -34,26 +52,24 @@ createHotkeySequence(['G', 'G'], callback, { let isVimMode = $state(true) - createHotkeySequence(['G', 'G'], () => scrollToTop(), { - enabled: () => isVimMode, - }) + createHotkeySequence( + ['G', 'G'], + () => scrollToTop(), + () => ({ enabled: isVimMode }), + ) ``` -## Global Default Options via Provider +## Default options ```svelte + import { setHotkeysContext } from '@tanstack/svelte-hotkeys' - - - + }) + ``` ## Common Patterns diff --git a/docs/framework/svelte/quick-start.md b/docs/framework/svelte/quick-start.md index 1a6a857..44290bc 100644 --- a/docs/framework/svelte/quick-start.md +++ b/docs/framework/svelte/quick-start.md @@ -9,7 +9,7 @@ Don't have TanStack Hotkeys installed yet? See the [Installation](../../installa ## Your First Hotkey -The `createHotkey` function is the primary way to register keyboard shortcuts in Svelte: +Use `createHotkey` for global shortcuts and attachments for element-scoped shortcuts. ```svelte ``` -### Scoped Hotkeys with Refs +### Scoped hotkeys with attachments ```svelte -
+

Press Escape while focused here to close

``` -### Conditional Hotkeys +### Reactive options ```svelte ``` -### Multi-Key Sequences +### Scoped sequences ```svelte + +
+ Focus here, then press g then g +
``` -### Tracking Held Keys +### Tracking held keys ```svelte
- {#if isShiftHeld}Shift mode active{/if} - {#if $heldKeys.length > 0}Keys: {$heldKeys.join('+')}{/if} + {#if isShiftHeld.held}Shift mode active{/if} + {#if heldKeys.keys.length > 0} + Keys: {heldKeys.keys.join('+')} + {/if}
``` -### Displaying Hotkeys in the UI +### Recording shortcuts + +```svelte + + + +``` + +### Displaying hotkeys in the UI ```svelte + import { setHotkeysContext } from '@tanstack/svelte-hotkeys' - console.log('Recording cancelled') }, - }} -> - - + }) + ``` ## Next Steps diff --git a/docs/framework/svelte/reference/functions/createHotkey.md b/docs/framework/svelte/reference/functions/createHotkey.md index 2a8872b..0ff3f2d 100644 --- a/docs/framework/svelte/reference/functions/createHotkey.md +++ b/docs/framework/svelte/reference/functions/createHotkey.md @@ -12,24 +12,15 @@ function createHotkey( options): void; ``` -Defined in: [packages/svelte-hotkeys/src/createHotkey.svelte.ts:84](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkey.svelte.ts#L84) +Defined in: [packages/svelte-hotkeys/src/createHotkey.svelte.ts:68](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkey.svelte.ts#L68) -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. +Register a global hotkey for the current component. ## Parameters ### hotkey -`RegisterableHotkey` | () => `RegisterableHotkey` +`MaybeGetter`\<`RegisterableHotkey`\> ### callback @@ -37,45 +28,20 @@ the latest values. ### options -[`CreateHotkeyOptions`](../interfaces/CreateHotkeyOptions.md) | () => [`CreateHotkeyOptions`](../interfaces/CreateHotkeyOptions.md) +`MaybeGetter`\<[`CreateHotkeyOptions`](../interfaces/CreateHotkeyOptions.md)\> = `{}` ## Returns `void` -## Examples - -```svelte - - - -
- .... -
-``` +## Example ```svelte - -
- Count: {count} -
``` diff --git a/docs/framework/svelte/reference/functions/createHotkeyAttachment.md b/docs/framework/svelte/reference/functions/createHotkeyAttachment.md new file mode 100644 index 0000000..70800a5 --- /dev/null +++ b/docs/framework/svelte/reference/functions/createHotkeyAttachment.md @@ -0,0 +1,53 @@ +--- +id: createHotkeyAttachment +title: createHotkeyAttachment +--- + +# Function: createHotkeyAttachment() + +```ts +function createHotkeyAttachment( + hotkey, + callback, +options): Attachment; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkey.svelte.ts:106](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkey.svelte.ts#L106) + +Create an attachment for element-scoped hotkeys. + +## Parameters + +### hotkey + +`MaybeGetter`\<`RegisterableHotkey`\> + +### callback + +`HotkeyCallback` + +### options + +`MaybeGetter`\<[`CreateHotkeyOptions`](../interfaces/CreateHotkeyOptions.md)\> = `{}` + +## Returns + +`Attachment`\<`HTMLElement`\> + +## Example + +```svelte + + +
+ Count: {count} +
+``` diff --git a/docs/framework/svelte/reference/functions/createHotkeyRecorder.md b/docs/framework/svelte/reference/functions/createHotkeyRecorder.md index 6340934..41e3db2 100644 --- a/docs/framework/svelte/reference/functions/createHotkeyRecorder.md +++ b/docs/framework/svelte/reference/functions/createHotkeyRecorder.md @@ -9,51 +9,14 @@ title: createHotkeyRecorder function createHotkeyRecorder(options): SvelteHotkeyRecorder; ``` -Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:57](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L57) - -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. +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:98](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L98) ## Parameters ### options -`HotkeyRecorderOptions` - -Configuration options for the recorder +`MaybeGetter`\<`HotkeyRecorderOptions`\> ## Returns [`SvelteHotkeyRecorder`](../interfaces/SvelteHotkeyRecorder.md) - -An object with recording state and control functions - -## Example - -```svelte - - -
- - {recorder.recordedHotkey && ( -
Recording: {recorder.recordedHotkey}
- )} -
-``` diff --git a/docs/framework/svelte/reference/functions/createHotkeySequence.md b/docs/framework/svelte/reference/functions/createHotkeySequence.md index dc4a82f..ddf71ce 100644 --- a/docs/framework/svelte/reference/functions/createHotkeySequence.md +++ b/docs/framework/svelte/reference/functions/createHotkeySequence.md @@ -12,32 +12,23 @@ function createHotkeySequence( options): void; ``` -Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:67](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L67) +Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:65](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L65) -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. +Register a global keyboard shortcut sequence for the current component. ## Parameters ### sequence -`HotkeySequence` - -Array of hotkey strings that form the sequence +`MaybeGetter`\<`HotkeySequence`\> ### callback `HotkeyCallback` -Function to call when the sequence is completed - ### options -[`CreateHotkeySequenceOptions`](../interfaces/CreateHotkeySequenceOptions.md) = `{}` - -Options for the sequence behavior +`MaybeGetter`\<[`CreateHotkeySequenceOptions`](../interfaces/CreateHotkeySequenceOptions.md)\> = `{}` ## Returns diff --git a/docs/framework/svelte/reference/functions/createHotkeySequenceAttachment.md b/docs/framework/svelte/reference/functions/createHotkeySequenceAttachment.md new file mode 100644 index 0000000..9db6292 --- /dev/null +++ b/docs/framework/svelte/reference/functions/createHotkeySequenceAttachment.md @@ -0,0 +1,51 @@ +--- +id: createHotkeySequenceAttachment +title: createHotkeySequenceAttachment +--- + +# Function: createHotkeySequenceAttachment() + +```ts +function createHotkeySequenceAttachment( + sequence, + callback, +options): Attachment; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:111](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L111) + +Create an attachment for element-scoped keyboard sequences. + +## Parameters + +### sequence + +`MaybeGetter`\<`HotkeySequence`\> + +### callback + +`HotkeyCallback` + +### options + +`MaybeGetter`\<[`CreateHotkeySequenceOptions`](../interfaces/CreateHotkeySequenceOptions.md)\> = `{}` + +## Returns + +`Attachment`\<`HTMLElement`\> + +## Example + +```svelte + + +
+ Focus here and press g then g +
+``` diff --git a/docs/framework/svelte/reference/functions/getDefaultHotkeysOptions.md b/docs/framework/svelte/reference/functions/getDefaultHotkeysOptions.md index 0948846..e3db423 100644 --- a/docs/framework/svelte/reference/functions/getDefaultHotkeysOptions.md +++ b/docs/framework/svelte/reference/functions/getDefaultHotkeysOptions.md @@ -9,7 +9,7 @@ title: getDefaultHotkeysOptions function getDefaultHotkeysOptions(): HotkeysProviderOptions; ``` -Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:33](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L33) +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:55](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L55) ## Returns diff --git a/docs/framework/svelte/reference/functions/getHeldKeyCodesMap.md b/docs/framework/svelte/reference/functions/getHeldKeyCodesMap.md index 4f20b4a..11767fc 100644 --- a/docs/framework/svelte/reference/functions/getHeldKeyCodesMap.md +++ b/docs/framework/svelte/reference/functions/getHeldKeyCodesMap.md @@ -6,42 +6,34 @@ title: getHeldKeyCodesMap # Function: getHeldKeyCodesMap() ```ts -function getHeldKeyCodesMap(): object; +function getHeldKeyCodesMap(): SvelteHeldKeyCodesMap; ``` -Defined in: [packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts:30](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts#L30) +Defined in: [packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts:42](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts#L42) -Svelte function that returns a reactive reference to the map of currently held key names +Svelte function that returns reactive access to the 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"). -Use `$derived(getHeldKeyCodesMap().current)` for reactive access in templates. ## Returns -`object` +[`SvelteHeldKeyCodesMap`](../interfaces/SvelteHeldKeyCodesMap.md) -Object with `current` property containing the held key codes map +Object with a reactive `codes` property ```svelte
- {Object.entries(heldKeyCodesMap).map(([key, code]) => ( - - {key} {code} - - ))} + {#each Object.entries(heldKeyCodesMap.codes) as [key, code]} + + {key} {code} + + {/each}
``` - -### current - -```ts -readonly current: Record; -``` diff --git a/docs/framework/svelte/reference/functions/getHeldKeys.md b/docs/framework/svelte/reference/functions/getHeldKeys.md index 1f17926..a50cd94 100644 --- a/docs/framework/svelte/reference/functions/getHeldKeys.md +++ b/docs/framework/svelte/reference/functions/getHeldKeys.md @@ -6,27 +6,21 @@ title: getHeldKeys # Function: getHeldKeys() ```ts -function getHeldKeys(): object; +function getHeldKeys(): SvelteHeldKeys; ``` -Defined in: [packages/svelte-hotkeys/src/getHeldKeys.svelte.ts:25](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts#L25) +Defined in: [packages/svelte-hotkeys/src/getHeldKeys.svelte.ts:38](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts#L38) -Svelte function that returns a reactive reference to currently held keyboard keys. +Svelte function that returns reactive access to currently held keyboard keys. This function uses the global KeyStateTracker and updates whenever keys are pressed -or released. Use `$derived(getHeldKeys().current)` for reactive access in templates. +or released. ## Returns -`object` +[`SvelteHeldKeys`](../interfaces/SvelteHeldKeys.md) -Object with `current` property containing the array of held key names - -### current - -```ts -readonly current: string[]; -``` +Object with a reactive `keys` property ## Example @@ -34,10 +28,9 @@ readonly current: string[];
- Currently pressed: {heldKeys.join(' + ') || 'None'} + Currently pressed: {heldKeys.keys.join(' + ') || 'None'}
``` diff --git a/docs/framework/svelte/reference/functions/getHotkeysContext.md b/docs/framework/svelte/reference/functions/getHotkeysContext.md new file mode 100644 index 0000000..bcefd64 --- /dev/null +++ b/docs/framework/svelte/reference/functions/getHotkeysContext.md @@ -0,0 +1,16 @@ +--- +id: getHotkeysContext +title: getHotkeysContext +--- + +# Function: getHotkeysContext() + +```ts +function getHotkeysContext(): HotkeysContextValue | null; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:47](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L47) + +## Returns + +`HotkeysContextValue` \| `null` diff --git a/docs/framework/svelte/reference/functions/getIsKeyHeld.md b/docs/framework/svelte/reference/functions/getIsKeyHeld.md index 415b3b2..5bf281a 100644 --- a/docs/framework/svelte/reference/functions/getIsKeyHeld.md +++ b/docs/framework/svelte/reference/functions/getIsKeyHeld.md @@ -6,15 +6,15 @@ title: getIsKeyHeld # Function: getIsKeyHeld() ```ts -function getIsKeyHeld(key): object; +function getIsKeyHeld(key): SvelteHeldKeyState; ``` -Defined in: [packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts:46](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts#L46) +Defined in: [packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts:66](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts#L66) -Svelte function that returns a reactive reference to whether a specific key is currently being held. +Svelte function that returns reactive access to whether a specific key is currently being held. This function uses the global KeyStateTracker and updates whenever keys are pressed -or released. Use `$derived(getIsKeyHeld('Shift').current)` for reactive access in templates. +or released. ## Parameters @@ -26,15 +26,9 @@ The key to check (e.g., 'Shift', 'Control', 'A') ## Returns -`object` +[`SvelteHeldKeyState`](../interfaces/SvelteHeldKeyState.md) -Object with `current` property - true if the key is currently held down - -### current - -```ts -readonly current: boolean; -``` +Object with a reactive `held` property ## Examples @@ -42,12 +36,11 @@ readonly current: boolean;
- {isShiftHeld ? 'Shift is pressed!' : 'Press Shift'} + {isShiftHeld.held ? 'Shift is pressed!' : 'Press Shift'}
``` @@ -55,14 +48,14 @@ readonly current: boolean;
- Ctrl - Shift - Alt + Ctrl + Shift + Alt
``` diff --git a/docs/framework/svelte/reference/functions/setHotkeysContext.md b/docs/framework/svelte/reference/functions/setHotkeysContext.md new file mode 100644 index 0000000..a778858 --- /dev/null +++ b/docs/framework/svelte/reference/functions/setHotkeysContext.md @@ -0,0 +1,22 @@ +--- +id: setHotkeysContext +title: setHotkeysContext +--- + +# Function: setHotkeysContext() + +```ts +function setHotkeysContext(defaultOptions): HotkeysContextValue; +``` + +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:27](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L27) + +## Parameters + +### defaultOptions + +[`HotkeysProviderOptions`](../interfaces/HotkeysProviderOptions.md) = `DEFAULT_OPTIONS` + +## Returns + +`HotkeysContextValue` diff --git a/docs/framework/svelte/reference/index.md b/docs/framework/svelte/reference/index.md index 1ccd2a6..d9e09c8 100644 --- a/docs/framework/svelte/reference/index.md +++ b/docs/framework/svelte/reference/index.md @@ -11,27 +11,30 @@ title: "@tanstack/svelte-hotkeys" - [CreateHotkeySequenceOptions](interfaces/CreateHotkeySequenceOptions.md) - [HotkeysProviderOptions](interfaces/HotkeysProviderOptions.md) - [HotkeysProviderProps](interfaces/HotkeysProviderProps.md) +- [SvelteHeldKeyCodesMap](interfaces/SvelteHeldKeyCodesMap.md) +- [SvelteHeldKeys](interfaces/SvelteHeldKeys.md) +- [SvelteHeldKeyState](interfaces/SvelteHeldKeyState.md) - [SvelteHotkeyRecorder](interfaces/SvelteHotkeyRecorder.md) ## Type Aliases - [HotkeysProvider](type-aliases/HotkeysProvider.md) -- [ResolvedTarget](type-aliases/ResolvedTarget.md) -- [Target](type-aliases/Target.md) ## Variables - [DEFAULT\_OPTIONS](variables/DEFAULT_OPTIONS.md) -- [getHotkeysContext](variables/getHotkeysContext.md) - [HotkeysProvider](variables/HotkeysProvider.md) -- [setHotkeysContext](variables/setHotkeysContext.md) ## Functions - [createHotkey](functions/createHotkey.md) +- [createHotkeyAttachment](functions/createHotkeyAttachment.md) - [createHotkeyRecorder](functions/createHotkeyRecorder.md) - [createHotkeySequence](functions/createHotkeySequence.md) +- [createHotkeySequenceAttachment](functions/createHotkeySequenceAttachment.md) - [getDefaultHotkeysOptions](functions/getDefaultHotkeysOptions.md) - [getHeldKeyCodesMap](functions/getHeldKeyCodesMap.md) - [getHeldKeys](functions/getHeldKeys.md) +- [getHotkeysContext](functions/getHotkeysContext.md) - [getIsKeyHeld](functions/getIsKeyHeld.md) +- [setHotkeysContext](functions/setHotkeysContext.md) diff --git a/docs/framework/svelte/reference/interfaces/CreateHotkeyOptions.md b/docs/framework/svelte/reference/interfaces/CreateHotkeyOptions.md index 99768d1..ef23d53 100644 --- a/docs/framework/svelte/reference/interfaces/CreateHotkeyOptions.md +++ b/docs/framework/svelte/reference/interfaces/CreateHotkeyOptions.md @@ -5,22 +5,8 @@ title: CreateHotkeyOptions # Interface: CreateHotkeyOptions -Defined in: [packages/svelte-hotkeys/src/createHotkey.svelte.ts:17](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkey.svelte.ts#L17) +Defined in: [packages/svelte-hotkeys/src/createHotkey.svelte.ts:18](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkey.svelte.ts#L18) ## Extends - `Omit`\<`HotkeyOptions`, `"target"`\> - -## Properties - -### target? - -```ts -optional target: Target; -``` - -Defined in: [packages/svelte-hotkeys/src/createHotkey.svelte.ts:23](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkey.svelte.ts#L23) - -The DOM element to attach the event listener to. -Can be a Svelte ref, direct DOM element, or null. -Defaults to document. diff --git a/docs/framework/svelte/reference/interfaces/CreateHotkeySequenceOptions.md b/docs/framework/svelte/reference/interfaces/CreateHotkeySequenceOptions.md index 319dfc9..b8dfd60 100644 --- a/docs/framework/svelte/reference/interfaces/CreateHotkeySequenceOptions.md +++ b/docs/framework/svelte/reference/interfaces/CreateHotkeySequenceOptions.md @@ -10,17 +10,3 @@ Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:12](http ## Extends - `Omit`\<`SequenceOptions`, `"target"`\> - -## Properties - -### target? - -```ts -optional target: Target; -``` - -Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:21](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L21) - -The DOM element to attach the event listener to. -Can be a Svelte ref, direct DOM element, or null. -Defaults to document. diff --git a/docs/framework/svelte/reference/interfaces/HotkeysProviderOptions.md b/docs/framework/svelte/reference/interfaces/HotkeysProviderOptions.md index 20b2e87..4716f04 100644 --- a/docs/framework/svelte/reference/interfaces/HotkeysProviderOptions.md +++ b/docs/framework/svelte/reference/interfaces/HotkeysProviderOptions.md @@ -5,7 +5,7 @@ title: HotkeysProviderOptions # Interface: HotkeysProviderOptions -Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:11](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L11) +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:7](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L7) ## Properties @@ -15,7 +15,7 @@ Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:11](https://github.com/Ta optional hotkey: Partial; ``` -Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:12](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L12) +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:8](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L8) *** @@ -25,7 +25,7 @@ Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:12](https://github.com/Ta optional hotkeyRecorder: Partial; ``` -Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:13](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L13) +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:9](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L9) *** @@ -35,4 +35,4 @@ Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:13](https://github.com/Ta optional hotkeySequence: Partial; ``` -Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:14](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L14) +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:10](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L10) diff --git a/docs/framework/svelte/reference/interfaces/HotkeysProviderProps.md b/docs/framework/svelte/reference/interfaces/HotkeysProviderProps.md index 15ca21a..6a02782 100644 --- a/docs/framework/svelte/reference/interfaces/HotkeysProviderProps.md +++ b/docs/framework/svelte/reference/interfaces/HotkeysProviderProps.md @@ -5,7 +5,7 @@ title: HotkeysProviderProps # Interface: HotkeysProviderProps -Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:17](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L17) +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:13](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L13) ## Properties @@ -15,7 +15,7 @@ Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:17](https://github.com/Ta children: Snippet; ``` -Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:18](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L18) +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:14](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L14) *** @@ -25,4 +25,4 @@ Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:18](https://github.com/Ta optional defaultOptions: HotkeysProviderOptions; ``` -Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:19](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L19) +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:15](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L15) diff --git a/docs/framework/svelte/reference/interfaces/SvelteHeldKeyCodesMap.md b/docs/framework/svelte/reference/interfaces/SvelteHeldKeyCodesMap.md new file mode 100644 index 0000000..dfa7564 --- /dev/null +++ b/docs/framework/svelte/reference/interfaces/SvelteHeldKeyCodesMap.md @@ -0,0 +1,18 @@ +--- +id: SvelteHeldKeyCodesMap +title: SvelteHeldKeyCodesMap +--- + +# Interface: SvelteHeldKeyCodesMap + +Defined in: [packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts:4](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts#L4) + +## Properties + +### codes + +```ts +readonly codes: Record; +``` + +Defined in: [packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts:5](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts#L5) diff --git a/docs/framework/svelte/reference/interfaces/SvelteHeldKeyState.md b/docs/framework/svelte/reference/interfaces/SvelteHeldKeyState.md new file mode 100644 index 0000000..31ecd19 --- /dev/null +++ b/docs/framework/svelte/reference/interfaces/SvelteHeldKeyState.md @@ -0,0 +1,18 @@ +--- +id: SvelteHeldKeyState +title: SvelteHeldKeyState +--- + +# Interface: SvelteHeldKeyState + +Defined in: [packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts:5](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts#L5) + +## Properties + +### held + +```ts +readonly held: boolean; +``` + +Defined in: [packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts:6](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts#L6) diff --git a/docs/framework/svelte/reference/interfaces/SvelteHeldKeys.md b/docs/framework/svelte/reference/interfaces/SvelteHeldKeys.md new file mode 100644 index 0000000..259e130 --- /dev/null +++ b/docs/framework/svelte/reference/interfaces/SvelteHeldKeys.md @@ -0,0 +1,18 @@ +--- +id: SvelteHeldKeys +title: SvelteHeldKeys +--- + +# Interface: SvelteHeldKeys + +Defined in: [packages/svelte-hotkeys/src/getHeldKeys.svelte.ts:4](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts#L4) + +## Properties + +### keys + +```ts +readonly keys: string[]; +``` + +Defined in: [packages/svelte-hotkeys/src/getHeldKeys.svelte.ts:5](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts#L5) diff --git a/docs/framework/svelte/reference/interfaces/SvelteHotkeyRecorder.md b/docs/framework/svelte/reference/interfaces/SvelteHotkeyRecorder.md index fb39159..a2e7f44 100644 --- a/docs/framework/svelte/reference/interfaces/SvelteHotkeyRecorder.md +++ b/docs/framework/svelte/reference/interfaces/SvelteHotkeyRecorder.md @@ -5,7 +5,7 @@ title: SvelteHotkeyRecorder # Interface: SvelteHotkeyRecorder -Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:7](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L7) +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:8](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L8) ## Properties @@ -15,7 +15,7 @@ Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:7](https cancelRecording: () => void; ``` -Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:17](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L17) +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:18](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L18) Cancel recording without saving @@ -28,10 +28,10 @@ Cancel recording without saving ### isRecording ```ts -isRecording: boolean; +readonly isRecording: boolean; ``` -Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:9](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L9) +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:10](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L10) Whether recording is currently active @@ -40,10 +40,10 @@ Whether recording is currently active ### recordedHotkey ```ts -recordedHotkey: Hotkey | null; +readonly recordedHotkey: Hotkey | null; ``` -Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:11](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L11) +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:12](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L12) The currently recorded hotkey (for live preview) @@ -55,7 +55,7 @@ The currently recorded hotkey (for live preview) startRecording: () => void; ``` -Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:13](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L13) +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:14](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L14) Start recording a new hotkey @@ -71,7 +71,7 @@ Start recording a new hotkey stopRecording: () => void; ``` -Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:15](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L15) +Defined in: [packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts:16](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts#L16) Stop recording (same as cancel) diff --git a/docs/framework/svelte/reference/type-aliases/ResolvedTarget.md b/docs/framework/svelte/reference/type-aliases/ResolvedTarget.md deleted file mode 100644 index 4f8ba32..0000000 --- a/docs/framework/svelte/reference/type-aliases/ResolvedTarget.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -id: ResolvedTarget -title: ResolvedTarget ---- - -# Type Alias: ResolvedTarget - -```ts -type ResolvedTarget = HTMLElement | Document | Window | null; -``` - -Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:9](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L9) diff --git a/docs/framework/svelte/reference/type-aliases/Target.md b/docs/framework/svelte/reference/type-aliases/Target.md deleted file mode 100644 index 13d0165..0000000 --- a/docs/framework/svelte/reference/type-aliases/Target.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -id: Target -title: Target ---- - -# Type Alias: Target - -```ts -type Target = - | ResolvedTarget - | () => ResolvedTarget; -``` - -Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:7](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L7) diff --git a/docs/framework/svelte/reference/variables/DEFAULT_OPTIONS.md b/docs/framework/svelte/reference/variables/DEFAULT_OPTIONS.md index a526377..0d1171c 100644 --- a/docs/framework/svelte/reference/variables/DEFAULT_OPTIONS.md +++ b/docs/framework/svelte/reference/variables/DEFAULT_OPTIONS.md @@ -9,4 +9,4 @@ title: DEFAULT_OPTIONS const DEFAULT_OPTIONS: HotkeysProviderOptions = {}; ``` -Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:22](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L22) +Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:18](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L18) diff --git a/docs/framework/svelte/reference/variables/getHotkeysContext.md b/docs/framework/svelte/reference/variables/getHotkeysContext.md deleted file mode 100644 index 55617d0..0000000 --- a/docs/framework/svelte/reference/variables/getHotkeysContext.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -id: getHotkeysContext -title: getHotkeysContext ---- - -# Variable: getHotkeysContext() - -```ts -getHotkeysContext: () => HotkeysContextValue | null; -``` - -Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:28](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L28) - -## Returns - -`HotkeysContextValue` \| `null` diff --git a/docs/framework/svelte/reference/variables/setHotkeysContext.md b/docs/framework/svelte/reference/variables/setHotkeysContext.md deleted file mode 100644 index e159996..0000000 --- a/docs/framework/svelte/reference/variables/setHotkeysContext.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: setHotkeysContext -title: setHotkeysContext ---- - -# Variable: setHotkeysContext() - -```ts -setHotkeysContext: (context) => HotkeysContextValue | null; -``` - -Defined in: [packages/svelte-hotkeys/src/HotkeysCtx.ts:28](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/HotkeysCtx.ts#L28) - -## Parameters - -### context - -`HotkeysContextValue` | `null` - -## Returns - -`HotkeysContextValue` \| `null` diff --git a/examples/svelte/create-hotkey-recorder/src/App.svelte b/examples/svelte/create-hotkey-recorder/src/App.svelte index bfce624..cd10d25 100644 --- a/examples/svelte/create-hotkey-recorder/src/App.svelte +++ b/examples/svelte/create-hotkey-recorder/src/App.svelte @@ -231,19 +231,28 @@

Usage

-
{`import { createHotkey, formatForDisplay } from '@tanstack/svelte-hotkeys'
+      
{`import {
+  createHotkey,
+  createHotkeyRecorder,
+  formatForDisplay,
+} from '@tanstack/svelte-hotkeys'
 
 let shortcuts = $state({
   save: 'Mod+K',
   open: 'Mod+E',
 })
 
+const recorder = createHotkeyRecorder({
+  onRecord: (hotkey) => {
+    shortcuts = { ...shortcuts, save: hotkey }
+  },
+})
+
 // Register shortcuts dynamically
 createHotkey(
-  shortcuts.save,
+  () => shortcuts.save,
   () => handleSave(),
-  { enabled: !isRecording }
+  () => ({ enabled: !recorder.isRecording })
 )
 
 // In template:
diff --git a/examples/svelte/create-hotkey-recorder/src/Root.svelte b/examples/svelte/create-hotkey-recorder/src/Root.svelte
index 2aa9ee8..12e94e9 100644
--- a/examples/svelte/create-hotkey-recorder/src/Root.svelte
+++ b/examples/svelte/create-hotkey-recorder/src/Root.svelte
@@ -1,8 +1,5 @@
 
 
-
-  
-
+
diff --git a/examples/svelte/create-hotkey-recorder/src/ShortcutListItem.svelte b/examples/svelte/create-hotkey-recorder/src/ShortcutListItem.svelte
index 611574a..c34268d 100644
--- a/examples/svelte/create-hotkey-recorder/src/ShortcutListItem.svelte
+++ b/examples/svelte/create-hotkey-recorder/src/ShortcutListItem.svelte
@@ -12,8 +12,7 @@
 
   let { actionName, hotkey, isRecording, onEdit, onCancel }: Props = $props()
 
-  const heldKeysRef = getHeldKeys()
-  const heldKeys = $derived(heldKeysRef.current)
+  const heldKeys = getHeldKeys()
 
 
 
@@ -22,9 +21,9 @@
{#if isRecording}
- {#if heldKeys.length > 0} + {#if heldKeys.keys.length > 0}
- {#each heldKeys as key, index} + {#each heldKeys.keys as key, index} {#if index > 0} + {/if} diff --git a/examples/svelte/create-hotkey-sequence/src/App.svelte b/examples/svelte/create-hotkey-sequence/src/App.svelte index a0ea73b..f28fcd1 100644 --- a/examples/svelte/create-hotkey-sequence/src/App.svelte +++ b/examples/svelte/create-hotkey-sequence/src/App.svelte @@ -164,24 +164,21 @@
{`import { createHotkeySequence } from '@tanstack/svelte-hotkeys'
 
-function VimEditor() {
-  // Basic sequence
+`}
{#if history.length > 0} diff --git a/examples/svelte/create-hotkey-sequence/src/Root.svelte b/examples/svelte/create-hotkey-sequence/src/Root.svelte index 2aa9ee8..12e94e9 100644 --- a/examples/svelte/create-hotkey-sequence/src/Root.svelte +++ b/examples/svelte/create-hotkey-sequence/src/Root.svelte @@ -1,8 +1,5 @@ - - - + diff --git a/examples/svelte/create-hotkey/src/App.svelte b/examples/svelte/create-hotkey/src/App.svelte index 934eedf..c31ccde 100644 --- a/examples/svelte/create-hotkey/src/App.svelte +++ b/examples/svelte/create-hotkey/src/App.svelte @@ -1,5 +1,9 @@
@@ -327,7 +309,7 @@ createHotkey( (event, { hotkey }) => { alert('Triggered!') }, - { enabled } + () => ({ enabled }) )`}
@@ -449,10 +431,11 @@ createHotkey('Mod+2', () => activeTab = 2)`}

Sidebar (Scoped Area)

Click here to focus, then try:

@@ -489,6 +472,8 @@ createHotkey('Mod+2', () => activeTab = 2)`} tabindex="0" onclick={(e) => e.stopPropagation()} role="dialog" + {@attach closeModalHotkey} + {@attach submitModalHotkey} >

Modal Dialog (Scoped)

Try these shortcuts while modal is open:

@@ -525,11 +510,13 @@ createHotkey('Mod+2', () => activeTab = 2)`}
{formatForDisplay('Mod+K')} — Clear editor
Editor shortcuts: {editorShortcutCount}x

@@ -543,33 +530,20 @@ createHotkey('Mod+2', () => activeTab = 2)`}

-
{`// Scoped to a ref
-let sidebarRef = $state(null)
-
-createHotkey(
+      
{`const sidebarHotkey = createHotkeyAttachment(
   'Mod+B',
   () => console.log('Sidebar shortcut!'),
-  { target: () => sidebarRef }
 )
 
-// Scoped to a modal (only when open)
-let modalRef = $state(null)
-let modalOpen = $state(false)
-
-createHotkey(
+const modalEscape = createHotkeyAttachment(
   'Escape',
-  () => modalOpen = false,
-  { target: () => modalRef, enabled: modalOpen }
+  () => (modalOpen = false),
+  () => ({ enabled: modalOpen }),
 )
 
-// Scoped to an editor
-let editorRef = $state(null)
-
-createHotkey(
-  'Mod+S',
-  () => saveEditorContent(),
-  { target: () => editorRef }
-)`}
+const editorSave = createHotkeyAttachment('Mod+S', () => { + saveEditorContent() +})`}
diff --git a/examples/svelte/create-hotkey/src/Root.svelte b/examples/svelte/create-hotkey/src/Root.svelte index 2aa9ee8..12e94e9 100644 --- a/examples/svelte/create-hotkey/src/Root.svelte +++ b/examples/svelte/create-hotkey/src/Root.svelte @@ -1,8 +1,5 @@ - - - + diff --git a/examples/svelte/get-held-keys/src/App.svelte b/examples/svelte/get-held-keys/src/App.svelte index 5834c0b..5e2901e 100644 --- a/examples/svelte/get-held-keys/src/App.svelte +++ b/examples/svelte/get-held-keys/src/App.svelte @@ -5,16 +5,14 @@ getHeldKeyCodesMap, } from '@tanstack/svelte-hotkeys' - const heldKeysRef = getHeldKeys() - const heldKeyCodesMapRef = getHeldKeyCodesMap() - const heldKeys = $derived(heldKeysRef.current) - const heldKeyCodesMap = $derived(heldKeyCodesMapRef.current) + const heldKeys = getHeldKeys() + const heldKeyCodesMap = getHeldKeyCodesMap() let history = $state>([]) $effect(() => { - if (heldKeys.length > 0) { - const combo = heldKeys + if (heldKeys.keys.length > 0) { + const combo = heldKeys.keys .map((k) => formatKeyForDebuggingDisplay(k)) .join(' + ') if (history[history.length - 1] !== combo) { @@ -37,9 +35,9 @@

Currently Held Keys

- {#if heldKeys.length > 0} - {#each heldKeys as key, index} - {@const code = heldKeyCodesMap[key]} + {#if heldKeys.keys.length > 0} + {#each heldKeys.keys as key, index} + {@const code = heldKeyCodesMap.codes[key]} {#if index > 0} + {/if} @@ -59,7 +57,7 @@ {/if}
- Keys held: {heldKeys.length} + Keys held: {heldKeys.keys.length}
@@ -68,11 +66,10 @@
{`import { getHeldKeys } from '@tanstack/svelte-hotkeys'
 
-const heldKeysRef = getHeldKeys()
-const heldKeys = $derived(heldKeysRef.current)
+const heldKeys = getHeldKeys()
 
 // In template:
-// Currently pressed: {heldKeys.join(' + ') || 'None'}`}
+// Currently pressed: {heldKeys.keys.join(' + ') || 'None'}`}
diff --git a/examples/svelte/get-held-keys/src/Root.svelte b/examples/svelte/get-held-keys/src/Root.svelte index 2aa9ee8..12e94e9 100644 --- a/examples/svelte/get-held-keys/src/Root.svelte +++ b/examples/svelte/get-held-keys/src/Root.svelte @@ -1,8 +1,5 @@ - - - + diff --git a/examples/svelte/get-is-key-held/src/App.svelte b/examples/svelte/get-is-key-held/src/App.svelte index 3c7ab6a..19a4c43 100644 --- a/examples/svelte/get-is-key-held/src/App.svelte +++ b/examples/svelte/get-is-key-held/src/App.svelte @@ -1,17 +1,11 @@
@@ -27,33 +21,33 @@

Modifier Key States

-
+
Shift - {isShiftHeld ? 'HELD' : 'Released'} + {isShiftHeld.held ? 'HELD' : 'Released'}
-
+
Control - {isControlHeld ? 'HELD' : 'Released'} + {isControlHeld.held ? 'HELD' : 'Released'}
-
+
Alt / Option - {isAltHeld ? 'HELD' : 'Released'} + {isAltHeld.held ? 'HELD' : 'Released'}
-
+
Meta (⌘ / ⊞) - {isMetaHeld ? 'HELD' : 'Released'} + {isMetaHeld.held ? 'HELD' : 'Released'}

Space Bar Demo

-
- {isSpaceHeld ? '🚀 SPACE HELD!' : 'Hold Space Bar'} +
+ {isSpaceHeld.held ? '🚀 SPACE HELD!' : 'Hold Space Bar'}
@@ -62,12 +56,11 @@
{`import { getIsKeyHeld } from '@tanstack/svelte-hotkeys'
 
-const isShiftHeldRef = getIsKeyHeld('Shift')
-const isShiftHeld = $derived(isShiftHeldRef.current)
+const isShiftHeld = getIsKeyHeld('Shift')
 
 // In template:
-// 
-// {isShiftHeld ? 'Shift is pressed!' : 'Press Shift'} +//
+// {isShiftHeld.held ? 'Shift is pressed!' : 'Press Shift'} //
`}
@@ -76,8 +69,8 @@ const isShiftHeld = $derived(isShiftHeldRef.current)

Hold Shift to reveal the secret message:

-
- {#if isShiftHeld} +
+ {#if isShiftHeld.held} 🎉 The secret password is: tanstack-hotkeys-rocks! {:else} •••••••••••••••••••••••••• diff --git a/examples/svelte/get-is-key-held/src/Root.svelte b/examples/svelte/get-is-key-held/src/Root.svelte index 2aa9ee8..12e94e9 100644 --- a/examples/svelte/get-is-key-held/src/Root.svelte +++ b/examples/svelte/get-is-key-held/src/Root.svelte @@ -1,8 +1,5 @@ - - - + diff --git a/packages/svelte-hotkeys/package.json b/packages/svelte-hotkeys/package.json index 799fb51..7a06d04 100644 --- a/packages/svelte-hotkeys/package.json +++ b/packages/svelte-hotkeys/package.json @@ -49,8 +49,7 @@ "src" ], "dependencies": { - "@tanstack/hotkeys": "workspace:*", - "@tanstack/svelte-store": "^0.10.2" + "@tanstack/hotkeys": "workspace:*" }, "devDependencies": { "@sveltejs/package": "^2.5.7", diff --git a/packages/svelte-hotkeys/src/HotkeysCtx.ts b/packages/svelte-hotkeys/src/HotkeysCtx.ts index 1951927..686e1c9 100644 --- a/packages/svelte-hotkeys/src/HotkeysCtx.ts +++ b/packages/svelte-hotkeys/src/HotkeysCtx.ts @@ -4,10 +4,6 @@ import type { CreateHotkeyOptions } from './createHotkey.svelte' import type { Snippet } from 'svelte' import type { CreateHotkeySequenceOptions } from './createHotkeySequence.svelte' -export type Target = ResolvedTarget | (() => ResolvedTarget) - -export type ResolvedTarget = HTMLElement | Document | Window | null - export interface HotkeysProviderOptions { hotkey?: Partial hotkeyRecorder?: Partial @@ -25,10 +21,36 @@ interface HotkeysContextValue { defaultOptions: HotkeysProviderOptions } -const [getHotkeysContext, setHotkeysContext] = +const [useHotkeysContext, setHotkeysContextValue] = createContext() -export { getHotkeysContext, setHotkeysContext } +export function setHotkeysContext( + defaultOptions: HotkeysProviderOptions = DEFAULT_OPTIONS, +): HotkeysContextValue { + return setHotkeysContextValue({ + get defaultOptions() { + return defaultOptions + }, + })! +} + +export function setHotkeysContextSource( + defaultOptions: () => HotkeysProviderOptions, +): HotkeysContextValue { + return setHotkeysContextValue({ + get defaultOptions() { + return defaultOptions() + }, + })! +} + +export function getHotkeysContext(): HotkeysContextValue | null { + try { + return useHotkeysContext() + } catch { + return null + } +} 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 index f861aa6..a431589 100644 --- a/packages/svelte-hotkeys/src/HotkeysProvider.svelte +++ b/packages/svelte-hotkeys/src/HotkeysProvider.svelte @@ -1,16 +1,12 @@ {@render children()} diff --git a/packages/svelte-hotkeys/src/createHotkey.svelte.ts b/packages/svelte-hotkeys/src/createHotkey.svelte.ts index 679ee85..18f993a 100644 --- a/packages/svelte-hotkeys/src/createHotkey.svelte.ts +++ b/packages/svelte-hotkeys/src/createHotkey.svelte.ts @@ -5,175 +5,114 @@ import { rawHotkeyToParsedHotkey, } from '@tanstack/hotkeys' import { getDefaultHotkeysOptions } from './HotkeysCtx' -import type { ResolvedTarget, Target } from './HotkeysCtx' +import { resolveMaybeGetter } from './internal.svelte' import type { Hotkey, HotkeyCallback, HotkeyOptions, - HotkeyRegistrationHandle, RegisterableHotkey, } from '@tanstack/hotkeys' +import type { MaybeGetter } from './internal.svelte' +import type { Attachment } from 'svelte/attachments' -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?: Target -} +export interface CreateHotkeyOptions extends Omit {} -function resolveTarget(target: Target): ResolvedTarget { - if (typeof target === 'function') { - return target() ?? null - } +function normalizeHotkey( + hotkey: RegisterableHotkey, + options: CreateHotkeyOptions, +): Hotkey { + const platform = options.platform ?? detectPlatform() + + return typeof hotkey === 'string' + ? hotkey + : (formatHotkey(rawHotkeyToParsedHotkey(hotkey, platform)) as Hotkey) +} - return target +function registerHotkey( + target: HTMLElement | Document | Window, + hotkey: MaybeGetter, + callback: HotkeyCallback, + options: MaybeGetter, +) { + const resolvedHotkey = resolveMaybeGetter(hotkey) + const resolvedOptions = resolveMaybeGetter(options) + const mergedOptions = { + ...getDefaultHotkeysOptions().hotkey, + ...resolvedOptions, + } as CreateHotkeyOptions + + return getHotkeyManager().register( + normalizeHotkey(resolvedHotkey, mergedOptions), + callback, + { + ...mergedOptions, + target, + }, + ) } /** - * 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. + * Register a global hotkey for the current component. * * @example * ```svelte - * * - * - *
- * .... - *
* ``` + */ +export function createHotkey( + hotkey: MaybeGetter, + callback: HotkeyCallback, + options: MaybeGetter = {}, +): void { + $effect(() => { + if (typeof document === 'undefined') { + return + } + + const registration = registerHotkey(document, hotkey, callback, options) + + return () => { + registration.unregister() + } + }) +} + +/** + * Create an attachment for element-scoped hotkeys. * * @example * ```svelte * * - *
+ *
* Count: {count} *
* ``` */ - -export function createHotkey( - hotkey: RegisterableHotkey | (() => RegisterableHotkey), +export function createHotkeyAttachment( + hotkey: MaybeGetter, callback: HotkeyCallback, - options: CreateHotkeyOptions | (() => CreateHotkeyOptions) = {}, -): void { - const manager = getHotkeyManager() - - // Stable ref for registration handle - let registrationRef: HotkeyRegistrationHandle | null = null - - // Refs to capture current values for use in effect without adding dependencies - let callbackRef = callback - let managerRef = manager - - $effect(() => { - callbackRef = callback - managerRef = manager - }) - - // Track previous target and hotkey to detect changes requiring re-registration - let prevTargetRef: ResolvedTarget = null - let prevHotkeyRef: string | null = null - - $effect(() => { - // Resolve reactive values (support getters for dynamic shortcuts) - const resolvedHotkey = typeof hotkey === 'function' ? hotkey() : hotkey - const resolvedOptions = typeof options === 'function' ? options() : options - - const mergedOptions = { - ...getDefaultHotkeysOptions().hotkey, - ...resolvedOptions, - } as CreateHotkeyOptions - - // Normalize to hotkey string - const platform = mergedOptions.platform ?? detectPlatform() - const hotkeyString: Hotkey = - typeof resolvedHotkey === 'string' - ? resolvedHotkey - : (formatHotkey( - rawHotkeyToParsedHotkey(resolvedHotkey, platform), - ) as Hotkey) - - // Resolve target inside the effect so refs are already attached after mount - const resolvedTarget = mergedOptions.target - ? resolveTarget(mergedOptions.target) - : typeof document !== 'undefined' - ? document - : null + options: MaybeGetter = {}, +): Attachment { + return (element) => { + const registration = registerHotkey(element, hotkey, callback, options) - // Skip if no valid target (SSR or ref still null) - if (!resolvedTarget) { - return - } - - // Extract options without target (target is handled separately) - const { target: _target, ...optionsWithoutTarget } = mergedOptions - - // 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) - if (!registrationRef || !registrationRef.isActive) { - registrationRef = managerRef.register(hotkeyString, callbackRef, { - ...optionsWithoutTarget, - target: resolvedTarget, - }) - } - - // Update callback and options - if (registrationRef.isActive) { - registrationRef.callback = callbackRef - registrationRef.setOptions(optionsWithoutTarget) - } - - // Update tracking refs - prevTargetRef = resolvedTarget - prevHotkeyRef = hotkeyString - - // Cleanup on unmount return () => { - if (registrationRef?.isActive) { - registrationRef.unregister() - registrationRef = null - } + registration.unregister() } - }) + } } diff --git a/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts b/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts index a1aafb9..f490a3e 100644 --- a/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts +++ b/packages/svelte-hotkeys/src/createHotkeyRecorder.svelte.ts @@ -1,14 +1,15 @@ import { HotkeyRecorder } from '@tanstack/hotkeys' -import { useStore } from '@tanstack/svelte-store' import { onDestroy } from 'svelte' import { getDefaultHotkeysOptions } from './HotkeysCtx' +import { createStoreSubscriber, resolveMaybeGetter } from './internal.svelte' import type { Hotkey, HotkeyRecorderOptions } from '@tanstack/hotkeys' +import type { MaybeGetter } from './internal.svelte' export interface SvelteHotkeyRecorder { /** Whether recording is currently active */ - isRecording: boolean + readonly isRecording: boolean /** The currently recorded hotkey (for live preview) */ - recordedHotkey: Hotkey | null + readonly recordedHotkey: Hotkey | null /** Start recording a new hotkey */ startRecording: () => void /** Stop recording (same as cancel) */ @@ -44,51 +45,74 @@ export interface SvelteHotkeyRecorder { * * *
- * - * {recorder.recordedHotkey && ( + * {#if recorder.recordedHotkey} *
Recording: {recorder.recordedHotkey}
- * )} + * {/if} *
* ``` */ +class SvelteHotkeyRecorderState implements SvelteHotkeyRecorder { + #recorder: HotkeyRecorder + #subscribe: () => void + + constructor(options: HotkeyRecorderOptions) { + this.#recorder = new HotkeyRecorder(options) + this.#subscribe = createStoreSubscriber(this.#recorder.store) + } + + get isRecording(): boolean { + this.#subscribe() + return this.#recorder.store.state.isRecording + } + + get recordedHotkey(): Hotkey | null { + this.#subscribe() + return this.#recorder.store.state.recordedHotkey + } + + setOptions(options: HotkeyRecorderOptions): void { + this.#recorder.setOptions(options) + } + + startRecording(): void { + this.#recorder.start() + } + + stopRecording(): void { + this.#recorder.stop() + } + + cancelRecording(): void { + this.#recorder.cancel() + } + + destroy(): void { + this.#recorder.destroy() + } +} + export function createHotkeyRecorder( - options: HotkeyRecorderOptions, + options: MaybeGetter, ): SvelteHotkeyRecorder { - const mergedOptions = { + const recorder = new SvelteHotkeyRecorderState({ ...getDefaultHotkeysOptions().hotkeyRecorder, - ...options, - } as HotkeyRecorderOptions - - const recorder = new HotkeyRecorder(mergedOptions) + ...resolveMaybeGetter(options), + } as HotkeyRecorderOptions) - // Sync options on every render (same pattern as createHotkey) - // This ensures callbacks always have access to latest values $effect(() => { - recorder.setOptions(mergedOptions) + recorder.setOptions({ + ...getDefaultHotkeysOptions().hotkeyRecorder, + ...resolveMaybeGetter(options), + } as HotkeyRecorderOptions) }) - const isRecordingRef = useStore(recorder.store, (state) => state.isRecording) - const recordedHotkeyRef = useStore( - recorder.store, - (state) => state.recordedHotkey, - ) - onDestroy(() => { recorder.destroy() }) - return { - get isRecording() { - return isRecordingRef.current - }, - get recordedHotkey() { - return recordedHotkeyRef.current - }, - startRecording: () => recorder.start(), - stopRecording: () => recorder.stop(), - cancelRecording: () => recorder.cancel(), - } + return recorder } diff --git a/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts b/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts index 31cb059..873979a 100644 --- a/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts +++ b/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts @@ -1,42 +1,40 @@ -import { formatHotkeySequence, getSequenceManager } from '@tanstack/hotkeys' +import { getSequenceManager } from '@tanstack/hotkeys' import { getDefaultHotkeysOptions } from './HotkeysCtx' +import { resolveMaybeGetter } from './internal.svelte' import type { HotkeyCallback, - HotkeyCallbackContext, HotkeySequence, SequenceOptions, - SequenceRegistrationHandle, } from '@tanstack/hotkeys' -import type { ResolvedTarget, Target } from './HotkeysCtx' +import type { MaybeGetter } from './internal.svelte' +import type { Attachment } from 'svelte/attachments' 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?: Target -} +> {} -function resolveTarget(target: Target): ResolvedTarget { - if (typeof target === 'function') { - return target() ?? null - } - return target +function registerHotkeySequence( + target: HTMLElement | Document | Window, + sequence: MaybeGetter, + callback: HotkeyCallback, + options: MaybeGetter, +) { + const resolvedSequence = resolveMaybeGetter(sequence) + const resolvedOptions = resolveMaybeGetter(options) + const mergedOptions = { + ...getDefaultHotkeysOptions().hotkeySequence, + ...resolvedOptions, + } as CreateHotkeySequenceOptions + + return getSequenceManager().register(resolvedSequence, callback, { + ...mergedOptions, + target, + }) } /** - * 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 + * Register a global keyboard shortcut sequence for the current component. * * @example * ```svelte @@ -65,97 +63,72 @@ function resolveTarget(target: Target): ResolvedTarget { * ``` */ export function createHotkeySequence( - sequence: HotkeySequence, + sequence: MaybeGetter, callback: HotkeyCallback, - options: CreateHotkeySequenceOptions = {}, + options: MaybeGetter = {}, ): void { - const mergedOptions = { - ...getDefaultHotkeysOptions().hotkeySequence, - ...options, - } as CreateHotkeySequenceOptions - - const manager = getSequenceManager() - - // Stable ref for registration handle - let registrationRef: SequenceRegistrationHandle | null = null - - // Refs to capture current values for use in effect without adding dependencies - let callbackRef = callback - let optionsRef = mergedOptions - - $effect(() => { - callbackRef = callback - optionsRef = mergedOptions - }) - - // Track previous target and sequence to detect changes requiring re-registration - let prevTargetRef: ResolvedTarget = null - let prevSequenceRef: string | null = null - - // Normalize to hotkey sequence string (join with spaces) - const hotkeySequenceString = $derived.by(() => formatHotkeySequence(sequence)) - - // Extract options without target (target is handled separately) - const { target: _target, ...optionsWithoutTarget } = $derived(mergedOptions) - $effect(() => { - if (sequence.length === 0) { + if (typeof document === 'undefined') { return } - // Resolve target inside the effect so refs are already attached after mount - const resolvedTarget = optionsRef.target - ? resolveTarget(optionsRef.target) - : typeof document !== 'undefined' - ? document - : null - - // Skip if no valid target (SSR or ref still null) - if (!resolvedTarget) { + const resolvedSequence = resolveMaybeGetter(sequence) + if (resolvedSequence.length === 0) { return } - // Check if we need to re-register (target or sequence changed) - const targetChanged = - prevTargetRef !== null && prevTargetRef !== resolvedTarget - const sequenceChanged = - prevSequenceRef !== null && prevSequenceRef !== hotkeySequenceString + const registration = registerHotkeySequence( + document, + resolvedSequence, + callback, + options, + ) - // If we have an active registration and target/sequence changed, unregister first - if (registrationRef?.isActive && (targetChanged || sequenceChanged)) { - registrationRef.unregister() - registrationRef = null + return () => { + registration.unregister() } + }) +} + +/** + * Create an attachment for element-scoped keyboard sequences. + * + * @example + * ```svelte + * + * + *
+ * Focus here and press g then g + *
+ * ``` + */ +export function createHotkeySequenceAttachment( + sequence: MaybeGetter, + callback: HotkeyCallback, + options: MaybeGetter = {}, +): Attachment { + return (element) => { + const resolvedSequence = resolveMaybeGetter(sequence) - // Register if needed (no active registration) - if (!registrationRef || !registrationRef.isActive) { - registrationRef = manager.register(sequence, callback, { - ...optionsRef, - target: resolvedTarget, - }) + if (resolvedSequence.length === 0) { + return } - // Update tracking refs - prevTargetRef = resolvedTarget - prevSequenceRef = hotkeySequenceString + const registration = registerHotkeySequence( + element, + resolvedSequence, + callback, + options, + ) - // Cleanup on unmount return () => { - if (registrationRef?.isActive) { - registrationRef.unregister() - registrationRef = null - } + registration.unregister() } - }) - - // 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 index 6230871..1d59981 100644 --- a/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts +++ b/packages/svelte-hotkeys/src/getHeldKeyCodesMap.svelte.ts @@ -1,35 +1,44 @@ import { getKeyStateTracker } from '@tanstack/hotkeys' -import { useStore } from '@tanstack/svelte-store' +import { createStoreSubscriber } from './internal.svelte' + +export interface SvelteHeldKeyCodesMap { + readonly codes: Record +} + +class HeldKeyCodesMapState implements SvelteHeldKeyCodesMap { + #tracker = getKeyStateTracker() + #subscribe = createStoreSubscriber(this.#tracker.store) + + get codes(): Record { + this.#subscribe() + return this.#tracker.store.state.heldCodes + } +} /** - * Svelte function that returns a reactive reference to the map of currently held key names + * Svelte function that returns reactive access to the 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"). - * Use `$derived(getHeldKeyCodesMap().current)` for reactive access in templates. * - * @returns Object with `current` property containing the held key codes map + * @returns Object with a reactive `codes` property * * ```svelte * * *
- * {Object.entries(heldKeyCodesMap).map(([key, code]) => ( - * - * {key} {code} - * - * ))} + * {#each Object.entries(heldKeyCodesMap.codes) as [key, code]} + * + * {key} {code} + * + * {/each} *
* ``` */ -export function getHeldKeyCodesMap(): { - readonly current: Record -} { - const tracker = getKeyStateTracker() - return useStore(tracker.store, (state) => state.heldCodes) +export function getHeldKeyCodesMap(): SvelteHeldKeyCodesMap { + return new HeldKeyCodesMapState() } diff --git a/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts b/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts index c91d65a..eddc222 100644 --- a/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts +++ b/packages/svelte-hotkeys/src/getHeldKeys.svelte.ts @@ -1,28 +1,40 @@ import { getKeyStateTracker } from '@tanstack/hotkeys' -import { useStore } from '@tanstack/svelte-store' +import { createStoreSubscriber } from './internal.svelte' + +export interface SvelteHeldKeys { + readonly keys: Array +} + +class HeldKeysState implements SvelteHeldKeys { + #tracker = getKeyStateTracker() + #subscribe = createStoreSubscriber(this.#tracker.store) + + get keys(): Array { + this.#subscribe() + return this.#tracker.store.state.heldKeys + } +} /** - * Svelte function that returns a reactive reference to currently held keyboard keys. + * Svelte function that returns reactive access to currently held keyboard keys. * * This function uses the global KeyStateTracker and updates whenever keys are pressed - * or released. Use `$derived(getHeldKeys().current)` for reactive access in templates. + * or released. * - * @returns Object with `current` property containing the array of held key names + * @returns Object with a reactive `keys` property * * @example * ```svelte * *
- * Currently pressed: {heldKeys.join(' + ') || 'None'} + * Currently pressed: {heldKeys.keys.join(' + ') || 'None'} *
* ``` */ -export function getHeldKeys(): { readonly current: Array } { - const tracker = getKeyStateTracker() - return useStore(tracker.store, (state) => state.heldKeys) +export function getHeldKeys(): SvelteHeldKeys { + return new HeldKeysState() } diff --git a/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts b/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts index ec9a89a..797681c 100644 --- a/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts +++ b/packages/svelte-hotkeys/src/getIsKeyHeld.svelte.ts @@ -1,27 +1,47 @@ import { getKeyStateTracker } from '@tanstack/hotkeys' -import { useStore } from '@tanstack/svelte-store' +import { createStoreSubscriber } from './internal.svelte' import type { HeldKey } from '@tanstack/hotkeys' +export interface SvelteHeldKeyState { + readonly held: boolean +} + +class HeldKeyState implements SvelteHeldKeyState { + #tracker = getKeyStateTracker() + #normalizedKey: string + #subscribe = createStoreSubscriber(this.#tracker.store) + + constructor(key: HeldKey) { + this.#normalizedKey = key.toLowerCase() + } + + get held(): boolean { + this.#subscribe() + return this.#tracker.store.state.heldKeys.some( + (heldKey) => heldKey.toLowerCase() === this.#normalizedKey, + ) + } +} + /** - * Svelte function that returns a reactive reference to whether a specific key is currently being held. + * Svelte function that returns reactive access to whether a specific key is currently being held. * * This function uses the global KeyStateTracker and updates whenever keys are pressed - * or released. Use `$derived(getIsKeyHeld('Shift').current)` for reactive access in templates. + * or released. * * @param key - The key to check (e.g., 'Shift', 'Control', 'A') - * @returns Object with `current` property - true if the key is currently held down + * @returns Object with a reactive `held` property * * @example * ```svelte * * *
- * {isShiftHeld ? 'Shift is pressed!' : 'Press Shift'} + * {isShiftHeld.held ? 'Shift is pressed!' : 'Press Shift'} *
* ``` * @@ -30,24 +50,19 @@ import type { HeldKey } from '@tanstack/hotkeys' * * *
- * Ctrl - * Shift - * Alt + * Ctrl + * Shift + * Alt *
* ``` */ -export function getIsKeyHeld(key: HeldKey): { readonly current: boolean } { - const tracker = getKeyStateTracker() - const normalizedKey = key.toLowerCase() - - return useStore(tracker.store, (state) => - state.heldKeys.some((heldKey) => heldKey.toLowerCase() === normalizedKey), - ) +export function getIsKeyHeld(key: HeldKey): SvelteHeldKeyState { + return new HeldKeyState(key) } diff --git a/packages/svelte-hotkeys/src/index.ts b/packages/svelte-hotkeys/src/index.ts index 0919778..0d62f94 100644 --- a/packages/svelte-hotkeys/src/index.ts +++ b/packages/svelte-hotkeys/src/index.ts @@ -8,4 +8,10 @@ export * from './getHeldKeys.svelte' export * from './getHeldKeyCodesMap.svelte' export * from './getIsKeyHeld.svelte' export { default as HotkeysProvider } from './HotkeysProvider.svelte' -export * from './HotkeysCtx' +export { + DEFAULT_OPTIONS, + getDefaultHotkeysOptions, + getHotkeysContext, + setHotkeysContext, +} from './HotkeysCtx' +export type { HotkeysProviderOptions, HotkeysProviderProps } from './HotkeysCtx' diff --git a/packages/svelte-hotkeys/src/internal.svelte.ts b/packages/svelte-hotkeys/src/internal.svelte.ts new file mode 100644 index 0000000..058b925 --- /dev/null +++ b/packages/svelte-hotkeys/src/internal.svelte.ts @@ -0,0 +1,26 @@ +import { createSubscriber } from 'svelte/reactivity' + +export type MaybeGetter = T | (() => T) + +interface SubscribableStore { + state: TState + subscribe: ( + listener: () => void, + ) => (() => void) | { unsubscribe: () => void } +} + +export function resolveMaybeGetter(value: MaybeGetter): T { + return typeof value === 'function' ? (value as () => T)() : value +} + +export function createStoreSubscriber( + store: SubscribableStore, +): () => void { + return createSubscriber((update) => { + const subscription = store.subscribe(update) + + return typeof subscription === 'function' + ? subscription + : () => subscription.unsubscribe() + }) +} diff --git a/packages/svelte-hotkeys/tests/createHotkey.test.ts b/packages/svelte-hotkeys/tests/createHotkey.test.ts new file mode 100644 index 0000000..f35fd2f --- /dev/null +++ b/packages/svelte-hotkeys/tests/createHotkey.test.ts @@ -0,0 +1,9 @@ +import { describe, expect, it } from 'vitest' +import { getDefaultHotkeysOptions, getHotkeysContext } from '../src/HotkeysCtx' + +describe('HotkeysCtx', () => { + it('falls back cleanly when no parent context exists', () => { + expect(getHotkeysContext()).toBeNull() + expect(getDefaultHotkeysOptions()).toEqual({}) + }) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0fbc720..0552ebc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -860,7 +860,7 @@ importers: examples/svelte/create-hotkey: dependencies: '@tanstack/svelte-hotkeys': - specifier: workspace:* + specifier: 0.0.1 version: link:../../../packages/svelte-hotkeys svelte: specifier: ^5.53.7 @@ -879,7 +879,7 @@ importers: examples/svelte/create-hotkey-recorder: dependencies: '@tanstack/svelte-hotkeys': - specifier: workspace:* + specifier: 0.0.1 version: link:../../../packages/svelte-hotkeys svelte: specifier: ^5.53.7 @@ -898,7 +898,7 @@ importers: examples/svelte/create-hotkey-sequence: dependencies: '@tanstack/svelte-hotkeys': - specifier: workspace:* + specifier: 0.0.1 version: link:../../../packages/svelte-hotkeys svelte: specifier: ^5.53.7 @@ -917,7 +917,7 @@ importers: examples/svelte/get-held-keys: dependencies: '@tanstack/svelte-hotkeys': - specifier: workspace:* + specifier: 0.0.1 version: link:../../../packages/svelte-hotkeys svelte: specifier: ^5.53.7 @@ -936,7 +936,7 @@ importers: examples/svelte/get-is-key-held: dependencies: '@tanstack/svelte-hotkeys': - specifier: workspace:* + specifier: 0.0.1 version: link:../../../packages/svelte-hotkeys svelte: specifier: ^5.53.7 @@ -1276,9 +1276,6 @@ importers: '@tanstack/hotkeys': specifier: workspace:* version: link:../hotkeys - '@tanstack/svelte-store': - specifier: ^0.10.2 - version: 0.10.2(svelte@5.53.7) devDependencies: '@sveltejs/package': specifier: ^2.5.7 @@ -4004,11 +4001,6 @@ packages: '@tanstack/store@0.9.2': resolution: {integrity: sha512-K013lUJEFJK2ofFQ/hZKJUmCnpcV00ebLyOyFOWQvyQHUOZp/iYO84BM6aOGiV81JzwbX0APTVmW8YI7yiG5oA==} - '@tanstack/svelte-store@0.10.2': - resolution: {integrity: sha512-6yrYg6ukZZeqPf3CY5+3H0DI6bUoLNwmImgqLNchavK89LtHL4Sel1fDyLtXymMssK4kYrJOQQ9+BBZWZhhTDw==} - peerDependencies: - svelte: ^5.0.0 - '@tanstack/typedoc-config@0.3.3': resolution: {integrity: sha512-wVT2YfKDSpd+4f7fk6UaPIP3a2J7LSovlyVuFF1PH2yQb7gjqehod5zdFiwFyEXgvI9XGuFvvs1OehkKNYcr6A==} engines: {node: '>=18'} @@ -11561,11 +11553,6 @@ snapshots: '@tanstack/store@0.9.2': {} - '@tanstack/svelte-store@0.10.2(svelte@5.53.7)': - dependencies: - '@tanstack/store': 0.9.2 - svelte: 5.53.7 - '@tanstack/typedoc-config@0.3.3(typescript@5.9.3)': dependencies: typedoc: 0.28.14(typescript@5.9.3) From 0a637f3186c7487155ce4e89b5a22355241917bd Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Tue, 10 Mar 2026 11:20:29 -0500 Subject: [PATCH 14/16] update docs and fix type error --- docs/config.json | 44 ++++++++++++++++++- .../reference/functions/createHotkey.md | 2 +- .../functions/createHotkeyAttachment.md | 2 +- .../functions/createHotkeySequence.md | 2 +- .../createHotkeySequenceAttachment.md | 2 +- .../interfaces/CreateHotkeyOptions.md | 10 +++++ .../interfaces/CreateHotkeySequenceOptions.md | 10 +++++ .../create-hotkey-recorder/src/App.svelte | 4 +- .../svelte-hotkeys/src/createHotkey.svelte.ts | 4 +- .../src/createHotkeySequence.svelte.ts | 4 +- 10 files changed, 74 insertions(+), 10 deletions(-) diff --git a/docs/config.json b/docs/config.json index be973df..bf75e84 100644 --- a/docs/config.json +++ b/docs/config.json @@ -427,13 +427,41 @@ "label": "createHotkey", "to": "framework/svelte/reference/functions/createHotkey" }, + { + "label": "createHotkeyAttachment", + "to": "framework/svelte/reference/functions/createHotkeyAttachment" + }, { "label": "CreateHotkeyOptions", "to": "framework/svelte/reference/interfaces/CreateHotkeyOptions" }, + { + "label": "HotkeysProvider", + "to": "framework/svelte/reference/variables/HotkeysProvider" + }, { "label": "HotkeysProviderOptions", "to": "framework/svelte/reference/interfaces/HotkeysProviderOptions" + }, + { + "label": "HotkeysProviderProps", + "to": "framework/svelte/reference/interfaces/HotkeysProviderProps" + }, + { + "label": "DEFAULT_OPTIONS", + "to": "framework/svelte/reference/variables/DEFAULT_OPTIONS" + }, + { + "label": "getDefaultHotkeysOptions", + "to": "framework/svelte/reference/functions/getDefaultHotkeysOptions" + }, + { + "label": "getHotkeysContext", + "to": "framework/svelte/reference/functions/getHotkeysContext" + }, + { + "label": "setHotkeysContext", + "to": "framework/svelte/reference/functions/setHotkeysContext" } ] } @@ -538,6 +566,10 @@ "label": "createHotkeySequence", "to": "framework/svelte/reference/functions/createHotkeySequence" }, + { + "label": "createHotkeySequenceAttachment", + "to": "framework/svelte/reference/functions/createHotkeySequenceAttachment" + }, { "label": "CreateHotkeySequenceOptions", "to": "framework/svelte/reference/interfaces/CreateHotkeySequenceOptions" @@ -620,6 +652,10 @@ { "label": "getIsKeyHeld", "to": "framework/svelte/reference/functions/getIsKeyHeld" + }, + { + "label": "SvelteHeldKeyState", + "to": "framework/svelte/reference/interfaces/SvelteHeldKeyState" } ] } @@ -720,13 +756,17 @@ "label": "getHeldKeys", "to": "framework/svelte/reference/functions/getHeldKeys" }, + { + "label": "SvelteHeldKeys", + "to": "framework/svelte/reference/interfaces/SvelteHeldKeys" + }, { "label": "getHeldKeyCodesMap", "to": "framework/svelte/reference/functions/getHeldKeyCodesMap" }, { - "label": "getIsKeyHeld", - "to": "framework/svelte/reference/functions/getIsKeyHeld" + "label": "SvelteHeldKeyCodesMap", + "to": "framework/svelte/reference/interfaces/SvelteHeldKeyCodesMap" } ] } diff --git a/docs/framework/svelte/reference/functions/createHotkey.md b/docs/framework/svelte/reference/functions/createHotkey.md index 0ff3f2d..323583c 100644 --- a/docs/framework/svelte/reference/functions/createHotkey.md +++ b/docs/framework/svelte/reference/functions/createHotkey.md @@ -12,7 +12,7 @@ function createHotkey( options): void; ``` -Defined in: [packages/svelte-hotkeys/src/createHotkey.svelte.ts:68](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkey.svelte.ts#L68) +Defined in: [packages/svelte-hotkeys/src/createHotkey.svelte.ts:70](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkey.svelte.ts#L70) Register a global hotkey for the current component. diff --git a/docs/framework/svelte/reference/functions/createHotkeyAttachment.md b/docs/framework/svelte/reference/functions/createHotkeyAttachment.md index 70800a5..2b27ca3 100644 --- a/docs/framework/svelte/reference/functions/createHotkeyAttachment.md +++ b/docs/framework/svelte/reference/functions/createHotkeyAttachment.md @@ -12,7 +12,7 @@ function createHotkeyAttachment( options): Attachment; ``` -Defined in: [packages/svelte-hotkeys/src/createHotkey.svelte.ts:106](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkey.svelte.ts#L106) +Defined in: [packages/svelte-hotkeys/src/createHotkey.svelte.ts:108](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkey.svelte.ts#L108) Create an attachment for element-scoped hotkeys. diff --git a/docs/framework/svelte/reference/functions/createHotkeySequence.md b/docs/framework/svelte/reference/functions/createHotkeySequence.md index ddf71ce..0fb3c55 100644 --- a/docs/framework/svelte/reference/functions/createHotkeySequence.md +++ b/docs/framework/svelte/reference/functions/createHotkeySequence.md @@ -12,7 +12,7 @@ function createHotkeySequence( options): void; ``` -Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:65](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L65) +Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:67](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L67) Register a global keyboard shortcut sequence for the current component. diff --git a/docs/framework/svelte/reference/functions/createHotkeySequenceAttachment.md b/docs/framework/svelte/reference/functions/createHotkeySequenceAttachment.md index 9db6292..69a28c8 100644 --- a/docs/framework/svelte/reference/functions/createHotkeySequenceAttachment.md +++ b/docs/framework/svelte/reference/functions/createHotkeySequenceAttachment.md @@ -12,7 +12,7 @@ function createHotkeySequenceAttachment( options): Attachment; ``` -Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:111](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L111) +Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:113](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L113) Create an attachment for element-scoped keyboard sequences. diff --git a/docs/framework/svelte/reference/interfaces/CreateHotkeyOptions.md b/docs/framework/svelte/reference/interfaces/CreateHotkeyOptions.md index ef23d53..142abc9 100644 --- a/docs/framework/svelte/reference/interfaces/CreateHotkeyOptions.md +++ b/docs/framework/svelte/reference/interfaces/CreateHotkeyOptions.md @@ -10,3 +10,13 @@ Defined in: [packages/svelte-hotkeys/src/createHotkey.svelte.ts:18](https://gith ## Extends - `Omit`\<`HotkeyOptions`, `"target"`\> + +## Properties + +### target? + +```ts +optional target: Document | Window; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkey.svelte.ts:19](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkey.svelte.ts#L19) diff --git a/docs/framework/svelte/reference/interfaces/CreateHotkeySequenceOptions.md b/docs/framework/svelte/reference/interfaces/CreateHotkeySequenceOptions.md index b8dfd60..e25631a 100644 --- a/docs/framework/svelte/reference/interfaces/CreateHotkeySequenceOptions.md +++ b/docs/framework/svelte/reference/interfaces/CreateHotkeySequenceOptions.md @@ -10,3 +10,13 @@ Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:12](http ## Extends - `Omit`\<`SequenceOptions`, `"target"`\> + +## Properties + +### target? + +```ts +optional target: Document | Window; +``` + +Defined in: [packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts:16](https://github.com/TanStack/hotkeys/blob/main/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts#L16) diff --git a/examples/svelte/create-hotkey-recorder/src/App.svelte b/examples/svelte/create-hotkey-recorder/src/App.svelte index cd10d25..8c0d819 100644 --- a/examples/svelte/create-hotkey-recorder/src/App.svelte +++ b/examples/svelte/create-hotkey-recorder/src/App.svelte @@ -14,7 +14,7 @@ } } - const DEFAULT_SHORTCUT_ACTIONS: ShortcutActions = { + const DEFAULT_SHORTCUT_ACTIONS = { save: { name: 'Save', defaultHotkey: 'Mod+K', @@ -39,7 +39,7 @@ name: 'Redo', defaultHotkey: 'Mod+Shift+G', }, - } + } as const satisfies ShortcutActions let shortcuts = $state>( Object.fromEntries( diff --git a/packages/svelte-hotkeys/src/createHotkey.svelte.ts b/packages/svelte-hotkeys/src/createHotkey.svelte.ts index 18f993a..be32cad 100644 --- a/packages/svelte-hotkeys/src/createHotkey.svelte.ts +++ b/packages/svelte-hotkeys/src/createHotkey.svelte.ts @@ -15,7 +15,9 @@ import type { import type { MaybeGetter } from './internal.svelte' import type { Attachment } from 'svelte/attachments' -export interface CreateHotkeyOptions extends Omit {} +export interface CreateHotkeyOptions extends Omit { + target?: Document | Window // not html elements, use attachment instead +} function normalizeHotkey( hotkey: RegisterableHotkey, diff --git a/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts b/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts index 873979a..2fc8dbe 100644 --- a/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts +++ b/packages/svelte-hotkeys/src/createHotkeySequence.svelte.ts @@ -12,7 +12,9 @@ import type { Attachment } from 'svelte/attachments' export interface CreateHotkeySequenceOptions extends Omit< SequenceOptions, 'target' -> {} +> { + target?: Document | Window // not html elements, use attachment instead +} function registerHotkeySequence( target: HTMLElement | Document | Window, From df2b4ba093e5b15453288fffe8c777fa1b811322 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Tue, 10 Mar 2026 14:50:39 -0500 Subject: [PATCH 15/16] update svelte hotkeys version --- packages/svelte-hotkeys/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte-hotkeys/package.json b/packages/svelte-hotkeys/package.json index 7a06d04..1e130fc 100644 --- a/packages/svelte-hotkeys/package.json +++ b/packages/svelte-hotkeys/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/svelte-hotkeys", - "version": "0.0.1", + "version": "0.4.1", "description": "Svelte adapter for TanStack Hotkeys", "author": "Kunal Rao", "license": "MIT", From 402f322502f901b4d57cbfa904218ce508ead6d5 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Tue, 10 Mar 2026 14:53:22 -0500 Subject: [PATCH 16/16] update example versions --- examples/svelte/create-hotkey-recorder/package.json | 2 +- examples/svelte/create-hotkey-sequence/package.json | 2 +- examples/svelte/create-hotkey/package.json | 2 +- examples/svelte/get-held-keys/package.json | 2 +- examples/svelte/get-is-key-held/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/svelte/create-hotkey-recorder/package.json b/examples/svelte/create-hotkey-recorder/package.json index 46f0195..ce7942a 100644 --- a/examples/svelte/create-hotkey-recorder/package.json +++ b/examples/svelte/create-hotkey-recorder/package.json @@ -6,7 +6,7 @@ "dev": "vite --port=3069" }, "dependencies": { - "@tanstack/svelte-hotkeys": "0.0.1", + "@tanstack/svelte-hotkeys": "0.4.1", "svelte": "^5.53.7" }, "devDependencies": { diff --git a/examples/svelte/create-hotkey-sequence/package.json b/examples/svelte/create-hotkey-sequence/package.json index 64560f0..436c6bb 100644 --- a/examples/svelte/create-hotkey-sequence/package.json +++ b/examples/svelte/create-hotkey-sequence/package.json @@ -6,7 +6,7 @@ "dev": "vite --port=3069" }, "dependencies": { - "@tanstack/svelte-hotkeys": "0.0.1", + "@tanstack/svelte-hotkeys": "0.4.1", "svelte": "^5.53.7" }, "devDependencies": { diff --git a/examples/svelte/create-hotkey/package.json b/examples/svelte/create-hotkey/package.json index 7f2558b..9f52691 100644 --- a/examples/svelte/create-hotkey/package.json +++ b/examples/svelte/create-hotkey/package.json @@ -11,7 +11,7 @@ "test:types": "tsc" }, "dependencies": { - "@tanstack/svelte-hotkeys": "0.0.1", + "@tanstack/svelte-hotkeys": "0.4.1", "svelte": "^5.53.7" }, "devDependencies": { diff --git a/examples/svelte/get-held-keys/package.json b/examples/svelte/get-held-keys/package.json index 16b8f5e..e6c1329 100644 --- a/examples/svelte/get-held-keys/package.json +++ b/examples/svelte/get-held-keys/package.json @@ -6,7 +6,7 @@ "dev": "vite --port=3069" }, "dependencies": { - "@tanstack/svelte-hotkeys": "0.0.1", + "@tanstack/svelte-hotkeys": "0.4.1", "svelte": "^5.53.7" }, "devDependencies": { diff --git a/examples/svelte/get-is-key-held/package.json b/examples/svelte/get-is-key-held/package.json index c39e426..e13b44e 100644 --- a/examples/svelte/get-is-key-held/package.json +++ b/examples/svelte/get-is-key-held/package.json @@ -6,7 +6,7 @@ "dev": "vite --port=3069" }, "dependencies": { - "@tanstack/svelte-hotkeys": "0.0.1", + "@tanstack/svelte-hotkeys": "0.4.1", "svelte": "^5.53.7" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0552ebc..48e96f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -860,7 +860,7 @@ importers: examples/svelte/create-hotkey: dependencies: '@tanstack/svelte-hotkeys': - specifier: 0.0.1 + specifier: 0.4.1 version: link:../../../packages/svelte-hotkeys svelte: specifier: ^5.53.7 @@ -879,7 +879,7 @@ importers: examples/svelte/create-hotkey-recorder: dependencies: '@tanstack/svelte-hotkeys': - specifier: 0.0.1 + specifier: 0.4.1 version: link:../../../packages/svelte-hotkeys svelte: specifier: ^5.53.7 @@ -898,7 +898,7 @@ importers: examples/svelte/create-hotkey-sequence: dependencies: '@tanstack/svelte-hotkeys': - specifier: 0.0.1 + specifier: 0.4.1 version: link:../../../packages/svelte-hotkeys svelte: specifier: ^5.53.7 @@ -917,7 +917,7 @@ importers: examples/svelte/get-held-keys: dependencies: '@tanstack/svelte-hotkeys': - specifier: 0.0.1 + specifier: 0.4.1 version: link:../../../packages/svelte-hotkeys svelte: specifier: ^5.53.7 @@ -936,7 +936,7 @@ importers: examples/svelte/get-is-key-held: dependencies: '@tanstack/svelte-hotkeys': - specifier: 0.0.1 + specifier: 0.4.1 version: link:../../../packages/svelte-hotkeys svelte: specifier: ^5.53.7