@octanejs/cmdk 0.1.46 → 0.1.48

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octanejs/cmdk",
3
- "version": "0.1.46",
3
+ "version": "0.1.48",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -31,18 +31,18 @@
31
31
  ".": "./src/index.ts"
32
32
  },
33
33
  "dependencies": {
34
- "@octanejs/radix": "0.1.62"
34
+ "@octanejs/radix": "0.1.64"
35
35
  },
36
36
  "peerDependencies": {
37
- "octane": "^0.10.0"
37
+ "octane": "^0.11.0"
38
38
  },
39
39
  "devDependencies": {
40
- "@octanejs/testing-library": "0.1.59",
40
+ "@octanejs/testing-library": "0.1.61",
41
41
  "@testing-library/jest-dom": "^6.9.1",
42
- "@tsrx/react": "^0.3.7",
42
+ "@tsrx/react": "^0.4.5",
43
43
  "cmdk": "1.1.1",
44
44
  "esbuild": "^0.28.1",
45
- "octane": "0.10.0",
45
+ "octane": "0.11.0",
46
46
  "@types/react": "19.2.17",
47
47
  "@types/react-dom": "19.2.3",
48
48
  "react": "19.2.7",
package/src/cmdk.tsrx CHANGED
@@ -50,4 +50,13 @@ const Command = Object.assign(CommandRoot, {
50
50
 
51
51
  export { Command, CommandRoot, Item, Input, List, Group, Separator, Dialog, Empty, Loading };
52
52
  export { useCmdk as useCommandState };
53
- export { Item as CommandItem, Input as CommandInput, List as CommandList, Group as CommandGroup, Separator as CommandSeparator, Dialog as CommandDialog, Empty as CommandEmpty, Loading as CommandLoading };
53
+ export {
54
+ Item as CommandItem,
55
+ Input as CommandInput,
56
+ List as CommandList,
57
+ Group as CommandGroup,
58
+ Separator as CommandSeparator,
59
+ Dialog as CommandDialog,
60
+ Empty as CommandEmpty,
61
+ Loading as CommandLoading,
62
+ };
@@ -18,15 +18,13 @@ import {
18
18
  import type { CommandProps, Context, State, Store } from '../types';
19
19
 
20
20
  export function CommandRoot(props: CommandProps) @{
21
- const state = useLazyRef<State>(
22
- () => ({
23
- search: '',
24
- value: props.value ?? props.defaultValue ?? '',
25
- selectedItemId: undefined,
26
- filtered: { count: 0, items: new Map(), groups: new Set() },
27
- forceMountedCount: 0,
28
- }),
29
- );
21
+ const state = useLazyRef<State>(() => ({
22
+ search: '',
23
+ value: props.value ?? props.defaultValue ?? '',
24
+ selectedItemId: undefined,
25
+ filtered: { count: 0, items: new Map(), groups: new Set() },
26
+ forceMountedCount: 0,
27
+ }));
30
28
  const allItems = useLazyRef<Set<string>>(() => new Set());
31
29
  const allForceMounted = useLazyRef<Set<string>>(() => new Set());
32
30
  const allGroups = useLazyRef<Map<string, Set<string>>>(() => new Map());
@@ -228,7 +226,7 @@ export function CommandRoot(props: CommandProps) @{
228
226
  };
229
227
  },
230
228
  filter: () => propsRef.current.shouldFilter !== false,
231
- label: label || props['aria-label'] as string,
229
+ label: label || (props['aria-label'] as string),
232
230
  getDisablePointerSelection: () => propsRef.current.disablePointerSelection ?? false,
233
231
  listId,
234
232
  inputId,
@@ -239,13 +237,13 @@ export function CommandRoot(props: CommandProps) @{
239
237
  );
240
238
 
241
239
  /**
242
- * DEV ONLY. Selection is keyed by VALUE, not by item id, so two items sharing
243
- * a value both report `aria-selected="true"` while only the first one — the
244
- * one `getSelectedItem()`'s `querySelector` finds — actually responds to
245
- * Enter. That is an authoring error (cmdk requires unique values) rather than
246
- * something the runtime can resolve, so report it instead of silently
247
- * rendering a listbox with two selected options.
248
- */
240
+ * DEV ONLY. Selection is keyed by VALUE, not by item id, so two items sharing
241
+ * a value both report `aria-selected="true"` while only the first one — the
242
+ * one `getSelectedItem()`'s `querySelector` finds — actually responds to
243
+ * Enter. That is an authoring error (cmdk requires unique values) rather than
244
+ * something the runtime can resolve, so report it instead of silently
245
+ * rendering a listbox with two selected options.
246
+ */
249
247
  function trackValueOwner(previous: string | undefined, next: string) {
250
248
  if (previous) {
251
249
  const remaining = (valueOwners.current.get(previous) ?? 1) - 1;
@@ -267,34 +265,34 @@ export function CommandRoot(props: CommandProps) @{
267
265
  }
268
266
 
269
267
  /**
270
- * Queue a teardown's follow-up work.
271
- *
272
- * Octane runs a removed child's effect cleanups DURING the parent's render
273
- * (`renderBranchSlot` → `unmountBlock` → `unmountScope` →
274
- * `runEffectCleanupCallback`), so calling `schedule` straight from a teardown
275
- * lands its `ss({})` mid-render and the runtime reports "Cannot update a
276
- * component (`CommandRoot`) while rendering a different component" — advice a
277
- * teardown cannot act on, since it does not choose when it runs. Hopping a
278
- * microtask moves the update out of the render pass; the queued work itself is
279
- * unchanged and still runs before paint.
280
- *
281
- * Only the teardowns defer. Every other caller schedules from a layout effect,
282
- * where the synchronous update is both correct and load-bearing (the search →
283
- * filter → select-first chain has to settle inside the same commit).
284
- */
268
+ * Queue a teardown's follow-up work.
269
+ *
270
+ * Octane runs a removed child's effect cleanups DURING the parent's render
271
+ * (`renderBranchSlot` → `unmountBlock` → `unmountScope` →
272
+ * `runEffectCleanupCallback`), so calling `schedule` straight from a teardown
273
+ * lands its `ss({})` mid-render and the runtime reports "Cannot update a
274
+ * component (`CommandRoot`) while rendering a different component" — advice a
275
+ * teardown cannot act on, since it does not choose when it runs. Hopping a
276
+ * microtask moves the update out of the render pass; the queued work itself is
277
+ * unchanged and still runs before paint.
278
+ *
279
+ * Only the teardowns defer. Every other caller schedules from a layout effect,
280
+ * where the synchronous update is both correct and load-bearing (the search →
281
+ * filter → select-first chain has to settle inside the same commit).
282
+ */
285
283
  function scheduleFromTeardown(id: string | number, cb: () => void) {
286
284
  queueMicrotask(() => schedule(id, cb));
287
285
  }
288
286
 
289
287
  /**
290
- * Drop everything `context.value` registered for `id`. Items, force-mounted
291
- * items AND groups all register their value through the same `useValue` hook,
292
- * so all three teardown paths have to release through here. Releasing on only
293
- * one of them leaves the entry alive past the component: the dev-only owner
294
- * count then reports a duplicate for a value just one live component holds
295
- * (re-showing a force-mounted item counts 2, then 3, ...), and `ids` /
296
- * `filtered.items` keep growing for the Command's lifetime.
297
- */
288
+ * Drop everything `context.value` registered for `id`. Items, force-mounted
289
+ * items AND groups all register their value through the same `useValue` hook,
290
+ * so all three teardown paths have to release through here. Releasing on only
291
+ * one of them leaves the entry alive past the component: the dev-only owner
292
+ * count then reports a duplicate for a value just one live component holds
293
+ * (re-showing a force-mounted item counts 2, then 3, ...), and `ids` /
294
+ * `filtered.items` keep growing for the Command's lifetime.
295
+ */
298
296
  function releaseValue(id: string) {
299
297
  if (process.env.NODE_ENV !== 'production') {
300
298
  trackValueOwner(ids.current.get(id)?.value, '');
@@ -357,10 +355,10 @@ export function CommandRoot(props: CommandProps) @{
357
355
  }
358
356
 
359
357
  /**
360
- * Inline styles this component applied, mapped to the values that were there
361
- * before, so clearing a search restores the consumer's own inline styling
362
- * rather than deleting it.
363
- */
358
+ * Inline styles this component applied, mapped to the values that were there
359
+ * before, so clearing a search restores the consumer's own inline styling
360
+ * rather than deleting it.
361
+ */
364
362
  const styled = useLazyRef<Map<HTMLElement, Record<string, string>>>(() => new Map());
365
363
 
366
364
  function setStyle(element: HTMLElement, property: string, value: string) {
@@ -384,7 +382,7 @@ export function CommandRoot(props: CommandProps) @{
384
382
  }
385
383
 
386
384
  /** The direct child of `container` that positions `item` — the item itself, or
387
- * the wrapper element it sits inside. */
385
+ * the wrapper element it sits inside. */
388
386
  function positionedNode(item: Element, container: Element): HTMLElement | null {
389
387
  // Walk up to the child the container actually lays out. `order` applies to
390
388
  // a flex CHILD, so an item nested in a wrapper has to rank through that
@@ -405,30 +403,30 @@ export function CommandRoot(props: CommandProps) @{
405
403
  }
406
404
 
407
405
  /**
408
- * Order items by score, and groups by their best item score, using CSS `order`.
409
- *
410
- * OCTANE DIVERGENCE[runtime-adaptation-divergences][differential:cmdk-filter-selection-empty]: cmdk's sort is DOM-authoritative — it puts matches in
411
- * score order by physically relocating item nodes into the list sizer. Octane
412
- * fences every component's DOM with a pair of comment markers and tracks the
413
- * range between them, and a template construct like `@for` or `@if` wraps each
414
- * item in a SECOND, outer range. Relocating a node carries it out of every one
415
- * of those ranges at once: the loop later clears a range that is now empty and
416
- * the real node is orphaned in the list forever (observed as every item
417
- * surviving a removal, stacked above a "No results found." empty state).
418
- *
419
- * Carrying the flanking markers along only ever repairs the innermost range,
420
- * so it breaks again at each new nesting construct. Expressing the order
421
- * declaratively removes the class of bug instead of an instance of it: no node
422
- * is ever moved, so no range is ever violated, and clearing the search
423
- * restores true source order for free — physical reordering left a scrambled
424
- * residue that accumulated across searches and never recovered.
425
- *
426
- * The cost is that the sizer and each group's item container become flex
427
- * columns while a filter is active, so a consumer who relies on physical DOM
428
- * order (`:nth-child` styling, drag handles) or on a custom container
429
- * `display` sees a divergence from upstream. This marker authenticates ranking
430
- * and aria wiring only; event/ref adaptations are out of scope for this case.
431
- */
406
+ * Order items by score, and groups by their best item score, using CSS `order`.
407
+ *
408
+ * OCTANE DIVERGENCE[runtime-adaptation-divergences][differential:cmdk-filter-selection-empty]: cmdk's sort is DOM-authoritative — it puts matches in
409
+ * score order by physically relocating item nodes into the list sizer. Octane
410
+ * fences every component's DOM with a pair of comment markers and tracks the
411
+ * range between them, and a template construct like `@for` or `@if` wraps each
412
+ * item in a SECOND, outer range. Relocating a node carries it out of every one
413
+ * of those ranges at once: the loop later clears a range that is now empty and
414
+ * the real node is orphaned in the list forever (observed as every item
415
+ * surviving a removal, stacked above a "No results found." empty state).
416
+ *
417
+ * Carrying the flanking markers along only ever repairs the innermost range,
418
+ * so it breaks again at each new nesting construct. Expressing the order
419
+ * declaratively removes the class of bug instead of an instance of it: no node
420
+ * is ever moved, so no range is ever violated, and clearing the search
421
+ * restores true source order for free — physical reordering left a scrambled
422
+ * residue that accumulated across searches and never recovered.
423
+ *
424
+ * The cost is that the sizer and each group's item container become flex
425
+ * columns while a filter is active, so a consumer who relies on physical DOM
426
+ * order (`:nth-child` styling, drag handles) or on a custom container
427
+ * `display` sees a divergence from upstream. This marker authenticates ranking
428
+ * and aria wiring only; event/ref adaptations are out of scope for this case.
429
+ */
432
430
  function sort() {
433
431
  if (!state.current.search || propsRef.current.shouldFilter === false) {
434
432
  // No active filter: source order is the answer, so drop everything we set.
@@ -474,29 +472,28 @@ export function CommandRoot(props: CommandProps) @{
474
472
  //
475
473
  // The sort is stable on the DOM index, so items that score equally keep
476
474
  // their source order.
477
- getValidItems().map(
478
- (item, index) => ({
475
+ getValidItems()
476
+ .map((item, index) => ({
479
477
  item,
480
478
  index,
481
479
  score: scores.get(item.getAttribute('id') ?? '') ?? 0,
482
- }),
483
- ).sort((a, b) => b.score - a.score || a.index - b.index).map(({ item }) => item).forEach((
484
- item,
485
- rank,
486
- ) => {
487
- const group = item.closest(GROUP_ITEMS_SELECTOR);
488
- const container = group ?? listInsertionElement;
489
- if (group) asOrderedContainer(group);
490
- const node = positionedNode(item, container);
491
- if (!node) return;
492
- // A grouped item is ranked against its own container's children, so the
493
- // global rank works there — it is sparse but monotonic. A sizer child
494
- // shares one rank space with the group hosts numbered below, so it needs
495
- // a DENSE counter: the global rank skips ahead whenever a grouped item
496
- // outranks it, which let an ungrouped item tie a group host and leave the
497
- // two painting in DOM order.
498
- setStyle(node, 'order', String(group ? rank + 1 : topLevel += 1));
499
- });
480
+ }))
481
+ .sort((a, b) => b.score - a.score || a.index - b.index)
482
+ .map(({ item }) => item)
483
+ .forEach((item, rank) => {
484
+ const group = item.closest(GROUP_ITEMS_SELECTOR);
485
+ const container = group ?? listInsertionElement;
486
+ if (group) asOrderedContainer(group);
487
+ const node = positionedNode(item, container);
488
+ if (!node) return;
489
+ // A grouped item is ranked against its own container's children, so the
490
+ // global rank works there — it is sparse but monotonic. A sizer child
491
+ // shares one rank space with the group hosts numbered below, so it needs
492
+ // a DENSE counter: the global rank skips ahead whenever a grouped item
493
+ // outranks it, which let an ungrouped item tie a group host and leave the
494
+ // two painting in DOM order.
495
+ setStyle(node, 'order', String(group ? rank + 1 : (topLevel += 1)));
496
+ });
500
497
 
501
498
  // OCTANE DIVERGENCE[corrected-group-ordering][differential:cmdk-groups]: upstream looks each group element up by
502
499
  // `[cmdk-group=""][data-value="<groupId>"]`, but `data-value` holds the
@@ -507,18 +504,19 @@ export function CommandRoot(props: CommandProps) @{
507
504
  // attribute directly also avoids escaping problems for values containing
508
505
  // quotes (upstream encodes the selector but writes `data-value` raw).
509
506
  const groupElements = Array.from(listInsertionElement.querySelectorAll(GROUP_SELECTOR));
510
- groups.sort((a, b) => b[1] - a[1]).forEach(([groupId], rank) => {
511
- const groupValue = ids.current.get(groupId)?.value;
512
- if (!groupValue) return;
513
- const element = groupElements.find((el) => el.getAttribute(VALUE_ATTR) === groupValue);
514
- // Through the positioned node for the same reason items do: a group
515
- // wrapped in another element is not itself the sizer's flex child, so
516
- // ordering the group host would do nothing and leave the wrapper
517
- // unranked at `order: 0`, ahead of everything.
518
- const node =
519
- element ? positionedNode(element, listInsertionElement) : null;
520
- if (node) setStyle(node, 'order', String(topLevel + rank + 1));
521
- });
507
+ groups
508
+ .sort((a, b) => b[1] - a[1])
509
+ .forEach(([groupId], rank) => {
510
+ const groupValue = ids.current.get(groupId)?.value;
511
+ if (!groupValue) return;
512
+ const element = groupElements.find((el) => el.getAttribute(VALUE_ATTR) === groupValue);
513
+ // Through the positioned node for the same reason items do: a group
514
+ // wrapped in another element is not itself the sizer's flex child, so
515
+ // ordering the group host would do nothing and leave the wrapper
516
+ // unranked at `order: 0`, ahead of everything.
517
+ const node = element ? positionedNode(element, listInsertionElement) : null;
518
+ if (node) setStyle(node, 'order', String(topLevel + rank + 1));
519
+ });
522
520
  }
523
521
 
524
522
  function getSelectedItem() {
@@ -539,26 +537,26 @@ export function CommandRoot(props: CommandProps) @{
539
537
 
540
538
  /** Element children of `container`, in the order they are painted. */
541
539
  function orderedChildren(container: Element): HTMLElement[] {
542
- return Array.from(container.children).filter(
543
- (child): child is HTMLElement => child instanceof HTMLElement,
544
- ).map((child, index) => ({ child, index, order: Number(child.style.order) || 0 })).sort(
545
- (a, b) => a.order - b.order || a.index - b.index,
546
- ).map(({ child }) => child);
540
+ return Array.from(container.children)
541
+ .filter((child): child is HTMLElement => child instanceof HTMLElement)
542
+ .map((child, index) => ({ child, index, order: Number(child.style.order) || 0 }))
543
+ .sort((a, b) => a.order - b.order || a.index - b.index)
544
+ .map(({ child }) => child);
547
545
  }
548
546
 
549
547
  /**
550
- * Selectable items in the order the user sees them.
551
- *
552
- * Selection and arrow-key navigation both read this, so it has to agree with
553
- * what is on screen. While a filter is active the ranking lives in CSS `order`
554
- * rather than in DOM order (see `sort()`), and the two disagree — walking the
555
- * DOM would select and navigate in source order behind a differently-ordered
556
- * list. Descending through the ranked containers keeps group ranking and
557
- * within-group ranking composed in the right order.
558
- *
559
- * With no filter active nothing carries an `order`, so DOM order already IS
560
- * visual order and the flat query is both correct and cheaper.
561
- */
548
+ * Selectable items in the order the user sees them.
549
+ *
550
+ * Selection and arrow-key navigation both read this, so it has to agree with
551
+ * what is on screen. While a filter is active the ranking lives in CSS `order`
552
+ * rather than in DOM order (see `sort()`), and the two disagree — walking the
553
+ * DOM would select and navigate in source order behind a differently-ordered
554
+ * list. Descending through the ranked containers keeps group ranking and
555
+ * within-group ranking composed in the right order.
556
+ *
557
+ * With no filter active nothing carries an `order`, so DOM order already IS
558
+ * visual order and the flat query is both correct and cheaper.
559
+ */
562
560
  function getValidItems(): Element[] {
563
561
  const root = listInnerRef.current;
564
562
  if (!root) return [];
@@ -592,19 +590,20 @@ export function CommandRoot(props: CommandProps) @{
592
590
  let newSelected = items[index + change];
593
591
 
594
592
  if (propsRef.current?.loop) {
595
- newSelected = index + change < 0
596
- ? items[items.length - 1]
597
- : index + change === items.length
598
- ? items[0]
599
- : items[index + change];
593
+ newSelected =
594
+ index + change < 0
595
+ ? items[items.length - 1]
596
+ : index + change === items.length
597
+ ? items[0]
598
+ : items[index + change];
600
599
  }
601
600
 
602
601
  if (newSelected) store.setState('value', newSelected.getAttribute(VALUE_ATTR) || '');
603
602
  }
604
603
 
605
604
  /** Groups in the order they are painted, for the same reason `getValidItems`
606
- * exists: while a filter is active the ranking lives in CSS `order`, so DOM
607
- * order and on-screen order disagree. */
605
+ * exists: while a filter is active the ranking lives in CSS `order`, so DOM
606
+ * order and on-screen order disagree. */
608
607
  function getOrderedGroups(): Element[] {
609
608
  const root = listInnerRef.current;
610
609
  if (!root) return [];
@@ -613,13 +612,14 @@ export function CommandRoot(props: CommandProps) @{
613
612
  // Read the rank off the positioned node, not the group host: a wrapped group
614
613
  // carries its `order` on the wrapper, so reading the host would see 0 for
615
614
  // every such group and collapse navigation back to DOM order.
616
- return groups.map(
617
- (group, index) => ({
615
+ return groups
616
+ .map((group, index) => ({
618
617
  group,
619
618
  index,
620
619
  order: Number(positionedNode(group, root)?.style.order) || 0,
621
- }),
622
- ).sort((a, b) => a.order - b.order || a.index - b.index).map(({ group }) => group);
620
+ }))
621
+ .sort((a, b) => a.order - b.order || a.index - b.index)
622
+ .map(({ group }) => group);
623
623
  }
624
624
 
625
625
  function updateSelectedByGroup(change: 1 | -1) {
@@ -632,8 +632,7 @@ export function CommandRoot(props: CommandProps) @{
632
632
  // filter is active.
633
633
  const groups = getOrderedGroups();
634
634
  const items = getValidItems();
635
- let index =
636
- group ? groups.indexOf(group) : -1;
635
+ let index = group ? groups.indexOf(group) : -1;
637
636
  let item: Element | undefined;
638
637
 
639
638
  while (index >= 0 && !item) {
@@ -731,7 +730,9 @@ export function CommandRoot(props: CommandProps) @{
731
730
  }
732
731
  }}
733
732
  >
734
- <label cmdk-label="" for={inputId} id={labelId} style={srOnlyStyles}>{label}</label>
733
+ <label cmdk-label="" for={inputId} id={labelId} style={srOnlyStyles}>
734
+ {label}
735
+ </label>
735
736
  <StoreContext value={store}>
736
737
  <CommandContext value={context}>{children}</CommandContext>
737
738
  </StoreContext>
@@ -26,5 +26,7 @@ export function Empty(props: EmptyProps) @{
26
26
  if (!render) return null;
27
27
 
28
28
  const { ref: _ref, children, ...etc } = props;
29
- <div ref={props.ref ?? null} {...etc} cmdk-empty="" role="presentation">{children}</div>
29
+ <div ref={props.ref ?? null} {...etc} cmdk-empty="" role="presentation">
30
+ {children}
31
+ </div>
30
32
  }
@@ -12,8 +12,8 @@ export function Group(props: GroupProps) @{
12
12
  const headingRef = useRef<HTMLDivElement | null>(null);
13
13
  const headingId = useId();
14
14
  const context = useCommand()!;
15
- const render = useCmdk(
16
- (s) => forceMount
15
+ const render = useCmdk((s) =>
16
+ forceMount
17
17
  ? true
18
18
  : context.filter() === false
19
19
  ? true
@@ -43,7 +43,9 @@ export function Group(props: GroupProps) @{
43
43
  hidden={render ? undefined : true}
44
44
  >
45
45
  @if (heading) {
46
- <div ref={headingRef} cmdk-group-heading="" aria-hidden={true} id={headingId}>{heading}</div>
46
+ <div ref={headingRef} cmdk-group-heading="" aria-hidden={true} id={headingId}>
47
+ {heading}
48
+ </div>
47
49
  }
48
50
  <div cmdk-group-items="" role="group" aria-labelledby={heading ? headingId : undefined}>
49
51
  <GroupContext value={contextValue}>{children}</GroupContext>
@@ -28,23 +28,23 @@ export function Item(props: ItemProps) @{
28
28
 
29
29
  const store = useStore()!;
30
30
  const selected = useCmdk((s) => Boolean(s.value && s.value === value.current));
31
- const render = useCmdk(
32
- (s) => forceMount
31
+ const render = useCmdk((s) =>
32
+ forceMount
33
33
  ? true
34
34
  : context.filter() === false
35
35
  ? true
36
36
  : !s.search
37
37
  ? true
38
38
  : // An item with no entry in `filtered.items` has never been
39
- // scored — that is not the same as having scored zero, and
40
- // treating it as zero deadlocks: the item renders null, so
41
- // `ref.current` stays null, so `useValue` has no textContent to
42
- // infer from, so it registers an empty value, which scores zero
43
- // forever. Upstream reads string children synchronously; octane's
44
- // compiled children are opaque, so the first render IS the
45
- // registration. Rendering unscored items lets that first pass
46
- // happen; the next filter pass hides it if it does not match.
47
- s.filtered.items.has(id)
39
+ // scored — that is not the same as having scored zero, and
40
+ // treating it as zero deadlocks: the item renders null, so
41
+ // `ref.current` stays null, so `useValue` has no textContent to
42
+ // infer from, so it registers an empty value, which scores zero
43
+ // forever. Upstream reads string children synchronously; octane's
44
+ // compiled children are opaque, so the first render IS the
45
+ // registration. Rendering unscored items lets that first pass
46
+ // happen; the next filter pass hides it if it does not match.
47
+ s.filtered.items.has(id)
48
48
  ? (s.filtered.items.get(id) ?? 0) > 0
49
49
  : true,
50
50
  );
@@ -90,5 +90,7 @@ export function Item(props: ItemProps) @{
90
90
  data-selected={Boolean(selected)}
91
91
  onPointerMove={disabled || context.getDisablePointerSelection() ? undefined : select}
92
92
  onClick={disabled ? undefined : onSelect}
93
- >{children}</div>
93
+ >
94
+ {children}
95
+ </div>
94
96
  }
@@ -45,6 +45,8 @@ export function List(props: ListProps) @{
45
45
  aria-label={label}
46
46
  id={context.listId}
47
47
  >
48
- <div ref={[height, context.listInnerRef]} cmdk-list-sizer="">{children}</div>
48
+ <div ref={[height, context.listInnerRef]} cmdk-list-sizer="">
49
+ {children}
50
+ </div>
49
51
  </div>
50
52
  }
package/src/hooks.tsrx CHANGED
@@ -82,8 +82,7 @@ export function useValue(
82
82
  return undefined;
83
83
  })();
84
84
 
85
- const keywords =
86
- Array.isArray(aliases) ? aliases.map((alias) => alias.trim()) : [];
85
+ const keywords = Array.isArray(aliases) ? aliases.map((alias) => alias.trim()) : [];
87
86
  context.value(id, value, keywords);
88
87
  ref.current?.setAttribute(VALUE_ATTR, value ?? '');
89
88
  valueRef.current = value;