modern-cmdk 1.2.2 → 1.3.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.
@@ -4,10 +4,10 @@ import { Component, Suspense, createContext, use, useCallback, useEffect, useId,
4
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  import { Dialog } from "radix-ui";
6
6
  import { createPortal } from "react-dom";
7
- //#region src/react/activity.tsx
7
+ // src/react/activity.tsx
8
8
  const ActivityComponent = "Activity" in React ? React.Activity : null;
9
9
  /**
10
- * Wraps content in React 19.3.0-canary Activity API.
10
+ * Wraps content in React's <Activity> API (React 19.2+).
11
11
  * When mode="hidden", the component tree is preserved in memory but not rendered.
12
12
  * Falls back to conditional rendering if Activity is unavailable.
13
13
  */
@@ -19,8 +19,7 @@ function CommandActivity({ mode, children }) {
19
19
  if (mode === "hidden") return null;
20
20
  return /* @__PURE__ */ jsx(Fragment, { children });
21
21
  }
22
- //#endregion
23
- //#region src/react/context.ts
22
+ // src/react/context.ts
24
23
  const CommandStableContext = createContext(null);
25
24
  CommandStableContext.displayName = "CommandStableContext";
26
25
  const CommandStateContext = createContext(null);
@@ -42,8 +41,7 @@ CommandGroupContext.displayName = "CommandGroupContext";
42
41
  /** Context for the status container rendered outside the listbox — used by Command.Empty */
43
42
  const CommandListStatusContext = createContext(null);
44
43
  CommandListStatusContext.displayName = "CommandListStatusContext";
45
- //#endregion
46
- //#region src/react/async-items.tsx
44
+ // src/react/async-items.tsx
47
45
  /** Inner component that suspends via use() until items resolve */
48
46
  function AsyncItemsInner({ items: itemsPromise, children }) {
49
47
  const stable = useStableContext("Command.AsyncItems");
@@ -68,8 +66,7 @@ function CommandAsyncItems({ items, fallback, children }) {
68
66
  })
69
67
  });
70
68
  }
71
- //#endregion
72
- //#region src/react/badge.tsx
69
+ // src/react/badge.tsx
73
70
  /** Inline badge for categorization or status indicators on command items. */
