modern-cmdk 1.3.0 → 1.4.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/LICENSE +1 -1
- package/README.md +61 -161
- package/dist/codemod/cli.mjs +21 -19
- package/dist/react/chunk-dialog.mjs +237 -0
- package/dist/react/dialog.d.mts +18 -0
- package/dist/react/dialog.mjs +3 -0
- package/dist/react/index.d.mts +5 -331
- package/dist/react/index.mjs +19 -1371
- package/dist/react/styles.css +2 -2
- package/package.json +8 -12
- package/dist/codemod/cli.d.mts +0 -16
- package/dist/codemod/transforms/data-attrs.d.mts +0 -6
- package/dist/codemod/transforms/forward-ref.d.mts +0 -6
- package/dist/codemod/transforms/import-rewrite.d.mts +0 -6
- package/dist/codemod/transforms/should-filter.d.mts +0 -5
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { createContext, use, useCallback, useEffect, useId, useMemo, useOptimistic, useRef, useSyncExternalStore, useTransition } from "react";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { createCommandMachine } from "modern-cmdk";
|
|
5
|
+
import { Dialog } from "radix-ui";
|
|
6
|
+
// src/react/context.ts
|
|
7
|
+
const CommandStableContext = createContext(null);
|
|
8
|
+
CommandStableContext.displayName = "CommandStableContext";
|
|
9
|
+
const CommandStateContext = createContext(null);
|
|
10
|
+
CommandStateContext.displayName = "CommandStateContext";
|
|
11
|
+
/** Consume stable context with a descriptive error — replaces 3-line boilerplate in every component */
|
|
12
|
+
function useStableContext(component) {
|
|
13
|
+
const ctx = use(CommandStableContext);
|
|
14
|
+
if (!ctx) throw new Error(`${component} must be used within a <Command> component`);
|
|
15
|
+
return ctx;
|
|
16
|
+
}
|
|
17
|
+
/** Consume state context with a descriptive error — replaces 3-line boilerplate in every component */
|
|
18
|
+
function useStateContext(component) {
|
|
19
|
+
const ctx = use(CommandStateContext);
|
|
20
|
+
if (!ctx) throw new Error(`${component} must be used within a <Command> component`);
|
|
21
|
+
return ctx;
|
|
22
|
+
}
|
|
23
|
+
const CommandGroupContext = createContext(null);
|
|
24
|
+
CommandGroupContext.displayName = "CommandGroupContext";
|
|
25
|
+
/** Context for the status container rendered outside the listbox — used by Command.Empty */
|
|
26
|
+
const CommandListStatusContext = createContext(null);
|
|
27
|
+
CommandListStatusContext.displayName = "CommandListStatusContext";
|
|
28
|
+
// src/react/hooks/use-command.ts
|
|
29
|
+
/** Core hook that binds a CommandMachine to React via useSyncExternalStore, useOptimistic, and a transition for optimistic active-item updates. */
|
|
30
|
+
function useCommand(machine) {
|
|
31
|
+
const id = useId();
|
|
32
|
+
const [isPending, startTransition] = useTransition();
|
|
33
|
+
const state = useSyncExternalStore(machine.subscribe, machine.getState, machine.getState);
|
|
34
|
+
const filteredIdSet = useMemo(() => machine.getFilteredIdSet(), [state.filteredIds, machine]);
|
|
35
|
+
const [optimisticActiveId, rawSetOptimisticActiveId] = useOptimistic(state.activeId);
|
|
36
|
+
const setOptimisticActiveId = useCallback((id) => {
|
|
37
|
+
startTransition(() => {
|
|
38
|
+
rawSetOptimisticActiveId(id);
|
|
39
|
+
});
|
|
40
|
+
}, [rawSetOptimisticActiveId]);
|
|
41
|
+
const updateSearch = useCallback((query) => {
|
|
42
|
+
machine.send({
|
|
43
|
+
type: "SEARCH_CHANGE",
|
|
44
|
+
query
|
|
45
|
+
});
|
|
46
|
+
}, [machine]);
|
|
47
|
+
return {
|
|
48
|
+
state: optimisticActiveId !== state.activeId ? {
|
|
49
|
+
...state,
|
|
50
|
+
activeId: optimisticActiveId
|
|
51
|
+
} : state,
|
|
52
|
+
isPending,
|
|
53
|
+
updateSearch,
|
|
54
|
+
setOptimisticActiveId,
|
|
55
|
+
filteredIdSet,
|
|
56
|
+
id
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// src/react/hooks/use-keyboard.ts
|
|
60
|
+
/** Navigation key to machine direction mapping */
|
|
61
|
+
const NAVIGATION_KEYS = {
|
|
62
|
+
ArrowDown: "next",
|
|
63
|
+
ArrowUp: "prev",
|
|
64
|
+
Home: "first",
|
|
65
|
+
End: "last"
|
|
66
|
+
};
|
|
67
|
+
/** Create a keydown handler for command palette keyboard navigation */
|
|
68
|
+
function createKeydownHandler(machine, getState) {
|
|
69
|
+
return (event) => {
|
|
70
|
+
const { key } = event;
|
|
71
|
+
const state = getState();
|
|
72
|
+
const direction = NAVIGATION_KEYS[key];
|
|
73
|
+
if (direction) {
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
machine.send({
|
|
76
|
+
type: "NAVIGATE",
|
|
77
|
+
direction
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
switch (key) {
|
|
82
|
+
case "Enter": {
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
const { activeId } = state;
|
|
85
|
+
if (activeId) machine.send({
|
|
86
|
+
type: "ITEM_SELECT",
|
|
87
|
+
id: activeId
|
|
88
|
+
});
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case "Backspace":
|
|
92
|
+
if (state.search === "" && state.pageStack.length > 0) {
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
machine.send({ type: "PAGE_POP" });
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
case "Escape":
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
machine.send({ type: "CLOSE" });
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
// src/react/hooks/use-command-setup.ts
|
|
104
|
+
/**
|
|
105
|
+
* Shared setup for Command and Command.Dialog — creates machine once,
|
|
106
|
+
* subscribes via useSyncExternalStore, memoizes context values, and
|
|
107
|
+
* registers disposal on unmount.
|
|
108
|
+
*
|
|
109
|
+
* @param machineFactory - Called exactly once (first render) to create the machine.
|
|
110
|
+
* Subsequent renders skip it via ref guard.
|
|
111
|
+
* @param label - Accessible label for the command palette (included in stable context).
|
|
112
|
+
*/
|
|
113
|
+
function useCommandSetup(machineFactory, label) {
|
|
114
|
+
const machineRef = useRef(null);
|
|
115
|
+
if (machineRef.current === null) machineRef.current = machineFactory();
|
|
116
|
+
const machine = machineRef.current;
|
|
117
|
+
const { state, isPending, updateSearch, setOptimisticActiveId, filteredIdSet, id } = useCommand(machine);
|
|
118
|
+
const listId = `${id}-list`;
|
|
119
|
+
const inputId = `${id}-input`;
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
return () => {
|
|
122
|
+
machineRef.current?.[Symbol.dispose]();
|
|
123
|
+
machineRef.current = null;
|
|
124
|
+
};
|
|
125
|
+
}, []);
|
|
126
|
+
return {
|
|
127
|
+
machine,
|
|
128
|
+
state,
|
|
129
|
+
isPending,
|
|
130
|
+
listId,
|
|
131
|
+
inputId,
|
|
132
|
+
handleKeyDown: useMemo(() => createKeydownHandler(machine, () => machine.getState()), [machine]),
|
|
133
|
+
stableContextValue: useMemo(() => ({
|
|
134
|
+
machine,
|
|
135
|
+
rootId: id,
|
|
136
|
+
listId,
|
|
137
|
+
inputId,
|
|
138
|
+
label,
|
|
139
|
+
updateSearch,
|
|
140
|
+
setOptimisticActiveId
|
|
141
|
+
}), [
|
|
142
|
+
machine,
|
|
143
|
+
id,
|
|
144
|
+
listId,
|
|
145
|
+
inputId,
|
|
146
|
+
label,
|
|
147
|
+
updateSearch,
|
|
148
|
+
setOptimisticActiveId
|
|
149
|
+
]),
|
|
150
|
+
stateContextValue: useMemo(() => ({
|
|
151
|
+
state,
|
|
152
|
+
isPending,
|
|
153
|
+
filteredIdSet,
|
|
154
|
+
visibleIdSet: null,
|
|
155
|
+
virtualPositionMap: null
|
|
156
|
+
}), [
|
|
157
|
+
state,
|
|
158
|
+
isPending,
|
|
159
|
+
filteredIdSet
|
|
160
|
+
])
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// src/react/dialog.tsx
|
|
164
|
+
/** Dialog variant that wraps the command palette in a Radix UI Dialog with focus trap and overlay. */
|
|
165
|
+
function CommandDialog({ children, className, overlayClassName, contentClassName, label = "Command palette", description = "Type a command or search...", open: controlledOpen, onOpenChange, container, ...machineOptions }) {
|
|
166
|
+
const onOpenChangeRef = useRef(onOpenChange);
|
|
167
|
+
onOpenChangeRef.current = onOpenChange;
|
|
168
|
+
const { machine, state, handleKeyDown, stableContextValue, stateContextValue, inputId } = useCommandSetup(() => createCommandMachine({
|
|
169
|
+
...machineOptions,
|
|
170
|
+
open: controlledOpen ?? false,
|
|
171
|
+
onOpenChange: (open) => {
|
|
172
|
+
onOpenChangeRef.current?.(open);
|
|
173
|
+
}
|
|
174
|
+
}), label);
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
if (!state.open) return;
|
|
177
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
178
|
+
return () => {
|
|
179
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
180
|
+
};
|
|
181
|
+
}, [handleKeyDown, state.open]);
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
if (controlledOpen !== void 0) machine.send({ type: controlledOpen ? "OPEN" : "CLOSE" });
|
|
184
|
+
}, [controlledOpen, machine]);
|
|
185
|
+
const handleOpenAutoFocus = useCallback((e) => {
|
|
186
|
+
e.preventDefault();
|
|
187
|
+
document.getElementById(inputId)?.focus();
|
|
188
|
+
}, [inputId]);
|
|
189
|
+
const handleRadixOpenChange = useCallback((open) => {
|
|
190
|
+
machine.send({ type: open ? "OPEN" : "CLOSE" });
|
|
191
|
+
}, [machine]);
|
|
192
|
+
return /* @__PURE__ */ jsx(Dialog.Root, {
|
|
193
|
+
open: state.open,
|
|
194
|
+
onOpenChange: handleRadixOpenChange,
|
|
195
|
+
children: /* @__PURE__ */ jsxs(Dialog.Portal, {
|
|
196
|
+
container,
|
|
197
|
+
children: [/* @__PURE__ */ jsx(Dialog.Overlay, {
|
|
198
|
+
"data-command-overlay": "",
|
|
199
|
+
"data-state": state.open ? "open" : "closed",
|
|
200
|
+
className: overlayClassName
|
|
201
|
+
}), /* @__PURE__ */ jsxs(Dialog.Content, {
|
|
202
|
+
"data-command-dialog": "",
|
|
203
|
+
"data-state": state.open ? "open" : "closed",
|
|
204
|
+
className,
|
|
205
|
+
"aria-label": label,
|
|
206
|
+
onOpenAutoFocus: handleOpenAutoFocus,
|
|
207
|
+
children: [
|
|
208
|
+
/* @__PURE__ */ jsx(Dialog.Title, {
|
|
209
|
+
className: "sr-only",
|
|
210
|
+
children: label
|
|
211
|
+
}),
|
|
212
|
+
/* @__PURE__ */ jsx(Dialog.Description, {
|
|
213
|
+
className: "sr-only",
|
|
214
|
+
children: description
|
|
215
|
+
}),
|
|
216
|
+
/* @__PURE__ */ jsx(CommandStableContext, {
|
|
217
|
+
value: stableContextValue,
|
|
218
|
+
children: /* @__PURE__ */ jsx(CommandStateContext, {
|
|
219
|
+
value: stateContextValue,
|
|
220
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
221
|
+
"data-command-dialog-content": "",
|
|
222
|
+
className: contentClassName,
|
|
223
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
224
|
+
"data-command-root": "",
|
|
225
|
+
role: "search",
|
|
226
|
+
"aria-label": label,
|
|
227
|
+
children
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
]
|
|
233
|
+
})]
|
|
234
|
+
})
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
export { CommandGroupContext as a, CommandStateContext as c, useCommand as i, useStableContext as l, useCommandSetup as n, CommandListStatusContext as o, createKeydownHandler as r, CommandStableContext as s, CommandDialog as t, useStateContext as u };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
import { CommandMachineOptions } from "modern-cmdk";
|
|
4
|
+
export interface CommandDialogProps extends CommandMachineOptions {
|
|
5
|
+
readonly children?: ReactNode;
|
|
6
|
+
readonly className?: string;
|
|
7
|
+
readonly overlayClassName?: string;
|
|
8
|
+
readonly contentClassName?: string;
|
|
9
|
+
readonly label?: string;
|
|
10
|
+
readonly open?: boolean;
|
|
11
|
+
readonly onOpenChange?: (open: boolean) => void;
|
|
12
|
+
/** Accessible description for screen readers (default: 'Type a command or search...') */
|
|
13
|
+
readonly description?: string;
|
|
14
|
+
/** Container element for the portal (default: document.body) */
|
|
15
|
+
readonly container?: HTMLElement | null;
|
|
16
|
+
}
|
|
17
|
+
/** Dialog variant that wraps the command palette in a Radix UI Dialog with focus trap and overlay. */
|
|
18
|
+
export declare function CommandDialog({ children, className, overlayClassName, contentClassName, label, description, open: controlledOpen, onOpenChange, container, ...machineOptions }: CommandDialogProps): ReactNode;
|
package/dist/react/index.d.mts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
|
|
2
|
+
import { CommandDialog, CommandDialogProps } from "./dialog.mjs";
|
|
2
3
|
import { Component, ComponentPropsWithRef, Context, ErrorInfo, ReactNode } from "react";
|
|
4
|
+
import { CommandItem as CommandItem$1, CommandMachine, CommandMachineOptions, CommandState, GroupId, ItemId } from "modern-cmdk";
|
|
3
5
|
/** Activity mode — maps directly to React's Activity API */
|
|
4
6
|
type ActivityMode = "visible" | "hidden";
|
|
5
7
|
/** Props for the CommandActivity wrapper that controls visibility via React's Activity API. */
|
|
@@ -14,319 +16,6 @@ interface CommandActivityProps {
|
|
|
14
16
|
* Falls back to conditional rendering if Activity is unavailable.
|
|
15
17
|
*/
|
|
16
18
|
export declare function CommandActivity({ mode, children }: CommandActivityProps): ReactNode;
|
|
17
|
-
/** Result of scoring a single item against a query */
|
|
18
|
-
interface SearchResult {
|
|
19
|
-
readonly id: ItemId;
|
|
20
|
-
readonly score: number;
|
|
21
|
-
/** Character ranges that matched (for highlighting) — half-open [start, end) tuples, end exclusive */
|
|
22
|
-
readonly matches: ReadonlyArray<readonly [start: number, end: number]>;
|
|
23
|
-
}
|
|
24
|
-
/** Search engine interface — pluggable into the state machine */
|
|
25
|
-
interface SearchEngine extends Disposable {
|
|
26
|
-
/** Index items for fast lookup (call on registration) */
|
|
27
|
-
index(items: readonly CommandItem$1[]): void;
|
|
28
|
-
/** Search items by query, return ordered results — uses Iterator pipeline */
|
|
29
|
-
search(query: string, items: readonly CommandItem$1[]): IteratorObject<SearchResult>;
|
|
30
|
-
/** Remove items from index by id */
|
|
31
|
-
remove(ids: ReadonlySet<ItemId>): void;
|
|
32
|
-
/** Clear the entire index */
|
|
33
|
-
clear(): void;
|
|
34
|
-
/** Dispose resources */
|
|
35
|
-
[Symbol.dispose](): void;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Branded type for command item IDs.
|
|
39
|
-
* Prevents accidental mixing with plain strings at the type level.
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* ```ts
|
|
43
|
-
* const id = itemId('copy');
|
|
44
|
-
* // id has type ItemId, not string — catches type errors at compile time
|
|
45
|
-
* ```
|
|
46
|
-
*/
|
|
47
|
-
declare const __itemIdBrand: unique symbol;
|
|
48
|
-
type ItemId = string & {
|
|
49
|
-
readonly [__itemIdBrand]: never;
|
|
50
|
-
};
|
|
51
|
-
/**
|
|
52
|
-
* Branded type for group IDs.
|
|
53
|
-
* Prevents accidental mixing with plain strings or ItemId at the type level.
|
|
54
|
-
*/
|
|
55
|
-
declare const __groupIdBrand: unique symbol;
|
|
56
|
-
type GroupId = string & {
|
|
57
|
-
readonly [__groupIdBrand]: never;
|
|
58
|
-
};
|
|
59
|
-
/**
|
|
60
|
-
* A registered command item in the palette.
|
|
61
|
-
* All fields are readonly — state machine creates new items on update.
|
|
62
|
-
*/
|
|
63
|
-
interface CommandItem$1 {
|
|
64
|
-
/** Unique identifier (branded type) */
|
|
65
|
-
readonly id: ItemId;
|
|
66
|
-
/** Display text — also used as the primary search target */
|
|
67
|
-
readonly value: string;
|
|
68
|
-
/** Additional searchable terms (matched but not displayed) */
|
|
69
|
-
readonly keywords?: readonly string[] | undefined;
|
|
70
|
-
/** Group this item belongs to (items without groupId are ungrouped) */
|
|
71
|
-
readonly groupId?: GroupId | undefined;
|
|
72
|
-
/** Keyboard shortcut string (e.g., "Ctrl+C", "Mod+K") */
|
|
73
|
-
readonly shortcut?: string | undefined;
|
|
74
|
-
/** Whether this item is disabled (excluded from filtering and selection) */
|
|
75
|
-
readonly disabled?: boolean | undefined;
|
|
76
|
-
/** Callback fired when this item is selected */
|
|
77
|
-
readonly onSelect?: (() => void) | undefined;
|
|
78
|
-
/** Arbitrary metadata attached to the item */
|
|
79
|
-
readonly data?: Readonly<Record<string, unknown>> | undefined;
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* A command group for categorizing items.
|
|
83
|
-
* Groups are rendered with headings and sorted by priority.
|
|
84
|
-
*/
|
|
85
|
-
interface CommandGroup$1 {
|
|
86
|
-
/** Unique identifier (branded type) */
|
|
87
|
-
readonly id: GroupId;
|
|
88
|
-
/** Display heading text (rendered above the group's items) */
|
|
89
|
-
readonly heading?: string | undefined;
|
|
90
|
-
/** Sort priority — lower values appear first (default: 0) */
|
|
91
|
-
readonly priority?: number | undefined;
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Immutable state snapshot of the command palette.
|
|
95
|
-
* Consumed by React via `useSyncExternalStore` — each mutation
|
|
96
|
-
* produces a new object reference for efficient re-render detection.
|
|
97
|
-
*/
|
|
98
|
-
interface CommandState {
|
|
99
|
-
/** Current search query string */
|
|
100
|
-
readonly search: string;
|
|
101
|
-
/** Currently active (highlighted) item ID, or null if none */
|
|
102
|
-
readonly activeId: ItemId | null;
|
|
103
|
-
/** Ordered list of item IDs that pass the current filter */
|
|
104
|
-
readonly filteredIds: readonly ItemId[];
|
|
105
|
-
/** Map of group IDs to their visible item IDs */
|
|
106
|
-
readonly groupedIds: ReadonlyMap<GroupId, readonly ItemId[]>;
|
|
107
|
-
/** Count of visible items (shortcut for `filteredIds.length`) */
|
|
108
|
-
readonly filteredCount: number;
|
|
109
|
-
/** Whether async items are currently loading */
|
|
110
|
-
readonly loading: boolean;
|
|
111
|
-
/** Current page identifier (for multi-page navigation) */
|
|
112
|
-
readonly page: string;
|
|
113
|
-
/** Stack of previous pages for back-navigation */
|
|
114
|
-
readonly pageStack: readonly string[];
|
|
115
|
-
/** Whether the command palette dialog is open */
|
|
116
|
-
readonly open: boolean;
|
|
117
|
-
/** Epoch-millisecond timestamp of the last state mutation */
|
|
118
|
-
readonly lastUpdated: number;
|
|
119
|
-
}
|
|
120
|
-
/** All possible events the state machine accepts */
|
|
121
|
-
type CommandEvent = {
|
|
122
|
-
readonly type: "SEARCH_CHANGE";
|
|
123
|
-
readonly query: string;
|
|
124
|
-
} | {
|
|
125
|
-
readonly type: "ITEM_SELECT";
|
|
126
|
-
readonly id: ItemId;
|
|
127
|
-
} | {
|
|
128
|
-
readonly type: "ITEM_ACTIVATE";
|
|
129
|
-
readonly id: ItemId;
|
|
130
|
-
} | {
|
|
131
|
-
readonly type: "NAVIGATE";
|
|
132
|
-
readonly direction: "next" | "prev" | "first" | "last";
|
|
133
|
-
} | {
|
|
134
|
-
readonly type: "PAGE_PUSH";
|
|
135
|
-
readonly page: string;
|
|
136
|
-
} | {
|
|
137
|
-
readonly type: "PAGE_POP";
|
|
138
|
-
} | {
|
|
139
|
-
readonly type: "OPEN";
|
|
140
|
-
} | {
|
|
141
|
-
readonly type: "CLOSE";
|
|
142
|
-
} | {
|
|
143
|
-
readonly type: "TOGGLE";
|
|
144
|
-
} | {
|
|
145
|
-
readonly type: "ITEMS_LOADED";
|
|
146
|
-
readonly items: readonly CommandItem$1[];
|
|
147
|
-
} | {
|
|
148
|
-
readonly type: "REGISTER_ITEM";
|
|
149
|
-
readonly item: CommandItem$1;
|
|
150
|
-
} | {
|
|
151
|
-
readonly type: "UNREGISTER_ITEM";
|
|
152
|
-
readonly id: ItemId;
|
|
153
|
-
} | {
|
|
154
|
-
readonly type: "REGISTER_GROUP";
|
|
155
|
-
readonly group: CommandGroup$1;
|
|
156
|
-
} | {
|
|
157
|
-
readonly type: "UNREGISTER_GROUP";
|
|
158
|
-
readonly id: GroupId;
|
|
159
|
-
};
|
|
160
|
-
/** Frecency record for a single item */
|
|
161
|
-
interface FrecencyRecord {
|
|
162
|
-
readonly frequency: number;
|
|
163
|
-
/** Epoch-millisecond timestamp of last usage */
|
|
164
|
-
readonly lastUsed: number;
|
|
165
|
-
}
|
|
166
|
-
/** Complete frecency data for persistence */
|
|
167
|
-
interface FrecencyData {
|
|
168
|
-
readonly records: ReadonlyMap<ItemId, FrecencyRecord>;
|
|
169
|
-
}
|
|
170
|
-
/** Pluggable frecency storage interface */
|
|
171
|
-
interface FrecencyStorage extends Disposable {
|
|
172
|
-
load(namespace: string): FrecencyData | Promise<FrecencyData>;
|
|
173
|
-
save(namespace: string, data: FrecencyData): void | Promise<void>;
|
|
174
|
-
}
|
|
175
|
-
/** Frecency step-weight configuration per recency bucket (hour/day/week/month/older) */
|
|
176
|
-
interface FrecencyDecayConfig {
|
|
177
|
-
readonly hourWeight?: number | undefined;
|
|
178
|
-
readonly dayWeight?: number | undefined;
|
|
179
|
-
readonly weekWeight?: number | undefined;
|
|
180
|
-
readonly monthWeight?: number | undefined;
|
|
181
|
-
readonly olderWeight?: number | undefined;
|
|
182
|
-
}
|
|
183
|
-
/** Frecency configuration options */
|
|
184
|
-
interface FrecencyOptions {
|
|
185
|
-
readonly enabled: boolean;
|
|
186
|
-
readonly storage?: FrecencyStorage | undefined;
|
|
187
|
-
readonly namespace?: string | undefined;
|
|
188
|
-
readonly decayConfig?: FrecencyDecayConfig | undefined;
|
|
189
|
-
}
|
|
190
|
-
/** Options for creating a command machine */
|
|
191
|
-
interface CommandMachineOptions {
|
|
192
|
-
/** Pre-registered items to populate on creation */
|
|
193
|
-
readonly items?: readonly CommandItem$1[] | undefined;
|
|
194
|
-
/** Pre-registered groups for item categorization */
|
|
195
|
-
readonly groups?: readonly CommandGroup$1[] | undefined;
|
|
196
|
-
/** Custom filter function, or `false` to disable filtering entirely */
|
|
197
|
-
readonly filter?: ((item: CommandItem$1, query: string) => number | false) | false | undefined;
|
|
198
|
-
/** Accessible label for the command palette */
|
|
199
|
-
readonly label?: string | undefined;
|
|
200
|
-
/** Initial open state */
|
|
201
|
-
readonly open?: boolean | undefined;
|
|
202
|
-
/** Frecency configuration for usage-aware ranking */
|
|
203
|
-
readonly frecency?: FrecencyOptions | undefined;
|
|
204
|
-
/** Pluggable search engine — drop-in replacement for the default scorer (e.g., WASM engine) */
|
|
205
|
-
readonly search?: SearchEngine | undefined;
|
|
206
|
-
/** Callback fired when an item is selected */
|
|
207
|
-
readonly onSelect?: ((id: ItemId) => void) | undefined;
|
|
208
|
-
/** Callback fired when the active (highlighted) item changes */
|
|
209
|
-
readonly onActiveChange?: ((id: ItemId | null) => void) | undefined;
|
|
210
|
-
/** Callback fired when the dialog open state changes */
|
|
211
|
-
readonly onOpenChange?: ((open: boolean) => void) | undefined;
|
|
212
|
-
/** Callback fired when the search query changes */
|
|
213
|
-
readonly onSearchChange?: ((query: string) => void) | undefined;
|
|
214
|
-
/** Whether keyboard navigation loops from last to first item (default: true) */
|
|
215
|
-
readonly loop?: boolean | undefined;
|
|
216
|
-
/** Accepted but not read yet: Command.List uses fixed thresholds (on above 100 matches, off at 50 or fewer) (default: 100) */
|
|
217
|
-
readonly virtualizeThreshold?: number | undefined;
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* Structured representation of a keyboard shortcut after parsing.
|
|
221
|
-
* The `normalized` field provides a deterministic string for dedup and comparison.
|
|
222
|
-
*/
|
|
223
|
-
interface ParsedShortcut {
|
|
224
|
-
readonly key: string;
|
|
225
|
-
readonly meta: boolean;
|
|
226
|
-
readonly ctrl: boolean;
|
|
227
|
-
readonly shift: boolean;
|
|
228
|
-
readonly alt: boolean;
|
|
229
|
-
readonly raw: string;
|
|
230
|
-
readonly normalized: string;
|
|
231
|
-
}
|
|
232
|
-
interface ShortcutBinding {
|
|
233
|
-
readonly shortcut: ParsedShortcut;
|
|
234
|
-
readonly itemId: ItemId;
|
|
235
|
-
readonly handler: () => void;
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Manages keyboard shortcut bindings with global keydown listening.
|
|
239
|
-
* Supports `using` for automatic cleanup of both individual bindings and the
|
|
240
|
-
* global document listener. Detects conflicts with the local mapGroupBy helper (core/utils/group-by.ts).
|
|
241
|
-
*/
|
|
242
|
-
declare class KeyboardShortcutRegistry implements Disposable {
|
|
243
|
-
#private;
|
|
244
|
-
constructor(options?: {
|
|
245
|
-
enabled?: boolean;
|
|
246
|
-
});
|
|
247
|
-
/** Register a shortcut binding — returns Disposable for `using` auto-deregister */
|
|
248
|
-
register(shortcutStr: string, itemId: ItemId, handler: () => void): Disposable;
|
|
249
|
-
/** Unregister a shortcut by its string representation */
|
|
250
|
-
unregister(shortcutStr: string): void;
|
|
251
|
-
/** Unregister all shortcuts for a given item — Map spec allows deletion during iteration */
|
|
252
|
-
unregisterByItem(itemId: ItemId): void;
|
|
253
|
-
/** Check for conflicting shortcuts */
|
|
254
|
-
getConflicts(): ReadonlyMap<string, readonly ParsedShortcut[]>;
|
|
255
|
-
/** Enable or disable global shortcut listening */
|
|
256
|
-
setEnabled(enabled: boolean): void;
|
|
257
|
-
/** Get all registered bindings */
|
|
258
|
-
getBindings(): ReadonlyMap<string, ShortcutBinding>;
|
|
259
|
-
[Symbol.dispose](): void;
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Manages registration and lookup of command items and groups.
|
|
263
|
-
* Uses set operation helpers (union/intersection/difference) for efficient ID operations.
|
|
264
|
-
* All registration methods return Disposable for automatic cleanup via `using`.
|
|
265
|
-
*/
|
|
266
|
-
declare class CommandRegistry implements Disposable {
|
|
267
|
-
#private;
|
|
268
|
-
/** Register an item — returns Disposable for `using` auto-deregister */
|
|
269
|
-
registerItem(item: CommandItem$1): Disposable;
|
|
270
|
-
/** Register multiple items at once — single Disposable for batch cleanup. for...of for hot path. */
|
|
271
|
-
registerItems(items: readonly CommandItem$1[]): Disposable;
|
|
272
|
-
/** Unregister a single item — O(1) Map/Set delete, indexOf+toSpliced for order */
|
|
273
|
-
unregisterItem(id: ItemId): void;
|
|
274
|
-
/** Bulk unregister — for...of for hot path */
|
|
275
|
-
unregisterItems(ids: ReadonlySet<ItemId>): void;
|
|
276
|
-
/** Register a group */
|
|
277
|
-
registerGroup(group: CommandGroup$1): Disposable;
|
|
278
|
-
/** Unregister a group */
|
|
279
|
-
unregisterGroup(id: GroupId): void;
|
|
280
|
-
/** Get an item by ID — O(1) */
|
|
281
|
-
getItem(id: ItemId): CommandItem$1 | undefined;
|
|
282
|
-
/** Get a group by ID — O(1) */
|
|
283
|
-
getGroup(id: GroupId): CommandGroup$1 | undefined;
|
|
284
|
-
/** Get all registered items in insertion order — cached between mutations */
|
|
285
|
-
getItems(): readonly CommandItem$1[];
|
|
286
|
-
/** Get all registered groups sorted by priority — cached between group mutations */
|
|
287
|
-
getGroups(): readonly CommandGroup$1[];
|
|
288
|
-
/** Get items grouped by groupId — direct Map, no Object.entries conversion */
|
|
289
|
-
getGroupedItems(): ReadonlyMap<GroupId, readonly CommandItem$1[]>;
|
|
290
|
-
/** Get item IDs as a Set — returns internal set (O(1), no allocation) */
|
|
291
|
-
getItemIds(): ReadonlySet<ItemId>;
|
|
292
|
-
/** Find intersection of registered items with a candidate set */
|
|
293
|
-
intersectWith(candidateIds: ReadonlySet<ItemId>): ReadonlySet<ItemId>;
|
|
294
|
-
/** Find items registered but not in a given set */
|
|
295
|
-
differenceFrom(otherIds: ReadonlySet<ItemId>): ReadonlySet<ItemId>;
|
|
296
|
-
/** Union of registered IDs with another set */
|
|
297
|
-
unionWith(otherIds: ReadonlySet<ItemId>): ReadonlySet<ItemId>;
|
|
298
|
-
/** Check if registered items are a subset of the given set */
|
|
299
|
-
isSubsetOf(otherIds: ReadonlySet<ItemId>): boolean;
|
|
300
|
-
/** Check if registered items are a superset of the given set */
|
|
301
|
-
isSupersetOf(otherIds: ReadonlySet<ItemId>): boolean;
|
|
302
|
-
/** Check if registered items share no IDs with the given set */
|
|
303
|
-
isDisjointFrom(otherIds: ReadonlySet<ItemId>): boolean;
|
|
304
|
-
/** Symmetric difference between registered IDs and another set */
|
|
305
|
-
symmetricDifferenceWith(otherIds: ReadonlySet<ItemId>): ReadonlySet<ItemId>;
|
|
306
|
-
/** Total number of registered items */
|
|
307
|
-
get size(): number;
|
|
308
|
-
/** Total number of registered groups */
|
|
309
|
-
get groupCount(): number;
|
|
310
|
-
/** Clear all items and groups */
|
|
311
|
-
clear(): void;
|
|
312
|
-
[Symbol.dispose](): void;
|
|
313
|
-
}
|
|
314
|
-
/** Command machine — explicit resource management via `using` */
|
|
315
|
-
interface CommandMachine extends Disposable {
|
|
316
|
-
getState(): CommandState;
|
|
317
|
-
send(event: CommandEvent): void;
|
|
318
|
-
/** Subscribe to state changes — useSyncExternalStore-compatible (returns cleanup function) */
|
|
319
|
-
subscribe(listener: () => void): () => void;
|
|
320
|
-
/** Subscribe to full state changes — returns Disposable for `using` pattern */
|
|
321
|
-
subscribeState(listener: (state: CommandState) => void): Disposable;
|
|
322
|
-
getRegistry(): CommandRegistry;
|
|
323
|
-
getKeyboardRegistry(): KeyboardShortcutRegistry;
|
|
324
|
-
/** Get the current set of filtered item IDs — O(1) membership checks */
|
|
325
|
-
getFilteredIdSet(): ReadonlySet<ItemId>;
|
|
326
|
-
/** Get the index map for filtered IDs — O(1) positional lookup */
|
|
327
|
-
getFilteredIdIndex(): ReadonlyMap<ItemId, number>;
|
|
328
|
-
[Symbol.dispose](): void;
|
|
329
|
-
}
|
|
330
19
|
interface CommandAsyncItemsProps {
|
|
331
20
|
/** Promise that resolves to items */
|
|
332
21
|
readonly items: Promise<readonly CommandItem$1[]>;
|
|
@@ -340,21 +29,6 @@ export declare function CommandAsyncItems({ items, fallback, children }: Command
|
|
|
340
29
|
interface CommandBadgeProps extends ComponentPropsWithRef<"span"> {}
|
|
341
30
|
/** Inline badge for categorization or status indicators on command items. */
|
|
342
31
|
export declare function CommandBadge({ ref, children, ...props }: CommandBadgeProps): ReactNode;
|
|
343
|
-
interface CommandDialogProps extends CommandMachineOptions {
|
|
344
|
-
readonly children?: ReactNode;
|
|
345
|
-
readonly className?: string;
|
|
346
|
-
readonly overlayClassName?: string;
|
|
347
|
-
readonly contentClassName?: string;
|
|
348
|
-
readonly label?: string;
|
|
349
|
-
readonly open?: boolean;
|
|
350
|
-
readonly onOpenChange?: (open: boolean) => void;
|
|
351
|
-
/** Accessible description for screen readers (default: 'Type a command or search...') */
|
|
352
|
-
readonly description?: string;
|
|
353
|
-
/** Container element for the portal (default: document.body) */
|
|
354
|
-
readonly container?: HTMLElement | null;
|
|
355
|
-
}
|
|
356
|
-
/** Dialog variant that wraps the command palette in a Radix UI Dialog with focus trap and overlay. */
|
|
357
|
-
export declare function CommandDialog({ children, className, overlayClassName, contentClassName, label, description, open: controlledOpen, onOpenChange, container, ...machineOptions }: CommandDialogProps): ReactNode;
|
|
358
32
|
interface CommandEmptyProps extends ComponentPropsWithRef<"div"> {}
|
|
359
33
|
/** Rendered when no items match the current search query. */
|
|
360
34
|
export declare function CommandEmpty({ ref, children, ...props }: CommandEmptyProps): ReactNode;
|
|
@@ -471,7 +145,7 @@ interface CommandContextValue {
|
|
|
471
145
|
readonly filteredIdSet: ReadonlySet<ItemId>;
|
|
472
146
|
/** IDs currently visible in virtual viewport + overscan. null = not virtualizing (render all filtered). */
|
|
473
147
|
readonly visibleIdSet: ReadonlySet<ItemId> | null;
|
|
474
|
-
/** Map of visible item IDs to their
|
|
148
|
+
/** Map of visible item IDs to their translate offsets in px. null = not virtualizing. */
|
|
475
149
|
readonly virtualPositionMap: ReadonlyMap<ItemId, number> | null;
|
|
476
150
|
readonly rootId: CommandRootId;
|
|
477
151
|
readonly listId: string;
|
|
@@ -496,7 +170,7 @@ interface CommandStateContextValue {
|
|
|
496
170
|
readonly filteredIdSet: ReadonlySet<ItemId>;
|
|
497
171
|
/** IDs currently visible in virtual viewport + overscan. null = not virtualizing (render all filtered). */
|
|
498
172
|
readonly visibleIdSet: ReadonlySet<ItemId> | null;
|
|
499
|
-
/** Map of visible item IDs to their
|
|
173
|
+
/** Map of visible item IDs to their translate offsets in px. null = not virtualizing. */
|
|
500
174
|
readonly virtualPositionMap: ReadonlyMap<ItemId, number> | null;
|
|
501
175
|
}
|
|
502
176
|
export declare const CommandStableContext: Context<CommandStableContextValue | null>;
|
|
@@ -594,4 +268,4 @@ interface VirtualizerReturn {
|
|
|
594
268
|
}
|
|
595
269
|
/** Lightweight virtualizer for large item lists using ResizeObserver and scroll tracking. */
|
|
596
270
|
export declare function useVirtualizer(options: VirtualizerOptions): VirtualizerReturn;
|
|
597
|
-
export type
|
|
271
|
+
export { type ActivityMode, type CommandActivityProps, type CommandAsyncItemsProps, type CommandBadgeProps, type CommandContextValue, CommandDialog, type CommandDialogProps, type CommandEmptyProps, type CommandErrorBoundaryProps, type CommandGroupContextValue, type CommandGroupProps, type CommandHighlightProps, type CommandInputProps, type CommandItemProps, type CommandListProps, type CommandLoadingProps, type CommandPageId, type CommandPageProps, type CommandRootId, type CommandRootProps, type CommandSeparatorProps, type CommandShortcutProps, type CommandStableContextValue, type CommandStateContextValue, type RegisterGroupOptions, type RegisterItemOptions, type UseCommandReturn, type VirtualItem, type VirtualizerOptions, type VirtualizerReturn };
|