pure-shortcut 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +212 -0
- package/dist/Shortcut.d.ts +23 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +340 -0
- package/dist/index.umd.cjs +22 -0
- package/dist/tsShortcut.d.ts +63 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 pure-shortcut
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# pure-shortcut
|
|
2
|
+
|
|
3
|
+
[npm version](https://www.npmjs.com/package/pure-shortcut)
|
|
4
|
+
[npm downloads](https://www.npmjs.com/package/pure-shortcut)
|
|
5
|
+
[license](LICENSE)
|
|
6
|
+
|
|
7
|
+
> **Elegant, customizable keyboard shortcut provider for React components (with pure JS support).**
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## π Table of Contents
|
|
12
|
+
|
|
13
|
+
- [Project Description](#project-description)
|
|
14
|
+
- [Demo](#demo)
|
|
15
|
+
- [Installation](#installation)
|
|
16
|
+
- [Dependencies](#dependencies)
|
|
17
|
+
- [Quick Start](#quick-start)
|
|
18
|
+
- [API Reference](#api-reference)
|
|
19
|
+
- [Advanced Usage](#advanced-usage)
|
|
20
|
+
- [Global Shortcuts with JavaScript](#global-shortcuts-with-javascript)
|
|
21
|
+
- [Events System](#events-system)
|
|
22
|
+
- [Examples](#examples)
|
|
23
|
+
- [Contributing & Feedback](#contributing--feedback)
|
|
24
|
+
- [License](#license)
|
|
25
|
+
- [Author](#author)
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## π Project Description
|
|
30
|
+
|
|
31
|
+
**pure-shortcut** is a React component and TypeScript utility enabling easy creation and management of global or scoped keyboard shortcuts (hotkeys). Designed for accessibility and productivity, it lets you assign one or more key combinations to any UI partβideal for commands, CRUD, navigation, and dashboards.
|
|
32
|
+
|
|
33
|
+
- Supports both React (via the Shortcut component) and vanilla JS/TS (addShortcuts/removeShortcuts).
|
|
34
|
+
- Ignores input, textarea, and contentEditable by default to avoid interfering with user text input.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## π Dependencies
|
|
39
|
+
|
|
40
|
+
- **React** (version 18+)
|
|
41
|
+
- **TailwindCSS** (optional; only if you utilize `className` for styling)
|
|
42
|
+
- **@iconify/react** (optional; only if your children require these icons)
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## π Quick Start
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import React from "react";
|
|
50
|
+
import Shortcut from "pure-shortcut";
|
|
51
|
+
|
|
52
|
+
function App() {
|
|
53
|
+
return (
|
|
54
|
+
<Shortcut
|
|
55
|
+
onShortPressed={[
|
|
56
|
+
{
|
|
57
|
+
key: "s",
|
|
58
|
+
ctrlKey: true,
|
|
59
|
+
onPress: (e) => {
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
alert("Saved!");
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
key: "x",
|
|
66
|
+
altKey: true,
|
|
67
|
+
onPress: (e) => {
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
alert("Detected ALT+X combination");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
]}
|
|
73
|
+
>
|
|
74
|
+
<input placeholder="Press Ctrl+S to save or Alt+X for another action" />
|
|
75
|
+
</Shortcut>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default App;
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## π οΈ API Reference
|
|
85
|
+
|
|
86
|
+
### `<Shortcut />` Component
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
| Prop | Type | Required | Description |
|
|
90
|
+
| ---------------- | ---------------------- | -------- | ----------------------------------------------------------- |
|
|
91
|
+
| `children` | `ReactNode` | Yes | The React content to wrap and enable shortcut listening on. |
|
|
92
|
+
| `onShortPressed` | `OnShortPressedItem[]` | Yes | Array of shortcut definitions. See below for details. |
|
|
93
|
+
| `className` | `string` | No | Optional CSS class for the wrapping `<div>`. |
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
#### **OnShortPressedItem**
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
{
|
|
100
|
+
key: string; // The key to listen for (e.g., "s", "Enter", "ArrowUp")
|
|
101
|
+
ctrlKey?: boolean; // Requires Ctrl key (optional)
|
|
102
|
+
shiftKey?: boolean; // Requires Shift key (optional)
|
|
103
|
+
altKey?: boolean; // Requires Alt key (optional)
|
|
104
|
+
metaKey?: boolean; // Requires Meta (β on Mac, Windows key on PC) (optional)
|
|
105
|
+
onPress: (e: KeyboardEvent) => void; // Callback executed when the shortcut triggers
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
##### Example:
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
onShortPressed={[
|
|
113
|
+
{ key: "s", ctrlKey: true, onPress: (e) => { e.preventDefault(); alert("Saved!"); } }
|
|
114
|
+
]}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## β‘οΈ Advanced Usage
|
|
120
|
+
|
|
121
|
+
### Global Shortcuts with JavaScript (Utility API)
|
|
122
|
+
|
|
123
|
+
You can also use pure JavaScript/TypeScriptβfor instance, in non-React contexts or for global shortcuts:
|
|
124
|
+
|
|
125
|
+
```js
|
|
126
|
+
import { addShortcuts, removeShortcuts } from "pure-shortcut";
|
|
127
|
+
|
|
128
|
+
const handler = (e) => { e.preventDefault(); alert("Saved!"); };
|
|
129
|
+
|
|
130
|
+
addShortcuts([
|
|
131
|
+
{ key: "s", ctrlKey: true, onPress: handler }
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
// Later, when you want to clean up:
|
|
135
|
+
removeShortcuts();
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
You can also use in HTML directly:
|
|
139
|
+
|
|
140
|
+
```html
|
|
141
|
+
<script src="Shortcut.js"></script>
|
|
142
|
+
<script>
|
|
143
|
+
addShortcuts([
|
|
144
|
+
{ key: "n", ctrlKey: true, altKey: true, onPress: function(e) { alert('Ctrl+Alt+N!'); } }
|
|
145
|
+
]);
|
|
146
|
+
</script>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## π― Events System
|
|
152
|
+
|
|
153
|
+
- **Key Events**: All registered shortcuts receive the native `KeyboardEvent` object.
|
|
154
|
+
- **Input Safety**: Shortcuts are ignored when typing in `<input>`, `<textarea>`, or content-editable elements by default (for UX).
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## π‘ Examples
|
|
159
|
+
|
|
160
|
+
### Basic Example
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
<Shortcut
|
|
164
|
+
onShortPressed={[
|
|
165
|
+
{ key: "s", ctrlKey: true, onPress: (e) => { e.preventDefault(); alert("Saved!"); } }
|
|
166
|
+
]}
|
|
167
|
+
>
|
|
168
|
+
<input />
|
|
169
|
+
</Shortcut>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Styled Example
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
import Shortcut from "pure-shortcut";
|
|
176
|
+
|
|
177
|
+
function Demo() {
|
|
178
|
+
return (
|
|
179
|
+
<Shortcut
|
|
180
|
+
className="bg-gray-100 p-4 rounded"
|
|
181
|
+
onShortPressed={[
|
|
182
|
+
{ key: "s", ctrlKey: true, onPress: (e) => { e.preventDefault(); alert("Saved"); } },
|
|
183
|
+
{ key: "u", shiftKey: true, onPress: (e) => { e.preventDefault(); alert("Shift+U"); } }
|
|
184
|
+
]}
|
|
185
|
+
>
|
|
186
|
+
<input placeholder="Try the shortcuts (Ctrl+S, Shift+U)" />
|
|
187
|
+
</Shortcut>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## π Contributing / Feedback
|
|
195
|
+
|
|
196
|
+
Feedback, issues, and PRs are welcome!
|
|
197
|
+
Please open an [issue](https://github.com/your-repo/issues) or submit a pull request.
|
|
198
|
+
|
|
199
|
+
- To contribute, fork the repo, create your feature branch, and submit a pull request.
|
|
200
|
+
- For questions or feature requests, contact the author below.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## π License
|
|
205
|
+
|
|
206
|
+
Distributed under the [MIT License](./LICENSE).
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## π€ Author
|
|
211
|
+
|
|
212
|
+
Developed and maintained by [Antonio Benavides H.](mailto:your-email@example.com)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { default as React, ReactNode } from 'react';
|
|
2
|
+
export interface OnShortPressedItem {
|
|
3
|
+
key: string;
|
|
4
|
+
ctrlKey?: boolean;
|
|
5
|
+
shiftKey?: boolean;
|
|
6
|
+
altKey?: boolean;
|
|
7
|
+
metaKey?: boolean;
|
|
8
|
+
onPress: (e: KeyboardEvent) => void;
|
|
9
|
+
}
|
|
10
|
+
export interface ShortcutProps {
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
onShortPressed: OnShortPressedItem[];
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Shortcut: Component to handle custom keyboard shortcuts.
|
|
17
|
+
* Improvements:
|
|
18
|
+
* - Uses ref to always have freshest onShortPressed handler.
|
|
19
|
+
* - Case-insensitive key support.
|
|
20
|
+
* - Ignores events from input, textarea, or contentEditable by default for better UX.
|
|
21
|
+
*/
|
|
22
|
+
declare const Shortcut: React.FC<ShortcutProps>;
|
|
23
|
+
export default Shortcut;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import ne, { useRef as ae, useEffect as F } from "react";
|
|
2
|
+
var k = { exports: {} }, y = {};
|
|
3
|
+
/**
|
|
4
|
+
* @license React
|
|
5
|
+
* react-jsx-runtime.production.js
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
8
|
+
*
|
|
9
|
+
* This source code is licensed under the MIT license found in the
|
|
10
|
+
* LICENSE file in the root directory of this source tree.
|
|
11
|
+
*/
|
|
12
|
+
var K;
|
|
13
|
+
function oe() {
|
|
14
|
+
if (K) return y;
|
|
15
|
+
K = 1;
|
|
16
|
+
var t = Symbol.for("react.transitional.element"), c = Symbol.for("react.fragment");
|
|
17
|
+
function f(d, l, u) {
|
|
18
|
+
var a = null;
|
|
19
|
+
if (u !== void 0 && (a = "" + u), l.key !== void 0 && (a = "" + l.key), "key" in l) {
|
|
20
|
+
u = {};
|
|
21
|
+
for (var i in l)
|
|
22
|
+
i !== "key" && (u[i] = l[i]);
|
|
23
|
+
} else u = l;
|
|
24
|
+
return l = u.ref, {
|
|
25
|
+
$$typeof: t,
|
|
26
|
+
type: d,
|
|
27
|
+
key: a,
|
|
28
|
+
ref: l !== void 0 ? l : null,
|
|
29
|
+
props: u
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return y.Fragment = c, y.jsx = f, y.jsxs = f, y;
|
|
33
|
+
}
|
|
34
|
+
var R = {};
|
|
35
|
+
/**
|
|
36
|
+
* @license React
|
|
37
|
+
* react-jsx-runtime.development.js
|
|
38
|
+
*
|
|
39
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
40
|
+
*
|
|
41
|
+
* This source code is licensed under the MIT license found in the
|
|
42
|
+
* LICENSE file in the root directory of this source tree.
|
|
43
|
+
*/
|
|
44
|
+
var D;
|
|
45
|
+
function se() {
|
|
46
|
+
return D || (D = 1, process.env.NODE_ENV !== "production" && (function() {
|
|
47
|
+
function t(e) {
|
|
48
|
+
if (e == null) return null;
|
|
49
|
+
if (typeof e == "function")
|
|
50
|
+
return e.$$typeof === ee ? null : e.displayName || e.name || null;
|
|
51
|
+
if (typeof e == "string") return e;
|
|
52
|
+
switch (e) {
|
|
53
|
+
case h:
|
|
54
|
+
return "Fragment";
|
|
55
|
+
case V:
|
|
56
|
+
return "Profiler";
|
|
57
|
+
case J:
|
|
58
|
+
return "StrictMode";
|
|
59
|
+
case X:
|
|
60
|
+
return "Suspense";
|
|
61
|
+
case B:
|
|
62
|
+
return "SuspenseList";
|
|
63
|
+
case Q:
|
|
64
|
+
return "Activity";
|
|
65
|
+
}
|
|
66
|
+
if (typeof e == "object")
|
|
67
|
+
switch (typeof e.tag == "number" && console.error(
|
|
68
|
+
"Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."
|
|
69
|
+
), e.$$typeof) {
|
|
70
|
+
case q:
|
|
71
|
+
return "Portal";
|
|
72
|
+
case G:
|
|
73
|
+
return e.displayName || "Context";
|
|
74
|
+
case z:
|
|
75
|
+
return (e._context.displayName || "Context") + ".Consumer";
|
|
76
|
+
case H:
|
|
77
|
+
var r = e.render;
|
|
78
|
+
return e = e.displayName, e || (e = r.displayName || r.name || "", e = e !== "" ? "ForwardRef(" + e + ")" : "ForwardRef"), e;
|
|
79
|
+
case Z:
|
|
80
|
+
return r = e.displayName || null, r !== null ? r : t(e.type) || "Memo";
|
|
81
|
+
case A:
|
|
82
|
+
r = e._payload, e = e._init;
|
|
83
|
+
try {
|
|
84
|
+
return t(e(r));
|
|
85
|
+
} catch {
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function c(e) {
|
|
91
|
+
return "" + e;
|
|
92
|
+
}
|
|
93
|
+
function f(e) {
|
|
94
|
+
try {
|
|
95
|
+
c(e);
|
|
96
|
+
var r = !1;
|
|
97
|
+
} catch {
|
|
98
|
+
r = !0;
|
|
99
|
+
}
|
|
100
|
+
if (r) {
|
|
101
|
+
r = console;
|
|
102
|
+
var n = r.error, o = typeof Symbol == "function" && Symbol.toStringTag && e[Symbol.toStringTag] || e.constructor.name || "Object";
|
|
103
|
+
return n.call(
|
|
104
|
+
r,
|
|
105
|
+
"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
|
|
106
|
+
o
|
|
107
|
+
), c(e);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function d(e) {
|
|
111
|
+
if (e === h) return "<>";
|
|
112
|
+
if (typeof e == "object" && e !== null && e.$$typeof === A)
|
|
113
|
+
return "<...>";
|
|
114
|
+
try {
|
|
115
|
+
var r = t(e);
|
|
116
|
+
return r ? "<" + r + ">" : "<...>";
|
|
117
|
+
} catch {
|
|
118
|
+
return "<...>";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function l() {
|
|
122
|
+
var e = O.A;
|
|
123
|
+
return e === null ? null : e.getOwner();
|
|
124
|
+
}
|
|
125
|
+
function u() {
|
|
126
|
+
return Error("react-stack-top-frame");
|
|
127
|
+
}
|
|
128
|
+
function a(e) {
|
|
129
|
+
if (N.call(e, "key")) {
|
|
130
|
+
var r = Object.getOwnPropertyDescriptor(e, "key").get;
|
|
131
|
+
if (r && r.isReactWarning) return !1;
|
|
132
|
+
}
|
|
133
|
+
return e.key !== void 0;
|
|
134
|
+
}
|
|
135
|
+
function i(e, r) {
|
|
136
|
+
function n() {
|
|
137
|
+
L || (L = !0, console.error(
|
|
138
|
+
"%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",
|
|
139
|
+
r
|
|
140
|
+
));
|
|
141
|
+
}
|
|
142
|
+
n.isReactWarning = !0, Object.defineProperty(e, "key", {
|
|
143
|
+
get: n,
|
|
144
|
+
configurable: !0
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function m() {
|
|
148
|
+
var e = t(this.type);
|
|
149
|
+
return Y[e] || (Y[e] = !0, console.error(
|
|
150
|
+
"Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release."
|
|
151
|
+
)), e = this.props.ref, e !== void 0 ? e : null;
|
|
152
|
+
}
|
|
153
|
+
function v(e, r, n, o, T, x) {
|
|
154
|
+
var s = n.ref;
|
|
155
|
+
return e = {
|
|
156
|
+
$$typeof: C,
|
|
157
|
+
type: e,
|
|
158
|
+
key: r,
|
|
159
|
+
props: n,
|
|
160
|
+
_owner: o
|
|
161
|
+
}, (s !== void 0 ? s : null) !== null ? Object.defineProperty(e, "ref", {
|
|
162
|
+
enumerable: !1,
|
|
163
|
+
get: m
|
|
164
|
+
}) : Object.defineProperty(e, "ref", { enumerable: !1, value: null }), e._store = {}, Object.defineProperty(e._store, "validated", {
|
|
165
|
+
configurable: !1,
|
|
166
|
+
enumerable: !1,
|
|
167
|
+
writable: !0,
|
|
168
|
+
value: 0
|
|
169
|
+
}), Object.defineProperty(e, "_debugInfo", {
|
|
170
|
+
configurable: !1,
|
|
171
|
+
enumerable: !1,
|
|
172
|
+
writable: !0,
|
|
173
|
+
value: null
|
|
174
|
+
}), Object.defineProperty(e, "_debugStack", {
|
|
175
|
+
configurable: !1,
|
|
176
|
+
enumerable: !1,
|
|
177
|
+
writable: !0,
|
|
178
|
+
value: T
|
|
179
|
+
}), Object.defineProperty(e, "_debugTask", {
|
|
180
|
+
configurable: !1,
|
|
181
|
+
enumerable: !1,
|
|
182
|
+
writable: !0,
|
|
183
|
+
value: x
|
|
184
|
+
}), Object.freeze && (Object.freeze(e.props), Object.freeze(e)), e;
|
|
185
|
+
}
|
|
186
|
+
function b(e, r, n, o, T, x) {
|
|
187
|
+
var s = r.children;
|
|
188
|
+
if (s !== void 0)
|
|
189
|
+
if (o)
|
|
190
|
+
if (re(s)) {
|
|
191
|
+
for (o = 0; o < s.length; o++)
|
|
192
|
+
p(s[o]);
|
|
193
|
+
Object.freeze && Object.freeze(s);
|
|
194
|
+
} else
|
|
195
|
+
console.error(
|
|
196
|
+
"React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead."
|
|
197
|
+
);
|
|
198
|
+
else p(s);
|
|
199
|
+
if (N.call(r, "key")) {
|
|
200
|
+
s = t(e);
|
|
201
|
+
var E = Object.keys(r).filter(function(te) {
|
|
202
|
+
return te !== "key";
|
|
203
|
+
});
|
|
204
|
+
o = 0 < E.length ? "{key: someKey, " + E.join(": ..., ") + ": ...}" : "{key: someKey}", $[s + o] || (E = 0 < E.length ? "{" + E.join(": ..., ") + ": ...}" : "{}", console.error(
|
|
205
|
+
`A props object containing a "key" prop is being spread into JSX:
|
|
206
|
+
let props = %s;
|
|
207
|
+
<%s {...props} />
|
|
208
|
+
React keys must be passed directly to JSX without using spread:
|
|
209
|
+
let props = %s;
|
|
210
|
+
<%s key={someKey} {...props} />`,
|
|
211
|
+
o,
|
|
212
|
+
s,
|
|
213
|
+
E,
|
|
214
|
+
s
|
|
215
|
+
), $[s + o] = !0);
|
|
216
|
+
}
|
|
217
|
+
if (s = null, n !== void 0 && (f(n), s = "" + n), a(r) && (f(r.key), s = "" + r.key), "key" in r) {
|
|
218
|
+
n = {};
|
|
219
|
+
for (var P in r)
|
|
220
|
+
P !== "key" && (n[P] = r[P]);
|
|
221
|
+
} else n = r;
|
|
222
|
+
return s && i(
|
|
223
|
+
n,
|
|
224
|
+
typeof e == "function" ? e.displayName || e.name || "Unknown" : e
|
|
225
|
+
), v(
|
|
226
|
+
e,
|
|
227
|
+
s,
|
|
228
|
+
n,
|
|
229
|
+
l(),
|
|
230
|
+
T,
|
|
231
|
+
x
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
function p(e) {
|
|
235
|
+
w(e) ? e._store && (e._store.validated = 1) : typeof e == "object" && e !== null && e.$$typeof === A && (e._payload.status === "fulfilled" ? w(e._payload.value) && e._payload.value._store && (e._payload.value._store.validated = 1) : e._store && (e._store.validated = 1));
|
|
236
|
+
}
|
|
237
|
+
function w(e) {
|
|
238
|
+
return typeof e == "object" && e !== null && e.$$typeof === C;
|
|
239
|
+
}
|
|
240
|
+
var _ = ne, C = Symbol.for("react.transitional.element"), q = Symbol.for("react.portal"), h = Symbol.for("react.fragment"), J = Symbol.for("react.strict_mode"), V = Symbol.for("react.profiler"), z = Symbol.for("react.consumer"), G = Symbol.for("react.context"), H = Symbol.for("react.forward_ref"), X = Symbol.for("react.suspense"), B = Symbol.for("react.suspense_list"), Z = Symbol.for("react.memo"), A = Symbol.for("react.lazy"), Q = Symbol.for("react.activity"), ee = Symbol.for("react.client.reference"), O = _.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, N = Object.prototype.hasOwnProperty, re = Array.isArray, S = console.createTask ? console.createTask : function() {
|
|
241
|
+
return null;
|
|
242
|
+
};
|
|
243
|
+
_ = {
|
|
244
|
+
react_stack_bottom_frame: function(e) {
|
|
245
|
+
return e();
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
var L, Y = {}, I = _.react_stack_bottom_frame.bind(
|
|
249
|
+
_,
|
|
250
|
+
u
|
|
251
|
+
)(), M = S(d(u)), $ = {};
|
|
252
|
+
R.Fragment = h, R.jsx = function(e, r, n) {
|
|
253
|
+
var o = 1e4 > O.recentlyCreatedOwnerStacks++;
|
|
254
|
+
return b(
|
|
255
|
+
e,
|
|
256
|
+
r,
|
|
257
|
+
n,
|
|
258
|
+
!1,
|
|
259
|
+
o ? Error("react-stack-top-frame") : I,
|
|
260
|
+
o ? S(d(e)) : M
|
|
261
|
+
);
|
|
262
|
+
}, R.jsxs = function(e, r, n) {
|
|
263
|
+
var o = 1e4 > O.recentlyCreatedOwnerStacks++;
|
|
264
|
+
return b(
|
|
265
|
+
e,
|
|
266
|
+
r,
|
|
267
|
+
n,
|
|
268
|
+
!0,
|
|
269
|
+
o ? Error("react-stack-top-frame") : I,
|
|
270
|
+
o ? S(d(e)) : M
|
|
271
|
+
);
|
|
272
|
+
};
|
|
273
|
+
})()), R;
|
|
274
|
+
}
|
|
275
|
+
var W;
|
|
276
|
+
function le() {
|
|
277
|
+
return W || (W = 1, process.env.NODE_ENV === "production" ? k.exports = oe() : k.exports = se()), k.exports;
|
|
278
|
+
}
|
|
279
|
+
var ue = le();
|
|
280
|
+
const me = ({
|
|
281
|
+
children: t,
|
|
282
|
+
onShortPressed: c,
|
|
283
|
+
className: f
|
|
284
|
+
}) => {
|
|
285
|
+
const d = ae(c);
|
|
286
|
+
return F(() => {
|
|
287
|
+
d.current = c;
|
|
288
|
+
}, [c]), F(() => {
|
|
289
|
+
const l = (a) => {
|
|
290
|
+
if (!a || !(a instanceof HTMLElement)) return !1;
|
|
291
|
+
const i = a.tagName.toLowerCase();
|
|
292
|
+
return i === "input" || i === "textarea" || a.isContentEditable;
|
|
293
|
+
}, u = (a) => {
|
|
294
|
+
l(a.target) || d.current.forEach((i) => {
|
|
295
|
+
const {
|
|
296
|
+
key: m,
|
|
297
|
+
ctrlKey: v = !1,
|
|
298
|
+
shiftKey: b = !1,
|
|
299
|
+
altKey: p = !1,
|
|
300
|
+
metaKey: w = !1
|
|
301
|
+
} = i;
|
|
302
|
+
(m.length === 1 ? a.key.toLowerCase() === m.toLowerCase() : a.key === m) && a.ctrlKey === v && a.shiftKey === b && a.altKey === p && a.metaKey === w && i.onPress(a);
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
return window.addEventListener("keydown", u), () => {
|
|
306
|
+
window.removeEventListener("keydown", u);
|
|
307
|
+
};
|
|
308
|
+
}, []), /* @__PURE__ */ ue.jsx("div", { className: f, children: t });
|
|
309
|
+
};
|
|
310
|
+
let j = [], g = !1;
|
|
311
|
+
function ie(t) {
|
|
312
|
+
if (!t || !(t instanceof HTMLElement)) return !1;
|
|
313
|
+
const c = t.tagName.toLowerCase();
|
|
314
|
+
return c === "input" || c === "textarea" || t.isContentEditable === !0;
|
|
315
|
+
}
|
|
316
|
+
function U(t) {
|
|
317
|
+
ie(t.target) || j.forEach(function(c) {
|
|
318
|
+
const {
|
|
319
|
+
key: f,
|
|
320
|
+
ctrlKey: d = !1,
|
|
321
|
+
shiftKey: l = !1,
|
|
322
|
+
altKey: u = !1,
|
|
323
|
+
metaKey: a = !1,
|
|
324
|
+
onPress: i
|
|
325
|
+
} = c;
|
|
326
|
+
(f.length === 1 ? t.key && t.key.toLowerCase() === f.toLowerCase() : t.key === f) && !!t.ctrlKey == !!d && !!t.shiftKey == !!l && !!t.altKey == !!u && !!t.metaKey == !!a && typeof i == "function" && i(t);
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
function ce(t) {
|
|
330
|
+
Array.isArray(t) && (j = t, g || (window.addEventListener("keydown", U), g = !0));
|
|
331
|
+
}
|
|
332
|
+
function fe() {
|
|
333
|
+
window.removeEventListener("keydown", U), j = [], g = !1;
|
|
334
|
+
}
|
|
335
|
+
typeof window < "u" && (window.addShortcuts = ce, window.removeShortcuts = fe);
|
|
336
|
+
export {
|
|
337
|
+
me as Shortcut,
|
|
338
|
+
ce as addShortcuts,
|
|
339
|
+
fe as removeShortcuts
|
|
340
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
(function(m,E){typeof exports=="object"&&typeof module<"u"?E(exports,require("react")):typeof define=="function"&&define.amd?define(["exports","react"],E):(m=typeof globalThis<"u"?globalThis:m||self,E(m.shortcut={},m.React))})(this,(function(m,E){"use strict";var T={exports:{}},R={};/**
|
|
2
|
+
* @license React
|
|
3
|
+
* react-jsx-runtime.production.js
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
6
|
+
*
|
|
7
|
+
* This source code is licensed under the MIT license found in the
|
|
8
|
+
* LICENSE file in the root directory of this source tree.
|
|
9
|
+
*/var L;function G(){if(L)return R;L=1;var t=Symbol.for("react.transitional.element"),i=Symbol.for("react.fragment");function f(d,u,l){var o=null;if(l!==void 0&&(o=""+l),u.key!==void 0&&(o=""+u.key),"key"in u){l={};for(var c in u)c!=="key"&&(l[c]=u[c])}else l=u;return u=l.ref,{$$typeof:t,type:d,key:o,ref:u!==void 0?u:null,props:l}}return R.Fragment=i,R.jsx=f,R.jsxs=f,R}var b={};/**
|
|
10
|
+
* @license React
|
|
11
|
+
* react-jsx-runtime.development.js
|
|
12
|
+
*
|
|
13
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
14
|
+
*
|
|
15
|
+
* This source code is licensed under the MIT license found in the
|
|
16
|
+
* LICENSE file in the root directory of this source tree.
|
|
17
|
+
*/var Y;function q(){return Y||(Y=1,process.env.NODE_ENV!=="production"&&(function(){function t(e){if(e==null)return null;if(typeof e=="function")return e.$$typeof===ce?null:e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case x:return"Fragment";case re:return"Profiler";case ee:return"StrictMode";case ae:return"Suspense";case se:return"SuspenseList";case le:return"Activity"}if(typeof e=="object")switch(typeof e.tag=="number"&&console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),e.$$typeof){case Q:return"Portal";case ne:return e.displayName||"Context";case te:return(e._context.displayName||"Context")+".Consumer";case oe:var r=e.render;return e=e.displayName,e||(e=r.displayName||r.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case ue:return r=e.displayName||null,r!==null?r:t(e.type)||"Memo";case P:r=e._payload,e=e._init;try{return t(e(r))}catch{}}return null}function i(e){return""+e}function f(e){try{i(e);var r=!1}catch{r=!0}if(r){r=console;var n=r.error,a=typeof Symbol=="function"&&Symbol.toStringTag&&e[Symbol.toStringTag]||e.constructor.name||"Object";return n.call(r,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",a),i(e)}}function d(e){if(e===x)return"<>";if(typeof e=="object"&&e!==null&&e.$$typeof===P)return"<...>";try{var r=t(e);return r?"<"+r+">":"<...>"}catch{return"<...>"}}function u(){var e=g.A;return e===null?null:e.getOwner()}function l(){return Error("react-stack-top-frame")}function o(e){if(W.call(e,"key")){var r=Object.getOwnPropertyDescriptor(e,"key").get;if(r&&r.isReactWarning)return!1}return e.key!==void 0}function c(e,r){function n(){U||(U=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",r))}n.isReactWarning=!0,Object.defineProperty(e,"key",{get:n,configurable:!0})}function _(){var e=t(this.type);return $[e]||($[e]=!0,console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")),e=this.props.ref,e!==void 0?e:null}function A(e,r,n,a,h,C){var s=n.ref;return e={$$typeof:D,type:e,key:r,props:n,_owner:a},(s!==void 0?s:null)!==null?Object.defineProperty(e,"ref",{enumerable:!1,get:_}):Object.defineProperty(e,"ref",{enumerable:!1,value:null}),e._store={},Object.defineProperty(e._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(e,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(e,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:h}),Object.defineProperty(e,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:C}),Object.freeze&&(Object.freeze(e.props),Object.freeze(e)),e}function w(e,r,n,a,h,C){var s=r.children;if(s!==void 0)if(a)if(ie(s)){for(a=0;a<s.length;a++)k(s[a]);Object.freeze&&Object.freeze(s)}else console.error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else k(s);if(W.call(r,"key")){s=t(e);var y=Object.keys(r).filter(function(fe){return fe!=="key"});a=0<y.length?"{key: someKey, "+y.join(": ..., ")+": ...}":"{key: someKey}",z[s+a]||(y=0<y.length?"{"+y.join(": ..., ")+": ...}":"{}",console.error(`A props object containing a "key" prop is being spread into JSX:
|
|
18
|
+
let props = %s;
|
|
19
|
+
<%s {...props} />
|
|
20
|
+
React keys must be passed directly to JSX without using spread:
|
|
21
|
+
let props = %s;
|
|
22
|
+
<%s key={someKey} {...props} />`,a,s,y,s),z[s+a]=!0)}if(s=null,n!==void 0&&(f(n),s=""+n),o(r)&&(f(r.key),s=""+r.key),"key"in r){n={};for(var N in r)N!=="key"&&(n[N]=r[N])}else n=r;return s&&c(n,typeof e=="function"?e.displayName||e.name||"Unknown":e),A(e,s,n,u(),h,C)}function k(e){v(e)?e._store&&(e._store.validated=1):typeof e=="object"&&e!==null&&e.$$typeof===P&&(e._payload.status==="fulfilled"?v(e._payload.value)&&e._payload.value._store&&(e._payload.value._store.validated=1):e._store&&(e._store.validated=1))}function v(e){return typeof e=="object"&&e!==null&&e.$$typeof===D}var p=E,D=Symbol.for("react.transitional.element"),Q=Symbol.for("react.portal"),x=Symbol.for("react.fragment"),ee=Symbol.for("react.strict_mode"),re=Symbol.for("react.profiler"),te=Symbol.for("react.consumer"),ne=Symbol.for("react.context"),oe=Symbol.for("react.forward_ref"),ae=Symbol.for("react.suspense"),se=Symbol.for("react.suspense_list"),ue=Symbol.for("react.memo"),P=Symbol.for("react.lazy"),le=Symbol.for("react.activity"),ce=Symbol.for("react.client.reference"),g=p.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,W=Object.prototype.hasOwnProperty,ie=Array.isArray,j=console.createTask?console.createTask:function(){return null};p={react_stack_bottom_frame:function(e){return e()}};var U,$={},J=p.react_stack_bottom_frame.bind(p,l)(),V=j(d(l)),z={};b.Fragment=x,b.jsx=function(e,r,n){var a=1e4>g.recentlyCreatedOwnerStacks++;return w(e,r,n,!1,a?Error("react-stack-top-frame"):J,a?j(d(e)):V)},b.jsxs=function(e,r,n){var a=1e4>g.recentlyCreatedOwnerStacks++;return w(e,r,n,!0,a?Error("react-stack-top-frame"):J,a?j(d(e)):V)}})()),b}var M;function H(){return M||(M=1,process.env.NODE_ENV==="production"?T.exports=G():T.exports=q()),T.exports}var X=H();const B=({children:t,onShortPressed:i,className:f})=>{const d=E.useRef(i);return E.useEffect(()=>{d.current=i},[i]),E.useEffect(()=>{const u=o=>{if(!o||!(o instanceof HTMLElement))return!1;const c=o.tagName.toLowerCase();return c==="input"||c==="textarea"||o.isContentEditable},l=o=>{u(o.target)||d.current.forEach(c=>{const{key:_,ctrlKey:A=!1,shiftKey:w=!1,altKey:k=!1,metaKey:v=!1}=c;(_.length===1?o.key.toLowerCase()===_.toLowerCase():o.key===_)&&o.ctrlKey===A&&o.shiftKey===w&&o.altKey===k&&o.metaKey===v&&c.onPress(o)})};return window.addEventListener("keydown",l),()=>{window.removeEventListener("keydown",l)}},[]),X.jsx("div",{className:f,children:t})};let S=[],O=!1;function Z(t){if(!t||!(t instanceof HTMLElement))return!1;const i=t.tagName.toLowerCase();return i==="input"||i==="textarea"||t.isContentEditable===!0}function I(t){Z(t.target)||S.forEach(function(i){const{key:f,ctrlKey:d=!1,shiftKey:u=!1,altKey:l=!1,metaKey:o=!1,onPress:c}=i;(f.length===1?t.key&&t.key.toLowerCase()===f.toLowerCase():t.key===f)&&!!t.ctrlKey==!!d&&!!t.shiftKey==!!u&&!!t.altKey==!!l&&!!t.metaKey==!!o&&typeof c=="function"&&c(t)})}function F(t){Array.isArray(t)&&(S=t,O||(window.addEventListener("keydown",I),O=!0))}function K(){window.removeEventListener("keydown",I),S=[],O=!1}typeof window<"u"&&(window.addShortcuts=F,window.removeShortcuts=K),m.Shortcut=B,m.addShortcuts=F,m.removeShortcuts=K,Object.defineProperty(m,Symbol.toStringTag,{value:"Module"})}));
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shortcut (HOTKEYS) - Pure JavaScript Utility
|
|
3
|
+
*
|
|
4
|
+
* Allows you to define custom keyboard shortcuts (hotkeys) to execute functions
|
|
5
|
+
* when specific key combinations are pressed.
|
|
6
|
+
*
|
|
7
|
+
* ## Usage in JavaScript (ES6+):
|
|
8
|
+
*
|
|
9
|
+
* ```js
|
|
10
|
+
* import { addShortcuts, removeShortcuts } from "./Shortcut";
|
|
11
|
+
*
|
|
12
|
+
* const handler = (e) => { e.preventDefault(); alert("Saved!"); };
|
|
13
|
+
* addShortcuts([
|
|
14
|
+
* { key: "s", ctrlKey: true, onPress: handler }
|
|
15
|
+
* ]);
|
|
16
|
+
*
|
|
17
|
+
* // To remove all shortcuts later:
|
|
18
|
+
* removeShortcuts();
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* ## Direct HTML Usage:
|
|
22
|
+
* ```html
|
|
23
|
+
* <script src="Shortcut.js"></script>
|
|
24
|
+
* <script>
|
|
25
|
+
* addShortcuts([
|
|
26
|
+
* { key: "n", ctrlKey: true, altKey: true, onPress: function(e) { alert('Ctrl+Alt+N!'); } }
|
|
27
|
+
* ]);
|
|
28
|
+
* </script>
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* ## Type Definition
|
|
32
|
+
* @typedef {Object} ShortPressedItem
|
|
33
|
+
* @property {string} key - The key to detect (e.g., "s" or "Enter").
|
|
34
|
+
* @property {boolean} [ctrlKey] - Requires Control key. Default: false.
|
|
35
|
+
* @property {boolean} [shiftKey] - Requires Shift key. Default: false.
|
|
36
|
+
* @property {boolean} [altKey] - Requires Alt key. Default: false.
|
|
37
|
+
* @property {boolean} [metaKey] - Requires Meta/Super key (β, Windows). Default: false.
|
|
38
|
+
* @property {(e: KeyboardEvent) => void} onPress - Function to execute when shortcut is triggered.
|
|
39
|
+
*/
|
|
40
|
+
type ShortPressedItem = {
|
|
41
|
+
key: string;
|
|
42
|
+
ctrlKey?: boolean;
|
|
43
|
+
shiftKey?: boolean;
|
|
44
|
+
altKey?: boolean;
|
|
45
|
+
metaKey?: boolean;
|
|
46
|
+
onPress: (e: KeyboardEvent) => void;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Register global keyboard shortcuts.
|
|
50
|
+
* @param {ShortPressedItem[]} shortcuts
|
|
51
|
+
*/
|
|
52
|
+
export declare function addShortcuts(shortcuts: ShortPressedItem[]): void;
|
|
53
|
+
/**
|
|
54
|
+
* Remove all currently active keyboard shortcuts.
|
|
55
|
+
*/
|
|
56
|
+
export declare function removeShortcuts(): void;
|
|
57
|
+
declare global {
|
|
58
|
+
interface Window {
|
|
59
|
+
addShortcuts?: typeof addShortcuts;
|
|
60
|
+
removeShortcuts?: typeof removeShortcuts;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pure-shortcut",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.umd.cjs",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"license":"MIT",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"require":"./dist/index.umd.cjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"react": "^18 || ^19",
|
|
20
|
+
"react-dom": "^18 || ^19",
|
|
21
|
+
"tailwindcss": "^4"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@iconify/react": "^6.0.2"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@tailwindcss/cli": "^4.2.1",
|
|
28
|
+
"@types/react": "^19.2.14",
|
|
29
|
+
"tailwindcss": "^4.2.1"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "vite build"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"shortcut",
|
|
36
|
+
"hotkey",
|
|
37
|
+
"react",
|
|
38
|
+
"keyboard",
|
|
39
|
+
"hook",
|
|
40
|
+
"shortcuts",
|
|
41
|
+
"keyboard-shortcut",
|
|
42
|
+
"custom-keyboard-shortcut",
|
|
43
|
+
"react-component",
|
|
44
|
+
"keybinding",
|
|
45
|
+
"keyboard-event",
|
|
46
|
+
"tailwind",
|
|
47
|
+
"typescript",
|
|
48
|
+
"shortcut-provider"
|
|
49
|
+
]
|
|
50
|
+
}
|