74
71
  function CommandBadge({ ref, children, ...props }) {
75
72
  return /* @__PURE__ */ jsx("span", {
@@ -79,8 +76,7 @@ function CommandBadge({ ref, children, ...props }) {
79
76
  children
80
77
  });
81
78
  }
82
- //#endregion
83
- //#region src/core/utils/string-wellformed.ts
79
+ // src/core/utils/string-wellformed.ts
84
80
  /**
85
81
  * Regex matching lone surrogates (unpaired high or low surrogates).
86
82
  * A lone high surrogate (U+D800–U+DBFF) not followed by a low surrogate,
@@ -97,8 +93,7 @@ const REPLACEMENT_CHAR = "�";
97
93
  function ensureWellFormed(str) {
98
94
  return str.replace(LONE_SURROGATE_RE, REPLACEMENT_CHAR);
99
95
  }
100
- //#endregion
101
- //#region src/core/types.ts
96
+ // src/core/types.ts
102
97
  /** Create a branded {@link ItemId} from a plain string. Ensures well-formed Unicode. */
103
98
  function itemId(id) {
104
99
  return ensureWellFormed(id);
@@ -130,8 +125,7 @@ function createInitialState(options) {
130
125
  lastUpdated: Date.now()
131
126
  };
132
127
  }
133
- //#endregion
134
- //#region src/core/frecency/memory-storage.ts
128
+ // src/core/frecency/memory-storage.ts
135
129
  /**
136
130
  * In-memory implementation of FrecencyStorage using a Map.
137
131
  * Data does not persist across page reloads. Suitable for ephemeral
@@ -145,7 +139,7 @@ var MemoryFrecencyStorage = class {
145
139
  save(namespace, data) {
146
140
  this.#store.set(namespace, data);
147
141
  }
148
- /** Serialize all stored data — Iterator Helpers pipeline (ES2026) */
142
+ /** Serialize all stored data — Iterator Helpers pipeline (ES2025) */
149
143
  toJSON() {
150
144
  return Object.fromEntries(this.#store.entries().map(([namespace, data]) => [namespace, { records: data.records.entries().toArray() }]));
151
145
  }
@@ -153,11 +147,10 @@ var MemoryFrecencyStorage = class {
153
147
  this.#store.clear();
154
148
  }
155
149
  };
156
- //#endregion
157
- //#region src/core/frecency/index.ts
150
+ // src/core/frecency/index.ts
158
151
  /** Shared empty bonus map — avoids allocation when no frecency records exist */
159
152
  const EMPTY_BONUS_MAP = /* @__PURE__ */ new Map();
160
- /** Pre-computed bucket boundaries in hours — avoids Duration.total() per call */
153
+ /** Bucket boundaries as hour constants — no per-call unit conversion */
161
154
  const BUCKET_HOURS = {
162
155
  hour: 1,
163
156
  day: 24,
@@ -167,7 +160,7 @@ const BUCKET_HOURS = {
167
160
  /**
168
161
  * Computes a frecency bonus for an item based on usage frequency and recency.
169
162
  * Uses hour/day/week/month bucket boundaries with
170
- * configurable exponential decay weights per time bucket.
163
+ * configurable step weights per recency bucket (hour/day/week/month/older).
171
164
  */
172
165
  function computeFrecencyBonus(history, now = Date.now(), config = DEFAULT_FRECENCY_DECAY) {
173
166
  const hours = (now - history.lastUsed) / 36e5;
@@ -256,8 +249,7 @@ var FrecencyEngine = class {
256
249
  this.#storage[Symbol.dispose]();
257
250
  }
258
251
  };
259
- //#endregion
260
- //#region src/core/utils/group-by.ts
252
+ // src/core/utils/group-by.ts
261
253
  /**
262
254
  * Groups items from an iterable into a Map keyed by the result of `keyFn`.
263
255
  * Cross-browser replacement for `Map.groupBy()`.
@@ -272,8 +264,7 @@ function mapGroupBy(items, keyFn) {
272
264
  }
273
265
  return map;
274
266
  }
275
- //#endregion
276
- //#region src/core/keyboard/matcher.ts
267
+ // src/core/keyboard/matcher.ts
277
268
  /**
278
269
  * Tests whether a KeyboardEvent exactly matches a ParsedShortcut,
279
270
  * comparing all four modifier keys (meta, ctrl, shift, alt) and the primary key.
@@ -294,8 +285,7 @@ function detectConflicts(shortcuts) {
294
285
  const grouped = mapGroupBy(shortcuts, (s) => s.normalized);
295
286
  return new Map(grouped.entries().filter(([, group]) => group.length > 1));
296
287
  }
297
- //#endregion
298
- //#region src/core/keyboard/parser.ts
288
+ // src/core/keyboard/parser.ts
299
289
  const isMac = typeof navigator !== "undefined" && (navigator.userAgentData?.platform === "macOS" || /Mac|iPod|iPhone|iPad/.test(navigator.platform));
300
290
  /** Modifier aliases — cross-platform Mod resolution */
301
291
  const MODIFIER_MAP = {
@@ -368,12 +358,11 @@ function formatShortcut(parsed) {
368
358
  parts.push(displayKey);
369
359
  return isMac ? parts.join("") : parts.join("+");
370
360
  }
371
- //#endregion
372
- //#region src/core/keyboard/index.ts
361
+ // src/core/keyboard/index.ts
373
362
  /**
374
363
  * Manages keyboard shortcut bindings with global keydown listening.
375
364
  * Supports `using` for automatic cleanup of both individual bindings and the
376
- * global document listener. Detects conflicts via Map.groupBy (ES2026).
365
+ * global document listener. Detects conflicts with the local mapGroupBy helper (core/utils/group-by.ts).
377
366
  */
378
367
  var KeyboardShortcutRegistry = class {
379
368
  #bindings = /* @__PURE__ */ new Map();
@@ -440,8 +429,7 @@ var KeyboardShortcutRegistry = class {
440
429
  this.#bindings.clear();
441
430
  }
442
431
  };
443
- //#endregion
444
- //#region src/core/utils/set-ops.ts
432
+ // src/core/utils/set-ops.ts
445
433
  /** Returns a new Set containing elements present in both `a` and `b`. */
446
434
  function setIntersection(a, b) {
447
435
  const result = /* @__PURE__ */ new Set();
@@ -484,8 +472,7 @@ function setSymmetricDifference(a, b) {
484
472
  else result.add(item);
485
473
  return result;
486
474
  }
487
- //#endregion
488
- //#region src/core/registry.ts
475
+ // src/core/registry.ts
489
476
  /**
490
477
  * Manages registration and lookup of command items and groups.
491
478
  * Uses set operation helpers (union/intersection/difference) for efficient ID operations.
@@ -646,8 +633,23 @@ var CommandRegistry = class {
646
633
  this.clear();
647
634
  }
648
635
  };
649
- //#endregion
650
- //#region src/core/search/default-scorer.ts
636
+ // src/core/utils/env.ts
637
+ /**
638
+ * `true` unless `process.env.NODE_ENV` is `'production'`.
639
+ *
640
+ * Consumer bundlers (Vite, webpack, Next, esbuild) replace `process.env.NODE_ENV` statically,
641
+ * so the dev-only branches guarded by this flag fold away in production builds. A browser
642
+ * running the unbundled module throws a ReferenceError on the bare `process` reference;
643
+ * the catch turns that into the production value (`false`), so dev-only code stays off.
644
+ */
645
+ const isDev = (() => {
646
+ try {
647
+ return process.env.NODE_ENV !== "production";
648
+ } catch {
649
+ return false;
650
+ }
651
+ })();
652
+ // src/core/search/default-scorer.ts
651
653
  function isWordSeparator(code) {
652
654
  return code === 32 || code === 9 || code === 45 || code === 95 || code === 46 || code === 47 || code === 92;
653
655
  }
@@ -769,18 +771,16 @@ function scoreFuzzy(query, qLen, lowerTarget, tLen) {
769
771
  matches
770
772
  };
771
773
  }
772
- //#endregion
773
- //#region src/core/search/index.ts
774
- const __DEV__$1 = globalThis.process ? globalThis.process.env.NODE_ENV !== "production" : false;
774
+ // src/core/search/index.ts
775
775
  let leakRegistry;
776
- if (__DEV__$1) leakRegistry = new FinalizationRegistry((label) => {
776
+ if (isDev && typeof window !== "undefined") leakRegistry = new FinalizationRegistry((label) => {
777
777
  console.warn(`[modern-cmdk] ${label} was garbage collected without being disposed. Use \`using\` or call [Symbol.dispose]() to prevent leaks.`);
778
778
  });
779
779
  /**
780
780
  * Creates a search engine with pluggable scorer and incremental filtering.
781
781
  * When the query extends the previous one, only prior matches are re-scored.
782
782
  * Pre-caches lowercase at index time — zero toLowerCase() calls during search.
783
- * Uses Iterator Helpers (ES2026) for result pipelines and Set.difference for pruning.
783
+ * Uses Iterator Helpers (ES2025) for result pipelines.
784
784
  */
785
785
  function createSearchEngine(options) {
786
786
  const customScorer = options?.scorer;
@@ -789,7 +789,7 @@ function createSearchEngine(options) {
789
789
  let previousQuery = "";
790
790
  let previousResults = /* @__PURE__ */ new Set();
791
791
  const sentinel = {};
792
- if (__DEV__$1 && leakRegistry) leakRegistry.register(sentinel, "SearchEngine", sentinel);
792
+ if (leakRegistry) leakRegistry.register(sentinel, "SearchEngine", sentinel);
793
793
  return {
794
794
  index(items) {
795
795
  for (const item of items) indexedEntries.set(item.id, {
@@ -839,12 +839,11 @@ function createSearchEngine(options) {
839
839
  [Symbol.dispose]() {
840
840
  indexedEntries.clear();
841
841
  previousResults.clear();
842
- if (__DEV__$1 && leakRegistry) leakRegistry.unregister(sentinel);
842
+ if (leakRegistry) leakRegistry.unregister(sentinel);
843
843
  }
844
844
  };
845
845
  }
846
- //#endregion
847
- //#region src/core/utils/event-emitter.ts
846
+ // src/core/utils/event-emitter.ts
848
847
  /**
849
848
  * Type-safe event emitter with strong references and Disposable subscriptions.
850
849
  * Returns Disposable from `on()` for automatic unsubscription via `using`.
@@ -887,13 +886,13 @@ var TypedEmitter = class {
887
886
  this.#listeners.clear();
888
887
  }
889
888
  };
890
- //#endregion
891
- //#region src/core/utils/scheduler.ts
889
+ // src/core/utils/scheduler.ts
892
890
  /** Whether we're in a browser environment with rAF available */
893
891
  const hasRAF = typeof requestAnimationFrame === "function";
894
- /** Feature detection for cutting-edge scheduling APIs */
892
+ /** Feature detection for cutting-edge scheduling APIs, read through globalThis: typeof guards only a bare
893
+ * identifier, so `typeof navigator?.scheduling` would throw where navigator is undeclared (runtimes without one). */
895
894
  const canYield = typeof globalThis.scheduler?.yield === "function";
896
- const hasInputPending = typeof navigator?.scheduling?.isInputPending === "function";
895
+ const hasInputPending = typeof globalThis.navigator?.scheduling?.isInputPending === "function";
897
896
  /**
898
897
  * Creates a batched update scheduler. Coalesces rapid state updates into a single
899
898
  * execution pass using rAF (browser) or queueMicrotask (Node.js).
@@ -949,8 +948,7 @@ function createScheduler() {
949
948
  }
950
949
  };
951
950
  }
952
- //#endregion
953
- //#region src/core/machine.ts
951
+ // src/core/machine.ts
954
952
  /**
955
953
  * Creates a headless command palette state machine with search, frecency,
956
954
  * and keyboard shortcut support. Implements Disposable for `using` cleanup.
@@ -1216,9 +1214,8 @@ function createCommandMachine(options = {}) {
1216
1214
  }
1217
1215
  };
1218
1216
  }
1219
- //#endregion
1220
- //#region src/react/hooks/use-command.ts
1221
- /** Core hook that binds a CommandMachine to React via useSyncExternalStore, useTransition, and useOptimistic. */
1217
+ // src/react/hooks/use-command.ts
1218
+ /** Core hook that binds a CommandMachine to React via useSyncExternalStore, useOptimistic, and a transition for optimistic active-item updates. */
1222
1219
  function useCommand(machine) {
1223
1220
  const id = useId();
1224
1221
  const [isPending, startTransition] = useTransition();
@@ -1231,11 +1228,9 @@ function useCommand(machine) {
1231
1228
  });
1232
1229
  }, [rawSetOptimisticActiveId]);
1233
1230
  const updateSearch = useCallback((query) => {
1234
- startTransition(() => {
1235
- machine.send({
1236
- type: "SEARCH_CHANGE",
1237
- query
1238
- });
1231
+ machine.send({
1232
+ type: "SEARCH_CHANGE",
1233
+ query
1239
1234
  });
1240
1235
  }, [machine]);
1241
1236
  return {
@@ -1250,8 +1245,7 @@ function useCommand(machine) {
1250
1245
  id
1251
1246
  };
1252
1247
  }
1253
- //#endregion
1254
- //#region src/react/hooks/use-keyboard.ts
1248
+ // src/react/hooks/use-keyboard.ts
1255
1249
  /** Navigation key to machine direction mapping */
1256
1250
  const NAVIGATION_KEYS = {
1257
1251
  ArrowDown: "next",
@@ -1295,8 +1289,7 @@ function createKeydownHandler(machine, getState) {
1295
1289
  }
1296
1290
  };
1297
1291
  }
1298
- //#endregion
1299
- //#region src/react/hooks/use-command-setup.ts
1292
+ // src/react/hooks/use-command-setup.ts
1300
1293
  /**
1301
1294
  * Shared setup for Command and Command.Dialog — creates machine once,
1302
1295
  * subscribes via useSyncExternalStore, memoizes context values, and
@@ -1356,8 +1349,7 @@ function useCommandSetup(machineFactory, label) {
1356
1349
  ])
1357
1350
  };
1358
1351
  }
1359
- //#endregion
1360
- //#region src/react/dialog.tsx
1352
+ // src/react/dialog.tsx
1361
1353
  /** Dialog variant that wraps the command palette in a Radix UI Dialog with focus trap and overlay. */
1362
1354
  function CommandDialog({ children, className, overlayClassName, contentClassName, label = "Command palette", description = "Type a command or search...", open: controlledOpen, onOpenChange, container, ...machineOptions }) {
1363
1355
  const onOpenChangeRef = useRef(onOpenChange);
@@ -1431,8 +1423,7 @@ function CommandDialog({ children, className, overlayClassName, contentClassName
1431
1423
  })
1432
1424
  });
1433
1425
  }
1434
- //#endregion
1435
- //#region src/react/empty.tsx
1426
+ // src/react/empty.tsx
1436
1427
  /** Rendered when no items match the current search query. */
1437
1428
  function CommandEmpty({ ref, children, ...props }) {
1438
1429
  const stateCtx = useStateContext("Command.Empty");
@@ -1449,8 +1440,7 @@ function CommandEmpty({ ref, children, ...props }) {
1449
1440
  if (statusContainerRef?.current) return createPortal(content, statusContainerRef.current);
1450
1441
  return content;
1451
1442
  }
1452
- //#endregion
1453
- //#region src/react/hooks/use-register.ts
1443
+ // src/react/hooks/use-register.ts
1454
1444
  /** Register a command item with the machine — auto-deregisters on unmount */
1455
1445
  function useRegisterItem(value, options) {
1456
1446
  const stable = useStableContext("useRegisterItem");
@@ -1522,8 +1512,7 @@ function useRegisterGroup(heading, options) {
1522
1512
  ]);
1523
1513
  return id;
1524
1514
  }
1525
- //#endregion
1526
- //#region src/react/group.tsx
1515
+ // src/react/group.tsx
1527
1516
  /** Module-level constant — avoids allocating a new object on every render of hidden groups */
1528
1517
  const HIDDEN_STYLE = { display: "none" };
1529
1518
  /** Visual grouping container with an optional heading that hides when no items match. */
@@ -1567,8 +1556,7 @@ function CommandGroup({ ref, children, heading, priority, forceId, ...props }) {
1567
1556
  })
1568
1557
  });
1569
1558
  }
1570
- //#endregion
1571
- //#region src/react/highlight.tsx
1559
+ // src/react/highlight.tsx
1572
1560
  /** Renders search match highlighting by splitting text at scored match ranges. */
1573
1561
  function CommandHighlight({ text, ranges, highlightClassName }) {
1574
1562
  return useMemo(() => {
@@ -1593,14 +1581,27 @@ function CommandHighlight({ text, ranges, highlightClassName }) {
1593
1581
  highlightClassName
1594
1582
  ]);
1595
1583
  }
1596
- //#endregion
1597
- //#region src/react/input.tsx
1584
+ // src/react/input.tsx
1598
1585
  /** Combobox input that drives the command palette search state. */
1599
1586
  function CommandInput({ ref, placeholder = "Search...", onValueChange, ...props }) {
1600
1587
  const stable = useStableContext("Command.Input");
1601
1588
  const stateCtx = useStateContext("Command.Input");
1589
+ const machineState = useSyncExternalStore(stable.machine.subscribe, stable.machine.getState, stable.machine.getState);
1590
+ const [draft, setDraft] = useState(machineState.search);
1591
+ const inFlight = useRef([]);
1592
+ useLayoutEffect(() => {
1593
+ const { search } = machineState;
1594
+ const echoed = inFlight.current.lastIndexOf(search);
1595
+ if (echoed !== -1) inFlight.current.splice(0, echoed + 1);
1596
+ else {
1597
+ inFlight.current = [];
1598
+ setDraft(search);
1599
+ }
1600
+ }, [machineState]);
1602
1601
  const handleChange = useCallback((event) => {
1603
1602
  const { value } = event.target;
1603
+ inFlight.current.push(value);
1604
+ setDraft(value);
1604
1605
  stable.updateSearch(value);
1605
1606
  onValueChange?.(value);
1606
1607
  }, [stable.updateSearch, onValueChange]);
@@ -1619,13 +1620,12 @@ function CommandInput({ ref, placeholder = "Search...", onValueChange, ...props
1619
1620
  autoCorrect: "off",
1620
1621
  spellCheck: false,
1621
1622
  placeholder,
1622
- value: stateCtx.state.search,
1623
+ value: draft,
1623
1624
  onChange: handleChange,
1624
1625
  ...props
1625
1626
  });
1626
1627
  }
1627
- //#endregion
1628
- //#region src/react/item.tsx
1628
+ // src/react/item.tsx
1629
1629
  /** Individual selectable item in the command list with keyboard and pointer activation. */
1630
1630
  function CommandItem({ ref, children, value, keywords, onSelect, disabled = false, shortcut, forceId, ...props }) {
1631
1631
  const stable = useStableContext("Command.Item");
@@ -1695,8 +1695,7 @@ function CommandItem({ ref, children, value, keywords, onSelect, disabled = fals
1695
1695
  children
1696
1696
  });
1697
1697
  }
1698
- //#endregion
1699
- //#region src/react/hooks/use-virtualizer.ts
1698
+ // src/react/hooks/use-virtualizer.ts
1700
1699
  /** Lightweight virtualizer for large item lists using ResizeObserver and scroll tracking. */
1701
1700
  function useVirtualizer(options) {
1702
1701
  const { count, estimateSize, overscan = 8, scrollElement, enabled = true } = options;
@@ -1800,8 +1799,7 @@ function useVirtualizer(options) {
1800
1799
  }, [enabled])
1801
1800
  };
1802
1801
  }
1803
- //#endregion
1804
- //#region src/react/list.tsx
1802
+ // src/react/list.tsx
1805
1803
  /** Scrollable list container with auto-virtualization and aria-live announcements. */
1806
1804
  function CommandList({ ref, children, virtualize, estimateSize = 44, overscan = 8, announceResults, style, ...props }) {
1807
1805
  const stable = useStableContext("Command.List");
@@ -1941,8 +1939,7 @@ function CommandList({ ref, children, virtualize, estimateSize = 44, overscan =
1941
1939
  ]
1942
1940
  });
1943
1941
  }
1944
- //#endregion
1945
- //#region src/react/loading.tsx
1942
+ // src/react/loading.tsx
1946
1943
  /** Shown during async operations, with configurable loading state override. */
1947
1944
  function CommandLoading({ ref, children, loading: loadingOverride, ...props }) {
1948
1945
  const stateCtx = useStateContext("Command.Loading");
@@ -1957,8 +1954,7 @@ function CommandLoading({ ref, children, loading: loadingOverride, ...props }) {
1957
1954
  children
1958
1955
  });
1959
1956
  }
1960
- //#endregion
1961
- //#region src/react/page.tsx
1957
+ // src/react/page.tsx
1962
1958
  /** Page navigation container that only renders when its ID matches the active page. */
1963
1959
  function CommandPage({ id, children }) {
1964
1960
  if (useStateContext("Command.Page").state.page !== id) return null;
@@ -1968,8 +1964,7 @@ function CommandPage({ id, children }) {
1968
1964
  children
1969
1965
  });
1970
1966
  }
