@octanejs/cmdk 0.1.17 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,7 +15,9 @@ wiring to Octane (native events, ref-as-prop, `useId`), and building on
15
15
  > and `defaultFilter` — with filtering, score sorting, keyboard navigation,
16
16
  > controlled modes, and SSR + hydration. `asChild` is the one unsupported prop.
17
17
  > A differential suite runs the same fixture through this port and the published
18
- > `cmdk@1.1.1` on React and asserts byte-equal HTML. See
18
+ > `cmdk@1.1.1` on React and asserts byte-equal HTML for its declared cases. The
19
+ > canonical upstream Playwright suite is vendored but not yet adapted
20
+ > exhaustively, so parity remains recorded-unverified. See [`UPSTREAM.md`](./UPSTREAM.md),
19
21
  > [`docs/cmdk-port-plan.md`](../../docs/cmdk-port-plan.md) and the authoritative
20
22
  > [`docs/bindings-status.md`](../../docs/bindings-status.md).
21
23
 
@@ -47,9 +49,10 @@ Standard Octane binding adaptations (see
47
49
  on. Upstream never registers them, so it does neither.
48
50
  - `Command.Dialog` also accepts `defaultOpen` and `modal`; upstream forwards
49
51
  only `open`/`onOpenChange`.
50
- - After a search is cleared, items stay in the order `sort()` left them rather
51
- than returning to source order — octane's reconciler does not reposition nodes
52
- it did not move. Same items, same selection; only the residual order differs.
52
+ - Score ranking uses CSS `order` while filtering instead of physically moving
53
+ renderer-owned nodes. Clearing the search removes those declarations and
54
+ restores source order; DOM-order selectors can differ from visual order while
55
+ a filter is active.
53
56
 
54
57
  ## License
55
58
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octanejs/cmdk",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -30,21 +30,23 @@
30
30
  ".": "./src/index.ts"
31
31
  },
32
32
  "dependencies": {
33
- "@octanejs/radix": "0.1.33"
33
+ "@octanejs/radix": "0.1.35"
34
34
  },
35
35
  "peerDependencies": {
36
- "octane": "0.1.34"
36
+ "octane": "0.1.36"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@testing-library/jest-dom": "^6.9.1",
40
40
  "@tsrx/react": "^0.2.56",
41
41
  "cmdk": "1.1.1",
42
42
  "esbuild": "^0.28.1",
43
- "react": "^19.2.7",
44
- "react-dom": "^19.2.7",
43
+ "@types/react": "19.2.17",
44
+ "@types/react-dom": "19.2.3",
45
+ "react": "19.2.7",
46
+ "react-dom": "19.2.7",
45
47
  "vitest": "^4.1.10",
46
- "@octanejs/testing-library": "0.1.31",
47
- "octane": "0.1.34"
48
+ "@octanejs/testing-library": "0.1.33",
49
+ "octane": "0.1.36"
48
50
  },
