bits-ui 1.3.11 → 1.3.12

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.
@@ -26,6 +26,7 @@ declare class CommandRootState {
26
26
  labelNode: HTMLElement | null;
27
27
  commandState: CommandState;
28
28
  _commandState: CommandState;
29
+ searchHasHadValue: boolean;
29
30
  setState<K extends keyof CommandState>(key: K, value: CommandState[K], opts?: boolean): void;
30
31
  constructor(opts: CommandRootStateProps);
31
32
  /**
@@ -266,7 +267,7 @@ declare class CommandSeparatorState {
266
267
  constructor(opts: CommandSeparatorStateProps, root: CommandRootState);
267
268
  props: {
268
269
  readonly id: string;
269
- readonly role: "separator";
270
+ readonly "aria-hidden": "true";
270
271
  readonly "data-command-separator": "";
271
272
  };
272
273
  }
@@ -44,6 +44,8 @@ class CommandRootState {
44
44
  commandState = $state.raw(null);
45
45
  // internal state that we mutate in batches and publish to the `state` at once
46
46
  _commandState = $state(null);
47
+ // whether the search has had a value other than ""
48
+ searchHasHadValue = $state(false);
47
49
  #snapshot() {
48
50
  return $state.snapshot(this._commandState);
49
51
  }
@@ -70,6 +72,9 @@ class CommandRootState {
70
72
  this.#filterItems();
71
73
  this.#sort();
72
74
  this.#selectFirstItem();
75
+ afterTick(() => {
76
+ this.#selectFirstItem();
77
+ });
73
78
  }
74
79
  else if (key === "value") {
75
80
  // opts is a boolean referring to whether it should NOT be scrolled into view
@@ -100,6 +105,11 @@ class CommandRootState {
100
105
  this.commandState = defaultState;
101
106
  useRefById(opts);
102
107
  this.onkeydown = this.onkeydown.bind(this);
108
+ $effect(() => {
109
+ if (this._commandState.search !== "") {
110
+ this.searchHasHadValue = true;
111
+ }
112
+ });
103
113
  }
104
114
  /**
105
115
  * Calculates score for an item based on search text and keywords.
@@ -268,7 +278,7 @@ class CommandRootState {
268
278
  * Special handling for first items in groups.
269
279
  */
270
280
  #scrollSelectedIntoView() {
271
- afterSleep(1, () => {
281
+ afterTick(() => {
272
282
  const item = this.#getSelectedItem();
273
283
  if (!item)
274
284
  return;
@@ -277,10 +287,10 @@ class CommandRootState {
277
287
  return;
278
288
  const firstChildOfParent = getFirstNonCommentChild(grandparent);
279
289
  if (firstChildOfParent && firstChildOfParent.dataset?.value === item.dataset?.value) {
280
- item
290
+ const closestGroupHeader = item
281
291
  ?.closest(COMMAND_GROUP_SELECTOR)
282
- ?.querySelector(COMMAND_GROUP_HEADING_SELECTOR)
283
- ?.scrollIntoView({ block: "nearest" });
292
+ ?.querySelector(COMMAND_GROUP_HEADING_SELECTOR);
293
+ closestGroupHeader?.scrollIntoView({ block: "nearest" });
284
294
  return;
285
295
  }
286
296
  item.scrollIntoView({ block: "nearest" });
@@ -419,15 +429,16 @@ class CommandRootState {
419
429
  this.#sort();
420
430
  this.#scheduleUpdate();
421
431
  return () => {
432
+ const selectedItem = this.#getSelectedItem();
422
433
  this.allIds.delete(id);
423
434
  this.allItems.delete(id);
424
435
  this.commandState.filtered.items.delete(id);
425
- const selectedItem = this.#getSelectedItem();
426
436
  this.#filterItems();
427
437
  // The item removed have been the selected one,
428
438
  // so selection should be moved to the first
429
- if (selectedItem?.getAttribute("id") === id)
439
+ if (selectedItem?.getAttribute("id") === id) {
430
440
  this.#selectFirstItem();
441
+ }
431
442
  this.#scheduleUpdate();
432
443
  };
433
444
  }
@@ -558,12 +569,16 @@ class CommandEmptyState {
558
569
  opts;
559
570
  root;
560
571
  #isInitialRender = true;
561
- shouldRender = $derived.by(() => (this.root._commandState.filtered.count === 0 && this.#isInitialRender === false) ||
562
- this.opts.forceMount.current);
572
+ shouldRender = $derived.by(() => {
573
+ return ((this.root._commandState.filtered.count === 0 &&
574
+ this.#isInitialRender === false &&
575
+ this.root.searchHasHadValue) ||
576
+ this.opts.forceMount.current);
577
+ });
563
578
  constructor(opts, root) {
564
579
  this.opts = opts;
565
580
  this.root = root;
566
- $effect(() => {
581
+ $effect.pre(() => {
567
582
  this.#isInitialRender = false;
568
583
  });
569
584
  useRefById({
@@ -599,7 +614,7 @@ class CommandGroupContainerState {
599
614
  ...opts,
600
615
  deps: () => this.shouldRender,
601
616
  });
602
- $effect(() => {
617
+ watch(() => this.opts.id.current, () => {
603
618
  return this.root.registerGroup(this.opts.id.current);
604
619
  });
605
620
  $effect(() => {
@@ -712,6 +727,7 @@ class CommandItemState {
712
727
  });
713
728
  trueValue = $state("");
714
729
  shouldRender = $derived.by(() => {
730
+ this.opts.ref.current;
715
731
  if (this.#trueForceMount ||
716
732
  this.root.opts.shouldFilter.current === false ||
717
733
  !this.root.commandState.search) {
@@ -736,6 +752,7 @@ class CommandItemState {
736
752
  () => this.opts.id.current,
737
753
  () => this.#group?.opts.id.current,
738
754
  () => this.opts.forceMount.current,
755
+ () => this.opts.ref.current,
739
756
  ], () => {
740
757
  if (this.opts.forceMount.current)
741
758
  return;
@@ -807,7 +824,7 @@ class CommandLoadingState {
807
824
  class CommandSeparatorState {
808
825
  opts;
809
826
  root;
810
- shouldRender = $derived.by(() => !this.root.commandState.search || this.opts.forceMount.current);
827
+ shouldRender = $derived.by(() => !this.root._commandState.search || this.opts.forceMount.current);
811
828
  constructor(opts, root) {
812
829
  this.opts = opts;
813
830
  this.root = root;
@@ -818,7 +835,8 @@ class CommandSeparatorState {
818
835
  }
819
836
  props = $derived.by(() => ({
820
837
  id: this.opts.id.current,
821
- role: "separator",
838
+ // role="separator" cannot belong to a role="listbox"
839
+ "aria-hidden": "true",
822
840
  [COMMAND_SEPARATOR_ATTR]: "",
823
841
  }));
824
842
  }
@@ -35,7 +35,7 @@
35
35
  </script>
36
36
 
37
37
  {#key itemState.root.key}
38
- <div style="display: contents;" data-item-wrapper data-value={value}>
38
+ <div style="display: contents;" data-item-wrapper data-value={itemState.trueValue}>
39
39
  {#if itemState.shouldRender}
40
40
  {#if child}
41
41
  {@render child({ props: mergedProps })}
@@ -25,10 +25,12 @@
25
25
  const mergedProps = $derived(mergeProps(restProps, listState.props));
26
26
  </script>
27
27
 
28
- {#if child}
29
- {@render child({ props: mergedProps })}
30
- {:else}
31
- <div {...mergedProps}>
32
- {@render children?.()}
33
- </div>
34
- {/if}
28
+ {#key listState.root._commandState.search === ""}
29
+ {#if child}
30
+ {@render child({ props: mergedProps })}
31
+ {:else}
32
+ <div {...mergedProps}>
33
+ {@render children?.()}
34
+ </div>
35
+ {/if}
36
+ {/key}
@@ -1,5 +1,5 @@
1
1
  import { useRefById } from "svelte-toolbelt";
2
- import { Context } from "runed";
2
+ import { Context, watch } from "runed";
3
3
  import { getAriaChecked, getAriaRequired, getDataDisabled } from "../../internal/attrs.js";
4
4
  import { useRovingFocus, } from "../../internal/use-roving-focus.svelte.js";
5
5
  import { kbd } from "../../internal/kbd.js";
@@ -43,6 +43,7 @@ class RadioGroupItemState {
43
43
  checked = $derived.by(() => this.root.opts.value.current === this.opts.value.current);
44
44
  #isDisabled = $derived.by(() => this.opts.disabled.current || this.root.opts.disabled.current);
45
45
  #isChecked = $derived.by(() => this.root.isChecked(this.opts.value.current));
46
+ #tabIndex = $state(-1);
46
47
  constructor(opts, root) {
47
48
  this.opts = opts;
48
49
  this.root = root;
@@ -57,6 +58,12 @@ class RadioGroupItemState {
57
58
  $effect(() => {
58
59
  this.#tabIndex = this.root.rovingFocusGroup.getTabIndex(this.opts.ref.current);
59
60
  });
61
+ watch([() => this.opts.value.current, () => this.root.opts.value.current], () => {
62
+ if (this.opts.value.current === this.root.opts.value.current) {
63
+ this.root.rovingFocusGroup.setCurrentTabStopId(this.opts.id.current);
64
+ this.#tabIndex = 0;
65
+ }
66
+ });
60
67
  this.onclick = this.onclick.bind(this);
61
68
  this.onkeydown = this.onkeydown.bind(this);
62
69
  this.onfocus = this.onfocus.bind(this);
@@ -81,7 +88,6 @@ class RadioGroupItemState {
81
88
  }
82
89
  this.root.rovingFocusGroup.handleKeydown(this.opts.ref.current, e, true);
83
90
  }
84
- #tabIndex = $state(-1);
85
91
  snippetProps = $derived.by(() => ({ checked: this.#isChecked }));
86
92
  props = $derived.by(() => ({
87
93
  id: this.opts.id.current,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bits-ui",
3
- "version": "1.3.11",
3
+ "version": "1.3.12",
4
4
  "license": "MIT",
5
5
  "repository": "github:huntabyte/bits-ui",
6
6
  "funding": "https://github.com/sponsors/huntabyte",