bits-ui 2.9.5 → 2.9.6
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/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/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/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,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 {
|
|
@@ -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
|
}
|