modern-cmdk 1.0.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.
Files changed (32) hide show
  1. package/LICENSE +21 -0
  2. package/dist/codemod/cli.d.mts +1 -0
  3. package/dist/codemod/cli.mjs +89836 -0
  4. package/dist/codemod/cli.mjs.map +1 -0
  5. package/dist/codemod/transforms/data-attrs.d.mts +11 -0
  6. package/dist/codemod/transforms/data-attrs.d.mts.map +1 -0
  7. package/dist/codemod/transforms/data-attrs.mjs +68 -0
  8. package/dist/codemod/transforms/data-attrs.mjs.map +1 -0
  9. package/dist/codemod/transforms/forward-ref.d.mts +11 -0
  10. package/dist/codemod/transforms/forward-ref.d.mts.map +1 -0
  11. package/dist/codemod/transforms/forward-ref.mjs +53 -0
  12. package/dist/codemod/transforms/forward-ref.mjs.map +1 -0
  13. package/dist/codemod/transforms/import-rewrite.d.mts +11 -0
  14. package/dist/codemod/transforms/import-rewrite.d.mts.map +1 -0
  15. package/dist/codemod/transforms/import-rewrite.mjs +45 -0
  16. package/dist/codemod/transforms/import-rewrite.mjs.map +1 -0
  17. package/dist/codemod/transforms/should-filter.d.mts +10 -0
  18. package/dist/codemod/transforms/should-filter.d.mts.map +1 -0
  19. package/dist/codemod/transforms/should-filter.mjs +21 -0
  20. package/dist/codemod/transforms/should-filter.mjs.map +1 -0
  21. package/dist/core/index.d.mts +2 -0
  22. package/dist/core/index.mjs +2 -0
  23. package/dist/core-Bq0eDvlc.mjs +1217 -0
  24. package/dist/core-Bq0eDvlc.mjs.map +1 -0
  25. package/dist/index-Ctz7l29k.d.mts +562 -0
  26. package/dist/index-Ctz7l29k.d.mts.map +1 -0
  27. package/dist/react/index.d.mts +358 -0
  28. package/dist/react/index.d.mts.map +1 -0
  29. package/dist/react/index.mjs +905 -0
  30. package/dist/react/index.mjs.map +1 -0
  31. package/package.json +71 -0
  32. package/src/react/styles.css +609 -0
