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.
- package/dist/bits/command/command.svelte.d.ts +26 -2
- package/dist/bits/command/command.svelte.js +378 -27
- package/dist/bits/command/components/command.svelte +4 -0
- package/dist/bits/command/components/command.svelte.d.ts +1 -1
- package/dist/bits/command/types.d.ts +16 -2
- package/dist/bits/tabs/tabs.svelte.d.ts +1 -0
- package/dist/bits/tabs/tabs.svelte.js +1 -0
- package/dist/internal/floating-svelte/use-floating.svelte.js +2 -0
- package/dist/internal/kbd-constants.d.ts +2 -0
- package/dist/internal/kbd-constants.js +2 -0
- package/package.json +1 -1
|
@@ -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],
|
|
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:
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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 (
|
|
527
|
-
this
|
|
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 (
|
|
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 (
|
|
1031
|
+
if (item === undefined || item === null)
|
|
682
1032
|
return;
|
|
683
|
-
return item
|
|
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 (
|
|
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:
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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);
|