@ailuracode/alpine-command 0.1.0 → 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/README.md CHANGED
@@ -7,12 +7,12 @@ Headless command palette store for Alpine.js — searchable actions, keyboard na
7
7
  ## Install
8
8
 
9
9
  ```bash
10
- npm install @ailuracode/alpine-command alpinejs
10
+ pnpm add @ailuracode/alpine-command alpinejs
11
11
  ```
12
12
 
13
13
  ## Store API
14
14
 
15
- ```js
15
+ ```ts
16
16
  $store.command.open();
17
17
  $store.command.search = "theme";
18
18
  $store.command.register({
package/dist/global.d.ts CHANGED
@@ -1,16 +1,8 @@
1
1
  /// <reference types="@types/alpinejs" />
2
2
 
3
- export type CommandAction = () => void | Promise<void>;
3
+ import type { CommandItem } from "./types";
4
4
 
5
- export type CommandItem = {
6
- id: string;
7
- label: string;
8
- group?: string;
9
- shortcut?: string;
10
- keywords?: string[];
11
- disabled?: boolean;
12
- action: CommandAction;
13
- };
5
+ export type { CommandAction, CommandItem, CommandStoreConfig } from "./types";
14
6
 
15
7
  export interface CommandStore {
16
8
  search: string;
@@ -30,18 +22,12 @@ export interface CommandStore {
30
22
  destroy(): void;
31
23
  }
32
24
 
33
- export interface CommandPluginOptions {
34
- onOpen?: () => void;
35
- onClose?: () => void;
36
- onRun?: (item: CommandItem) => void;
37
- filter?: (item: CommandItem, search: string) => boolean;
38
- }
39
-
40
- export function commandOptions<const T extends CommandPluginOptions>(options: T): T;
41
- export function createCommandStore(options?: CommandPluginOptions): CommandStore;
25
+ export function createCommandController(
26
+ config?: import("./types").CommandStoreConfig
27
+ ): import("./types").CommandController;
42
28
 
43
29
  export default function commandPlugin(
44
- options?: CommandPluginOptions
30
+ options?: import("./types").CommandPluginOptions
45
31
  ): import("alpinejs").PluginCallback;
46
32
 
47
33
  declare global {
package/dist/index.d.ts CHANGED
@@ -1,6 +1,17 @@
1
- import AlpineType from 'alpinejs';
1
+ import { Alpine, PluginCallback, BaseController } from '@ailuracode/alpine-core';
2
+ export { Unsubscribe } from '@ailuracode/alpine-core';
3
+ import { Alpine as Alpine$1 } from 'alpinejs';
2
4
 
5
+ /**
6
+ * Public type contracts for `@ailuracode/alpine-command`.
7
+ *
8
+ * Every public type lives in a `types.ts` module so consumers can import
9
+ * them without pulling the implementation. The shape IS the contract.
10
+ */
11
+
12
+ /** Action callback invoked when a command is executed. */
3
13
  type CommandAction = () => void | Promise<void>;
14
+ /** A registered command item. */
4
15
  type CommandItem = {
5
16
  id: string;
6
17
  label: string;
@@ -10,7 +21,19 @@ type CommandItem = {
10
21
  disabled?: boolean;
11
22
  action: CommandAction;
12
23
  };
13
- type CommandStore = {
24
+ /** Config callbacks for the command palette. */
25
+ type CommandStoreConfig = {
26
+ onOpen?: () => void;
27
+ onClose?: () => void;
28
+ onRun?: (item: CommandItem) => void;
29
+ filter?: (item: CommandItem, search: string) => boolean;
30
+ };
31
+ /** Options accepted by the command plugin factory. */
32
+ interface CommandPluginOptions extends CommandStoreConfig {
33
+ readonly id?: string;
34
+ }
35
+ /** Alpine-facing store surface. */
36
+ interface CommandStore {
14
37
  search: string;
15
38
  activeIndex: number;
16
39
  /** Whether the palette is visible. */
@@ -28,31 +51,97 @@ type CommandStore = {
28
51
  readonly filteredItems: CommandItem[];
29
52
  readonly groupedItems: Record<string, CommandItem[]>;
30
53
  destroy(): void;
54
+ }
55
+ /** Typed view of `Alpine` the command plugin uses internally. */
56
+ type CommandAlpine = Alpine<{
57
+ command: CommandStore;
58
+ }> & {
59
+ cleanup?(callback: () => void): void;
31
60
  };
32
- type CommandStoreConfig = {
33
- onOpen?: () => void;
34
- onClose?: () => void;
35
- onRun?: (item: CommandItem) => void;
36
- filter?: (item: CommandItem, search: string) => boolean;
37
- };
38
- /** Creates the headless command palette store. */
39
- declare function createCommandStore(config?: CommandStoreConfig): CommandStore;
61
+ /** `Alpine.plugin()` callback signature. */
62
+ type CommandPluginCallback = PluginCallback<Alpine$1>;
40
63
 
41
- interface CommandPluginOptions extends CommandStoreConfig {
64
+ /**
65
+ * Strongly-typed event map for the command controller.
66
+ */
67
+
68
+ /**
69
+ * Event map for command palette state changes.
70
+ * `open` — palette opened.
71
+ * `close` — palette closed.
72
+ * `run` — a command was executed.
73
+ */
74
+ interface CommandEvents extends Record<string, unknown> {
75
+ open: undefined;
76
+ close: undefined;
77
+ run: CommandItem;
78
+ }
79
+
80
+ /**
81
+ * Command palette controller — the framework-agnostic core of
82
+ * `@ailuracode/alpine-command`. Manages a singleton command palette
83
+ * with search, item registry, open/close/toggle, keyboard navigation,
84
+ * and filtered/grouped item views.
85
+ *
86
+ * Emits typed `open`, `close`, and `run` events so consumers can
87
+ * react programmatically.
88
+ */
89
+
90
+ /**
91
+ * Headless command palette controller. Manages a singleton command palette
92
+ * with search, item registry, open/close/toggle, keyboard navigation,
93
+ * and filtered/grouped item views.
94
+ */
95
+ declare class CommandController extends BaseController<CommandEvents> {
96
+ #private;
97
+ constructor(id?: string, config?: CommandStoreConfig);
98
+ get items(): Readonly<Record<string, CommandItem>>;
99
+ get search(): string;
100
+ set search(value: string);
101
+ get activeIndex(): number;
102
+ set activeIndex(value: number);
103
+ get visible(): boolean;
104
+ get isOpen(): boolean;
105
+ get filteredItems(): CommandItem[];
106
+ get groupedItems(): Record<string, CommandItem[]>;
107
+ open(): void;
108
+ close(): void;
109
+ toggle(): void;
110
+ register(item: CommandItem): void;
111
+ unregister(id: string): void;
112
+ run(id: string): Promise<void>;
113
+ handleKeydown(event: KeyboardEvent): void;
114
+ /**
115
+ * Returns a store-shaped object for Alpine's `$store.command`.
116
+ * The store delegates to this controller.
117
+ */
118
+ toStore(): CommandStore;
42
119
  }
120
+ /**
121
+ * Creates a CommandController instance.
122
+ * Convenience for non-Alpine consumers.
123
+ */
124
+ declare function createCommandController(config?: CommandStoreConfig): CommandController;
125
+ /**
126
+ * Creates a CommandStore (store-shaped object) directly.
127
+ * Backward-compatible alias.
128
+ */
129
+ declare function createCommandStore(config?: CommandStoreConfig): CommandStore;
130
+
131
+ /**
132
+ * Alpine.js integration for `@ailuracode/alpine-command`.
133
+ *
134
+ * Thin adapter that wires {@link CommandController} into
135
+ * `$store.command` and the `$command` magic.
136
+ */
137
+
138
+ /**
139
+ * Plugin factory — returns the `Alpine.plugin()` callback. Pass
140
+ * {@link CommandPluginOptions} to configure the controller,
141
+ * or `{}` for the package defaults.
142
+ */
143
+ declare function commandPlugin(options?: CommandPluginOptions): CommandPluginCallback;
43
144
  /** Builds typed command plugin options. */
44
145
  declare function commandOptions<const T extends CommandPluginOptions>(options: T): T;
45
- /** Alpine.js command palette plugin. Registers `$store.command`. */
46
- declare function commandPlugin(options?: CommandPluginOptions): AlpineType.PluginCallback;
47
- declare global {
48
- namespace Alpine {
49
- interface Stores {
50
- command: CommandStore;
51
- }
52
- interface Magics<T> {
53
- $command: CommandStore;
54
- }
55
- }
56
- }
57
146
 
58
- export { type CommandAction, type CommandItem, type CommandPluginOptions, type CommandStore, type CommandStoreConfig, commandOptions, createCommandStore, commandPlugin as default };
147
+ export { type CommandAction, type CommandAlpine, CommandController, type CommandEvents, type CommandItem, type CommandPluginCallback, type CommandPluginOptions, type CommandStore, type CommandStoreConfig, commandOptions, commandPlugin, createCommandController, createCommandStore, commandPlugin as default };
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
- // src/store.ts
1
+ // src/controller.ts
2
+ import { BaseController, generateId } from "@ailuracode/alpine-core";
2
3
  function defaultFilter(item, search) {
3
4
  if (!search.trim()) {
4
5
  return true;
@@ -17,157 +18,287 @@ function isEditableTarget(target) {
17
18
  function isTypingKey(event) {
18
19
  return event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey && !event.isComposing;
19
20
  }
20
- function handlePaletteTyping(store, event) {
21
- if (isEditableTarget(event.target)) {
22
- return false;
21
+ var CommandController = class extends BaseController {
22
+ #items = {};
23
+ #search = "";
24
+ #activeIndex = 0;
25
+ #visible = false;
26
+ #filter;
27
+ #config;
28
+ constructor(id, config = {}) {
29
+ super(id ?? generateId("command"));
30
+ this.#filter = config.filter ?? defaultFilter;
31
+ this.#config = config;
23
32
  }
24
- if (event.key === "Backspace") {
25
- event.preventDefault();
26
- store.search = store.search.slice(0, -1);
27
- store.activeIndex = 0;
28
- return true;
33
+ get items() {
34
+ return this.#items;
29
35
  }
30
- if (isTypingKey(event)) {
31
- event.preventDefault();
32
- store.search += event.key;
33
- store.activeIndex = 0;
34
- return true;
36
+ get search() {
37
+ return this.#search;
35
38
  }
36
- return false;
37
- }
38
- function createCommandStore(config = {}) {
39
- const filter = config.filter ?? defaultFilter;
40
- const store = {
41
- search: "",
42
- activeIndex: 0,
43
- visible: false,
44
- items: {},
45
- get isOpen() {
46
- return this.visible;
47
- },
48
- get filteredItems() {
49
- const list = Object.values(this.items).filter(
50
- (item) => !item.disabled && filter(item, this.search)
51
- );
52
- if (this.activeIndex >= list.length) {
53
- this.activeIndex = Math.max(list.length - 1, 0);
54
- }
55
- return list;
56
- },
57
- get groupedItems() {
58
- const groups = {};
59
- for (const item of this.filteredItems) {
60
- const group = item.group ?? "General";
61
- groups[group] ??= [];
62
- groups[group].push(item);
63
- }
64
- return groups;
65
- },
66
- open() {
67
- if (this.visible) {
68
- return;
69
- }
70
- this.visible = true;
71
- this.search = "";
72
- this.activeIndex = 0;
73
- config.onOpen?.();
74
- },
75
- close() {
76
- if (!this.visible) {
77
- return;
78
- }
79
- this.visible = false;
80
- this.search = "";
81
- this.activeIndex = 0;
82
- config.onClose?.();
83
- },
84
- toggle() {
85
- if (this.visible) {
86
- this.close();
87
- } else {
88
- this.open();
89
- }
90
- },
91
- register(item) {
92
- this.items[item.id] = item;
93
- },
94
- unregister(id) {
95
- delete this.items[id];
96
- },
97
- async run(id) {
98
- const item = this.items[id];
99
- if (!item || item.disabled) {
100
- return;
101
- }
102
- await item.action();
103
- config.onRun?.(item);
39
+ set search(value) {
40
+ this.#search = value;
41
+ }
42
+ get activeIndex() {
43
+ return this.#activeIndex;
44
+ }
45
+ set activeIndex(value) {
46
+ this.#activeIndex = value;
47
+ }
48
+ get visible() {
49
+ return this.#visible;
50
+ }
51
+ get isOpen() {
52
+ return this.#visible;
53
+ }
54
+ get filteredItems() {
55
+ const list = Object.values(this.#items).filter(
56
+ (item) => !item.disabled && this.#filter(item, this.#search)
57
+ );
58
+ if (this.#activeIndex >= list.length) {
59
+ this.#activeIndex = Math.max(list.length - 1, 0);
60
+ }
61
+ return list;
62
+ }
63
+ get groupedItems() {
64
+ const groups = {};
65
+ for (const item of this.filteredItems) {
66
+ const group = item.group ?? "General";
67
+ groups[group] ??= [];
68
+ groups[group].push(item);
69
+ }
70
+ return groups;
71
+ }
72
+ open() {
73
+ if (this.isDestroyed || this.#visible) {
74
+ return;
75
+ }
76
+ this.#visible = true;
77
+ this.#search = "";
78
+ this.#activeIndex = 0;
79
+ this.#config.onOpen?.();
80
+ this.emit("open", void 0);
81
+ }
82
+ close() {
83
+ if (this.isDestroyed || !this.#visible) {
84
+ return;
85
+ }
86
+ this.#visible = false;
87
+ this.#search = "";
88
+ this.#activeIndex = 0;
89
+ this.#config.onClose?.();
90
+ this.emit("close", void 0);
91
+ }
92
+ toggle() {
93
+ if (this.#visible) {
104
94
  this.close();
105
- },
106
- handleKeydown(event) {
107
- if (!this.visible) {
108
- return;
109
- }
110
- const list = this.filteredItems;
111
- if (handlePaletteTyping(this, event)) {
112
- return;
95
+ } else {
96
+ this.open();
97
+ }
98
+ }
99
+ register(item) {
100
+ if (this.isDestroyed) {
101
+ return;
102
+ }
103
+ this.#items[item.id] = item;
104
+ }
105
+ unregister(id) {
106
+ if (this.isDestroyed) {
107
+ return;
108
+ }
109
+ delete this.#items[id];
110
+ }
111
+ async run(id) {
112
+ if (this.isDestroyed) {
113
+ return;
114
+ }
115
+ const item = this.#items[id];
116
+ if (!item || item.disabled) {
117
+ return;
118
+ }
119
+ await item.action();
120
+ this.#config.onRun?.(item);
121
+ this.emit("run", item);
122
+ this.close();
123
+ }
124
+ handleKeydown(event) {
125
+ if (this.isDestroyed || !this.#visible) {
126
+ return;
127
+ }
128
+ const list = this.filteredItems;
129
+ if (isEditableTarget(event.target)) {
130
+ } else if (event.key === "Backspace") {
131
+ event.preventDefault();
132
+ this.#search = this.#search.slice(0, -1);
133
+ this.#activeIndex = 0;
134
+ return;
135
+ } else if (isTypingKey(event)) {
136
+ event.preventDefault();
137
+ this.#search += event.key;
138
+ this.#activeIndex = 0;
139
+ return;
140
+ }
141
+ switch (event.key) {
142
+ case "ArrowDown":
143
+ event.preventDefault();
144
+ this.#activeIndex = list.length === 0 ? 0 : (this.#activeIndex + 1) % list.length;
145
+ break;
146
+ case "ArrowUp":
147
+ event.preventDefault();
148
+ this.#activeIndex = list.length === 0 ? 0 : (this.#activeIndex - 1 + list.length) % list.length;
149
+ break;
150
+ case "Home":
151
+ event.preventDefault();
152
+ this.#activeIndex = 0;
153
+ break;
154
+ case "End":
155
+ event.preventDefault();
156
+ this.#activeIndex = Math.max(list.length - 1, 0);
157
+ break;
158
+ case "Enter": {
159
+ event.preventDefault();
160
+ const active = list[this.#activeIndex];
161
+ if (active) {
162
+ void this.run(active.id);
163
+ }
164
+ break;
113
165
  }
114
- switch (event.key) {
115
- case "ArrowDown":
116
- event.preventDefault();
117
- this.activeIndex = list.length === 0 ? 0 : (this.activeIndex + 1) % list.length;
118
- break;
119
- case "ArrowUp":
120
- event.preventDefault();
121
- this.activeIndex = list.length === 0 ? 0 : (this.activeIndex - 1 + list.length) % list.length;
122
- break;
123
- case "Home":
124
- event.preventDefault();
125
- this.activeIndex = 0;
126
- break;
127
- case "End":
128
- event.preventDefault();
129
- this.activeIndex = Math.max(list.length - 1, 0);
130
- break;
131
- case "Enter": {
132
- event.preventDefault();
133
- const active = list[this.activeIndex];
134
- if (active) {
135
- void this.run(active.id);
166
+ case "Escape":
167
+ event.preventDefault();
168
+ this.close();
169
+ break;
170
+ default:
171
+ break;
172
+ }
173
+ }
174
+ /**
175
+ * Returns a store-shaped object for Alpine's `$store.command`.
176
+ * The store delegates to this controller.
177
+ */
178
+ toStore() {
179
+ const controller = this;
180
+ return {
181
+ get search() {
182
+ return controller.search;
183
+ },
184
+ set search(value) {
185
+ controller.search = value;
186
+ },
187
+ get activeIndex() {
188
+ return controller.activeIndex;
189
+ },
190
+ set activeIndex(value) {
191
+ controller.activeIndex = value;
192
+ },
193
+ get visible() {
194
+ return controller.visible;
195
+ },
196
+ get items() {
197
+ return controller.items;
198
+ },
199
+ get isOpen() {
200
+ return controller.isOpen;
201
+ },
202
+ open: () => controller.open(),
203
+ close: () => controller.close(),
204
+ toggle: () => controller.toggle(),
205
+ register: (item) => controller.register(item),
206
+ unregister: (id) => controller.unregister(id),
207
+ run: (id) => controller.run(id),
208
+ handleKeydown: (event) => controller.handleKeydown(event),
209
+ get filteredItems() {
210
+ return controller.filteredItems;
211
+ },
212
+ get groupedItems() {
213
+ return controller.groupedItems;
214
+ },
215
+ destroy: () => controller.destroy()
216
+ };
217
+ }
218
+ };
219
+ function createCommandController(config = {}) {
220
+ return new CommandController(void 0, config);
221
+ }
222
+ function createCommandStore(config = {}) {
223
+ return new CommandController(void 0, config).toStore();
224
+ }
225
+
226
+ // src/plugin.ts
227
+ var COMMAND_STORE_KEY = "command";
228
+ function commandPlugin(options = {}) {
229
+ return function registerCommand(alpine) {
230
+ const Alpine = alpine;
231
+ const controller = new CommandController(options.id, options);
232
+ const store = {
233
+ search: "",
234
+ activeIndex: 0,
235
+ visible: false,
236
+ items: {},
237
+ get isOpen() {
238
+ return this.visible;
239
+ },
240
+ get filteredItems() {
241
+ const defaultFilter2 = (i, s) => {
242
+ if (!s.trim()) {
243
+ return true;
136
244
  }
137
- break;
245
+ const query = s.trim().toLowerCase();
246
+ const haystack = [i.label, i.group ?? "", i.shortcut ?? "", ...i.keywords ?? []].join(" ").toLowerCase();
247
+ return haystack.includes(query);
248
+ };
249
+ const filter = options.filter ?? defaultFilter2;
250
+ const list = Object.values(this.items).filter(
251
+ (item) => !item.disabled && filter(item, this.search)
252
+ );
253
+ if (this.activeIndex >= list.length) {
254
+ this.activeIndex = Math.max(list.length - 1, 0);
138
255
  }
139
- case "Escape":
140
- event.preventDefault();
141
- this.close();
142
- break;
143
- default:
144
- break;
145
- }
146
- },
147
- destroy() {
148
- this.items = {};
149
- this.visible = false;
150
- this.search = "";
151
- this.activeIndex = 0;
256
+ return list;
257
+ },
258
+ get groupedItems() {
259
+ const groups = {};
260
+ for (const item of this.filteredItems) {
261
+ const group = item.group ?? "General";
262
+ groups[group] ??= [];
263
+ groups[group].push(item);
264
+ }
265
+ return groups;
266
+ },
267
+ open: () => controller.open(),
268
+ close: () => controller.close(),
269
+ toggle: () => controller.toggle(),
270
+ register: (item) => controller.register(item),
271
+ unregister: (id) => controller.unregister(id),
272
+ run: (id) => controller.run(id),
273
+ handleKeydown: (event) => controller.handleKeydown(event),
274
+ destroy: () => controller.destroy()
275
+ };
276
+ Alpine.store(COMMAND_STORE_KEY, store);
277
+ const reactiveStore = Alpine.store(COMMAND_STORE_KEY);
278
+ controller.on("open", () => {
279
+ reactiveStore.visible = true;
280
+ reactiveStore.search = "";
281
+ reactiveStore.activeIndex = 0;
282
+ });
283
+ controller.on("close", () => {
284
+ reactiveStore.visible = false;
285
+ reactiveStore.search = "";
286
+ reactiveStore.activeIndex = 0;
287
+ });
288
+ Alpine.magic(COMMAND_STORE_KEY, () => reactiveStore);
289
+ if (typeof Alpine.cleanup === "function") {
290
+ Alpine.cleanup(() => controller.destroy());
152
291
  }
153
292
  };
154
- return store;
155
293
  }
156
-
157
- // src/index.ts
158
294
  function commandOptions(options) {
159
295
  return options;
160
296
  }
161
- function commandPlugin(options = {}) {
162
- return function registerCommand(Alpine) {
163
- const store = createCommandStore(options);
164
- Alpine.store("command", store);
165
- Alpine.magic("command", () => Alpine.store("command"));
166
- };
167
- }
168
297
  export {
298
+ CommandController,
169
299
  commandOptions,
300
+ commandPlugin,
301
+ createCommandController,
170
302
  createCommandStore,
171
303
  commandPlugin as default
172
304
  };
173
- //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ailuracode/alpine-command",
3
- "version": "0.1.0",
3
+ "version": "1.0.0",
4
4
  "description": "Alpine.js headless command palette store",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -31,7 +31,11 @@
31
31
  }
32
32
  },
33
33
  "types": "./dist/global.d.ts",
34
+ "devDependencies": {
35
+ "@ailuracode/alpine-core": "0.2.0"
36
+ },
34
37
  "peerDependencies": {
38
+ "@ailuracode/alpine-core": "^0.2.0",
35
39
  "alpinejs": "^3.0.0",
36
40
  "@types/alpinejs": "^3.13.11"
37
41
  },
@@ -50,7 +54,7 @@
50
54
  "headless"
51
55
  ],
52
56
  "scripts": {
53
- "build": "tsup src/index.ts --format esm --dts --clean --sourcemap --out-dir dist && cp src/global.d.ts dist/global.d.ts",
57
+ "build": "tsup src/index.ts --format esm --dts --clean --out-dir dist && cp src/global.d.ts dist/global.d.ts",
54
58
  "test": "vitest run --config ../../vitest.config.ts test"
55
59
  }
56
60
  }
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/store.ts","../src/index.ts"],"sourcesContent":["export type CommandAction = () => void | Promise<void>;\n\nexport type CommandItem = {\n id: string;\n label: string;\n group?: string;\n shortcut?: string;\n keywords?: string[];\n disabled?: boolean;\n action: CommandAction;\n};\n\nexport type CommandStore = {\n search: string;\n activeIndex: number;\n /** Whether the palette is visible. */\n visible: boolean;\n /** Registered command items. */\n items: Record<string, CommandItem>;\n readonly isOpen: boolean;\n open(): void;\n close(): void;\n toggle(): void;\n register(item: CommandItem): void;\n unregister(id: string): void;\n run(id: string): Promise<void>;\n handleKeydown(event: KeyboardEvent): void;\n readonly filteredItems: CommandItem[];\n readonly groupedItems: Record<string, CommandItem[]>;\n destroy(): void;\n};\n\nexport type CommandStoreConfig = {\n onOpen?: () => void;\n onClose?: () => void;\n onRun?: (item: CommandItem) => void;\n filter?: (item: CommandItem, search: string) => boolean;\n};\n\nfunction defaultFilter(item: CommandItem, search: string): boolean {\n if (!search.trim()) {\n return true;\n }\n\n const query = search.trim().toLowerCase();\n const haystack = [item.label, item.group ?? \"\", item.shortcut ?? \"\", ...(item.keywords ?? [])]\n .join(\" \")\n .toLowerCase();\n\n return haystack.includes(query);\n}\n\nfunction isEditableTarget(target: EventTarget | null): boolean {\n if (!(target instanceof HTMLElement)) {\n return false;\n }\n\n const tag = target.tagName;\n return tag === \"INPUT\" || tag === \"TEXTAREA\" || target.isContentEditable;\n}\n\nfunction isTypingKey(event: KeyboardEvent): boolean {\n return (\n event.key.length === 1 &&\n !event.metaKey &&\n !event.ctrlKey &&\n !event.altKey &&\n !event.isComposing\n );\n}\n\nfunction handlePaletteTyping(\n store: Pick<CommandStore, \"search\" | \"activeIndex\">,\n event: KeyboardEvent\n): boolean {\n if (isEditableTarget(event.target)) {\n return false;\n }\n\n if (event.key === \"Backspace\") {\n event.preventDefault();\n store.search = store.search.slice(0, -1);\n store.activeIndex = 0;\n return true;\n }\n\n if (isTypingKey(event)) {\n event.preventDefault();\n store.search += event.key;\n store.activeIndex = 0;\n return true;\n }\n\n return false;\n}\n\n/** Creates the headless command palette store. */\nexport function createCommandStore(config: CommandStoreConfig = {}): CommandStore {\n const filter = config.filter ?? defaultFilter;\n\n const store: CommandStore = {\n search: \"\",\n activeIndex: 0,\n visible: false,\n items: {},\n\n get isOpen() {\n return this.visible;\n },\n\n get filteredItems() {\n const list = Object.values(this.items).filter(\n (item) => !item.disabled && filter(item, this.search)\n );\n if (this.activeIndex >= list.length) {\n this.activeIndex = Math.max(list.length - 1, 0);\n }\n return list;\n },\n\n get groupedItems() {\n const groups: Record<string, CommandItem[]> = {};\n for (const item of this.filteredItems) {\n const group = item.group ?? \"General\";\n groups[group] ??= [];\n groups[group].push(item);\n }\n return groups;\n },\n\n open() {\n if (this.visible) {\n return;\n }\n this.visible = true;\n this.search = \"\";\n this.activeIndex = 0;\n config.onOpen?.();\n },\n\n close() {\n if (!this.visible) {\n return;\n }\n this.visible = false;\n this.search = \"\";\n this.activeIndex = 0;\n config.onClose?.();\n },\n\n toggle() {\n if (this.visible) {\n this.close();\n } else {\n this.open();\n }\n },\n\n register(item) {\n this.items[item.id] = item;\n },\n\n unregister(id) {\n delete this.items[id];\n },\n\n async run(id) {\n const item = this.items[id];\n if (!item || item.disabled) {\n return;\n }\n\n await item.action();\n config.onRun?.(item);\n this.close();\n },\n\n handleKeydown(event) {\n if (!this.visible) {\n return;\n }\n\n const list = this.filteredItems;\n\n if (handlePaletteTyping(this, event)) {\n return;\n }\n\n switch (event.key) {\n case \"ArrowDown\":\n event.preventDefault();\n this.activeIndex = list.length === 0 ? 0 : (this.activeIndex + 1) % list.length;\n break;\n case \"ArrowUp\":\n event.preventDefault();\n this.activeIndex =\n list.length === 0 ? 0 : (this.activeIndex - 1 + list.length) % list.length;\n break;\n case \"Home\":\n event.preventDefault();\n this.activeIndex = 0;\n break;\n case \"End\":\n event.preventDefault();\n this.activeIndex = Math.max(list.length - 1, 0);\n break;\n case \"Enter\": {\n event.preventDefault();\n const active = list[this.activeIndex];\n if (active) {\n void this.run(active.id);\n }\n break;\n }\n case \"Escape\":\n event.preventDefault();\n this.close();\n break;\n default:\n break;\n }\n },\n\n destroy() {\n this.items = {};\n this.visible = false;\n this.search = \"\";\n this.activeIndex = 0;\n },\n };\n\n return store;\n}\n","import type AlpineType from \"alpinejs\";\nimport { type CommandStore, type CommandStoreConfig, createCommandStore } from \"./store.js\";\n\nexport {\n type CommandAction,\n type CommandItem,\n type CommandStore,\n type CommandStoreConfig,\n createCommandStore,\n} from \"./store.js\";\n\nexport interface CommandPluginOptions extends CommandStoreConfig {}\n\n/** Builds typed command plugin options. */\nexport function commandOptions<const T extends CommandPluginOptions>(options: T): T {\n return options;\n}\n\n/** Alpine.js command palette plugin. Registers `$store.command`. */\nexport default function commandPlugin(\n options: CommandPluginOptions = {}\n): AlpineType.PluginCallback {\n return function registerCommand(Alpine) {\n const store = createCommandStore(options);\n Alpine.store(\"command\", store);\n Alpine.magic(\"command\", () => Alpine.store(\"command\"));\n };\n}\n\ndeclare global {\n namespace Alpine {\n interface Stores {\n command: CommandStore;\n }\n interface Magics<T> {\n $command: CommandStore;\n }\n }\n}\n"],"mappings":"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","names":[]}