bits-ui 1.0.0-next.31 → 1.0.0-next.32
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 +14 -10
- package/dist/bits/dialog/components/dialog-content.svelte +14 -10
- package/dist/bits/dialog/dialog.svelte.d.ts +6 -0
- package/dist/bits/dialog/dialog.svelte.js +6 -0
- package/dist/bits/dialog/types.d.ts +2 -3
- package/dist/bits/utilities/focus-scope/useFocusScope.svelte.js +4 -4
- package/dist/bits/utilities/popper-layer/types.d.ts +2 -2
- package/dist/bits/utilities/scroll-lock/index.d.ts +10 -0
- package/dist/bits/utilities/scroll-lock/scroll-lock.svelte +2 -2
- package/dist/internal/should-trap-focus.d.ts +6 -0
- package/dist/internal/should-trap-focus.js +6 -0
- package/dist/internal/use-body-scroll-lock.svelte.d.ts +2 -1
- package/dist/internal/use-body-scroll-lock.svelte.js +10 -4
- package/package.json +1 -1
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { noop } from "../../../internal/noop.js";
|
|
11
11
|
import ScrollLock from "../../utilities/scroll-lock/scroll-lock.svelte";
|
|
12
12
|
import { useDialogContent } from "../../dialog/dialog.svelte.js";
|
|
13
|
+
import { shouldTrapFocus } from "../../../internal/should-trap-focus.js";
|
|
13
14
|
|
|
14
15
|
let {
|
|
15
16
|
id = useId(),
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
onInteractOutside = noop,
|
|
25
26
|
preventScroll = true,
|
|
26
27
|
trapFocus = true,
|
|
28
|
+
restoreScrollDelay = null,
|
|
27
29
|
...restProps
|
|
28
30
|
}: AlertDialogContentProps = $props();
|
|
29
31
|
|
|
@@ -38,12 +40,16 @@
|
|
|
38
40
|
const mergedProps = $derived(mergeProps(restProps, contentState.props));
|
|
39
41
|
</script>
|
|
40
42
|
|
|
41
|
-
<PresenceLayer {...mergedProps} present={contentState.root.open.current || forceMount}>
|
|
43
|
+
<PresenceLayer {...mergedProps} {forceMount} present={contentState.root.open.current || forceMount}>
|
|
42
44
|
{#snippet presence({ present })}
|
|
43
|
-
<ScrollLock {preventScroll} />
|
|
44
45
|
<FocusScope
|
|
45
46
|
loop
|
|
46
|
-
trapFocus={
|
|
47
|
+
trapFocus={shouldTrapFocus({
|
|
48
|
+
forceMount,
|
|
49
|
+
present: present.current,
|
|
50
|
+
trapFocus,
|
|
51
|
+
open: contentState.root.open.current,
|
|
52
|
+
})}
|
|
47
53
|
{...mergedProps}
|
|
48
54
|
onCloseAutoFocus={(e) => {
|
|
49
55
|
onCloseAutoFocus(e);
|
|
@@ -79,18 +85,16 @@
|
|
|
79
85
|
>
|
|
80
86
|
<TextSelectionLayer {...mergedProps} enabled={present.current}>
|
|
81
87
|
{#if child}
|
|
88
|
+
{#if contentState.root.open.current}
|
|
89
|
+
<ScrollLock {preventScroll} {restoreScrollDelay} />
|
|
90
|
+
{/if}
|
|
82
91
|
{@render child({
|
|
83
92
|
props: mergeProps(mergedProps, focusScopeProps),
|
|
84
93
|
...contentState.snippetProps,
|
|
85
94
|
})}
|
|
86
95
|
{:else}
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
style: {
|
|
90
|
-
pointerEvents: "auto",
|
|
91
|
-
},
|
|
92
|
-
})}
|
|
93
|
-
>
|
|
96
|
+
<ScrollLock {preventScroll} />
|
|
97
|
+
<div {...mergeProps(mergedProps, focusScopeProps)}>
|
|
94
98
|
{@render children?.()}
|
|
95
99
|
</div>
|
|
96
100
|
{/if}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { useId } from "../../../internal/use-id.js";
|
|
11
11
|
import { noop } from "../../../internal/noop.js";
|
|
12
12
|
import ScrollLock from "../../utilities/scroll-lock/scroll-lock.svelte";
|
|
13
|
+
import { shouldTrapFocus } from "../../../internal/should-trap-focus.js";
|
|
13
14
|
|
|
14
15
|
let {
|
|
15
16
|
id = useId(),
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
onInteractOutside = noop,
|
|
23
24
|
trapFocus = true,
|
|
24
25
|
preventScroll = true,
|
|
26
|
+
restoreScrollDelay = null,
|
|
25
27
|
...restProps
|
|
26
28
|
}: DialogContentProps = $props();
|
|
27
29
|
|
|
@@ -36,11 +38,16 @@
|
|
|
36
38
|
const mergedProps = $derived(mergeProps(restProps, contentState.props));
|
|
37
39
|
</script>
|
|
38
40
|
|
|
39
|
-
<PresenceLayer {...mergedProps} present={contentState.root.open.current || forceMount}>
|
|
41
|
+
<PresenceLayer {...mergedProps} {forceMount} present={contentState.root.open.current || forceMount}>
|
|
40
42
|
{#snippet presence({ present })}
|
|
41
43
|
<FocusScope
|
|
42
44
|
loop
|
|
43
|
-
trapFocus={
|
|
45
|
+
trapFocus={shouldTrapFocus({
|
|
46
|
+
forceMount,
|
|
47
|
+
present: present.current,
|
|
48
|
+
trapFocus,
|
|
49
|
+
open: contentState.root.open.current,
|
|
50
|
+
})}
|
|
44
51
|
{...mergedProps}
|
|
45
52
|
onCloseAutoFocus={(e) => {
|
|
46
53
|
onCloseAutoFocus(e);
|
|
@@ -68,20 +75,17 @@
|
|
|
68
75
|
}}
|
|
69
76
|
>
|
|
70
77
|
<TextSelectionLayer {...mergedProps} enabled={present.current}>
|
|
71
|
-
<ScrollLock {preventScroll} />
|
|
72
78
|
{#if child}
|
|
79
|
+
{#if contentState.root.open.current}
|
|
80
|
+
<ScrollLock {preventScroll} {restoreScrollDelay} />
|
|
81
|
+
{/if}
|
|
73
82
|
{@render child({
|
|
74
83
|
props: mergeProps(mergedProps, focusScopeProps),
|
|
75
84
|
...contentState.snippetProps,
|
|
76
85
|
})}
|
|
77
86
|
{:else}
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
style: {
|
|
81
|
-
pointerEvents: "auto",
|
|
82
|
-
},
|
|
83
|
-
})}
|
|
84
|
-
>
|
|
87
|
+
<ScrollLock {preventScroll} />
|
|
88
|
+
<div {...mergeProps(mergedProps, focusScopeProps)}>
|
|
85
89
|
{@render children?.()}
|
|
86
90
|
</div>
|
|
87
91
|
{/if}
|
|
@@ -112,6 +112,9 @@ declare class DialogContentState {
|
|
|
112
112
|
readonly role: "dialog" | "alertdialog";
|
|
113
113
|
readonly "aria-describedby": string | undefined;
|
|
114
114
|
readonly "aria-labelledby": string | undefined;
|
|
115
|
+
readonly style: {
|
|
116
|
+
readonly pointerEvents: "auto";
|
|
117
|
+
};
|
|
115
118
|
};
|
|
116
119
|
}
|
|
117
120
|
type DialogOverlayStateProps = WithRefProps;
|
|
@@ -125,6 +128,9 @@ declare class DialogOverlayState {
|
|
|
125
128
|
props: {
|
|
126
129
|
readonly "data-state": "open" | "closed";
|
|
127
130
|
readonly id: string;
|
|
131
|
+
readonly style: {
|
|
132
|
+
readonly pointerEvents: "auto";
|
|
133
|
+
};
|
|
128
134
|
};
|
|
129
135
|
}
|
|
130
136
|
type AlertDialogCancelStateProps = WithRefProps & ReadableBoxedValues<{
|
|
@@ -254,6 +254,9 @@ class DialogContentState {
|
|
|
254
254
|
"aria-describedby": this.root.descriptionId,
|
|
255
255
|
"aria-labelledby": this.root.titleId,
|
|
256
256
|
[this.root.attrs.content]: "",
|
|
257
|
+
style: {
|
|
258
|
+
pointerEvents: "auto",
|
|
259
|
+
},
|
|
257
260
|
...this.root.sharedProps,
|
|
258
261
|
}));
|
|
259
262
|
}
|
|
@@ -275,6 +278,9 @@ class DialogOverlayState {
|
|
|
275
278
|
props = $derived.by(() => ({
|
|
276
279
|
id: this.#id.current,
|
|
277
280
|
[this.root.attrs.overlay]: "",
|
|
281
|
+
style: {
|
|
282
|
+
pointerEvents: "auto",
|
|
283
|
+
},
|
|
278
284
|
...this.root.sharedProps,
|
|
279
285
|
}));
|
|
280
286
|
}
|
|
@@ -3,6 +3,7 @@ import type { DismissibleLayerProps } from "../utilities/dismissible-layer/types
|
|
|
3
3
|
import type { PresenceLayerProps } from "../utilities/presence-layer/types.js";
|
|
4
4
|
import type { FocusScopeProps } from "../utilities/focus-scope/types.js";
|
|
5
5
|
import type { TextSelectionLayerProps } from "../utilities/text-selection-layer/types.js";
|
|
6
|
+
import type { ScrollLockProps } from "../utilities/scroll-lock/index.js";
|
|
6
7
|
import type { OnChangeFn, WithChild, WithChildNoChildrenSnippetProps, WithChildren, Without } from "../../internal/types.js";
|
|
7
8
|
import type { BitsPrimitiveButtonAttributes, BitsPrimitiveDivAttributes } from "../../shared/attributes.js";
|
|
8
9
|
import type { PortalProps } from "../utilities/portal/index.js";
|
|
@@ -28,9 +29,7 @@ export type DialogRootProps = DialogRootPropsWithoutHTML;
|
|
|
28
29
|
export type DialogContentSnippetProps = {
|
|
29
30
|
open: boolean;
|
|
30
31
|
};
|
|
31
|
-
export type DialogContentPropsWithoutHTML = WithChildNoChildrenSnippetProps<Omit<EscapeLayerProps & Omit<DismissibleLayerProps, "onInteractOutsideStart"> & PresenceLayerProps & FocusScopeProps & TextSelectionLayerProps &
|
|
32
|
-
preventScroll?: boolean;
|
|
33
|
-
}, "loop">, DialogContentSnippetProps>;
|
|
32
|
+
export type DialogContentPropsWithoutHTML = WithChildNoChildrenSnippetProps<Omit<EscapeLayerProps & Omit<DismissibleLayerProps, "onInteractOutsideStart"> & PresenceLayerProps & FocusScopeProps & TextSelectionLayerProps & ScrollLockProps, "loop">, DialogContentSnippetProps>;
|
|
34
33
|
export type DialogContentProps = DialogContentPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, DialogContentPropsWithoutHTML>;
|
|
35
34
|
export type DialogOverlaySnippetProps = {
|
|
36
35
|
open: boolean;
|
|
@@ -84,19 +84,19 @@ export function useFocusScope({ id, loop, enabled, onOpenAutoFocus, onCloseAutoF
|
|
|
84
84
|
};
|
|
85
85
|
});
|
|
86
86
|
$effect(() => {
|
|
87
|
-
let container =
|
|
87
|
+
let container = ref.current;
|
|
88
88
|
const previouslyFocusedElement = document.activeElement;
|
|
89
89
|
untrack(() => {
|
|
90
90
|
if (!container) {
|
|
91
|
-
container = document.getElementById(
|
|
91
|
+
container = document.getElementById(id.current);
|
|
92
92
|
}
|
|
93
93
|
if (!container)
|
|
94
94
|
return;
|
|
95
|
-
|
|
95
|
+
focusScopeStack.add(focusScope);
|
|
96
96
|
const hasFocusedCandidate = container.contains(previouslyFocusedElement);
|
|
97
97
|
if (!hasFocusedCandidate) {
|
|
98
98
|
const mountEvent = new CustomEvent(AUTOFOCUS_ON_MOUNT, EVENT_OPTIONS);
|
|
99
|
-
container.addEventListener(AUTOFOCUS_ON_MOUNT,
|
|
99
|
+
container.addEventListener(AUTOFOCUS_ON_MOUNT, onOpenAutoFocus.current);
|
|
100
100
|
container.dispatchEvent(mountEvent);
|
|
101
101
|
if (!mountEvent.defaultPrevented) {
|
|
102
102
|
afterTick(() => {
|
|
@@ -7,8 +7,8 @@ import type { PresenceLayerImplProps, PresenceLayerProps } from "../presence-lay
|
|
|
7
7
|
import type { FocusScopeImplProps, FocusScopeProps } from "../focus-scope/types.js";
|
|
8
8
|
import type { ScrollLockProps } from "../scroll-lock/index.js";
|
|
9
9
|
import type { Direction } from "../../../shared/index.js";
|
|
10
|
-
export type PopperLayerProps = EscapeLayerProps & Omit<DismissibleLayerProps, "onInteractOutsideStart"> & FloatingLayerContentProps & PresenceLayerProps & TextSelectionLayerProps & FocusScopeProps & ScrollLockProps
|
|
11
|
-
export type PopperLayerStaticProps = EscapeLayerProps & Omit<DismissibleLayerProps, "onInteractOutsideStart"> & PresenceLayerProps & TextSelectionLayerProps & FocusScopeProps & ScrollLockProps & {
|
|
10
|
+
export type PopperLayerProps = EscapeLayerProps & Omit<DismissibleLayerProps, "onInteractOutsideStart"> & FloatingLayerContentProps & PresenceLayerProps & TextSelectionLayerProps & FocusScopeProps & Omit<ScrollLockProps, "restoreScrollDelay">;
|
|
11
|
+
export type PopperLayerStaticProps = EscapeLayerProps & Omit<DismissibleLayerProps, "onInteractOutsideStart"> & PresenceLayerProps & TextSelectionLayerProps & FocusScopeProps & Omit<ScrollLockProps, "restoreScrollDelay"> & {
|
|
12
12
|
content?: Snippet<[{
|
|
13
13
|
props: Record<string, unknown>;
|
|
14
14
|
}]>;
|
|
@@ -5,4 +5,14 @@ export type ScrollLockProps = {
|
|
|
5
5
|
* @defaultValue true
|
|
6
6
|
*/
|
|
7
7
|
preventScroll?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* The delay in milliseconds before the scrollbar is restored after closing the
|
|
10
|
+
* dialog. This is only applicable when using the `child` snippet for custom
|
|
11
|
+
* transitions and `preventScroll` is `true`. You should set this to a value
|
|
12
|
+
* greater than the transition duration to prevent content from shifting during
|
|
13
|
+
* the transition.
|
|
14
|
+
*
|
|
15
|
+
* @defaultValue null
|
|
16
|
+
*/
|
|
17
|
+
restoreScrollDelay?: number | null;
|
|
8
18
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { ScrollLockProps } from "./index.js";
|
|
3
3
|
import { useBodyScrollLock } from "../../../internal/use-body-scroll-lock.svelte.js";
|
|
4
4
|
|
|
5
|
-
let { preventScroll = true }: ScrollLockProps = $props();
|
|
5
|
+
let { preventScroll = true, restoreScrollDelay = null }: ScrollLockProps = $props();
|
|
6
6
|
|
|
7
|
-
useBodyScrollLock(preventScroll);
|
|
7
|
+
useBodyScrollLock(preventScroll, () => restoreScrollDelay);
|
|
8
8
|
</script>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type Getter } from "svelte-toolbelt";
|
|
1
2
|
export type ScrollBodyOption = {
|
|
2
3
|
padding?: boolean | number;
|
|
3
4
|
margin?: boolean | number;
|
|
4
5
|
};
|
|
5
|
-
export declare function useBodyScrollLock(initialState?: boolean | undefined): import("svelte-toolbelt").WritableBox<boolean>;
|
|
6
|
+
export declare function useBodyScrollLock(initialState?: boolean | undefined, restoreScrollDelay?: Getter<number | null>): import("svelte-toolbelt").WritableBox<boolean>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SvelteMap } from "svelte/reactivity";
|
|
2
|
-
import { afterTick, box } from "svelte-toolbelt";
|
|
3
|
-
import { Previous
|
|
2
|
+
import { afterSleep, afterTick, box } from "svelte-toolbelt";
|
|
3
|
+
import { Previous } from "runed";
|
|
4
4
|
import { untrack } from "svelte";
|
|
5
5
|
import { isBrowser, isIOS } from "./is.js";
|
|
6
6
|
import { addEventListener } from "./events.js";
|
|
@@ -80,9 +80,10 @@ const useBodyLockStackCount = createSharedHook(() => {
|
|
|
80
80
|
resetBodyStyle,
|
|
81
81
|
};
|
|
82
82
|
});
|
|
83
|
-
export function useBodyScrollLock(initialState) {
|
|
83
|
+
export function useBodyScrollLock(initialState, restoreScrollDelay = () => null) {
|
|
84
84
|
const id = useId();
|
|
85
85
|
const countState = useBodyLockStackCount();
|
|
86
|
+
const _restoreScrollDelay = $derived(restoreScrollDelay());
|
|
86
87
|
countState.map.set(id, initialState ?? false);
|
|
87
88
|
const locked = box.with(() => countState.map.get(id) ?? false, (v) => countState.map.set(id, v));
|
|
88
89
|
$effect(() => {
|
|
@@ -90,7 +91,12 @@ export function useBodyScrollLock(initialState) {
|
|
|
90
91
|
countState.map.delete(id);
|
|
91
92
|
const length = Array.from(countState.map.values()).length;
|
|
92
93
|
if (length === 0) {
|
|
93
|
-
|
|
94
|
+
if (_restoreScrollDelay === null) {
|
|
95
|
+
countState.resetBodyStyle();
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
afterSleep(_restoreScrollDelay, () => countState.resetBodyStyle());
|
|
99
|
+
}
|
|
94
100
|
}
|
|
95
101
|
};
|
|
96
102
|
});
|