bits-ui 1.3.10 → 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.
- package/dist/bits/alert-dialog/components/alert-dialog-content.svelte +4 -2
- package/dist/bits/command/command.svelte.d.ts +2 -1
- package/dist/bits/command/command.svelte.js +32 -14
- package/dist/bits/command/components/command-item.svelte +1 -1
- package/dist/bits/command/components/command-list.svelte +9 -7
- package/dist/bits/context-menu/components/context-menu-content.svelte +3 -0
- package/dist/bits/context-menu/types.d.ts +1 -1
- package/dist/bits/dialog/dialog.svelte.d.ts +5 -0
- package/dist/bits/dialog/dialog.svelte.js +5 -0
- package/dist/bits/radio-group/radio-group.svelte.js +8 -2
- package/dist/bits/select/select.svelte.d.ts +3 -1
- package/dist/bits/select/select.svelte.js +2 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { box, mergeProps } from "svelte-toolbelt";
|
|
2
|
+
import { afterTick, box, mergeProps } from "svelte-toolbelt";
|
|
3
3
|
import type { AlertDialogContentProps } from "../types.js";
|
|
4
4
|
import DismissibleLayer from "../../utilities/dismissible-layer/dismissible-layer.svelte";
|
|
5
5
|
import EscapeLayer from "../../utilities/escape-layer/escape-layer.svelte";
|
|
@@ -64,7 +64,9 @@
|
|
|
64
64
|
onOpenAutoFocus(e);
|
|
65
65
|
if (e.defaultPrevented) return;
|
|
66
66
|
e.preventDefault();
|
|
67
|
-
|
|
67
|
+
afterTick(() => {
|
|
68
|
+
contentState.opts.ref.current?.focus();
|
|
69
|
+
});
|
|
68
70
|
}}
|
|
69
71
|
>
|
|
70
72
|
{#snippet focusScope({ props: focusScopeProps })}
|
|
@@ -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
|
|
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.
|
|
@@ -258,7 +268,7 @@ class CommandRootState {
|
|
|
258
268
|
const node = this.opts.ref.current;
|
|
259
269
|
if (!node)
|
|
260
270
|
return;
|
|
261
|
-
const selectedNode = node.querySelector(`${COMMAND_VALID_ITEM_SELECTOR}[
|
|
271
|
+
const selectedNode = node.querySelector(`${COMMAND_VALID_ITEM_SELECTOR}[data-selected]`);
|
|
262
272
|
if (!selectedNode)
|
|
263
273
|
return;
|
|
264
274
|
return selectedNode;
|
|
@@ -268,7 +278,7 @@ class CommandRootState {
|
|
|
268
278
|
* Special handling for first items in groups.
|
|
269
279
|
*/
|
|
270
280
|
#scrollSelectedIntoView() {
|
|
271
|
-
|
|
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
|
-
|
|
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(() =>
|
|
562
|
-
this.
|
|
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
|
-
|
|
617
|
+
watch(() => this.opts.id.current, () => {
|
|
603
618
|
return this.root.registerGroup(this.opts.id.current);
|
|
604
619
|
});
|
|
605
620
|
$effect(() => {
|
|
@@ -662,7 +677,7 @@ class CommandInputState {
|
|
|
662
677
|
opts;
|
|
663
678
|
root;
|
|
664
679
|
#selectedItemId = $derived.by(() => {
|
|
665
|
-
const item = this.root.viewportNode?.querySelector(`${COMMAND_ITEM_SELECTOR}[${COMMAND_VALUE_ATTR}="${encodeURIComponent(this.opts.value.current)}"]`);
|
|
680
|
+
const item = this.root.viewportNode?.querySelector(`${COMMAND_ITEM_SELECTOR}[${COMMAND_VALUE_ATTR}="${encodeURIComponent(this.root.opts.value.current)}"]`);
|
|
666
681
|
if (!item)
|
|
667
682
|
return;
|
|
668
683
|
return item?.getAttribute("id") ?? undefined;
|
|
@@ -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.
|
|
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
|
|
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={
|
|
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
|
-
{#
|
|
29
|
-
{
|
|
30
|
-
{:
|
|
31
|
-
|
|
32
|
-
{
|
|
33
|
-
|
|
34
|
-
|
|
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}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
loop = true,
|
|
18
18
|
onInteractOutside = noop,
|
|
19
19
|
onCloseAutoFocus = noop,
|
|
20
|
+
onOpenAutoFocus = noop,
|
|
20
21
|
preventScroll = true,
|
|
21
22
|
// we need to explicitly pass this prop to the PopperLayer to override
|
|
22
23
|
// the default menu behavior of handling outside interactions on the trigger
|
|
@@ -73,6 +74,7 @@
|
|
|
73
74
|
{preventScroll}
|
|
74
75
|
onInteractOutside={handleInteractOutside}
|
|
75
76
|
onEscapeKeydown={handleEscapeKeydown}
|
|
77
|
+
{onOpenAutoFocus}
|
|
76
78
|
{isValidEvent}
|
|
77
79
|
trapFocus
|
|
78
80
|
{loop}
|
|
@@ -105,6 +107,7 @@
|
|
|
105
107
|
{preventScroll}
|
|
106
108
|
onInteractOutside={handleInteractOutside}
|
|
107
109
|
onEscapeKeydown={handleEscapeKeydown}
|
|
110
|
+
{onOpenAutoFocus}
|
|
108
111
|
{isValidEvent}
|
|
109
112
|
trapFocus
|
|
110
113
|
{loop}
|
|
@@ -2,7 +2,7 @@ import type { MenuContentProps, MenuContentPropsWithoutHTML } from "../menu/type
|
|
|
2
2
|
import type { WithChild, Without } from "../../internal/types.js";
|
|
3
3
|
import type { BitsPrimitiveDivAttributes } from "../../shared/attributes.js";
|
|
4
4
|
export type ContextMenuContentPropsWithoutHTML = Omit<MenuContentPropsWithoutHTML, "align" | "side" | "sideOffset">;
|
|
5
|
-
export type ContextMenuContentProps = Omit<MenuContentProps, "side" | "
|
|
5
|
+
export type ContextMenuContentProps = Omit<MenuContentProps, "side" | "sideOffset" | "align">;
|
|
6
6
|
export type ContextMenuTriggerPropsWithoutHTML = WithChild<{
|
|
7
7
|
/**
|
|
8
8
|
* Whether the context menu trigger is disabled. If disabled, the trigger will not
|
|
@@ -71,6 +71,7 @@ declare class DialogCloseState {
|
|
|
71
71
|
readonly onclick: (e: BitsMouseEvent) => void;
|
|
72
72
|
readonly onkeydown: (e: BitsKeyboardEvent) => void;
|
|
73
73
|
readonly disabled: true | undefined;
|
|
74
|
+
readonly tabindex: 0;
|
|
74
75
|
};
|
|
75
76
|
}
|
|
76
77
|
type DialogActionStateProps = WithRefProps;
|
|
@@ -120,11 +121,14 @@ declare class DialogContentState {
|
|
|
120
121
|
readonly "data-state": "open" | "closed";
|
|
121
122
|
readonly id: string;
|
|
122
123
|
readonly role: "dialog" | "alertdialog";
|
|
124
|
+
readonly "aria-modal": "true";
|
|
123
125
|
readonly "aria-describedby": string | undefined;
|
|
124
126
|
readonly "aria-labelledby": string | undefined;
|
|
125
127
|
readonly style: {
|
|
126
128
|
readonly pointerEvents: "auto";
|
|
129
|
+
readonly outline: "none" | undefined;
|
|
127
130
|
};
|
|
131
|
+
readonly tabindex: -1 | undefined;
|
|
128
132
|
};
|
|
129
133
|
}
|
|
130
134
|
type DialogOverlayStateProps = WithRefProps;
|
|
@@ -157,6 +161,7 @@ declare class AlertDialogCancelState {
|
|
|
157
161
|
readonly id: string;
|
|
158
162
|
readonly onclick: (e: BitsMouseEvent) => void;
|
|
159
163
|
readonly onkeydown: (e: BitsKeyboardEvent) => void;
|
|
164
|
+
readonly tabindex: 0;
|
|
160
165
|
};
|
|
161
166
|
}
|
|
162
167
|
export declare function useDialogRoot(props: DialogRootStateProps): DialogRootState;
|
|
@@ -123,6 +123,7 @@ class DialogCloseState {
|
|
|
123
123
|
onclick: this.onclick,
|
|
124
124
|
onkeydown: this.onkeydown,
|
|
125
125
|
disabled: this.opts.disabled.current ? true : undefined,
|
|
126
|
+
tabindex: 0,
|
|
126
127
|
...this.root.sharedProps,
|
|
127
128
|
}));
|
|
128
129
|
}
|
|
@@ -204,12 +205,15 @@ class DialogContentState {
|
|
|
204
205
|
props = $derived.by(() => ({
|
|
205
206
|
id: this.opts.id.current,
|
|
206
207
|
role: this.root.opts.variant.current === "alert-dialog" ? "alertdialog" : "dialog",
|
|
208
|
+
"aria-modal": "true",
|
|
207
209
|
"aria-describedby": this.root.descriptionId,
|
|
208
210
|
"aria-labelledby": this.root.titleId,
|
|
209
211
|
[this.root.attrs.content]: "",
|
|
210
212
|
style: {
|
|
211
213
|
pointerEvents: "auto",
|
|
214
|
+
outline: this.root.opts.variant.current === "alert-dialog" ? "none" : undefined,
|
|
212
215
|
},
|
|
216
|
+
tabindex: this.root.opts.variant.current === "alert-dialog" ? -1 : undefined,
|
|
213
217
|
...this.root.sharedProps,
|
|
214
218
|
}));
|
|
215
219
|
}
|
|
@@ -270,6 +274,7 @@ class AlertDialogCancelState {
|
|
|
270
274
|
[this.root.attrs.cancel]: "",
|
|
271
275
|
onclick: this.onclick,
|
|
272
276
|
onkeydown: this.onkeydown,
|
|
277
|
+
tabindex: 0,
|
|
273
278
|
...this.root.sharedProps,
|
|
274
279
|
}));
|
|
275
280
|
}
|
|
@@ -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,
|
|
@@ -138,6 +138,7 @@ declare class SelectTriggerState {
|
|
|
138
138
|
readonly id: string;
|
|
139
139
|
readonly disabled: true | undefined;
|
|
140
140
|
readonly "aria-haspopup": "listbox";
|
|
141
|
+
readonly "aria-expanded": "true" | "false";
|
|
141
142
|
readonly "aria-activedescendant": string | undefined;
|
|
142
143
|
readonly "data-state": "open" | "closed";
|
|
143
144
|
readonly "data-disabled": "" | undefined;
|
|
@@ -174,9 +175,10 @@ declare class SelectContentState {
|
|
|
174
175
|
readonly outline: "none";
|
|
175
176
|
readonly boxSizing: "border-box";
|
|
176
177
|
readonly pointerEvents: "auto";
|
|
177
|
-
};
|
|
178
|
+
} | undefined;
|
|
178
179
|
readonly id: string;
|
|
179
180
|
readonly role: "listbox";
|
|
181
|
+
readonly "aria-multiselectable": "true" | undefined;
|
|
180
182
|
readonly "data-state": "open" | "closed";
|
|
181
183
|
readonly style: {
|
|
182
184
|
readonly display: "flex";
|
|
@@ -589,6 +589,7 @@ class SelectTriggerState {
|
|
|
589
589
|
id: this.opts.id.current,
|
|
590
590
|
disabled: this.root.opts.disabled.current ? true : undefined,
|
|
591
591
|
"aria-haspopup": "listbox",
|
|
592
|
+
"aria-expanded": getAriaExpanded(this.root.opts.open.current),
|
|
592
593
|
"aria-activedescendant": this.root.highlightedId,
|
|
593
594
|
"data-state": getDataOpenClosed(this.root.opts.open.current),
|
|
594
595
|
"data-disabled": getDataDisabled(this.root.opts.disabled.current),
|
|
@@ -665,6 +666,7 @@ class SelectContentState {
|
|
|
665
666
|
props = $derived.by(() => ({
|
|
666
667
|
id: this.opts.id.current,
|
|
667
668
|
role: "listbox",
|
|
669
|
+
"aria-multiselectable": this.root.isMulti ? "true" : undefined,
|
|
668
670
|
"data-state": getDataOpenClosed(this.root.opts.open.current),
|
|
669
671
|
[this.root.bitsAttrs.content]: "",
|
|
670
672
|
style: {
|