use-command-palette 0.1.1

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 ADDED
@@ -0,0 +1,517 @@
1
+ # use-command-palette
2
+
3
+ [![npm version](https://img.shields.io/npm/v/use-command-palette)](https://www.npmjs.com/package/use-command-palette)
4
+ [![npm downloads](https://img.shields.io/npm/dm/use-command-palette)](https://www.npmjs.com/package/use-command-palette)
5
+ [![license](https://img.shields.io/npm/l/use-command-palette)](LICENSE)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue)](https://www.typescriptlang.org/)
7
+
8
+ **Headless React hook for command palettes. You bring the UI, we bring the logic.**
9
+
10
+ No component library. No Provider. No opinions about your CSS. Just a hook that returns state, actions, and ARIA-ready prop getters — and gets out of your way.
11
+
12
+ ---
13
+
14
+ ## Why use-command-palette?
15
+
16
+ | | cmdk | kbar | **use-command-palette** |
17
+ |---|---|---|---|
18
+ | Zero dependencies | ✗ | ✗ | **✓** |
19
+ | No Provider required | ✗ | ✗ | **✓** |
20
+ | No UI shipped | ✗ | ✗ | **✓** |
21
+ | Async filter support | ✗ | ✗ | **✓** |
22
+ | Nested commands (pages) | ✗ | ✗ | **✓** |
23
+ | Recent items built-in | ✗ | ✗ | **✓** |
24
+ | Animation state machine | ✗ | ✗ | **✓** |
25
+ | Testing utilities | ✗ | ✗ | **✓** |
26
+ | TypeScript strict | ✓ | partial | **✓** |
27
+ | SSR safe | partial | partial | **✓** |
28
+
29
+ ---
30
+
31
+ ## Live Demos
32
+
33
+ | Style | Description |
34
+ |-------|-------------|
35
+ | [Plain CSS demo](#) | CSS custom properties, dark mode, animations |
36
+ | [Tailwind demo](#) | Utility-first, dark/light, nested pages |
37
+ | [MUI demo](#) | Material-UI primitives, no custom CSS |
38
+ | [Minimal demo](#) | Inline styles only — shows the raw API |
39
+
40
+ ---
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ npm install use-command-palette
46
+ ```
47
+
48
+ React 16.8+ is the only peer dependency.
49
+
50
+ ---
51
+
52
+ ## Quick Start
53
+
54
+ ```tsx
55
+ import { useCommandPalette } from 'use-command-palette'
56
+
57
+ const commands = [
58
+ { id: 'open', label: 'Open File', keywords: ['open'] },
59
+ { id: 'save', label: 'Save File', keywords: ['save'] },
60
+ { id: 'theme', label: 'Change Theme' },
61
+ ]
62
+
63
+ export default function App() {
64
+ const {
65
+ isOpen, isMounted, open, close, announcement,
66
+ filteredItems, highlightedIndex,
67
+ getContainerProps, getInputProps, getListProps, getItemProps, getAnnouncerProps,
68
+ } = useCommandPalette({
69
+ items: commands,
70
+ onSelect: (item) => console.log('selected:', item.label),
71
+ })
72
+
73
+ return (
74
+ <>
75
+ {/* Aria live announcer — visually hidden, required for screen readers */}
76
+ <div {...getAnnouncerProps()}>{announcement}</div>
77
+
78
+ <button onClick={open}>Open palette (Ctrl+K)</button>
79
+
80
+ {isMounted && (
81
+ <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)' }} onClick={close}>
82
+ <div {...getContainerProps()} onClick={(e) => e.stopPropagation()}>
83
+ <input {...getInputProps({ placeholder: 'Search commands…' })} />
84
+ <ul {...getListProps()}>
85
+ {filteredItems.map((item, index) => (
86
+ <li
87
+ key={item.id}
88
+ {...getItemProps({ index, item })}
89
+ style={{ background: index === highlightedIndex ? '#e0e7ff' : 'transparent' }}
90
+ >
91
+ {item.label}
92
+ </li>
93
+ ))}
94
+ </ul>
95
+ </div>
96
+ </div>
97
+ )}
98
+ </>
99
+ )
100
+ }
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Full API Reference
106
+
107
+ ### `useCommandPalette(options)`
108
+
109
+ #### Options
110
+
111
+ | Option | Type | Default | Description |
112
+ |--------|------|---------|-------------|
113
+ | `items` | `CommandItem[]` | **required** | The full list of commands |
114
+ | `onSelect` | `(item: CommandItem) => void` | **required** | Called when the user picks an item |
115
+ | `filterFn` | `(items, query) => CommandItem[] \| Promise<CommandItem[]>` | built-in fuzzy | Override the default filter; sync or async |
116
+ | `defaultOpen` | `boolean` | `false` | Uncontrolled initial open state |
117
+ | `isOpen` | `boolean` | — | Controlled open state (omit for uncontrolled) |
118
+ | `onOpenChange` | `(open: boolean) => void` | — | Called in controlled mode when open state should change |
119
+ | `hotkey` | `string \| string[]` | `'mod+k'` | Global keyboard shortcut(s). `mod` = Cmd on Mac, Ctrl elsewhere |
120
+ | `closeOnSelect` | `boolean` | `true` | Whether selecting an item closes the palette |
121
+ | `animationDuration` | `number` | `0` | Duration in ms for enter/exit animation; 0 = instant |
122
+ | `recent` | `{ enabled: boolean; max?: number; storageKey?: string }` | — | Persist recently used items to localStorage |
123
+
124
+ #### Return value — State
125
+
126
+ | Property | Type | Description |
127
+ |----------|------|-------------|
128
+ | `isOpen` | `boolean` | Whether the palette is open |
129
+ | `query` | `string` | Current search query |
130
+ | `highlightedIndex` | `number` | Index of the highlighted item |
131
+ | `filteredItems` | `CommandItem[]` | Items matching the current query |
132
+ | `groupedItems` | `GroupedItems[]` | `filteredItems` grouped by their `group` field |
133
+ | `isLoading` | `boolean` | `true` while an async `filterFn` is in-flight |
134
+ | `isMounted` | `boolean` | `true` while the palette should be in the DOM (use instead of `isOpen` when `animationDuration > 0`) |
135
+ | `animationState` | `'entering' \| 'entered' \| 'exiting' \| 'exited'` | Current animation phase |
136
+ | `recentItems` | `CommandItem[]` | Recently selected items (requires `recent.enabled`) |
137
+ | `currentPage` | `CommandItem \| null` | Active nested page; null at root |
138
+ | `breadcrumb` | `CommandItem[]` | Ancestors of the current page |
139
+ | `canGoBack` | `boolean` | Whether the user can navigate back |
140
+ | `announcement` | `string` | Current screen-reader announcement text |
141
+
142
+ #### Return value — Actions
143
+
144
+ | Property | Type | Description |
145
+ |----------|------|-------------|
146
+ | `open` | `() => void` | Open the palette (saves focus) |
147
+ | `close` | `() => void` | Close the palette (restores focus, clears query) |
148
+ | `toggle` | `() => void` | Toggle open/closed |
149
+ | `setQuery` | `(q: string) => void` | Update the search query |
150
+ | `selectItem` | `(item: CommandItem) => void` | Programmatically select an item |
151
+ | `highlightIndex` | `(i: number) => void` | Move highlight to a specific index |
152
+ | `goToPage` | `(item: CommandItem) => void` | Navigate into an item's children |
153
+ | `goBack` | `() => void` | Navigate back to the parent page |
154
+
155
+ #### Return value — Prop getters
156
+
157
+ | Property | Description |
158
+ |----------|-------------|
159
+ | `getContainerProps()` | Spread onto your modal/dialog wrapper |
160
+ | `getInputProps(overrides?)` | Spread onto the search `<input>` |
161
+ | `getListProps()` | Spread onto the results `<ul>` |
162
+ | `getItemProps({ index, item })` | Spread onto each result `<li>` |
163
+ | `getAnnouncerProps()` | Spread onto a visually-hidden `<div>` for `aria-live` announcements |
164
+
165
+ ---
166
+
167
+ ## Features in depth
168
+
169
+ ### Async filtering
170
+
171
+ `filterFn` can return a `Promise`. The hook sets `isLoading: true` while the promise is in-flight, cancels stale results when a new query arrives, and resolves to `isLoading: false` when done. Sync functions work exactly as before — no loading flash.
172
+
173
+ ```tsx
174
+ useCommandPalette({
175
+ items,
176
+ onSelect,
177
+ filterFn: async (items, query) => {
178
+ const results = await searchAPI(query)
179
+ return results
180
+ },
181
+ })
182
+ ```
183
+
184
+ ---
185
+
186
+ ### Animation state
187
+
188
+ Set `animationDuration` (in ms) to get a four-state machine: `entering → entered → exiting → exited`. Use `isMounted` to decide when to add the element to the DOM; use `animationState` to drive your CSS class names or inline styles.
189
+
190
+ ```tsx
191
+ const { isMounted, animationState } = useCommandPalette({
192
+ items, onSelect,
193
+ animationDuration: 200,
194
+ })
195
+
196
+ // Render while mounted (includes exit phase)
197
+ {isMounted && (
198
+ <div className={`palette ${animationState}`}>
199
+ {/* CSS: .palette.entering { opacity: 0 } .palette.entered { opacity: 1 } */}
200
+ </div>
201
+ )}
202
+ ```
203
+
204
+ When `animationDuration` is 0 (the default), `isMounted === isOpen` and `animationState` is always `'entered'` or `'exited'` — identical to the old behaviour.
205
+
206
+ ---
207
+
208
+ ### Recent items
209
+
210
+ Pass `recent: { enabled: true }` to persist recently selected items to `localStorage`. They are surfaced in `recentItems` and updated automatically on each selection.
211
+
212
+ ```tsx
213
+ const { recentItems } = useCommandPalette({
214
+ items, onSelect,
215
+ recent: { enabled: true, max: 5, storageKey: 'my-app-recent' },
216
+ })
217
+
218
+ // Show recentItems at the top of an empty palette, or merge them into items
219
+ ```
220
+
221
+ ---
222
+
223
+ ### Controlled isOpen
224
+
225
+ Pass `isOpen` and `onOpenChange` to take control of the open state — useful for URL-driven palettes or when the open state lives in a state management store.
226
+
227
+ ```tsx
228
+ const [open, setOpen] = useState(false)
229
+
230
+ useCommandPalette({
231
+ items, onSelect,
232
+ isOpen: open,
233
+ onOpenChange: setOpen,
234
+ })
235
+ ```
236
+
237
+ Omit both props for the existing uncontrolled behaviour.
238
+
239
+ ---
240
+
241
+ ### Nested commands (pages)
242
+
243
+ Add a `children` array to any `CommandItem`. When a user selects a parent item the hook navigates into the children instead of calling `onSelect`. Use `currentPage`, `breadcrumb`, `canGoBack`, and `goBack` to render navigation chrome.
244
+
245
+ ```tsx
246
+ const commands = [
247
+ {
248
+ id: 'theme',
249
+ label: 'Change Theme',
250
+ children: [
251
+ { id: 'theme-dark', label: 'Dark' },
252
+ { id: 'theme-light', label: 'Light' },
253
+ ],
254
+ },
255
+ ]
256
+
257
+ const { currentPage, breadcrumb, canGoBack, goBack } = useCommandPalette({ items: commands, onSelect })
258
+
259
+ // Render a breadcrumb:
260
+ {canGoBack && (
261
+ <div>
262
+ <button onClick={goBack}>← back</button>
263
+ {breadcrumb.map((crumb) => <span key={crumb.id}> / {crumb.label}</span>)}
264
+ </div>
265
+ )}
266
+ ```
267
+
268
+ ---
269
+
270
+ ### Multiple hotkeys
271
+
272
+ `hotkey` accepts a string or array of strings. Any match opens/closes the palette.
273
+
274
+ ```tsx
275
+ useCommandPalette({ items, onSelect, hotkey: ['mod+k', 'mod+p'] })
276
+ ```
277
+
278
+ ---
279
+
280
+ ### useRegisterCommands (no Provider)
281
+
282
+ Register commands from anywhere in your component tree without a Provider. The module-level registry re-renders all subscribers when commands change.
283
+
284
+ ```tsx
285
+ // In a deep component:
286
+ import { useRegisterCommands } from 'use-command-palette'
287
+
288
+ function SettingsPanel() {
289
+ useRegisterCommands([
290
+ { id: 'reset', label: 'Reset Settings' },
291
+ ], []) // deps array, like useEffect
292
+ }
293
+
294
+ // In the palette component:
295
+ import { useRegisteredCommands } from 'use-command-palette'
296
+
297
+ function CommandPalette() {
298
+ const { commands: registeredCommands } = useRegisteredCommands()
299
+
300
+ const { filteredItems } = useCommandPalette({
301
+ items: [...myStaticCommands, ...registeredCommands],
302
+ onSelect,
303
+ })
304
+ }
305
+ ```
306
+
307
+ ---
308
+
309
+ ### Aria-live announcements
310
+
311
+ `getAnnouncerProps()` returns props for a visually-hidden `div` with `aria-live="polite"`. The `announcement` string updates automatically:
312
+
313
+ - Palette opens → `"Command palette open"`
314
+ - Results change → `"N results"`
315
+ - Item selected → `"[label] selected"`
316
+
317
+ ```tsx
318
+ <div {...getAnnouncerProps()}>{announcement}</div>
319
+ ```
320
+
321
+ ---
322
+
323
+ ### Testing utilities
324
+
325
+ ```bash
326
+ import {
327
+ openPaletteWithHotkey,
328
+ typeInPalette,
329
+ pressArrowDown,
330
+ pressEnter,
331
+ pressEscape,
332
+ getAllPaletteItems,
333
+ getHighlightedItem,
334
+ getPaletteInput,
335
+ waitForPaletteResults,
336
+ } from 'use-command-palette/testing'
337
+ ```
338
+
339
+ ```tsx
340
+ it('filters and selects', async () => {
341
+ render(<MyApp />)
342
+ openPaletteWithHotkey()
343
+ typeInPalette('save')
344
+ await waitForPaletteResults()
345
+ expect(getAllPaletteItems()).toHaveLength(1)
346
+ pressEnter()
347
+ expect(onSelect).toHaveBeenCalledWith(expect.objectContaining({ label: 'Save File' }))
348
+ })
349
+ ```
350
+
351
+ ---
352
+
353
+ ## Prop Getters
354
+
355
+ Prop getters are the headless pattern: we return the attributes, you decide where they go.
356
+
357
+ ### `getContainerProps()`
358
+
359
+ ```tsx
360
+ <div {...getContainerProps()}>
361
+ {/* role="dialog" aria-modal={true} aria-label="Command palette" */}
362
+ </div>
363
+ ```
364
+
365
+ ### `getInputProps(overrides?)`
366
+
367
+ ```tsx
368
+ <input {...getInputProps({ placeholder: 'Search…', className: 'my-input' })} />
369
+ // value, onChange, onKeyDown, role="combobox", aria-expanded,
370
+ // aria-controls, aria-activedescendant, autoComplete="off", ref
371
+ // Your onChange/onKeyDown are merged, not replaced.
372
+ ```
373
+
374
+ ### `getListProps()`
375
+
376
+ ```tsx
377
+ <ul {...getListProps()}>
378
+ {/* role="listbox" id="cmd-palette-list" */}
379
+ </ul>
380
+ ```
381
+
382
+ ### `getItemProps({ index, item })`
383
+
384
+ ```tsx
385
+ {filteredItems.map((item, index) => (
386
+ <li key={item.id} {...getItemProps({ index, item })}>
387
+ {/* role="option" aria-selected aria-disabled onClick onMouseEnter id */}
388
+ {item.label}
389
+ </li>
390
+ ))}
391
+ ```
392
+
393
+ ### `getAnnouncerProps()`
394
+
395
+ ```tsx
396
+ <div {...getAnnouncerProps()}>{announcement}</div>
397
+ {/* aria-live="polite" aria-atomic={true} + visually-hidden style */}
398
+ ```
399
+
400
+ ---
401
+
402
+ ## Keyboard Shortcuts
403
+
404
+ | Key | Behaviour |
405
+ |-----|-----------|
406
+ | `Mod+K` | Toggle the palette open/closed from anywhere |
407
+ | `ArrowDown` / `Tab` | Move highlight down; wraps |
408
+ | `ArrowUp` / `Shift+Tab` | Move highlight up; wraps |
409
+ | `Enter` | Select the highlighted item |
410
+ | `Escape` | Close palette (or go back one page if nested) |
411
+
412
+ Focus moves to the input on open; restores to the previously focused element on close.
413
+
414
+ ---
415
+
416
+ ## Filtering
417
+
418
+ The built-in filter uses fuzzy scoring with three tiers:
419
+
420
+ 1. **Exact substring at word boundary** — highest score
421
+ 2. **Exact substring elsewhere** — high score, slight position penalty
422
+ 3. **Fuzzy match** (characters in order, not adjacent) — lower score, bonuses for consecutive runs and word-start positions
423
+
424
+ Override with your own `filterFn` for server search, `match-sorter`, etc.:
425
+
426
+ ```tsx
427
+ import { matchSorter } from 'match-sorter'
428
+
429
+ useCommandPalette({
430
+ items, onSelect,
431
+ filterFn: (items, query) =>
432
+ query ? matchSorter(items, query, { keys: ['label', 'keywords'] }) : items,
433
+ })
434
+ ```
435
+
436
+ Or go async for remote search:
437
+
438
+ ```tsx
439
+ filterFn: async (items, query) => {
440
+ if (!query) return items
441
+ const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`)
442
+ return res.json()
443
+ }
444
+ ```
445
+
446
+ ---
447
+
448
+ ## Grouping
449
+
450
+ ```tsx
451
+ const { groupedItems, filteredItems, highlightedIndex, getItemProps } = useCommandPalette({ items, onSelect })
452
+
453
+ <ul {...getListProps()}>
454
+ {groupedItems.map(({ group, items }) => (
455
+ <li key={group ?? '__ungrouped__'}>
456
+ {group && <div className="group-label">{group}</div>}
457
+ <ul>
458
+ {items.map((item) => {
459
+ const index = filteredItems.indexOf(item)
460
+ return (
461
+ <li key={item.id} {...getItemProps({ index, item })}>
462
+ {item.label}
463
+ </li>
464
+ )
465
+ })}
466
+ </ul>
467
+ </li>
468
+ ))}
469
+ </ul>
470
+ ```
471
+
472
+ ---
473
+
474
+ ## TypeScript
475
+
476
+ ### `CommandItem`
477
+
478
+ ```ts
479
+ interface CommandItem {
480
+ id: string
481
+ label: string
482
+ keywords?: string[]
483
+ group?: string
484
+ disabled?: boolean
485
+ children?: CommandItem[] // nested pages
486
+ [key: string]: unknown // attach any extra data
487
+ }
488
+ ```
489
+
490
+ ### Extending `CommandItem`
491
+
492
+ ```ts
493
+ type AppCommand = CommandItem & {
494
+ icon: React.ReactNode
495
+ shortcut?: string
496
+ }
497
+
498
+ const commands: AppCommand[] = [
499
+ { id: 'save', label: 'Save', icon: <SaveIcon />, shortcut: 'Ctrl+S' },
500
+ ]
501
+ ```
502
+
503
+ ---
504
+
505
+ ## Contributing
506
+
507
+ 1. Fork and create a branch
508
+ 2. `npm install`
509
+ 3. `npm test` — run the test suite (114 tests)
510
+ 4. `npm run lint` — TypeScript strict check
511
+ 5. Submit a PR with a clear description
512
+
513
+ ---
514
+
515
+ ## License
516
+
517
+ MIT © use-command-palette contributors
@@ -0,0 +1,37 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
8
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
9
+ }) : x)(function(x) {
10
+ if (typeof require !== "undefined") return require.apply(this, arguments);
11
+ throw Error('Dynamic require of "' + x + '" is not supported');
12
+ });
13
+ var __commonJS = (cb, mod) => function __require2() {
14
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
25
+ // If the importer is in node compatibility mode or this is not an ESM
26
+ // file that has been converted to a CommonJS file using a Babel-
27
+ // compatible transform (i.e. "__esModule" has not been set), then set
28
+ // "default" to the CommonJS "module.exports" for node compatibility.
29
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
+ mod
31
+ ));
32
+
33
+ export {
34
+ __require,
35
+ __commonJS,
36
+ __toESM
37
+ };
@@ -0,0 +1,132 @@
1
+ import { InputHTMLAttributes, RefCallback } from 'react';
2
+
3
+ interface CommandItem {
4
+ id: string;
5
+ label: string;
6
+ keywords?: string[];
7
+ group?: string;
8
+ disabled?: boolean;
9
+ children?: CommandItem[];
10
+ [key: string]: unknown;
11
+ }
12
+ interface GroupedItems {
13
+ group: string | undefined;
14
+ items: CommandItem[];
15
+ }
16
+ interface UseCommandPaletteOptions {
17
+ items: CommandItem[];
18
+ onSelect: (item: CommandItem) => void;
19
+ filterFn?: (items: CommandItem[], query: string) => CommandItem[] | Promise<CommandItem[]>;
20
+ defaultOpen?: boolean;
21
+ /** Pass a value to enable controlled mode; omit for uncontrolled */
22
+ isOpen?: boolean;
23
+ onOpenChange?: (open: boolean) => void;
24
+ hotkey?: string | string[];
25
+ closeOnSelect?: boolean;
26
+ animationDuration?: number;
27
+ recent?: {
28
+ enabled: boolean;
29
+ max?: number;
30
+ storageKey?: string;
31
+ };
32
+ }
33
+ interface ContainerProps {
34
+ role: 'dialog';
35
+ 'aria-modal': true;
36
+ 'aria-label': string;
37
+ }
38
+ interface ListProps {
39
+ role: 'listbox';
40
+ id: string;
41
+ }
42
+ interface ItemProps {
43
+ role: 'option';
44
+ 'aria-selected': boolean;
45
+ 'aria-disabled': boolean;
46
+ onClick: () => void;
47
+ onMouseEnter: () => void;
48
+ id: string;
49
+ }
50
+ type InputPropsOverrides = Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'onKeyDown' | 'role' | 'autoComplete' | 'aria-expanded' | 'aria-controls' | 'aria-activedescendant'>;
51
+ interface InputProps extends InputPropsOverrides {
52
+ ref: RefCallback<HTMLInputElement>;
53
+ value: string;
54
+ onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
55
+ onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void;
56
+ role: 'combobox';
57
+ 'aria-expanded': boolean;
58
+ 'aria-controls': string;
59
+ 'aria-activedescendant': string | undefined;
60
+ autoComplete: 'off';
61
+ }
62
+ type AnimationState = 'entering' | 'entered' | 'exiting' | 'exited';
63
+ interface AnnouncerProps {
64
+ 'aria-live': 'polite';
65
+ 'aria-atomic': true;
66
+ style: React.CSSProperties;
67
+ }
68
+ interface UseCommandPaletteReturn {
69
+ isOpen: boolean;
70
+ query: string;
71
+ highlightedIndex: number;
72
+ filteredItems: CommandItem[];
73
+ groupedItems: GroupedItems[];
74
+ isLoading: boolean;
75
+ isMounted: boolean;
76
+ animationState: AnimationState;
77
+ recentItems: CommandItem[];
78
+ currentPage: CommandItem | null;
79
+ breadcrumb: CommandItem[];
80
+ canGoBack: boolean;
81
+ announcement: string;
82
+ open: () => void;
83
+ close: () => void;
84
+ toggle: () => void;
85
+ setQuery: (query: string) => void;
86
+ selectItem: (item: CommandItem) => void;
87
+ highlightIndex: (index: number) => void;
88
+ goToPage: (item: CommandItem) => void;
89
+ goBack: () => void;
90
+ getContainerProps: () => ContainerProps;
91
+ getListProps: () => ListProps;
92
+ getItemProps: (args: {
93
+ index: number;
94
+ item: CommandItem;
95
+ }) => ItemProps;
96
+ getInputProps: (overrides?: InputPropsOverrides) => InputProps;
97
+ getAnnouncerProps: () => AnnouncerProps;
98
+ }
99
+
100
+ declare function useCommandPalette({ items, onSelect, filterFn, defaultOpen, isOpen: controlledIsOpen, onOpenChange, hotkey, closeOnSelect, animationDuration, recent, }: UseCommandPaletteOptions): UseCommandPaletteReturn;
101
+
102
+ declare function useRegisterCommands(commands: CommandItem[], deps: unknown[]): void;
103
+
104
+ declare function useRegisteredCommands(): {
105
+ commands: CommandItem[];
106
+ };
107
+
108
+ type Listener = () => void;
109
+ declare class CommandRegistry {
110
+ private store;
111
+ private listeners;
112
+ register(key: string, commands: CommandItem[]): void;
113
+ unregister(key: string): void;
114
+ getAll(): CommandItem[];
115
+ subscribe(listener: Listener): () => void;
116
+ private notify;
117
+ }
118
+ declare const registry: CommandRegistry;
119
+
120
+ /**
121
+ * Scores how well `query` matches `text` using fuzzy matching.
122
+ *
123
+ * Scoring tiers:
124
+ * - Exact substring at a word boundary → highest (150)
125
+ * - Exact substring elsewhere → high (100 - small position penalty)
126
+ * - Fuzzy (chars in order, not adjacent) → lower (accumulates per-char points)
127
+ *
128
+ * Returns `null` when there is no match at all.
129
+ */
130
+ declare function fuzzyScore(text: string, query: string): number | null;
131
+
132
+ export { type AnimationState, type AnnouncerProps, type CommandItem, type ContainerProps, type GroupedItems, type InputProps, type InputPropsOverrides, type ItemProps, type ListProps, type UseCommandPaletteOptions, type UseCommandPaletteReturn, fuzzyScore, registry, useCommandPalette, useRegisterCommands, useRegisteredCommands };