@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 +2 -2
- package/dist/global.d.ts +6 -20
- package/dist/index.d.ts +113 -24
- package/dist/index.js +268 -137
- package/package.json +6 -2
- package/dist/index.js.map +0 -1
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
|
-
|
|
10
|
+
pnpm add @ailuracode/alpine-command alpinejs
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Store API
|
|
14
14
|
|
|
15
|
-
```
|
|
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
|
-
|
|
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
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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/
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
store.search += event.key;
|
|
33
|
-
store.activeIndex = 0;
|
|
34
|
-
return true;
|
|
36
|
+
get search() {
|
|
37
|
+
return this.#search;
|
|
35
38
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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": "
|
|
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 --
|
|
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":[]}
|