modern-cmdk 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/codemod/cli.mjs +1 -3
- package/dist/codemod/transforms/data-attrs.d.mts +1 -2
- package/dist/codemod/transforms/data-attrs.mjs +0 -2
- package/dist/codemod/transforms/forward-ref.d.mts +1 -2
- package/dist/codemod/transforms/forward-ref.mjs +0 -2
- package/dist/codemod/transforms/import-rewrite.d.mts +1 -2
- package/dist/codemod/transforms/import-rewrite.mjs +0 -2
- package/dist/codemod/transforms/should-filter.d.mts +1 -2
- package/dist/codemod/transforms/should-filter.mjs +0 -2
- package/dist/core/index.d.mts +568 -2
- package/dist/core/index.mjs +1268 -1
- package/dist/react/index.d.mts +349 -3
- package/dist/react/index.mjs +1292 -209
- package/package.json +26 -12
- package/dist/codemod/cli.mjs.map +0 -1
- package/dist/codemod/transforms/data-attrs.d.mts.map +0 -1
- package/dist/codemod/transforms/data-attrs.mjs.map +0 -1
- package/dist/codemod/transforms/forward-ref.d.mts.map +0 -1
- package/dist/codemod/transforms/forward-ref.mjs.map +0 -1
- package/dist/codemod/transforms/import-rewrite.d.mts.map +0 -1
- package/dist/codemod/transforms/import-rewrite.mjs.map +0 -1
- package/dist/codemod/transforms/should-filter.d.mts.map +0 -1
- package/dist/codemod/transforms/should-filter.mjs.map +0 -1
- package/dist/core-CgQYpHK3.mjs +0 -1235
- package/dist/core-CgQYpHK3.mjs.map +0 -1
- package/dist/index-C6g94P7V.d.mts +0 -563
- package/dist/index-C6g94P7V.d.mts.map +0 -1
- package/dist/react/index.d.mts.map +0 -1
- package/dist/react/index.mjs.map +0 -1
- /package/{src → dist}/react/styles.css +0 -0
package/dist/codemod/cli.mjs
CHANGED
|
@@ -89734,7 +89734,7 @@ const AVAILABLE_TRANSFORMS = new Map([
|
|
|
89734
89734
|
* Dynamically import a transform module by name and return its default export.
|
|
89735
89735
|
*/
|
|
89736
89736
|
async function loadTransform(name) {
|
|
89737
|
-
return (await import(`./transforms/${name}.
|
|
89737
|
+
return (await import(`./transforms/${name}.mjs`)).default;
|
|
89738
89738
|
}
|
|
89739
89739
|
function printUsage() {
|
|
89740
89740
|
AVAILABLE_TRANSFORMS.entries().forEach(([_name, _description]) => {});
|
|
@@ -89832,5 +89832,3 @@ main().catch((_err) => {
|
|
|
89832
89832
|
});
|
|
89833
89833
|
//#endregion
|
|
89834
89834
|
export {};
|
|
89835
|
-
|
|
89836
|
-
//# sourceMappingURL=cli.mjs.map
|
package/dist/core/index.d.mts
CHANGED
|
@@ -1,2 +1,568 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
//#region src/core/search/types.d.ts
|
|
2
|
+
/** Result of scoring a single item against a query */
|
|
3
|
+
interface SearchResult {
|
|
4
|
+
readonly id: ItemId;
|
|
5
|
+
readonly score: number;
|
|
6
|
+
/** Character ranges that matched (for highlighting) — [start, end] tuples */
|
|
7
|
+
readonly matches: ReadonlyArray<readonly [start: number, end: number]>;
|
|
8
|
+
}
|
|
9
|
+
/** A scoring function that evaluates a single item against a query */
|
|
10
|
+
type ScorerFn = (query: string, item: CommandItem) => SearchResult | null;
|
|
11
|
+
/** Search engine interface — pluggable into the state machine */
|
|
12
|
+
interface SearchEngine extends Disposable {
|
|
13
|
+
/** Index items for fast lookup (call on registration) */
|
|
14
|
+
index(items: readonly CommandItem[]): void;
|
|
15
|
+
/** Search items by query, return ordered results — uses Iterator pipeline */
|
|
16
|
+
search(query: string, items: readonly CommandItem[]): IteratorObject<SearchResult>;
|
|
17
|
+
/** Remove items from index — uses Set.difference for efficient bulk removal */
|
|
18
|
+
remove(ids: ReadonlySet<ItemId>): void;
|
|
19
|
+
/** Clear the entire index */
|
|
20
|
+
clear(): void;
|
|
21
|
+
/** Dispose resources */
|
|
22
|
+
[Symbol.dispose](): void;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/core/types.d.ts
|
|
26
|
+
/**
|
|
27
|
+
* Branded type for command item IDs.
|
|
28
|
+
* Prevents accidental mixing with plain strings at the type level.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const id = itemId('copy');
|
|
33
|
+
* // id has type ItemId, not string — catches type errors at compile time
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
declare const __itemIdBrand: unique symbol;
|
|
37
|
+
type ItemId = string & {
|
|
38
|
+
readonly [__itemIdBrand]: never;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Branded type for group IDs.
|
|
42
|
+
* Prevents accidental mixing with plain strings or ItemId at the type level.
|
|
43
|
+
*/
|
|
44
|
+
declare const __groupIdBrand: unique symbol;
|
|
45
|
+
type GroupId = string & {
|
|
46
|
+
readonly [__groupIdBrand]: never;
|
|
47
|
+
};
|
|
48
|
+
/** Create a branded {@link ItemId} from a plain string. Ensures well-formed Unicode (ES2026). */
|
|
49
|
+
declare function itemId(id: string): ItemId;
|
|
50
|
+
/** Create a branded {@link GroupId} from a plain string. Ensures well-formed Unicode (ES2026). */
|
|
51
|
+
declare function groupId(id: string): GroupId;
|
|
52
|
+
/**
|
|
53
|
+
* A registered command item in the palette.
|
|
54
|
+
* All fields are readonly — state machine creates new items on update.
|
|
55
|
+
*/
|
|
56
|
+
interface CommandItem {
|
|
57
|
+
/** Unique identifier (branded type) */
|
|
58
|
+
readonly id: ItemId;
|
|
59
|
+
/** Display text — also used as the primary search target */
|
|
60
|
+
readonly value: string;
|
|
61
|
+
/** Additional searchable terms (matched but not displayed) */
|
|
62
|
+
readonly keywords?: readonly string[] | undefined;
|
|
63
|
+
/** Group this item belongs to (items without groupId are ungrouped) */
|
|
64
|
+
readonly groupId?: GroupId | undefined;
|
|
65
|
+
/** Keyboard shortcut string (e.g., "Ctrl+C", "Mod+K") */
|
|
66
|
+
readonly shortcut?: string | undefined;
|
|
67
|
+
/** Whether this item is disabled (excluded from filtering and selection) */
|
|
68
|
+
readonly disabled?: boolean | undefined;
|
|
69
|
+
/** Callback fired when this item is selected */
|
|
70
|
+
readonly onSelect?: (() => void) | undefined;
|
|
71
|
+
/** Arbitrary metadata attached to the item */
|
|
72
|
+
readonly data?: Readonly<Record<string, unknown>> | undefined;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A command group for categorizing items.
|
|
76
|
+
* Groups are rendered with headings and sorted by priority.
|
|
77
|
+
*/
|
|
78
|
+
interface CommandGroup {
|
|
79
|
+
/** Unique identifier (branded type) */
|
|
80
|
+
readonly id: GroupId;
|
|
81
|
+
/** Display heading text (rendered above the group's items) */
|
|
82
|
+
readonly heading?: string | undefined;
|
|
83
|
+
/** Sort priority — lower values appear first (default: 0) */
|
|
84
|
+
readonly priority?: number | undefined;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Immutable state snapshot of the command palette.
|
|
88
|
+
* Consumed by React via `useSyncExternalStore` — each mutation
|
|
89
|
+
* produces a new object reference for efficient re-render detection.
|
|
90
|
+
*/
|
|
91
|
+
interface CommandState {
|
|
92
|
+
/** Current search query string */
|
|
93
|
+
readonly search: string;
|
|
94
|
+
/** Currently active (highlighted) item ID, or null if none */
|
|
95
|
+
readonly activeId: ItemId | null;
|
|
96
|
+
/** Ordered list of item IDs that pass the current filter */
|
|
97
|
+
readonly filteredIds: readonly ItemId[];
|
|
98
|
+
/** Map of group IDs to their visible item IDs */
|
|
99
|
+
readonly groupedIds: ReadonlyMap<GroupId, readonly ItemId[]>;
|
|
100
|
+
/** Count of visible items (shortcut for `filteredIds.length`) */
|
|
101
|
+
readonly filteredCount: number;
|
|
102
|
+
/** Whether async items are currently loading */
|
|
103
|
+
readonly loading: boolean;
|
|
104
|
+
/** Current page identifier (for multi-page navigation) */
|
|
105
|
+
readonly page: string;
|
|
106
|
+
/** Stack of previous pages for back-navigation */
|
|
107
|
+
readonly pageStack: readonly string[];
|
|
108
|
+
/** Whether the command palette dialog is open */
|
|
109
|
+
readonly open: boolean;
|
|
110
|
+
/** Temporal.Instant of the last state mutation (ES2026) */
|
|
111
|
+
readonly lastUpdated: Temporal.Instant;
|
|
112
|
+
}
|
|
113
|
+
/** All possible events the state machine accepts */
|
|
114
|
+
type CommandEvent = {
|
|
115
|
+
readonly type: "SEARCH_CHANGE";
|
|
116
|
+
readonly query: string;
|
|
117
|
+
} | {
|
|
118
|
+
readonly type: "ITEM_SELECT";
|
|
119
|
+
readonly id: ItemId;
|
|
120
|
+
} | {
|
|
121
|
+
readonly type: "ITEM_ACTIVATE";
|
|
122
|
+
readonly id: ItemId;
|
|
123
|
+
} | {
|
|
124
|
+
readonly type: "NAVIGATE";
|
|
125
|
+
readonly direction: "next" | "prev" | "first" | "last";
|
|
126
|
+
} | {
|
|
127
|
+
readonly type: "PAGE_PUSH";
|
|
128
|
+
readonly page: string;
|
|
129
|
+
} | {
|
|
130
|
+
readonly type: "PAGE_POP";
|
|
131
|
+
} | {
|
|
132
|
+
readonly type: "OPEN";
|
|
133
|
+
} | {
|
|
134
|
+
readonly type: "CLOSE";
|
|
135
|
+
} | {
|
|
136
|
+
readonly type: "TOGGLE";
|
|
137
|
+
} | {
|
|
138
|
+
readonly type: "ITEMS_LOADED";
|
|
139
|
+
readonly items: readonly CommandItem[];
|
|
140
|
+
} | {
|
|
141
|
+
readonly type: "REGISTER_ITEM";
|
|
142
|
+
readonly item: CommandItem;
|
|
143
|
+
} | {
|
|
144
|
+
readonly type: "UNREGISTER_ITEM";
|
|
145
|
+
readonly id: ItemId;
|
|
146
|
+
} | {
|
|
147
|
+
readonly type: "REGISTER_GROUP";
|
|
148
|
+
readonly group: CommandGroup;
|
|
149
|
+
} | {
|
|
150
|
+
readonly type: "UNREGISTER_GROUP";
|
|
151
|
+
readonly id: GroupId;
|
|
152
|
+
};
|
|
153
|
+
/** Frecency record for a single item */
|
|
154
|
+
interface FrecencyRecord {
|
|
155
|
+
readonly frequency: number;
|
|
156
|
+
readonly lastUsed: Temporal.Instant;
|
|
157
|
+
}
|
|
158
|
+
/** Complete frecency data for persistence */
|
|
159
|
+
interface FrecencyData {
|
|
160
|
+
readonly records: ReadonlyMap<ItemId, FrecencyRecord>;
|
|
161
|
+
}
|
|
162
|
+
/** Pluggable frecency storage interface */
|
|
163
|
+
interface FrecencyStorage extends Disposable {
|
|
164
|
+
load(namespace: string): FrecencyData | Promise<FrecencyData>;
|
|
165
|
+
save(namespace: string, data: FrecencyData): void | Promise<void>;
|
|
166
|
+
}
|
|
167
|
+
/** Frecency exponential decay bucket configuration */
|
|
168
|
+
interface FrecencyDecayConfig {
|
|
169
|
+
readonly hourWeight?: number | undefined;
|
|
170
|
+
readonly dayWeight?: number | undefined;
|
|
171
|
+
readonly weekWeight?: number | undefined;
|
|
172
|
+
readonly monthWeight?: number | undefined;
|
|
173
|
+
readonly olderWeight?: number | undefined;
|
|
174
|
+
}
|
|
175
|
+
/** Frecency configuration options */
|
|
176
|
+
interface FrecencyOptions {
|
|
177
|
+
readonly enabled: boolean;
|
|
178
|
+
readonly storage?: FrecencyStorage | undefined;
|
|
179
|
+
readonly namespace?: string | undefined;
|
|
180
|
+
readonly decayConfig?: FrecencyDecayConfig | undefined;
|
|
181
|
+
}
|
|
182
|
+
/** Default frecency decay configuration */
|
|
183
|
+
declare const DEFAULT_FRECENCY_DECAY: Required<FrecencyDecayConfig>;
|
|
184
|
+
/** Options for creating a command machine — const type param for narrowed option inference */
|
|
185
|
+
interface CommandMachineOptions {
|
|
186
|
+
/** Pre-registered items to populate on creation */
|
|
187
|
+
readonly items?: readonly CommandItem[] | undefined;
|
|
188
|
+
/** Pre-registered groups for item categorization */
|
|
189
|
+
readonly groups?: readonly CommandGroup[] | undefined;
|
|
190
|
+
/** Custom filter function, or `false` to disable filtering entirely */
|
|
191
|
+
readonly filter?: ((item: CommandItem, query: string) => number | false) | false | undefined;
|
|
192
|
+
/** Accessible label for the command palette */
|
|
193
|
+
readonly label?: string | undefined;
|
|
194
|
+
/** Initial open state */
|
|
195
|
+
readonly open?: boolean | undefined;
|
|
196
|
+
/** Frecency configuration for usage-aware ranking */
|
|
197
|
+
readonly frecency?: FrecencyOptions | undefined;
|
|
198
|
+
/** Pluggable search engine — drop-in replacement for the default scorer (e.g., WASM engine) */
|
|
199
|
+
readonly search?: SearchEngine | undefined;
|
|
200
|
+
/** Callback fired when an item is selected */
|
|
201
|
+
readonly onSelect?: ((id: ItemId) => void) | undefined;
|
|
202
|
+
/** Callback fired when the active (highlighted) item changes */
|
|
203
|
+
readonly onActiveChange?: ((id: ItemId | null) => void) | undefined;
|
|
204
|
+
/** Callback fired when the dialog open state changes */
|
|
205
|
+
readonly onOpenChange?: ((open: boolean) => void) | undefined;
|
|
206
|
+
/** Callback fired when the search query changes */
|
|
207
|
+
readonly onSearchChange?: ((query: string) => void) | undefined;
|
|
208
|
+
/** Whether keyboard navigation loops from last to first item (default: true) */
|
|
209
|
+
readonly loop?: boolean | undefined;
|
|
210
|
+
/** Item count threshold for automatic virtualization (default: 100) */
|
|
211
|
+
readonly virtualizeThreshold?: number | undefined;
|
|
212
|
+
}
|
|
213
|
+
/** Default machine options */
|
|
214
|
+
declare const DEFAULT_MACHINE_OPTIONS: Partial<CommandMachineOptions>;
|
|
215
|
+
/** Initial state factory */
|
|
216
|
+
declare function createInitialState(options?: CommandMachineOptions): CommandState;
|
|
217
|
+
//#endregion
|
|
218
|
+
//#region src/core/frecency/idb-storage.d.ts
|
|
219
|
+
/**
|
|
220
|
+
* IndexedDB-backed frecency storage using idb-keyval 6.2.2.
|
|
221
|
+
*
|
|
222
|
+
* Persists frecency data across browser sessions. Implements both
|
|
223
|
+
* `Disposable` and `AsyncDisposable` for use with `using` and `await using`.
|
|
224
|
+
*
|
|
225
|
+
* idb-keyval is lazy-loaded on first method call — consumers who import
|
|
226
|
+
* the core module without using IDB frecency pay zero cost.
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```ts
|
|
230
|
+
* await using storage = new IdbFrecencyStorage();
|
|
231
|
+
* const data = await storage.load('my-app');
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
declare class IdbFrecencyStorage implements FrecencyStorage, AsyncDisposable {
|
|
235
|
+
#private;
|
|
236
|
+
/**
|
|
237
|
+
* Create an IDB-backed frecency storage.
|
|
238
|
+
*
|
|
239
|
+
* @param dbName — IndexedDB database name (default: 'modern-cmdk')
|
|
240
|
+
* @param storeName — IndexedDB object store name (default: 'frecency')
|
|
241
|
+
*/
|
|
242
|
+
constructor(dbName?: string, storeName?: string);
|
|
243
|
+
/** Load frecency data from IndexedDB for the given namespace */
|
|
244
|
+
load(namespace: string): Promise<FrecencyData>;
|
|
245
|
+
/** Save frecency data to IndexedDB for the given namespace */
|
|
246
|
+
save(namespace: string, data: FrecencyData): Promise<void>;
|
|
247
|
+
/** Delete frecency data for a namespace from IndexedDB */
|
|
248
|
+
delete(namespace: string): Promise<void>;
|
|
249
|
+
/** Synchronous dispose — marks as disposed (best-effort cleanup) */
|
|
250
|
+
[Symbol.dispose](): void;
|
|
251
|
+
/** Async dispose — marks as disposed, suitable for `await using` */
|
|
252
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
253
|
+
}
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region src/core/frecency/index.d.ts
|
|
256
|
+
/**
|
|
257
|
+
* Computes a frecency bonus for an item based on usage frequency and recency.
|
|
258
|
+
* Uses Temporal.Duration bucket boundaries (hour/day/week/month) with
|
|
259
|
+
* configurable exponential decay weights per time bucket.
|
|
260
|
+
*/
|
|
261
|
+
declare function computeFrecencyBonus(history: FrecencyRecord, now?: Temporal.Instant, config?: FrecencyDecayConfig): number;
|
|
262
|
+
/**
|
|
263
|
+
* Stateful frecency engine that tracks item selection history and provides
|
|
264
|
+
* ranking boosts for search results. Supports pluggable storage backends
|
|
265
|
+
* and automatic flush on dispose. Uses Temporal.Now.instant() for timestamps.
|
|
266
|
+
*/
|
|
267
|
+
declare class FrecencyEngine implements Disposable, AsyncDisposable {
|
|
268
|
+
#private;
|
|
269
|
+
constructor(options: FrecencyOptions);
|
|
270
|
+
/** Load frecency data from storage */
|
|
271
|
+
load(): Promise<void>;
|
|
272
|
+
/** Record a selection event for an item */
|
|
273
|
+
recordSelection(itemId: ItemId): void;
|
|
274
|
+
/** Get frecency bonus for an item */
|
|
275
|
+
getBonus(itemId: ItemId, now?: Temporal.Instant): number;
|
|
276
|
+
/** Get all frecency bonuses as a map — for...of for hot ranking path (no closure overhead) */
|
|
277
|
+
getAllBonuses(now?: Temporal.Instant): ReadonlyMap<ItemId, number>;
|
|
278
|
+
/** Get the decay configuration */
|
|
279
|
+
getConfig(): FrecencyDecayConfig;
|
|
280
|
+
/** Update decay configuration at runtime */
|
|
281
|
+
setConfig(config: FrecencyDecayConfig): void;
|
|
282
|
+
/** Flush dirty data to storage */
|
|
283
|
+
flush(): Promise<void>;
|
|
284
|
+
/** Get the raw frecency data */
|
|
285
|
+
getData(): FrecencyData;
|
|
286
|
+
/** Async dispose — flushes dirty data before cleanup. Use with `await using`. */
|
|
287
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
288
|
+
[Symbol.dispose](): void;
|
|
289
|
+
}
|
|
290
|
+
//#endregion
|
|
291
|
+
//#region src/core/frecency/memory-storage.d.ts
|
|
292
|
+
/**
|
|
293
|
+
* In-memory implementation of FrecencyStorage using a Map.
|
|
294
|
+
* Data does not persist across page reloads. Suitable for ephemeral
|
|
295
|
+
* sessions or as the default when no persistent storage is configured.
|
|
296
|
+
*/
|
|
297
|
+
declare class MemoryFrecencyStorage implements FrecencyStorage {
|
|
298
|
+
#private;
|
|
299
|
+
load(namespace: string): FrecencyData;
|
|
300
|
+
save(namespace: string, data: FrecencyData): void;
|
|
301
|
+
/** Serialize all stored data — Iterator Helpers pipeline (ES2026) */
|
|
302
|
+
toJSON(): Record<string, {
|
|
303
|
+
records: Array<[string, FrecencyRecord]>;
|
|
304
|
+
}>;
|
|
305
|
+
[Symbol.dispose](): void;
|
|
306
|
+
}
|
|
307
|
+
//#endregion
|
|
308
|
+
//#region src/core/keyboard/parser.d.ts
|
|
309
|
+
/**
|
|
310
|
+
* Structured representation of a keyboard shortcut after parsing.
|
|
311
|
+
* The `normalized` field provides a deterministic string for dedup and comparison.
|
|
312
|
+
*/
|
|
313
|
+
interface ParsedShortcut {
|
|
314
|
+
readonly key: string;
|
|
315
|
+
readonly meta: boolean;
|
|
316
|
+
readonly ctrl: boolean;
|
|
317
|
+
readonly shift: boolean;
|
|
318
|
+
readonly alt: boolean;
|
|
319
|
+
readonly raw: string;
|
|
320
|
+
readonly normalized: string;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Parse a shortcut string like "Mod+Shift+K" into a structured ParsedShortcut.
|
|
324
|
+
* Uses String.isWellFormed() (ES2026) to ensure valid Unicode input.
|
|
325
|
+
*/
|
|
326
|
+
declare function parseShortcut(shortcut: string): ParsedShortcut;
|
|
327
|
+
/**
|
|
328
|
+
* Formats a ParsedShortcut for display using platform-appropriate symbols.
|
|
329
|
+
* On macOS: renders glyphs (⌘, ⇧, ⌥, ⌃). On other platforms: text labels joined by "+".
|
|
330
|
+
*/
|
|
331
|
+
declare function formatShortcut(parsed: ParsedShortcut): string;
|
|
332
|
+
//#endregion
|
|
333
|
+
//#region src/core/keyboard/matcher.d.ts
|
|
334
|
+
/**
|
|
335
|
+
* Tests whether a KeyboardEvent exactly matches a ParsedShortcut,
|
|
336
|
+
* comparing all four modifier keys (meta, ctrl, shift, alt) and the primary key.
|
|
337
|
+
*/
|
|
338
|
+
declare function matchesShortcut(event: KeyboardEvent, shortcut: ParsedShortcut): boolean;
|
|
339
|
+
/** Match a KeyboardEvent against a list of shortcuts — Iterator Helpers .find() */
|
|
340
|
+
declare function findMatchingShortcut(event: KeyboardEvent, shortcuts: readonly ParsedShortcut[]): ParsedShortcut | null;
|
|
341
|
+
/**
|
|
342
|
+
* Detects conflicting shortcut bindings by grouping on their normalized form
|
|
343
|
+
* via Map.groupBy (ES2026). Returns only groups with two or more colliding shortcuts.
|
|
344
|
+
* Map.groupBy returns a Map directly — no Object.entries conversion needed.
|
|
345
|
+
*/
|
|
346
|
+
declare function detectConflicts(shortcuts: readonly ParsedShortcut[]): ReadonlyMap<string, readonly ParsedShortcut[]>;
|
|
347
|
+
//#endregion
|
|
348
|
+
//#region src/core/keyboard/index.d.ts
|
|
349
|
+
interface ShortcutBinding {
|
|
350
|
+
readonly shortcut: ParsedShortcut;
|
|
351
|
+
readonly itemId: ItemId;
|
|
352
|
+
readonly handler: () => void;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Manages keyboard shortcut bindings with global keydown listening.
|
|
356
|
+
* Supports `using` for automatic cleanup of both individual bindings and the
|
|
357
|
+
* global document listener. Detects conflicts via Map.groupBy (ES2026).
|
|
358
|
+
*/
|
|
359
|
+
declare class KeyboardShortcutRegistry implements Disposable {
|
|
360
|
+
#private;
|
|
361
|
+
constructor(options?: {
|
|
362
|
+
enabled?: boolean;
|
|
363
|
+
});
|
|
364
|
+
/** Register a shortcut binding — returns Disposable for `using` auto-deregister */
|
|
365
|
+
register(shortcutStr: string, itemId: ItemId, handler: () => void): Disposable;
|
|
366
|
+
/** Unregister a shortcut by its string representation */
|
|
367
|
+
unregister(shortcutStr: string): void;
|
|
368
|
+
/** Unregister all shortcuts for a given item — Map spec allows deletion during iteration */
|
|
369
|
+
unregisterByItem(itemId: ItemId): void;
|
|
370
|
+
/** Check for conflicting shortcuts */
|
|
371
|
+
getConflicts(): ReadonlyMap<string, readonly ParsedShortcut[]>;
|
|
372
|
+
/** Enable or disable global shortcut listening */
|
|
373
|
+
setEnabled(enabled: boolean): void;
|
|
374
|
+
/** Get all registered bindings */
|
|
375
|
+
getBindings(): ReadonlyMap<string, ShortcutBinding>;
|
|
376
|
+
[Symbol.dispose](): void;
|
|
377
|
+
}
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region src/core/registry.d.ts
|
|
380
|
+
/**
|
|
381
|
+
* Manages registration and lookup of command items and groups.
|
|
382
|
+
* Uses ES2026 Set methods (union/intersection/difference) for efficient ID operations.
|
|
383
|
+
* All registration methods return Disposable for automatic cleanup via `using`.
|
|
384
|
+
*/
|
|
385
|
+
declare class CommandRegistry implements Disposable {
|
|
386
|
+
#private;
|
|
387
|
+
/** Register an item — returns Disposable for `using` auto-deregister */
|
|
388
|
+
registerItem(item: CommandItem): Disposable;
|
|
389
|
+
/** Register multiple items at once — single Disposable for batch cleanup. for...of for hot path. */
|
|
390
|
+
registerItems(items: readonly CommandItem[]): Disposable;
|
|
391
|
+
/** Unregister a single item — O(1) Map/Set delete, indexOf+toSpliced for order */
|
|
392
|
+
unregisterItem(id: ItemId): void;
|
|
393
|
+
/** Bulk unregister using Set.difference (ES2026) — for...of for hot path */
|
|
394
|
+
unregisterItems(ids: ReadonlySet<ItemId>): void;
|
|
395
|
+
/** Register a group */
|
|
396
|
+
registerGroup(group: CommandGroup): Disposable;
|
|
397
|
+
/** Unregister a group */
|
|
398
|
+
unregisterGroup(id: GroupId): void;
|
|
399
|
+
/** Get an item by ID — O(1) */
|
|
400
|
+
getItem(id: ItemId): CommandItem | undefined;
|
|
401
|
+
/** Get a group by ID — O(1) */
|
|
402
|
+
getGroup(id: GroupId): CommandGroup | undefined;
|
|
403
|
+
/** Get all registered items in insertion order — cached between mutations */
|
|
404
|
+
getItems(): readonly CommandItem[];
|
|
405
|
+
/** Get all registered groups sorted by priority — cached between group mutations */
|
|
406
|
+
getGroups(): readonly CommandGroup[];
|
|
407
|
+
/** Get items grouped by groupId using Map.groupBy (ES2026) — direct Map, no Object.entries conversion */
|
|
408
|
+
getGroupedItems(): ReadonlyMap<GroupId, readonly CommandItem[]>;
|
|
409
|
+
/** Get item IDs as a Set — returns internal set (O(1), no allocation) */
|
|
410
|
+
getItemIds(): ReadonlySet<ItemId>;
|
|
411
|
+
/** Find intersection of registered items with a candidate set (ES2026 Set.intersection) */
|
|
412
|
+
intersectWith(candidateIds: ReadonlySet<ItemId>): ReadonlySet<ItemId>;
|
|
413
|
+
/** Find items registered but not in a given set (ES2026 Set.difference) */
|
|
414
|
+
differenceFrom(otherIds: ReadonlySet<ItemId>): ReadonlySet<ItemId>;
|
|
415
|
+
/** Union of registered IDs with another set (ES2026 Set.union) */
|
|
416
|
+
unionWith(otherIds: ReadonlySet<ItemId>): ReadonlySet<ItemId>;
|
|
417
|
+
/** Check if registered items are a subset of the given set (ES2026 Set.isSubsetOf) */
|
|
418
|
+
isSubsetOf(otherIds: ReadonlySet<ItemId>): boolean;
|
|
419
|
+
/** Check if registered items are a superset of the given set (ES2026 Set.isSupersetOf) */
|
|
420
|
+
isSupersetOf(otherIds: ReadonlySet<ItemId>): boolean;
|
|
421
|
+
/** Check if registered items share no IDs with the given set (ES2026 Set.isDisjointFrom) */
|
|
422
|
+
isDisjointFrom(otherIds: ReadonlySet<ItemId>): boolean;
|
|
423
|
+
/** Symmetric difference between registered IDs and another set (ES2026 Set.symmetricDifference) */
|
|
424
|
+
symmetricDifferenceWith(otherIds: ReadonlySet<ItemId>): ReadonlySet<ItemId>;
|
|
425
|
+
/** Total number of registered items */
|
|
426
|
+
get size(): number;
|
|
427
|
+
/** Total number of registered groups */
|
|
428
|
+
get groupCount(): number;
|
|
429
|
+
/** Clear all items and groups */
|
|
430
|
+
clear(): void;
|
|
431
|
+
[Symbol.dispose](): void;
|
|
432
|
+
}
|
|
433
|
+
//#endregion
|
|
434
|
+
//#region src/core/machine.d.ts
|
|
435
|
+
/** Command machine — explicit resource management via `using` */
|
|
436
|
+
interface CommandMachine extends Disposable {
|
|
437
|
+
getState(): CommandState;
|
|
438
|
+
send(event: CommandEvent): void;
|
|
439
|
+
/** Subscribe to state changes — useSyncExternalStore-compatible (returns cleanup function) */
|
|
440
|
+
subscribe(listener: () => void): () => void;
|
|
441
|
+
/** Subscribe to full state changes — returns Disposable for `using` pattern */
|
|
442
|
+
subscribeState(listener: (state: CommandState) => void): Disposable;
|
|
443
|
+
getRegistry(): CommandRegistry;
|
|
444
|
+
getKeyboardRegistry(): KeyboardShortcutRegistry;
|
|
445
|
+
/** Get the current set of filtered item IDs — O(1) membership checks */
|
|
446
|
+
getFilteredIdSet(): ReadonlySet<ItemId>;
|
|
447
|
+
/** Get the index map for filtered IDs — O(1) positional lookup */
|
|
448
|
+
getFilteredIdIndex(): ReadonlyMap<ItemId, number>;
|
|
449
|
+
[Symbol.dispose](): void;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Creates a headless command palette state machine with search, frecency,
|
|
453
|
+
* and keyboard shortcut support. Implements Disposable for `using` cleanup.
|
|
454
|
+
*
|
|
455
|
+
* @example
|
|
456
|
+
* ```ts
|
|
457
|
+
* using machine = createCommandMachine({ items, loop: true });
|
|
458
|
+
* machine.subscribe(() => console.log(machine.getState()));
|
|
459
|
+
* ```
|
|
460
|
+
*/
|
|
461
|
+
declare function createCommandMachine(options?: CommandMachineOptions): CommandMachine;
|
|
462
|
+
//#endregion
|
|
463
|
+
//#region src/core/search/default-scorer.d.ts
|
|
464
|
+
/**
|
|
465
|
+
* Public API: scores a command item against a search query.
|
|
466
|
+
* Multi-strategy: exact → prefix → substring → word-boundary → fuzzy.
|
|
467
|
+
* Returns null if no match. For batch use, prefer the search engine
|
|
468
|
+
* which pre-caches lowercase and normalizes query once.
|
|
469
|
+
*/
|
|
470
|
+
declare function scoreItem(query: string, item: CommandItem): SearchResult | null;
|
|
471
|
+
//#endregion
|
|
472
|
+
//#region src/core/search/fuzzy-scorer.d.ts
|
|
473
|
+
/**
|
|
474
|
+
* Async-safe fuzzy scorer — wraps scoring in Promise.try (ES2026) for error resilience.
|
|
475
|
+
* Falls back gracefully on error via Promise.try's synchronous-throw capture.
|
|
476
|
+
*/
|
|
477
|
+
declare function scoreItemAsync(query: string, item: CommandItem): Promise<SearchResult | null>;
|
|
478
|
+
/**
|
|
479
|
+
* Batch score items synchronously inside a single Promise.try (ES2026).
|
|
480
|
+
* Eliminates per-item microtask overhead from async generators — runs the
|
|
481
|
+
* entire scoring pipeline in one synchronous pass, wrapped for error safety.
|
|
482
|
+
*/
|
|
483
|
+
declare function batchScoreItems(query: string, items: readonly CommandItem[]): Promise<SearchResult[]>;
|
|
484
|
+
//#endregion
|
|
485
|
+
//#region src/core/search/index.d.ts
|
|
486
|
+
interface SearchEngineOptions {
|
|
487
|
+
readonly scorer?: ScorerFn | undefined;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Creates a search engine with pluggable scorer and incremental filtering.
|
|
491
|
+
* When the query extends the previous one, only prior matches are re-scored.
|
|
492
|
+
* Pre-caches lowercase at index time — zero toLowerCase() calls during search.
|
|
493
|
+
* Uses Iterator Helpers (ES2026) for result pipelines and Set.difference for pruning.
|
|
494
|
+
*/
|
|
495
|
+
declare function createSearchEngine(options?: SearchEngineOptions): SearchEngine;
|
|
496
|
+
//#endregion
|
|
497
|
+
//#region src/core/telemetry.d.ts
|
|
498
|
+
interface CommandTelemetryHooks {
|
|
499
|
+
/** Called when the command palette opens */
|
|
500
|
+
readonly onPaletteOpen?: () => void;
|
|
501
|
+
/** Called when the command palette closes, with session duration in ms */
|
|
502
|
+
readonly onPaletteClose?: (durationMs: number) => void;
|
|
503
|
+
/** Called after each search completes, with query, result count, and latency */
|
|
504
|
+
readonly onSearchComplete?: (query: string, resultCount: number, durationMs: number) => void;
|
|
505
|
+
/** Called when an item is selected, with item ID, active query, and position in list */
|
|
506
|
+
readonly onItemSelected?: (itemId: ItemId, searchQuery: string, position: number) => void;
|
|
507
|
+
}
|
|
508
|
+
interface TelemetryMiddleware {
|
|
509
|
+
readonly onOpen: () => void;
|
|
510
|
+
readonly onClose: () => void;
|
|
511
|
+
readonly onSearch: (query: string, resultCount: number, startTime: Temporal.Instant) => void;
|
|
512
|
+
readonly onSelect: (itemId: ItemId, query: string, position: number) => void;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Creates a telemetry middleware that measures command palette interactions.
|
|
516
|
+
* Uses Temporal.Now.instant() (ES2026) for nanosecond-precision timestamps
|
|
517
|
+
* and Temporal.Instant.since() for accurate duration computation.
|
|
518
|
+
*
|
|
519
|
+
* @example
|
|
520
|
+
* ```ts
|
|
521
|
+
* const telemetry = createTelemetryMiddleware({
|
|
522
|
+
* onPaletteOpen: () => analytics.track('palette_opened'),
|
|
523
|
+
* onItemSelected: (id, query, pos) => analytics.track('item_selected', { id, query, position: pos }),
|
|
524
|
+
* });
|
|
525
|
+
* ```
|
|
526
|
+
*/
|
|
527
|
+
declare function createTelemetryMiddleware(hooks: CommandTelemetryHooks): TelemetryMiddleware;
|
|
528
|
+
//#endregion
|
|
529
|
+
//#region src/core/utils/event-emitter.d.ts
|
|
530
|
+
type EventMap = {
|
|
531
|
+
[key: string]: unknown;
|
|
532
|
+
};
|
|
533
|
+
/**
|
|
534
|
+
* Type-safe event emitter with strong references and Disposable subscriptions.
|
|
535
|
+
* Returns Disposable from `on()` for automatic unsubscription via `using`.
|
|
536
|
+
*/
|
|
537
|
+
declare class TypedEmitter<T extends EventMap> implements Disposable {
|
|
538
|
+
#private;
|
|
539
|
+
/** Subscribe to an event. Returns a Disposable that removes the listener when disposed. */
|
|
540
|
+
on<K extends keyof T>(event: K, listener: (data: T[K]) => void): Disposable;
|
|
541
|
+
/** Emit an event to all listeners. Snapshots the set to prevent live-iteration issues. */
|
|
542
|
+
emit<K extends keyof T>(event: K, data: T[K]): void;
|
|
543
|
+
/** Check if any listeners exist for an event */
|
|
544
|
+
has<K extends keyof T>(event: K): boolean;
|
|
545
|
+
/** Get count of listeners for an event */
|
|
546
|
+
listenerCount<K extends keyof T>(event: K): number;
|
|
547
|
+
/** Remove all listeners */
|
|
548
|
+
removeAll(): void;
|
|
549
|
+
[Symbol.dispose](): void;
|
|
550
|
+
}
|
|
551
|
+
//#endregion
|
|
552
|
+
//#region src/core/utils/scheduler.d.ts
|
|
553
|
+
/**
|
|
554
|
+
* Batched update scheduler that coalesces state updates into a single tick.
|
|
555
|
+
* Uses requestAnimationFrame in browsers, queueMicrotask in Node.js/tests.
|
|
556
|
+
*/
|
|
557
|
+
interface Scheduler extends Disposable {
|
|
558
|
+
schedule(update: () => void): void;
|
|
559
|
+
flush(): void;
|
|
560
|
+
[Symbol.dispose](): void;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Creates a batched update scheduler. Coalesces rapid state updates into a single
|
|
564
|
+
* execution pass using rAF (browser) or queueMicrotask (Node.js).
|
|
565
|
+
*/
|
|
566
|
+
declare function createScheduler(): Scheduler;
|
|
567
|
+
//#endregion
|
|
568
|
+
export { type CommandEvent, type CommandGroup, type CommandItem, type CommandMachine, type CommandMachineOptions, CommandRegistry, type CommandState, type CommandTelemetryHooks, DEFAULT_FRECENCY_DECAY, DEFAULT_MACHINE_OPTIONS, type FrecencyData, type FrecencyDecayConfig, FrecencyEngine, type FrecencyOptions, type FrecencyRecord, type FrecencyStorage, type GroupId, IdbFrecencyStorage, type ItemId, KeyboardShortcutRegistry, MemoryFrecencyStorage, type ParsedShortcut, type Scheduler, type ScorerFn, type SearchEngine, type SearchResult, type TelemetryMiddleware, TypedEmitter, batchScoreItems, computeFrecencyBonus, createCommandMachine, createInitialState, createScheduler, createSearchEngine, createTelemetryMiddleware, detectConflicts, findMatchingShortcut, formatShortcut, groupId, itemId, matchesShortcut, parseShortcut, scoreItem, scoreItemAsync };
|