modern-cmdk 1.1.0 → 1.1.3
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 +3 -3
- package/dist/codemod/cli.mjs +21 -4
- package/dist/core/index.d.mts +28 -27
- package/dist/core/index.mjs +144 -63
- package/dist/react/index.d.mts +22 -13
- package/dist/react/index.mjs +191 -78
- package/dist/react/styles.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -99,7 +99,7 @@ A ground-up rewrite of `cmdk` for **React 19**, **ES2026**, and **TypeScript 6**
|
|
|
99
99
|
| **Architecture** | React-only, tightly coupled | Framework-agnostic core + thin adapters |
|
|
100
100
|
| **React** | 18 | 19 (`use()`, `useOptimistic`, `useId`, `ref` as prop) |
|
|
101
101
|
| **Search** | Basic substring | Fuzzy scoring + optional WASM (sub-1ms on 100K items) |
|
|
102
|
-
| **Ranking** | Static order | Frecency with
|
|
102
|
+
| **Ranking** | Static order | Frecency with time-based exponential decay |
|
|
103
103
|
| **Virtualization** | None | Automatic variable-height, `content-visibility: auto` |
|
|
104
104
|
| **Animations** | CSS transitions | GPU-composited: `@starting-style`, `scroll-timeline`, spring easing |
|
|
105
105
|
| **Keyboard** | External | Built-in registry, `Mod` key, conflict detection |
|
|
@@ -118,8 +118,8 @@ A ground-up rewrite of `cmdk` for **React 19**, **ES2026**, and **TypeScript 6**
|
|
|
118
118
|
- **React 19 adapter** -- 14 compound components. `useSyncExternalStore`, `useTransition`, `useOptimistic`, `use()` for Suspense. React Compiler compatible.
|
|
119
119
|
- **Automatic virtualization** -- Variable-height virtual scrolling kicks in at 100+ items. Handles 100K+ items smoothly with `content-visibility: auto`.
|
|
120
120
|
- **Fuzzy search** -- Built-in TypeScript scorer with incremental filtering. Optional [WASM engine](https://www.npmjs.com/package/modern-cmdk-search-wasm) for sub-1ms on 100K items.
|
|
121
|
-
- **Frecency ranking** -- Frequency x recency with
|
|
122
|
-
- **Keyboard shortcuts** -- Built-in registry with cross-platform `Mod` key, conflict detection, and `RegExp.escape` parsing.
|
|
121
|
+
- **Frecency ranking** -- Frequency x recency with time-based exponential decay. Pluggable persistence (memory or IndexedDB).
|
|
122
|
+
- **Keyboard shortcuts** -- Built-in registry with cross-platform `Mod` key, conflict detection, and `RegExp.escape` parsing. Cross-browser helper functions for grouping and set operations.
|
|
123
123
|
- **Full accessibility** -- WAI-ARIA combobox pattern, `aria-live` announcements, `forced-colors`, `prefers-contrast`, `prefers-reduced-motion`.
|
|
124
124
|
- **GPU-composited animations** -- `@starting-style` entry, `scroll-timeline` progress, spring `linear()` easing. All customizable via CSS custom properties.
|
|
125
125
|
- **Error boundary** -- `CommandErrorBoundary` with static or render-function fallback.
|
package/dist/codemod/cli.mjs
CHANGED
|
@@ -89714,7 +89714,7 @@ var require_core = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
89714
89714
|
module.exports = enrichCore(core, getParser());
|
|
89715
89715
|
}));
|
|
89716
89716
|
//#endregion
|
|
89717
|
-
//#region src/
|
|
89717
|
+
//#region src/core/utils/group-by.ts
|
|
89718
89718
|
var import_jscodeshift = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
89719
89719
|
/**
|
|
89720
89720
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
@@ -89724,6 +89724,21 @@ var import_jscodeshift = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(
|
|
|
89724
89724
|
*/
|
|
89725
89725
|
module.exports = require_core();
|
|
89726
89726
|
})))(), 1);
|
|
89727
|
+
/**
|
|
89728
|
+
* Groups items from an iterable into a plain object keyed by the string result of `keyFn`.
|
|
89729
|
+
* Cross-browser replacement for `Object.groupBy()`.
|
|
89730
|
+
*/
|
|
89731
|
+
function objectGroupBy(items, keyFn) {
|
|
89732
|
+
const result = Object.create(null);
|
|
89733
|
+
for (const item of items) {
|
|
89734
|
+
const key = keyFn(item);
|
|
89735
|
+
if (key in result) result[key].push(item);
|
|
89736
|
+
else result[key] = [item];
|
|
89737
|
+
}
|
|
89738
|
+
return result;
|
|
89739
|
+
}
|
|
89740
|
+
//#endregion
|
|
89741
|
+
//#region src/codemod/cli.ts
|
|
89727
89742
|
const AVAILABLE_TRANSFORMS = new Map([
|
|
89728
89743
|
["import-rewrite", "Rewrites cmdk imports to modern-cmdk/react"],
|
|
89729
89744
|
["data-attrs", "Converts [cmdk-*] selectors to [data-command-*]"],
|
|
@@ -89802,7 +89817,10 @@ async function main() {
|
|
|
89802
89817
|
stats: () => {},
|
|
89803
89818
|
report: () => {}
|
|
89804
89819
|
});
|
|
89805
|
-
|
|
89820
|
+
let resolveDone;
|
|
89821
|
+
const done = new Promise((r) => {
|
|
89822
|
+
resolveDone = r;
|
|
89823
|
+
});
|
|
89806
89824
|
const allResults = [];
|
|
89807
89825
|
for await (const batch of chunked(files, concurrency)) (await Promise.allSettled(batch.map((filePath) => processFile(filePath, transformFn, apiForParser, dryRun)))).values().forEach((result) => {
|
|
89808
89826
|
if (result.status === "fulfilled") allResults.push(result.value);
|
|
@@ -89813,8 +89831,7 @@ async function main() {
|
|
|
89813
89831
|
});
|
|
89814
89832
|
});
|
|
89815
89833
|
resolveDone(allResults);
|
|
89816
|
-
const
|
|
89817
|
-
const grouped = Object.groupBy(results, (r) => {
|
|
89834
|
+
const grouped = objectGroupBy(await done, (r) => {
|
|
89818
89835
|
if (r.error) return "errors";
|
|
89819
89836
|
if (r.changed) return "changed";
|
|
89820
89837
|
return "unchanged";
|
package/dist/core/index.d.mts
CHANGED
|
@@ -45,9 +45,9 @@ declare const __groupIdBrand: unique symbol;
|
|
|
45
45
|
type GroupId = string & {
|
|
46
46
|
readonly [__groupIdBrand]: never;
|
|
47
47
|
};
|
|
48
|
-
/** Create a branded {@link ItemId} from a plain string. Ensures well-formed Unicode
|
|
48
|
+
/** Create a branded {@link ItemId} from a plain string. Ensures well-formed Unicode. */
|
|
49
49
|
declare function itemId(id: string): ItemId;
|
|
50
|
-
/** Create a branded {@link GroupId} from a plain string. Ensures well-formed Unicode
|
|
50
|
+
/** Create a branded {@link GroupId} from a plain string. Ensures well-formed Unicode. */
|
|
51
51
|
declare function groupId(id: string): GroupId;
|
|
52
52
|
/**
|
|
53
53
|
* A registered command item in the palette.
|
|
@@ -107,8 +107,8 @@ interface CommandState {
|
|
|
107
107
|
readonly pageStack: readonly string[];
|
|
108
108
|
/** Whether the command palette dialog is open */
|
|
109
109
|
readonly open: boolean;
|
|
110
|
-
/**
|
|
111
|
-
readonly lastUpdated:
|
|
110
|
+
/** Epoch-millisecond timestamp of the last state mutation */
|
|
111
|
+
readonly lastUpdated: number;
|
|
112
112
|
}
|
|
113
113
|
/** All possible events the state machine accepts */
|
|
114
114
|
type CommandEvent = {
|
|
@@ -153,7 +153,8 @@ type CommandEvent = {
|
|
|
153
153
|
/** Frecency record for a single item */
|
|
154
154
|
interface FrecencyRecord {
|
|
155
155
|
readonly frequency: number;
|
|
156
|
-
|
|
156
|
+
/** Epoch-millisecond timestamp of last usage */
|
|
157
|
+
readonly lastUsed: number;
|
|
157
158
|
}
|
|
158
159
|
/** Complete frecency data for persistence */
|
|
159
160
|
interface FrecencyData {
|
|
@@ -255,14 +256,14 @@ declare class IdbFrecencyStorage implements FrecencyStorage, AsyncDisposable {
|
|
|
255
256
|
//#region src/core/frecency/index.d.ts
|
|
256
257
|
/**
|
|
257
258
|
* Computes a frecency bonus for an item based on usage frequency and recency.
|
|
258
|
-
* Uses
|
|
259
|
+
* Uses hour/day/week/month bucket boundaries with
|
|
259
260
|
* configurable exponential decay weights per time bucket.
|
|
260
261
|
*/
|
|
261
|
-
declare function computeFrecencyBonus(history: FrecencyRecord, now?:
|
|
262
|
+
declare function computeFrecencyBonus(history: FrecencyRecord, now?: number, config?: FrecencyDecayConfig): number;
|
|
262
263
|
/**
|
|
263
264
|
* Stateful frecency engine that tracks item selection history and provides
|
|
264
265
|
* ranking boosts for search results. Supports pluggable storage backends
|
|
265
|
-
* and automatic flush on dispose. Uses
|
|
266
|
+
* and automatic flush on dispose. Uses Date.now() for timestamps.
|
|
266
267
|
*/
|
|
267
268
|
declare class FrecencyEngine implements Disposable, AsyncDisposable {
|
|
268
269
|
#private;
|
|
@@ -272,9 +273,9 @@ declare class FrecencyEngine implements Disposable, AsyncDisposable {
|
|
|
272
273
|
/** Record a selection event for an item */
|
|
273
274
|
recordSelection(itemId: ItemId): void;
|
|
274
275
|
/** Get frecency bonus for an item */
|
|
275
|
-
getBonus(itemId: ItemId, now?:
|
|
276
|
+
getBonus(itemId: ItemId, now?: number): number;
|
|
276
277
|
/** Get all frecency bonuses as a map — for...of for hot ranking path (no closure overhead) */
|
|
277
|
-
getAllBonuses(now?:
|
|
278
|
+
getAllBonuses(now?: number): ReadonlyMap<ItemId, number>;
|
|
278
279
|
/** Get the decay configuration */
|
|
279
280
|
getConfig(): FrecencyDecayConfig;
|
|
280
281
|
/** Update decay configuration at runtime */
|
|
@@ -321,7 +322,7 @@ interface ParsedShortcut {
|
|
|
321
322
|
}
|
|
322
323
|
/**
|
|
323
324
|
* Parse a shortcut string like "Mod+Shift+K" into a structured ParsedShortcut.
|
|
324
|
-
*
|
|
325
|
+
* Ensures valid Unicode input via ensureWellFormed helper.
|
|
325
326
|
*/
|
|
326
327
|
declare function parseShortcut(shortcut: string): ParsedShortcut;
|
|
327
328
|
/**
|
|
@@ -379,7 +380,7 @@ declare class KeyboardShortcutRegistry implements Disposable {
|
|
|
379
380
|
//#region src/core/registry.d.ts
|
|
380
381
|
/**
|
|
381
382
|
* Manages registration and lookup of command items and groups.
|
|
382
|
-
* Uses
|
|
383
|
+
* Uses set operation helpers (union/intersection/difference) for efficient ID operations.
|
|
383
384
|
* All registration methods return Disposable for automatic cleanup via `using`.
|
|
384
385
|
*/
|
|
385
386
|
declare class CommandRegistry implements Disposable {
|
|
@@ -390,7 +391,7 @@ declare class CommandRegistry implements Disposable {
|
|
|
390
391
|
registerItems(items: readonly CommandItem[]): Disposable;
|
|
391
392
|
/** Unregister a single item — O(1) Map/Set delete, indexOf+toSpliced for order */
|
|
392
393
|
unregisterItem(id: ItemId): void;
|
|
393
|
-
/** Bulk unregister
|
|
394
|
+
/** Bulk unregister — for...of for hot path */
|
|
394
395
|
unregisterItems(ids: ReadonlySet<ItemId>): void;
|
|
395
396
|
/** Register a group */
|
|
396
397
|
registerGroup(group: CommandGroup): Disposable;
|
|
@@ -404,23 +405,23 @@ declare class CommandRegistry implements Disposable {
|
|
|
404
405
|
getItems(): readonly CommandItem[];
|
|
405
406
|
/** Get all registered groups sorted by priority — cached between group mutations */
|
|
406
407
|
getGroups(): readonly CommandGroup[];
|
|
407
|
-
/** Get items grouped by groupId
|
|
408
|
+
/** Get items grouped by groupId — direct Map, no Object.entries conversion */
|
|
408
409
|
getGroupedItems(): ReadonlyMap<GroupId, readonly CommandItem[]>;
|
|
409
410
|
/** Get item IDs as a Set — returns internal set (O(1), no allocation) */
|
|
410
411
|
getItemIds(): ReadonlySet<ItemId>;
|
|
411
|
-
/** Find intersection of registered items with a candidate set
|
|
412
|
+
/** Find intersection of registered items with a candidate set */
|
|
412
413
|
intersectWith(candidateIds: ReadonlySet<ItemId>): ReadonlySet<ItemId>;
|
|
413
|
-
/** Find items registered but not in a given set
|
|
414
|
+
/** Find items registered but not in a given set */
|
|
414
415
|
differenceFrom(otherIds: ReadonlySet<ItemId>): ReadonlySet<ItemId>;
|
|
415
|
-
/** Union of registered IDs with another set
|
|
416
|
+
/** Union of registered IDs with another set */
|
|
416
417
|
unionWith(otherIds: ReadonlySet<ItemId>): ReadonlySet<ItemId>;
|
|
417
|
-
/** Check if registered items are a subset of the given set
|
|
418
|
+
/** Check if registered items are a subset of the given set */
|
|
418
419
|
isSubsetOf(otherIds: ReadonlySet<ItemId>): boolean;
|
|
419
|
-
/** Check if registered items are a superset of the given set
|
|
420
|
+
/** Check if registered items are a superset of the given set */
|
|
420
421
|
isSupersetOf(otherIds: ReadonlySet<ItemId>): boolean;
|
|
421
|
-
/** Check if registered items share no IDs with the given set
|
|
422
|
+
/** Check if registered items share no IDs with the given set */
|
|
422
423
|
isDisjointFrom(otherIds: ReadonlySet<ItemId>): boolean;
|
|
423
|
-
/** Symmetric difference between registered IDs and another set
|
|
424
|
+
/** Symmetric difference between registered IDs and another set */
|
|
424
425
|
symmetricDifferenceWith(otherIds: ReadonlySet<ItemId>): ReadonlySet<ItemId>;
|
|
425
426
|
/** Total number of registered items */
|
|
426
427
|
get size(): number;
|
|
@@ -471,12 +472,12 @@ declare function scoreItem(query: string, item: CommandItem): SearchResult | nul
|
|
|
471
472
|
//#endregion
|
|
472
473
|
//#region src/core/search/fuzzy-scorer.d.ts
|
|
473
474
|
/**
|
|
474
|
-
* Async-safe fuzzy scorer — wraps scoring in Promise
|
|
475
|
-
* Falls back gracefully on error via
|
|
475
|
+
* Async-safe fuzzy scorer — wraps scoring in Promise for error resilience.
|
|
476
|
+
* Falls back gracefully on error via synchronous-throw capture.
|
|
476
477
|
*/
|
|
477
478
|
declare function scoreItemAsync(query: string, item: CommandItem): Promise<SearchResult | null>;
|
|
478
479
|
/**
|
|
479
|
-
* Batch score items synchronously inside a single Promise.
|
|
480
|
+
* Batch score items synchronously inside a single Promise.
|
|
480
481
|
* Eliminates per-item microtask overhead from async generators — runs the
|
|
481
482
|
* entire scoring pipeline in one synchronous pass, wrapped for error safety.
|
|
482
483
|
*/
|
|
@@ -508,13 +509,13 @@ interface CommandTelemetryHooks {
|
|
|
508
509
|
interface TelemetryMiddleware {
|
|
509
510
|
readonly onOpen: () => void;
|
|
510
511
|
readonly onClose: () => void;
|
|
511
|
-
readonly onSearch: (query: string, resultCount: number, startTime:
|
|
512
|
+
readonly onSearch: (query: string, resultCount: number, startTime: number) => void;
|
|
512
513
|
readonly onSelect: (itemId: ItemId, query: string, position: number) => void;
|
|
513
514
|
}
|
|
514
515
|
/**
|
|
515
516
|
* Creates a telemetry middleware that measures command palette interactions.
|
|
516
|
-
* Uses
|
|
517
|
-
* and
|
|
517
|
+
* Uses Date.now() for millisecond-precision timestamps
|
|
518
|
+
* and subtraction for duration computation.
|
|
518
519
|
*
|
|
519
520
|
* @example
|
|
520
521
|
* ```ts
|