@ailuracode/alpine-command 1.0.0 → 2.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
@@ -1,26 +1,135 @@
1
1
  # @ailuracode/alpine-command
2
2
 
3
- Headless command palette store for Alpine.js — searchable actions, keyboard navigation, groups, and shortcuts. Compose optionally with dialog and toast plugins.
4
-
5
- **[Full documentation →](../../docs/plugins/command.md)**
3
+ Headless command palette (Spotlight-style) store — searchable actions, groups, keyboard navigation, nested pages, async execution, aliases, and ARIA helpers.
6
4
 
7
5
  ## Install
8
6
 
9
7
  ```bash
10
- pnpm add @ailuracode/alpine-command alpinejs
8
+ pnpm add @ailuracode/alpine-command @ailuracode/alpine-core alpinejs
9
+ ```
10
+
11
+ Active item navigation uses inline helpers — no extra dependency.
12
+
13
+ ## Quick start
14
+
15
+ ```js
16
+ import Alpine from "alpinejs";
17
+ import { commandPlugin } from "@ailuracode/alpine-command";
18
+
19
+ Alpine.plugin(
20
+ commandPlugin({
21
+ searchStrategy: "substring",
22
+ onRun(item) {
23
+ console.log("Ran", item.id);
24
+ },
25
+ persistence: {
26
+ maxRecent: 8,
27
+ getRecent: () => JSON.parse(localStorage.getItem("recent-commands") ?? "[]"),
28
+ setRecent: (ids) => localStorage.setItem("recent-commands", JSON.stringify(ids)),
29
+ },
30
+ })
31
+ );
32
+ Alpine.start();
11
33
  ```
12
34
 
13
35
  ## Store API
14
36
 
37
+ | Member | Description |
38
+ |--------|-------------|
39
+ | `open()` / `close()` / `toggle()` | Palette visibility |
40
+ | `isOpen` | Whether the palette is open |
41
+ | `search` | Reactive filter string |
42
+ | `activeIndex` | Keyboard-highlighted row |
43
+ | `filteredItems` / `visibleItems` | Visible commands for the current page |
44
+ | `groupedItems` | Filtered items grouped by `group` |
45
+ | `register(item)` | Register an action; returns `unregister()` |
46
+ | `run(id)` / `cancelRun()` | Execute or cancel in-flight async work |
47
+ | `pushPage(page)` / `goBack()` | Nested command pages |
48
+ | `executionState` / `runningId` | Async execution state |
49
+ | `inputProps()` / `listboxProps()` / `optionProps(id)` | Headless ARIA props |
50
+ | `handleKeydown(event)` | Typing, Backspace, Arrow/Home/End/Enter/Escape |
51
+
52
+ ### Command item
53
+
15
54
  ```ts
16
- $store.command.open();
17
- $store.command.search = "theme";
18
- $store.command.register({
55
+ {
19
56
  id: "toggle-theme",
20
57
  label: "Toggle theme",
21
- group: "Appearance",
22
- shortcut: "⌘K",
58
+ group?: "Appearance",
59
+ shortcut?: "⌘K",
60
+ keywords?: ["dark", "light"],
61
+ aliases?: ["spotlight"],
62
+ disabled?: false | (() => boolean),
63
+ hidden?: false | (() => boolean),
64
+ enabled?: true | (() => boolean),
65
+ pinned?: false,
66
+ page?: "root",
67
+ load?: async () => {},
23
68
  action: () => {},
24
- });
25
- $store.command.run("toggle-theme");
69
+ }
26
70
  ```
71
+
72
+ ### Search
73
+
74
+ - Default strategy: substring ranking on label, aliases, keywords, group, and shortcut
75
+ - `searchStrategy: "fuzzy"` enables lightweight fuzzy matching
76
+ - `rank(item, search)` replaces the deprecated `filter(item, search)` boolean API
77
+
78
+ Disabled commands remain visible unless `hidden` is true. Keyboard navigation and `run()` skip disabled or loading commands.
79
+
80
+ ## Plugin options
81
+
82
+ | Option | Default | Description |
83
+ |--------|---------|-------------|
84
+ | `id` | auto-generated | Controller identifier |
85
+ | `rank` / `searchStrategy` | substring | Ranker for the filter pipeline |
86
+ | `persistence` | — | Recent/pinned hooks (maxRecent, getRecent, setRecent, getPinned, setPinned) |
87
+ | `storeKey` | `'command'` | `$store` key the Alpine plugin registers under |
88
+ | `magicKey` | `'command'` (or `storeKey` when renamed) | `$command` magic key the Alpine plugin registers under |
89
+
90
+ ### Avoiding name collisions
91
+
92
+ If your application already owns a `$store.command` or another toolkit plugin registers on that name, rename the integration surface without touching the controller:
93
+
94
+ ```ts
95
+ Alpine.plugin(commandPlugin({
96
+ storeKey: "palette", // → $store.palette
97
+ // magicKey follows storeKey by default → $palette
98
+ magicKey: "cmd", // explicit override → $cmd
99
+ }));
100
+ ```
101
+
102
+ `storeKey` is the only argument most hosts need. `magicKey` moves independently only when both names must be freed. The exposed constants `DEFAULT_COMMAND_STORE_KEY` and `DEFAULT_COMMAND_MAGIC_KEY` keep the rename discoverable from TypeScript.
103
+
104
+ ## Integration
105
+
106
+ - **Overlay** — optional `overlayId` documents a palette layer id for `$store.overlay.zIndexOf(overlayId, layer)`
107
+ - **Scroll** — pass `scroll: $store.scroll` to lock page scroll while open (enabled by default when provided)
108
+ - **Keyboard** — global open shortcuts remain consumer-owned; compose with `@ailuracode/alpine-keyboard` when needed
109
+ - **Dialog / Toast** — render the palette in a dialog panel or call `$toast()` from `action` / `onRun`
110
+
111
+ Neither overlay, scroll, keyboard, dialog, nor toast is required.
112
+
113
+ ## SSR
114
+
115
+ Register commands on the client. The controller does not touch browser globals during import or construction.
116
+
117
+ ## Standalone usage
118
+
119
+ ```ts
120
+ import { createCommandController } from "@ailuracode/alpine-command";
121
+
122
+ const command = createCommandController();
123
+ command.register({ id: "save", label: "Save", action: () => {} });
124
+ command.open();
125
+ ```
126
+
127
+ ## Migration notes
128
+
129
+ - `register()` now returns an unregister callback; `unregister(id)` remains available
130
+ - `filter` is deprecated in favor of `rank` or `searchStrategy`
131
+ - `filteredItems` now includes disabled commands; use `itemState(id)?.disabled` or `visibleItems` for runtime state
132
+
133
+ ## License
134
+
135
+ MIT
package/dist/global.d.ts CHANGED
@@ -1,30 +1,66 @@
1
1
  /// <reference types="@types/alpinejs" />