1971
- //#endregion
1972
- //#region src/react/separator.tsx
1967
+ // src/react/separator.tsx
1973
1968
  /** Visual divider between items or groups, rendered as a presentational element. */
1974
1969
  function CommandSeparator({ ref, ...props }) {
1975
1970
  return /* @__PURE__ */ jsx("div", {
@@ -1979,8 +1974,7 @@ function CommandSeparator({ ref, ...props }) {
1979
1974
  ...props
1980
1975
  });
1981
1976
  }
1982
- //#endregion
1983
- //#region src/react/shortcut.tsx
1977
+ // src/react/shortcut.tsx
1984
1978
  /** Keyboard shortcut display with platform-aware formatting (e.g., Cmd vs Ctrl). */
1985
1979
  function CommandShortcut({ ref, shortcut, children, ...props }) {
1986
1980
  const formattedShortcut = useMemo(() => shortcut ? formatShortcut(parseShortcut(shortcut)) : null, [shortcut]);
@@ -1991,8 +1985,7 @@ function CommandShortcut({ ref, shortcut, children, ...props }) {
1991
1985
  children: formattedShortcut ?? children
1992
1986
  });
1993
1987
  }
1994
- //#endregion
1995
- //#region src/react/command.tsx
1988
+ // src/react/command.tsx
1996
1989
  /** Root compound component that provides the command state machine context to all children. */
