modern-cmdk 1.4.0 → 1.6.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 +54 -34
- package/THIRD-PARTY-NOTICES.md +303 -0
- package/dist/codemod/bin.mjs +14 -0
- package/dist/codemod/chunk-jscodeshift.mjs +36210 -0
- package/dist/codemod/chunk-print-options.mjs +43 -0
- package/dist/codemod/chunk-worker.mjs +485 -0
- package/dist/codemod/cli.mjs +297 -43660
- package/dist/codemod/transforms/data-attrs.mjs +583 -39
- package/dist/codemod/transforms/forward-ref.mjs +197 -32
- package/dist/codemod/transforms/import-rewrite.mjs +389 -30
- package/dist/codemod/transforms/should-filter.mjs +199 -10
- package/dist/codemod/worker.mjs +1 -0
- package/dist/core/index.d.mts +122 -35
- package/dist/core/index.mjs +466 -292
- package/dist/react/dialog.mjs +0 -1
- package/dist/react/index.d.mts +25 -4
- package/dist/react/index.mjs +58 -25
- package/dist/react/styles.css +33 -31
- package/package.json +5 -7
package/dist/core/index.d.mts
CHANGED
|
@@ -2,14 +2,23 @@
|
|
|
2
2
|
interface SearchResult {
|
|
3
3
|
readonly id: ItemId;
|
|
4
4
|
readonly score: number;
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Character ranges that matched (for highlighting) — half-open [start, end) tuples, end exclusive. The
|
|
7
|
+
* ranges index the original `item.value`, not its lowercased copy. A match on keywords only reports
|
|
8
|
+
* `matches: []`: there is nothing in the value to highlight.
|
|
9
|
+
*/
|
|
6
10
|
readonly matches: ReadonlyArray<readonly [start: number, end: number]>;
|
|
7
11
|
}
|
|
8
12
|
/** A scoring function that evaluates a single item against a query */
|
|
9
13
|
type ScorerFn = (query: string, item: CommandItem) => SearchResult | null;
|
|
10
14
|
/** Search engine interface — pluggable into the state machine */
|
|
11
15
|
interface SearchEngine extends Disposable {
|
|
12
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Index items for fast lookup. The machine calls it on registration (the initial items, ITEMS_LOADED,
|
|
18
|
+
* REGISTER_ITEM) and again on UPDATE_ITEM with the updated item, so an id that is already indexed must
|
|
19
|
+
* be replaced by the new item, not appended to the index. Indexing is incremental: ids not in `items`
|
|
20
|
+
* stay indexed.
|
|
21
|
+
*/
|
|
13
22
|
index(items: readonly CommandItem[]): void;
|
|
14
23
|
/** Search items by query, return ordered results — uses Iterator pipeline */
|
|
15
24
|
search(query: string, items: readonly CommandItem[]): IteratorObject<SearchResult>;
|
|
@@ -42,6 +51,12 @@ declare const __groupIdBrand: unique symbol;
|
|
|
42
51
|
type GroupId = string & {
|
|
43
52
|
readonly [__groupIdBrand]: never;
|
|
44
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* The `groupedIds` key of items that have no group. The value is an observable key of
|
|
56
|
+
* `CommandState.groupedIds` and stays `'__ungrouped'`. A group registered under this id shares the
|
|
57
|
+
* bucket: its items are listed with the ungrouped ones and the bucket takes its priority.
|
|
58
|
+
*/
|
|
59
|
+
export declare const UNGROUPED_GROUP_ID: GroupId;
|
|
45
60
|
/** Create a branded {@link ItemId} from a plain string. Ensures well-formed Unicode. */
|
|
46
61
|
export declare function itemId(id: string): ItemId;
|
|
47
62
|
/** Create a branded {@link GroupId} from a plain string. Ensures well-formed Unicode. */
|
|
@@ -61,7 +76,7 @@ interface CommandItem {
|
|
|
61
76
|
readonly groupId?: GroupId | undefined;
|
|
62
77
|
/** Keyboard shortcut string (e.g., "Ctrl+C", "Mod+K") */
|
|
63
78
|
readonly shortcut?: string | undefined;
|
|
64
|
-
/** Whether this item is disabled
|
|
79
|
+
/** Whether this item is disabled: excluded from navigation and selection; rendered unless `disabledItems: 'hide'` */
|
|
65
80
|
readonly disabled?: boolean | undefined;
|
|
66
81
|
/** Callback fired when this item is selected */
|
|
67
82
|
readonly onSelect?: (() => void) | undefined;
|
|
@@ -92,7 +107,12 @@ interface CommandState {
|
|
|
92
107
|
readonly activeId: ItemId | null;
|
|
93
108
|
/** Ordered list of item IDs that pass the current filter */
|
|
94
109
|
readonly filteredIds: readonly ItemId[];
|
|
95
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* Map of group IDs to their visible item IDs. The keys are in group priority order (lower first; ungrouped
|
|
112
|
+
* items and unregistered group ids count as 0, in first-appearance order), while `filteredIds` and
|
|
113
|
+
* `NAVIGATE` keep the search order: a renderer that draws by `groupedIds` shows the groups in this order,
|
|
114
|
+
* so arrow-key order and display order can differ when group priorities differ.
|
|
115
|
+
*/
|
|
96
116
|
readonly groupedIds: ReadonlyMap<GroupId, readonly ItemId[]>;
|
|
97
117
|
/** Count of visible items (shortcut for `filteredIds.length`) */
|
|
98
118
|
readonly filteredCount: number;
|
|
@@ -134,9 +154,16 @@ type CommandEvent = {
|
|
|
134
154
|
} | {
|
|
135
155
|
readonly type: "ITEMS_LOADED";
|
|
136
156
|
readonly items: readonly CommandItem[];
|
|
157
|
+
} | {
|
|
158
|
+
readonly type: "LOADING";
|
|
159
|
+
readonly source: string;
|
|
160
|
+
readonly pending: boolean;
|
|
137
161
|
} | {
|
|
138
162
|
readonly type: "REGISTER_ITEM";
|
|
139
163
|
readonly item: CommandItem;
|
|
164
|
+
} | {
|
|
165
|
+
readonly type: "UPDATE_ITEM";
|
|
166
|
+
readonly item: CommandItem;
|
|
140
167
|
} | {
|
|
141
168
|
readonly type: "UNREGISTER_ITEM";
|
|
142
169
|
readonly id: ItemId;
|
|
@@ -172,7 +199,9 @@ interface FrecencyDecayConfig {
|
|
|
172
199
|
}
|
|
173
200
|
/** Frecency configuration options */
|
|
174
201
|
interface FrecencyOptions {
|
|
175
|
-
|
|
202
|
+
/** Turns frecency tracking and re-ranking on. Default false. */
|
|
203
|
+
readonly enabled?: boolean | undefined;
|
|
204
|
+
/** Where the history persists; a storage you pass is yours to dispose, the engine disposes only the in-memory one it creates when you pass none. */
|
|
176
205
|
readonly storage?: FrecencyStorage | undefined;
|
|
177
206
|
readonly namespace?: string | undefined;
|
|
178
207
|
readonly decayConfig?: FrecencyDecayConfig | undefined;
|
|
@@ -185,7 +214,10 @@ interface CommandMachineOptions {
|
|
|
185
214
|
readonly items?: readonly CommandItem[] | undefined;
|
|
186
215
|
/** Pre-registered groups for item categorization */
|
|
187
216
|
readonly groups?: readonly CommandGroup[] | undefined;
|
|
188
|
-
/**
|
|
217
|
+
/**
|
|
218
|
+
* Custom filter function, or `false` to disable filtering entirely. It returns a score: above 0 keeps the item
|
|
219
|
+
* (higher ranks first); 0, a negative score or `false` excludes it.
|
|
220
|
+
*/
|
|
189
221
|
readonly filter?: ((item: CommandItem, query: string) => number | false) | false | undefined;
|
|
190
222
|
/** Accessible label for the command palette */
|
|
191
223
|
readonly label?: string | undefined;
|
|
@@ -205,15 +237,31 @@ interface CommandMachineOptions {
|
|
|
205
237
|
readonly onSearchChange?: ((query: string) => void) | undefined;
|
|
206
238
|
/** Whether keyboard navigation loops from last to first item (default: true) */
|
|
207
239
|
readonly loop?: boolean | undefined;
|
|
240
|
+
/**
|
|
241
|
+
* How disabled items are listed: `'show'` keeps them in `filteredIds` under every query (rendered with
|
|
242
|
+
* `aria-disabled`, never active, skipped by navigation and activation, not selectable); `'hide'` drops them
|
|
243
|
+
* from `filteredIds` in every branch (default: `'show'`)
|
|
244
|
+
*/
|
|
245
|
+
readonly disabledItems?: "hide" | "show" | undefined;
|
|
246
|
+
/**
|
|
247
|
+
* What an item's keyboard shortcut does. `'direct'` (the default through 1.x) calls the item's `onSelect`
|
|
248
|
+
* and binds only items that have one; `'select'` dispatches `ITEM_SELECT` for the item, so the disabled guard,
|
|
249
|
+
* frecency, the select event and `options.onSelect` apply, and binds every enabled item with a shortcut. 2.0 flips
|
|
250
|
+
* the default to `'select'`. Under both, a disabled item is not bound: its shortcut does nothing and leaves the key to the page.
|
|
251
|
+
*/
|
|
252
|
+
readonly shortcutAction?: "select" | "direct" | undefined;
|
|
208
253
|
/** Accepted but not read yet: Command.List uses fixed thresholds (on above 100 matches, off at 50 or fewer) (default: 100) */
|
|
209
254
|
readonly virtualizeThreshold?: number | undefined;
|
|
210
255
|
}
|
|
211
|
-
/**
|
|
256
|
+
/**
|
|
257
|
+
* Default machine options. The machine reads `loop` and `open` from here when its options leave them out
|
|
258
|
+
* (the other options are opt-ins whose absence is their default); `virtualizeThreshold` is not read yet.
|
|
259
|
+
*/
|
|
212
260
|
export declare const DEFAULT_MACHINE_OPTIONS: Partial<CommandMachineOptions>;
|
|
213
261
|
/** Initial state factory */
|
|
214
262
|
export declare function createInitialState(options?: CommandMachineOptions): CommandState;
|
|
215
263
|
/**
|
|
216
|
-
* IndexedDB-backed frecency storage using idb-keyval.
|
|
264
|
+
* IndexedDB-backed frecency storage using idb-keyval's get, set and del.
|
|
217
265
|
*
|
|
218
266
|
* Persists frecency data across browser sessions. Implements both
|
|
219
267
|
* `Disposable` and `AsyncDisposable` for use with `using` and `await using`.
|
|
@@ -255,15 +303,27 @@ export declare class IdbFrecencyStorage implements FrecencyStorage, AsyncDisposa
|
|
|
255
303
|
export declare function computeFrecencyBonus(history: FrecencyRecord, now?: number, config?: FrecencyDecayConfig): number;
|
|
256
304
|
/**
|
|
257
305
|
* Stateful frecency engine that tracks item selection history and provides
|
|
258
|
-
* ranking boosts for search results. Supports pluggable storage backends
|
|
259
|
-
*
|
|
306
|
+
* ranking boosts for search results. Supports pluggable storage backends.
|
|
307
|
+
* Dirty data is saved 1000 ms after the last `recordSelection`, when the page
|
|
308
|
+
* becomes hidden or is hidden for good (`visibilitychange`, `pagehide`), on
|
|
309
|
+
* `flush()` and on dispose; a save waits for a `load()` in flight, so it writes the merged
|
|
310
|
+
* history. Dispose disposes the storage only when the engine created it (no `storage`
|
|
311
|
+
* option): a storage the caller passes is the caller's to dispose. Uses Date.now() for
|
|
312
|
+
* timestamps.
|
|
260
313
|
*/
|
|
261
314
|
export declare class FrecencyEngine implements Disposable, AsyncDisposable {
|
|
262
315
|
#private;
|
|
263
316
|
constructor(options: FrecencyOptions);
|
|
264
|
-
/**
|
|
317
|
+
/**
|
|
318
|
+
* Merge the data in storage into the engine: frequencies add, `lastUsed` takes the later
|
|
319
|
+
* time, and selections recorded before the load resolves are kept. Call it once per
|
|
320
|
+
* engine; a second load would add the persisted frequencies again. A save started while
|
|
321
|
+
* the load is in flight waits for it, so the history it writes is the merged one, and a load
|
|
322
|
+
* that never settles holds every save, and `await using`, forever. A failed load rejects here
|
|
323
|
+
* and turns persistence off for this engine: saving would replace a history it could not read.
|
|
324
|
+
*/
|
|
265
325
|
load(): Promise<void>;
|
|
266
|
-
/** Record a selection event for an item */
|
|
326
|
+
/** Record a selection event for an item and schedule a save 1000 ms after the last one */
|
|
267
327
|
recordSelection(itemId: ItemId): void;
|
|
268
328
|
/** Get frecency bonus for an item */
|
|
269
329
|
getBonus(itemId: ItemId, now?: number): number;
|
|
@@ -273,12 +333,20 @@ export declare class FrecencyEngine implements Disposable, AsyncDisposable {
|
|
|
273
333
|
getConfig(): FrecencyDecayConfig;
|
|
274
334
|
/** Update decay configuration at runtime */
|
|
275
335
|
setConfig(config: FrecencyDecayConfig): void;
|
|
276
|
-
/**
|
|
336
|
+
/**
|
|
337
|
+
* Flush dirty data to storage and wait for every save in flight; saves run one after
|
|
338
|
+
* another, each after the load in flight (one that never settles holds them all), none after a
|
|
339
|
+
* rejected load, and a rejected save leaves the data dirty and rethrows to the flush that started it
|
|
340
|
+
*/
|
|
277
341
|
flush(): Promise<void>;
|
|
278
342
|
/** Get the raw frecency data */
|
|
279
343
|
getData(): FrecencyData;
|
|
280
|
-
/**
|
|
344
|
+
/**
|
|
345
|
+
* Async dispose — flushes dirty data, then disposes the storage the engine created, even
|
|
346
|
+
* when the save rejects (the rejection propagates). Use with `await using`.
|
|
347
|
+
*/
|
|
281
348
|
[Symbol.asyncDispose](): Promise<void>;
|
|
349
|
+
/** Sync dispose — starts the flush without awaiting it and disposes the storage the engine created once it settles */
|
|
282
350
|
[Symbol.dispose](): void;
|
|
283
351
|
}
|
|
284
352
|
/**
|
|
@@ -340,7 +408,9 @@ interface ShortcutBinding {
|
|
|
340
408
|
/**
|
|
341
409
|
* Manages keyboard shortcut bindings with global keydown listening.
|
|
342
410
|
* Supports `using` for automatic cleanup of both individual bindings and the
|
|
343
|
-
* global document listener.
|
|
411
|
+
* global document listener. Every normalized shortcut keeps a stack of bindings, one per item: the later
|
|
412
|
+
* binding wins the keydown, disposing it restores the earlier one, and `getConflicts()` reports the keys with
|
|
413
|
+
* two or more bindings (through `detectConflicts`, which groups with the local mapGroupBy helper).
|
|
344
414
|
*/
|
|
345
415
|
export declare class KeyboardShortcutRegistry implements Disposable {
|
|
346
416
|
#private;
|
|
@@ -349,15 +419,18 @@ export declare class KeyboardShortcutRegistry implements Disposable {
|
|
|
349
419
|
});
|
|
350
420
|
/** Register a shortcut binding — returns Disposable for `using` auto-deregister */
|
|
351
421
|
register(shortcutStr: string, itemId: ItemId, handler: () => void): Disposable;
|
|
352
|
-
/** Unregister a shortcut by its string representation */
|
|
422
|
+
/** Unregister a shortcut by its string representation — every binding of that key */
|
|
353
423
|
unregister(shortcutStr: string): void;
|
|
354
|
-
/**
|
|
355
|
-
|
|
356
|
-
|
|
424
|
+
/**
|
|
425
|
+
* Unregister all shortcuts for a given item — only the stacks of the keys it is bound under are touched.
|
|
426
|
+
* `keep` names one shortcut whose binding stays (a re-bind then replaces it in place, see register).
|
|
427
|
+
*/
|
|
428
|
+
unregisterByItem(itemId: ItemId, keep?: string): void;
|
|
429
|
+
/** Keys bound by two or more items (normalized shortcut -> the parsed shortcuts, earliest first) */
|
|
357
430
|
getConflicts(): ReadonlyMap<string, readonly ParsedShortcut[]>;
|
|
358
431
|
/** Enable or disable global shortcut listening */
|
|
359
432
|
setEnabled(enabled: boolean): void;
|
|
360
|
-
/**
|
|
433
|
+
/** The winning binding of every key (a snapshot: the later of the bindings that share a key) */
|
|
361
434
|
getBindings(): ReadonlyMap<string, ShortcutBinding>;
|
|
362
435
|
[Symbol.dispose](): void;
|
|
363
436
|
}
|
|
@@ -372,7 +445,7 @@ export declare class CommandRegistry implements Disposable {
|
|
|
372
445
|
registerItem(item: CommandItem): Disposable;
|
|
373
446
|
/** Register multiple items at once — single Disposable for batch cleanup. for...of for hot path. */
|
|
374
447
|
registerItems(items: readonly CommandItem[]): Disposable;
|
|
375
|
-
/** Unregister a single item — O(1) Map/Set delete
|
|
448
|
+
/** Unregister a single item — O(1) Map/Set delete */
|
|
376
449
|
unregisterItem(id: ItemId): void;
|
|
377
450
|
/** Bulk unregister — for...of for hot path */
|
|
378
451
|
unregisterItems(ids: ReadonlySet<ItemId>): void;
|
|
@@ -432,20 +505,31 @@ interface CommandMachine extends Disposable {
|
|
|
432
505
|
}
|
|
433
506
|
/**
|
|
434
507
|
* Creates a headless command palette state machine with search, frecency,
|
|
435
|
-
* and keyboard shortcut support. Implements Disposable
|
|
508
|
+
* and keyboard shortcut support. Implements Disposable and AsyncDisposable for
|
|
509
|
+
* `using` and `await using` cleanup (the latter awaits the pending frecency save).
|
|
436
510
|
*
|
|
437
511
|
* @example
|
|
438
512
|
* ```ts
|
|
439
|
-
*
|
|
440
|
-
* machine.subscribe(() => console.log(machine.getState()));
|
|
513
|
+
* const machine = createCommandMachine({ items, loop: true });
|
|
514
|
+
* const unsubscribe = machine.subscribe(() => console.log(machine.getState()));
|
|
515
|
+
* machine.send({ type: 'SEARCH_CHANGE', query: 'app' }); // handled in the next frame
|
|
516
|
+
*
|
|
517
|
+
* // on teardown
|
|
518
|
+
* unsubscribe();
|
|
519
|
+
* machine[Symbol.dispose]();
|
|
441
520
|
* ```
|
|
442
521
|
*/
|
|
443
|
-
export declare function createCommandMachine(options?: CommandMachineOptions): CommandMachine;
|
|
522
|
+
export declare function createCommandMachine(options?: CommandMachineOptions): CommandMachine & AsyncDisposable;
|
|
444
523
|
/**
|
|
445
524
|
* Public API: scores a command item against a search query.
|
|
446
525
|
* Multi-strategy: exact → prefix → substring → word-boundary → fuzzy.
|
|
447
526
|
* Returns null if no match. For batch use, prefer the search engine
|
|
448
527
|
* which pre-caches lowercase and normalizes query once.
|
|
528
|
+
*
|
|
529
|
+
* `matches` are half-open ranges into `item.value` itself (UTF-16 indices of the original
|
|
530
|
+
* string, even where lowercasing changes its length, as 'İ' → 'i̇' does). A keyword has no
|
|
531
|
+
* position in the label: when a keyword outscores the value, `matches` keeps the value's own
|
|
532
|
+
* ranges if the value matched and is `[]` otherwise.
|
|
449
533
|
*/
|
|
450
534
|
export declare function scoreItem(query: string, item: CommandItem): SearchResult | null;
|
|
451
535
|
/**
|
|
@@ -463,9 +547,10 @@ interface SearchEngineOptions {
|
|
|
463
547
|
readonly scorer?: ScorerFn | undefined;
|
|
464
548
|
}
|
|
465
549
|
/**
|
|
466
|
-
* Creates a search engine with pluggable scorer
|
|
467
|
-
*
|
|
468
|
-
*
|
|
550
|
+
* Creates a search engine with pluggable scorer.
|
|
551
|
+
* With the default scorer, a query that extends the previous one (compared lowercased) re-scores
|
|
552
|
+
* only the prior matches; a custom scorer is called for every item on every non-empty query.
|
|
553
|
+
* Pre-caches lowercase at index time; indexed items are not re-lowered during search.
|
|
469
554
|
* Uses Iterator Helpers (ES2025) for result pipelines.
|
|
470
555
|
*/
|
|
471
556
|
export declare function createSearchEngine(options?: SearchEngineOptions): SearchEngine;
|
|
@@ -499,18 +584,19 @@ interface TelemetryMiddleware {
|
|
|
499
584
|
* ```
|
|
500
585
|
*/
|
|
501
586
|
export declare function createTelemetryMiddleware(hooks: CommandTelemetryHooks): TelemetryMiddleware;
|
|
502
|
-
type EventMap = {
|
|
503
|
-
[key: string]: unknown;
|
|
504
|
-
};
|
|
505
587
|
/**
|
|
506
588
|
* Type-safe event emitter with strong references and Disposable subscriptions.
|
|
589
|
+
* `T` maps event names to payload types; an interface or a type alias both work (`object`, not an index signature, which an interface lacks).
|
|
507
590
|
* Returns Disposable from `on()` for automatic unsubscription via `using`.
|
|
508
591
|
*/
|
|
509
|
-
export declare class TypedEmitter<T extends
|
|
592
|
+
export declare class TypedEmitter<T extends object> implements Disposable {
|
|
510
593
|
#private;
|
|
511
594
|
/** Subscribe to an event. Returns a Disposable that removes the listener when disposed. */
|
|
512
595
|
on<K extends keyof T>(event: K, listener: (data: T[K]) => void): Disposable;
|
|
513
|
-
/**
|
|
596
|
+
/**
|
|
597
|
+
* Emit an event to all listeners. Snapshots the set to prevent live-iteration issues.
|
|
598
|
+
* A throwing listener does not stop the later ones: its error is rethrown from a microtask, as the scheduler does.
|
|
599
|
+
*/
|
|
514
600
|
emit<K extends keyof T>(event: K, data: T[K]): void;
|
|
515
601
|
/** Check if any listeners exist for an event */
|
|
516
602
|
has<K extends keyof T>(event: K): boolean;
|
|
@@ -522,7 +608,8 @@ export declare class TypedEmitter<T extends EventMap> implements Disposable {
|
|
|
522
608
|
}
|
|
523
609
|
/**
|
|
524
610
|
* Batched update scheduler that coalesces state updates into a single tick.
|
|
525
|
-
* Uses requestAnimationFrame in browsers, queueMicrotask
|
|
611
|
+
* Uses requestAnimationFrame in browsers, queueMicrotask where requestAnimationFrame is absent (Node.js, SSR).
|
|
612
|
+
* flush() synchronously applies the updates pending when it is called, in the order scheduled; one scheduled during the flush runs on the next frame or microtask.
|
|
526
613
|
*/
|
|
527
614
|
interface Scheduler extends Disposable {
|
|
528
615
|
schedule(update: () => void): void;
|
|
@@ -531,7 +618,7 @@ interface Scheduler extends Disposable {
|
|
|
531
618
|
}
|
|
532
619
|
/**
|
|
533
620
|
* Creates a batched update scheduler. Coalesces rapid state updates into a single
|
|
534
|
-
* execution pass using rAF (browser) or queueMicrotask (Node.js).
|
|
621
|
+
* execution pass using rAF (browser) or queueMicrotask (where rAF is absent: Node.js, SSR).
|
|
535
622
|
*/
|
|
536
623
|
export declare function createScheduler(): Scheduler;
|
|
537
624
|
export type { CommandEvent, CommandGroup, CommandItem, CommandMachine, CommandMachineOptions, CommandState, CommandTelemetryHooks, FrecencyData, FrecencyDecayConfig, FrecencyOptions, FrecencyRecord, FrecencyStorage, GroupId, ItemId, ParsedShortcut, Scheduler, ScorerFn, SearchEngine, SearchResult, TelemetryMiddleware };
|