2
2
 
3
- import type { CommandItem } from "./types";
3
+ import type {
4
+ CommandExecutionState,
5
+ CommandItem,
6
+ CommandItemState,
7
+ CommandPage,
8
+ CommandStoreConfig,
9
+ } from "./types";
4
10
 
5
- export type { CommandAction, CommandItem, CommandStoreConfig } from "./types";
11
+ export type {
12
+ CommandAction,
13
+ CommandExecutionState,
14
+ CommandFilterFn,
15
+ CommandItem,
16
+ CommandItemState,
17
+ CommandLoader,
18
+ CommandPage,
19
+ CommandPersistence,
20
+ CommandPredicate,
21
+ CommandRankFn,
22
+ CommandSearchStrategy,
23
+ CommandStoreConfig,
24
+ } from "./types";
6
25
 
7
26
  export interface CommandStore {
8
27
  search: string;
9
28
  activeIndex: number;
10
29
  visible: boolean;
11
30
  items: Record<string, CommandItem>;
12
- readonly isOpen: boolean;
31
+ isOpen: boolean;
32
+ executionState: CommandExecutionState;
33
+ runningId: string | null;
34
+ currentPageId: string;
35
+ pageStack: string[];
36
+ pages: Record<string, CommandPage>;
37
+ loadingIds: string[];
38
+ pinnedIds: string[];
39
+ recentIds: string[];
13
40
  open(): void;
14
41
  close(): void;
15
42
  toggle(): void;
16
- register(item: CommandItem): void;
43
+ register(item: CommandItem): () => void;
17
44
  unregister(id: string): void;
18
45
  run(id: string): Promise<void>;
46
+ cancelRun(): void;
19
47
  handleKeydown(event: KeyboardEvent): void;
20
- readonly filteredItems: CommandItem[];
21
- readonly groupedItems: Record<string, CommandItem[]>;
48
+ pushPage(page: CommandPage): Promise<void>;
49
+ popPage(): void;
50
+ goBack(): void;
51
+ itemState(id: string): CommandItemState | null;
52
+ inputProps(): Record<string, string | boolean | undefined>;
53
+ listboxProps(): Record<string, string | boolean | undefined>;
54
+ optionProps(id: string): Record<string, string | number | boolean | undefined>;
55
+ filteredItems: CommandItem[];
56
+ visibleItems: CommandItemState[];
57
+ groupedItems: Record<string, CommandItem[]>;
22
58
  destroy(): void;
23
59
  }
24
60
 
25
61
  export function createCommandController(
26
- config?: import("./types").CommandStoreConfig
27
- ): import("./types").CommandController;
62
+ config?: CommandStoreConfig
63
+ ): import("./controller").CommandController;
28
64
 