1997
1990
  function CommandRoot({ children, className, label = "Command palette", ...machineOptions }) {
1998
1991
  const rootRef = useRef(null);
@@ -2041,8 +2034,7 @@ const Command = Object.assign(CommandRoot, {
2041
2034
  AsyncItems: CommandAsyncItems,
2042
2035
  Activity: CommandActivity
2043
2036
  });
2044
- //#endregion
2045
- //#region src/react/error-boundary.tsx
2037
+ // src/react/error-boundary.tsx
2046
2038
  /** Initial state — no error captured */
2047
2039
  const INITIAL_STATE = { error: null };
2048
2040
  /** Error boundary for graceful failure recovery in command subtrees. */
@@ -2064,8 +2056,7 @@ var CommandErrorBoundary = class extends Component {
2064
2056
  return this.props.children;
2065
2057
  }
2066
2058
  };
2067
- //#endregion
2068
- //#region src/react/hooks/use-command-state.ts
2059
+ // src/react/hooks/use-command-state.ts
2069
2060
  function useCommandState(selector) {
2070
2061
  const stable = useStableContext("useCommandState");
2071
2062
  const selectorRef = useRef(selector);
@@ -2073,19 +2064,20 @@ function useCommandState(selector) {
2073
2064
  const getSnapshot = useCallback(() => selectorRef.current ? selectorRef.current(stable.machine.getState()) : stable.machine.getState(), [stable.machine]);
2074
2065
  return useSyncExternalStore(stable.machine.subscribe, getSnapshot, getSnapshot);
2075
2066
  }
2076
- //#endregion
2077
- //#region src/react/hooks/use-devtools.ts
2067
+ // src/react/hooks/use-devtools.ts
2078
2068
  /** Branded key for the global devtools instance map */
2079
2069
  const DEVTOOLS_KEY = "__CRIMSON_COMMAND_DEVTOOLS__";
2080
2070
  /**
2081
2071
  * Exposes command palette internals to browser devtools.
2082
- * Only active in development — completely tree-shaken in production builds.
2072
+ * Development only: the effect is gated on `process.env.NODE_ENV` and returns early in
2073
+ * production builds (the hook itself still ships). Call it from a component rendered inside
2074
+ * `<Command>`: outside one there is no machine to inspect and the hook does nothing.
2083
2075
  *
2084
2076
  * @example
2085
2077
  * ```tsx
2086
- * function MyPalette() {
2078
+ * function DevtoolsBridge() {
2087
2079
  * useCommandDevtools('main');
2088
- * return <Command>...</Command>;
2080
+ * return null;
2089
2081
  * }
2090
2082
  * ```
2091
2083
  */
@@ -2094,7 +2086,7 @@ function useCommandDevtools(label = "default") {
2094
2086
  const stateCtx = use(CommandStateContext);
2095
2087
  const stateRef = useRef(null);
2096
2088
  useEffect(() => {
2097
- if (!__DEV__) return;
2089
+ if (!isDev) return;
2098
2090
  if (!stable || !stateCtx) return;
2099
2091
  const update = () => {
2100
2092
  const s = stateCtx.state;
@@ -2129,5 +2121,4 @@ function useCommandDevtools(label = "default") {
2129
2121
  label
2130
2122
  ]);
2131
2123
  }
2132
- //#endregion
2133
2124
  export { Command, CommandActivity, CommandAsyncItems, CommandBadge, CommandDialog, CommandEmpty, CommandErrorBoundary, CommandGroup, CommandHighlight, CommandInput, CommandItem, CommandList, CommandLoading, CommandPage, CommandSeparator, CommandShortcut, CommandStableContext, CommandStateContext, createKeydownHandler, useCommand, useCommandDevtools, useCommandState, useRegisterGroup, useRegisterItem, useVirtualizer };
@@ -6,6 +6,12 @@
6
6
  /* CSS Logical Properties: full RTL/LTR support */
7
7
  /* CSS Nesting, @layer cascade control, oklch() color space */
8
8
  /* Container queries for responsive item layout */
9
+ /* Secondary text (group headings, shortcuts, empty state, placeholder) is coloured with
10
+ --command-muted-color, never faded with opacity: opacity stacks on the consumer's text
11
+ colour and cannot be checked against it. The default is light-dark(), so it follows the
12
+ used color-scheme of the page (set color-scheme on :root or a parent, or override the
13
+ token); it is 4.5:1 or better on white, black and the usual near-white / near-black
14
+ surfaces. Only [data-disabled] items keep opacity (WCAG 1.4.3 exempts disabled controls). */
9
15
 
10
16
  /* ─── CSS Custom Properties ─── */
11
17
 
@@ -40,7 +46,7 @@
40
46
  }
41
47
 
42
48
  /* ─── User-Customizable Animation Properties ─── */
43
- /* Override these on [data-command-root] or :root to customize all animations */
49
+ /* Override these with unlayered CSS on [data-command-root] loaded after this file, or on a more specific unlayered selector; this block sits outside every @layer, so a normal (non-!important) value set inside a layer loses here whatever its selector, and a :root value is shadowed by this declaration */
44
50
 
45
51
  [data-command-root] {
46
52
  --command-spring-easing: linear(
@@ -99,10 +105,11 @@
99
105
  --command-item-height: 44px;
100
106
  --command-fade-edge-size: 16px;
101
107
  --command-highlight-color: oklch(0.85 0.15 90 / 0.3);
108
+ --command-muted-color: light-dark(oklch(0.45 0.015 260), oklch(0.75 0.015 260));
102
109
  }
103
110
 
104
111
  /* ─── Spring Easing ─── */
105
- /* linear() approximation of a critically-damped spring (stiffness: 100, damping: 15) */
112
+ /* linear() spring approximations: --command-spring-easing settles without overshoot; --command-spring-easing-bouncy peaks at 1.033 before settling */
106
113
  /* Generated from spring physics: x(t) = 1 - e^(-zt)(cos(wd*t) + (zw/wd)sin(wd*t)) */
107
114
 
108
115
  @layer command.base, command.layout, command.animation, command.a11y;
@@ -173,7 +180,9 @@
173
180
  font: inherit;
174
181
 
175
182
  &::placeholder {
176
- opacity: 0.5;
183
+ color: var(--command-muted-color);
184
+ /* Firefox fades ::placeholder by default; the colour token carries the contrast */
185
+ opacity: 1;
177
186
  }
178
187
  }
179
188
 
@@ -232,7 +241,7 @@
232
241
  font-size: 0.75em;
233
242
  text-transform: uppercase;
234
243
  letter-spacing: 0.05em;
235
- opacity: 0.6;
244
+ color: var(--command-muted-color);
236
245
  }
237
246
 
238
247
  /* ─── Separator ─── */
@@ -255,7 +264,7 @@
255
264
 
256
265
  [data-command-empty] {
257
266
  text-align: center;
258
- opacity: 0.6;
267
+ color: var(--command-muted-color);
259
268
  }
260
269
 
261
270
  /* ─── Shortcut Badge ─── */
@@ -263,7 +272,7 @@
263
272
  [data-command-shortcut] {
264
273
  font-family: inherit;
265
274
  font-size: 0.75em;
266
- opacity: 0.5;
275
+ color: var(--command-muted-color);
267
276
  letter-spacing: 0.05em;
268
277
  }
269
278
 
@@ -543,7 +552,7 @@
543
552
  }
544
553
 
545
554
  [data-command-group-heading] {
546
- opacity: 0.8;
555
+ color: inherit;
547
556
  }
548
557
 
549
558
  [data-command-highlight] {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modern-cmdk",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "description": "The definitive command palette engine for the modern web",
6
6
  "license": "MIT",
@@ -62,7 +62,7 @@
62
62
  "devDependencies": {
63
63
  "globby": "16.2.4",
64
64
  "jscodeshift": "17.4.0",
65
- "radix-ui": "1.6.7",
65
+ "radix-ui": "1.8.2-rc.1791984254107",
66
66
  "@types/jscodeshift": "17.3.0"
67
67
  },
68
68
  "keywords": [
@@ -80,8 +80,8 @@
80
80
  "directory": "packages/modern-cmdk"
81
81
  },
82
82
  "scripts": {
83
- "build": "tsdown --config tsdown.config.ts && node -e \"const fs=require('fs');fs.cpSync('src/react/styles.css','dist/react/styles.css')\"",
84
- "dev": "tsdown src/core/index.ts src/react/index.ts --format esm --dts --watch",
83
+ "build": "tsdown",
84
+ "dev": "tsdown --watch",
85
85
  "typecheck": "tsc --noEmit"
86
86
  }
87
87
  }