@@ -0,0 +1,905 @@
1
+ import { C as itemId, S as groupId, d as formatShortcut, f as parseShortcut, i as createCommandMachine } from "../core-Bq0eDvlc.mjs";
2
+ import * as React from "react";
3
+ import { Component, Suspense, createContext, use, useCallback, useEffect, useId, useInsertionEffect, useMemo, useOptimistic, useRef, useState, useSyncExternalStore, useTransition } from "react";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ import { Dialog } from "radix-ui";
6
+ //#region src/react/activity.tsx
7
+ const ActivityComponent = "Activity" in React ? React.Activity : null;
8
+ /**
9
+ * Wraps content in React 19.3.0-canary Activity API.
10
+ * When mode="hidden", the component tree is preserved in memory but not rendered.
11
+ * Falls back to conditional rendering if Activity is unavailable.
12
+ */
13
+ function CommandActivity({ mode, children }) {
14
+ if (ActivityComponent) return /* @__PURE__ */ jsx(ActivityComponent, {
15
+ mode,
16
+ children
17
+ });
18
+ if (mode === "hidden") return null;
19
+ return /* @__PURE__ */ jsx(Fragment, { children });
20
+ }
21
+ //#endregion
22
+ //#region src/react/context.ts
23
+ const CommandContext = createContext(null);
24
+ CommandContext.displayName = "CommandContext";
25
+ const CommandGroupContext = createContext(null);
26
+ CommandGroupContext.displayName = "CommandGroupContext";
27
+ //#endregion
28
+ //#region src/react/async-items.tsx
29
+ /** Inner component that suspends via use() until items resolve */
30
+ function AsyncItemsInner({ items: itemsPromise, children }) {
31
+ const ctx = use(CommandContext);
32
+ if (!ctx) throw new Error("Command.AsyncItems must be used within a <Command> component");
33
+ const items = use(itemsPromise);
34
+ useEffect(() => {
35
+ ctx.machine.send({
36
+ type: "ITEMS_LOADED",
37
+ items
38
+ });
39
+ }, [ctx.machine, items]);
40
+ return /* @__PURE__ */ jsx(Fragment, { children: children(items) });
41
+ }
42
+ function CommandAsyncItems({ items, fallback, children }) {
43
+ const stableRef = useRef(items);
44
+ if (stableRef.current !== items) stableRef.current = items;
45
+ return /* @__PURE__ */ jsx(Suspense, {
46
+ fallback,
47
+ children: /* @__PURE__ */ jsx(AsyncItemsInner, {
48
+ items: stableRef.current,
49
+ children
50
+ })
51
+ });
52
+ }
53
+ //#endregion
54
+ //#region src/react/badge.tsx
55
+ function CommandBadge({ ref, children, ...props }) {
56
+ return /* @__PURE__ */ jsx("span", {
57
+ ref,
58
+ "data-command-badge": "",
59
+ ...props,
60
+ children
61
+ });
62
+ }
63
+ //#endregion
64
+ //#region src/react/hooks/use-command.ts
65
+ function useCommand(machine) {
66
+ const id = useId();
67
+ const [isPending, startTransition] = useTransition();
68
+ const state = useSyncExternalStore(machine.subscribe, machine.getState, machine.getState);
69
+ const filteredIdSet = useMemo(() => new Set(state.filteredIds), [state.filteredIds]);
70
+ const [optimisticActiveId, setOptimisticActiveId] = useOptimistic(state.activeId);
71
+ const updateSearch = useCallback((query) => {
72
+ startTransition(() => {
73
+ machine.send({
74
+ type: "SEARCH_CHANGE",
75
+ query
76
+ });
77
+ });
78
+ }, [machine]);
79
+ return {
80
+ state: optimisticActiveId !== state.activeId ? {
81
+ ...state,
82
+ activeId: optimisticActiveId
83
+ } : state,
84
+ isPending,
85
+ updateSearch,
86
+ setOptimisticActiveId,
87
+ filteredIdSet,
88
+ id
89
+ };
90
+ }
91
+ //#endregion
92
+ //#region src/react/hooks/use-keyboard.ts
93
+ /** Navigation key to machine direction mapping */
94
+ const NAVIGATION_KEYS = {
95
+ ArrowDown: "next",
96
+ ArrowUp: "prev",
97
+ Home: "first",
98
+ End: "last"
99
+ };
100
+ /** Create a keydown handler for command palette keyboard navigation */
101
+ function createKeydownHandler(machine, getState) {
102
+ return (event) => {
103
+ const { key } = event;
104
+ const state = getState();
105
+ const direction = NAVIGATION_KEYS[key];
106
+ if (direction) {
107
+ event.preventDefault();
108
+ machine.send({
109
+ type: "NAVIGATE",
110
+ direction
111
+ });
112
+ return;
113
+ }
114
+ switch (key) {
115
+ case "Enter": {
116
+ event.preventDefault();
117
+ const { activeId } = state;
118
+ if (activeId) machine.send({
119
+ type: "ITEM_SELECT",
120
+ id: activeId
121
+ });
122
+ break;
123
+ }
124
+ case "Backspace":
125
+ if (state.search === "" && state.pageStack.length > 0) {
126
+ event.preventDefault();
127
+ machine.send({ type: "PAGE_POP" });
128
+ }
129
+ break;
130
+ case "Escape":
131
+ event.preventDefault();
132
+ machine.send({ type: "CLOSE" });
133
+ break;
134
+ }
135
+ };
136
+ }
137
+ //#endregion
138
+ //#region src/react/dialog.tsx
139
+ function CommandDialog({ children, className, overlayClassName, contentClassName, label = "Command palette", open: controlledOpen, onOpenChange, container, ...machineOptions }) {
140
+ const rootId = useId();
141
+ const listId = `${rootId}-list`;
142
+ const inputId = `${rootId}-input`;
143
+ const machineRef = useRef(null);
144
+ if (machineRef.current === null) machineRef.current = createCommandMachine({
145
+ ...machineOptions,
146
+ open: controlledOpen ?? false,
147
+ onOpenChange: (open) => {
148
+ onOpenChange?.(open);
149
+ }
150
+ });
151
+ const machine = machineRef.current;
152
+ const { state, isPending, updateSearch, setOptimisticActiveId, filteredIdSet, id } = useCommand(machine);
153
+ useEffect(() => {
154
+ return () => {
155
+ machineRef.current?.[Symbol.dispose]();
156
+ machineRef.current = null;
157
+ };
158
+ }, []);
159
+ const handleKeyDown = useCallback(createKeydownHandler(machine, () => machine.getState()), []);
160
+ useEffect(() => {
161
+ if (!state.open) return;
162
+ document.addEventListener("keydown", handleKeyDown);
163
+ return () => {
164
+ document.removeEventListener("keydown", handleKeyDown);
165
+ };
166
+ }, [handleKeyDown, state.open]);
167
+ const prevControlledOpen = useRef(controlledOpen);
168
+ useEffect(() => {
169
+ if (controlledOpen !== void 0 && controlledOpen !== prevControlledOpen.current) {
170
+ prevControlledOpen.current = controlledOpen;
171
+ machine.send({ type: controlledOpen ? "OPEN" : "CLOSE" });
172
+ }
173
+ }, [controlledOpen, machine]);
174
+ const handleRadixOpenChange = useCallback((open) => {
175
+ machine.send({ type: open ? "OPEN" : "CLOSE" });
176
+ }, [machine]);
177
+ const contextValue = useMemo(() => ({
178
+ machine,
179
+ state,
180
+ isPending,
181
+ updateSearch,
182
+ setOptimisticActiveId,
183
+ filteredIdSet,
184
+ rootId: id,
185
+ listId,
186
+ inputId,
187
+ label
188
+ }), [
189
+ machine,
190
+ state,
191
+ isPending,
192
+ updateSearch,
193
+ setOptimisticActiveId,
194
+ filteredIdSet,
195
+ id,
196
+ listId,
197
+ inputId,
198
+ label
199
+ ]);
200
+ return /* @__PURE__ */ jsx(Dialog.Root, {
201
+ open: state.open,
202
+ onOpenChange: handleRadixOpenChange,
203
+ children: /* @__PURE__ */ jsxs(Dialog.Portal, {
204
+ container,
205
+ children: [/* @__PURE__ */ jsx(Dialog.Overlay, {
206
+ "data-command-overlay": "",
207
+ "data-state": state.open ? "open" : "closed",
208
+ className: overlayClassName
209
+ }), /* @__PURE__ */ jsxs(Dialog.Content, {
210
+ "data-command-dialog": "",
211
+ "data-state": state.open ? "open" : "closed",
212
+ className,
213
+ "aria-label": label,
214
+ onOpenAutoFocus: (e) => {
215
+ e.preventDefault();
216
+ document.getElementById(inputId)?.focus();
217
+ },
218
+ children: [
219
+ /* @__PURE__ */ jsx(Dialog.Title, {
220
+ className: "sr-only",
221
+ children: label
222
+ }),
223
+ /* @__PURE__ */ jsx(Dialog.Description, {
224
+ className: "sr-only",
225
+ children: "Type a command or search..."
226
+ }),
227
+ /* @__PURE__ */ jsx(CommandContext, {
228
+ value: contextValue,
229
+ children: /* @__PURE__ */ jsx("div", {
230
+ "data-command-dialog-content": "",
231
+ className: contentClassName,
232
+ children: /* @__PURE__ */ jsx("div", {
233
+ "data-command-root": "",
234
+ role: "search",
235
+ "aria-label": label,
236
+ children
237
+ })
238
+ })
239
+ })
240
+ ]
241
+ })]
242
+ })
243
+ });
244
+ }
245
+ //#endregion
246
+ //#region src/react/empty.tsx
247
+ function CommandEmpty({ ref, children, ...props }) {
248
+ const ctx = use(CommandContext);
249
+ if (!ctx) throw new Error("Command.Empty must be used within a <Command> component");
250
+ if (ctx.state.filteredCount > 0 || ctx.state.loading) return null;
251
+ return /* @__PURE__ */ jsx("div", {
252
+ ref,
253
+ "data-command-empty": "",
254
+ role: "status",
255
+ "aria-live": "polite",
256
+ ...props,
257
+ children
258
+ });
259
+ }
260
+ //#endregion
261
+ //#region src/react/hooks/use-register.ts
262
+ /** Register a command item with the machine — auto-deregisters on unmount */
263
+ function useRegisterItem(value, options) {
264
+ const ctx = use(CommandContext);
265
+ if (!ctx) throw new Error("useRegisterItem must be used within a <Command> component");
266
+ const generatedId = useId();
267
+ const id = itemId(options?.forceId ?? generatedId);
268
+ useInsertionEffect(() => {
269
+ const item = {
270
+ id,
271
+ value,
272
+ keywords: options?.keywords,
273
+ groupId: options?.groupId ? groupId(options.groupId) : void 0,
274
+ shortcut: options?.shortcut,
275
+ disabled: options?.disabled,
276
+ onSelect: options?.onSelect,
277
+ data: options?.data
278
+ };
279
+ ctx.machine.send({
280
+ type: "REGISTER_ITEM",
281
+ item
282
+ });
283
+ return () => {
284
+ ctx.machine.send({
285
+ type: "UNREGISTER_ITEM",
286
+ id
287
+ });
288
+ };
289
+ }, [
290
+ ctx.machine,
291
+ id,
292
+ value,
293
+ options?.groupId,
294
+ options?.data,
295
+ options?.disabled,
296
+ options?.keywords,
297
+ options?.onSelect,
298
+ options?.shortcut
299
+ ]);
300
+ return id;
301
+ }
302
+ /** Register a command group with the machine — auto-deregisters on unmount */
303
+ function useRegisterGroup(heading, options) {
304
+ const ctx = use(CommandContext);
305
+ if (!ctx) throw new Error("useRegisterGroup must be used within a <Command> component");
306
+ const generatedId = useId();
307
+ const id = groupId(options?.forceId ?? generatedId);
308
+ useInsertionEffect(() => {
309
+ const group = {
310
+ id,
311
+ heading,
312
+ priority: options?.priority
313
+ };
314
+ ctx.machine.send({
315
+ type: "REGISTER_GROUP",
316
+ group
317
+ });
318
+ return () => {
319
+ ctx.machine.send({
320
+ type: "UNREGISTER_GROUP",
321
+ id
322
+ });
323
+ };
324
+ }, [
325
+ ctx.machine,
326
+ id,
327
+ heading,
328
+ options?.priority
329
+ ]);
330
+ return id;
331
+ }
332
+ //#endregion
333
+ //#region src/react/group.tsx
334
+ function CommandGroup({ ref, children, heading, priority, forceId, ...props }) {
335
+ const ctx = use(CommandContext);
336
+ if (!ctx) throw new Error("Command.Group must be used within a <Command> component");
337
+ const headingId = useId();
338
+ const groupId = useRegisterGroup(heading, {
339
+ ...priority !== void 0 && { priority },
340
+ ...forceId !== void 0 && { forceId }
341
+ });
342
+ const groupItems = ctx.state.groupedIds.get(groupId);
343
+ const hasVisibleItems = groupItems !== void 0 && groupItems.length > 0;
344
+ const groupContext = useMemo(() => ({
345
+ groupId,
346
+ headingId
347
+ }), [groupId, headingId]);
348
+ if (!hasVisibleItems) return null;
349
+ return /* @__PURE__ */ jsx(CommandGroupContext, {
350
+ value: groupContext,
351
+ children: /* @__PURE__ */ jsxs("div", {
352
+ ref,
353
+ "data-command-group": "",
354
+ role: "group",
355
+ "aria-labelledby": heading ? headingId : void 0,
356
+ ...props,
357
+ children: [heading && /* @__PURE__ */ jsx("div", {
358
+ "data-command-group-heading": "",
359
+ id: headingId,
360
+ "aria-hidden": true,
361
+ children: heading
362
+ }), /* @__PURE__ */ jsx("div", {
363
+ "data-command-group-items": "",
364
+ children
365
+ })]
366
+ })
367
+ });
368
+ }
369
+ //#endregion
370
+ //#region src/react/highlight.tsx
371
+ function CommandHighlight({ text, ranges, highlightClassName }) {
372
+ return useMemo(() => {
373
+ if (ranges.length === 0) return text;
374
+ const sorted = Iterator.from(ranges).toArray().sort((a, b) => a[0] - b[0]);
375
+ const segments = [];
376
+ let lastEnd = 0;
377
+ Iterator.from(sorted).forEach(([start, end], i) => {
378
+ if (start > lastEnd) segments.push(text.slice(lastEnd, start));
379
+ segments.push(/* @__PURE__ */ jsx("mark", {
380
+ "data-command-highlight": "",
381
+ className: highlightClassName,
382
+ children: text.slice(start, end)
383
+ }, i));
384
+ lastEnd = end;
385
+ });
386
+ if (lastEnd < text.length) segments.push(text.slice(lastEnd));
387
+ return /* @__PURE__ */ jsx(Fragment, { children: segments });
388
+ }, [
389
+ text,
390
+ ranges,
391
+ highlightClassName
392
+ ]);
393
+ }
394
+ //#endregion
395
+ //#region src/react/input.tsx
396
+ function CommandInput({ ref, placeholder = "Search...", onValueChange, ...props }) {
397
+ const ctx = use(CommandContext);
398
+ if (!ctx) throw new Error("Command.Input must be used within a <Command> component");
399
+ const handleChange = useCallback((event) => {
400
+ const { value } = event.target;
401
+ ctx.updateSearch(value);
402
+ onValueChange?.(value);
403
+ }, [ctx.updateSearch, onValueChange]);
404
+ return /* @__PURE__ */ jsx("input", {
405
+ ref,
406
+ "data-command-input": "",
407
+ type: "text",
408
+ role: "combobox",
409
+ "aria-expanded": ctx.state.filteredCount > 0,
410
+ "aria-controls": ctx.listId,
411
+ "aria-activedescendant": ctx.state.activeId ?? void 0,
412
+ "aria-autocomplete": "list",
413
+ "aria-label": ctx.label,
414
+ id: ctx.inputId,
415
+ autoComplete: "off",
416
+ autoCorrect: "off",
417
+ spellCheck: false,
418
+ placeholder,
419
+ value: ctx.state.search,
420
+ onChange: handleChange,
421
+ ...props
422
+ });
423
+ }
424
+ //#endregion
425
+ //#region src/react/item.tsx
426
+ function CommandItem({ ref, children, value, keywords, onSelect, disabled = false, shortcut, forceId, ...props }) {
427
+ const ctx = use(CommandContext);
428
+ if (!ctx) throw new Error("Command.Item must be used within a <Command> component");
429
+ const id = useRegisterItem(value, {
430
+ ...keywords !== void 0 && { keywords },
431
+ ...shortcut !== void 0 && { shortcut },
432
+ disabled,
433
+ ...onSelect !== void 0 && { onSelect },
434
+ ...forceId !== void 0 && { forceId }
435
+ });
436
+ const isActive = ctx.state.activeId === id;
437
+ const isFiltered = ctx.filteredIdSet.has(id);
438
+ const handleSelect = useCallback(() => {
439
+ if (!disabled) ctx.machine.send({
440
+ type: "ITEM_SELECT",
441
+ id
442
+ });
443
+ }, [
444
+ ctx.machine,
445
+ id,
446
+ disabled
447
+ ]);
448
+ const activeIdRef = useRef(ctx.state.activeId);
449
+ activeIdRef.current = ctx.state.activeId;
450
+ const handlePointerMove = useCallback(() => {
451
+ if (!disabled && activeIdRef.current !== id) {
452
+ ctx.setOptimisticActiveId(id);
453
+ ctx.machine.send({
454
+ type: "ITEM_ACTIVATE",
455
+ id
456
+ });
457
+ }
458
+ }, [
459
+ ctx.machine,
460
+ ctx.setOptimisticActiveId,
461
+ id,
462
+ disabled
463
+ ]);
464
+ if (!isFiltered) return null;
465
+ return /* @__PURE__ */ jsx("div", {
466
+ ref,
467
+ "data-command-item": "",
468
+ "data-active": isActive ? "" : void 0,
469
+ "data-disabled": disabled ? "" : void 0,
470
+ "data-value": value,
471
+ id,
472
+ role: "option",
473
+ "aria-selected": isActive,
474
+ "aria-disabled": disabled || void 0,
475
+ "aria-keyshortcuts": shortcut ?? void 0,
476
+ onClick: handleSelect,
477
+ onPointerMove: handlePointerMove,
478
+ ...props,
479
+ children
480
+ });
481
+ }
482
+ //#endregion
483
+ //#region src/react/hooks/use-virtualizer.ts
484
+ function useVirtualizer(options) {
485
+ const { count, estimateSize, overscan = 8, scrollElement, enabled = true } = options;
486
+ const [scrollOffset, setScrollOffset] = useState(0);
487
+ const [containerHeight, setContainerHeight] = useState(0);
488
+ const measuredSizes = useRef(/* @__PURE__ */ new Map());
489
+ const [measureVersion, setMeasureVersion] = useState(0);
490
+ useEffect(() => {
491
+ if (!scrollElement || !enabled) return;
492
+ const observer = new ResizeObserver((entries) => {
493
+ const lastEntry = entries.at(-1);
494
+ if (lastEntry) setContainerHeight(lastEntry.contentRect.height);
495
+ });
496
+ observer.observe(scrollElement);
497
+ return () => {
498
+ observer.disconnect();
499
+ };
500
+ }, [scrollElement, enabled]);
501
+ useEffect(() => {
502
+ if (!scrollElement || !enabled) return;
503
+ const handleScroll = () => {
504
+ setScrollOffset(scrollElement.scrollTop);
505
+ };
506
+ scrollElement.addEventListener("scroll", handleScroll, { passive: true });
507
+ return () => {
508
+ scrollElement.removeEventListener("scroll", handleScroll);
509
+ };
510
+ }, [scrollElement, enabled]);
511
+ const getItemSize = useCallback((index) => measuredSizes.current.get(index) ?? estimateSize, [estimateSize]);
512
+ const totalSize = useMemo(() => {
513
+ if (!enabled) return 0;
514
+ const sizes = Iterator.from({ [Symbol.iterator]: function* () {
515
+ for (let i = 0; i < count; i++) yield getItemSize(i);
516
+ } }).toArray();
517
+ return Math.sumPrecise(sizes);
518
+ }, [
519
+ enabled,
520
+ count,
521
+ getItemSize,
522
+ measureVersion
523
+ ]);
524
+ return {
525
+ virtualItems: useMemo(() => {
526
+ if (!enabled || containerHeight <= 0) return [];
527
+ let offset = 0;
528
+ let startIdx = 0;
529
+ while (startIdx < count && offset + getItemSize(startIdx) < scrollOffset) {
530
+ offset += getItemSize(startIdx);
531
+ startIdx++;
532
+ }
533
+ startIdx = Math.max(0, startIdx - overscan);
534
+ const offsetSizes = Iterator.from({ [Symbol.iterator]: function* () {
535
+ for (let i = 0; i < startIdx; i++) yield getItemSize(i);
536
+ } }).toArray();
537
+ offset = Math.sumPrecise(offsetSizes);
538
+ const items = [];
539
+ let currentOffset = offset;
540
+ const endOffset = scrollOffset + containerHeight;
541
+ for (let i = startIdx; i < count; i++) {
542
+ const size = getItemSize(i);
543
+ items.push({
544
+ index: i,
545
+ start: currentOffset,
546
+ size,
547
+ key: i
548
+ });
549
+ currentOffset += size;
550
+ if (currentOffset > endOffset + overscan * estimateSize) break;
551
+ }
552
+ return items;
553
+ }, [
554
+ enabled,
555
+ containerHeight,
556
+ count,
557
+ scrollOffset,
558
+ overscan,
559
+ estimateSize,
560
+ getItemSize
561
+ ]),
562
+ totalSize,
563
+ scrollToIndex: useCallback((index) => {
564
+ if (!scrollElement || !enabled) return;
565
+ const clampedIndex = Math.min(index, count);
566
+ const sizes = Iterator.from({ [Symbol.iterator]: function* () {
567
+ for (let i = 0; i < clampedIndex; i++) yield getItemSize(i);
568
+ } }).toArray();
569
+ const targetOffset = Math.sumPrecise(sizes);
570
+ scrollElement.scrollTo({
571
+ top: targetOffset,
572
+ behavior: "smooth"
573
+ });
574
+ }, [
575
+ scrollElement,
576
+ enabled,
577
+ count,
578
+ getItemSize
579
+ ]),
580
+ measureElement: useCallback((element) => {
581
+ if (!element || !enabled) return;
582
+ const index = Number(element.dataset.index);
583
+ if (Number.isNaN(index)) return;
584
+ const measure = () => {
585
+ const height = element.getBoundingClientRect().height;
586
+ if (height > 0 && measuredSizes.current.get(index) !== height) {
587
+ measuredSizes.current.set(index, height);
588
+ setMeasureVersion((v) => v + 1);
589
+ }
590
+ };
591
+ if ("requestIdleCallback" in globalThis) requestIdleCallback(measure);
592
+ else setTimeout(measure, 0);
593
+ }, [enabled])
594
+ };
595
+ }
596
+ //#endregion
597
+ //#region src/react/list.tsx
598
+ function CommandList({ ref, children, virtualize, estimateSize = 44, overscan = 8, style, ...props }) {
599
+ const ctx = use(CommandContext);
600
+ if (!ctx) throw new Error("Command.List must be used within a <Command> component");
601
+ const innerRef = useRef(null);
602
+ const scrollRef = useRef(null);
603
+ const [height, setHeight] = useState(0);
604
+ const shouldVirtualize = virtualize ?? ctx.state.filteredCount > 100;
605
+ useEffect(() => {
606
+ const element = innerRef.current;
607
+ if (!element) return;
608
+ const observer = new ResizeObserver((entries) => {
609
+ const lastEntry = entries.at(-1);
610
+ if (lastEntry) setHeight(lastEntry.contentRect.height);
611
+ });
612
+ observer.observe(element);
613
+ return () => {
614
+ observer.disconnect();
615
+ };
616
+ }, []);
617
+ const virtualizer = useVirtualizer({
618
+ count: ctx.state.filteredCount,
619
+ estimateSize,
620
+ overscan,
621
+ scrollElement: shouldVirtualize ? scrollRef.current : null,
622
+ enabled: shouldVirtualize
623
+ });
624
+ const prevActiveId = useRef(ctx.state.activeId);
625
+ useEffect(() => {
626
+ if (ctx.state.activeId && ctx.state.activeId !== prevActiveId.current) {
627
+ prevActiveId.current = ctx.state.activeId;
628
+ if (shouldVirtualize) {
629
+ const idx = ctx.state.filteredIds.indexOf(ctx.state.activeId);
630
+ if (idx >= 0) virtualizer.scrollToIndex(idx);
631
+ } else (scrollRef.current?.querySelector(`[id="${CSS.escape(ctx.state.activeId)}"]`))?.scrollIntoView({
632
+ block: "nearest",
633
+ behavior: "smooth"
634
+ });
635
+ }
636
+ }, [
637
+ ctx.state.activeId,
638
+ ctx.state.filteredIds,
639
+ shouldVirtualize,
640
+ virtualizer
641
+ ]);
642
+ const mergedStyle = {
643
+ ...style,
644
+ "--command-list-height": `${height.toFixed(1)}px`
645
+ };
646
+ return /* @__PURE__ */ jsxs("div", {
647
+ ref: useCallback((el) => {
648
+ scrollRef.current = el;
649
+ if (typeof ref === "function") ref(el);
650
+ else if (ref) ref.current = el;
651
+ }, [ref]),
652
+ "data-command-list": "",
653
+ role: "listbox",
654
+ "aria-label": ctx.label,
655
+ "aria-busy": ctx.state.loading,
656
+ id: ctx.listId,
657
+ style: mergedStyle,
658
+ ...props,
659
+ children: [shouldVirtualize ? /* @__PURE__ */ jsxs("div", {
660
+ "data-command-list-virtual": "",
661
+ style: {
662
+ height: `${virtualizer.totalSize}px`,
663
+ position: "relative"
664
+ },
665
+ children: [virtualizer.virtualItems.map((vItem) => /* @__PURE__ */ jsx("div", {
666
+ "data-command-virtual-item": "",
667
+ "data-index": vItem.index,
668
+ ref: virtualizer.measureElement,
669
+ style: {
670
+ position: "absolute",
671
+ top: 0,
672
+ left: 0,
673
+ width: "100%",
674
+ translate: `0 ${vItem.start}px`
675
+ }
676
+ }, vItem.key)), children]
677
+ }) : /* @__PURE__ */ jsx("div", {
678
+ ref: innerRef,
679
+ "data-command-list-inner": "",
680
+ children
681
+ }), /* @__PURE__ */ jsxs("div", {
682
+ "aria-live": "polite",
683
+ "aria-atomic": true,
684
+ role: "status",
685
+ "data-command-aria-live": "",
686
+ className: "sr-only",
687
+ children: [
688
+ ctx.state.filteredCount,
689
+ " result",
690
+ ctx.state.filteredCount !== 1 ? "s" : "",
691
+ " available."
692
+ ]
693
+ })]
694
+ });
695
+ }
696
+ //#endregion
697
+ //#region src/react/loading.tsx
698
+ function CommandLoading({ ref, children, loading: loadingOverride, ...props }) {
699
+ const ctx = use(CommandContext);
700
+ if (!ctx) throw new Error("Command.Loading must be used within a <Command> component");
701
+ if (!(loadingOverride ?? ctx.state.loading ?? ctx.isPending)) return null;
702
+ return /* @__PURE__ */ jsx("div", {
703
+ ref,
704
+ "data-command-loading": "",
705
+ "aria-busy": true,
706
+ "aria-live": "assertive",
707
+ role: "status",
708
+ ...props,
709
+ children
710
+ });
711
+ }
712
+ //#endregion
713
+ //#region src/react/page.tsx
714
+ function CommandPage({ id, children }) {
715
+ const ctx = use(CommandContext);
716
+ if (!ctx) throw new Error("Command.Page must be used within a <Command> component");
717
+ if (ctx.state.page !== id) return null;
718
+ return /* @__PURE__ */ jsx("div", {
719
+ "data-command-page": "",
720
+ "data-command-page-id": id,
721
+ children
722
+ });
723
+ }
724
+ //#endregion
725
+ //#region src/react/separator.tsx
726
+ function CommandSeparator({ ref, ...props }) {
727
+ return /* @__PURE__ */ jsx("div", {
728
+ ref,
729
+ "data-command-separator": "",
730
+ role: "separator",
731
+ ...props
732
+ });
733
+ }
734
+ //#endregion
735
+ //#region src/react/shortcut.tsx
736
+ function CommandShortcut({ ref, shortcut, children, ...props }) {
737
+ const displayText = useMemo(() => shortcut ? formatShortcut(parseShortcut(shortcut)) : children, [shortcut, children]);
738
+ return /* @__PURE__ */ jsx("kbd", {
739
+ ref,
740
+ "data-command-shortcut": "",
741
+ ...props,
742
+ children: displayText
743
+ });
744
+ }
745
+ //#endregion
746
+ //#region src/react/command.tsx
747
+ function CommandRoot({ children, className, label = "Command palette", ...machineOptions }) {
748
+ const rootId = useId();
749
+ const listId = `${rootId}-list`;
750
+ const inputId = `${rootId}-input`;
751
+ const rootRef = useRef(null);
752
+ const machineRef = useRef(null);
753
+ if (machineRef.current === null) machineRef.current = createCommandMachine(machineOptions);
754
+ const machine = machineRef.current;
755
+ const { state, isPending, updateSearch, setOptimisticActiveId, filteredIdSet, id } = useCommand(machine);
756
+ useEffect(() => {
757
+ return () => {
758
+ machineRef.current?.[Symbol.dispose]();
759
+ machineRef.current = null;
760
+ };
761
+ }, []);
762
+ const handleKeyDown = useCallback(createKeydownHandler(machine, () => machine.getState()), []);
763
+ useEffect(() => {
764
+ document.addEventListener("keydown", handleKeyDown);
765
+ return () => {
766
+ document.removeEventListener("keydown", handleKeyDown);
767
+ };
768
+ }, [handleKeyDown]);
769
+ return /* @__PURE__ */ jsx(CommandContext, {
770
+ value: useMemo(() => ({
771
+ machine,
772
+ state,
773
+ isPending,
774
+ updateSearch,
775
+ setOptimisticActiveId,
776
+ filteredIdSet,
777
+ rootId: id,
778
+ listId,
779
+ inputId,
780
+ label
781
+ }), [
782
+ machine,
783
+ state,
784
+ isPending,
785
+ updateSearch,
786
+ setOptimisticActiveId,
787
+ filteredIdSet,
788
+ id,
789
+ listId,
790
+ inputId,
791
+ label
792
+ ]),
793
+ children: /* @__PURE__ */ jsx("div", {
794
+ ref: rootRef,
795
+ "data-command-root": "",
796
+ "data-command-state": state.open ? "open" : "closed",
797
+ className,
798
+ role: "search",
799
+ "aria-label": label,
800
+ children
801
+ })
802
+ });
803
+ }
804
+ /** Compound component namespace */
805
+ const Command = Object.assign(CommandRoot, {
806
+ Input: CommandInput,
807
+ List: CommandList,
808
+ Item: CommandItem,
809
+ Group: CommandGroup,
810
+ Empty: CommandEmpty,
811
+ Loading: CommandLoading,
812
+ Separator: CommandSeparator,
813
+ Dialog: CommandDialog,
814
+ Highlight: CommandHighlight,
815
+ Shortcut: CommandShortcut,
816
+ Badge: CommandBadge,
817
+ Page: CommandPage,
818
+ AsyncItems: CommandAsyncItems,
819
+ Activity: CommandActivity
820
+ });
821
+ //#endregion
822
+ //#region src/react/error-boundary.tsx
823
+ /** Initial state — no error captured */
824
+ const INITIAL_STATE = { error: null };
825
+ var CommandErrorBoundary = class extends Component {
826
+ state = INITIAL_STATE;
827
+ static getDerivedStateFromError(error) {
828
+ return { error };
829
+ }
830
+ componentDidCatch(error, errorInfo) {
831
+ this.props.onError?.(error, errorInfo);
832
+ }
833
+ render() {
834
+ if (this.state.error) {
835
+ const { fallback } = this.props;
836
+ if (typeof fallback === "function") return fallback(this.state.error);
837
+ if (fallback) return fallback;
838
+ return null;
839
+ }
840
+ return this.props.children;
841
+ }
842
+ };
843
+ //#endregion
844
+ //#region src/react/hooks/use-command-state.ts
845
+ function useCommandState(selector) {
846
+ const ctx = use(CommandContext);
847
+ if (!ctx) throw new Error("useCommandState must be used within a <Command> component");
848
+ const getSnapshot = useCallback(() => selector ? selector(ctx.machine.getState()) : ctx.machine.getState(), [ctx.machine, selector]);
849
+ return useSyncExternalStore(ctx.machine.subscribe, getSnapshot, getSnapshot);
850
+ }
851
+ //#endregion
852
+ //#region src/react/hooks/use-devtools.ts
853
+ /** Branded key for the global devtools instance map */
854
+ const DEVTOOLS_KEY = "__CRIMSON_COMMAND_DEVTOOLS__";
855
+ /**
856
+ * Exposes command palette internals to browser devtools.
857
+ * Only active in development — completely tree-shaken in production builds.
858
+ *
859
+ * @example
860
+ * ```tsx
861
+ * function MyPalette() {
862
+ * useCommandDevtools('main');
863
+ * return <Command>...</Command>;
864
+ * }
865
+ * ```
866
+ */
867
+ function useCommandDevtools(label = "default") {
868
+ if (!__DEV__) return;
869
+ const ctx = use(CommandContext);
870
+ const stateRef = useRef(null);
871
+ useEffect(() => {
872
+ if (!ctx) return;
873
+ const update = () => {
874
+ const s = ctx.state;
875
+ const devState = {
876
+ registeredItems: s.filteredCount,
877
+ filteredItems: s.filteredIds.length,
878
+ searchQuery: s.search,
879
+ activeId: s.activeId,
880
+ page: s.page,
881
+ pageStack: s.pageStack,
882
+ open: s.open,
883
+ loading: s.loading
884
+ };
885
+ stateRef.current = devState;
886
+ const globals = globalThis;
887
+ if (!globals[DEVTOOLS_KEY]) globals[DEVTOOLS_KEY] = /* @__PURE__ */ new Map();
888
+ globals[DEVTOOLS_KEY].set(label, devState);
889
+ globalThis.dispatchEvent?.(new CustomEvent("crimson-command-devtools", { detail: {
890
+ label,
891
+ state: devState
892
+ } }));
893
+ };
894
+ const unsub = ctx.machine.subscribe(update);
895
+ update();
896
+ return () => {
897
+ unsub();
898
+ globalThis[DEVTOOLS_KEY]?.delete(label);
899
+ };
900
+ }, [ctx, label]);
901
+ }
902
+ //#endregion
903
+ export { Command, CommandActivity, CommandAsyncItems, CommandBadge, CommandDialog, CommandEmpty, CommandErrorBoundary, CommandGroup, CommandHighlight, CommandInput, CommandItem, CommandList, CommandLoading, CommandPage, CommandSeparator, CommandShortcut, createKeydownHandler, useCommand, useCommandDevtools, useCommandState, useRegisterGroup, useRegisterItem, useVirtualizer };
904
+
905
+ //# sourceMappingURL=index.mjs.map