29
65
  export default function commandPlugin(
30
66
  options?: import("./types").CommandPluginOptions
package/dist/index.d.ts CHANGED
@@ -1,55 +1,161 @@
1
1
  import { Alpine, PluginCallback, BaseController } from '@ailuracode/alpine-core';
2
2
  export { Unsubscribe } from '@ailuracode/alpine-core';
3
+ import { ScrollStore } from '@ailuracode/alpine-scroll';
3
4
  import { Alpine as Alpine$1 } from 'alpinejs';
4
5
 
5
6
  /**
6
7
  * 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
8
  */
11
9
 
12
10
  /** Action callback invoked when a command is executed. */
13
11
  type CommandAction = () => void | Promise<void>;
12
+ /** Static or reactive predicate for command state. */
13
+ type CommandPredicate = boolean | (() => boolean);
14
+ /** Async loader for lazy command registration. */
15
+ type CommandLoader = () => void | Promise<void>;
16
+ /** Search ranking strategy. */
17
+ type CommandSearchStrategy = "substring" | "fuzzy";
18
+ /** Rank function — higher scores surface earlier; `null` excludes the item. */
19
+ type CommandRankFn = (item: CommandItem, search: string) => number | null;
20
+ /**
21
+ * @deprecated Use {@link CommandRankFn} via `rank` or `searchStrategy` instead.
22
+ * Legacy boolean filter kept for backward compatibility.
23
+ */
24
+ type CommandFilterFn = (item: CommandItem, search: string) => boolean;
25
+ /** Optional recent/pinned persistence hooks. */
26
+ type CommandPersistence = {
27
+ readonly maxRecent?: number;
28
+ readonly getRecent?: () => readonly string[] | Promise<readonly string[]>;
29
+ readonly setRecent?: (ids: readonly string[]) => void | Promise<void>;
30
+ readonly getPinned?: () => readonly string[] | Promise<readonly string[]>;
31
+ readonly setPinned?: (ids: readonly string[]) => void | Promise<void>;
32
+ };
33
+ /** Nested command page metadata. */
34
+ type CommandPage = {
35
+ readonly id: string;
36
+ readonly title: string;
37
+ readonly parentId?: string;
38
+ readonly load?: CommandLoader;
39
+ };
14
40
  /** A registered command item. */
15
41
  type CommandItem = {
16
- id: string;
17
- label: string;
18
- group?: string;
19
- shortcut?: string;
20
- keywords?: string[];
21
- disabled?: boolean;
22
- action: CommandAction;
42
+ readonly id: string;
43
+ readonly label: string;
44
+ readonly group?: string;
45
+ readonly shortcut?: string;
46
+ readonly keywords?: string[];
47
+ readonly aliases?: string[];
48
+ readonly disabled?: CommandPredicate;
49
+ readonly hidden?: CommandPredicate;
50
+ readonly enabled?: CommandPredicate;
51
+ readonly pinned?: boolean;
52
+ readonly page?: string;
53
+ readonly load?: CommandLoader;
54
+ readonly action: CommandAction;
55
+ };
56
+ /** Resolved runtime state for a visible command row. */
57
+ type CommandItemState = {
58
+ readonly id: string;
59
+ readonly item: CommandItem;
60
+ readonly disabled: boolean;
61
+ readonly hidden: boolean;
62
+ readonly loading: boolean;
63
+ readonly pinned: boolean;
64
+ readonly recent: boolean;
65
+ readonly rank: number;
66
+ readonly selectable: boolean;
23
67
  };
24
- /** Config callbacks for the command palette. */
68
+ /** Explicit async lifecycle state for the palette. */
69
+ type CommandExecutionState = "idle" | "loading" | "running";
70
+ /** Config callbacks and behavior for the command palette. */
25
71
  type CommandStoreConfig = {
26
- onOpen?: () => void;
27
- onClose?: () => void;
28
- onRun?: (item: CommandItem) => void;
29
- filter?: (item: CommandItem, search: string) => boolean;
72
+ readonly onOpen?: () => void;
73
+ readonly onClose?: () => void;
74
+ readonly onRun?: (item: CommandItem) => void;
75
+ /** @deprecated Use `rank` or `searchStrategy` instead. */
76
+ readonly filter?: CommandFilterFn;
77
+ readonly rank?: CommandRankFn;
78
+ readonly searchStrategy?: CommandSearchStrategy;
79
+ readonly persistence?: CommandPersistence;
80
+ readonly overlayId?: string;
81
+ readonly editableSelector?: string;
82
+ readonly idPrefix?: string;
83
+ readonly closeOnRun?: boolean;
84
+ readonly scroll?: ScrollStore;
85
+ readonly scrollLock?: boolean;
86
+ };
87
+ /** Normalized options used internally by the controller. */
88
+ type NormalizedCommandOptions = {
89
+ readonly id?: string;
90
+ readonly onOpen?: () => void;
91
+ readonly onClose?: () => void;
92
+ readonly onRun?: (item: CommandItem) => void;
93
+ readonly rank: CommandRankFn;
94
+ readonly searchStrategy: CommandSearchStrategy;
95
+ readonly persistence: Required<Pick<CommandPersistence, "maxRecent">> & Omit<CommandPersistence, "maxRecent">;
96
+ readonly overlayId?: string;
97
+ readonly editableSelector: string;
98
+ readonly idPrefix: string;
99
+ readonly closeOnRun: boolean;
100
+ readonly scroll?: ScrollStore;
101
+ readonly scrollLock: boolean;
30
102
  };
31
103
  /** Options accepted by the command plugin factory. */
32
104
  interface CommandPluginOptions extends CommandStoreConfig {
33
105
  readonly id?: string;
106
+ /**
107
+ * `$store` key the Alpine plugin registers under. Defaults to
108
+ * {@link DEFAULT_COMMAND_STORE_KEY}. Set when the host already owns
109
+ * a `command` store or another toolkit plugin would collide on that
110
+ * name — the rename avoids the collision without touching the
111
+ * controller. Ignored by the standalone `createCommandStore` factory.
112
+ */
113
+ readonly storeKey?: string;
114
+ /**
115
+ * `$command` magic key the Alpine plugin registers under. Defaults
116
+ * to {@link DEFAULT_COMMAND_MAGIC_KEY}, or to `storeKey` when that
117
+ * is renamed (the magic follows the store so consumers only rename
118
+ * one). Ignored by the standalone factory.
119
+ */
120
+ readonly magicKey?: string;
34
121
  }
122
+ /** Default `$store` key registered by {@link commandPlugin}. */
123
+ declare const DEFAULT_COMMAND_STORE_KEY = "command";
124
+ /** Default `$command` magic key registered by {@link commandPlugin}. */
125
+ declare const DEFAULT_COMMAND_MAGIC_KEY = "command";
35
126
  /** Alpine-facing store surface. */
36
127
  interface CommandStore {
37
128
  search: string;
38
129
  activeIndex: number;
39
- /** Whether the palette is visible. */
40
130
  visible: boolean;
41
- /** Registered command items. */
42
131
  items: Record<string, CommandItem>;
43
- readonly isOpen: boolean;
132
+ isOpen: boolean;
133
+ executionState: CommandExecutionState;
134
+ runningId: string | null;
135
+ currentPageId: string;
136
+ pageStack: string[];
137
+ pages: Record<string, CommandPage>;
138
+ loadingIds: string[];
139
+ pinnedIds: string[];
140
+ recentIds: string[];
44
141
  open(): void;
45
142
  close(): void;
46
143
  toggle(): void;
47
- register(item: CommandItem): void;
144
+ register(item: CommandItem): () => void;
48
145
  unregister(id: string): void;
49
146
  run(id: string): Promise<void>;
147
+ cancelRun(): void;
50
148
  handleKeydown(event: KeyboardEvent): void;
51
- readonly filteredItems: CommandItem[];
52
- readonly groupedItems: Record<string, CommandItem[]>;
149
+ pushPage(page: CommandPage): Promise<void>;
150
+ popPage(): void;
151
+ goBack(): void;
152
+ itemState(id: string): CommandItemState | null;
153
+ inputProps(): Record<string, string | boolean | undefined>;
154
+ listboxProps(): Record<string, string | boolean | undefined>;
155
+ optionProps(id: string): Record<string, string | number | boolean | undefined>;
156
+ filteredItems: CommandItem[];
157
+ visibleItems: CommandItemState[];
158
+ groupedItems: Record<string, CommandItem[]>;
53
159
  destroy(): void;
54
160
  }
55
161
  /** Typed view of `Alpine` the command plugin uses internally. */
@@ -70,27 +176,23 @@ type CommandPluginCallback = PluginCallback<Alpine$1>;
70
176
  * `open` — palette opened.
71
177
  * `close` — palette closed.
72
178
  * `run` — a command was executed.
179
+ * `change` — reactive state changed (search, selection, pages, loading).
73
180
  */
74
181
  interface CommandEvents extends Record<string, unknown> {
75
182
  open: undefined;
76
183
  close: undefined;
77
184
  run: CommandItem;
185
+ change: undefined;
78
186
  }
79
187
 
80
188
  /**
81
189
  * 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.
190
+ * `@ailuracode/alpine-command`.
88
191
  */
89
192
 
90
193
  /**
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.
194
+ * Headless command palette controller with search ranking, nested pages,
195
+ * async loading/execution, and ARIA helpers.
94
196
  */
95
197
  declare class CommandController extends BaseController<CommandEvents> {
96
198
  #private;
@@ -102,37 +204,67 @@ declare class CommandController extends BaseController<CommandEvents> {
102
204
  set activeIndex(value: number);
103
205
  get visible(): boolean;
104
206
  get isOpen(): boolean;
207
+ get executionState(): CommandExecutionState;
208
+ get runningId(): string | null;
209
+ get currentPageId(): string;
210
+ get pageStack(): readonly string[];
211
+ get pages(): Readonly<Record<string, CommandPage>>;
212
+ get loadingIds(): readonly string[];
213
+ get pinnedIds(): readonly string[];
214
+ get recentIds(): readonly string[];
215
+ get visibleItems(): readonly CommandItemState[];
105
216
  get filteredItems(): CommandItem[];
106
217
  get groupedItems(): Record<string, CommandItem[]>;
107
218
  open(): void;
108
219
  close(): void;
109
220
  toggle(): void;
110
- register(item: CommandItem): void;
221
+ register(item: CommandItem): () => void;
111
222
  unregister(id: string): void;
223
+ pushPage(page: CommandPage): Promise<void>;
224
+ popPage(): void;
225
+ goBack(): void;
226
+ itemState(id: string): CommandItemState | null;
227
+ inputProps(): Record<string, string | boolean | undefined>;
228
+ listboxProps(): Record<string, string | boolean | undefined>;
229
+ optionProps(id: string): Record<string, string | number | boolean | undefined>;
230
+ cancelRun(): void;
112
231
  run(id: string): Promise<void>;
113
232
  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
233
  toStore(): CommandStore;
234
+ destroy(): void;
119
235
  }
120
- /**
121
- * Creates a CommandController instance.
122
- * Convenience for non-Alpine consumers.
123
- */
236
+ /** Creates a CommandController instance. */
124
237
  declare function createCommandController(config?: CommandStoreConfig): CommandController;
238
+ /** Creates a CommandStore (store-shaped object) directly. */
239
+ declare function createCommandStore(config?: CommandStoreConfig): CommandStore;
240
+
125
241
  /**
126
- * Creates a CommandStore (store-shaped object) directly.
127
- * Backward-compatible alias.
242
+ * Alpine adapter for the command controller.
243
+ *
244
+ * Fields are direct properties (not getters) so the plugin can mirror
245
+ * controller state onto Alpine's reactive store proxy.
128
246
  */
129
- declare function createCommandStore(config?: CommandStoreConfig): CommandStore;
247
+
248
+ /** Mirrors controller state into a plain store object for Alpine. */
249
+ declare function syncCommandStore(store: CommandStore, controller: CommandController): void;
250
+ /** Builds the {@link CommandStore} installed at `$store.command`. */
251
+ declare function createCommandAlpineStore(controller: CommandController): CommandStore;
252
+
253
+ type CommandErrorCode = "COMMAND_DESTROYED" | "COMMAND_DUPLICATE_ID" | "COMMAND_UNKNOWN_ITEM" | "COMMAND_UNKNOWN_PAGE" | "COMMAND_ALREADY_RUNNING";
254
+ declare class CommandError extends Error {
255
+ readonly code: CommandErrorCode;
256
+ readonly cause?: unknown;
257
+ constructor(message: string, code: CommandErrorCode, options?: {
258
+ cause?: unknown;
259
+ });
260
+ }
261
+
262
+ declare const ROOT_PAGE_ID = "root";
263
+ /** Normalizes command controller and plugin options once at construction. */
264
+ declare function normalizeCommandOptions(options?: CommandPluginOptions): NormalizedCommandOptions;
130
265
 
131
266
  /**
132
267
  * Alpine.js integration for `@ailuracode/alpine-command`.
133
- *
134
- * Thin adapter that wires {@link CommandController} into
135
- * `$store.command` and the `$command` magic.
136
268
  */
137
269
 
138
270
  /**
@@ -143,5 +275,7 @@ declare function createCommandStore(config?: CommandStoreConfig): CommandStore;
143
275
  declare function commandPlugin(options?: CommandPluginOptions): CommandPluginCallback;
144
276
  /** Builds typed command plugin options. */
145
277
  declare function commandOptions<const T extends CommandPluginOptions>(options: T): T;
278
+ /** @deprecated Use {@link createCommandAlpineStore} via the plugin adapter. */
279
+ declare function createCommandStoreFromController(controller: CommandController): CommandStore;
146
280
 
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 };
281
+ export { type CommandAction, type CommandAlpine, CommandController, CommandError, type CommandErrorCode, type CommandEvents, type CommandExecutionState, type CommandFilterFn, type CommandItem, type CommandItemState, type CommandLoader, type CommandPage, type CommandPersistence, type CommandPluginCallback, type CommandPluginOptions, type CommandPredicate, type CommandRankFn, type CommandSearchStrategy, type CommandStore, type CommandStoreConfig, DEFAULT_COMMAND_MAGIC_KEY, DEFAULT_COMMAND_STORE_KEY, type NormalizedCommandOptions, ROOT_PAGE_ID, commandOptions, commandPlugin, createCommandAlpineStore, createCommandController, createCommandStore, createCommandStoreFromController, commandPlugin as default, normalizeCommandOptions, syncCommandStore };
package/dist/index.js CHANGED
@@ -1,304 +1 @@
1
- // src/controller.ts
2
- import { BaseController, generateId } from "@ailuracode/alpine-core";
3
- function defaultFilter(item, search) {
4
- if (!search.trim()) {
5
- return true;
6
- }
7
- const query = search.trim().toLowerCase();
8
- const haystack = [item.label, item.group ?? "", item.shortcut ?? "", ...item.keywords ?? []].join(" ").toLowerCase();
9
- return haystack.includes(query);
10
- }
11
- function isEditableTarget(target) {
12
- if (!(target instanceof HTMLElement)) {
13
- return false;
14
- }
15
- const tag = target.tagName;
16
- return tag === "INPUT" || tag === "TEXTAREA" || target.isContentEditable;
17
- }
18
- function isTypingKey(event) {
19
- return event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey && !event.isComposing;
20
- }
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;
32
- }
33
- get items() {
34
- return this.#items;
35
- }
36
- get search() {
37
- return this.#search;
38
- }
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) {
94
- this.close();
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;
165
- }
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;
244
- }
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);
255
- }
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());
291
- }
292
- };
293
- }
294
- function commandOptions(options) {
295
- return options;
296
- }
297
- export {
298
- CommandController,
299
- commandOptions,
300
- commandPlugin,
301
- createCommandController,
302
- createCommandStore,
303
- commandPlugin as default
304
- };
1
+ function c(t,e){t.search=e.search,t.activeIndex=e.activeIndex,t.visible=e.visible,t.isOpen=e.isOpen,t.items={...e.items},t.executionState=e.executionState,t.runningId=e.runningId,t.currentPageId=e.currentPageId,Object.assign(t,{pageStack:[...e.pageStack],pages:{...e.pages},loadingIds:[...e.loadingIds],pinnedIds:[...e.pinnedIds],recentIds:[...e.recentIds],visibleItems:[...e.visibleItems],filteredItems:[...e.filteredItems],groupedItems:{...e.groupedItems}})}function m(t){return{search:t.search,activeIndex:t.activeIndex,visible:t.visible,isOpen:t.isOpen,items:{...t.items},executionState:t.executionState,runningId:t.runningId,currentPageId:t.currentPageId,pageStack:[...t.pageStack],pages:{...t.pages},loadingIds:[...t.loadingIds],pinnedIds:[...t.pinnedIds],recentIds:[...t.recentIds],visibleItems:[...t.visibleItems],filteredItems:[...t.filteredItems],groupedItems:{...t.groupedItems},open:()=>t.open(),close:()=>t.close(),toggle:()=>t.toggle(),register:n=>t.register(n),unregister:n=>t.unregister(n),run:n=>t.run(n),cancelRun:()=>t.cancelRun(),handleKeydown:n=>t.handleKeydown(n),pushPage:n=>t.pushPage(n),popPage:()=>t.popPage(),goBack:()=>t.goBack(),itemState:n=>t.itemState(n),inputProps:()=>t.inputProps(),listboxProps:()=>t.listboxProps(),optionProps:n=>t.optionProps(n),destroy:()=>t.destroy()}}import{BaseController as F,generateId as j}from"@ailuracode/alpine-core";function S(t,e){let n=t.target;return n instanceof Element?n.closest(e)?!0:n instanceof HTMLElement&&n.isContentEditable:!1}function v(t){return t.key.length===1&&!t.metaKey&&!t.ctrlKey&&!t.altKey&&!t.isComposing}var l=class extends Error{code;cause;constructor(e,n,i={}){super(e,{cause:i.cause}),this.name="CommandError",this.code=n,this.cause=i.cause}};function T(t){return[t.label,t.group??"",t.shortcut??"",...t.keywords??[],...t.aliases??[]]}function k(t,e){let n=e.trim().toLowerCase();if(!n)return 0;let i=t.label.toLowerCase();if(i.startsWith(n))return 100;if(i.includes(n))return 80;for(let s of t.aliases??[]){let r=s.toLowerCase();if(r.startsWith(n))return 70;if(r.includes(n))return 60}return T(t).join(" ").toLowerCase().includes(n)?50:null}function N(t,e){let n=e.trim().toLowerCase();if(!n)return 0;let i=t.label.toLowerCase(),o=0,s=0;for(let r=0;r<i.length&&s<n.length;r++)i[r]===n[s]&&(o+=10,s++);return s===n.length?o+(i.startsWith(n)?20:0):k(t,e)}function R(t,e){return e||(t==="fuzzy"?N:k)}function O(t){return(e,n)=>t(e,n)?1:null}var K='input, textarea, select, [contenteditable=""], [contenteditable="true"], [contenteditable="plaintext-only"]',C=10,a="root";function f(t={}){let e=t.searchStrategy??"substring",n=t.rank??(t.filter?O(t.filter):R(e));return{id:t.id,onOpen:t.onOpen,onClose:t.onClose,onRun:t.onRun,rank:n,searchStrategy:e,persistence:{maxRecent:t.persistence?.maxRecent??C,getRecent:t.persistence?.getRecent,setRecent:t.persistence?.setRecent,getPinned:t.persistence?.getPinned,setPinned:t.persistence?.setPinned},overlayId:t.overlayId,editableSelector:t.editableSelector??K,idPrefix:t.idPrefix??"command",closeOnRun:t.closeOnRun!==!1,scroll:t.scroll,scrollLock:t.scrollLock!==!1}}function g(t,e){return t===void 0?e:typeof t=="function"?t():t}function E(t,e,n){let i=n.length;if(i===0)return 0;let o=t;for(let s=0;s<i;s++)if(o=(o+e+i)%i,n[o])return o;return t}function z(t){let e=t.findIndex(Boolean);return e===-1?0:e}function U(t){for(let e=t.length-1;e>=0;e--)if(t[e])return e;return 0}function A(t,e){return(t.page??a)===e}function y(t){return g(t.hidden,!1)}function D(t){return y(t)?!0:t.enabled!==void 0?!g(t.enabled,!0):g(t.disabled,!1)}var d=class extends F{#n;#d={};#h={[a]:{id:a,title:"Commands"}};#i=new Set;#l=new Set;#c=new Set;#r=[];#o="";#t=0;#s=!1;#a=[a];#u="idle";#f=null;#y=0;#p=0;#x=!1;#g=0;#m=null;#C;#I;constructor(e,n={}){super(e??j("command")),this.#n=f(n),this.#C=this.#n.scroll,this.#I=this.#n.scrollLock}get items(){return this.#d}get search(){return this.#o}set search(e){this.#o!==e&&(this.#o=e,this.#t=0,this.#e())}get activeIndex(){return this.#t}set activeIndex(e){let n=Math.max(0,e);this.#t!==n&&(this.#t=n,this.#e())}get visible(){return this.#s}get isOpen(){return this.#s}get executionState(){return this.#u}get runningId(){return this.#f}get currentPageId(){return this.#a[this.#a.length-1]??a}get pageStack(){return this.#a}get pages(){return this.#h}get loadingIds(){return[...this.#i]}get pinnedIds(){return[...this.#c]}get recentIds(){return[...this.#r]}get visibleItems(){return this.#v()}get filteredItems(){return this.visibleItems.map(e=>e.item)}get groupedItems(){let e={};for(let n of this.visibleItems){let i=n.item.group??"General";e[i]??=[],e[i].push(n.item)}return e}open(){this.isDestroyed||this.#s||(this.#s=!0,this.#o="",this.#t=0,this.#I&&this.#S(!0),this.#R(),this.#O(),this.#n.onOpen?.(),this.emit("open",void 0),this.#e())}close(){this.isDestroyed||!this.#s||(this.#s=!1,this.#o="",this.#t=0,this.#a=[a],this.#I&&this.#S(!1),this.cancelRun(),this.#n.onClose?.(),this.emit("close",void 0),this.#e())}toggle(){this.#s?this.close():this.open()}register(e){if(this.isDestroyed)throw new l("Cannot register command after destroy()","COMMAND_DESTROYED");if(this.#d[e.id])throw new l(`Command id "${e.id}" is already registered`,"COMMAND_DUPLICATE_ID");return this.#d[e.id]=e,e.pinned&&this.#c.add(e.id),this.#b(),this.#e(),()=>{this.unregister(e.id)}}unregister(e){if(this.isDestroyed)return;delete this.#d[e],this.#i.delete(e),this.#l.delete(e),this.#c.delete(e);let n=this.#r.indexOf(e);n!==-1&&this.#r.splice(n,1),this.#b(),this.#e()}async pushPage(e){this.isDestroyed||(this.#h[e.id]=e,this.#a.push(e.id),this.#o="",this.#t=0,await this.#P(e),this.#e())}popPage(){this.isDestroyed||this.#a.length<=1||(this.#a.pop(),this.#o="",this.#t=0,this.#b(),this.#e())}goBack(){this.popPage()}itemState(e){return this.visibleItems.find(n=>n.id===e)??null}inputProps(){let e=`${this.#n.idPrefix}-listbox`,n=this.visibleItems[this.#t];return{role:"combobox","aria-expanded":this.#s,"aria-controls":e,"aria-activedescendant":n?`${this.#n.idPrefix}-option-${n.id}`:void 0,"aria-autocomplete":"list"}}listboxProps(){return{role:"listbox",id:`${this.#n.idPrefix}-listbox`,"aria-label":this.#h[this.currentPageId]?.title??"Commands"}}optionProps(e){let n=this.itemState(e),i=this.visibleItems.findIndex(o=>o.id===e);return{role:"option",id:`${this.#n.idPrefix}-option-${e}`,"aria-selected":i===this.#t,"aria-disabled":n?.disabled??!1,"data-disabled":n?.disabled??!1,"data-loading":n?.loading??!1}}cancelRun(){this.#y++,this.#f=null,this.#u="idle",this.#e()}async run(e){if(this.isDestroyed)return;let n=this.itemState(e)??this.#k(e);if(!n||n.disabled||n.loading)return;let i=++this.#y;this.#f=e,this.#u="running",this.#e();try{if(await n.item.action(),i!==this.#y||this.isDestroyed)return;if(this.#n.onRun?.(n.item),this.emit("run",n.item),await this.#A(e),this.#n.closeOnRun){this.close();return}}finally{i===this.#y&&(this.#f=null,this.#u="idle",this.#e())}}handleKeydown(e){if(this.isDestroyed||!this.#s)return;if(e.key==="Backspace"&&this.#a.length>1&&this.#o.length===0){e.preventDefault(),this.popPage();return}if(!S(e,this.#n.editableSelector)){if(e.key==="Backspace"){e.preventDefault(),this.#o=this.#o.slice(0,-1),this.#t=0,this.#e();return}if(v(e)){e.preventDefault(),this.#o+=e.key,this.#t=0,this.#e();return}}let n=this.visibleItems,i=n.map(o=>o.selectable);switch(e.key){case"ArrowDown":e.preventDefault(),this.#t=E(this.#t,1,i),this.#e();break;case"ArrowUp":e.preventDefault(),this.#t=E(this.#t,-1,i),this.#e();break;case"Home":e.preventDefault(),this.#t=z(i),this.#e();break;case"End":e.preventDefault(),this.#t=U(i),this.#e();break;case"Enter":{e.preventDefault();let o=n[this.#t];o?.selectable&&this.run(o.id);break}case"Escape":e.preventDefault(),this.#a.length>1?this.popPage():this.close();break;default:break}}toStore(){let e=this,n=m(e),i=()=>{c(n,e)};return e.on("open",i),e.on("close",i),e.on("change",i),Object.defineProperty(n,"search",{get(){return e.search},set(o){e.search=o},enumerable:!0,configurable:!0}),Object.defineProperty(n,"activeIndex",{get(){return e.activeIndex},set(o){e.activeIndex=o},enumerable:!0,configurable:!0}),i(),n}destroy(){if(!this.isDestroyed){this.cancelRun(),this.#p++,this.#m!==null&&(this.#C?.unlock(this.#m),this.#m=null),this.#g=0;for(let e of Object.keys(this.#d))delete this.#d[e];for(let e of Object.keys(this.#h))e!==a&&delete this.#h[e];this.#a=[a],this.#i.clear(),this.#l.clear(),this.#c.clear(),this.#r.length=0,this.#s=!1,this.#o="",this.#t=0,super.destroy()}}#v(){let e=this.currentPageId,n=[];for(let i of Object.values(this.#d)){if(!A(i,e)||y(i))continue;let o=this.#n.rank(i,this.#o);if(o===null)continue;let s=D(i),r=i.pinned===!0||this.#c.has(i.id),u=this.#r.includes(i.id);n.push({id:i.id,item:i,disabled:s,hidden:!1,loading:this.#i.has(i.id),pinned:r,recent:u,rank:o+(r?1e3:0)+(u?100:0),selectable:!(s||this.#i.has(i.id))}),i.load&&!this.#l.has(i.id)&&!this.#i.has(i.id)&&this.#E(i)}return n.sort((i,o)=>o.rank!==i.rank?o.rank-i.rank:i.item.label.localeCompare(o.item.label)),n}#k(e){let n=this.#d[e];if(!(n&&A(n,this.currentPageId))||y(n))return null;let i=D(n);return{id:e,item:n,disabled:i,hidden:!1,loading:this.#i.has(e),pinned:n.pinned===!0||this.#c.has(e),recent:this.#r.includes(e),rank:0,selectable:!(i||this.#i.has(e))}}async#R(){if(this.#x)return;this.#x=!0;let e=await this.#n.persistence.getPinned?.();if(e)for(let i of e)this.#c.add(i);let n=await this.#n.persistence.getRecent?.();n&&this.#r.splice(0,this.#r.length,...n),this.#e()}async#O(){let e=this.#h[this.currentPageId];e&&await this.#P(e)}async#P(e){if(!e.load||this.#l.has(e.id))return;let n=++this.#p;this.#i.add(e.id),this.#u="loading",this.#e();try{if(await e.load(),n!==this.#p||this.isDestroyed)return;this.#l.add(e.id)}finally{this.#i.delete(e.id),this.#i.size===0&&this.#u==="loading"&&(this.#u="idle"),this.#e()}}async#E(e){if(!e.load||this.#l.has(e.id))return;let n=++this.#p;this.#i.add(e.id),this.#e();try{if(await e.load(),n!==this.#p||this.isDestroyed)return;this.#l.add(e.id)}finally{this.#i.delete(e.id),this.#e()}}async#A(e){let n=this.#n.persistence.maxRecent??C,i=[e,...this.#r.filter(o=>o!==e)].slice(0,n);this.#r.splice(0,this.#r.length,...i),await this.#n.persistence.setRecent?.(i),this.#e()}#b(){let e=this.visibleItems.length;this.#t>=e&&(this.#t=Math.max(e-1,0))}#e(){this.isDestroyed||(this.#b(),this.emit("change",void 0))}#S(e){if(e){this.#g===0&&this.#C&&this.#m===null&&(this.#m=this.#C.lock("command")),this.#g++;return}this.#g!==0&&(this.#g--,this.#g===0&&this.#m!==null&&(this.#C?.unlock(this.#m),this.#m=null))}};function B(t={}){return new d(void 0,t)}function G(t={}){return new d(void 0,t).toStore()}import{bridgeControllerStore as Y}from"@ailuracode/alpine-core";var b="command",I="command";function _(t={}){let e=t.storeKey??b,n=t.magicKey??t.storeKey??I;return function(o){let s=o,r=new d(t.id,t),u=m(r);Y({alpine:s,storeKey:e,magicKey:n,store:u,controller:r,packageName:"command",subscribe:x=>{let p=!1,h=()=>{p=!0,c(x,r),p=!1},w=r.on("open",h),L=r.on("close",h),M=r.on("change",h);return o.effect(()=>{if(p)return;let P=x.search;r.search!==P&&(r.search=P)}),h(),()=>{w(),L(),M()}}})}}function $(t){return t}function H(t){return m(t)}export{d as CommandController,l as CommandError,I as DEFAULT_COMMAND_MAGIC_KEY,b as DEFAULT_COMMAND_STORE_KEY,a as ROOT_PAGE_ID,$ as commandOptions,_ as commandPlugin,m as createCommandAlpineStore,B as createCommandController,G as createCommandStore,H as createCommandStoreFromController,_ as default,f as normalizeCommandOptions,c as syncCommandStore};
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@ailuracode/alpine-command",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "Alpine.js headless command palette store",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "ailuracode",
8
+ "sideEffects": false,
8
9
  "publishConfig": {
9
10
  "access": "public"
10
11
  },
@@ -32,14 +33,21 @@
32
33
  },
