bits-ui 2.5.0 → 2.6.1

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.
@@ -1,12 +1,20 @@
1
1
  import type { CommandState } from "./types.js";
2
2
  import type { BitsKeyboardEvent, BitsMouseEvent, BitsPointerEvent, WithRefProps } from "../../internal/types.js";
3
3
  import type { ReadableBoxedValues, WritableBoxedValues } from "../../internal/box.svelte.js";
4
+ type GridItem = {
5
+ index: number;
6
+ firstRowOfGroup: boolean;
7
+ ref: HTMLElement;
8
+ };
9
+ type ItemsGrid = GridItem[][];
4
10
  type CommandRootStateProps = WithRefProps<ReadableBoxedValues<{
5
11
  filter: (value: string, search: string, keywords?: string[]) => number;
6
12
  shouldFilter: boolean;
7
13
  loop: boolean;
8
14
  vimBindings: boolean;
15
+ columns: number | null;
9
16
  disablePointerSelection: boolean;
17
+ disableInitialScroll: boolean;
10
18
  onStateChange?: (state: Readonly<CommandState>) => void;
11
19
  }> & WritableBoxedValues<{
12
20
  value: string;
@@ -28,7 +36,7 @@ declare class CommandRootState {
28
36
  labelNode: HTMLElement | null;
29
37
  commandState: CommandState;
30
38
  _commandState: CommandState;
31
- setState<K extends keyof CommandState>(key: K, value: CommandState[K], opts?: boolean): void;
39
+ setState<K extends keyof CommandState>(key: K, value: CommandState[K], preventScroll?: boolean): void;
32
40
  constructor(opts: CommandRootStateProps);
33
41
  /**
34
42
  * Sets current value and triggers re-render if cleared.
@@ -43,6 +51,20 @@ declare class CommandRootState {
43
51
  * @remarks Exposed for direct item access and bound checking
44
52
  */
45
53
  getValidItems(): HTMLElement[];
54
+ /**
55
+ * Gets all visible command items.
56
+ *
57
+ * @returns Array of valid item elements
58
+ * @remarks Exposed for direct item access and bound checking
59
+ */
60
+ getVisibleItems(): HTMLElement[];
61
+ /** Returns all visible items in a matrix structure
62
+ *
63
+ * @remarks Returns empty if the command isn't configured as a grid
64
+ *
65
+ * @returns
66
+ */
67
+ get itemsGrid(): ItemsGrid;
46
68
  /**
47
69
  * Sets selection to item at specified index in valid items array.
48
70
  * If index is out of bounds, does nothing.
@@ -80,7 +102,7 @@ declare class CommandRootState {
80
102
  * // get all valid items
81
103
  * const items = getValidItems()
82
104
  */
83
- updateSelectedByItem(change: 1 | -1): void;
105
+ updateSelectedByItem(change: number): void;
84
106
  /**
85
107
  * Moves selection to the first valid item in the next/previous group.
86
108
  * If no group is found, falls back to selecting the next/previous item globally.
@@ -120,6 +142,7 @@ declare class CommandRootState {
120
142
  * @returns Cleanup function
121
143
  */
122
144
  registerGroup(id: string): () => void;
145
+ get isGrid(): boolean;
123
146
  onkeydown(e: BitsKeyboardEvent): void;
124
147
  props: {
125
148
  readonly id: string;
@@ -230,6 +253,7 @@ declare class CommandItemState {
230
253
  readonly "data-disabled": "" | undefined;
231
254
  readonly "data-selected": "" | undefined;
232
255
  readonly "data-value": string;
256
+ readonly "data-group": string | undefined;
233
257
  readonly role: "option";
234
258
  readonly onpointermove: (_: BitsPointerEvent) => void;
235
259
  readonly onclick: (_: BitsMouseEvent) => void;
@@ -50,6 +50,7 @@ const defaultState = {
50
50
  class CommandRootState {
51
51
  opts;
52
52
  #updateScheduled = false;
53
+ #isInitialMount = true;
53
54
  sortAfterTick = false;
54
55
  sortAndFilterAfterTick = false;
55
56
  allItems = new Set();
@@ -81,7 +82,7 @@ class CommandRootState {
81
82
  }
82
83
  });
83
84
  }
84
- setState(key, value, opts) {
85
+ setState(key, value, preventScroll) {
85
86
  if (Object.is(this._commandState[key], value))
86
87
  return;
87
88
  this._commandState[key] = value;
@@ -91,11 +92,8 @@ class CommandRootState {
91
92
  this.#sort();
92
93
  }
93
94
  else if (key === "value") {
94
- // opts is a boolean referring to whether it should NOT be scrolled into view
95
- if (!opts) {
96
- // Scroll the selected item into view
95
+ if (!preventScroll)
97
96
  this.#scrollSelectedIntoView();
98
- }
99
97
  }
100
98
  this.#scheduleUpdate();
101
99
  }
@@ -128,9 +126,6 @@ class CommandRootState {
128
126
  if (!this._commandState.search || this.opts.shouldFilter.current === false) {
129
127
  // If no search and no selection yet, select first item
130
128
  this.#selectFirstItem();
131
- // if (!this.commandState.value) {
132
- // this.#selectFirstItem();
133
- // }
134
129
  return;
135
130
  }
136
131
  const scores = this._commandState.filtered.items;
@@ -208,7 +203,9 @@ class CommandRootState {
208
203
  afterTick(() => {
209
204
  const item = this.getValidItems().find((item) => item.getAttribute("aria-disabled") !== "true");
210
205
  const value = item?.getAttribute(COMMAND_VALUE_ATTR);
211
- this.setValue(value || "");
206
+ const shouldPreventScroll = this.#isInitialMount && this.opts.disableInitialScroll.current;
207
+ this.setValue(value ?? "", shouldPreventScroll);
208
+ this.#isInitialMount = false;
212
209
  });
213
210
  }
214
211
  /**
@@ -257,6 +254,59 @@ class CommandRootState {
257
254
  const validItems = Array.from(node.querySelectorAll(COMMAND_VALID_ITEM_SELECTOR)).filter((el) => !!el);
258
255
  return validItems;
259
256
  }
257
+ /**
258
+ * Gets all visible command items.
259
+ *
260
+ * @returns Array of valid item elements
261
+ * @remarks Exposed for direct item access and bound checking
262
+ */
263
+ getVisibleItems() {
264
+ const node = this.opts.ref.current;
265
+ if (!node)
266
+ return [];
267
+ const visibleItems = Array.from(node.querySelectorAll(COMMAND_ITEM_SELECTOR)).filter((el) => !!el);
268
+ return visibleItems;
269
+ }
270
+ /** Returns all visible items in a matrix structure
271
+ *
272
+ * @remarks Returns empty if the command isn't configured as a grid
273
+ *
274
+ * @returns
275
+ */
276
+ get itemsGrid() {
277
+ if (!this.isGrid)
278
+ return [];
279
+ const columns = this.opts.columns.current ?? 1;
280
+ const items = this.getVisibleItems();
281
+ const grid = [[]];
282
+ let currentGroup = items[0]?.getAttribute("data-group");
283
+ let column = 0;
284
+ let row = 0;
285
+ for (let i = 0; i < items.length; i++) {
286
+ const item = items[i];
287
+ const itemGroup = item?.getAttribute("data-group");
288
+ if (currentGroup !== itemGroup) {
289
+ currentGroup = itemGroup;
290
+ column = 1;
291
+ row++;
292
+ grid.push([{ index: i, firstRowOfGroup: true, ref: item }]);
293
+ }
294
+ else {
295
+ column++;
296
+ if (column > columns) {
297
+ row++;
298
+ column = 1;
299
+ grid.push([]);
300
+ }
301
+ grid[row]?.push({
302
+ index: i,
303
+ firstRowOfGroup: grid[row]?.[0]?.firstRowOfGroup ?? i === 0,
304
+ ref: item,
305
+ });
306
+ }
307
+ }
308
+ return grid;
309
+ }
260
310
  /**
261
311
  * Gets currently selected command item.
262
312
  *
@@ -283,17 +333,47 @@ class CommandRootState {
283
333
  const grandparent = item.parentElement?.parentElement;
284
334
  if (!grandparent)
285
335
  return;
286
- const firstChildOfParent = getFirstNonCommentChild(grandparent);
287
- if (firstChildOfParent && firstChildOfParent.dataset?.value === item.dataset?.value) {
288
- const closestGroupHeader = item
289
- ?.closest(COMMAND_GROUP_SELECTOR)
290
- ?.querySelector(COMMAND_GROUP_HEADING_SELECTOR);
291
- closestGroupHeader?.scrollIntoView({ block: "nearest" });
292
- return;
336
+ if (this.isGrid) {
337
+ const isFirstRowOfGroup = this.#itemIsFirstRowOfGroup(item);
338
+ if (isFirstRowOfGroup) {
339
+ const closestGroupHeader = item
340
+ ?.closest(COMMAND_GROUP_SELECTOR)
341
+ ?.querySelector(COMMAND_GROUP_HEADING_SELECTOR);
342
+ closestGroupHeader?.scrollIntoView({ block: "nearest" });
343
+ return;
344
+ }
345
+ }
346
+ else {
347
+ const firstChildOfParent = getFirstNonCommentChild(grandparent);
348
+ if (firstChildOfParent &&
349
+ firstChildOfParent.dataset?.value === item.dataset?.value) {
350
+ const closestGroupHeader = item
351
+ ?.closest(COMMAND_GROUP_SELECTOR)
352
+ ?.querySelector(COMMAND_GROUP_HEADING_SELECTOR);
353
+ closestGroupHeader?.scrollIntoView({ block: "nearest" });
354
+ return;
355
+ }
293
356
  }
294
357
  item.scrollIntoView({ block: "nearest" });
295
358
  });
296
359
  }
360
+ #itemIsFirstRowOfGroup(item) {
361
+ const grid = this.itemsGrid;
362
+ if (grid.length === 0)
363
+ return false;
364
+ for (let r = 0; r < grid.length; r++) {
365
+ const row = grid[r];
366
+ if (row === undefined)
367
+ continue;
368
+ for (let c = 0; c < row.length; c++) {
369
+ const column = row[c];
370
+ if (column === undefined || column.ref !== item)
371
+ continue;
372
+ return column.firstRowOfGroup;
373
+ }
374
+ }
375
+ return false;
376
+ }
297
377
  /**
298
378
  * Sets selection to item at specified index in valid items array.
299
379
  * If index is out of bounds, does nothing.
@@ -311,11 +391,10 @@ class CommandRootState {
311
391
  * }
312
392
  */
313
393
  updateSelectedToIndex(index) {
314
- const items = this.getValidItems();
315
- const item = items[index];
316
- if (item) {
317
- this.setValue(item.getAttribute(COMMAND_VALUE_ATTR) ?? "");
318
- }
394
+ const item = this.getValidItems()[index];
395
+ if (!item)
396
+ return;
397
+ this.setValue(item.getAttribute(COMMAND_VALUE_ATTR) ?? "");
319
398
  }
320
399
  /**
321
400
  * Updates selected item by moving up/down relative to current selection.
@@ -469,6 +548,9 @@ class CommandRootState {
469
548
  this.allGroups.delete(id);
470
549
  };
471
550
  }
551
+ get isGrid() {
552
+ return this.opts.columns.current !== null;
553
+ }
472
554
  /**
473
555
  * Selects last valid item.
474
556
  */
@@ -495,6 +577,224 @@ class CommandRootState {
495
577
  this.updateSelectedByItem(1);
496
578
  }
497
579
  }
580
+ #down(e) {
581
+ if (this.opts.columns.current === null)
582
+ return;
583
+ e.preventDefault();
584
+ if (e.metaKey) {
585
+ this.updateSelectedByGroup(1);
586
+ }
587
+ else {
588
+ this.updateSelectedByItem(this.#nextRowColumnOffset(e));
589
+ }
590
+ }
591
+ #getColumn(item, grid) {
592
+ if (grid.length === 0)
593
+ return null;
594
+ for (let r = 0; r < grid.length; r++) {
595
+ const row = grid[r];
596
+ if (row === undefined)
597
+ continue;
598
+ for (let c = 0; c < row.length; c++) {
599
+ const column = row[c];
600
+ if (column === undefined || column.ref !== item)
601
+ continue;
602
+ return { columnIndex: c, rowIndex: r };
603
+ }
604
+ }
605
+ return null;
606
+ }
607
+ #nextRowColumnOffset(e) {
608
+ const grid = this.itemsGrid;
609
+ const selected = this.#getSelectedItem();
610
+ if (!selected)
611
+ return 0;
612
+ const column = this.#getColumn(selected, grid);
613
+ if (!column)
614
+ return 0;
615
+ let newItem = null;
616
+ const skipRows = e.altKey ? 1 : 0;
617
+ // if this is the second to last row then we need to go to the last row when skipping and not in loop mode
618
+ if (e.altKey && column.rowIndex === grid.length - 2 && !this.opts.loop.current) {
619
+ newItem = this.#findNextNonDisabledItem({
620
+ start: grid.length - 1,
621
+ end: grid.length,
622
+ expectedColumnIndex: column.columnIndex,
623
+ grid,
624
+ });
625
+ }
626
+ else if (column.rowIndex === grid.length - 1) {
627
+ // if this is the last row we apply the loop logic
628
+ if (!this.opts.loop.current)
629
+ return 0;
630
+ newItem = this.#findNextNonDisabledItem({
631
+ start: 0 + skipRows,
632
+ end: column.rowIndex,
633
+ expectedColumnIndex: column.columnIndex,
634
+ grid,
635
+ });
636
+ }
637
+ else {
638
+ newItem = this.#findNextNonDisabledItem({
639
+ start: column.rowIndex + 1 + skipRows,
640
+ end: grid.length,
641
+ expectedColumnIndex: column.columnIndex,
642
+ grid,
643
+ });
644
+ // this happens if there were no non-disabled columns below the current column
645
+ // we can now try starting from the beginning to find the right column
646
+ if (newItem === null && this.opts.loop.current) {
647
+ newItem = this.#findNextNonDisabledItem({
648
+ start: 0,
649
+ end: column.rowIndex,
650
+ expectedColumnIndex: column.columnIndex,
651
+ grid,
652
+ });
653
+ }
654
+ }
655
+ return this.#calculateOffset(selected, newItem);
656
+ }
657
+ /** Attempts to find the next non-disabled column that matches the expected column.
658
+ *
659
+ * @remarks
660
+ * - Skips over disabled columns
661
+ * - When a row is shorter than the expected column it defaults to the last item in the row
662
+ *
663
+ * @param param0
664
+ * @returns
665
+ */
666
+ #findNextNonDisabledItem({ start, end, grid, expectedColumnIndex, }) {
667
+ let newItem = null;
668
+ for (let r = start; r < end; r++) {
669
+ const row = grid[r];
670
+ // try to get the next column
671
+ newItem = row[expectedColumnIndex]?.ref ?? null;
672
+ // skip over disabled items
673
+ if (newItem !== null && itemIsDisabled(newItem)) {
674
+ newItem = null;
675
+ continue;
676
+ }
677
+ // if that column doesn't exist default to the next highest column
678
+ if (newItem === null) {
679
+ // try and find the next highest non-disabled item in the row
680
+ // if there aren't any non-disabled items we just give up and return null
681
+ for (let i = row.length - 1; i >= 0; i--) {
682
+ const item = row[row.length - 1];
683
+ if (item === undefined || itemIsDisabled(item.ref))
684
+ continue;
685
+ newItem = item.ref;
686
+ break;
687
+ }
688
+ }
689
+ break;
690
+ }
691
+ return newItem;
692
+ }
693
+ #calculateOffset(selected, newSelected) {
694
+ if (newSelected === null)
695
+ return 0;
696
+ const items = this.getValidItems();
697
+ const ogIndex = items.findIndex((item) => item === selected);
698
+ const newIndex = items.findIndex((item) => item === newSelected);
699
+ return newIndex - ogIndex;
700
+ }
701
+ #up(e) {
702
+ if (this.opts.columns.current === null)
703
+ return;
704
+ e.preventDefault();
705
+ if (e.metaKey) {
706
+ this.updateSelectedByGroup(-1);
707
+ }
708
+ else {
709
+ this.updateSelectedByItem(this.#previousRowColumnOffset(e));
710
+ }
711
+ }
712
+ #previousRowColumnOffset(e) {
713
+ const grid = this.itemsGrid;
714
+ const selected = this.#getSelectedItem();
715
+ if (selected === undefined)
716
+ return 0;
717
+ const column = this.#getColumn(selected, grid);
718
+ if (column === null)
719
+ return 0;
720
+ let newItem = null;
721
+ const skipRows = e.altKey ? 1 : 0;
722
+ // if this is the second row then we need to go to the top when skipping and not in loop mode
723
+ if (e.altKey && column.rowIndex === 1 && this.opts.loop.current === false) {
724
+ newItem = this.#findNextNonDisabledItemDesc({
725
+ start: 0,
726
+ end: 0,
727
+ expectedColumnIndex: column.columnIndex,
728
+ grid,
729
+ });
730
+ }
731
+ else if (column.rowIndex === 0) {
732
+ // if this is the last row we apply the loop logic
733
+ if (this.opts.loop.current === false)
734
+ return 0;
735
+ newItem = this.#findNextNonDisabledItemDesc({
736
+ start: grid.length - 1 - skipRows,
737
+ end: column.rowIndex + 1,
738
+ expectedColumnIndex: column.columnIndex,
739
+ grid,
740
+ });
741
+ }
742
+ else {
743
+ newItem = this.#findNextNonDisabledItemDesc({
744
+ start: column.rowIndex - 1 - skipRows,
745
+ end: 0,
746
+ expectedColumnIndex: column.columnIndex,
747
+ grid,
748
+ });
749
+ // this happens if there were no non-disabled columns below the current column
750
+ // we can now try starting from the beginning to find the right column
751
+ if (newItem === null && this.opts.loop.current) {
752
+ newItem = this.#findNextNonDisabledItemDesc({
753
+ start: grid.length - 1,
754
+ end: column.rowIndex + 1,
755
+ expectedColumnIndex: column.columnIndex,
756
+ grid,
757
+ });
758
+ }
759
+ }
760
+ return this.#calculateOffset(selected, newItem);
761
+ }
762
+ /**
763
+ * Attempts to find the next non-disabled column that matches the expected column.
764
+ *
765
+ * @remarks
766
+ * - Skips over disabled columns
767
+ * - When a row is shorter than the expected column it defaults to the last item in the row
768
+ */
769
+ #findNextNonDisabledItemDesc({ start, end, grid, expectedColumnIndex, }) {
770
+ let newItem = null;
771
+ for (let r = start; r >= end; r--) {
772
+ const row = grid[r];
773
+ if (row === undefined)
774
+ continue;
775
+ // try to get the next column
776
+ newItem = row[expectedColumnIndex]?.ref ?? null;
777
+ // skip over disabled items
778
+ if (newItem !== null && itemIsDisabled(newItem)) {
779
+ newItem = null;
780
+ continue;
781
+ }
782
+ // if that column doesn't exist default to the next highest column
783
+ if (newItem === null) {
784
+ // try and find the next highest non-disabled item in the row
785
+ // if there aren't any non-disabled items we just give up and return null
786
+ for (let i = row.length - 1; i >= 0; i--) {
787
+ const item = row[row.length - 1];
788
+ if (item === undefined || itemIsDisabled(item.ref))
789
+ continue;
790
+ newItem = item.ref;
791
+ break;
792
+ }
793
+ }
794
+ break;
795
+ }
796
+ return newItem;
797
+ }
498
798
  /**
499
799
  * Handles previous item selection:
500
800
  * - Meta: Jump to first
@@ -519,27 +819,74 @@ class CommandRootState {
519
819
  }
520
820
  }
521
821
  onkeydown(e) {
822
+ const isVim = this.opts.vimBindings.current && e.ctrlKey;
522
823
  switch (e.key) {
523
824
  case kbd.n:
524
825
  case kbd.j: {
525
826
  // vim down
526
- if (this.opts.vimBindings.current && e.ctrlKey) {
527
- this.#next(e);
827
+ if (isVim) {
828
+ if (this.isGrid) {
829
+ this.#down(e);
830
+ }
831
+ else {
832
+ this.#next(e);
833
+ }
834
+ }
835
+ break;
836
+ }
837
+ case kbd.l: {
838
+ // vim right
839
+ if (isVim) {
840
+ if (this.isGrid) {
841
+ this.#next(e);
842
+ }
528
843
  }
529
844
  break;
530
845
  }
531
846
  case kbd.ARROW_DOWN:
847
+ if (this.isGrid) {
848
+ this.#down(e);
849
+ }
850
+ else {
851
+ this.#next(e);
852
+ }
853
+ break;
854
+ case kbd.ARROW_RIGHT:
855
+ if (!this.isGrid)
856
+ break;
532
857
  this.#next(e);
533
858
  break;
534
859
  case kbd.p:
535
860
  case kbd.k: {
536
861
  // vim up
537
- if (this.opts.vimBindings.current && e.ctrlKey) {
862
+ if (isVim) {
863
+ if (this.isGrid) {
864
+ this.#up(e);
865
+ }
866
+ else {
867
+ this.#prev(e);
868
+ }
869
+ }
870
+ break;
871
+ }
872
+ case kbd.h: {
873
+ // vim left
874
+ if (isVim && this.isGrid) {
538
875
  this.#prev(e);
539
876
  }
540
877
  break;
541
878
  }
542
879
  case kbd.ARROW_UP:
880
+ if (this.isGrid) {
881
+ this.#up(e);
882
+ }
883
+ else {
884
+ this.#prev(e);
885
+ }
886
+ break;
887
+ case kbd.ARROW_LEFT:
888
+ if (!this.isGrid)
889
+ break;
543
890
  this.#prev(e);
544
891
  break;
545
892
  case kbd.HOME:
@@ -578,6 +925,9 @@ class CommandRootState {
578
925
  ...attachRef(this.opts.ref),
579
926
  }));
580
927
  }
928
+ function itemIsDisabled(item) {
929
+ return item.getAttribute("aria-disabled") === "true";
930
+ }
581
931
  class CommandEmptyState {
582
932
  opts;
583
933
  root;
@@ -678,9 +1028,9 @@ class CommandInputState {
678
1028
  root;
679
1029
  #selectedItemId = $derived.by(() => {
680
1030
  const item = this.root.viewportNode?.querySelector(`${COMMAND_ITEM_SELECTOR}[${COMMAND_VALUE_ATTR}="${cssesc(this.root.opts.value.current)}"]`);
681
- if (!item)
1031
+ if (item === undefined || item === null)
682
1032
  return;
683
- return item?.getAttribute("id") ?? undefined;
1033
+ return item.getAttribute("id") ?? undefined;
684
1034
  });
685
1035
  constructor(opts, root) {
686
1036
  this.opts = opts;
@@ -787,6 +1137,7 @@ class CommandItemState {
787
1137
  "data-disabled": getDataDisabled(this.opts.disabled.current),
788
1138
  "data-selected": getDataSelected(this.isSelected),
789
1139
  "data-value": this.trueValue,
1140
+ "data-group": this.#group?.trueValue,
790
1141
  [commandAttrs.item]: "",
791
1142
  role: "option",
792
1143
  onpointermove: this.onpointermove,
@@ -865,7 +1216,7 @@ class CommandViewportState {
865
1216
  $effect(() => {
866
1217
  const node = this.opts.ref.current;
867
1218
  const listNode = this.list.opts.ref.current;
868
- if (!node || !listNode)
1219
+ if (node === null || listNode === null)
869
1220
  return;
870
1221
  let aF;
871
1222
  const observer = new ResizeObserver(() => {
@@ -21,6 +21,8 @@
21
21
  label = "",
22
22
  vimBindings = true,
23
23
  disablePointerSelection = false,
24
+ disableInitialScroll = false,
25
+ columns = null,
24
26
  children,
25
27
  child,
26
28
  ...restProps
@@ -46,7 +48,9 @@
46
48
  ),
47
49
  vimBindings: box.with(() => vimBindings),
48
50
  disablePointerSelection: box.with(() => disablePointerSelection),
51
+ disableInitialScroll: box.with(() => disableInitialScroll),
49
52
  onStateChange: box.with(() => onStateChange),
53
+ columns: box.with(() => columns),
50
54
  });
51
55
 
52
56
  // Imperative APIs - DO NOT REMOVE OR RENAME
@@ -47,7 +47,7 @@ declare const Command: import("svelte").Component<CommandRootProps, {
47
47
  *
48
48
  * // get all valid items
49
49
  * const items = getValidItems()
50
- */ updateSelectedByItem: (change: 1 | -1) => void;
50
+ */ updateSelectedByItem: (change: number) => void;
51
51
  /**
52
52
  * Gets all non-disabled, visible command items.
53
53
  *
@@ -61,9 +61,23 @@ export type CommandRootPropsWithoutHTML = WithChild<{
61
61
  /**
62
62
  * Set this prop to `false` to disable the option to use ctrl+n/j/p/k (vim style) navigation.
63
63
  *
64
- * @defaultValue true
64
+ * @default true
65
65
  */
