Skip to content

Commit 8d5c31a

Browse files
authored
feat: useHotkeySequences hooks (#80)
* feat: useHotkeySequences hooks * address pr feedback * rewrite sequence enabled stuff * fix pr feedback. clean up unregistration code in hooks
1 parent d058c76 commit 8d5c31a

158 files changed

Lines changed: 7039 additions & 239 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/plural-sequences.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@tanstack/react-hotkeys': minor
3+
'@tanstack/preact-hotkeys': minor
4+
'@tanstack/vue-hotkeys': minor
5+
'@tanstack/solid-hotkeys': minor
6+
'@tanstack/svelte-hotkeys': minor
7+
'@tanstack/angular-hotkeys': minor
8+
---
9+
10+
Add plural sequence APIs (`useHotkeySequences`, `createHotkeySequences`, `createHotkeySequencesAttachment`, `injectHotkeySequences`) and align `enabled` across adapters: disabled registrations stay in the manager for devtools, only core dispatch is skipped, and toggling `enabled` updates handles via `setOptions` instead of churning unregister/register.

docs/config.json

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,14 @@
652652
{
653653
"label": "UseHotkeySequenceOptions",
654654
"to": "framework/react/reference/interfaces/UseHotkeySequenceOptions"
655+
},
656+
{
657+
"label": "useHotkeySequences",
658+
"to": "framework/react/reference/functions/useHotkeySequences"
659+
},
660+
{
661+
"label": "UseHotkeySequenceDefinition",
662+
"to": "framework/react/reference/interfaces/UseHotkeySequenceDefinition"
655663
}
656664
]
657665
},
@@ -665,6 +673,14 @@
665673
{
666674
"label": "UseHotkeySequenceOptions",
667675
"to": "framework/preact/reference/interfaces/UseHotkeySequenceOptions"
676+
},
677+
{
678+
"label": "useHotkeySequences",
679+
"to": "framework/preact/reference/functions/useHotkeySequences"
680+
},
681+
{
682+
"label": "UseHotkeySequenceDefinition",
683+
"to": "framework/preact/reference/interfaces/UseHotkeySequenceDefinition"
668684
}
669685
]
670686
},
@@ -678,6 +694,14 @@
678694
{
679695
"label": "CreateHotkeySequenceOptions",
680696
"to": "framework/solid/reference/interfaces/CreateHotkeySequenceOptions"
697+
},
698+
{
699+
"label": "createHotkeySequences",
700+
"to": "framework/solid/reference/functions/createHotkeySequences"
701+
},
702+
{
703+
"label": "CreateHotkeySequenceDefinition",
704+
"to": "framework/solid/reference/interfaces/CreateHotkeySequenceDefinition"
681705
}
682706
]
683707
},
@@ -691,6 +715,14 @@
691715
{
692716
"label": "InjectHotkeySequenceOptions",
693717
"to": "framework/angular/reference/interfaces/InjectHotkeySequenceOptions"
718+
},
719+
{
720+
"label": "injectHotkeySequences",
721+
"to": "framework/angular/reference/functions/injectHotkeySequences"
722+
},
723+
{
724+
"label": "InjectHotkeySequenceDefinition",
725+
"to": "framework/angular/reference/interfaces/InjectHotkeySequenceDefinition"
694726
}
695727
]
696728
},
@@ -704,6 +736,14 @@
704736
{
705737
"label": "UseHotkeySequenceOptions",
706738
"to": "framework/vue/reference/interfaces/UseHotkeySequenceOptions"
739+
},
740+
{
741+
"label": "useHotkeySequences",
742+
"to": "framework/vue/reference/functions/useHotkeySequences"
743+
},
744+
{
745+
"label": "UseHotkeySequenceDefinition",
746+
"to": "framework/vue/reference/interfaces/UseHotkeySequenceDefinition"
707747
}
708748
]
709749
},
@@ -721,6 +761,18 @@
721761
{
722762
"label": "CreateHotkeySequenceOptions",
723763
"to": "framework/svelte/reference/interfaces/CreateHotkeySequenceOptions"
764+
},
765+
{
766+
"label": "createHotkeySequences",
767+
"to": "framework/svelte/reference/functions/createHotkeySequences"
768+
},
769+
{
770+
"label": "createHotkeySequencesAttachment",
771+
"to": "framework/svelte/reference/functions/createHotkeySequencesAttachment"
772+
},
773+
{
774+
"label": "CreateHotkeySequenceDefinition",
775+
"to": "framework/svelte/reference/interfaces/CreateHotkeySequenceDefinition"
724776
}
725777
]
726778
}
@@ -1200,6 +1252,10 @@
12001252
"label": "useHotkeySequence",
12011253
"to": "framework/react/examples/useHotkeySequence"
12021254
},
1255+
{
1256+
"label": "useHotkeySequences",
1257+
"to": "framework/react/examples/useHotkeySequences"
1258+
},
12031259
{
12041260
"label": "useHotkeyRecorder",
12051261
"to": "framework/react/examples/useHotkeyRecorder"
@@ -1233,6 +1289,10 @@
12331289
"label": "useHotkeySequence",
12341290
"to": "framework/preact/examples/useHotkeySequence"
12351291
},
1292+
{
1293+
"label": "useHotkeySequences",
1294+
"to": "framework/preact/examples/useHotkeySequences"
1295+
},
12361296
{
12371297
"label": "useHotkeyRecorder",
12381298
"to": "framework/preact/examples/useHotkeyRecorder"
@@ -1266,6 +1326,10 @@
12661326
"label": "createHotkeySequence",
12671327
"to": "framework/solid/examples/createHotkeySequence"
12681328
},
1329+
{
1330+
"label": "createHotkeySequences",
1331+
"to": "framework/solid/examples/createHotkeySequences"
1332+
},
12691333
{
12701334
"label": "createHotkeyRecorder",
12711335
"to": "framework/solid/examples/createHotkeyRecorder"
@@ -1299,6 +1363,10 @@
12991363
"label": "injectHotkeySequence",
13001364
"to": "framework/angular/examples/injectHotkeySequence"
13011365
},
1366+
{
1367+
"label": "injectHotkeySequences",
1368+
"to": "framework/angular/examples/injectHotkeySequences"
1369+
},
13021370
{
13031371
"label": "injectHotkeyRecorder",
13041372
"to": "framework/angular/examples/injectHotkeyRecorder"
@@ -1332,6 +1400,10 @@
13321400
"label": "useHotkeySequence",
13331401
"to": "framework/vue/examples/useHotkeySequence"
13341402
},
1403+
{
1404+
"label": "useHotkeySequences",
1405+
"to": "framework/vue/examples/useHotkeySequences"
1406+
},
13351407
{
13361408
"label": "useHotkeyRecorder",
13371409
"to": "framework/vue/examples/useHotkeyRecorder"
@@ -1365,6 +1437,10 @@
13651437
"label": "createHotkeySequence",
13661438
"to": "framework/svelte/examples/create-hotkey-sequence"
13671439
},
1440+
{
1441+
"label": "createHotkeySequences",
1442+
"to": "framework/svelte/examples/create-hotkey-sequences"
1443+
},
13681444
{
13691445
"label": "createHotkeyRecorder",
13701446
"to": "framework/svelte/examples/create-hotkey-recorder"

docs/framework/angular/guides/hotkeys.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ For reactive state, pass an accessor function as the third argument.
5454

5555
### `enabled`
5656

57+
When `enabled` is false, the hotkey **stays registered** (visible in devtools); only the callback is suppressed.
58+
5759
```ts
5860
import { Component, signal } from '@angular/core'
5961
import { injectHotkey } from '@tanstack/angular-hotkeys'

docs/framework/angular/guides/sequences.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,35 @@ export class AppComponent {
2121
}
2222
```
2323

24+
## Many sequences at once
25+
26+
Use `injectHotkeySequences` when you want several sequences (or a list built from data) in one injection context, instead of many `injectHotkeySequence` calls.
27+
28+
```ts
29+
import { Component } from '@angular/core'
30+
import { injectHotkeySequences } from '@tanstack/angular-hotkeys'
31+
32+
@Component({ standalone: true, template: `` })
33+
export class AppComponent {
34+
constructor() {
35+
injectHotkeySequences([
36+
{
37+
sequence: ['G', 'G'],
38+
callback: () =>
39+
window.scrollTo({ top: 0, behavior: 'smooth' }),
40+
},
41+
{
42+
sequence: ['D', 'D'],
43+
callback: () => console.log('delete line'),
44+
options: { timeout: 500 },
45+
},
46+
])
47+
}
48+
}
49+
```
50+
51+
Options merge like `injectHotkeys`: `provideHotkeys` defaults, then `commonOptions`, then each definition’s `options`.
52+
2453
## Sequence Options
2554

2655
```ts
@@ -32,6 +61,8 @@ injectHotkeySequence(['G', 'G'], callback, {
3261

3362
### Reactive `enabled`
3463

64+
When disabled, the sequence **stays registered** (visible in devtools); only execution is suppressed.
65+
3566
```ts
3667
import { Component, signal } from '@angular/core'
3768
import { injectHotkeySequence } from '@tanstack/angular-hotkeys'

docs/framework/angular/reference/functions/injectHotkey.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ function injectHotkey(
1212
options): void;
1313
```
1414

15-
Defined in: [injectHotkey.ts:83](https://github.com/TanStack/hotkeys/blob/main/packages/angular-hotkeys/src/injectHotkey.ts#L83)
15+
Defined in: [injectHotkey.ts:86](https://github.com/TanStack/hotkeys/blob/main/packages/angular-hotkeys/src/injectHotkey.ts#L86)
1616

1717
Angular inject-based API for registering a keyboard hotkey.
1818

@@ -23,6 +23,8 @@ containing the hotkey string and parsed hotkey.
2323
Call in an injection context (e.g. constructor or field initializer).
2424
Uses effect() to track reactive dependencies and update registration
2525
when options or the callback change.
26+
`enabled: false` keeps the registration (visible in devtools) and only suppresses firing; the same
27+
handle is updated instead of unregistering and re-registering when identity is unchanged.
2628

2729
## Parameters
2830

docs/framework/angular/reference/functions/injectHotkeySequence.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ function injectHotkeySequence(
1212
options): void;
1313
```
1414

15-
Defined in: [injectHotkeySequence.ts:48](https://github.com/TanStack/hotkeys/blob/main/packages/angular-hotkeys/src/injectHotkeySequence.ts#L48)
15+
Defined in: [injectHotkeySequence.ts:58](https://github.com/TanStack/hotkeys/blob/main/packages/angular-hotkeys/src/injectHotkeySequence.ts#L58)
1616

1717
Angular inject-based API for registering a keyboard shortcut sequence (Vim-style).
1818

@@ -40,7 +40,8 @@ Function to call when the sequence is completed
4040

4141
### options
4242

43-
Options for the sequence behavior (or getter function)
43+
Options for the sequence behavior (or getter function). `enabled: false` still registers
44+
the sequence (visible in devtools); only execution is suppressed.
4445

4546
[`InjectHotkeySequenceOptions`](../interfaces/InjectHotkeySequenceOptions.md) | () => [`InjectHotkeySequenceOptions`](../interfaces/InjectHotkeySequenceOptions.md)
4647

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
id: injectHotkeySequences
3+
title: injectHotkeySequences
4+
---
5+
6+
# Function: injectHotkeySequences()
7+
8+
```ts
9+
function injectHotkeySequences(sequences, commonOptions): void;
10+
```
11+
12+
Defined in: [injectHotkeySequences.ts:51](https://github.com/TanStack/hotkeys/blob/main/packages/angular-hotkeys/src/injectHotkeySequences.ts#L51)
13+
14+
Angular inject-based API for registering multiple keyboard shortcut sequences at once (Vim-style).
15+
16+
Uses the singleton SequenceManager. Call in an injection context (e.g. constructor).
17+
Uses `effect()` to track reactive dependencies when definitions or options are getters.
18+
19+
Options are merged in this order:
20+
provideHotkeys defaults < commonOptions < per-definition options
21+
22+
Definitions with an empty `sequence` are skipped. Disabled sequences (`enabled: false`)
23+
remain registered so they stay visible in devtools; the core manager suppresses execution.
24+
25+
## Parameters
26+
27+
### sequences
28+
29+
Array of sequence definitions, or getter returning them
30+
31+
[`InjectHotkeySequenceDefinition`](../interfaces/InjectHotkeySequenceDefinition.md)[] | () => [`InjectHotkeySequenceDefinition`](../interfaces/InjectHotkeySequenceDefinition.md)[]
32+
33+
### commonOptions
34+
35+
Shared options for all sequences, or getter
36+
37+
[`InjectHotkeySequenceOptions`](../interfaces/InjectHotkeySequenceOptions.md) | () => [`InjectHotkeySequenceOptions`](../interfaces/InjectHotkeySequenceOptions.md)
38+
39+
## Returns
40+
41+
`void`
42+
43+
## Example
44+
45+
```ts
46+
@Component({ ... })
47+
export class VimComponent {
48+
constructor() {
49+
injectHotkeySequences([
50+
{ sequence: ['G', 'G'], callback: () => this.goTop() },
51+
{ sequence: ['D', 'D'], callback: () => this.deleteLine() },
52+
])
53+
}
54+
}
55+
```

docs/framework/angular/reference/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ title: "@tanstack/angular-hotkeys"
1313
- [HotkeysProviderOptions](interfaces/HotkeysProviderOptions.md)
1414
- [InjectHotkeyDefinition](interfaces/InjectHotkeyDefinition.md)
1515
- [InjectHotkeyOptions](interfaces/InjectHotkeyOptions.md)
16+
- [InjectHotkeySequenceDefinition](interfaces/InjectHotkeySequenceDefinition.md)
1617
- [InjectHotkeySequenceOptions](interfaces/InjectHotkeySequenceOptions.md)
1718

1819
## Variables
@@ -30,5 +31,6 @@ title: "@tanstack/angular-hotkeys"
3031
- [injectHotkeysContext](functions/injectHotkeysContext.md)
3132
- [injectHotkeySequence](functions/injectHotkeySequence.md)
3233
- [injectHotkeySequenceRecorder](functions/injectHotkeySequenceRecorder.md)
34+
- [injectHotkeySequences](functions/injectHotkeySequences.md)
3335
- [injectKeyHold](functions/injectKeyHold.md)
3436
- [provideHotkeys](functions/provideHotkeys.md)

docs/framework/angular/reference/interfaces/InjectHotkeyOptions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ title: InjectHotkeyOptions
55

66
# Interface: InjectHotkeyOptions
77

8-
Defined in: [injectHotkey.ts:16](https://github.com/TanStack/hotkeys/blob/main/packages/angular-hotkeys/src/injectHotkey.ts#L16)
8+
Defined in: [injectHotkey.ts:17](https://github.com/TanStack/hotkeys/blob/main/packages/angular-hotkeys/src/injectHotkey.ts#L17)
99

1010
## Extends
1111

@@ -19,7 +19,7 @@ Defined in: [injectHotkey.ts:16](https://github.com/TanStack/hotkeys/blob/main/p
1919
optional target: HTMLElement | Document | Window | null;
2020
```
2121

22-
Defined in: [injectHotkey.ts:24](https://github.com/TanStack/hotkeys/blob/main/packages/angular-hotkeys/src/injectHotkey.ts#L24)
22+
Defined in: [injectHotkey.ts:25](https://github.com/TanStack/hotkeys/blob/main/packages/angular-hotkeys/src/injectHotkey.ts#L25)
2323

2424
The DOM element to attach the event listener to.
2525
Can be a direct DOM element, an accessor (for reactive targets that become
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
id: InjectHotkeySequenceDefinition
3+
title: InjectHotkeySequenceDefinition
4+
---
5+
6+
# Interface: InjectHotkeySequenceDefinition
7+
8+
Defined in: [injectHotkeySequences.ts:14](https://github.com/TanStack/hotkeys/blob/main/packages/angular-hotkeys/src/injectHotkeySequences.ts#L14)
9+
10+
A single sequence definition for use with `injectHotkeySequences`.
11+
12+
## Properties
13+
14+
### callback
15+
16+
```ts
17+
callback: HotkeyCallback;
18+
```
19+
20+
Defined in: [injectHotkeySequences.ts:18](https://github.com/TanStack/hotkeys/blob/main/packages/angular-hotkeys/src/injectHotkeySequences.ts#L18)
21+
22+
The function to call when the sequence is completed
23+
24+
***
25+
26+
### options?
27+
28+
```ts
29+
optional options:
30+
| InjectHotkeySequenceOptions
31+
| () => InjectHotkeySequenceOptions;
32+
```
33+
34+
Defined in: [injectHotkeySequences.ts:20](https://github.com/TanStack/hotkeys/blob/main/packages/angular-hotkeys/src/injectHotkeySequences.ts#L20)
35+
36+
Per-sequence options (merged on top of commonOptions)
37+
38+
***
39+
40+
### sequence
41+
42+
```ts
43+
sequence: HotkeySequence | () => HotkeySequence;
44+
```
45+
46+
Defined in: [injectHotkeySequences.ts:16](https://github.com/TanStack/hotkeys/blob/main/packages/angular-hotkeys/src/injectHotkeySequences.ts#L16)
47+
48+
Array of hotkey strings that form the sequence

0 commit comments

Comments
 (0)