33
34
  "types": "./dist/global.d.ts",
34
35
  "devDependencies": {
35
- "@ailuracode/alpine-core": "0.2.0"
36
+ "@types/alpinejs": "^3.13.11",
37
+ "alpinejs": "^3.15.12",
38
+ "@ailuracode/alpine-scroll": "3.0.0",
39
+ "@ailuracode/alpine-core": "0.3.0"
36
40
  },
37
41
  "peerDependencies": {
38
- "@ailuracode/alpine-core": "^0.2.0",
42
+ "@ailuracode/alpine-core": "^0.3.0",
43
+ "@ailuracode/alpine-scroll": "^3.0.0",
39
44
  "alpinejs": "^3.0.0",
40
45
  "@types/alpinejs": "^3.13.11"
41
46
  },
42
47
  "peerDependenciesMeta": {
48
+ "@ailuracode/alpine-scroll": {
49
+ "optional": true
50
+ },
43
51
  "@types/alpinejs": {
44
52
  "optional": true
45
53
  }
@@ -53,8 +61,30 @@
53
61
  "spotlight",
54
62
  "headless"
55
63
  ],
64
+ "toolkit": {
65
+ "bundleBudget": {
66
+ "category": "complex-feature"
67
+ }
68
+ },
69
+ "size-limit": [
70
+ {
71
+ "name": "full surface",
72
+ "path": "dist/index.js",
73
+ "import": "*",
74
+ "ignore": [
75
+ "alpinejs",
76
+ "@ailuracode/alpine-core",
77
+ "@types/alpinejs"
78
+ ],
79
+ "gzip": true,
80
+ "brotli": true,
81
+ "limit": "4 kB"
82
+ }
83
+ ],
56
84
  "scripts": {
57
- "build": "tsup src/index.ts --format esm --dts --clean --out-dir dist && cp src/global.d.ts dist/global.d.ts",
58
- "test": "vitest run --config ../../vitest.config.ts test"
85
+ "build": "tsup src/index.ts --format esm --dts --clean --out-dir dist --minify && cp src/global.d.ts dist/global.d.ts",
86
+ "size": "size-limit",
87
+ "test": "vitest run --config ../../vitest.config.ts packages/command",
88
+ "test:e2e": "playwright test --config playwright.config.ts"
59
89
  }
60
90
  }