browser-shortcuts 0.1.0 → 0.2.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/dist/angular.cjs +10 -52
- package/dist/angular.cjs.map +1 -1
- package/dist/angular.js +10 -52
- package/dist/angular.js.map +1 -1
- package/dist/solid.cjs +3 -8
- package/dist/solid.cjs.map +1 -1
- package/dist/solid.js +3 -9
- package/dist/solid.js.map +1 -1
- package/package.json +33 -9
package/dist/angular.cjs
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
5
|
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
6
|
var __export = (target, all) => {
|
|
14
7
|
for (var name in all)
|
|
15
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -23,47 +16,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
23
16
|
return to;
|
|
24
17
|
};
|
|
25
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
26
|
-
var
|
|
27
|
-
var
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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;
|
|
19
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
20
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
21
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
22
|
+
if (decorator = decorators[i])
|
|
23
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
24
|
+
if (kind && result) __defProp(target, key, result);
|
|
25
|
+
return result;
|
|
61
26
|
};
|
|
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
27
|
|
|
68
28
|
// src/angular/index.ts
|
|
69
29
|
var angular_exports = {};
|
|
@@ -293,8 +253,6 @@ var ShortcutsManager = class {
|
|
|
293
253
|
|
|
294
254
|
// src/angular/index.ts
|
|
295
255
|
var import_core = require("@angular/core");
|
|
296
|
-
var _ShortcutsService_decorators, _init;
|
|
297
|
-
_ShortcutsService_decorators = [(0, import_core.Injectable)({ providedIn: "root" })];
|
|
298
256
|
var ShortcutsService = class {
|
|
299
257
|
constructor() {
|
|
300
258
|
this.manager = new ShortcutsManager();
|
|
@@ -317,9 +275,9 @@ var ShortcutsService = class {
|
|
|
317
275
|
this.detach();
|
|
318
276
|
}
|
|
319
277
|
};
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
278
|
+
ShortcutsService = __decorateClass([
|
|
279
|
+
(0, import_core.Injectable)({ providedIn: "root" })
|
|
280
|
+
], ShortcutsService);
|
|
323
281
|
function provideShortcuts() {
|
|
324
282
|
return (0, import_core.makeEnvironmentProviders)([
|
|
325
283
|
{
|
package/dist/angular.cjs.map
CHANGED
|
@@ -1 +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":[]}
|
|
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":[]}
|
package/dist/angular.js
CHANGED
|
@@ -1,53 +1,13 @@
|
|
|
1
|
-
var __create = Object.create;
|
|
2
1
|
var __defProp = Object.defineProperty;
|
|
3
2
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result) __defProp(target, key, result);
|
|
9
|
+
return result;
|
|
7
10
|
};
|
|
8
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
9
|
-
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
10
|
-
var __decoratorStart = (base) => {
|
|
11
|
-
var _a;
|
|
12
|
-
return [, , , __create((_a = base == null ? void 0 : base[__knownSymbol("metadata")]) != null ? _a : null)];
|
|
13
|
-
};
|
|
14
|
-
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
15
|
-
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
|
|
16
|
-
var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
|
|
17
|
-
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
|
18
|
-
var __runInitializers = (array, flags, self, value) => {
|
|
19
|
-
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);
|
|
20
|
-
return value;
|
|
21
|
-
};
|
|
22
|
-
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
23
|
-
var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
|
|
24
|
-
var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
|
|
25
|
-
var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
|
|
26
|
-
var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
|
|
27
|
-
return __privateGet(this, extra);
|
|
28
|
-
}, set [name](x) {
|
|
29
|
-
return __privateSet(this, extra, x);
|
|
30
|
-
} }, name));
|
|
31
|
-
k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
|
|
32
|
-
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
33
|
-
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
|
34
|
-
if (k) {
|
|
35
|
-
ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
|
|
36
|
-
if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
|
|
37
|
-
if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
|
|
38
|
-
}
|
|
39
|
-
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;
|
|
40
|
-
if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
|
|
41
|
-
else if (typeof it !== "object" || it === null) __typeError("Object expected");
|
|
42
|
-
else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
|
|
43
|
-
}
|
|
44
|
-
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
45
|
-
};
|
|
46
|
-
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
47
|
-
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
|
|
48
|
-
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
49
|
-
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
50
|
-
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
51
11
|
|
|
52
12
|
// src/config/shortcuts.ts
|
|
53
13
|
var KEYBINDS = {
|
|
@@ -275,8 +235,6 @@ import {
|
|
|
275
235
|
makeEnvironmentProviders,
|
|
276
236
|
APP_INITIALIZER
|
|
277
237
|
} from "@angular/core";
|
|
278
|
-
var _ShortcutsService_decorators, _init;
|
|
279
|
-
_ShortcutsService_decorators = [Injectable({ providedIn: "root" })];
|
|
280
238
|
var ShortcutsService = class {
|
|
281
239
|
constructor() {
|
|
282
240
|
this.manager = new ShortcutsManager();
|
|
@@ -299,9 +257,9 @@ var ShortcutsService = class {
|
|
|
299
257
|
this.detach();
|
|
300
258
|
}
|
|
301
259
|
};
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
260
|
+
ShortcutsService = __decorateClass([
|
|
261
|
+
Injectable({ providedIn: "root" })
|
|
262
|
+
], ShortcutsService);
|
|
305
263
|
function provideShortcuts() {
|
|
306
264
|
return makeEnvironmentProviders([
|
|
307
265
|
{
|
package/dist/angular.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config/shortcuts.ts","../src/utils/misc.ts","../src/core/shortcuts-manager.ts","../src/angular/index.ts"],"sourcesContent":["/**\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","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"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/config/shortcuts.ts","../src/utils/misc.ts","../src/core/shortcuts-manager.ts","../src/angular/index.ts"],"sourcesContent":["/**\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","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"],"mappings":";;;;;;;;;;;;AAUA,IAAM,WAAW;AAAA,EACf,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,UAAU;AAAA,EACV,SAAS;AAAA,EACT,UAAU;AAAA,EACV,SAAS;AAAA,EACT,UAAU;AAAA,EACV,SAAS;AAAA,EACT,UAAU;AAAA,EACV,SAAS;AAAA,EACT,oBAAoB;AAAA,EACpB,aAAa;AAAA,EACb,eAAe;AAAA,EACf,cAAc;AAChB;AAOO,IAAM,YAAY;AAAA,EACvB,kBAAkB,CAAC,UAAiB;AAClC,YAAQ,IAAI,oBAAoB,KAAK;AAAA,EACvC;AAAA,EACA,YAAY,CAAC,UAAiB;AAC5B,YAAQ,IAAI,cAAc,KAAK;AAAA,EACjC;AAAA,EACA,YAAY,CAAC,UAAiB;AAC5B,YAAQ,IAAI,cAAc,KAAK;AAAA,EACjC;AAAA,EACA,cAAc,CAAC,UAAiB;AAC9B,YAAQ,IAAI,gBAAgB,KAAK;AAAA,EACnC;AAAA,EACA,MAAM,CAAC,UAAiB;AACtB,YAAQ,IAAI,QAAQ,KAAK;AAAA,EAC3B;AAAA,EACA,MAAM,CAAC,UAAiB;AACtB,YAAQ,IAAI,QAAQ,KAAK;AAAA,EAC3B;AAAA,EACA,QAAQ,CAAC,UAAiB;AACxB,YAAQ,IAAI,UAAU,KAAK;AAAA,EAC7B;AAAA,EACA,WAAW,CAAC,UAAiB;AAC3B,YAAQ,IAAI,aAAa,KAAK;AAAA,EAChC;AAAA,EACA,MAAM,CAAC,UAAiB;AACtB,YAAQ,IAAI,QAAQ,KAAK;AAAA,EAC3B;AAAA,EACA,OAAO,CAAC,UAAiB;AACvB,YAAQ,IAAI,SAAS,KAAK;AAAA,EAC5B;AAAA,EACA,MAAM,CAAC,UAAiB;AACtB,YAAQ,IAAI,QAAQ,KAAK;AAAA,EAC3B;AAAA,EACA,QAAQ,CAAC,UAAiB;AACxB,YAAQ,IAAI,UAAU,KAAK;AAAA,EAC7B;AAAA,EACA,iBAAiB,CAAC,UAAiB;AACjC,YAAQ,IAAI,mBAAmB,KAAK;AAAA,EACtC;AAAA,EACA,mBAAmB,CAAC,UAAiB;AACnC,YAAQ,IAAI,qBAAqB,KAAK;AAAA,EACxC;AAAA,EACA,kBAAkB,CAAC,UAAiB;AAClC,YAAQ,IAAI,oBAAoB,KAAK;AAAA,EACvC;AACF;AAQA,IAAO,oBAAQ;;;AC/FR,IAAM,SACX,OAAO,cAAc,eACrB,UAAU,SAAS,YAAY,EAAE,QAAQ,KAAK,KAAK;AAG9C,IAAM,uBAAuB,MAAM;AACxC,MAAI,OAAO,WAAW,eAAe,OAAO,cAAc,aAAa;AACrE,WAAO;AAAA,EACT;AACA,SAAO,EACL,kBAAkB,UAClB,UAAU,iBAAiB,KAC1B,sBAAsB,aACpB,UAAU,mBAA8B;AAE/C,GAAG;AAGI,IAAM,gBAAgB,CAAC,eAAe,iBAAiB,cAAc;AASrE,SAAS,oBACd,KACA,OACS;AACT,SAAO;AAAA,IACL,UAAU,MAAM,UAAU,QAAQ;AAAA,IAClC,MAAM,UAAU,SAAS;AAAA,IACzB,MAAM,SAAS,QAAQ;AAAA,IACvB,MAAM,WAAW,UAAU;AAAA,IAC3B,IAAI,YAAY;AAAA,EAClB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AACb;;;ACnBO,IAAM,mBAAN,MAAuB;AAAA,EAAvB;AACL,SAAQ,YAAY,oBAAI,IAAoC;AAK5D;AAAA,cAAK,CAAC,UAAmB,aAAqC;AA9BhE;AA+BI,UAAI,CAAC,KAAK,UAAU,IAAI,QAAQ,GAAG;AACjC,aAAK,UAAU,IAAI,UAAU,oBAAI,IAAI,CAAC;AAAA,MACxC;AACA,iBAAK,UAAU,IAAI,QAAQ,MAA3B,mBAA8B,IAAI;AAAA,IACpC;AAGA;AAAA,eAAM,CAAC,UAAmB,aAAqC;AAtCjE;AAuCI,iBAAK,UAAU,IAAI,QAAQ,MAA3B,mBAA8B,OAAO;AAAA,IACvC;AAEA,SAAQ,OAAO,CAAC,UAAmB,UAAuB;AA1C5D;AA2CI,iBAAK,UAAU,IAAI,QAAQ,MAA3B,mBAA8B,QAAQ,CAAC,aAAa,SAAS,KAAK;AAAA,IACpE;AAEA,SAAQ,kBAAkB,CAAC,UAAmB,UAAuB;AA9CvE;AA+CI,WAAK,KAAK,UAAU,KAAK;AAEzB,YAAM,SAAS,kBAAS,QAAQ;AAChC,UAAI,QAAQ;AACV,cAAM,eAAe;AACrB,YAAI,OAAO,WAAW,UAAU;AAC9B,iCAAU,YAAV,4BAAgC;AAAA,QAClC,OAAO;AACL,iBAAO,KAAK;AAAA,QACd;AAAA,MACF;AAAA,IACF;AAMA;AAAA;AAAA;AAAA;AAAA,kBAAS,CAAC,SAAsB,WAAyB;AACvD,WAAK,OAAO;AACZ,WAAK,SAAS;AAEd,YAAM,gBAAgB,CAAC,UAAiB;AACtC,cAAM,gBAAgB;AACtB,aAAK;AAAA,UACH,oBAAoB,cAAc,KAAK,aAAa;AAAA,UACpD;AAAA,QACF;AAAA,MACF;AAEA,YAAM,cAAc,CAAC,UAAiB;AACpC,cAAM,gBAAgB;AACtB,aAAK;AAAA,UACH,oBAAoB,GAAG,cAAc,GAAG,OAAO,aAAa;AAAA,UAC5D;AAAA,QACF;AAAA,MACF;AAEA,YAAM,kBAAkB,CAAC,UAAiB;AACxC,cAAM,aAAa;AACnB,aAAK;AAAA,UACH,oBAAoB,cAAc,WAAW,MAAM,GAAG,UAAU;AAAA,UAChE;AAAA,QACF;AAAA,MACF;AAEA,YAAM,gBAAgB,CAAC,UAAiB;AACtC,cAAM,aAAa;AACnB,aAAK;AAAA,UACH;AAAA,YACE,GAAG,cAAc,WAAW,MAAM,CAAC;AAAA,YACnC;AAAA,UACF;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAEA,YAAM,oBAAoB,CAAC,UAAiB;AAC1C,cAAM,aAAa;AACnB,aAAK;AAAA,UACH;AAAA,YACE,UAAU,cAAc,WAAW,MAAM,CAAC;AAAA,YAC1C;AAAA,UACF;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAEA,YAAM,oBAAoB,CAAC,UAAiB;AAC1C,aAAK;AAAA,UACH,oBAAoB,gBAAgB,KAAmB;AAAA,UACvD;AAAA,QACF;AAAA,MACF;AAEA,YAAM,cAAc,CAAC,UAAiB;AACpC,cAAM,aAAa;AACnB,cAAM,YAAY,WAAW,SAAS,IAAI,aAAa;AACvD,aAAK,gBAAgB,oBAAoB,WAAW,UAAU,GAAG,KAAK;AAAA,MACxE;AAEA,aAAO,iBAAiB,WAAW,aAAa;AAChD,aAAO,iBAAiB,SAAS,WAAW;AAC5C,aAAO,iBAAiB,aAAa,eAAe;AACpD,aAAO,iBAAiB,WAAW,aAAa;AAChD,aAAO,iBAAiB,YAAY,iBAAiB;AACrD,aAAO,iBAAiB,eAAe,iBAAiB;AACxD,aAAO,iBAAiB,SAAS,aAAa,EAAE,SAAS,KAAK,CAAC;AAE/D,WAAK,UAAU,MAAM;AACnB,eAAO,oBAAoB,WAAW,aAAa;AACnD,eAAO,oBAAoB,SAAS,WAAW;AAC/C,eAAO,oBAAoB,aAAa,eAAe;AACvD,eAAO,oBAAoB,WAAW,aAAa;AACnD,eAAO,oBAAoB,YAAY,iBAAiB;AACxD,eAAO,oBAAoB,eAAe,iBAAiB;AAC3D,eAAO,oBAAoB,SAAS,WAAW;AAC/C,aAAK,SAAS;AACd,aAAK,UAAU;AAAA,MACjB;AAEA,aAAO,KAAK;AAAA,IACd;AAGA;AAAA,kBAAS,MAAY;AAvJvB;AAwJI,iBAAK,YAAL;AAAA,IACF;AAAA;AACF;;;ACxJA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAWA,IAAM,mBAAN,MAAuB;AAAA,EAAvB;AACL,SAAQ,UAAU,IAAI,iBAAiB;AACvC,SAAQ,SAAS,KAAK,QAAQ,OAAO;AAErC,cAA8D,KAAK,QAAQ;AAC3E,eAA+D,KAAK,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO5E,YAAY,UAAmB,UAAkC;AAC/D,UAAM,aAAa,OAAO,UAAU;AACpC,SAAK,QAAQ,GAAG,UAAU,QAAQ;AAClC,eAAW,UAAU,MAAM,KAAK,QAAQ,IAAI,UAAU,QAAQ,CAAC;AAAA,EACjE;AAAA;AAAA,EAGA,cAAoB;AAClB,SAAK,OAAO;AAAA,EACd;AACF;AAtBa,mBAAN;AAAA,EADN,WAAW,EAAE,YAAY,OAAO,CAAC;AAAA,GACrB;AAoCN,SAAS,mBAAmB;AACjC,SAAO,yBAAyB;AAAA,IAC9B;AAAA,MACE,SAAS;AAAA,MACT,OAAO;AAAA,MACP,YAAY,MAAM,MAAM,OAAO,gBAAgB;AAAA,IACjD;AAAA,EACF,CAAC;AACH;","names":[]}
|
package/dist/solid.cjs
CHANGED
|
@@ -248,10 +248,7 @@ var import_solid_js = require("solid-js");
|
|
|
248
248
|
var ShortcutsContext = (0, import_solid_js.createContext)();
|
|
249
249
|
var ShortcutsProvider = (props) => {
|
|
250
250
|
const manager = new ShortcutsManager();
|
|
251
|
-
(0, import_solid_js.
|
|
252
|
-
const dispose = manager.attach();
|
|
253
|
-
(0, import_solid_js.onCleanup)(dispose);
|
|
254
|
-
});
|
|
251
|
+
(0, import_solid_js.onCleanup)(manager.attach());
|
|
255
252
|
return (0, import_solid_js.createComponent)(ShortcutsContext.Provider, {
|
|
256
253
|
value: manager,
|
|
257
254
|
get children() {
|
|
@@ -264,10 +261,8 @@ function useShortcut(shortcut, callback) {
|
|
|
264
261
|
if (!manager) {
|
|
265
262
|
throw new Error("useShortcut must be used within a ShortcutsProvider");
|
|
266
263
|
}
|
|
267
|
-
(
|
|
268
|
-
|
|
269
|
-
(0, import_solid_js.onCleanup)(() => manager.off(shortcut, callback));
|
|
270
|
-
});
|
|
264
|
+
manager.on(shortcut, callback);
|
|
265
|
+
(0, import_solid_js.onCleanup)(() => manager.off(shortcut, callback));
|
|
271
266
|
}
|
|
272
267
|
// Annotate the CommonJS export names for ESM import in node:
|
|
273
268
|
0 && (module.exports = {
|
package/dist/solid.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/solid/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 createComponent,\n createContext,\n onCleanup,\n onMount,\n useContext,\n type FlowComponent,\n} from 'solid-js';\n\nexport type { ShortcutListener } from '@/core/shortcuts-manager';\n\nconst ShortcutsContext = createContext<ShortcutsManager>();\n\n/**\n * Provides global keyboard, mouse and wheel shortcut handling to its\n * subtree via Solid context.\n *\n * @example\n * ```ts\n * import { ShortcutsProvider } from 'browser-shortcuts/solid';\n *\n * createComponent(ShortcutsProvider, { get children() { return App(); } });\n * ```\n */\nexport const ShortcutsProvider: FlowComponent = (props) => {\n const manager = new ShortcutsManager();\n\n onMount(() => {\n const dispose = manager.attach();\n onCleanup(dispose);\n });\n\n return createComponent(ShortcutsContext.Provider, {\n value: manager,\n get children() {\n return props.children;\n },\n });\n};\n\n/**\n * Subscribes `callback` to `shortcut` for as long as the calling component\n * is mounted. Must be used within a {@link ShortcutsProvider}.\n *\n * @example\n * ```ts\n * useShortcut('CTRL+SHIFT+F', () => setFullScreen((prev) => !prev));\n * ```\n */\nexport function useShortcut(shortcut: Keybind, callback: ShortcutListener) {\n const manager = useContext(ShortcutsContext);\n if (!manager) {\n throw new Error('useShortcut must be used within a ShortcutsProvider');\n }\n\n onMount(() => {\n manager.on(shortcut, callback);\n onCleanup(() => manager.off(shortcut, callback));\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":[]}
|
|
1
|
+
{"version":3,"sources":["../src/solid/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 createComponent,\n createContext,\n onCleanup,\n useContext,\n type FlowComponent,\n} from 'solid-js';\n\nexport type { ShortcutListener } from '@/core/shortcuts-manager';\n\nconst ShortcutsContext = createContext<ShortcutsManager>();\n\n/**\n * Provides global keyboard, mouse and wheel shortcut handling to its\n * subtree via Solid context.\n *\n * @example\n * ```ts\n * import { ShortcutsProvider } from 'browser-shortcuts/solid';\n *\n * createComponent(ShortcutsProvider, { get children() { return App(); } });\n * ```\n */\nexport const ShortcutsProvider: FlowComponent = (props) => {\n const manager = new ShortcutsManager();\n onCleanup(manager.attach());\n\n return createComponent(ShortcutsContext.Provider, {\n value: manager,\n get children() {\n return props.children;\n },\n });\n};\n\n/**\n * Subscribes `callback` to `shortcut` for as long as the calling component\n * is mounted. Must be used within a {@link ShortcutsProvider}.\n *\n * @example\n * ```ts\n * useShortcut('CTRL+SHIFT+F', () => setFullScreen((prev) => !prev));\n * ```\n */\nexport function useShortcut(shortcut: Keybind, callback: ShortcutListener) {\n const manager = useContext(ShortcutsContext);\n if (!manager) {\n throw new Error('useShortcut must be used within a ShortcutsProvider');\n }\n\n manager.on(shortcut, callback);\n onCleanup(() => manager.off(shortcut, callback));\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":[]}
|
package/dist/solid.js
CHANGED
|
@@ -221,16 +221,12 @@ import {
|
|
|
221
221
|
createComponent,
|
|
222
222
|
createContext,
|
|
223
223
|
onCleanup,
|
|
224
|
-
onMount,
|
|
225
224
|
useContext
|
|
226
225
|
} from "solid-js";
|
|
227
226
|
var ShortcutsContext = createContext();
|
|
228
227
|
var ShortcutsProvider = (props) => {
|
|
229
228
|
const manager = new ShortcutsManager();
|
|
230
|
-
|
|
231
|
-
const dispose = manager.attach();
|
|
232
|
-
onCleanup(dispose);
|
|
233
|
-
});
|
|
229
|
+
onCleanup(manager.attach());
|
|
234
230
|
return createComponent(ShortcutsContext.Provider, {
|
|
235
231
|
value: manager,
|
|
236
232
|
get children() {
|
|
@@ -243,10 +239,8 @@ function useShortcut(shortcut, callback) {
|
|
|
243
239
|
if (!manager) {
|
|
244
240
|
throw new Error("useShortcut must be used within a ShortcutsProvider");
|
|
245
241
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
onCleanup(() => manager.off(shortcut, callback));
|
|
249
|
-
});
|
|
242
|
+
manager.on(shortcut, callback);
|
|
243
|
+
onCleanup(() => manager.off(shortcut, callback));
|
|
250
244
|
}
|
|
251
245
|
export {
|
|
252
246
|
ShortcutsProvider,
|
package/dist/solid.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config/shortcuts.ts","../src/utils/misc.ts","../src/core/shortcuts-manager.ts","../src/solid/index.ts"],"sourcesContent":["/**\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","import type { Keybind } from '@/config';\nimport { ShortcutsManager, type ShortcutListener } from '@/core/shortcuts-manager';\nimport {\n createComponent,\n createContext,\n onCleanup,\n onMount,\n useContext,\n type FlowComponent,\n} from 'solid-js';\n\nexport type { ShortcutListener } from '@/core/shortcuts-manager';\n\nconst ShortcutsContext = createContext<ShortcutsManager>();\n\n/**\n * Provides global keyboard, mouse and wheel shortcut handling to its\n * subtree via Solid context.\n *\n * @example\n * ```ts\n * import { ShortcutsProvider } from 'browser-shortcuts/solid';\n *\n * createComponent(ShortcutsProvider, { get children() { return App(); } });\n * ```\n */\nexport const ShortcutsProvider: FlowComponent = (props) => {\n const manager = new ShortcutsManager();\n\n onMount(() => {\n const dispose = manager.attach();\n onCleanup(dispose);\n });\n\n return createComponent(ShortcutsContext.Provider, {\n value: manager,\n get children() {\n return props.children;\n },\n });\n};\n\n/**\n * Subscribes `callback` to `shortcut` for as long as the calling component\n * is mounted. Must be used within a {@link ShortcutsProvider}.\n *\n * @example\n * ```ts\n * useShortcut('CTRL+SHIFT+F', () => setFullScreen((prev) => !prev));\n * ```\n */\nexport function useShortcut(shortcut: Keybind, callback: ShortcutListener) {\n const manager = useContext(ShortcutsContext);\n if (!manager) {\n throw new Error('useShortcut must be used within a ShortcutsProvider');\n }\n\n onMount(() => {\n manager.on(shortcut, callback);\n onCleanup(() => manager.off(shortcut, callback));\n });\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/config/shortcuts.ts","../src/utils/misc.ts","../src/core/shortcuts-manager.ts","../src/solid/index.ts"],"sourcesContent":["/**\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","import type { Keybind } from '@/config';\nimport { ShortcutsManager, type ShortcutListener } from '@/core/shortcuts-manager';\nimport {\n createComponent,\n createContext,\n onCleanup,\n useContext,\n type FlowComponent,\n} from 'solid-js';\n\nexport type { ShortcutListener } from '@/core/shortcuts-manager';\n\nconst ShortcutsContext = createContext<ShortcutsManager>();\n\n/**\n * Provides global keyboard, mouse and wheel shortcut handling to its\n * subtree via Solid context.\n *\n * @example\n * ```ts\n * import { ShortcutsProvider } from 'browser-shortcuts/solid';\n *\n * createComponent(ShortcutsProvider, { get children() { return App(); } });\n * ```\n */\nexport const ShortcutsProvider: FlowComponent = (props) => {\n const manager = new ShortcutsManager();\n onCleanup(manager.attach());\n\n return createComponent(ShortcutsContext.Provider, {\n value: manager,\n get children() {\n return props.children;\n },\n });\n};\n\n/**\n * Subscribes `callback` to `shortcut` for as long as the calling component\n * is mounted. Must be used within a {@link ShortcutsProvider}.\n *\n * @example\n * ```ts\n * useShortcut('CTRL+SHIFT+F', () => setFullScreen((prev) => !prev));\n * ```\n */\nexport function useShortcut(shortcut: Keybind, callback: ShortcutListener) {\n const manager = useContext(ShortcutsContext);\n if (!manager) {\n throw new Error('useShortcut must be used within a ShortcutsProvider');\n }\n\n manager.on(shortcut, callback);\n onCleanup(() => manager.off(shortcut, callback));\n}\n"],"mappings":";AAUA,IAAM,WAAW;AAAA,EACf,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,UAAU;AAAA,EACV,SAAS;AAAA,EACT,UAAU;AAAA,EACV,SAAS;AAAA,EACT,UAAU;AAAA,EACV,SAAS;AAAA,EACT,UAAU;AAAA,EACV,SAAS;AAAA,EACT,oBAAoB;AAAA,EACpB,aAAa;AAAA,EACb,eAAe;AAAA,EACf,cAAc;AAChB;AAOO,IAAM,YAAY;AAAA,EACvB,kBAAkB,CAAC,UAAiB;AAClC,YAAQ,IAAI,oBAAoB,KAAK;AAAA,EACvC;AAAA,EACA,YAAY,CAAC,UAAiB;AAC5B,YAAQ,IAAI,cAAc,KAAK;AAAA,EACjC;AAAA,EACA,YAAY,CAAC,UAAiB;AAC5B,YAAQ,IAAI,cAAc,KAAK;AAAA,EACjC;AAAA,EACA,cAAc,CAAC,UAAiB;AAC9B,YAAQ,IAAI,gBAAgB,KAAK;AAAA,EACnC;AAAA,EACA,MAAM,CAAC,UAAiB;AACtB,YAAQ,IAAI,QAAQ,KAAK;AAAA,EAC3B;AAAA,EACA,MAAM,CAAC,UAAiB;AACtB,YAAQ,IAAI,QAAQ,KAAK;AAAA,EAC3B;AAAA,EACA,QAAQ,CAAC,UAAiB;AACxB,YAAQ,IAAI,UAAU,KAAK;AAAA,EAC7B;AAAA,EACA,WAAW,CAAC,UAAiB;AAC3B,YAAQ,IAAI,aAAa,KAAK;AAAA,EAChC;AAAA,EACA,MAAM,CAAC,UAAiB;AACtB,YAAQ,IAAI,QAAQ,KAAK;AAAA,EAC3B;AAAA,EACA,OAAO,CAAC,UAAiB;AACvB,YAAQ,IAAI,SAAS,KAAK;AAAA,EAC5B;AAAA,EACA,MAAM,CAAC,UAAiB;AACtB,YAAQ,IAAI,QAAQ,KAAK;AAAA,EAC3B;AAAA,EACA,QAAQ,CAAC,UAAiB;AACxB,YAAQ,IAAI,UAAU,KAAK;AAAA,EAC7B;AAAA,EACA,iBAAiB,CAAC,UAAiB;AACjC,YAAQ,IAAI,mBAAmB,KAAK;AAAA,EACtC;AAAA,EACA,mBAAmB,CAAC,UAAiB;AACnC,YAAQ,IAAI,qBAAqB,KAAK;AAAA,EACxC;AAAA,EACA,kBAAkB,CAAC,UAAiB;AAClC,YAAQ,IAAI,oBAAoB,KAAK;AAAA,EACvC;AACF;AAQA,IAAO,oBAAQ;;;AC/FR,IAAM,SACX,OAAO,cAAc,eACrB,UAAU,SAAS,YAAY,EAAE,QAAQ,KAAK,KAAK;AAG9C,IAAM,uBAAuB,MAAM;AACxC,MAAI,OAAO,WAAW,eAAe,OAAO,cAAc,aAAa;AACrE,WAAO;AAAA,EACT;AACA,SAAO,EACL,kBAAkB,UAClB,UAAU,iBAAiB,KAC1B,sBAAsB,aACpB,UAAU,mBAA8B;AAE/C,GAAG;AAGI,IAAM,gBAAgB,CAAC,eAAe,iBAAiB,cAAc;AASrE,SAAS,oBACd,KACA,OACS;AACT,SAAO;AAAA,IACL,UAAU,MAAM,UAAU,QAAQ;AAAA,IAClC,MAAM,UAAU,SAAS;AAAA,IACzB,MAAM,SAAS,QAAQ;AAAA,IACvB,MAAM,WAAW,UAAU;AAAA,IAC3B,IAAI,YAAY;AAAA,EAClB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AACb;;;ACnBO,IAAM,mBAAN,MAAuB;AAAA,EAAvB;AACL,SAAQ,YAAY,oBAAI,IAAoC;AAK5D;AAAA,cAAK,CAAC,UAAmB,aAAqC;AA9BhE;AA+BI,UAAI,CAAC,KAAK,UAAU,IAAI,QAAQ,GAAG;AACjC,aAAK,UAAU,IAAI,UAAU,oBAAI,IAAI,CAAC;AAAA,MACxC;AACA,iBAAK,UAAU,IAAI,QAAQ,MAA3B,mBAA8B,IAAI;AAAA,IACpC;AAGA;AAAA,eAAM,CAAC,UAAmB,aAAqC;AAtCjE;AAuCI,iBAAK,UAAU,IAAI,QAAQ,MAA3B,mBAA8B,OAAO;AAAA,IACvC;AAEA,SAAQ,OAAO,CAAC,UAAmB,UAAuB;AA1C5D;AA2CI,iBAAK,UAAU,IAAI,QAAQ,MAA3B,mBAA8B,QAAQ,CAAC,aAAa,SAAS,KAAK;AAAA,IACpE;AAEA,SAAQ,kBAAkB,CAAC,UAAmB,UAAuB;AA9CvE;AA+CI,WAAK,KAAK,UAAU,KAAK;AAEzB,YAAM,SAAS,kBAAS,QAAQ;AAChC,UAAI,QAAQ;AACV,cAAM,eAAe;AACrB,YAAI,OAAO,WAAW,UAAU;AAC9B,iCAAU,YAAV,4BAAgC;AAAA,QAClC,OAAO;AACL,iBAAO,KAAK;AAAA,QACd;AAAA,MACF;AAAA,IACF;AAMA;AAAA;AAAA;AAAA;AAAA,kBAAS,CAAC,SAAsB,WAAyB;AACvD,WAAK,OAAO;AACZ,WAAK,SAAS;AAEd,YAAM,gBAAgB,CAAC,UAAiB;AACtC,cAAM,gBAAgB;AACtB,aAAK;AAAA,UACH,oBAAoB,cAAc,KAAK,aAAa;AAAA,UACpD;AAAA,QACF;AAAA,MACF;AAEA,YAAM,cAAc,CAAC,UAAiB;AACpC,cAAM,gBAAgB;AACtB,aAAK;AAAA,UACH,oBAAoB,GAAG,cAAc,GAAG,OAAO,aAAa;AAAA,UAC5D;AAAA,QACF;AAAA,MACF;AAEA,YAAM,kBAAkB,CAAC,UAAiB;AACxC,cAAM,aAAa;AACnB,aAAK;AAAA,UACH,oBAAoB,cAAc,WAAW,MAAM,GAAG,UAAU;AAAA,UAChE;AAAA,QACF;AAAA,MACF;AAEA,YAAM,gBAAgB,CAAC,UAAiB;AACtC,cAAM,aAAa;AACnB,aAAK;AAAA,UACH;AAAA,YACE,GAAG,cAAc,WAAW,MAAM,CAAC;AAAA,YACnC;AAAA,UACF;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAEA,YAAM,oBAAoB,CAAC,UAAiB;AAC1C,cAAM,aAAa;AACnB,aAAK;AAAA,UACH;AAAA,YACE,UAAU,cAAc,WAAW,MAAM,CAAC;AAAA,YAC1C;AAAA,UACF;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAEA,YAAM,oBAAoB,CAAC,UAAiB;AAC1C,aAAK;AAAA,UACH,oBAAoB,gBAAgB,KAAmB;AAAA,UACvD;AAAA,QACF;AAAA,MACF;AAEA,YAAM,cAAc,CAAC,UAAiB;AACpC,cAAM,aAAa;AACnB,cAAM,YAAY,WAAW,SAAS,IAAI,aAAa;AACvD,aAAK,gBAAgB,oBAAoB,WAAW,UAAU,GAAG,KAAK;AAAA,MACxE;AAEA,aAAO,iBAAiB,WAAW,aAAa;AAChD,aAAO,iBAAiB,SAAS,WAAW;AAC5C,aAAO,iBAAiB,aAAa,eAAe;AACpD,aAAO,iBAAiB,WAAW,aAAa;AAChD,aAAO,iBAAiB,YAAY,iBAAiB;AACrD,aAAO,iBAAiB,eAAe,iBAAiB;AACxD,aAAO,iBAAiB,SAAS,aAAa,EAAE,SAAS,KAAK,CAAC;AAE/D,WAAK,UAAU,MAAM;AACnB,eAAO,oBAAoB,WAAW,aAAa;AACnD,eAAO,oBAAoB,SAAS,WAAW;AAC/C,eAAO,oBAAoB,aAAa,eAAe;AACvD,eAAO,oBAAoB,WAAW,aAAa;AACnD,eAAO,oBAAoB,YAAY,iBAAiB;AACxD,eAAO,oBAAoB,eAAe,iBAAiB;AAC3D,eAAO,oBAAoB,SAAS,WAAW;AAC/C,aAAK,SAAS;AACd,aAAK,UAAU;AAAA,MACjB;AAEA,aAAO,KAAK;AAAA,IACd;AAGA;AAAA,kBAAS,MAAY;AAvJvB;AAwJI,iBAAK,YAAL;AAAA,IACF;AAAA;AACF;;;ACxJA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAIP,IAAM,mBAAmB,cAAgC;AAalD,IAAM,oBAAmC,CAAC,UAAU;AACzD,QAAM,UAAU,IAAI,iBAAiB;AACrC,YAAU,QAAQ,OAAO,CAAC;AAE1B,SAAO,gBAAgB,iBAAiB,UAAU;AAAA,IAChD,OAAO;AAAA,IACP,IAAI,WAAW;AACb,aAAO,MAAM;AAAA,IACf;AAAA,EACF,CAAC;AACH;AAWO,SAAS,YAAY,UAAmB,UAA4B;AACzE,QAAM,UAAU,WAAW,gBAAgB;AAC3C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACvE;AAEA,UAAQ,GAAG,UAAU,QAAQ;AAC7B,YAAU,MAAM,QAAQ,IAAI,UAAU,QAAQ,CAAC;AACjD;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browser-shortcuts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Declarative keyboard, mouse and wheel shortcuts for React, Preact, Vue, Svelte, Solid and Angular.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Antonio Okoro",
|
|
@@ -68,6 +68,8 @@
|
|
|
68
68
|
"build": "tsup",
|
|
69
69
|
"dev": "tsup --watch",
|
|
70
70
|
"lint": "eslint src",
|
|
71
|
+
"test": "vitest run",
|
|
72
|
+
"test:watch": "vitest",
|
|
71
73
|
"prepublishOnly": "npm run build"
|
|
72
74
|
},
|
|
73
75
|
"peerDependencies": {
|
|
@@ -80,18 +82,38 @@
|
|
|
80
82
|
"vue": "^3.3.0"
|
|
81
83
|
},
|
|
82
84
|
"peerDependenciesMeta": {
|
|
83
|
-
"@angular/core": {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
"
|
|
85
|
+
"@angular/core": {
|
|
86
|
+
"optional": true
|
|
87
|
+
},
|
|
88
|
+
"preact": {
|
|
89
|
+
"optional": true
|
|
90
|
+
},
|
|
91
|
+
"react": {
|
|
92
|
+
"optional": true
|
|
93
|
+
},
|
|
94
|
+
"react-dom": {
|
|
95
|
+
"optional": true
|
|
96
|
+
},
|
|
97
|
+
"solid-js": {
|
|
98
|
+
"optional": true
|
|
99
|
+
},
|
|
100
|
+
"svelte": {
|
|
101
|
+
"optional": true
|
|
102
|
+
},
|
|
103
|
+
"vue": {
|
|
104
|
+
"optional": true
|
|
105
|
+
}
|
|
90
106
|
},
|
|
91
107
|
"devDependencies": {
|
|
108
|
+
"@angular/compiler": "^19.2.25",
|
|
92
109
|
"@angular/core": "^19.0.0",
|
|
110
|
+
"@angular/platform-browser": "^19.2.25",
|
|
111
|
+
"@angular/platform-browser-dynamic": "^19.2.25",
|
|
112
|
+
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
93
113
|
"@types/react": "^19",
|
|
94
114
|
"@types/react-dom": "^19",
|
|
115
|
+
"@vue/test-utils": "^2.5.1",
|
|
116
|
+
"jsdom": "^29.1.1",
|
|
95
117
|
"preact": "^10.24.3",
|
|
96
118
|
"react": "19.2.8",
|
|
97
119
|
"react-dom": "19.2.8",
|
|
@@ -99,6 +121,8 @@
|
|
|
99
121
|
"svelte": "^5.0.0",
|
|
100
122
|
"tsup": "^8.5.1",
|
|
101
123
|
"typescript": "^5",
|
|
102
|
-
"
|
|
124
|
+
"vitest": "^5.0.1",
|
|
125
|
+
"vue": "^3.5.0",
|
|
126
|
+
"zone.js": "^0.15.1"
|
|
103
127
|
}
|
|
104
128
|
}
|