react-actionkit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +126 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +111 -0
- package/dist/index.js +344 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ActionKit contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# ActionKit
|
|
2
|
+
|
|
3
|
+
ActionKit is a small headless React library for centralized application actions, keyboard shortcuts, and command palette state.
|
|
4
|
+
|
|
5
|
+
Register an action once, then expose it through shortcuts, search, settings, menus, or any UI you build yourself.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { ActionKitProvider, useRegisterAction } from "react-actionkit";
|
|
9
|
+
|
|
10
|
+
function SaveAction() {
|
|
11
|
+
useRegisterAction({
|
|
12
|
+
id: "save-document",
|
|
13
|
+
title: "Save Document",
|
|
14
|
+
description: "Save the current document",
|
|
15
|
+
shortcut: "mod+s",
|
|
16
|
+
execute: saveDocument
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function App() {
|
|
23
|
+
return (
|
|
24
|
+
<ActionKitProvider>
|
|
25
|
+
<SaveAction />
|
|
26
|
+
</ActionKitProvider>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
ActionKit ships no styled UI. Build your own command palette with `useCommandPalette`, render actions however you like, and keep business logic registered once.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
npm install react-actionkit
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
React and React DOM are peer dependencies.
|
|
40
|
+
|
|
41
|
+
## Core API
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import {
|
|
45
|
+
ActionKitProvider,
|
|
46
|
+
createLocalStorageAdapter,
|
|
47
|
+
useCommandPalette,
|
|
48
|
+
useRegisterAction,
|
|
49
|
+
useShortcutBindings
|
|
50
|
+
} from "react-actionkit";
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Provider
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
const shortcutStorage = createLocalStorageAdapter("my-app:shortcuts");
|
|
57
|
+
|
|
58
|
+
export function Root() {
|
|
59
|
+
return (
|
|
60
|
+
<ActionKitProvider storage={shortcutStorage}>
|
|
61
|
+
<App />
|
|
62
|
+
</ActionKitProvider>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Custom Command Palette
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
function CommandPalette() {
|
|
71
|
+
const { actions, closePalette, executeAction, open, query, setQuery } = useCommandPalette();
|
|
72
|
+
|
|
73
|
+
if (!open) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div role="dialog" aria-modal="true">
|
|
79
|
+
<input value={query} onChange={(event) => setQuery(event.target.value)} />
|
|
80
|
+
{actions.map((action) => (
|
|
81
|
+
<button
|
|
82
|
+
key={action.id}
|
|
83
|
+
type="button"
|
|
84
|
+
onClick={async () => {
|
|
85
|
+
await executeAction(action.id);
|
|
86
|
+
closePalette();
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
{action.title}
|
|
90
|
+
</button>
|
|
91
|
+
))}
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Shortcut Settings
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
function ShortcutSettings() {
|
|
101
|
+
const { clearShortcutOverride, getEffectiveShortcut, setShortcutOverride } = useShortcutBindings();
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<section>
|
|
105
|
+
<p>Save draft: {getEffectiveShortcut("save-draft")}</p>
|
|
106
|
+
<button type="button" onClick={() => setShortcutOverride("save-draft", "alt+s")}>
|
|
107
|
+
Use Alt+S
|
|
108
|
+
</button>
|
|
109
|
+
<button type="button" onClick={() => clearShortcutOverride("save-draft")}>
|
|
110
|
+
Reset
|
|
111
|
+
</button>
|
|
112
|
+
</section>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Demo
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
npm install
|
|
121
|
+
npm run demo
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Then open `http://127.0.0.1:5173`.
|
|
125
|
+
|
|
126
|
+
The demo is intentionally styled in the example app only. The library does not ship CSS or visual components.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var Y=Object.defineProperty;var $=(t,e,r)=>e in t?Y(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r;var T=(t,e,r)=>$(t,typeof e!="symbol"?e+"":e,r);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const q=require("react/jsx-runtime"),c=require("react");class D{constructor(){T(this,"actionsById",new Map)}register(e){const r=X(e);return this.actionsById.set(r.id,r),()=>{this.actionsById.get(r.id)===r&&this.actionsById.delete(r.id)}}get(e){return this.actionsById.get(e)}getAll(){return Array.from(this.actionsById.values())}async execute(e){const r=this.actionsById.get(e);return!r||r.disabled?!1:(await r.execute(),!0)}}function N(t){return!t.hidden}function X(t){return{...t,keywords:t.keywords?[...t.keywords]:void 0}}function C(t,e){const r=t.filter(N),n=d(e);return n?r.map(o=>({action:o,score:Z(o,n)})).filter(o=>o.score>0).sort(tt).map(o=>o.action):et(r)}function Z(t,e){var i;const r=d(t.title),n=d(t.description),o=d(t.group),a=d((i=t.keywords)==null?void 0:i.join(" "));return A(r,e,1e3,700,500)+A(a,e,650,450,300)+A(o,e,400,275,175)+A(n,e,250,175,100)}function A(t,e,r,n,o){return t?t===e?r:t.startsWith(e)?n:t.includes(e)?o:rt(e,t)?Math.floor(o/2):0:0}function tt(t,e){return e.score!==t.score?e.score-t.score:t.action.title.localeCompare(e.action.title)}function et(t){return[...t].sort((e,r)=>e.title.localeCompare(r.title))}function d(t){return(t==null?void 0:t.trim().toLowerCase())??""}function rt(t,e){let r=0;for(const n of e)if(n===t[r]&&(r+=1),r===t.length)return!0;return!1}const b=["mod","ctrl","meta","alt","shift"],nt=new Map([["cmd","mod"],["command","mod"],["option","alt"],["control","ctrl"],["esc","escape"],["return","enter"],["plus","+"],["space"," "]]);function h(t){const e=t.split("+").map(o=>G(o)).filter(Boolean),r=new Set(e.filter(S)),n=e.find(o=>!S(o));return n?[...Array.from(r).sort(z),n].join("+"):Array.from(r).sort(z).join("+")}function g(t,e){const n=e[t.id]??t.shortcut;return n?h(n):void 0}function V(t,e,r){return e.find(n=>{if(n.disabled||n.hidden)return!1;const o=g(n,r);return o?Q(o,t):!1})}function _(t,e,r,n){const o=h(e);return r.some(a=>a.id===t?!1:g(a,n)===o)}function J(t){if(!(t instanceof HTMLElement))return!1;if(t.isContentEditable)return!0;const e=t.tagName.toLowerCase();return e==="input"||e==="textarea"||e==="select"}function Q(t,e){const n=h(t).split("+"),o=n.find(i=>!S(i)),a=new Set(n.filter(S));return!o||o!==ct(e.key)?!1:ot(a,e)}function ot(t,e){const r=t.has("mod"),n=t.has("ctrl"),o=t.has("meta"),a=st(),i=a?e.metaKey:e.ctrlKey;return r&&!i||!r&&e.ctrlKey!==n||!r&&e.metaKey!==o||r&&a&&e.ctrlKey!==n||r&&!a&&e.metaKey!==o?!1:e.altKey===t.has("alt")&&e.shiftKey===t.has("shift")}function G(t){const e=t.trim().toLowerCase();return nt.get(e)??e}function ct(t){return G(t.length===1?t.toLowerCase():t)}function S(t){return b.some(e=>e===t)}function z(t,e){return j(t)-j(e)}function j(t){const e=b.findIndex(r=>r===t);return e===-1?b.length:e}function st(){return typeof navigator>"u"?!0:/Mac|iPhone|iPad|iPod/.test(navigator.platform)}const H=c.createContext(null);function it({children:t,storage:e,ignoreEditableElements:r=!0}){const n=c.useRef(new D),[o,a]=c.useState([]),[i,m]=c.useState({}),[O,v]=c.useState(!1),[p,U]=c.useState("");c.useEffect(()=>{let s=!0;async function u(){const l=await(e==null?void 0:e.loadShortcutOverrides());!s||!l||m(l)}return u(),()=>{s=!1}},[e]);const w=c.useCallback(()=>{a(n.current.getAll())},[]),k=c.useCallback(s=>{const u=n.current.register(s);return w(),()=>{u(),w()}},[w]),K=c.useCallback(async s=>n.current.execute(s),[]),E=c.useCallback(s=>n.current.get(s),[]),P=c.useCallback(s=>{const u=n.current.get(s);return u?g(u,i):void 0},[i]),y=c.useCallback(s=>{m(s),e==null||e.saveShortcutOverrides(s)},[e]),I=c.useCallback((s,u)=>{const l=h(u);return _(s,l,n.current.getAll(),i)?!1:(y({...i,[s]:l}),!0)},[y,i]),M=c.useCallback(s=>{const u={...i};delete u[s],y(u)},[y,i]),x=c.useCallback(()=>v(!1),[]),R=c.useCallback(()=>v(!0),[]),L=c.useCallback(()=>v(s=>!s),[]),B=c.useMemo(()=>C(o,p),[o,p]);c.useEffect(()=>{function s(u){if(r&&J(u.target))return;const l=V(u,n.current.getAll(),i);l&&(u.preventDefault(),n.current.execute(l.id))}return window.addEventListener("keydown",s),()=>window.removeEventListener("keydown",s)},[r,i]);const W=c.useMemo(()=>({actions:o,shortcutOverrides:i,registerAction:k,executeAction:K,getAction:E,getEffectiveShortcut:P,setShortcutOverride:I,clearShortcutOverride:M,paletteOpen:O,paletteQuery:p,paletteActions:B,openPalette:R,closePalette:x,togglePalette:L,setPaletteQuery:U}),[o,M,x,K,E,P,R,B,O,p,k,I,i,L]);return q.jsx(H.Provider,{value:W,children:t})}function f(){const t=c.useContext(H);if(!t)throw new Error("ActionKit hooks must be used inside ActionKitProvider.");return t}function ut(t){const{registerAction:e}=f(),r=c.useRef(t),n=mt(t);r.current=t,c.useEffect(()=>e({...t,execute:()=>r.current.execute()}),[n,e])}function at(t){const{actions:e}=f();return c.useMemo(()=>e.find(r=>r.id===t),[t,e])}function lt(){const{actions:t}=f();return t}function ft(){const{paletteOpen:t,paletteQuery:e,paletteActions:r,openPalette:n,closePalette:o,togglePalette:a,setPaletteQuery:i,executeAction:m}=f();return{open:t,query:e,actions:r,openPalette:n,closePalette:o,togglePalette:a,setQuery:i,executeAction:m}}function dt(){const{shortcutOverrides:t,getEffectiveShortcut:e,setShortcutOverride:r,clearShortcutOverride:n}=f();return{shortcutOverrides:t,getEffectiveShortcut:e,setShortcutOverride:r,clearShortcutOverride:n}}function ht(t){const{actions:e}=f();return c.useMemo(()=>C(e,t),[e,t])}function mt(t){return JSON.stringify({id:t.id,title:t.title,description:t.description,group:t.group,keywords:t.keywords,shortcut:t.shortcut,disabled:t.disabled,hidden:t.hidden})}const pt="actionkit:shortcut-overrides";function yt(t=pt){return{loadShortcutOverrides(){if(!F())return{};const e=window.localStorage.getItem(t);return e?At(e):{}},saveShortcutOverrides(e){F()&&window.localStorage.setItem(t,JSON.stringify(e))}}}function At(t){try{const e=JSON.parse(t);return St(e)?e:{}}catch{return{}}}function St(t){return!t||typeof t!="object"||Array.isArray(t)?!1:Object.values(t).every(e=>e===void 0||typeof e=="string")}function F(){return typeof window<"u"&&!!window.localStorage&&typeof window.localStorage.getItem=="function"&&typeof window.localStorage.setItem=="function"}exports.ActionKitProvider=it;exports.ActionRegistry=D;exports.createLocalStorageAdapter=yt;exports.findActionForKeyboardEvent=V;exports.getEffectiveShortcut=g;exports.isVisibleAction=N;exports.normalizeShortcut=h;exports.searchActions=C;exports.shortcutConflicts=_;exports.shortcutMatchesEvent=Q;exports.shouldIgnoreKeyboardTarget=J;exports.useAction=at;exports.useActionKitContext=f;exports.useActions=lt;exports.useCommandPalette=ft;exports.useFilteredActions=ht;exports.useRegisterAction=ut;exports.useShortcutBindings=dt;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { JSX } from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
export declare interface ActionDefinition {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
group?: string;
|
|
9
|
+
keywords?: string[];
|
|
10
|
+
shortcut?: string;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
hidden?: boolean;
|
|
13
|
+
execute: ActionExecute;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export declare type ActionExecute = () => void | Promise<void>;
|
|
17
|
+
|
|
18
|
+
export declare interface ActionKitContextValue {
|
|
19
|
+
actions: RegisteredAction[];
|
|
20
|
+
shortcutOverrides: ShortcutOverrides;
|
|
21
|
+
registerAction: (action: ActionDefinition) => () => void;
|
|
22
|
+
executeAction: (actionId: string) => Promise<boolean>;
|
|
23
|
+
getAction: (actionId: string) => RegisteredAction | undefined;
|
|
24
|
+
getEffectiveShortcut: (actionId: string) => string | undefined;
|
|
25
|
+
setShortcutOverride: (actionId: string, shortcut: string) => boolean;
|
|
26
|
+
clearShortcutOverride: (actionId: string) => void;
|
|
27
|
+
paletteOpen: boolean;
|
|
28
|
+
paletteQuery: string;
|
|
29
|
+
paletteActions: RegisteredAction[];
|
|
30
|
+
openPalette: () => void;
|
|
31
|
+
closePalette: () => void;
|
|
32
|
+
togglePalette: () => void;
|
|
33
|
+
setPaletteQuery: (query: string) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export declare function ActionKitProvider({ children, storage, ignoreEditableElements }: ActionKitProviderProps): JSX.Element;
|
|
37
|
+
|
|
38
|
+
export declare interface ActionKitProviderProps {
|
|
39
|
+
children: ReactNode;
|
|
40
|
+
storage?: ActionKitStorage;
|
|
41
|
+
ignoreEditableElements?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export declare interface ActionKitStorage {
|
|
45
|
+
loadShortcutOverrides: () => ShortcutOverrides | Promise<ShortcutOverrides>;
|
|
46
|
+
saveShortcutOverrides: (overrides: ShortcutOverrides) => void | Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export declare class ActionRegistry {
|
|
50
|
+
private readonly actionsById;
|
|
51
|
+
register(action: ActionDefinition): () => void;
|
|
52
|
+
get(actionId: string): RegisteredAction | undefined;
|
|
53
|
+
getAll(): RegisteredAction[];
|
|
54
|
+
execute(actionId: string): Promise<boolean>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export declare function createLocalStorageAdapter(storageKey?: string): ActionKitStorage;
|
|
58
|
+
|
|
59
|
+
export declare function findActionForKeyboardEvent(event: KeyboardEvent, actions: RegisteredAction[], shortcutOverrides: ShortcutOverrides): RegisteredAction | undefined;
|
|
60
|
+
|
|
61
|
+
export declare function getEffectiveShortcut(action: RegisteredAction, shortcutOverrides: ShortcutOverrides): string | undefined;
|
|
62
|
+
|
|
63
|
+
export declare function isVisibleAction(action: RegisteredAction): boolean;
|
|
64
|
+
|
|
65
|
+
export declare function normalizeShortcut(shortcut: string): string;
|
|
66
|
+
|
|
67
|
+
export declare interface RegisteredAction extends ActionDefinition {
|
|
68
|
+
shortcut?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export declare function searchActions(actions: RegisteredAction[], query: string): RegisteredAction[];
|
|
72
|
+
|
|
73
|
+
export declare function shortcutConflicts(actionId: string, shortcut: string, actions: RegisteredAction[], shortcutOverrides: ShortcutOverrides): boolean;
|
|
74
|
+
|
|
75
|
+
export declare function shortcutMatchesEvent(shortcut: string, event: KeyboardEvent): boolean;
|
|
76
|
+
|
|
77
|
+
export declare interface ShortcutOverrides {
|
|
78
|
+
[actionId: string]: string | undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export declare function shouldIgnoreKeyboardTarget(target: EventTarget | null): boolean;
|
|
82
|
+
|
|
83
|
+
export declare function useAction(actionId: string): RegisteredAction | undefined;
|
|
84
|
+
|
|
85
|
+
export declare function useActionKitContext(): ActionKitContextValue;
|
|
86
|
+
|
|
87
|
+
export declare function useActions(): RegisteredAction[];
|
|
88
|
+
|
|
89
|
+
export declare function useCommandPalette(): {
|
|
90
|
+
open: boolean;
|
|
91
|
+
query: string;
|
|
92
|
+
actions: RegisteredAction[];
|
|
93
|
+
openPalette: () => void;
|
|
94
|
+
closePalette: () => void;
|
|
95
|
+
togglePalette: () => void;
|
|
96
|
+
setQuery: (query: string) => void;
|
|
97
|
+
executeAction: (actionId: string) => Promise<boolean>;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export declare function useFilteredActions(query: string): RegisteredAction[];
|
|
101
|
+
|
|
102
|
+
export declare function useRegisterAction(action: ActionDefinition): void;
|
|
103
|
+
|
|
104
|
+
export declare function useShortcutBindings(): {
|
|
105
|
+
shortcutOverrides: ShortcutOverrides;
|
|
106
|
+
getEffectiveShortcut: (actionId: string) => string | undefined;
|
|
107
|
+
setShortcutOverride: (actionId: string, shortcut: string) => boolean;
|
|
108
|
+
clearShortcutOverride: (actionId: string) => void;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
var U = Object.defineProperty;
|
|
2
|
+
var W = (t, e, r) => e in t ? U(t, e, { enumerable: !0, configurable: !0, writable: !0, value: r }) : t[e] = r;
|
|
3
|
+
var D = (t, e, r) => W(t, typeof e != "symbol" ? e + "" : e, r);
|
|
4
|
+
import { jsx as Y } from "react/jsx-runtime";
|
|
5
|
+
import { useRef as _, useState as y, useEffect as K, useCallback as a, useMemo as S, useContext as $, createContext as X } from "react";
|
|
6
|
+
class Z {
|
|
7
|
+
constructor() {
|
|
8
|
+
D(this, "actionsById", /* @__PURE__ */ new Map());
|
|
9
|
+
}
|
|
10
|
+
register(e) {
|
|
11
|
+
const r = tt(e);
|
|
12
|
+
return this.actionsById.set(r.id, r), () => {
|
|
13
|
+
this.actionsById.get(r.id) === r && this.actionsById.delete(r.id);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
get(e) {
|
|
17
|
+
return this.actionsById.get(e);
|
|
18
|
+
}
|
|
19
|
+
getAll() {
|
|
20
|
+
return Array.from(this.actionsById.values());
|
|
21
|
+
}
|
|
22
|
+
async execute(e) {
|
|
23
|
+
const r = this.actionsById.get(e);
|
|
24
|
+
return !r || r.disabled ? !1 : (await r.execute(), !0);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function q(t) {
|
|
28
|
+
return !t.hidden;
|
|
29
|
+
}
|
|
30
|
+
function tt(t) {
|
|
31
|
+
return {
|
|
32
|
+
...t,
|
|
33
|
+
keywords: t.keywords ? [...t.keywords] : void 0
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function J(t, e) {
|
|
37
|
+
const r = t.filter(q), n = d(e);
|
|
38
|
+
return n ? r.map((o) => ({ action: o, score: et(o, n) })).filter((o) => o.score > 0).sort(rt).map((o) => o.action) : nt(r);
|
|
39
|
+
}
|
|
40
|
+
function et(t, e) {
|
|
41
|
+
var c;
|
|
42
|
+
const r = d(t.title), n = d(t.description), o = d(t.group), u = d((c = t.keywords) == null ? void 0 : c.join(" "));
|
|
43
|
+
return A(r, e, 1e3, 700, 500) + A(u, e, 650, 450, 300) + A(o, e, 400, 275, 175) + A(n, e, 250, 175, 100);
|
|
44
|
+
}
|
|
45
|
+
function A(t, e, r, n, o) {
|
|
46
|
+
return t ? t === e ? r : t.startsWith(e) ? n : t.includes(e) ? o : ot(e, t) ? Math.floor(o / 2) : 0 : 0;
|
|
47
|
+
}
|
|
48
|
+
function rt(t, e) {
|
|
49
|
+
return e.score !== t.score ? e.score - t.score : t.action.title.localeCompare(e.action.title);
|
|
50
|
+
}
|
|
51
|
+
function nt(t) {
|
|
52
|
+
return [...t].sort((e, r) => e.title.localeCompare(r.title));
|
|
53
|
+
}
|
|
54
|
+
function d(t) {
|
|
55
|
+
return (t == null ? void 0 : t.trim().toLowerCase()) ?? "";
|
|
56
|
+
}
|
|
57
|
+
function ot(t, e) {
|
|
58
|
+
let r = 0;
|
|
59
|
+
for (const n of e)
|
|
60
|
+
if (n === t[r] && (r += 1), r === t.length)
|
|
61
|
+
return !0;
|
|
62
|
+
return !1;
|
|
63
|
+
}
|
|
64
|
+
const E = ["mod", "ctrl", "meta", "alt", "shift"], it = /* @__PURE__ */ new Map([
|
|
65
|
+
["cmd", "mod"],
|
|
66
|
+
["command", "mod"],
|
|
67
|
+
["option", "alt"],
|
|
68
|
+
["control", "ctrl"],
|
|
69
|
+
["esc", "escape"],
|
|
70
|
+
["return", "enter"],
|
|
71
|
+
["plus", "+"],
|
|
72
|
+
["space", " "]
|
|
73
|
+
]);
|
|
74
|
+
function w(t) {
|
|
75
|
+
const e = t.split("+").map((o) => Q(o)).filter(Boolean), r = new Set(e.filter(g)), n = e.find((o) => !g(o));
|
|
76
|
+
return n ? [...Array.from(r).sort(j), n].join("+") : Array.from(r).sort(j).join("+");
|
|
77
|
+
}
|
|
78
|
+
function I(t, e) {
|
|
79
|
+
const n = e[t.id] ?? t.shortcut;
|
|
80
|
+
return n ? w(n) : void 0;
|
|
81
|
+
}
|
|
82
|
+
function ct(t, e, r) {
|
|
83
|
+
return e.find((n) => {
|
|
84
|
+
if (n.disabled || n.hidden)
|
|
85
|
+
return !1;
|
|
86
|
+
const o = I(n, r);
|
|
87
|
+
return o ? at(o, t) : !1;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function st(t, e, r, n) {
|
|
91
|
+
const o = w(e);
|
|
92
|
+
return r.some((u) => u.id === t ? !1 : I(u, n) === o);
|
|
93
|
+
}
|
|
94
|
+
function ut(t) {
|
|
95
|
+
if (!(t instanceof HTMLElement))
|
|
96
|
+
return !1;
|
|
97
|
+
if (t.isContentEditable)
|
|
98
|
+
return !0;
|
|
99
|
+
const e = t.tagName.toLowerCase();
|
|
100
|
+
return e === "input" || e === "textarea" || e === "select";
|
|
101
|
+
}
|
|
102
|
+
function at(t, e) {
|
|
103
|
+
const n = w(t).split("+"), o = n.find((c) => !g(c)), u = new Set(n.filter(g));
|
|
104
|
+
return !o || o !== lt(e.key) ? !1 : ft(u, e);
|
|
105
|
+
}
|
|
106
|
+
function ft(t, e) {
|
|
107
|
+
const r = t.has("mod"), n = t.has("ctrl"), o = t.has("meta"), u = dt(), c = u ? e.metaKey : e.ctrlKey;
|
|
108
|
+
return r && !c || !r && e.ctrlKey !== n || !r && e.metaKey !== o || r && u && e.ctrlKey !== n || r && !u && e.metaKey !== o ? !1 : e.altKey === t.has("alt") && e.shiftKey === t.has("shift");
|
|
109
|
+
}
|
|
110
|
+
function Q(t) {
|
|
111
|
+
const e = t.trim().toLowerCase();
|
|
112
|
+
return it.get(e) ?? e;
|
|
113
|
+
}
|
|
114
|
+
function lt(t) {
|
|
115
|
+
return Q(t.length === 1 ? t.toLowerCase() : t);
|
|
116
|
+
}
|
|
117
|
+
function g(t) {
|
|
118
|
+
return E.some((e) => e === t);
|
|
119
|
+
}
|
|
120
|
+
function j(t, e) {
|
|
121
|
+
return F(t) - F(e);
|
|
122
|
+
}
|
|
123
|
+
function F(t) {
|
|
124
|
+
const e = E.findIndex((r) => r === t);
|
|
125
|
+
return e === -1 ? E.length : e;
|
|
126
|
+
}
|
|
127
|
+
function dt() {
|
|
128
|
+
return typeof navigator > "u" ? !0 : /Mac|iPhone|iPad|iPod/.test(navigator.platform);
|
|
129
|
+
}
|
|
130
|
+
const V = X(null);
|
|
131
|
+
function wt({ children: t, storage: e, ignoreEditableElements: r = !0 }) {
|
|
132
|
+
const n = _(new Z()), [o, u] = y([]), [c, h] = y({}), [P, v] = y(!1), [p, G] = y("");
|
|
133
|
+
K(() => {
|
|
134
|
+
let i = !0;
|
|
135
|
+
async function s() {
|
|
136
|
+
const f = await (e == null ? void 0 : e.loadShortcutOverrides());
|
|
137
|
+
!i || !f || h(f);
|
|
138
|
+
}
|
|
139
|
+
return s(), () => {
|
|
140
|
+
i = !1;
|
|
141
|
+
};
|
|
142
|
+
}, [e]);
|
|
143
|
+
const O = a(() => {
|
|
144
|
+
u(n.current.getAll());
|
|
145
|
+
}, []), k = a(
|
|
146
|
+
(i) => {
|
|
147
|
+
const s = n.current.register(i);
|
|
148
|
+
return O(), () => {
|
|
149
|
+
s(), O();
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
[O]
|
|
153
|
+
), x = a(async (i) => n.current.execute(i), []), C = a((i) => n.current.get(i), []), b = a(
|
|
154
|
+
(i) => {
|
|
155
|
+
const s = n.current.get(i);
|
|
156
|
+
return s ? I(s, c) : void 0;
|
|
157
|
+
},
|
|
158
|
+
[c]
|
|
159
|
+
), m = a(
|
|
160
|
+
(i) => {
|
|
161
|
+
h(i), e == null || e.saveShortcutOverrides(i);
|
|
162
|
+
},
|
|
163
|
+
[e]
|
|
164
|
+
), M = a(
|
|
165
|
+
(i, s) => {
|
|
166
|
+
const f = w(s);
|
|
167
|
+
return st(i, f, n.current.getAll(), c) ? !1 : (m({
|
|
168
|
+
...c,
|
|
169
|
+
[i]: f
|
|
170
|
+
}), !0);
|
|
171
|
+
},
|
|
172
|
+
[m, c]
|
|
173
|
+
), L = a(
|
|
174
|
+
(i) => {
|
|
175
|
+
const s = { ...c };
|
|
176
|
+
delete s[i], m(s);
|
|
177
|
+
},
|
|
178
|
+
[m, c]
|
|
179
|
+
), B = a(() => v(!1), []), R = a(() => v(!0), []), T = a(() => v((i) => !i), []), z = S(() => J(o, p), [o, p]);
|
|
180
|
+
K(() => {
|
|
181
|
+
function i(s) {
|
|
182
|
+
if (r && ut(s.target))
|
|
183
|
+
return;
|
|
184
|
+
const f = ct(s, n.current.getAll(), c);
|
|
185
|
+
f && (s.preventDefault(), n.current.execute(f.id));
|
|
186
|
+
}
|
|
187
|
+
return window.addEventListener("keydown", i), () => window.removeEventListener("keydown", i);
|
|
188
|
+
}, [r, c]);
|
|
189
|
+
const H = S(
|
|
190
|
+
() => ({
|
|
191
|
+
actions: o,
|
|
192
|
+
shortcutOverrides: c,
|
|
193
|
+
registerAction: k,
|
|
194
|
+
executeAction: x,
|
|
195
|
+
getAction: C,
|
|
196
|
+
getEffectiveShortcut: b,
|
|
197
|
+
setShortcutOverride: M,
|
|
198
|
+
clearShortcutOverride: L,
|
|
199
|
+
paletteOpen: P,
|
|
200
|
+
paletteQuery: p,
|
|
201
|
+
paletteActions: z,
|
|
202
|
+
openPalette: R,
|
|
203
|
+
closePalette: B,
|
|
204
|
+
togglePalette: T,
|
|
205
|
+
setPaletteQuery: G
|
|
206
|
+
}),
|
|
207
|
+
[
|
|
208
|
+
o,
|
|
209
|
+
L,
|
|
210
|
+
B,
|
|
211
|
+
x,
|
|
212
|
+
C,
|
|
213
|
+
b,
|
|
214
|
+
R,
|
|
215
|
+
z,
|
|
216
|
+
P,
|
|
217
|
+
p,
|
|
218
|
+
k,
|
|
219
|
+
M,
|
|
220
|
+
c,
|
|
221
|
+
T
|
|
222
|
+
]
|
|
223
|
+
);
|
|
224
|
+
return /* @__PURE__ */ Y(V.Provider, { value: H, children: t });
|
|
225
|
+
}
|
|
226
|
+
function l() {
|
|
227
|
+
const t = $(V);
|
|
228
|
+
if (!t)
|
|
229
|
+
throw new Error("ActionKit hooks must be used inside ActionKitProvider.");
|
|
230
|
+
return t;
|
|
231
|
+
}
|
|
232
|
+
function vt(t) {
|
|
233
|
+
const { registerAction: e } = l(), r = _(t), n = ht(t);
|
|
234
|
+
r.current = t, K(
|
|
235
|
+
() => e({
|
|
236
|
+
...t,
|
|
237
|
+
execute: () => r.current.execute()
|
|
238
|
+
}),
|
|
239
|
+
[n, e]
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
function Ot(t) {
|
|
243
|
+
const { actions: e } = l();
|
|
244
|
+
return S(() => e.find((r) => r.id === t), [t, e]);
|
|
245
|
+
}
|
|
246
|
+
function Kt() {
|
|
247
|
+
const { actions: t } = l();
|
|
248
|
+
return t;
|
|
249
|
+
}
|
|
250
|
+
function Et() {
|
|
251
|
+
const {
|
|
252
|
+
paletteOpen: t,
|
|
253
|
+
paletteQuery: e,
|
|
254
|
+
paletteActions: r,
|
|
255
|
+
openPalette: n,
|
|
256
|
+
closePalette: o,
|
|
257
|
+
togglePalette: u,
|
|
258
|
+
setPaletteQuery: c,
|
|
259
|
+
executeAction: h
|
|
260
|
+
} = l();
|
|
261
|
+
return {
|
|
262
|
+
open: t,
|
|
263
|
+
query: e,
|
|
264
|
+
actions: r,
|
|
265
|
+
openPalette: n,
|
|
266
|
+
closePalette: o,
|
|
267
|
+
togglePalette: u,
|
|
268
|
+
setQuery: c,
|
|
269
|
+
executeAction: h
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function It() {
|
|
273
|
+
const { shortcutOverrides: t, getEffectiveShortcut: e, setShortcutOverride: r, clearShortcutOverride: n } = l();
|
|
274
|
+
return {
|
|
275
|
+
shortcutOverrides: t,
|
|
276
|
+
getEffectiveShortcut: e,
|
|
277
|
+
setShortcutOverride: r,
|
|
278
|
+
clearShortcutOverride: n
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function Pt(t) {
|
|
282
|
+
const { actions: e } = l();
|
|
283
|
+
return S(() => J(e, t), [e, t]);
|
|
284
|
+
}
|
|
285
|
+
function ht(t) {
|
|
286
|
+
return JSON.stringify({
|
|
287
|
+
id: t.id,
|
|
288
|
+
title: t.title,
|
|
289
|
+
description: t.description,
|
|
290
|
+
group: t.group,
|
|
291
|
+
keywords: t.keywords,
|
|
292
|
+
shortcut: t.shortcut,
|
|
293
|
+
disabled: t.disabled,
|
|
294
|
+
hidden: t.hidden
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
const pt = "actionkit:shortcut-overrides";
|
|
298
|
+
function kt(t = pt) {
|
|
299
|
+
return {
|
|
300
|
+
loadShortcutOverrides() {
|
|
301
|
+
if (!N())
|
|
302
|
+
return {};
|
|
303
|
+
const e = window.localStorage.getItem(t);
|
|
304
|
+
return e ? mt(e) : {};
|
|
305
|
+
},
|
|
306
|
+
saveShortcutOverrides(e) {
|
|
307
|
+
N() && window.localStorage.setItem(t, JSON.stringify(e));
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function mt(t) {
|
|
312
|
+
try {
|
|
313
|
+
const e = JSON.parse(t);
|
|
314
|
+
return yt(e) ? e : {};
|
|
315
|
+
} catch {
|
|
316
|
+
return {};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function yt(t) {
|
|
320
|
+
return !t || typeof t != "object" || Array.isArray(t) ? !1 : Object.values(t).every((e) => e === void 0 || typeof e == "string");
|
|
321
|
+
}
|
|
322
|
+
function N() {
|
|
323
|
+
return typeof window < "u" && !!window.localStorage && typeof window.localStorage.getItem == "function" && typeof window.localStorage.setItem == "function";
|
|
324
|
+
}
|
|
325
|
+
export {
|
|
326
|
+
wt as ActionKitProvider,
|
|
327
|
+
Z as ActionRegistry,
|
|
328
|
+
kt as createLocalStorageAdapter,
|
|
329
|
+
ct as findActionForKeyboardEvent,
|
|
330
|
+
I as getEffectiveShortcut,
|
|
331
|
+
q as isVisibleAction,
|
|
332
|
+
w as normalizeShortcut,
|
|
333
|
+
J as searchActions,
|
|
334
|
+
st as shortcutConflicts,
|
|
335
|
+
at as shortcutMatchesEvent,
|
|
336
|
+
ut as shouldIgnoreKeyboardTarget,
|
|
337
|
+
Ot as useAction,
|
|
338
|
+
l as useActionKitContext,
|
|
339
|
+
Kt as useActions,
|
|
340
|
+
Et as useCommandPalette,
|
|
341
|
+
Pt as useFilteredActions,
|
|
342
|
+
vt as useRegisterAction,
|
|
343
|
+
It as useShortcutBindings
|
|
344
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-actionkit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Headless React actions, keyboard shortcuts, and command palette state.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/saicharanar/actionkit.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/saicharanar/actionkit/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/saicharanar/actionkit#readme",
|
|
16
|
+
"main": "./dist/index.cjs",
|
|
17
|
+
"module": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js",
|
|
23
|
+
"require": "./dist/index.cjs"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "vite build",
|
|
34
|
+
"ci": "npm run typecheck && npm run test && npm run build && npm audit",
|
|
35
|
+
"demo": "vite --config examples/demo/vite.config.ts --host 127.0.0.1",
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"prepublishOnly": "npm run typecheck && npm run test && npm run build"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"react": ">=18.2.0 || >=19.0.0",
|
|
42
|
+
"react-dom": ">=18.2.0 || >=19.0.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@testing-library/react": "^16.1.0",
|
|
46
|
+
"@types/node": "^26.0.1",
|
|
47
|
+
"@types/react": "^19.0.2",
|
|
48
|
+
"@types/react-dom": "^19.0.2",
|
|
49
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
50
|
+
"jsdom": "^25.0.1",
|
|
51
|
+
"react": "^19.2.7",
|
|
52
|
+
"react-dom": "^19.2.7",
|
|
53
|
+
"typescript": "^5.7.2",
|
|
54
|
+
"vite": "^6.0.5",
|
|
55
|
+
"vite-plugin-dts": "^4.4.0",
|
|
56
|
+
"vitest": "^4.1.9"
|
|
57
|
+
}
|
|
58
|
+
}
|