bits-ui 2.9.5 → 2.9.7
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 +1 -5
- package/dist/bits/checkbox/checkbox.svelte.d.ts +3 -1
- package/dist/bits/checkbox/checkbox.svelte.js +8 -0
- package/dist/bits/command/command.svelte.js +9 -5
- package/dist/bits/dialog/components/dialog-content.svelte +2 -6
- package/dist/bits/popover/components/popover-content-static.svelte +2 -2
- package/dist/bits/popover/components/popover-content.svelte +2 -1
- package/dist/bits/popover/popover.svelte.d.ts +0 -3
- package/dist/bits/popover/popover.svelte.js +0 -8
- package/dist/bits/radio-group/radio-group.svelte.d.ts +3 -1
- package/dist/bits/radio-group/radio-group.svelte.js +8 -3
- package/dist/bits/utilities/focus-scope/focus-scope-manager.d.ts +3 -0
- package/dist/bits/utilities/focus-scope/focus-scope-manager.js +15 -0
- package/dist/bits/utilities/focus-scope/focus-scope.svelte.js +12 -4
- package/dist/internal/roving-focus-group.d.ts +1 -0
- package/dist/internal/roving-focus-group.js +10 -0
- package/dist/internal/warn.d.ts +1 -0
- package/dist/internal/warn.js +14 -0
- package/dist/shared/date/types.d.ts +1 -1
- package/package.json +1 -1
|
@@ -57,11 +57,7 @@
|
|
|
57
57
|
present: contentState.root.opts.open.current,
|
|
58
58
|
open: contentState.root.opts.open.current,
|
|
59
59
|
})}
|
|
60
|
-
onCloseAutoFocus
|
|
61
|
-
onCloseAutoFocus(e);
|
|
62
|
-
if (e.defaultPrevented) return;
|
|
63
|
-
afterSleep(0, () => contentState.root.triggerNode?.focus());
|
|
64
|
-
}}
|
|
60
|
+
{onCloseAutoFocus}
|
|
65
61
|
onOpenAutoFocus={(e) => {
|
|
66
62
|
onOpenAutoFocus(e);
|
|
67
63
|
if (e.defaultPrevented) return;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ReadableBoxedValues, type WritableBoxedValues } from "svelte-toolbelt";
|
|
2
2
|
import type { HTMLButtonAttributes } from "svelte/elements";
|
|
3
3
|
import { Context } from "runed";
|
|
4
|
-
import type { BitsKeyboardEvent, BitsMouseEvent, OnChangeFn, RefAttachment, WithRefOpts } from "../../internal/types.js";
|
|
4
|
+
import type { BitsFocusEvent, BitsKeyboardEvent, BitsMouseEvent, OnChangeFn, RefAttachment, WithRefOpts } from "../../internal/types.js";
|
|
5
5
|
interface CheckboxGroupStateOpts extends WithRefOpts, ReadableBoxedValues<{
|
|
6
6
|
name: string | undefined;
|
|
7
7
|
disabled: boolean;
|
|
@@ -91,6 +91,7 @@ export declare class CheckboxInputState {
|
|
|
91
91
|
readonly trueChecked: boolean;
|
|
92
92
|
readonly shouldRender: boolean;
|
|
93
93
|
constructor(root: CheckboxRootState);
|
|
94
|
+
onfocus(_: BitsFocusEvent): void;
|
|
94
95
|
readonly props: {
|
|
95
96
|
readonly type: "checkbox";
|
|
96
97
|
readonly checked: boolean;
|
|
@@ -99,6 +100,7 @@ export declare class CheckboxInputState {
|
|
|
99
100
|
readonly name: string | undefined;
|
|
100
101
|
readonly value: string | undefined;
|
|
101
102
|
readonly readonly: boolean;
|
|
103
|
+
readonly onfocus: (_: BitsFocusEvent) => void;
|
|
102
104
|
};
|
|
103
105
|
}
|
|
104
106
|
export {};
|
|
@@ -3,6 +3,7 @@ import { Context, watch } from "runed";
|
|
|
3
3
|
import { createBitsAttrs, getAriaChecked, getAriaReadonly, getAriaRequired, getDataDisabled, getDataReadonly, } from "../../internal/attrs.js";
|
|
4
4
|
import { kbd } from "../../internal/kbd.js";
|
|
5
5
|
import { arraysAreEqual } from "../../internal/arrays.js";
|
|
6
|
+
import { isHTMLElement } from "../../internal/is.js";
|
|
6
7
|
const checkboxAttrs = createBitsAttrs({
|
|
7
8
|
component: "checkbox",
|
|
8
9
|
parts: ["root", "group", "group-label", "input"],
|
|
@@ -192,6 +193,12 @@ export class CheckboxInputState {
|
|
|
192
193
|
shouldRender = $derived.by(() => Boolean(this.root.trueName));
|
|
193
194
|
constructor(root) {
|
|
194
195
|
this.root = root;
|
|
196
|
+
this.onfocus = this.onfocus.bind(this);
|
|
197
|
+
}
|
|
198
|
+
onfocus(_) {
|
|
199
|
+
if (!isHTMLElement(this.root.opts.ref.current))
|
|
200
|
+
return;
|
|
201
|
+
this.root.opts.ref.current.focus();
|
|
195
202
|
}
|
|
196
203
|
props = $derived.by(() => ({
|
|
197
204
|
type: "checkbox",
|
|
@@ -201,6 +208,7 @@ export class CheckboxInputState {
|
|
|
201
208
|
name: this.root.trueName,
|
|
202
209
|
value: this.root.opts.value.current,
|
|
203
210
|
readonly: this.root.trueReadonly,
|
|
211
|
+
onfocus: this.onfocus,
|
|
204
212
|
}));
|
|
205
213
|
}
|
|
206
214
|
function getCheckboxDataState(checked, indeterminate) {
|
|
@@ -528,7 +528,6 @@ export class CommandRootState {
|
|
|
528
528
|
this.#scheduleUpdate();
|
|
529
529
|
return () => {
|
|
530
530
|
const selectedItem = this.#getSelectedItem();
|
|
531
|
-
this.allIds.delete(id);
|
|
532
531
|
this.allItems.delete(id);
|
|
533
532
|
this.commandState.filtered.items.delete(id);
|
|
534
533
|
this.#filterItems();
|
|
@@ -1132,16 +1131,21 @@ export class CommandItemState {
|
|
|
1132
1131
|
() => this.#group?.trueValue,
|
|
1133
1132
|
() => this.opts.forceMount.current,
|
|
1134
1133
|
], () => {
|
|
1135
|
-
if (this.opts.forceMount.current)
|
|
1134
|
+
if (this.opts.forceMount.current || !this.trueValue)
|
|
1136
1135
|
return;
|
|
1137
1136
|
return this.root.registerItem(this.trueValue, this.#group?.trueValue);
|
|
1138
1137
|
});
|
|
1139
1138
|
watch([() => this.opts.value.current, () => this.opts.ref.current], () => {
|
|
1140
|
-
if (
|
|
1139
|
+
if (this.opts.value.current) {
|
|
1140
|
+
this.trueValue = this.opts.value.current;
|
|
1141
|
+
}
|
|
1142
|
+
else if (this.opts.ref.current?.textContent) {
|
|
1141
1143
|
this.trueValue = this.opts.ref.current.textContent.trim();
|
|
1142
1144
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
+
if (this.trueValue) {
|
|
1146
|
+
this.root.registerValue(this.trueValue, opts.keywords.current.map((kw) => kw.trim()));
|
|
1147
|
+
this.opts.ref.current?.setAttribute(COMMAND_VALUE_ATTR, this.trueValue);
|
|
1148
|
+
}
|
|
1145
1149
|
});
|
|
1146
1150
|
// bindings
|
|
1147
1151
|
this.onclick = this.onclick.bind(this);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { box, mergeProps } from "svelte-toolbelt";
|
|
3
3
|
import { DialogContentState } from "../dialog.svelte.js";
|
|
4
4
|
import type { DialogContentProps } from "../types.js";
|
|
5
5
|
import DismissibleLayer from "../../utilities/dismissible-layer/dismissible-layer.svelte";
|
|
@@ -57,11 +57,7 @@
|
|
|
57
57
|
open: contentState.root.opts.open.current,
|
|
58
58
|
})}
|
|
59
59
|
{onOpenAutoFocus}
|
|
60
|
-
onCloseAutoFocus
|
|
61
|
-
onCloseAutoFocus(e);
|
|
62
|
-
if (e.defaultPrevented) return;
|
|
63
|
-
afterSleep(1, () => contentState.root.triggerNode?.focus());
|
|
64
|
-
}}
|
|
60
|
+
{onCloseAutoFocus}
|
|
65
61
|
>
|
|
66
62
|
{#snippet focusScope({ props: focusScopeProps })}
|
|
67
63
|
<EscapeLayer
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
onInteractOutside = noop,
|
|
22
22
|
trapFocus = true,
|
|
23
23
|
preventScroll = false,
|
|
24
|
-
|
|
25
24
|
...restProps
|
|
26
25
|
}: PopoverContentStaticProps = $props();
|
|
27
26
|
|
|
@@ -33,7 +32,6 @@
|
|
|
33
32
|
),
|
|
34
33
|
onInteractOutside: box.with(() => onInteractOutside),
|
|
35
34
|
onEscapeKeydown: box.with(() => onEscapeKeydown),
|
|
36
|
-
onCloseAutoFocus: box.with(() => onCloseAutoFocus),
|
|
37
35
|
customAnchor: box.with(() => null),
|
|
38
36
|
});
|
|
39
37
|
|
|
@@ -52,6 +50,7 @@
|
|
|
52
50
|
{preventScroll}
|
|
53
51
|
loop
|
|
54
52
|
forceMount={true}
|
|
53
|
+
{onCloseAutoFocus}
|
|
55
54
|
>
|
|
56
55
|
{#snippet popper({ props })}
|
|
57
56
|
{@const finalProps = mergeProps(props, {
|
|
@@ -78,6 +77,7 @@
|
|
|
78
77
|
{preventScroll}
|
|
79
78
|
loop
|
|
80
79
|
forceMount={false}
|
|
80
|
+
{onCloseAutoFocus}
|
|
81
81
|
>
|
|
82
82
|
{#snippet popper({ props })}
|
|
83
83
|
{@const finalProps = mergeProps(props, {
|
|
@@ -33,7 +33,6 @@
|
|
|
33
33
|
),
|
|
34
34
|
onInteractOutside: box.with(() => onInteractOutside),
|
|
35
35
|
onEscapeKeydown: box.with(() => onEscapeKeydown),
|
|
36
|
-
onCloseAutoFocus: box.with(() => onCloseAutoFocus),
|
|
37
36
|
customAnchor: box.with(() => customAnchor),
|
|
38
37
|
});
|
|
39
38
|
|
|
@@ -52,6 +51,7 @@
|
|
|
52
51
|
loop
|
|
53
52
|
forceMount={true}
|
|
54
53
|
{customAnchor}
|
|
54
|
+
{onCloseAutoFocus}
|
|
55
55
|
>
|
|
56
56
|
{#snippet popper({ props, wrapperProps })}
|
|
57
57
|
{@const finalProps = mergeProps(props, {
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
loop
|
|
81
81
|
forceMount={false}
|
|
82
82
|
{customAnchor}
|
|
83
|
+
{onCloseAutoFocus}
|
|
83
84
|
>
|
|
84
85
|
{#snippet popper({ props, wrapperProps })}
|
|
85
86
|
{@const finalProps = mergeProps(props, {
|
|
@@ -43,7 +43,6 @@ export declare class PopoverTriggerState {
|
|
|
43
43
|
interface PopoverContentStateOpts extends WithRefOpts, ReadableBoxedValues<{
|
|
44
44
|
onInteractOutside: (e: PointerEvent) => void;
|
|
45
45
|
onEscapeKeydown: (e: KeyboardEvent) => void;
|
|
46
|
-
onCloseAutoFocus: (e: Event) => void;
|
|
47
46
|
customAnchor: string | HTMLElement | null | Measurable;
|
|
48
47
|
}> {
|
|
49
48
|
}
|
|
@@ -55,7 +54,6 @@ export declare class PopoverContentState {
|
|
|
55
54
|
constructor(opts: PopoverContentStateOpts, root: PopoverRootState);
|
|
56
55
|
onInteractOutside: (e: PointerEvent) => void;
|
|
57
56
|
onEscapeKeydown: (e: KeyboardEvent) => void;
|
|
58
|
-
onCloseAutoFocus: (e: Event) => void;
|
|
59
57
|
readonly snippetProps: {
|
|
60
58
|
open: boolean;
|
|
61
59
|
};
|
|
@@ -70,7 +68,6 @@ export declare class PopoverContentState {
|
|
|
70
68
|
readonly popperProps: {
|
|
71
69
|
onInteractOutside: (e: PointerEvent) => void;
|
|
72
70
|
onEscapeKeydown: (e: KeyboardEvent) => void;
|
|
73
|
-
onCloseAutoFocus: (e: Event) => void;
|
|
74
71
|
};
|
|
75
72
|
}
|
|
76
73
|
interface PopoverCloseStateOpts extends WithRefOpts {
|
|
@@ -124,13 +124,6 @@ export class PopoverContentState {
|
|
|
124
124
|
return;
|
|
125
125
|
this.root.handleClose();
|
|
126
126
|
};
|
|
127
|
-
onCloseAutoFocus = (e) => {
|
|
128
|
-
this.opts.onCloseAutoFocus.current?.(e);
|
|
129
|
-
if (e.defaultPrevented)
|
|
130
|
-
return;
|
|
131
|
-
e.preventDefault();
|
|
132
|
-
this.root.triggerNode?.focus();
|
|
133
|
-
};
|
|
134
127
|
snippetProps = $derived.by(() => ({ open: this.root.opts.open.current }));
|
|
135
128
|
props = $derived.by(() => ({
|
|
136
129
|
id: this.opts.id.current,
|
|
@@ -145,7 +138,6 @@ export class PopoverContentState {
|
|
|
145
138
|
popperProps = {
|
|
146
139
|
onInteractOutside: this.onInteractOutside,
|
|
147
140
|
onEscapeKeydown: this.onEscapeKeydown,
|
|
148
|
-
onCloseAutoFocus: this.onCloseAutoFocus,
|
|
149
141
|
};
|
|
150
142
|
}
|
|
151
143
|
export class PopoverCloseState {
|
|
@@ -73,12 +73,14 @@ export declare class RadioGroupInputState {
|
|
|
73
73
|
static create(): RadioGroupInputState;
|
|
74
74
|
readonly root: RadioGroupRootState;
|
|
75
75
|
readonly shouldRender: boolean;
|
|
76
|
+
constructor(root: RadioGroupRootState);
|
|
77
|
+
onfocus(_: BitsFocusEvent): void;
|
|
76
78
|
readonly props: {
|
|
77
79
|
readonly name: string | undefined;
|
|
78
80
|
readonly value: string;
|
|
79
81
|
readonly required: boolean;
|
|
80
82
|
readonly disabled: boolean;
|
|
83
|
+
readonly onfocus: (_: BitsFocusEvent) => void;
|
|
81
84
|
};
|
|
82
|
-
constructor(root: RadioGroupRootState);
|
|
83
85
|
}
|
|
84
86
|
export {};
|
|
@@ -130,13 +130,18 @@ export class RadioGroupInputState {
|
|
|
130
130
|
}
|
|
131
131
|
root;
|
|
132
132
|
shouldRender = $derived.by(() => this.root.opts.name.current !== undefined);
|
|
133
|
+
constructor(root) {
|
|
134
|
+
this.root = root;
|
|
135
|
+
this.onfocus = this.onfocus.bind(this);
|
|
136
|
+
}
|
|
137
|
+
onfocus(_) {
|
|
138
|
+
this.root.rovingFocusGroup.focusCurrentTabStop();
|
|
139
|
+
}
|
|
133
140
|
props = $derived.by(() => ({
|
|
134
141
|
name: this.root.opts.name.current,
|
|
135
142
|
value: this.root.opts.value.current,
|
|
136
143
|
required: this.root.opts.required.current,
|
|
137
144
|
disabled: this.root.opts.disabled.current,
|
|
145
|
+
onfocus: this.onfocus,
|
|
138
146
|
}));
|
|
139
|
-
constructor(root) {
|
|
140
|
-
this.root = root;
|
|
141
|
-
}
|
|
142
147
|
}
|
|
@@ -9,4 +9,7 @@ export declare class FocusScopeManager {
|
|
|
9
9
|
setFocusMemory(scope: FocusScope, element: HTMLElement): void;
|
|
10
10
|
getFocusMemory(scope: FocusScope): HTMLElement | undefined;
|
|
11
11
|
isActiveScope(scope: FocusScope): boolean;
|
|
12
|
+
setPreFocusMemory(scope: FocusScope, element: HTMLElement): void;
|
|
13
|
+
getPreFocusMemory(scope: FocusScope): HTMLElement | undefined;
|
|
14
|
+
clearPreFocusMemory(scope: FocusScope): void;
|
|
12
15
|
}
|
|
@@ -4,6 +4,7 @@ export class FocusScopeManager {
|
|
|
4
4
|
static instance;
|
|
5
5
|
#scopeStack = box([]);
|
|
6
6
|
#focusHistory = new WeakMap();
|
|
7
|
+
#preFocusHistory = new WeakMap();
|
|
7
8
|
static getInstance() {
|
|
8
9
|
if (!this.instance) {
|
|
9
10
|
this.instance = new FocusScopeManager();
|
|
@@ -15,6 +16,11 @@ export class FocusScopeManager {
|
|
|
15
16
|
if (current && current !== scope) {
|
|
16
17
|
current.pause();
|
|
17
18
|
}
|
|
19
|
+
// capture the currently focused element before this scope becomes active
|
|
20
|
+
const activeElement = document.activeElement;
|
|
21
|
+
if (activeElement && activeElement !== document.body) {
|
|
22
|
+
this.#preFocusHistory.set(scope, activeElement);
|
|
23
|
+
}
|
|
18
24
|
this.#scopeStack.current = this.#scopeStack.current.filter((s) => s !== scope);
|
|
19
25
|
this.#scopeStack.current.unshift(scope);
|
|
20
26
|
}
|
|
@@ -37,4 +43,13 @@ export class FocusScopeManager {
|
|
|
37
43
|
isActiveScope(scope) {
|
|
38
44
|
return this.getActive() === scope;
|
|
39
45
|
}
|
|
46
|
+
setPreFocusMemory(scope, element) {
|
|
47
|
+
this.#preFocusHistory.set(scope, element);
|
|
48
|
+
}
|
|
49
|
+
getPreFocusMemory(scope) {
|
|
50
|
+
return this.#preFocusHistory.get(scope);
|
|
51
|
+
}
|
|
52
|
+
clearPreFocusMemory(scope) {
|
|
53
|
+
this.#preFocusHistory.delete(scope);
|
|
54
|
+
}
|
|
40
55
|
}
|
|
@@ -43,6 +43,7 @@ export class FocusScope {
|
|
|
43
43
|
// handle close auto-focus
|
|
44
44
|
this.#handleCloseAutoFocus();
|
|
45
45
|
this.#manager.unregister(this);
|
|
46
|
+
this.#manager.clearPreFocusMemory(this);
|
|
46
47
|
this.#container = null;
|
|
47
48
|
}
|
|
48
49
|
#handleOpenAutoFocus() {
|
|
@@ -75,10 +76,17 @@ export class FocusScope {
|
|
|
75
76
|
});
|
|
76
77
|
this.#opts.onCloseAutoFocus.current?.(event);
|
|
77
78
|
if (!event.defaultPrevented) {
|
|
78
|
-
// return focus to
|
|
79
|
-
const
|
|
80
|
-
if (
|
|
81
|
-
|
|
79
|
+
// return focus to the element that was focused before this scope opened
|
|
80
|
+
const preFocusedElement = this.#manager.getPreFocusMemory(this);
|
|
81
|
+
if (preFocusedElement && document.contains(preFocusedElement)) {
|
|
82
|
+
// ensure the element is still focusable and in the document
|
|
83
|
+
try {
|
|
84
|
+
preFocusedElement.focus();
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// fallback if focus fails
|
|
88
|
+
document.body.focus();
|
|
89
|
+
}
|
|
82
90
|
}
|
|
83
91
|
}
|
|
84
92
|
}
|
|
@@ -39,5 +39,6 @@ export declare class RovingFocusGroup {
|
|
|
39
39
|
handleKeydown(node: HTMLElement | null | undefined, e: KeyboardEvent, both?: boolean): HTMLElement | undefined;
|
|
40
40
|
getTabIndex(node: HTMLElement | null | undefined): 0 | -1;
|
|
41
41
|
setCurrentTabStopId(id: string): void;
|
|
42
|
+
focusCurrentTabStop(): void;
|
|
42
43
|
}
|
|
43
44
|
export {};
|
|
@@ -3,6 +3,7 @@ import { getElemDirection } from "./locale.js";
|
|
|
3
3
|
import { getDirectionalKeys } from "./get-directional-keys.js";
|
|
4
4
|
import { kbd } from "./kbd.js";
|
|
5
5
|
import { BROWSER } from "esm-env";
|
|
6
|
+
import { isHTMLElement } from "./is.js";
|
|
6
7
|
export class RovingFocusGroup {
|
|
7
8
|
#opts;
|
|
8
9
|
#currentTabStopId = box(null);
|
|
@@ -84,4 +85,13 @@ export class RovingFocusGroup {
|
|
|
84
85
|
setCurrentTabStopId(id) {
|
|
85
86
|
this.#currentTabStopId.current = id;
|
|
86
87
|
}
|
|
88
|
+
focusCurrentTabStop() {
|
|
89
|
+
const currentTabStopId = this.#currentTabStopId.current;
|
|
90
|
+
if (!currentTabStopId)
|
|
91
|
+
return;
|
|
92
|
+
const currentTabStop = this.#opts.rootNode.current?.querySelector(`#${currentTabStopId}`);
|
|
93
|
+
if (!currentTabStop || !isHTMLElement(currentTabStop))
|
|
94
|
+
return;
|
|
95
|
+
currentTabStop.focus();
|
|
96
|
+
}
|
|
87
97
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function warn(...messages: string[]): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { DEV } from "esm-env";
|
|
2
|
+
let set;
|
|
3
|
+
if (DEV) {
|
|
4
|
+
set = new Set();
|
|
5
|
+
}
|
|
6
|
+
export function warn(...messages) {
|
|
7
|
+
if (!DEV)
|
|
8
|
+
return;
|
|
9
|
+
const msg = messages.join(" ");
|
|
10
|
+
if (set.has(msg))
|
|
11
|
+
return;
|
|
12
|
+
set.add(msg);
|
|
13
|
+
console.warn(`[Bits UI]: ${msg}`);
|
|
14
|
+
}
|
|
@@ -72,7 +72,7 @@ export type EditableTimeSegmentPart = (typeof EDITABLE_TIME_SEGMENT_PARTS)[numbe
|
|
|
72
72
|
export type EditableSegmentPart = (typeof EDITABLE_SEGMENT_PARTS)[number];
|
|
73
73
|
export type NonEditableSegmentPart = (typeof NON_EDITABLE_SEGMENT_PARTS)[number];
|
|
74
74
|
export type SegmentPart = EditableSegmentPart | NonEditableSegmentPart;
|
|
75
|
-
export type TimeSegmentPart = EditableTimeSegmentPart | "literal";
|
|
75
|
+
export type TimeSegmentPart = EditableTimeSegmentPart | "literal" | "timeZoneName";
|
|
76
76
|
export type AnyTimeExceptLiteral = Exclude<TimeSegmentPart, "literal">;
|
|
77
77
|
export type AnyExceptLiteral = Exclude<SegmentPart, "literal">;
|
|
78
78
|
export type DayPeriod = "AM" | "PM" | null;
|