66
66
  vimBindings?: boolean;
67
+ /**
68
+ * The number of columns in a grid layout.
69
+ *
70
+ * @default null
71
+ */
72
+ columns?: number | null;
73
+ /**
74
+ * Whether to disable scrolling the selected item into view on initial mount.
75
+ * When `true`, prevents automatic scrolling when the command menu first renders
76
+ * and selects its first item, but still allows scrolling on subsequent selections.
77
+ *
78
+ * @default false
79
+ */
80
+ disableInitialScroll?: boolean;
67
81
  }>;
68
82
  export type CommandRootProps = CommandRootPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, CommandRootPropsWithoutHTML>;
69
83
  export type CommandEmptyPropsWithoutHTML = WithChild<{
@@ -94,7 +108,7 @@ export type CommandItemPropsWithoutHTML = WithChild<{
94
108
  /**
95
109
  * Whether the item is disabled.
96
110
  *
97
- * @defaultValue false
111
+ * @default false
98
112
  */
99
113
  disabled?: boolean;
100
114
  /**
@@ -85,6 +85,7 @@ declare class TabsContentState {
85
85
  readonly "data-value": string;
86
86
  readonly "data-state": "active" | "inactive";
87
87
  readonly "aria-labelledby": string | undefined;
88
+ readonly "data-orientation": "horizontal" | "vertical";
88
89
  };
89
90
  }
90
91
  export declare function useTabsRoot(props: TabsRootStateProps): TabsRootState;
@@ -159,6 +159,7 @@ class TabsContentState {
159
159
  "data-value": this.opts.value.current,
160
160
  "data-state": getTabDataState(this.#isActive),
161
161
  "aria-labelledby": this.#ariaLabelledBy,
162
+ "data-orientation": getDataOrientation(this.root.opts.orientation.current),
162
163
  [tabsAttrs.content]: "",
163
164
  ...attachRef(this.opts.ref),
164
165
  }));
@@ -16,7 +16,9 @@ export function useFloating(options) {
16
16
  let x = $state(0);
17
17
  let y = $state(0);
18
18
  const floating = box(null);
19
+ // svelte-ignore state_referenced_locally
19
20
  let strategy = $state(strategyOption);
21
+ // svelte-ignore state_referenced_locally
20
22
  let placement = $state(placementOption);
21
23
  let middlewareData = $state({});
22
24
  let isPositioned = $state(false);
@@ -38,3 +38,5 @@ export declare const p = "p";
38
38
  export declare const n = "n";
39
39
  export declare const j = "j";
40
40
  export declare const k = "k";
41
+ export declare const h = "h";
42
+ export declare const l = "l";
@@ -38,3 +38,5 @@ export const p = "p";
38
38
  export const n = "n";
39
39
  export const j = "j";
40
40
  export const k = "k";
41
+ export const h = "h";
42
+ export const l = "l";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bits-ui",
3
- "version": "2.5.0",
3
+ "version": "2.6.1",
4
4
  "license": "MIT",
5
5
  "repository": "github:huntabyte/bits-ui",
6
6
  "funding": "https://github.com/sponsors/huntabyte",