browser-shortcuts 0.1.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/LICENSE +21 -0
- package/README.md +262 -0
- package/dist/angular.cjs +337 -0
- package/dist/angular.cjs.map +1 -0
- package/dist/angular.d.cts +71 -0
- package/dist/angular.d.ts +71 -0
- package/dist/angular.js +318 -0
- package/dist/angular.js.map +1 -0
- package/dist/index.cjs +260 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +114 -0
- package/dist/index.d.ts +114 -0
- package/dist/index.js +227 -0
- package/dist/index.js.map +1 -0
- package/dist/preact.cjs +280 -0
- package/dist/preact.cjs.map +1 -0
- package/dist/preact.d.cts +58 -0
- package/dist/preact.d.ts +58 -0
- package/dist/preact.js +257 -0
- package/dist/preact.js.map +1 -0
- package/dist/react.cjs +280 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +65 -0
- package/dist/react.d.ts +65 -0
- package/dist/react.js +258 -0
- package/dist/react.js.map +1 -0
- package/dist/solid.cjs +277 -0
- package/dist/solid.cjs.map +1 -0
- package/dist/solid.d.cts +60 -0
- package/dist/solid.d.ts +60 -0
- package/dist/solid.js +255 -0
- package/dist/solid.js.map +1 -0
- package/dist/svelte.cjs +275 -0
- package/dist/svelte.cjs.map +1 -0
- package/dist/svelte.d.cts +95 -0
- package/dist/svelte.d.ts +95 -0
- package/dist/svelte.js +247 -0
- package/dist/svelte.js.map +1 -0
- package/dist/vue.cjs +300 -0
- package/dist/vue.cjs.map +1 -0
- package/dist/vue.d.cts +101 -0
- package/dist/vue.d.ts +101 -0
- package/dist/vue.js +277 -0
- package/dist/vue.js.map +1 -0
- package/package.json +104 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Antonio Okoro
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# browser-shortcuts
|
|
2
|
+
|
|
3
|
+
Declarative keyboard, mouse and wheel shortcuts for the browser. Register
|
|
4
|
+
shortcuts from any component with a hook/composable, and configure global key
|
|
5
|
+
bindings in one place. Ships with adapters for React, Preact, Vue, Svelte,
|
|
6
|
+
Solid and Angular — all built on the same framework-agnostic core.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install browser-shortcuts
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Each adapter is a separate subpath, so you only pull in the code for the
|
|
13
|
+
framework you use:
|
|
14
|
+
|
|
15
|
+
| Framework | Import |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| React | `browser-shortcuts/react` |
|
|
18
|
+
| Preact | `browser-shortcuts/preact` |
|
|
19
|
+
| Vue | `browser-shortcuts/vue` |
|
|
20
|
+
| Svelte | `browser-shortcuts/svelte` |
|
|
21
|
+
| Solid | `browser-shortcuts/solid` |
|
|
22
|
+
| Angular | `browser-shortcuts/angular` |
|
|
23
|
+
|
|
24
|
+
## React
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
// app/layout.tsx
|
|
28
|
+
import { ShortcutsContextProvider } from 'browser-shortcuts/react';
|
|
29
|
+
|
|
30
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
31
|
+
return (
|
|
32
|
+
<html lang="en">
|
|
33
|
+
<body>
|
|
34
|
+
<ShortcutsContextProvider>{children}</ShortcutsContextProvider>
|
|
35
|
+
</body>
|
|
36
|
+
</html>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { useShortcut } from 'browser-shortcuts/react';
|
|
43
|
+
|
|
44
|
+
function Toolbar() {
|
|
45
|
+
useShortcut('CTRL+SHIFT+F', () => {
|
|
46
|
+
setFullScreen((prev) => !prev);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Preact
|
|
54
|
+
|
|
55
|
+
Identical API to React, imported from `browser-shortcuts/preact`:
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { ShortcutsContextProvider, useShortcut } from 'browser-shortcuts/preact';
|
|
59
|
+
|
|
60
|
+
function App() {
|
|
61
|
+
return (
|
|
62
|
+
<ShortcutsContextProvider>
|
|
63
|
+
<Toolbar />
|
|
64
|
+
</ShortcutsContextProvider>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function Toolbar() {
|
|
69
|
+
useShortcut('CTRL+SHIFT+F', () => setFullScreen((prev) => !prev));
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Vue
|
|
75
|
+
|
|
76
|
+
Install the plugin once, then use `useShortcut` from any component:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
// main.ts
|
|
80
|
+
import { createApp } from 'vue';
|
|
81
|
+
import { ShortcutsPlugin } from 'browser-shortcuts/vue';
|
|
82
|
+
|
|
83
|
+
createApp(App).use(ShortcutsPlugin).mount('#app');
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```vue
|
|
87
|
+
<script setup lang="ts">
|
|
88
|
+
import { useShortcut } from 'browser-shortcuts/vue';
|
|
89
|
+
|
|
90
|
+
const fullScreen = ref(false);
|
|
91
|
+
useShortcut('CTRL+SHIFT+F', () => (fullScreen.value = !fullScreen.value));
|
|
92
|
+
</script>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Svelte
|
|
96
|
+
|
|
97
|
+
Call `setupShortcuts()` once near the root, then `useShortcut` anywhere below it:
|
|
98
|
+
|
|
99
|
+
```svelte
|
|
100
|
+
<!-- App.svelte -->
|
|
101
|
+
<script>
|
|
102
|
+
import { setupShortcuts } from 'browser-shortcuts/svelte';
|
|
103
|
+
setupShortcuts();
|
|
104
|
+
</script>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```svelte
|
|
108
|
+
<script>
|
|
109
|
+
import { useShortcut } from 'browser-shortcuts/svelte';
|
|
110
|
+
|
|
111
|
+
let fullScreen = false;
|
|
112
|
+
useShortcut('CTRL+SHIFT+F', () => (fullScreen = !fullScreen));
|
|
113
|
+
</script>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Solid
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
import { ShortcutsProvider, useShortcut } from 'browser-shortcuts/solid';
|
|
120
|
+
|
|
121
|
+
function App() {
|
|
122
|
+
return (
|
|
123
|
+
<ShortcutsProvider>
|
|
124
|
+
<Toolbar />
|
|
125
|
+
</ShortcutsProvider>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function Toolbar() {
|
|
130
|
+
useShortcut('CTRL+SHIFT+F', () => setFullScreen((prev) => !prev));
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Angular
|
|
136
|
+
|
|
137
|
+
`ShortcutsService` is a root-provided singleton; use `provideShortcuts()` to
|
|
138
|
+
start listening as soon as the app bootstraps:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
// main.ts
|
|
142
|
+
import { bootstrapApplication } from '@angular/platform-browser';
|
|
143
|
+
import { provideShortcuts } from 'browser-shortcuts/angular';
|
|
144
|
+
|
|
145
|
+
bootstrapApplication(AppComponent, {
|
|
146
|
+
providers: [provideShortcuts()],
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { Component } from '@angular/core';
|
|
152
|
+
import { ShortcutsService } from 'browser-shortcuts/angular';
|
|
153
|
+
|
|
154
|
+
@Component({ selector: 'app-toolbar', template: '' })
|
|
155
|
+
export class ToolbarComponent {
|
|
156
|
+
constructor(shortcuts: ShortcutsService) {
|
|
157
|
+
shortcuts.useShortcut('CTRL+SHIFT+F', () => this.toggleFullScreen());
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
toggleFullScreen() {
|
|
161
|
+
/* ... */
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Global key bindings
|
|
167
|
+
|
|
168
|
+
`KEYBINDS` (the default export of `browser-shortcuts`'s core) maps a key
|
|
169
|
+
combination to either the name of a handler in `SHORTCUTS`, or an inline
|
|
170
|
+
function. These fire in addition to any component-level `useShortcut`
|
|
171
|
+
listeners for the same combination, and call `event.preventDefault()`. This
|
|
172
|
+
part of the API is framework-agnostic and lives on the root import:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import KEYBINDS, { SHORTCUTS } from 'browser-shortcuts';
|
|
176
|
+
|
|
177
|
+
// Named binding: references a handler declared once in SHORTCUTS.
|
|
178
|
+
KEYBINDS['CTRL+SHIFT+F'] = 'toggleFullScreen';
|
|
179
|
+
SHORTCUTS.toggleFullScreen = (event) => {
|
|
180
|
+
/* ... */
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// Inline binding: a one-off handler for a single combination.
|
|
184
|
+
KEYBINDS['CTRL+SHIFT+G'] = (event) => {
|
|
185
|
+
/* ... */
|
|
186
|
+
};
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Keybind syntax
|
|
190
|
+
|
|
191
|
+
A `Keybind` string is built from zero or more modifiers, joined with `+`,
|
|
192
|
+
followed by a base key name, all upper case:
|
|
193
|
+
|
|
194
|
+
| Segment | When it appears |
|
|
195
|
+
| --- | --- |
|
|
196
|
+
| `CMD` | `metaKey` held, on macOS only |
|
|
197
|
+
| `CTRL` | `ctrlKey` held |
|
|
198
|
+
| `ALT` | `altKey` held |
|
|
199
|
+
| `SHIFT` | `shiftKey` held |
|
|
200
|
+
| base key | the key/button name, e.g. `F`, `ESCAPE`, `LEFT_BUTTON` |
|
|
201
|
+
|
|
202
|
+
Examples: `"CTRL+SHIFT+F"`, `"CMD+Z"`, `"ESCAPE"`, `"LEFT_BUTTON"`.
|
|
203
|
+
|
|
204
|
+
## Watched events
|
|
205
|
+
|
|
206
|
+
Every adapter attaches a single `ShortcutsManager` that listens for the most
|
|
207
|
+
commonly used input events and normalizes each into a `Keybind` string:
|
|
208
|
+
|
|
209
|
+
| Event | Base key | Example |
|
|
210
|
+
| --- | --- | --- |
|
|
211
|
+
| `keydown` | `event.key` | `"CTRL+SHIFT+F"` |
|
|
212
|
+
| `keyup` | `` `${event.key}_UP` `` | `"F_UP"` |
|
|
213
|
+
| `mousedown` | `LEFT_BUTTON` \| `MIDDLE_BUTTON` \| `RIGHT_BUTTON` | `"LEFT_BUTTON"` |
|
|
214
|
+
| `mouseup` | `..._UP` | `"LEFT_BUTTON_UP"` |
|
|
215
|
+
| `dblclick` | `DOUBLE_...` | `"DOUBLE_LEFT_BUTTON"` |
|
|
216
|
+
| `contextmenu` | `RIGHT_BUTTON` | `"RIGHT_BUTTON"` |
|
|
217
|
+
| `wheel` | `WHEEL_UP` \| `WHEEL_DOWN` (by `deltaY` sign) | `"WHEEL_UP"` |
|
|
218
|
+
|
|
219
|
+
## API
|
|
220
|
+
|
|
221
|
+
### Core (`browser-shortcuts`)
|
|
222
|
+
|
|
223
|
+
- **`ShortcutsManager`** — framework-agnostic class with `on`/`off` and
|
|
224
|
+
`attach(target?)` (defaults to `window`), which starts listening and
|
|
225
|
+
returns a disposer. Every adapter wraps one instance of this class.
|
|
226
|
+
- **`KEYBINDS`** (default export) — map of `Keybind` → `Shortcut` name or
|
|
227
|
+
inline handler, evaluated on every matching event.
|
|
228
|
+
- **`SHORTCUTS`** — map of named handlers referenced from `KEYBINDS`.
|
|
229
|
+
- **`buildShortcutString(key, event)`** — builds a `Keybind` string from a
|
|
230
|
+
base key name and the modifiers held during `event`.
|
|
231
|
+
- **`MOUSE_BUTTONS`** — `["LEFT_BUTTON", "MIDDLE_BUTTON", "RIGHT_BUTTON"]`,
|
|
232
|
+
indexed by `MouseEvent.button`.
|
|
233
|
+
- **`IS_MAC`** / **`IS_NOT_TOUCH_DEVICE`** — environment helpers used
|
|
234
|
+
internally and exposed for convenience.
|
|
235
|
+
|
|
236
|
+
### Adapters
|
|
237
|
+
|
|
238
|
+
- **React / Preact** — `ShortcutsContextProvider` (mount once near the root)
|
|
239
|
+
and `useShortcut(shortcut, callback)`.
|
|
240
|
+
- **Vue** — `ShortcutsPlugin` (install once on the app), plus
|
|
241
|
+
`useShortcut(shortcut, callback)` and `useShortcutsManager()` composables.
|
|
242
|
+
- **Svelte** — `setupShortcuts()` (call once near the root) and
|
|
243
|
+
`useShortcut(shortcut, callback)`.
|
|
244
|
+
- **Solid** — `ShortcutsProvider` (mount once near the root) and
|
|
245
|
+
`useShortcut(shortcut, callback)`.
|
|
246
|
+
- **Angular** — `ShortcutsService` (root-provided singleton) with
|
|
247
|
+
`on`/`off`/`useShortcut`, and `provideShortcuts()` to eagerly start
|
|
248
|
+
listening on bootstrap.
|
|
249
|
+
|
|
250
|
+
### Types
|
|
251
|
+
|
|
252
|
+
- **`Keybind`** — union of the keys declared in `KEYBINDS`.
|
|
253
|
+
- **`Shortcut`** — union of the keys declared in `SHORTCUTS`.
|
|
254
|
+
- **`KeybindAction`** — `Shortcut | ((event: Event) => void)`.
|
|
255
|
+
- **`ShortcutListener`** — `(event: Event) => void`.
|
|
256
|
+
|
|
257
|
+
## Development
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
npm run dev # rebuild dist/ on change
|
|
261
|
+
npm run build # build the publishable library to dist/
|
|
262
|
+
```
|
package/dist/angular.cjs
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
|
|
8
|
+
var __typeError = (msg) => {
|
|
9
|
+
throw TypeError(msg);
|
|
10
|
+
};
|
|
11
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
12
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
13
|
+
var __export = (target, all) => {
|
|
14
|
+
for (var name in all)
|
|
15
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
|
+
};
|
|
17
|
+
var __copyProps = (to, from, except, desc) => {
|
|
18
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
19
|
+
for (let key of __getOwnPropNames(from))
|
|
20
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
21
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
22
|
+
}
|
|
23
|
+
return to;
|
|
24
|
+
};
|
|
25
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
26
|
+
var __decoratorStart = (base) => {
|
|
27
|
+
var _a;
|
|
28
|
+
return [, , , __create((_a = base == null ? void 0 : base[__knownSymbol("metadata")]) != null ? _a : null)];
|
|
29
|
+
};
|
|
30
|
+
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
31
|
+
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
|
|
32
|
+
var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
|
|
33
|
+
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
|
34
|
+
var __runInitializers = (array, flags, self, value) => {
|
|
35
|
+
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
|
|
36
|
+
return value;
|
|
37
|
+
};
|
|
38
|
+
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
39
|
+
var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
|
|
40
|
+
var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
|
|
41
|
+
var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
|
|
42
|
+
var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
|
|
43
|
+
return __privateGet(this, extra);
|
|
44
|
+
}, set [name](x) {
|
|
45
|
+
return __privateSet(this, extra, x);
|
|
46
|
+
} }, name));
|
|
47
|
+
k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
|
|
48
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
49
|
+
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
|
50
|
+
if (k) {
|
|
51
|
+
ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
|
|
52
|
+
if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
|
|
53
|
+
if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
|
|
54
|
+
}
|
|
55
|
+
it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
|
|
56
|
+
if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
|
|
57
|
+
else if (typeof it !== "object" || it === null) __typeError("Object expected");
|
|
58
|
+
else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
|
|
59
|
+
}
|
|
60
|
+
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
61
|
+
};
|
|
62
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
63
|
+
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
|
|
64
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
65
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
66
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
67
|
+
|
|
68
|
+
// src/angular/index.ts
|
|
69
|
+
var angular_exports = {};
|
|
70
|
+
__export(angular_exports, {
|
|
71
|
+
ShortcutsService: () => ShortcutsService,
|
|
72
|
+
provideShortcuts: () => provideShortcuts
|
|
73
|
+
});
|
|
74
|
+
module.exports = __toCommonJS(angular_exports);
|
|
75
|
+
|
|
76
|
+
// src/config/shortcuts.ts
|
|
77
|
+
var KEYBINDS = {
|
|
78
|
+
"CTRL+SHIFT+F": "toggleFullScreen",
|
|
79
|
+
"CMD+SHIFT+F": "toggleFullScreen",
|
|
80
|
+
"CTRL+SHIFT+G": "toggleGrid",
|
|
81
|
+
"CMD+SHIFT+G": "toggleGrid",
|
|
82
|
+
"CTRL+SHIFT+H": "toggleHelp",
|
|
83
|
+
"CMD+SHIFT+H": "toggleHelp",
|
|
84
|
+
ESCAPE: "closeOverlay",
|
|
85
|
+
"CTRL+Z": "undo",
|
|
86
|
+
"CMD+Z": "undo",
|
|
87
|
+
"CTRL+SHIFT+Z": "redo",
|
|
88
|
+
"CMD+SHIFT+Z": "redo",
|
|
89
|
+
"CTRL+Y": "redo",
|
|
90
|
+
DELETE: "delete",
|
|
91
|
+
BACKSPACE: "delete",
|
|
92
|
+
"CTRL+A": "selectAll",
|
|
93
|
+
"CMD+A": "selectAll",
|
|
94
|
+
"CTRL+C": "copy",
|
|
95
|
+
"CMD+C": "copy",
|
|
96
|
+
"CTRL+V": "paste",
|
|
97
|
+
"CMD+V": "paste",
|
|
98
|
+
"CTRL+S": "save",
|
|
99
|
+
"CMD+S": "save",
|
|
100
|
+
DOUBLE_LEFT_BUTTON: "zoomIn",
|
|
101
|
+
LEFT_BUTTON: "leftMouseButton",
|
|
102
|
+
MIDDLE_BUTTON: "middleMouseButton",
|
|
103
|
+
RIGHT_BUTTON: "rightMouseButton"
|
|
104
|
+
};
|
|
105
|
+
var SHORTCUTS = {
|
|
106
|
+
toggleFullScreen: (event) => {
|
|
107
|
+
console.log("toggleFullScreen", event);
|
|
108
|
+
},
|
|
109
|
+
toggleGrid: (event) => {
|
|
110
|
+
console.log("toggleGrid", event);
|
|
111
|
+
},
|
|
112
|
+
toggleHelp: (event) => {
|
|
113
|
+
console.log("toggleHelp", event);
|
|
114
|
+
},
|
|
115
|
+
closeOverlay: (event) => {
|
|
116
|
+
console.log("closeOverlay", event);
|
|
117
|
+
},
|
|
118
|
+
undo: (event) => {
|
|
119
|
+
console.log("undo", event);
|
|
120
|
+
},
|
|
121
|
+
redo: (event) => {
|
|
122
|
+
console.log("redo", event);
|
|
123
|
+
},
|
|
124
|
+
delete: (event) => {
|
|
125
|
+
console.log("delete", event);
|
|
126
|
+
},
|
|
127
|
+
selectAll: (event) => {
|
|
128
|
+
console.log("selectAll", event);
|
|
129
|
+
},
|
|
130
|
+
copy: (event) => {
|
|
131
|
+
console.log("copy", event);
|
|
132
|
+
},
|
|
133
|
+
paste: (event) => {
|
|
134
|
+
console.log("paste", event);
|
|
135
|
+
},
|
|
136
|
+
save: (event) => {
|
|
137
|
+
console.log("save", event);
|
|
138
|
+
},
|
|
139
|
+
zoomIn: (event) => {
|
|
140
|
+
console.log("zoomIn", event);
|
|
141
|
+
},
|
|
142
|
+
leftMouseButton: (event) => {
|
|
143
|
+
console.log("leftMouseButton", event);
|
|
144
|
+
},
|
|
145
|
+
middleMouseButton: (event) => {
|
|
146
|
+
console.log("middleMouseButton", event);
|
|
147
|
+
},
|
|
148
|
+
rightMouseButton: (event) => {
|
|
149
|
+
console.log("rightMouseButton", event);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
var shortcuts_default = KEYBINDS;
|
|
153
|
+
|
|
154
|
+
// src/utils/misc.ts
|
|
155
|
+
var IS_MAC = typeof navigator !== "undefined" && navigator.platform.toUpperCase().indexOf("MAC") >= 0;
|
|
156
|
+
var IS_NOT_TOUCH_DEVICE = (() => {
|
|
157
|
+
if (typeof window === "undefined" || typeof navigator === "undefined") {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
return !("ontouchstart" in window || navigator.maxTouchPoints > 0 || "msMaxTouchPoints" in navigator && navigator.msMaxTouchPoints > 0);
|
|
161
|
+
})();
|
|
162
|
+
var MOUSE_BUTTONS = ["LEFT_BUTTON", "MIDDLE_BUTTON", "RIGHT_BUTTON"];
|
|
163
|
+
function buildShortcutString(key, event) {
|
|
164
|
+
return [
|
|
165
|
+
IS_MAC && event.metaKey ? "CMD" : "",
|
|
166
|
+
event.ctrlKey ? "CTRL" : "",
|
|
167
|
+
event.altKey ? "ALT" : "",
|
|
168
|
+
event.shiftKey ? "SHIFT" : "",
|
|
169
|
+
key.toUpperCase()
|
|
170
|
+
].filter(Boolean).join("+");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/core/shortcuts-manager.ts
|
|
174
|
+
var ShortcutsManager = class {
|
|
175
|
+
constructor() {
|
|
176
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
177
|
+
/** Registers `callback` to run whenever `shortcut` is triggered. */
|
|
178
|
+
this.on = (shortcut, callback) => {
|
|
179
|
+
var _a;
|
|
180
|
+
if (!this.listeners.has(shortcut)) {
|
|
181
|
+
this.listeners.set(shortcut, /* @__PURE__ */ new Set());
|
|
182
|
+
}
|
|
183
|
+
(_a = this.listeners.get(shortcut)) == null ? void 0 : _a.add(callback);
|
|
184
|
+
};
|
|
185
|
+
/** Removes a `callback` previously registered with {@link on}. */
|
|
186
|
+
this.off = (shortcut, callback) => {
|
|
187
|
+
var _a;
|
|
188
|
+
(_a = this.listeners.get(shortcut)) == null ? void 0 : _a.delete(callback);
|
|
189
|
+
};
|
|
190
|
+
this.emit = (shortcut, event) => {
|
|
191
|
+
var _a;
|
|
192
|
+
(_a = this.listeners.get(shortcut)) == null ? void 0 : _a.forEach((callback) => callback(event));
|
|
193
|
+
};
|
|
194
|
+
this.triggerShortcut = (shortcut, event) => {
|
|
195
|
+
var _a, _b;
|
|
196
|
+
this.emit(shortcut, event);
|
|
197
|
+
const action = shortcuts_default[shortcut];
|
|
198
|
+
if (action) {
|
|
199
|
+
event.preventDefault();
|
|
200
|
+
if (typeof action === "string") {
|
|
201
|
+
(_b = (_a = SHORTCUTS)[action]) == null ? void 0 : _b.call(_a, event);
|
|
202
|
+
} else {
|
|
203
|
+
action(event);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Starts listening on `target` (`window` by default). Returns a disposer
|
|
209
|
+
* that removes the listeners; safe to call multiple times.
|
|
210
|
+
*/
|
|
211
|
+
this.attach = (target = window) => {
|
|
212
|
+
this.detach();
|
|
213
|
+
this.target = target;
|
|
214
|
+
const handleKeyDown = (event) => {
|
|
215
|
+
const keyboardEvent = event;
|
|
216
|
+
this.triggerShortcut(
|
|
217
|
+
buildShortcutString(keyboardEvent.key, keyboardEvent),
|
|
218
|
+
event
|
|
219
|
+
);
|
|
220
|
+
};
|
|
221
|
+
const handleKeyUp = (event) => {
|
|
222
|
+
const keyboardEvent = event;
|
|
223
|
+
this.triggerShortcut(
|
|
224
|
+
buildShortcutString(`${keyboardEvent.key}_UP`, keyboardEvent),
|
|
225
|
+
event
|
|
226
|
+
);
|
|
227
|
+
};
|
|
228
|
+
const handleMouseDown = (event) => {
|
|
229
|
+
const mouseEvent = event;
|
|
230
|
+
this.triggerShortcut(
|
|
231
|
+
buildShortcutString(MOUSE_BUTTONS[mouseEvent.button], mouseEvent),
|
|
232
|
+
event
|
|
233
|
+
);
|
|
234
|
+
};
|
|
235
|
+
const handleMouseUp = (event) => {
|
|
236
|
+
const mouseEvent = event;
|
|
237
|
+
this.triggerShortcut(
|
|
238
|
+
buildShortcutString(
|
|
239
|
+
`${MOUSE_BUTTONS[mouseEvent.button]}_UP`,
|
|
240
|
+
mouseEvent
|
|
241
|
+
),
|
|
242
|
+
event
|
|
243
|
+
);
|
|
244
|
+
};
|
|
245
|
+
const handleDoubleClick = (event) => {
|
|
246
|
+
const mouseEvent = event;
|
|
247
|
+
this.triggerShortcut(
|
|
248
|
+
buildShortcutString(
|
|
249
|
+
`DOUBLE_${MOUSE_BUTTONS[mouseEvent.button]}`,
|
|
250
|
+
mouseEvent
|
|
251
|
+
),
|
|
252
|
+
event
|
|
253
|
+
);
|
|
254
|
+
};
|
|
255
|
+
const handleContextMenu = (event) => {
|
|
256
|
+
this.triggerShortcut(
|
|
257
|
+
buildShortcutString("RIGHT_BUTTON", event),
|
|
258
|
+
event
|
|
259
|
+
);
|
|
260
|
+
};
|
|
261
|
+
const handleWheel = (event) => {
|
|
262
|
+
const wheelEvent = event;
|
|
263
|
+
const direction = wheelEvent.deltaY < 0 ? "WHEEL_UP" : "WHEEL_DOWN";
|
|
264
|
+
this.triggerShortcut(buildShortcutString(direction, wheelEvent), event);
|
|
265
|
+
};
|
|
266
|
+
target.addEventListener("keydown", handleKeyDown);
|
|
267
|
+
target.addEventListener("keyup", handleKeyUp);
|
|
268
|
+
target.addEventListener("mousedown", handleMouseDown);
|
|
269
|
+
target.addEventListener("mouseup", handleMouseUp);
|
|
270
|
+
target.addEventListener("dblclick", handleDoubleClick);
|
|
271
|
+
target.addEventListener("contextmenu", handleContextMenu);
|
|
272
|
+
target.addEventListener("wheel", handleWheel, { passive: true });
|
|
273
|
+
this.cleanup = () => {
|
|
274
|
+
target.removeEventListener("keydown", handleKeyDown);
|
|
275
|
+
target.removeEventListener("keyup", handleKeyUp);
|
|
276
|
+
target.removeEventListener("mousedown", handleMouseDown);
|
|
277
|
+
target.removeEventListener("mouseup", handleMouseUp);
|
|
278
|
+
target.removeEventListener("dblclick", handleDoubleClick);
|
|
279
|
+
target.removeEventListener("contextmenu", handleContextMenu);
|
|
280
|
+
target.removeEventListener("wheel", handleWheel);
|
|
281
|
+
this.target = void 0;
|
|
282
|
+
this.cleanup = void 0;
|
|
283
|
+
};
|
|
284
|
+
return this.cleanup;
|
|
285
|
+
};
|
|
286
|
+
/** Stops listening. Safe to call even if {@link attach} was never called. */
|
|
287
|
+
this.detach = () => {
|
|
288
|
+
var _a;
|
|
289
|
+
(_a = this.cleanup) == null ? void 0 : _a.call(this);
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// src/angular/index.ts
|
|
295
|
+
var import_core = require("@angular/core");
|
|
296
|
+
var _ShortcutsService_decorators, _init;
|
|
297
|
+
_ShortcutsService_decorators = [(0, import_core.Injectable)({ providedIn: "root" })];
|
|
298
|
+
var ShortcutsService = class {
|
|
299
|
+
constructor() {
|
|
300
|
+
this.manager = new ShortcutsManager();
|
|
301
|
+
this.detach = this.manager.attach();
|
|
302
|
+
this.on = this.manager.on;
|
|
303
|
+
this.off = this.manager.off;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Subscribes `callback` to `shortcut` and automatically unsubscribes when
|
|
307
|
+
* the current injection context (e.g. the injecting component) is
|
|
308
|
+
* destroyed. Must be called from an injection context.
|
|
309
|
+
*/
|
|
310
|
+
useShortcut(shortcut, callback) {
|
|
311
|
+
const destroyRef = (0, import_core.inject)(import_core.DestroyRef);
|
|
312
|
+
this.manager.on(shortcut, callback);
|
|
313
|
+
destroyRef.onDestroy(() => this.manager.off(shortcut, callback));
|
|
314
|
+
}
|
|
315
|
+
/** @internal */
|
|
316
|
+
ngOnDestroy() {
|
|
317
|
+
this.detach();
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
_init = __decoratorStart(null);
|
|
321
|
+
ShortcutsService = __decorateElement(_init, 0, "ShortcutsService", _ShortcutsService_decorators, ShortcutsService);
|
|
322
|
+
__runInitializers(_init, 1, ShortcutsService);
|
|
323
|
+
function provideShortcuts() {
|
|
324
|
+
return (0, import_core.makeEnvironmentProviders)([
|
|
325
|
+
{
|
|
326
|
+
provide: import_core.APP_INITIALIZER,
|
|
327
|
+
multi: true,
|
|
328
|
+
useFactory: () => () => (0, import_core.inject)(ShortcutsService)
|
|
329
|
+
}
|
|
330
|
+
]);
|
|
331
|
+
}
|
|
332
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
333
|
+
0 && (module.exports = {
|
|
334
|
+
ShortcutsService,
|
|
335
|
+
provideShortcuts
|
|
336
|
+
});
|
|
337
|
+
//# sourceMappingURL=angular.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/angular/index.ts","../src/config/shortcuts.ts","../src/utils/misc.ts","../src/core/shortcuts-manager.ts"],"sourcesContent":["import type { Keybind } from '@/config';\nimport { ShortcutsManager, type ShortcutListener } from '@/core/shortcuts-manager';\nimport {\n DestroyRef,\n inject,\n Injectable,\n makeEnvironmentProviders,\n APP_INITIALIZER,\n} from '@angular/core';\n\nexport type { ShortcutListener } from '@/core/shortcuts-manager';\n\n/**\n * App-wide keyboard, mouse and wheel shortcut dispatcher. Injected as a\n * singleton; call {@link ShortcutsService.on} / {@link ShortcutsService.off}\n * directly, or use {@link ShortcutsService.useShortcut} inside another\n * injection context (e.g. a component constructor) for automatic cleanup.\n */\n@Injectable({ providedIn: 'root' })\nexport class ShortcutsService {\n private manager = new ShortcutsManager();\n private detach = this.manager.attach();\n\n on: (shortcut: Keybind, callback: ShortcutListener) => void = this.manager.on;\n off: (shortcut: Keybind, callback: ShortcutListener) => void = this.manager.off;\n\n /**\n * Subscribes `callback` to `shortcut` and automatically unsubscribes when\n * the current injection context (e.g. the injecting component) is\n * destroyed. Must be called from an injection context.\n */\n useShortcut(shortcut: Keybind, callback: ShortcutListener): void {\n const destroyRef = inject(DestroyRef);\n this.manager.on(shortcut, callback);\n destroyRef.onDestroy(() => this.manager.off(shortcut, callback));\n }\n\n /** @internal */\n ngOnDestroy(): void {\n this.detach();\n }\n}\n\n/**\n * Eagerly instantiates {@link ShortcutsService} so global shortcuts start\n * listening as soon as the app bootstraps, rather than lazily on first\n * injection.\n *\n * @example\n * ```ts\n * bootstrapApplication(AppComponent, {\n * providers: [provideShortcuts()],\n * });\n * ```\n */\nexport function provideShortcuts() {\n return makeEnvironmentProviders([\n {\n provide: APP_INITIALIZER,\n multi: true,\n useFactory: () => () => inject(ShortcutsService),\n },\n ]);\n}\n","/**\n * Maps a keybind string (e.g. `\"CTRL+SHIFT+F\"`) to either the name of a\n * {@link SHORTCUTS} entry or an inline handler.\n *\n * Prefer referencing a named entry in {@link SHORTCUTS} so the same action\n * can be triggered programmatically (e.g. from a command palette) using the\n * same {@link Shortcut} name.\n */\nexport type KeybindAction = Shortcut | ((event: Event) => void);\n\nconst KEYBINDS = {\n 'CTRL+SHIFT+F': 'toggleFullScreen',\n 'CMD+SHIFT+F': 'toggleFullScreen',\n 'CTRL+SHIFT+G': 'toggleGrid',\n 'CMD+SHIFT+G': 'toggleGrid',\n 'CTRL+SHIFT+H': 'toggleHelp',\n 'CMD+SHIFT+H': 'toggleHelp',\n ESCAPE: 'closeOverlay',\n 'CTRL+Z': 'undo',\n 'CMD+Z': 'undo',\n 'CTRL+SHIFT+Z': 'redo',\n 'CMD+SHIFT+Z': 'redo',\n 'CTRL+Y': 'redo',\n DELETE: 'delete',\n BACKSPACE: 'delete',\n 'CTRL+A': 'selectAll',\n 'CMD+A': 'selectAll',\n 'CTRL+C': 'copy',\n 'CMD+C': 'copy',\n 'CTRL+V': 'paste',\n 'CMD+V': 'paste',\n 'CTRL+S': 'save',\n 'CMD+S': 'save',\n DOUBLE_LEFT_BUTTON: 'zoomIn',\n LEFT_BUTTON: 'leftMouseButton',\n MIDDLE_BUTTON: 'middleMouseButton',\n RIGHT_BUTTON: 'rightMouseButton',\n} satisfies { [key: string]: KeybindAction };\n\n/**\n * Named shortcut handlers, keyed by {@link Shortcut} name. Referenced from\n * {@link KEYBINDS} by name so the same action can be triggered from\n * multiple bindings (or invoked directly, outside of any input event).\n */\nexport const SHORTCUTS = {\n toggleFullScreen: (event: Event) => {\n console.log('toggleFullScreen', event);\n },\n toggleGrid: (event: Event) => {\n console.log('toggleGrid', event);\n },\n toggleHelp: (event: Event) => {\n console.log('toggleHelp', event);\n },\n closeOverlay: (event: Event) => {\n console.log('closeOverlay', event);\n },\n undo: (event: Event) => {\n console.log('undo', event);\n },\n redo: (event: Event) => {\n console.log('redo', event);\n },\n delete: (event: Event) => {\n console.log('delete', event);\n },\n selectAll: (event: Event) => {\n console.log('selectAll', event);\n },\n copy: (event: Event) => {\n console.log('copy', event);\n },\n paste: (event: Event) => {\n console.log('paste', event);\n },\n save: (event: Event) => {\n console.log('save', event);\n },\n zoomIn: (event: Event) => {\n console.log('zoomIn', event);\n },\n leftMouseButton: (event: Event) => {\n console.log('leftMouseButton', event);\n },\n middleMouseButton: (event: Event) => {\n console.log('middleMouseButton', event);\n },\n rightMouseButton: (event: Event) => {\n console.log('rightMouseButton', event);\n },\n};\n\n/** A key combination string understood by {@link KEYBINDS}, e.g. `\"CTRL+SHIFT+F\"`. */\nexport type Keybind = keyof typeof KEYBINDS;\n\n/** The name of a handler registered in {@link SHORTCUTS}. */\nexport type Shortcut = keyof typeof SHORTCUTS;\n\nexport default KEYBINDS;\n","import type { Keybind } from '@/config';\n\n/** Whether the current platform reports itself as macOS (used to prefer `CMD` over `CTRL`). */\nexport const IS_MAC =\n typeof navigator !== 'undefined' &&\n navigator.platform.toUpperCase().indexOf('MAC') >= 0;\n\n/** Whether the current device supports mouse/keyboard input rather than touch only. */\nexport const IS_NOT_TOUCH_DEVICE = (() => {\n if (typeof window === 'undefined' || typeof navigator === 'undefined') {\n return false;\n }\n return !(\n 'ontouchstart' in window ||\n navigator.maxTouchPoints > 0 ||\n ('msMaxTouchPoints' in navigator &&\n (navigator.msMaxTouchPoints as number) > 0)\n );\n})();\n\n/** Names for `MouseEvent.button` values 0, 1 and 2, in that order. */\nexport const MOUSE_BUTTONS = ['LEFT_BUTTON', 'MIDDLE_BUTTON', 'RIGHT_BUTTON'];\n\n/**\n * Builds a {@link Keybind} string (e.g. `\"CTRL+SHIFT+F\"`) from a base key\n * name and the modifier keys held during the triggering event.\n *\n * @param key - The key or button name, e.g. `\"F\"` or `\"LEFT_BUTTON\"`.\n * @param event - The DOM event the key combination is derived from.\n */\nexport function buildShortcutString(\n key: string,\n event: KeyboardEvent | MouseEvent | WheelEvent,\n): Keybind {\n return [\n IS_MAC && event.metaKey ? 'CMD' : '',\n event.ctrlKey ? 'CTRL' : '',\n event.altKey ? 'ALT' : '',\n event.shiftKey ? 'SHIFT' : '',\n key.toUpperCase(),\n ]\n .filter(Boolean)\n .join('+') as Keybind;\n}\n","import KEYBINDS, {\n SHORTCUTS,\n type Keybind,\n type KeybindAction,\n type Shortcut,\n} from '@/config/shortcuts';\nimport { buildShortcutString, MOUSE_BUTTONS } from '@/utils/misc';\n\n/** Callback invoked when a bound shortcut fires. */\nexport type ShortcutListener = (event: Event) => void;\n\n/**\n * Framework-agnostic keyboard, mouse and wheel shortcut dispatcher.\n *\n * Listens for the most commonly used input events (`keydown`, `keyup`,\n * `mousedown`, `mouseup`, `dblclick`, `contextmenu` and `wheel`) on a target\n * (`window` by default), converts each into a {@link Keybind} string, and\n * dispatches it to any listeners registered with {@link on} as well as the\n * static bindings declared in `KEYBINDS`.\n *\n * Every framework adapter in this package (React, Vue, Svelte, Angular,\n * Solid, Preact) wraps a single instance of this class in that framework's\n * own idioms (context/provider, composable, store, service, etc.).\n */\nexport class ShortcutsManager {\n private listeners = new Map<Keybind, Set<ShortcutListener>>();\n private target: EventTarget | undefined;\n private cleanup: (() => void) | undefined;\n\n /** Registers `callback` to run whenever `shortcut` is triggered. */\n on = (shortcut: Keybind, callback: ShortcutListener): void => {\n if (!this.listeners.has(shortcut)) {\n this.listeners.set(shortcut, new Set());\n }\n this.listeners.get(shortcut)?.add(callback);\n };\n\n /** Removes a `callback` previously registered with {@link on}. */\n off = (shortcut: Keybind, callback: ShortcutListener): void => {\n this.listeners.get(shortcut)?.delete(callback);\n };\n\n private emit = (shortcut: Keybind, event: Event): void => {\n this.listeners.get(shortcut)?.forEach((callback) => callback(event));\n };\n\n private triggerShortcut = (shortcut: Keybind, event: Event): void => {\n this.emit(shortcut, event);\n\n const action = KEYBINDS[shortcut] as KeybindAction | undefined;\n if (action) {\n event.preventDefault();\n if (typeof action === 'string') {\n SHORTCUTS[action as Shortcut]?.(event);\n } else {\n action(event);\n }\n }\n };\n\n /**\n * Starts listening on `target` (`window` by default). Returns a disposer\n * that removes the listeners; safe to call multiple times.\n */\n attach = (target: EventTarget = window): (() => void) => {\n this.detach();\n this.target = target;\n\n const handleKeyDown = (event: Event) => {\n const keyboardEvent = event as KeyboardEvent;\n this.triggerShortcut(\n buildShortcutString(keyboardEvent.key, keyboardEvent),\n event,\n );\n };\n\n const handleKeyUp = (event: Event) => {\n const keyboardEvent = event as KeyboardEvent;\n this.triggerShortcut(\n buildShortcutString(`${keyboardEvent.key}_UP`, keyboardEvent),\n event,\n );\n };\n\n const handleMouseDown = (event: Event) => {\n const mouseEvent = event as MouseEvent;\n this.triggerShortcut(\n buildShortcutString(MOUSE_BUTTONS[mouseEvent.button], mouseEvent),\n event,\n );\n };\n\n const handleMouseUp = (event: Event) => {\n const mouseEvent = event as MouseEvent;\n this.triggerShortcut(\n buildShortcutString(\n `${MOUSE_BUTTONS[mouseEvent.button]}_UP`,\n mouseEvent,\n ),\n event,\n );\n };\n\n const handleDoubleClick = (event: Event) => {\n const mouseEvent = event as MouseEvent;\n this.triggerShortcut(\n buildShortcutString(\n `DOUBLE_${MOUSE_BUTTONS[mouseEvent.button]}`,\n mouseEvent,\n ),\n event,\n );\n };\n\n const handleContextMenu = (event: Event) => {\n this.triggerShortcut(\n buildShortcutString('RIGHT_BUTTON', event as MouseEvent),\n event,\n );\n };\n\n const handleWheel = (event: Event) => {\n const wheelEvent = event as WheelEvent;\n const direction = wheelEvent.deltaY < 0 ? 'WHEEL_UP' : 'WHEEL_DOWN';\n this.triggerShortcut(buildShortcutString(direction, wheelEvent), event);\n };\n\n target.addEventListener('keydown', handleKeyDown);\n target.addEventListener('keyup', handleKeyUp);\n target.addEventListener('mousedown', handleMouseDown);\n target.addEventListener('mouseup', handleMouseUp);\n target.addEventListener('dblclick', handleDoubleClick);\n target.addEventListener('contextmenu', handleContextMenu);\n target.addEventListener('wheel', handleWheel, { passive: true });\n\n this.cleanup = () => {\n target.removeEventListener('keydown', handleKeyDown);\n target.removeEventListener('keyup', handleKeyUp);\n target.removeEventListener('mousedown', handleMouseDown);\n target.removeEventListener('mouseup', handleMouseUp);\n target.removeEventListener('dblclick', handleDoubleClick);\n target.removeEventListener('contextmenu', handleContextMenu);\n target.removeEventListener('wheel', handleWheel);\n this.target = undefined;\n this.cleanup = undefined;\n };\n\n return this.cleanup;\n };\n\n /** Stops listening. Safe to call even if {@link attach} was never called. */\n detach = (): void => {\n this.cleanup?.();\n };\n}\n"],"mappings":"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","names":[]}
|