modern-cmdk 1.0.0 → 1.0.2
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 +261 -0
- package/dist/core/index.d.mts +1 -1
- package/dist/core/index.mjs +1 -1
- package/dist/{core-Bq0eDvlc.mjs → core-CgQYpHK3.mjs} +35 -17
- package/dist/core-CgQYpHK3.mjs.map +1 -0
- package/dist/{index-Ctz7l29k.d.mts → index-C6g94P7V.d.mts} +6 -5
- package/dist/index-C6g94P7V.d.mts.map +1 -0
- package/dist/react/index.d.mts +22 -3
- package/dist/react/index.d.mts.map +1 -1
- package/dist/react/index.mjs +167 -135
- package/dist/react/index.mjs.map +1 -1
- package/package.json +4 -2
- package/src/react/styles.css +1 -3
- package/dist/core-Bq0eDvlc.mjs.map +0 -1
- package/dist/index-Ctz7l29k.d.mts.map +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/ABCrimson/modern-cmdk/main/apps/docs/public/og-image.svg" alt="modern-cmdk" width="640" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<strong>modern-cmdk</strong><br/>
|
|
7
|
+
The definitive command palette engine for the modern web.
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<a href="https://www.npmjs.com/package/modern-cmdk"><img alt="npm version" src="https://img.shields.io/npm/v/modern-cmdk?style=flat-square&color=dc2626&labelColor=0a0e27"/></a>
|
|
12
|
+
<a href="https://bundlephobia.com/package/modern-cmdk"><img alt="bundle size" src="https://img.shields.io/bundlephobia/minzip/modern-cmdk?style=flat-square&color=dc2626&labelColor=0a0e27&label=core"/></a>
|
|
13
|
+
<a href="https://github.com/ABCrimson/modern-cmdk/actions/workflows/ci.yml"><img alt="CI" src="https://img.shields.io/github/actions/workflow/status/ABCrimson/modern-cmdk/ci.yml?style=flat-square&color=dc2626&labelColor=0a0e27&label=CI"/></a>
|
|
14
|
+
<a href="https://www.typescriptlang.org/"><img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-6.0-dc2626?style=flat-square&labelColor=0a0e27"/></a>
|
|
15
|
+
<a href="https://react.dev/"><img alt="React" src="https://img.shields.io/badge/React-19-dc2626?style=flat-square&labelColor=0a0e27"/></a>
|
|
16
|
+
<a href="https://github.com/ABCrimson/modern-cmdk/blob/main/LICENSE"><img alt="MIT License" src="https://img.shields.io/npm/l/modern-cmdk?style=flat-square&color=dc2626&labelColor=0a0e27"/></a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<a href="https://command.crimson.dev">Documentation</a> ·
|
|
21
|
+
<a href="https://command.crimson.dev/guide/getting-started">Getting Started</a> ·
|
|
22
|
+
<a href="https://command.crimson.dev/api/command">API Reference</a> ·
|
|
23
|
+
<a href="https://command.crimson.dev/examples/basic">Examples</a>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install modern-cmdk
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The package exports three entry points:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { createCommandMachine } from 'modern-cmdk'; // Core engine (no React)
|
|
38
|
+
import { Command } from 'modern-cmdk/react'; // React 19 components
|
|
39
|
+
import 'modern-cmdk/styles.css'; // Default styles
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
React is an **optional** peer dependency — use the core engine standalone in any framework, or pair it with the React adapter for a batteries-included experience.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
'use client';
|
|
50
|
+
|
|
51
|
+
import { Command } from 'modern-cmdk/react';
|
|
52
|
+
import 'modern-cmdk/styles.css';
|
|
53
|
+
|
|
54
|
+
function CommandPalette() {
|
|
55
|
+
return (
|
|
56
|
+
<Command.Dialog>
|
|
57
|
+
<Command.Input placeholder="Type a command..." />
|
|
58
|
+
<Command.List>
|
|
59
|
+
<Command.Empty>No results found.</Command.Empty>
|
|
60
|
+
|
|
61
|
+
<Command.Group heading="Actions">
|
|
62
|
+
<Command.Item value="copy" onSelect={() => copyToClipboard()}>
|
|
63
|
+
<Command.Highlight>Copy to Clipboard</Command.Highlight>
|
|
64
|
+
<Command.Shortcut>Mod+C</Command.Shortcut>
|
|
65
|
+
</Command.Item>
|
|
66
|
+
<Command.Item value="paste" onSelect={() => paste()}>
|
|
67
|
+
<Command.Highlight>Paste</Command.Highlight>
|
|
68
|
+
<Command.Shortcut>Mod+V</Command.Shortcut>
|
|
69
|
+
</Command.Item>
|
|
70
|
+
</Command.Group>
|
|
71
|
+
|
|
72
|
+
<Command.Group heading="Navigation">
|
|
73
|
+
<Command.Item value="settings" onSelect={() => navigate('/settings')}>
|
|
74
|
+
Settings
|
|
75
|
+
<Command.Badge>New</Command.Badge>
|
|
76
|
+
</Command.Item>
|
|
77
|
+
</Command.Group>
|
|
78
|
+
</Command.List>
|
|
79
|
+
<Command.Loading>Searching...</Command.Loading>
|
|
80
|
+
</Command.Dialog>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Or scaffold a new project instantly:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm create modern-cmdk
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Why modern-cmdk?
|
|
94
|
+
|
|
95
|
+
A ground-up rewrite of `cmdk` for **React 19**, **ES2026**, and **TypeScript 6**. Framework-agnostic core. Zero compromise on performance, accessibility, or developer experience.
|
|
96
|
+
|
|
97
|
+
| | cmdk | modern-cmdk |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| **Architecture** | React-only, tightly coupled | Framework-agnostic core + thin adapters |
|
|
100
|
+
| **React** | 18 | 19 (`use()`, `useOptimistic`, `useId`, `ref` as prop) |
|
|
101
|
+
| **Search** | Basic substring | Fuzzy scoring + optional WASM (sub-1ms on 100K items) |
|
|
102
|
+
| **Ranking** | Static order | Frecency with `Temporal`-based exponential decay |
|
|
103
|
+
| **Virtualization** | None | Automatic variable-height, `content-visibility: auto` |
|
|
104
|
+
| **Animations** | CSS transitions | GPU-composited: `@starting-style`, `scroll-timeline`, spring easing |
|
|
105
|
+
| **Keyboard** | External | Built-in registry, `Mod` key, conflict detection |
|
|
106
|
+
| **Accessibility** | Partial ARIA | Full WAI-ARIA combobox, `forced-colors`, `prefers-contrast` |
|
|
107
|
+
| **Bundle** | ~6 KB | Core ~5.2 KB, React ~6.3 KB |
|
|
108
|
+
| **TypeScript** | 4.x/5.x | 6.0, isolated declarations, branded types |
|
|
109
|
+
| **Cleanup** | Manual | `using`/`await using` (Explicit Resource Management) |
|
|
110
|
+
| **Error handling** | None | `CommandErrorBoundary` with fallback UI |
|
|
111
|
+
| **DevTools** | None | Built-in devtools hook for browser inspection |
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Features
|
|
116
|
+
|
|
117
|
+
- **Framework-agnostic core** -- Pure TypeScript state machine. Zero DOM dependencies. Portable to any runtime.
|
|
118
|
+
- **React 19 adapter** -- 14 compound components. `useSyncExternalStore`, `useTransition`, `useOptimistic`, `use()` for Suspense. React Compiler compatible.
|
|
119
|
+
- **Automatic virtualization** -- Variable-height virtual scrolling kicks in at 100+ items. Handles 100K+ items smoothly with `content-visibility: auto`.
|
|
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 `Temporal`-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.
|
|
123
|
+
- **Full accessibility** -- WAI-ARIA combobox pattern, `aria-live` announcements, `forced-colors`, `prefers-contrast`, `prefers-reduced-motion`.
|
|
124
|
+
- **GPU-composited animations** -- `@starting-style` entry, `scroll-timeline` progress, spring `linear()` easing. All customizable via CSS custom properties.
|
|
125
|
+
- **Error boundary** -- `CommandErrorBoundary` with static or render-function fallback.
|
|
126
|
+
- **DevTools** -- `useCommandDevtools()` exposes machine state via `CustomEvent` for browser inspection.
|
|
127
|
+
- **Telemetry middleware** -- Pluggable hooks for palette open/close, search, and item selection analytics.
|
|
128
|
+
- **ESM-only** -- Zero CommonJS. Tree-shakeable. `sideEffects: false`. Isolated declarations.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## React Components
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
<Command> {/* Root -- state machine provider */}
|
|
136
|
+
<Command.Input /> {/* Search input */}
|
|
137
|
+
<Command.List> {/* Scrollable list with auto-virtualization */}
|
|
138
|
+
<Command.Empty /> {/* Shown when filteredCount === 0 */}
|
|
139
|
+
<Command.Loading /> {/* Shown when loading */}
|
|
140
|
+
<Command.Group> {/* Labeled group of items */}
|
|
141
|
+
<Command.Item> {/* Selectable item */}
|
|
142
|
+
<Command.Highlight /> {/* Fuzzy match highlighting */}
|
|
143
|
+
<Command.Badge /> {/* Status badge */}
|
|
144
|
+
<Command.Shortcut /> {/* Keyboard shortcut display */}
|
|
145
|
+
</Command.Item>
|
|
146
|
+
</Command.Group>
|
|
147
|
+
<Command.Separator />
|
|
148
|
+
</Command.List>
|
|
149
|
+
</Command>
|
|
150
|
+
|
|
151
|
+
<Command.Dialog /> {/* Radix Dialog + overlay + portal */}
|
|
152
|
+
<Command.Page /> {/* Nested page navigation */}
|
|
153
|
+
<Command.AsyncItems /> {/* Suspense-powered async loading */}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Core API (No React)
|
|
159
|
+
|
|
160
|
+
Use the core engine standalone in any framework or runtime:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { createCommandMachine, itemId } from 'modern-cmdk';
|
|
164
|
+
|
|
165
|
+
using machine = createCommandMachine({
|
|
166
|
+
items: [
|
|
167
|
+
{ id: itemId('copy'), value: 'Copy', shortcut: 'Mod+C', onSelect: () => copy() },
|
|
168
|
+
{ id: itemId('paste'), value: 'Paste', shortcut: 'Mod+V', onSelect: () => paste() },
|
|
169
|
+
],
|
|
170
|
+
frecency: { enabled: true },
|
|
171
|
+
loop: true,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
machine.send({ type: 'SEARCH_CHANGE', query: 'cop' });
|
|
175
|
+
machine.send({ type: 'NAVIGATE', direction: 'next' });
|
|
176
|
+
machine.send({ type: 'ITEM_SELECT', id: itemId('copy') });
|
|
177
|
+
|
|
178
|
+
machine.subscribe(() => {
|
|
179
|
+
console.log(machine.getState());
|
|
180
|
+
});
|
|
181
|
+
// Automatic cleanup via `using` -- no manual dispose needed
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Performance
|
|
187
|
+
|
|
188
|
+
| Benchmark | Target | Measured |
|
|
189
|
+
|---|---|---|
|
|
190
|
+
| Search 10K items (TS scorer) | < 16 ms | ~8.2 ms |
|
|
191
|
+
| Search 100K items (WASM) | < 1 ms | ~0.7 ms |
|
|
192
|
+
| Filter 10K (incremental) | < 2 ms | ~1.1 ms |
|
|
193
|
+
| State update cycle | < 4 ms | ~2.3 ms |
|
|
194
|
+
| Core bundle (gzipped) | <= 6 KB | ~5.2 KB |
|
|
195
|
+
| React bundle (gzipped) | <= 25 KB | ~6.3 KB |
|
|
196
|
+
|
|
197
|
+
CI enforces 5% warning / 15% failure regression thresholds with 3-run averaging.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## CSS Customization
|
|
202
|
+
|
|
203
|
+
All animations and dimensions are customizable via CSS custom properties:
|
|
204
|
+
|
|
205
|
+
```css
|
|
206
|
+
[data-command-root] {
|
|
207
|
+
--command-duration-enter: 200ms;
|
|
208
|
+
--command-duration-exit: 150ms;
|
|
209
|
+
--command-scale-from: 0.96;
|
|
210
|
+
--command-overlay-blur: 4px;
|
|
211
|
+
--command-dialog-radius: 12px;
|
|
212
|
+
--command-dialog-max-width: 640px;
|
|
213
|
+
--command-item-height: 44px;
|
|
214
|
+
--command-highlight-color: oklch(0.85 0.15 90 / 0.3);
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
All styles use `@layer command` for zero-specificity conflicts and CSS logical properties for full RTL support.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Migration from cmdk
|
|
223
|
+
|
|
224
|
+
Automated codemods handle the most common migration patterns:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
npx modern-cmdk --transform import-rewrite ./src
|
|
228
|
+
npx modern-cmdk --transform data-attrs ./src
|
|
229
|
+
npx modern-cmdk --transform forward-ref ./src
|
|
230
|
+
npx modern-cmdk --transform should-filter ./src
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
See the [full migration guide](https://command.crimson.dev/guide/migration-from-cmdk).
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Documentation
|
|
238
|
+
|
|
239
|
+
**[command.crimson.dev](https://command.crimson.dev)**
|
|
240
|
+
|
|
241
|
+
**Guides:** [Getting Started](https://command.crimson.dev/guide/getting-started) | [Installation](https://command.crimson.dev/guide/installation) | [Basic Usage](https://command.crimson.dev/guide/basic-usage) | [Async Items](https://command.crimson.dev/guide/async-items) | [WASM Search](https://command.crimson.dev/guide/wasm-search) | [Frecency](https://command.crimson.dev/guide/frecency) | [Shortcuts](https://command.crimson.dev/guide/shortcuts) | [Virtualization](https://command.crimson.dev/guide/virtualization) | [SSR / Next.js](https://command.crimson.dev/guide/ssr) | [Theming](https://command.crimson.dev/guide/theming) | [Accessibility](https://command.crimson.dev/guide/accessibility)
|
|
242
|
+
|
|
243
|
+
**Recipes:** [File Picker](https://command.crimson.dev/recipes/file-picker) | [Emoji Picker](https://command.crimson.dev/recipes/emoji-picker) | [AI Chat Commands](https://command.crimson.dev/recipes/ai-chat-commands) | [Nested Commands](https://command.crimson.dev/recipes/nested-commands) | [Spotlight Search](https://command.crimson.dev/recipes/spotlight-search)
|
|
244
|
+
|
|
245
|
+
**API:** [Core Engine](https://command.crimson.dev/api/command) | [React Adapter](https://command.crimson.dev/api/command-react) | [WASM Search](https://command.crimson.dev/api/command-search-wasm)
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Related Packages
|
|
250
|
+
|
|
251
|
+
| Package | Description |
|
|
252
|
+
|---|---|
|
|
253
|
+
| [`create-modern-cmdk`](https://www.npmjs.com/package/create-modern-cmdk) | `npm create modern-cmdk` -- scaffold a new project |
|
|
254
|
+
| [`modern-cmdk-search-wasm`](https://www.npmjs.com/package/modern-cmdk-search-wasm) | Rust/WASM fuzzy search -- sub-1ms on 100K items |
|
|
255
|
+
| [`vscode-modern-cmdk`](https://marketplace.visualstudio.com/items?itemName=crimson-dev.vscode-modern-cmdk) | VS Code snippets extension |
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## License
|
|
260
|
+
|
|
261
|
+
[MIT](https://github.com/ABCrimson/modern-cmdk/blob/main/LICENSE) -- Copyright (c) 2026 Crimson Dev
|
package/dist/core/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as DEFAULT_FRECENCY_DECAY, B as groupId, C as computeFrecencyBonus, D as CommandItem, E as CommandGroup, F as FrecencyRecord, H as ScorerFn, I as FrecencyStorage, L as GroupId, M as FrecencyData, N as FrecencyDecayConfig, O as CommandMachineOptions, P as FrecencyOptions, R as ItemId, S as FrecencyEngine, T as CommandEvent, U as SearchEngine, V as itemId, W as SearchResult, _ as matchesShortcut, a as TelemetryMiddleware, b as parseShortcut, c as batchScoreItems, d as CommandMachine, f as createCommandMachine, g as findMatchingShortcut, h as detectConflicts, i as CommandTelemetryHooks, j as DEFAULT_MACHINE_OPTIONS, k as CommandState, l as scoreItemAsync, m as KeyboardShortcutRegistry, n as createScheduler, o as createTelemetryMiddleware, p as CommandRegistry, r as TypedEmitter, s as createSearchEngine, t as Scheduler, u as scoreItem, v as ParsedShortcut, w as IdbFrecencyStorage, x as MemoryFrecencyStorage, y as formatShortcut, z as createInitialState } from "../index-
|
|
1
|
+
import { A as DEFAULT_FRECENCY_DECAY, B as groupId, C as computeFrecencyBonus, D as CommandItem, E as CommandGroup, F as FrecencyRecord, H as ScorerFn, I as FrecencyStorage, L as GroupId, M as FrecencyData, N as FrecencyDecayConfig, O as CommandMachineOptions, P as FrecencyOptions, R as ItemId, S as FrecencyEngine, T as CommandEvent, U as SearchEngine, V as itemId, W as SearchResult, _ as matchesShortcut, a as TelemetryMiddleware, b as parseShortcut, c as batchScoreItems, d as CommandMachine, f as createCommandMachine, g as findMatchingShortcut, h as detectConflicts, i as CommandTelemetryHooks, j as DEFAULT_MACHINE_OPTIONS, k as CommandState, l as scoreItemAsync, m as KeyboardShortcutRegistry, n as createScheduler, o as createTelemetryMiddleware, p as CommandRegistry, r as TypedEmitter, s as createSearchEngine, t as Scheduler, u as scoreItem, v as ParsedShortcut, w as IdbFrecencyStorage, x as MemoryFrecencyStorage, y as formatShortcut, z as createInitialState } from "../index-C6g94P7V.mjs";
|
|
2
2
|
export { CommandEvent, CommandGroup, CommandItem, CommandMachine, CommandMachineOptions, CommandRegistry, CommandState, CommandTelemetryHooks, DEFAULT_FRECENCY_DECAY, DEFAULT_MACHINE_OPTIONS, FrecencyData, FrecencyDecayConfig, FrecencyEngine, FrecencyOptions, FrecencyRecord, FrecencyStorage, GroupId, IdbFrecencyStorage, ItemId, KeyboardShortcutRegistry, MemoryFrecencyStorage, ParsedShortcut, Scheduler, ScorerFn, SearchEngine, SearchResult, TelemetryMiddleware, TypedEmitter, batchScoreItems, computeFrecencyBonus, createCommandMachine, createInitialState, createScheduler, createSearchEngine, createTelemetryMiddleware, detectConflicts, findMatchingShortcut, formatShortcut, groupId, itemId, matchesShortcut, parseShortcut, scoreItem, scoreItemAsync };
|
package/dist/core/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as itemId, S as groupId, _ as computeFrecencyBonus, a as createScheduler, b as DEFAULT_MACHINE_OPTIONS, c as scoreItem, d as formatShortcut, f as parseShortcut, g as FrecencyEngine, h as matchesShortcut, i as createCommandMachine, l as CommandRegistry, m as findMatchingShortcut, n as batchScoreItems, o as TypedEmitter, p as detectConflicts, r as scoreItemAsync, s as createSearchEngine, t as createTelemetryMiddleware, u as KeyboardShortcutRegistry, v as MemoryFrecencyStorage, w as IdbFrecencyStorage, x as createInitialState, y as DEFAULT_FRECENCY_DECAY } from "../core-
|
|
1
|
+
import { C as itemId, S as groupId, _ as computeFrecencyBonus, a as createScheduler, b as DEFAULT_MACHINE_OPTIONS, c as scoreItem, d as formatShortcut, f as parseShortcut, g as FrecencyEngine, h as matchesShortcut, i as createCommandMachine, l as CommandRegistry, m as findMatchingShortcut, n as batchScoreItems, o as TypedEmitter, p as detectConflicts, r as scoreItemAsync, s as createSearchEngine, t as createTelemetryMiddleware, u as KeyboardShortcutRegistry, v as MemoryFrecencyStorage, w as IdbFrecencyStorage, x as createInitialState, y as DEFAULT_FRECENCY_DECAY } from "../core-CgQYpHK3.mjs";
|
|
2
2
|
export { CommandRegistry, DEFAULT_FRECENCY_DECAY, DEFAULT_MACHINE_OPTIONS, FrecencyEngine, IdbFrecencyStorage, KeyboardShortcutRegistry, MemoryFrecencyStorage, TypedEmitter, batchScoreItems, computeFrecencyBonus, createCommandMachine, createInitialState, createScheduler, createSearchEngine, createTelemetryMiddleware, detectConflicts, findMatchingShortcut, formatShortcut, groupId, itemId, matchesShortcut, parseShortcut, scoreItem, scoreItemAsync };
|
|
@@ -45,10 +45,10 @@ var IdbFrecencyStorage = class {
|
|
|
45
45
|
/**
|
|
46
46
|
* Create an IDB-backed frecency storage.
|
|
47
47
|
*
|
|
48
|
-
* @param dbName — IndexedDB database name (default: '
|
|
48
|
+
* @param dbName — IndexedDB database name (default: 'modern-cmdk')
|
|
49
49
|
* @param storeName — IndexedDB object store name (default: 'frecency')
|
|
50
50
|
*/
|
|
51
|
-
constructor(dbName = "
|
|
51
|
+
constructor(dbName = "modern-cmdk", storeName = "frecency") {
|
|
52
52
|
this.#store = createStore(dbName, storeName);
|
|
53
53
|
}
|
|
54
54
|
/** Load frecency data from IndexedDB for the given namespace */
|
|
@@ -141,6 +141,8 @@ var MemoryFrecencyStorage = class {
|
|
|
141
141
|
};
|
|
142
142
|
//#endregion
|
|
143
143
|
//#region src/core/frecency/index.ts
|
|
144
|
+
/** Shared empty bonus map — avoids allocation when no frecency records exist */
|
|
145
|
+
const EMPTY_BONUS_MAP = /* @__PURE__ */ new Map();
|
|
144
146
|
/** Pre-computed bucket boundaries in hours — avoids Duration.total() per call */
|
|
145
147
|
const BUCKET_HOURS = {
|
|
146
148
|
hour: 1,
|
|
@@ -187,9 +189,7 @@ var FrecencyEngine = class {
|
|
|
187
189
|
frequency: (this.#data.records.get(itemId)?.frequency ?? 0) + 1,
|
|
188
190
|
lastUsed: now
|
|
189
191
|
};
|
|
190
|
-
|
|
191
|
-
newRecords.set(itemId, updated);
|
|
192
|
-
this.#data = { records: newRecords };
|
|
192
|
+
this.#data.records.set(itemId, updated);
|
|
193
193
|
this.#dirty = true;
|
|
194
194
|
}
|
|
195
195
|
/** Get frecency bonus for an item */
|
|
@@ -200,6 +200,7 @@ var FrecencyEngine = class {
|
|
|
200
200
|
}
|
|
201
201
|
/** Get all frecency bonuses as a map — for...of for hot ranking path (no closure overhead) */
|
|
202
202
|
getAllBonuses(now = Temporal.Now.instant()) {
|
|
203
|
+
if (this.#data.records.size === 0) return EMPTY_BONUS_MAP;
|
|
203
204
|
const bonuses = /* @__PURE__ */ new Map();
|
|
204
205
|
for (const [id, record] of this.#data.records) bonuses.set(id, computeFrecencyBonus(record, now, this.#config));
|
|
205
206
|
return bonuses;
|
|
@@ -277,7 +278,6 @@ const MODIFIER_MAP = {
|
|
|
277
278
|
};
|
|
278
279
|
/**
|
|
279
280
|
* Parse a shortcut string like "Mod+Shift+K" into a structured ParsedShortcut.
|
|
280
|
-
* Uses RegExp.escape (ES2026) for safe pattern construction from user-provided strings.
|
|
281
281
|
* Uses String.isWellFormed() (ES2026) to ensure valid Unicode input.
|
|
282
282
|
*/
|
|
283
283
|
function parseShortcut(shortcut) {
|
|
@@ -368,11 +368,9 @@ var KeyboardShortcutRegistry = class {
|
|
|
368
368
|
const shortcut = parseShortcut(shortcutStr);
|
|
369
369
|
this.#bindings.delete(shortcut.normalized);
|
|
370
370
|
}
|
|
371
|
-
/** Unregister all shortcuts for a given item —
|
|
371
|
+
/** Unregister all shortcuts for a given item — Map spec allows deletion during iteration */
|
|
372
372
|
unregisterByItem(itemId) {
|
|
373
|
-
const
|
|
374
|
-
for (const [key, binding] of this.#bindings) if (binding.itemId === itemId) keysToDelete.push(key);
|
|
375
|
-
for (const key of keysToDelete) this.#bindings.delete(key);
|
|
373
|
+
for (const [key, binding] of this.#bindings) if (binding.itemId === itemId) this.#bindings.delete(key);
|
|
376
374
|
}
|
|
377
375
|
/** Check for conflicting shortcuts */
|
|
378
376
|
getConflicts() {
|
|
@@ -423,9 +421,13 @@ var CommandRegistry = class {
|
|
|
423
421
|
#itemOrder = [];
|
|
424
422
|
#itemOrderSet = /* @__PURE__ */ new Set();
|
|
425
423
|
#cachedItems = null;
|
|
424
|
+
#cachedGroups = null;
|
|
426
425
|
#invalidateCache() {
|
|
427
426
|
this.#cachedItems = null;
|
|
428
427
|
}
|
|
428
|
+
#invalidateGroupCache() {
|
|
429
|
+
this.#cachedGroups = null;
|
|
430
|
+
}
|
|
429
431
|
/** Register an item — returns Disposable for `using` auto-deregister */
|
|
430
432
|
registerItem(item) {
|
|
431
433
|
this.#items.set(item.id, item);
|
|
@@ -471,13 +473,16 @@ var CommandRegistry = class {
|
|
|
471
473
|
/** Register a group */
|
|
472
474
|
registerGroup(group) {
|
|
473
475
|
this.#groups.set(group.id, group);
|
|
476
|
+
this.#invalidateGroupCache();
|
|
474
477
|
return { [Symbol.dispose]: () => {
|
|
475
478
|
this.#groups.delete(group.id);
|
|
479
|
+
this.#invalidateGroupCache();
|
|
476
480
|
} };
|
|
477
481
|
}
|
|
478
482
|
/** Unregister a group */
|
|
479
483
|
unregisterGroup(id) {
|
|
480
484
|
this.#groups.delete(id);
|
|
485
|
+
this.#invalidateGroupCache();
|
|
481
486
|
}
|
|
482
487
|
/** Get an item by ID — O(1) */
|
|
483
488
|
getItem(id) {
|
|
@@ -493,9 +498,11 @@ var CommandRegistry = class {
|
|
|
493
498
|
this.#cachedItems = this.#itemOrder.values().map((id) => this.#items.get(id)).filter((item) => item != null).toArray();
|
|
494
499
|
return this.#cachedItems;
|
|
495
500
|
}
|
|
496
|
-
/** Get all registered groups sorted by priority */
|
|
501
|
+
/** Get all registered groups sorted by priority — cached between group mutations */
|
|
497
502
|
getGroups() {
|
|
498
|
-
|
|
503
|
+
if (this.#cachedGroups !== null) return this.#cachedGroups;
|
|
504
|
+
this.#cachedGroups = this.#groups.values().toArray().sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
|
|
505
|
+
return this.#cachedGroups;
|
|
499
506
|
}
|
|
500
507
|
/** Get items grouped by groupId using Map.groupBy (ES2026) — direct Map, no Object.entries conversion */
|
|
501
508
|
getGroupedItems() {
|
|
@@ -557,6 +564,7 @@ var CommandRegistry = class {
|
|
|
557
564
|
this.#itemOrder = [];
|
|
558
565
|
this.#itemOrderSet.clear();
|
|
559
566
|
this.#invalidateCache();
|
|
567
|
+
this.#invalidateGroupCache();
|
|
560
568
|
}
|
|
561
569
|
[Symbol.dispose]() {
|
|
562
570
|
this.clear();
|
|
@@ -763,7 +771,7 @@ function createSearchEngine(options) {
|
|
|
763
771
|
}));
|
|
764
772
|
}
|
|
765
773
|
const lowerQuery = query.toLowerCase();
|
|
766
|
-
const candidateItems = query.startsWith(previousQuery) && previousQuery.length > 0 ? items.filter((item) => previousResults.has(item.id)) : items;
|
|
774
|
+
const candidateItems = query.startsWith(previousQuery) && previousQuery.length > 0 ? items.values().filter((item) => previousResults.has(item.id)) : items;
|
|
767
775
|
const results = [];
|
|
768
776
|
if (useDefaultScorer) for (const item of candidateItems) {
|
|
769
777
|
const entry = indexedEntries.get(item.id);
|
|
@@ -908,6 +916,7 @@ function createScheduler() {
|
|
|
908
916
|
function createCommandMachine(options = {}) {
|
|
909
917
|
let state = createInitialState(options);
|
|
910
918
|
let filteredIdSet = /* @__PURE__ */ new Set();
|
|
919
|
+
let filteredIdIndex = /* @__PURE__ */ new Map();
|
|
911
920
|
const registry = new CommandRegistry();
|
|
912
921
|
const searchEngine = options.search ?? createSearchEngine(typeof options.filter === "function" ? { scorer: (query, item) => {
|
|
913
922
|
const score = options.filter(item, query);
|
|
@@ -953,7 +962,13 @@ function createCommandMachine(options = {}) {
|
|
|
953
962
|
}
|
|
954
963
|
filteredIds = results.map((r) => r.id);
|
|
955
964
|
}
|
|
956
|
-
filteredIdSet = new Set(
|
|
965
|
+
filteredIdSet = /* @__PURE__ */ new Set();
|
|
966
|
+
filteredIdIndex = /* @__PURE__ */ new Map();
|
|
967
|
+
for (let i = 0; i < filteredIds.length; i++) {
|
|
968
|
+
const id = filteredIds[i];
|
|
969
|
+
filteredIdSet.add(id);
|
|
970
|
+
filteredIdIndex.set(id, i);
|
|
971
|
+
}
|
|
957
972
|
const groupedIds = Map.groupBy(filteredIds, (id) => {
|
|
958
973
|
return registry.getItem(id)?.groupId ?? "__ungrouped";
|
|
959
974
|
});
|
|
@@ -996,7 +1011,7 @@ function createCommandMachine(options = {}) {
|
|
|
996
1011
|
return;
|
|
997
1012
|
}
|
|
998
1013
|
const loop = options.loop ?? true;
|
|
999
|
-
const currentIdx = activeId ?
|
|
1014
|
+
const currentIdx = activeId ? filteredIdIndex.get(activeId) ?? -1 : -1;
|
|
1000
1015
|
let newIdx;
|
|
1001
1016
|
if (direction === "next") if (currentIdx === -1 || currentIdx >= filteredIds.length - 1) newIdx = loop ? 0 : filteredIds.length - 1;
|
|
1002
1017
|
else newIdx = currentIdx + 1;
|
|
@@ -1139,6 +1154,9 @@ function createCommandMachine(options = {}) {
|
|
|
1139
1154
|
getKeyboardRegistry() {
|
|
1140
1155
|
return keyboardRegistry;
|
|
1141
1156
|
},
|
|
1157
|
+
getFilteredIdSet() {
|
|
1158
|
+
return filteredIdSet;
|
|
1159
|
+
},
|
|
1142
1160
|
[Symbol.dispose]() {
|
|
1143
1161
|
scheduler[Symbol.dispose]();
|
|
1144
1162
|
registry[Symbol.dispose]();
|
|
@@ -1163,7 +1181,7 @@ async function scoreItemAsync(query, item) {
|
|
|
1163
1181
|
* Enables streaming consumption and lazy evaluation of scoring operations.
|
|
1164
1182
|
*/
|
|
1165
1183
|
async function* scoreItemsGenerator(query, items) {
|
|
1166
|
-
for (const item of items) yield Promise.try(() => scoreItem(query, item));
|
|
1184
|
+
for (const item of items) yield Promise.try(() => scoreItem(query, item)).catch(() => null);
|
|
1167
1185
|
}
|
|
1168
1186
|
/**
|
|
1169
1187
|
* Batch score items using Array.fromAsync (ES2026) with an async generator pipeline.
|
|
@@ -1214,4 +1232,4 @@ function createTelemetryMiddleware(hooks) {
|
|
|
1214
1232
|
//#endregion
|
|
1215
1233
|
export { itemId as C, groupId as S, computeFrecencyBonus as _, createScheduler as a, DEFAULT_MACHINE_OPTIONS as b, scoreItem as c, formatShortcut as d, parseShortcut as f, FrecencyEngine as g, matchesShortcut as h, createCommandMachine as i, CommandRegistry as l, findMatchingShortcut as m, batchScoreItems as n, TypedEmitter as o, detectConflicts as p, scoreItemAsync as r, createSearchEngine as s, createTelemetryMiddleware as t, KeyboardShortcutRegistry as u, MemoryFrecencyStorage as v, IdbFrecencyStorage as w, createInitialState as x, DEFAULT_FRECENCY_DECAY as y };
|
|
1216
1234
|
|
|
1217
|
-
//# sourceMappingURL=core-
|
|
1235
|
+
//# sourceMappingURL=core-CgQYpHK3.mjs.map
|