49
51
  "scripts": {
50
52
  "test": "vitest run"
package/src/cmdk.tsrx CHANGED
@@ -19,7 +19,8 @@
19
19
  // - `sort()` reorders only the surviving (score > 0) items — moving a
20
20
  // to-be-unmounted node orphans it in an octane hydrated tree (see
21
21
  // ./components/command.tsrx).
22
- // - `asChild` is NOT supported: cmdk's SlottableWithNestedChildren clones a child
22
+ // - OCTANE DIVERGENCE[as-child-unsupported][differential:cmdk-as-child]:
23
+ // `asChild` is NOT supported: cmdk's SlottableWithNestedChildren clones a child
23
24
  // element via React.cloneElement/isValidElement and re-parents the component's
24
25
  // own content into it. Octane's compiled children are opaque render bodies, so
25
26
  // that introspect-and-clone has no faithful equivalent — components always
@@ -130,6 +130,7 @@ export function CommandRoot(props: CommandProps) @{
130
130
  }
131
131
  ids.current.set(id, { value: itemValue ?? '', keywords });
132
132
  state.current.filtered.items.set(id, score(itemValue ?? '', keywords));
133
+ // OCTANE DIVERGENCE[value-change-filter-refresh][differential:cmdk-value-change-filter]:
133
134
  // Re-derive the aggregate before sorting. Scoring this one item
134
135
  // updates `filtered.items` but leaves `filtered.count` and
135
136
  // `filtered.groups` describing the previous values, and those two
@@ -167,7 +168,8 @@ export function CommandRoot(props: CommandProps) @{
167
168
  // An item leaving its group has to be dropped from that group's member
168
169
  // set too, or the set accumulates dead ids for the Command's lifetime
169
170
  // (unbounded growth in a menu whose results churn) and filterItems()
170
- // walks them on every keystroke.
171
+ // walks them on every keystroke. Documented in status.json; no paired
172
+ // DOM oracle distinguishes the bookkeeping from upstream.
171
173
  if (groupId) allGroups.current.get(groupId)?.delete(id);
172
174
  const selectedItem = getSelectedItem();
173
175
 
@@ -194,9 +196,13 @@ export function CommandRoot(props: CommandProps) @{
194
196
  schedule(8, () => store.emit());
195
197
 
196
198
  return () => {
199
+ // OCTANE DIVERGENCE[force-mount-empty-count][differential:cmdk-force-mount-empty]:
200
+ // Releasing the force-mount count on unmount is required so Empty can
201
+ // reappear once no force-mounted item remains visible.
197
202
  releaseValue(id);
198
203
  allForceMounted.current.delete(id);
199
204
  state.current.forceMountedCount = allForceMounted.current.size;
205
+ // OCTANE DIVERGENCE[selected-force-mount-teardown][differential:cmdk-selected-force-mount]:
200
206
  // A force-mounted item is selectable like any other, so losing the
201
207
  // selected one has to move the selection on — exactly as the plain
202
208
  // item teardown does. Without this the value stays pointing at a node
@@ -250,6 +256,8 @@ export function CommandRoot(props: CommandProps) @{
250
256
  const owners = (valueOwners.current.get(next) ?? 0) + 1;
251
257
  valueOwners.current.set(next, owners);
252
258
  if (owners > 1) {
259
+ // OCTANE DIVERGENCE[duplicate-value-warning][differential:cmdk-duplicate-value]:
260
+ // Development builds report duplicate values; upstream accepts them silently.
253
261
  console.warn(
254
262
  `[@octanejs/cmdk] ${owners} items share the value ${JSON.stringify(next)}. ` +
255
263
  `Selection is keyed by value, so they will all appear selected at once and ` +
@@ -399,7 +407,7 @@ export function CommandRoot(props: CommandProps) @{
399
407
  /**
400
408
  * Order items by score, and groups by their best item score, using CSS `order`.
401
409
  *
402
- * OCTANE DIVERGENCE: cmdk's sort is DOM-authoritative — it puts matches in
410
+ * OCTANE DIVERGENCE[runtime-adaptation-divergences][differential:cmdk-filter-selection-empty]: cmdk's sort is DOM-authoritative — it puts matches in
403
411
  * score order by physically relocating item nodes into the list sizer. Octane
404
412
  * fences every component's DOM with a pair of comment markers and tracks the
405
413
  * range between them, and a template construct like `@for` or `@if` wraps each
@@ -418,7 +426,8 @@ export function CommandRoot(props: CommandProps) @{
418
426
  * The cost is that the sizer and each group's item container become flex
419
427
  * columns while a filter is active, so a consumer who relies on physical DOM
420
428
  * order (`:nth-child` styling, drag handles) or on a custom container
421
- * `display` sees a divergence from upstream. Recorded in status.json.
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.
422
431
  */
423
432
  function sort() {
424
433
  if (!state.current.search || propsRef.current.shouldFilter === false) {
@@ -453,6 +462,7 @@ export function CommandRoot(props: CommandProps) @{
453
462
  // ranks below continue past them instead of tying.
454
463
  let topLevel = 0;
455
464
 
465
+ // OCTANE DIVERGENCE[force-mount-rank-order][differential:cmdk-force-mount-rank]:
456
466
  // EVERY valid item gets a rank, not just the matches. A non-match usually
457
467
  // unmounts and never needs one, but a force-mounted item never scores and
458
468
  // never unmounts — and an unranked child keeps the initial `order` of 0,
@@ -488,7 +498,7 @@ export function CommandRoot(props: CommandProps) @{
488
498
  setStyle(node, 'order', String(group ? rank + 1 : topLevel += 1));
489
499
  });
490
500
 
491
- // OCTANE DIVERGENCE: upstream looks each group element up by
501
+ // OCTANE DIVERGENCE[corrected-group-ordering][differential:cmdk-groups]: upstream looks each group element up by
492
502
  // `[cmdk-group=""][data-value="<groupId>"]`, but `data-value` holds the
493
503
  // group's VALUE (its heading text), never its id — so upstream's selector
494
504
  // can never match and its group reordering is effectively dead code.
@@ -23,6 +23,10 @@ export function Dialog(props: DialogProps) {
23
23
  ...etc
24
24
  } = props;
25
25
 
26
+ // OCTANE DIVERGENCE[dialog-default-open][differential:cmdk-dialog-default-open]:
27
+ // Upstream forwards only open/onOpenChange; the port also wires defaultOpen
28
+ // through to the Radix root. `modal` is forwarded too, but that prop is outside
29
+ // this record's observing case.
26
30
  return createElement(
27
31
  RadixDialog.Root,
28
32
  { open, defaultOpen, onOpenChange, modal },
@@ -6,6 +6,7 @@ import type { EmptyProps } from '../types';
6
6
  export function Empty(props: EmptyProps) @{
7
7
  const store = useStore()!;
8
8
 
9
+ // OCTANE DIVERGENCE[force-mount-empty-count][differential:cmdk-force-mount-empty]:
9
10
  // Force-mounted items never enter `filtered.count`, so consult both: "no
10
11
  // results" must not render while any item is visible.
11
12
  const snapshot = () => {
@@ -13,6 +14,7 @@ export function Empty(props: EmptyProps) @{
13
14
  return state.filtered.count === 0 && state.forceMountedCount === 0;
14
15
  };
15
16
 
17
+ // OCTANE DIVERGENCE[ssr-empty-suppressed][differential:cmdk-ssr-empty]:
16
18
  // The server snapshot is `false`. Items register in layout effects, which do
17
19
  // not run on the server, so the count is unavoidably 0 there — rendering on
18
20
  // that would ship "No results found." above a fully-populated list on every
@@ -40,11 +40,10 @@ export function Item(props: ItemProps) @{
40
40
  // treating it as zero deadlocks: the item renders null, so
41
41
  // `ref.current` stays null, so `useValue` has no textContent to
42
42
  // infer from, so it registers an empty value, which scores zero
43
- // forever. Upstream cannot reach this because it reads the value
44
- // out of `props.children` synchronously; octane's compiled
45
- // children are opaque, so the first render IS the registration.
46
- // Rendering unscored items lets that first pass happen; the very
47
- // next filter pass hides it if it genuinely does not match.
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.
48
47
  s.filtered.items.has(id)
49
48
  ? (s.filtered.items.get(id) ?? 0) > 0
50
49
  : true,
package/src/hooks.tsrx CHANGED
@@ -109,10 +109,11 @@ export function useScheduleLayoutEffect() {
109
109
  try {
110
110
  f();
111
111
  } catch (error) {
112
+ // OCTANE DIVERGENCE[batcher-isolation][differential:cmdk-batcher-isolation]:
112
113
  // Isolate, but never swallow. A scheduled callback can run user code
113
114
  // (selectFirstItem -> setState('value') -> onValueChange), so a failure
114
115
  // here must stay visible; report it the same way octane reports an
115
- // exception raised inside an effect, and keep the siblings running.
116
+ // exception raised inside an effect. Upstream rethrows out of the flush.
116
117
  console.error(error);
117
118
  }
118
119
  }