@jojovms/ninja-keys-core 0.0.1

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/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # @jojovms/ninja-keys-core 🥷
2
+
3
+ A zero-dependency "Command Palette" interface (Ctrl+K) for Vanilla JS.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @jojovms/ninja-keys-core
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```javascript
14
+ import { NinjaKeys } from '@jojovms/ninja-keys-core';
15
+
16
+ const ninja = new NinjaKeys({
17
+ actions: [
18
+ { id: 'home', title: 'Go Home', icon: '🏠', handler: () => window.location = '/' },
19
+ { id: 'theme', title: 'Toggle Dark Mode', icon: '🌙', handler: () => toggleMyTheme() }
20
+ ],
21
+ hotkey: 'k' // Ctrl+K or Cmd+K
22
+ });
23
+
24
+ ninja.init();
25
+
26
+ // You can also open it manually via a button
27
+ document.getElementById('my-btn').onclick = () => ninja.toggle();
28
+ ```
29
+
30
+ ## Options
31
+
32
+ | Option | Type | Default | Description |
33
+ |---|---|---|---|
34
+ | `actions` | `NinjaAction[]` | `[]` | Array of actions (see below). |
35
+ | `placeholder` | `string` | `'Type a command...'` | Search input placeholder. |
36
+ | `theme` | `'light' \| 'dark'` | `'light'` | Color theme. |
37
+ | `hotkey` | `string` | `'k'` | Hotkey to toggle (combined with Ctrl/Cmd). |
38
+
39
+ ## Action Object
40
+
41
+ ```typescript
42
+ {
43
+ id: string;
44
+ title: string;
45
+ handler: () => void;
46
+ icon?: string; // Emoji or SVG
47
+ hotkey?: string; // Visual hint (e.g., 'ctrl+h')
48
+ section?: string; // (Future support) Grouping
49
+ keywords?: string; // Hidden search terms
50
+ }
51
+ ```
@@ -0,0 +1,36 @@
1
+ export interface NinjaAction {
2
+ id: string;
3
+ title: string;
4
+ hotkey?: string;
5
+ handler?: () => void;
6
+ icon?: string;
7
+ section?: string;
8
+ keywords?: string;
9
+ }
10
+ export interface NinjaKeysOptions {
11
+ actions: NinjaAction[];
12
+ placeholder?: string;
13
+ theme?: 'light' | 'dark';
14
+ }
15
+ export declare class NinjaKeys {
16
+ private options;
17
+ private isOpen;
18
+ private overlay;
19
+ private input;
20
+ private resultsList;
21
+ private selectedIndex;
22
+ private filteredActions;
23
+ private cleanupFns;
24
+ constructor(options: NinjaKeysOptions);
25
+ init(): void;
26
+ cleanup(): void;
27
+ open(): void;
28
+ close(): void;
29
+ toggle(): void;
30
+ private createOverlay;
31
+ private filterActions;
32
+ private moveSelection;
33
+ private ensureVisible;
34
+ private executeSelected;
35
+ private renderList;
36
+ }
package/dist/index.mjs ADDED
@@ -0,0 +1,160 @@
1
+ var c = Object.defineProperty;
2
+ var p = (l, e, t) => e in l ? c(l, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : l[e] = t;
3
+ var n = (l, e, t) => p(l, typeof e != "symbol" ? e + "" : e, t);
4
+ class f {
5
+ constructor(e) {
6
+ n(this, "options");
7
+ n(this, "isOpen", !1);
8
+ n(this, "overlay", null);
9
+ n(this, "input", null);
10
+ n(this, "resultsList", null);
11
+ n(this, "selectedIndex", 0);
12
+ n(this, "filteredActions", []);
13
+ n(this, "cleanupFns", []);
14
+ this.options = {
15
+ placeholder: "Type a command or search...",
16
+ theme: "light",
17
+ ...e
18
+ }, this.filteredActions = this.options.actions;
19
+ }
20
+ init() {
21
+ this.createOverlay();
22
+ const e = (t) => {
23
+ (t.ctrlKey || t.metaKey) && t.key === "k" && (t.preventDefault(), this.toggle()), t.key === "Escape" && this.isOpen && this.close(), this.isOpen && (t.key === "ArrowDown" ? (t.preventDefault(), this.moveSelection(1)) : t.key === "ArrowUp" ? (t.preventDefault(), this.moveSelection(-1)) : t.key === "Enter" && (t.preventDefault(), this.executeSelected()));
24
+ };
25
+ window.addEventListener("keydown", e), this.cleanupFns.push(() => window.removeEventListener("keydown", e));
26
+ }
27
+ cleanup() {
28
+ this.cleanupFns.forEach((e) => e()), this.overlay && this.overlay.remove();
29
+ }
30
+ open() {
31
+ this.isOpen || !this.overlay || (this.isOpen = !0, this.overlay.style.display = "flex", this.overlay.style.opacity = "0", requestAnimationFrame(() => {
32
+ this.overlay && (this.overlay.style.opacity = "1");
33
+ }), this.input && (this.input.value = "", this.input.focus(), this.filterActions("")));
34
+ }
35
+ close() {
36
+ !this.isOpen || !this.overlay || (this.overlay.style.opacity = "0", setTimeout(() => {
37
+ this.overlay && (this.overlay.style.display = "none"), this.isOpen = !1;
38
+ }, 200));
39
+ }
40
+ toggle() {
41
+ this.isOpen ? this.close() : this.open();
42
+ }
43
+ createOverlay() {
44
+ if (this.overlay) return;
45
+ this.overlay = document.createElement("div"), Object.assign(this.overlay.style, {
46
+ position: "fixed",
47
+ top: "0",
48
+ left: "0",
49
+ width: "100%",
50
+ height: "100%",
51
+ background: "rgba(0,0,0,0.5)",
52
+ zIndex: "9999",
53
+ display: "none",
54
+ justifyContent: "center",
55
+ alignItems: "flex-start",
56
+ paddingTop: "10vh",
57
+ transition: "opacity 0.2s ease",
58
+ fontFamily: "sans-serif",
59
+ backdropFilter: "blur(2px)"
60
+ }), this.overlay.addEventListener("click", (r) => {
61
+ r.target === this.overlay && this.close();
62
+ });
63
+ const e = document.createElement("div"), t = this.options.theme === "dark" ? "#1f2937" : "#ffffff", s = this.options.theme === "dark" ? "#f3f4f6" : "#111827", o = this.options.theme === "dark" ? "#374151" : "#e5e7eb";
64
+ Object.assign(e.style, {
65
+ width: "600px",
66
+ maxWidth: "90%",
67
+ maxHeight: "80vh",
68
+ background: t,
69
+ color: s,
70
+ borderRadius: "8px",
71
+ boxShadow: "0 20px 25px -5px rgba(0,0,0,0.1), 0 10px 10px -5px rgba(0,0,0,0.04)",
72
+ overflow: "hidden",
73
+ display: "flex",
74
+ flexDirection: "column",
75
+ border: `1px solid ${o}`
76
+ });
77
+ const i = document.createElement("div");
78
+ i.style.borderBottom = `1px solid ${o}`, i.style.padding = "16px", this.input = document.createElement("input"), Object.assign(this.input.style, {
79
+ width: "100%",
80
+ background: "transparent",
81
+ border: "none",
82
+ outline: "none",
83
+ fontSize: "18px",
84
+ color: s
85
+ }), this.input.placeholder = this.options.placeholder || "Type to search...", this.input.addEventListener("input", (r) => {
86
+ this.filterActions(r.target.value);
87
+ }), i.appendChild(this.input), e.appendChild(i), this.resultsList = document.createElement("div"), Object.assign(this.resultsList.style, {
88
+ overflowY: "auto",
89
+ padding: "8px 0",
90
+ maxHeight: "50vh"
91
+ }), e.appendChild(this.resultsList), this.overlay.appendChild(e), document.body.appendChild(this.overlay);
92
+ }
93
+ filterActions(e) {
94
+ const t = e.toLowerCase().trim();
95
+ t ? this.filteredActions = this.options.actions.filter((s) => {
96
+ var o, i;
97
+ return s.title.toLowerCase().includes(t) || ((o = s.keywords) == null ? void 0 : o.toLowerCase().includes(t)) || ((i = s.section) == null ? void 0 : i.toLowerCase().includes(t));
98
+ }) : this.filteredActions = this.options.actions, this.selectedIndex = 0, this.renderList();
99
+ }
100
+ moveSelection(e) {
101
+ this.selectedIndex += e, this.selectedIndex < 0 && (this.selectedIndex = this.filteredActions.length - 1), this.selectedIndex >= this.filteredActions.length && (this.selectedIndex = 0), this.renderList(), this.ensureVisible();
102
+ }
103
+ ensureVisible() {
104
+ if (!this.resultsList) return;
105
+ const e = this.resultsList.children[this.selectedIndex];
106
+ e && e.scrollIntoView({ block: "nearest" });
107
+ }
108
+ executeSelected() {
109
+ const e = this.filteredActions[this.selectedIndex];
110
+ e && e.handler && (e.handler(), this.close());
111
+ }
112
+ renderList() {
113
+ if (!this.resultsList) return;
114
+ this.resultsList.innerHTML = "";
115
+ const e = this.options.theme === "dark" ? "#374151" : "#f3f4f6", t = this.options.theme === "dark" ? "#9ca3af" : "#6b7280";
116
+ if (this.filteredActions.forEach((s, o) => {
117
+ const i = document.createElement("div"), r = o === this.selectedIndex;
118
+ Object.assign(i.style, {
119
+ padding: "12px 16px",
120
+ display: "flex",
121
+ alignItems: "center",
122
+ justifyContent: "space-between",
123
+ cursor: "pointer",
124
+ background: r ? e : "transparent",
125
+ borderLeft: r ? "4px solid #6366f1" : "4px solid transparent"
126
+ });
127
+ const d = document.createElement("div");
128
+ if (d.style.display = "flex", d.style.alignItems = "center", d.style.gap = "12px", s.icon) {
129
+ const a = document.createElement("span");
130
+ a.innerHTML = s.icon, d.appendChild(a);
131
+ }
132
+ const h = document.createElement("span");
133
+ if (h.textContent = s.title, d.appendChild(h), i.appendChild(d), s.hotkey) {
134
+ const a = document.createElement("div");
135
+ a.textContent = s.hotkey, Object.assign(a.style, {
136
+ fontSize: "12px",
137
+ padding: "2px 6px",
138
+ borderRadius: "4px",
139
+ background: this.options.theme === "dark" ? "#4b5563" : "#e5e7eb",
140
+ color: t
141
+ }), i.appendChild(a);
142
+ }
143
+ i.addEventListener("click", () => {
144
+ s.handler && (s.handler(), this.close());
145
+ }), i.addEventListener("mouseenter", () => {
146
+ this.selectedIndex = o, this.renderList();
147
+ }), this.resultsList.appendChild(i);
148
+ }), this.filteredActions.length === 0) {
149
+ const s = document.createElement("div");
150
+ s.textContent = "No results found.", Object.assign(s.style, {
151
+ padding: "20px",
152
+ textAlign: "center",
153
+ color: t
154
+ }), this.resultsList.appendChild(s);
155
+ }
156
+ }
157
+ }
158
+ export {
159
+ f as NinjaKeys
160
+ };
@@ -0,0 +1 @@
1
+ (function(o,n){typeof exports=="object"&&typeof module<"u"?n(exports):typeof define=="function"&&define.amd?define(["exports"],n):(o=typeof globalThis<"u"?globalThis:o||self,n(o.NinjaKeysCore={}))})(this,function(o){"use strict";var f=Object.defineProperty;var u=(o,n,c)=>n in o?f(o,n,{enumerable:!0,configurable:!0,writable:!0,value:c}):o[n]=c;var l=(o,n,c)=>u(o,typeof n!="symbol"?n+"":n,c);class n{constructor(e){l(this,"options");l(this,"isOpen",!1);l(this,"overlay",null);l(this,"input",null);l(this,"resultsList",null);l(this,"selectedIndex",0);l(this,"filteredActions",[]);l(this,"cleanupFns",[]);this.options={placeholder:"Type a command or search...",theme:"light",...e},this.filteredActions=this.options.actions}init(){this.createOverlay();const e=t=>{(t.ctrlKey||t.metaKey)&&t.key==="k"&&(t.preventDefault(),this.toggle()),t.key==="Escape"&&this.isOpen&&this.close(),this.isOpen&&(t.key==="ArrowDown"?(t.preventDefault(),this.moveSelection(1)):t.key==="ArrowUp"?(t.preventDefault(),this.moveSelection(-1)):t.key==="Enter"&&(t.preventDefault(),this.executeSelected()))};window.addEventListener("keydown",e),this.cleanupFns.push(()=>window.removeEventListener("keydown",e))}cleanup(){this.cleanupFns.forEach(e=>e()),this.overlay&&this.overlay.remove()}open(){this.isOpen||!this.overlay||(this.isOpen=!0,this.overlay.style.display="flex",this.overlay.style.opacity="0",requestAnimationFrame(()=>{this.overlay&&(this.overlay.style.opacity="1")}),this.input&&(this.input.value="",this.input.focus(),this.filterActions("")))}close(){!this.isOpen||!this.overlay||(this.overlay.style.opacity="0",setTimeout(()=>{this.overlay&&(this.overlay.style.display="none"),this.isOpen=!1},200))}toggle(){this.isOpen?this.close():this.open()}createOverlay(){if(this.overlay)return;this.overlay=document.createElement("div"),Object.assign(this.overlay.style,{position:"fixed",top:"0",left:"0",width:"100%",height:"100%",background:"rgba(0,0,0,0.5)",zIndex:"9999",display:"none",justifyContent:"center",alignItems:"flex-start",paddingTop:"10vh",transition:"opacity 0.2s ease",fontFamily:"sans-serif",backdropFilter:"blur(2px)"}),this.overlay.addEventListener("click",d=>{d.target===this.overlay&&this.close()});const e=document.createElement("div"),t=this.options.theme==="dark"?"#1f2937":"#ffffff",i=this.options.theme==="dark"?"#f3f4f6":"#111827",r=this.options.theme==="dark"?"#374151":"#e5e7eb";Object.assign(e.style,{width:"600px",maxWidth:"90%",maxHeight:"80vh",background:t,color:i,borderRadius:"8px",boxShadow:"0 20px 25px -5px rgba(0,0,0,0.1), 0 10px 10px -5px rgba(0,0,0,0.04)",overflow:"hidden",display:"flex",flexDirection:"column",border:`1px solid ${r}`});const s=document.createElement("div");s.style.borderBottom=`1px solid ${r}`,s.style.padding="16px",this.input=document.createElement("input"),Object.assign(this.input.style,{width:"100%",background:"transparent",border:"none",outline:"none",fontSize:"18px",color:i}),this.input.placeholder=this.options.placeholder||"Type to search...",this.input.addEventListener("input",d=>{this.filterActions(d.target.value)}),s.appendChild(this.input),e.appendChild(s),this.resultsList=document.createElement("div"),Object.assign(this.resultsList.style,{overflowY:"auto",padding:"8px 0",maxHeight:"50vh"}),e.appendChild(this.resultsList),this.overlay.appendChild(e),document.body.appendChild(this.overlay)}filterActions(e){const t=e.toLowerCase().trim();t?this.filteredActions=this.options.actions.filter(i=>{var r,s;return i.title.toLowerCase().includes(t)||((r=i.keywords)==null?void 0:r.toLowerCase().includes(t))||((s=i.section)==null?void 0:s.toLowerCase().includes(t))}):this.filteredActions=this.options.actions,this.selectedIndex=0,this.renderList()}moveSelection(e){this.selectedIndex+=e,this.selectedIndex<0&&(this.selectedIndex=this.filteredActions.length-1),this.selectedIndex>=this.filteredActions.length&&(this.selectedIndex=0),this.renderList(),this.ensureVisible()}ensureVisible(){if(!this.resultsList)return;const e=this.resultsList.children[this.selectedIndex];e&&e.scrollIntoView({block:"nearest"})}executeSelected(){const e=this.filteredActions[this.selectedIndex];e&&e.handler&&(e.handler(),this.close())}renderList(){if(!this.resultsList)return;this.resultsList.innerHTML="";const e=this.options.theme==="dark"?"#374151":"#f3f4f6",t=this.options.theme==="dark"?"#9ca3af":"#6b7280";if(this.filteredActions.forEach((i,r)=>{const s=document.createElement("div"),d=r===this.selectedIndex;Object.assign(s.style,{padding:"12px 16px",display:"flex",alignItems:"center",justifyContent:"space-between",cursor:"pointer",background:d?e:"transparent",borderLeft:d?"4px solid #6366f1":"4px solid transparent"});const a=document.createElement("div");if(a.style.display="flex",a.style.alignItems="center",a.style.gap="12px",i.icon){const h=document.createElement("span");h.innerHTML=i.icon,a.appendChild(h)}const p=document.createElement("span");if(p.textContent=i.title,a.appendChild(p),s.appendChild(a),i.hotkey){const h=document.createElement("div");h.textContent=i.hotkey,Object.assign(h.style,{fontSize:"12px",padding:"2px 6px",borderRadius:"4px",background:this.options.theme==="dark"?"#4b5563":"#e5e7eb",color:t}),s.appendChild(h)}s.addEventListener("click",()=>{i.handler&&(i.handler(),this.close())}),s.addEventListener("mouseenter",()=>{this.selectedIndex=r,this.renderList()}),this.resultsList.appendChild(s)}),this.filteredActions.length===0){const i=document.createElement("div");i.textContent="No results found.",Object.assign(i.style,{padding:"20px",textAlign:"center",color:t}),this.resultsList.appendChild(i)}}}o.NinjaKeys=n,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"})});
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@jojovms/ninja-keys-core",
3
+ "version": "0.0.1",
4
+ "description": "Commandō: Your commands, unleashed. A VS Code-style command palette for your web applications.",
5
+ "keywords": [
6
+ "commando",
7
+ "command-palette",
8
+ "ninja-keys",
9
+ "keyboard-shortcuts",
10
+ "a11y",
11
+ "framework",
12
+ "jojovms"
13
+ ],
14
+ "author": "Shijo Shaji (https://shijoshaji.in)",
15
+ "homepage": "https://shijoshaji.in",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/shijoshaji/ninja-keys.git",
19
+ "directory": "core"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/shijoshaji/ninja-keys/issues"
23
+ },
24
+ "license": "MIT",
25
+ "main": "dist/index.js",
26
+ "module": "dist/index.mjs",
27
+ "types": "dist/index.d.ts",
28
+ "files": [
29
+ "dist"
30
+ ],
31
+ "scripts": {
32
+ "build": "vite build",
33
+ "dev": "vite build --watch"
34
+ },
35
+ "devDependencies": {
36
+ "typescript": "^5.0.0",
37
+ "vite": "^5.0.0",
38
+ "vite-plugin-dts": "^3.0.0"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ }
43
+ }