@tanstack/ember-hotkeys 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ember-hotkeys",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Ember adapter for TanStack Hotkeys",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"keywords": [
|
|
18
18
|
"ember",
|
|
19
19
|
"tanstack",
|
|
20
|
-
"hotkeys"
|
|
20
|
+
"hotkeys",
|
|
21
|
+
"tanstack-intent"
|
|
21
22
|
],
|
|
22
23
|
"type": "module",
|
|
23
24
|
"types": "./dist/index.d.ts",
|
|
@@ -34,11 +35,12 @@
|
|
|
34
35
|
},
|
|
35
36
|
"files": [
|
|
36
37
|
"dist",
|
|
37
|
-
"addon-main.cjs"
|
|
38
|
+
"addon-main.cjs",
|
|
39
|
+
"skills"
|
|
38
40
|
],
|
|
39
41
|
"dependencies": {
|
|
40
42
|
"@embroider/addon-shim": "^1.10.3",
|
|
41
|
-
"@tanstack/hotkeys": "0.
|
|
43
|
+
"@tanstack/hotkeys": "0.17.0",
|
|
42
44
|
"@tanstack/store": "^0.11.2",
|
|
43
45
|
"ember-modifier": "4.2.2"
|
|
44
46
|
},
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ember-hotkeys
|
|
3
|
+
description: >-
|
|
4
|
+
Use when adding or fixing keyboard shortcuts in an Ember app with
|
|
5
|
+
@tanstack/ember-hotkeys: the useHotkey and useHotkeys template helpers,
|
|
6
|
+
onHotkey and onHotkeys element modifiers, createHotkeysScope defaults,
|
|
7
|
+
scopes activated from the hotkey manager, app hotkeys, recorders, and held
|
|
8
|
+
keys in strict-mode .gts components, or replacing keydown event listeners.
|
|
9
|
+
metadata:
|
|
10
|
+
purpose: >-
|
|
11
|
+
Ember bindings for TanStack Hotkeys: template helpers, element modifiers,
|
|
12
|
+
owner-bound state readers, manager-activated scopes, and the
|
|
13
|
+
Ember-specific mistakes to avoid.
|
|
14
|
+
type: framework
|
|
15
|
+
framework: ember
|
|
16
|
+
library: '@tanstack/ember-hotkeys'
|
|
17
|
+
library_version: '0.4.0'
|
|
18
|
+
sources:
|
|
19
|
+
- src/useHotkey.ts
|
|
20
|
+
- src/useHotkeys.ts
|
|
21
|
+
- src/onHotkey.ts
|
|
22
|
+
- src/onHotkeys.ts
|
|
23
|
+
- src/createHotkeysScope.ts
|
|
24
|
+
- src/useAppHotkeys.ts
|
|
25
|
+
- TanStack/hotkeys:docs/framework/ember/quick-start.md
|
|
26
|
+
- TanStack/hotkeys:docs/framework/ember/guides/app-hotkeys.md
|
|
27
|
+
requires:
|
|
28
|
+
- '@tanstack/hotkeys#hotkeys-core'
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
# TanStack Hotkeys in Ember
|
|
32
|
+
|
|
33
|
+
Before starting, load the `@tanstack/hotkeys#hotkeys-core` skill (`npx @tanstack/intent@latest load @tanstack/hotkeys#hotkeys-core`).
|
|
34
|
+
|
|
35
|
+
```gts
|
|
36
|
+
import Component from '@glimmer/component'
|
|
37
|
+
import { tracked } from '@glimmer/tracking'
|
|
38
|
+
import { array } from '@ember/helper'
|
|
39
|
+
import { onHotkey, useHotkey } from '@tanstack/ember-hotkeys'
|
|
40
|
+
|
|
41
|
+
export default class Editor extends Component {
|
|
42
|
+
@tracked dirty = false
|
|
43
|
+
save = () => saveDocument()
|
|
44
|
+
close = () => closePanel()
|
|
45
|
+
scrollToTop = () => window.scrollTo({ top: 0 })
|
|
46
|
+
|
|
47
|
+
<template>
|
|
48
|
+
{{useHotkey "Mod+S" this.save enabled=this.dirty}}
|
|
49
|
+
{{useHotkey (array "G" "G") this.scrollToTop}}
|
|
50
|
+
<div tabindex="0" {{onHotkey "Escape" this.close}}>Panel</div>
|
|
51
|
+
</template>
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## API
|
|
56
|
+
|
|
57
|
+
| API | What it's for |
|
|
58
|
+
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
59
|
+
| `{{useHotkey hotkey callback ...options}}` | Register one chord, sequence, or app hotkey while the template renders the helper. |
|
|
60
|
+
| `{{useHotkeys definitions}}` | Register several hotkeys, or a list built from data, in one helper. |
|
|
61
|
+
| `{{onHotkey hotkey callback ...options}}` | Attach a hotkey to the element the modifier is on, so it fires while focus is inside that element. |
|
|
62
|
+
| `{{onHotkeys definitions}}` | Attach several hotkeys to one element. |
|
|
63
|
+
| `createHotkeysScope(defaults?)` | Create `useHotkey`, `useHotkeys`, `onHotkey`, `onHotkeys`, and `useHotkeyRecorder` that share default options. |
|
|
64
|
+
| `provideAppHotkeys(appHotkeys, options?)` | Declare app hotkeys and load the user's custom hotkeys before the app boots, since Ember has no provider. |
|
|
65
|
+
| `getHotkeyManager().activateScope(scope, options?)` | Turn on a scope, such as an exclusive one for a dialog; it returns the function that turns the scope off. |
|
|
66
|
+
| `useAppHotkeys(this)` | Get the declared app hotkeys with the user's custom hotkeys applied. |
|
|
67
|
+
| `useAppHotkeyList(this)` | List every app hotkey with its status, for a cheat sheet or settings screen. |
|
|
68
|
+
| `useCustomHotkeys(this)` | Read, change, and reset the user's custom hotkeys, and check a new one for conflicts. |
|
|
69
|
+
| `useHotkeyRecorder(this, options)` | Record a chord or sequence the user presses, for a settings screen. |
|
|
70
|
+
| `useHotkeyRegistrations(this)` | Read `.hotkeys`, every registered hotkey with its options and `meta`. |
|
|
71
|
+
| `useHotkeyHint(this, hotkey, options?)` | Get whether held modifiers should reveal a shortcut's hint. |
|
|
72
|
+
| `useKeyHold(this, key)` | Get whether one key is held. |
|
|
73
|
+
| `useHeldKeys(this)` | Get every key that's held right now. |
|
|
74
|
+
| `useHeldKeyCodes(this)` | Map each held key to its physical `event.code`. |
|
|
75
|
+
|
|
76
|
+
The package also re-exports `@tanstack/hotkeys`, such as `formatForDisplay`, `matchesKeyboardEvent`, and `defineAppHotkeys`.
|
|
77
|
+
|
|
78
|
+
The helpers are template helpers, not JavaScript functions. State readers take the component as their owner, and their state is read through `.value`, or `.hotkeys` for registrations. Use Ember 6.8 or newer and strict-mode `.gts` components.
|
|
79
|
+
|
|
80
|
+
## Pitfalls
|
|
81
|
+
|
|
82
|
+
- **Use arrow-function fields, or `@action`, for callbacks.** The helpers call a callback without the component as `this`, so a plain method throws when the key is pressed.
|
|
83
|
+
- **Build template sequences with `array`.** `{{useHotkey "G G" ...}}` registers a single key named `G G`, which never matches.
|
|
84
|
+
- **`createHotkeysScope` isn't a hotkey scope.** It only sets defaults for its helpers. For a dialog, call `getHotkeyManager().activateScope(name, { exclusive: true })` in the constructor, pass the result to `registerDestructor(this, release)`, and put `scope=name` on the dialog's hotkeys. Give each open dialog its own name.
|
|
85
|
+
- **Read app hotkeys through getters.** Template paths can't index ids that contain dots.
|
|
86
|
+
|
|
87
|
+
When declaring app hotkeys, building a cheat sheet, or letting users rebind shortcuts, load `@tanstack/hotkeys#app-hotkeys` and read [app hotkeys in Ember](references/app-hotkeys.md).
|
|
88
|
+
|
|
89
|
+
## API discovery and docs
|
|
90
|
+
|
|
91
|
+
Signatures: `node_modules/@tanstack/ember-hotkeys/dist/*.d.ts`, with state shapes in `types.d.ts`. Guides: `https://tanstack.com/hotkeys/latest/docs/framework/ember/guides/<page>.md`, such as `hotkeys` or `app-hotkeys`.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# App hotkeys in Ember
|
|
2
|
+
|
|
3
|
+
The `@tanstack/hotkeys#app-hotkeys` skill owns the rules. This is the Ember wiring.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
// app/app-hotkeys.ts
|
|
7
|
+
import { defineAppHotkeys } from '@tanstack/ember-hotkeys'
|
|
8
|
+
|
|
9
|
+
export const appHotkeys = defineAppHotkeys({
|
|
10
|
+
'doc.save': { hotkey: 'Mod+S', meta: { name: 'Save' } },
|
|
11
|
+
'help.shortcuts': { hotkey: 'Mod+/', meta: { name: 'Keyboard shortcuts' } },
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
declare module '@tanstack/ember-hotkeys' {
|
|
15
|
+
interface Register {
|
|
16
|
+
appHotkeys: typeof appHotkeys
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
// app/app.ts, before the application boots
|
|
23
|
+
import { provideAppHotkeys } from '@tanstack/ember-hotkeys'
|
|
24
|
+
import { appHotkeys } from './app-hotkeys'
|
|
25
|
+
|
|
26
|
+
provideAppHotkeys(appHotkeys, {
|
|
27
|
+
customHotkeys: JSON.parse(localStorage.getItem('hotkeys') ?? '{}'),
|
|
28
|
+
onCustomHotkeysChange: (custom) =>
|
|
29
|
+
localStorage.setItem('hotkeys', JSON.stringify(custom)),
|
|
30
|
+
})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The app hotkey utilities throw until `provideAppHotkeys` has run. When saved shortcuts come from a server, await them first. Register through a getter: `get saveHotkey() { return this.appHotkeys.value['doc.save'] }` with `appHotkeys = useAppHotkeys(this)`, then `{{useHotkey this.saveHotkey this.save}}`. Build a cheat sheet from `useAppHotkeyList(this).value`.
|
|
34
|
+
|
|
35
|
+
A settings row records with `useHotkeyRecorder` and saves through `useCustomHotkeys`:
|
|
36
|
+
|
|
37
|
+
```gts
|
|
38
|
+
import Component from '@glimmer/component'
|
|
39
|
+
import { on } from '@ember/modifier'
|
|
40
|
+
import { formatForDisplay, useAppHotkeys, useCustomHotkeys, useHotkeyRecorder } from '@tanstack/ember-hotkeys'
|
|
41
|
+
import type { AppHotkeyId } from '@tanstack/ember-hotkeys'
|
|
42
|
+
|
|
43
|
+
export default class ShortcutRow extends Component<{ Args: { id: AppHotkeyId } }> {
|
|
44
|
+
appHotkeys = useAppHotkeys(this)
|
|
45
|
+
custom = useCustomHotkeys(this)
|
|
46
|
+
recorder = useHotkeyRecorder(this, () => ({
|
|
47
|
+
maxSteps: Infinity,
|
|
48
|
+
validate: (hotkey) => {
|
|
49
|
+
const [conflict] = this.custom.findConflicts(this.args.id, hotkey)
|
|
50
|
+
return conflict ? `Used by ${conflict.meta?.name ?? conflict.id}` : true
|
|
51
|
+
},
|
|
52
|
+
onRecord: (hotkey) => this.custom.setCustomHotkey(this.args.id, hotkey),
|
|
53
|
+
onClear: () => this.custom.setCustomHotkey(this.args.id, null),
|
|
54
|
+
}))
|
|
55
|
+
|
|
56
|
+
get label() {
|
|
57
|
+
const { meta, hotkey } = this.appHotkeys.value[this.args.id]
|
|
58
|
+
return `${meta?.name}: ${hotkey ? formatForDisplay(hotkey) : 'Unassigned'}`
|
|
59
|
+
}
|
|
60
|
+
reset = () => this.custom.resetCustomHotkey(this.args.id)
|
|
61
|
+
|
|
62
|
+
<template>
|
|
63
|
+
{{this.label}}
|
|
64
|
+
<button type="button" {{on "click" this.recorder.startRecording}}>Change</button>
|
|
65
|
+
<button type="button" {{on "click" this.reset}}>Reset</button>
|
|
66
|
+
</template>
|
|
67
|
+
}
|
|
68
|
+
```
|