@johly/vaul-svelte 1.0.0-next.10 → 1.0.0-next.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/components/drawer/drawer.svelte +72 -3
- package/dist/components/drawer/drawer.svelte.d.ts +1 -1
- package/dist/components/drawer/types.d.ts +14 -0
- package/dist/drawer-registry.d.ts +15 -0
- package/dist/drawer-registry.js +32 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types.d.ts +11 -0
- package/dist/use-drawer-root.svelte.d.ts +78 -0
- package/dist/use-drawer-root.svelte.js +16 -2
- package/package.json +1 -1
|
@@ -5,12 +5,18 @@
|
|
|
5
5
|
import { noop } from "../../internal/noop.js";
|
|
6
6
|
import { CLOSE_THRESHOLD, SCROLL_LOCK_TIMEOUT } from "../../internal/constants.js";
|
|
7
7
|
import { useDrawerRoot } from "../../use-drawer-root.svelte.js";
|
|
8
|
+
import type { ParentDrawerState } from "../../types.js";
|
|
9
|
+
import {
|
|
10
|
+
registerDrawer,
|
|
11
|
+
unregisterDrawer,
|
|
12
|
+
getCurrentParentDrawer,
|
|
13
|
+
} from "../../drawer-registry.js";
|
|
8
14
|
|
|
9
15
|
let {
|
|
10
16
|
open = $bindable(false),
|
|
11
17
|
onOpenChange = noop,
|
|
12
|
-
onDrag = noop,
|
|
13
|
-
onRelease = noop,
|
|
18
|
+
onDrag: onDragProp = noop,
|
|
19
|
+
onRelease: onReleaseProp = noop,
|
|
14
20
|
snapPoints,
|
|
15
21
|
shouldScaleBackground = false,
|
|
16
22
|
setBackgroundColorOnScale = true,
|
|
@@ -37,6 +43,48 @@
|
|
|
37
43
|
...restProps
|
|
38
44
|
}: RootProps = $props();
|
|
39
45
|
|
|
46
|
+
// Track the auto-detected parent drawer
|
|
47
|
+
// If drawer starts open, try to auto-detect immediately
|
|
48
|
+
const initialParent = open && !nested ? getCurrentParentDrawer() : undefined;
|
|
49
|
+
let resolvedParentDrawer = $state<ParentDrawerState | undefined>(initialParent);
|
|
50
|
+
|
|
51
|
+
// Auto-detect parent when opening (for drawers that open after mount)
|
|
52
|
+
let wasOpen = open;
|
|
53
|
+
$effect.pre(() => {
|
|
54
|
+
// Detect parent on open transition (false -> true)
|
|
55
|
+
if (open && !wasOpen && !nested) {
|
|
56
|
+
resolvedParentDrawer = getCurrentParentDrawer();
|
|
57
|
+
}
|
|
58
|
+
wasOpen = open;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// When parentDrawer is provided or auto-detected, this drawer behaves as nested
|
|
62
|
+
const isNestedViaParent = $derived(!!resolvedParentDrawer);
|
|
63
|
+
const effectiveNested = $derived(nested || isNestedViaParent);
|
|
64
|
+
|
|
65
|
+
// Wrap onDrag to also notify parent drawer
|
|
66
|
+
function onDrag(event: PointerEvent, percentageDragged: number) {
|
|
67
|
+
onDragProp(event, percentageDragged);
|
|
68
|
+
if (resolvedParentDrawer) {
|
|
69
|
+
resolvedParentDrawer.onNestedDrag(event, percentageDragged);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Wrap onRelease to also notify parent drawer
|
|
74
|
+
function onRelease(event: PointerEvent, isOpen: boolean) {
|
|
75
|
+
onReleaseProp(event, isOpen);
|
|
76
|
+
if (resolvedParentDrawer) {
|
|
77
|
+
resolvedParentDrawer.onNestedRelease(event, isOpen);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Watch open state to notify parent drawer
|
|
82
|
+
$effect(() => {
|
|
83
|
+
if (resolvedParentDrawer) {
|
|
84
|
+
resolvedParentDrawer.onNestedOpenChange(open);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
40
88
|
const rootState = useDrawerRoot({
|
|
41
89
|
open: box.with(
|
|
42
90
|
() => open,
|
|
@@ -49,7 +97,7 @@
|
|
|
49
97
|
scrollLockTimeout: box.with(() => scrollLockTimeout),
|
|
50
98
|
snapPoints: box.with(() => snapPoints),
|
|
51
99
|
fadeFromIndex: box.with(() => fadeFromIndex),
|
|
52
|
-
nested: box.with(() =>
|
|
100
|
+
nested: box.with(() => effectiveNested),
|
|
53
101
|
shouldScaleBackground: box.with(() => shouldScaleBackground),
|
|
54
102
|
activeSnapPoint: box.with(
|
|
55
103
|
() => activeSnapPoint,
|
|
@@ -77,6 +125,27 @@
|
|
|
77
125
|
onOpenChange: box.with(() => onOpenChange),
|
|
78
126
|
onAnimationEnd: box.with(() => onAnimationEnd),
|
|
79
127
|
});
|
|
128
|
+
|
|
129
|
+
// Get stable reference to drawer state for registry
|
|
130
|
+
const stableDrawerState = rootState.parentDrawerState;
|
|
131
|
+
|
|
132
|
+
// Register/unregister with global drawer registry
|
|
133
|
+
$effect(() => {
|
|
134
|
+
if (open) {
|
|
135
|
+
registerDrawer(stableDrawerState);
|
|
136
|
+
return () => unregisterDrawer(stableDrawerState);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Clear auto-detected parent after drawer closes (separate effect to avoid loops)
|
|
141
|
+
$effect(() => {
|
|
142
|
+
if (!open && resolvedParentDrawer) {
|
|
143
|
+
// Use queueMicrotask to avoid triggering other effects synchronously
|
|
144
|
+
queueMicrotask(() => {
|
|
145
|
+
resolvedParentDrawer = undefined;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
});
|
|
80
149
|
</script>
|
|
81
150
|
|
|
82
151
|
<DialogPrimitive.Root
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
declare const Drawer: import("svelte").Component<import("
|
|
1
|
+
declare const Drawer: import("svelte").Component<import("../../types.js").DrawerRootPropsWithoutHTML, {}, "open" | "activeSnapPoint">;
|
|
2
2
|
type Drawer = ReturnType<typeof Drawer>;
|
|
3
3
|
export default Drawer;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BitsPrimitiveDivAttributes, DialogContentPropsWithoutHTML, DialogOverlayPropsWithoutHTML, Dialog as DrawerPrimitive, WithChild, WithoutChildrenOrChild } from "bits-ui";
|
|
2
2
|
import type { WithChildren, Without } from "svelte-toolbelt";
|
|
3
|
+
import type { ParentDrawerState } from "../../types.js";
|
|
3
4
|
export type WithFadeFromProps = {
|
|
4
5
|
/**
|
|
5
6
|
* Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up.
|
|
@@ -25,6 +26,19 @@ export type BaseDrawerRootPropsWithoutHTML = WithChildren<{
|
|
|
25
26
|
activeSnapPoint?: number | string | null;
|
|
26
27
|
onActiveSnapPointChange?: (snapPoint: number | string | null) => void;
|
|
27
28
|
open?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Exposes the drawer's state that can be passed to another drawer's `parentDrawer`
|
|
31
|
+
* prop to establish a parent-child relationship without component hierarchy.
|
|
32
|
+
* Useful for global/portal confirmation drawers.
|
|
33
|
+
*/
|
|
34
|
+
drawerState?: ParentDrawerState;
|
|
35
|
+
/**
|
|
36
|
+
* Pass another drawer's `drawerState` here to make this drawer behave as a nested
|
|
37
|
+
* drawer of that parent, even when rendered outside the component hierarchy.
|
|
38
|
+
* When provided, this drawer will automatically have `nested` behavior enabled
|
|
39
|
+
* and will scale/animate the parent drawer appropriately.
|
|
40
|
+
*/
|
|
41
|
+
parentDrawer?: ParentDrawerState;
|
|
28
42
|
/**
|
|
29
43
|
* Number between 0 and 1 that determines when the drawer should be closed.
|
|
30
44
|
* Example: threshold of 0.5 would close the drawer if the user swiped for 50% of the height of the drawer or more.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ParentDrawerState } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Register a drawer as open.
|
|
4
|
+
*/
|
|
5
|
+
export declare function registerDrawer(state: ParentDrawerState): void;
|
|
6
|
+
/**
|
|
7
|
+
* Unregister a drawer when it closes.
|
|
8
|
+
*/
|
|
9
|
+
export declare function unregisterDrawer(state: ParentDrawerState): void;
|
|
10
|
+
/**
|
|
11
|
+
* Get the current topmost drawer state (if any).
|
|
12
|
+
* Used to auto-detect parent drawer when a new drawer opens.
|
|
13
|
+
* Returns the most recently opened drawer, which becomes the parent of the new drawer.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getCurrentParentDrawer(): ParentDrawerState | undefined;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global registry for tracking open drawers.
|
|
3
|
+
* Enables automatic parent-child relationships without manual prop passing.
|
|
4
|
+
*
|
|
5
|
+
* NOTE: This is intentionally NOT using $state to avoid creating reactive
|
|
6
|
+
* dependencies that would cause infinite effect loops.
|
|
7
|
+
*/
|
|
8
|
+
// Stack of open drawers (most recent last) - intentionally non-reactive
|
|
9
|
+
let openDrawers = [];
|
|
10
|
+
/**
|
|
11
|
+
* Register a drawer as open.
|
|
12
|
+
*/
|
|
13
|
+
export function registerDrawer(state) {
|
|
14
|
+
openDrawers.push(state);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Unregister a drawer when it closes.
|
|
18
|
+
*/
|
|
19
|
+
export function unregisterDrawer(state) {
|
|
20
|
+
const index = openDrawers.indexOf(state);
|
|
21
|
+
if (index !== -1) {
|
|
22
|
+
openDrawers.splice(index, 1);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Get the current topmost drawer state (if any).
|
|
27
|
+
* Used to auto-detect parent drawer when a new drawer opens.
|
|
28
|
+
* Returns the most recently opened drawer, which becomes the parent of the new drawer.
|
|
29
|
+
*/
|
|
30
|
+
export function getCurrentParentDrawer() {
|
|
31
|
+
return openDrawers.at(-1);
|
|
32
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -3,6 +3,17 @@ export type DrawerDirection = "left" | "right" | "top" | "bottom";
|
|
|
3
3
|
export type OnChangeFn<T> = (value: T) => void;
|
|
4
4
|
export type OnDrag = (event: PointerEvent | MouseEvent, percentageDragged: number) => void;
|
|
5
5
|
export type OnRelease = (event: PointerEvent | MouseEvent, open: boolean) => void;
|
|
6
|
+
/**
|
|
7
|
+
* State object exposed by a drawer that can be passed to another drawer
|
|
8
|
+
* to establish a parent-child relationship without component hierarchy.
|
|
9
|
+
* Use this for global/portal drawers that need to behave as nested drawers.
|
|
10
|
+
*/
|
|
11
|
+
export type ParentDrawerState = {
|
|
12
|
+
onNestedOpenChange: (open: boolean) => void;
|
|
13
|
+
onNestedDrag: (event: PointerEvent, percentageDragged: number) => void;
|
|
14
|
+
onNestedRelease: (event: PointerEvent, open: boolean) => void;
|
|
15
|
+
direction: DrawerDirection;
|
|
16
|
+
};
|
|
6
17
|
export type AnyFunction = (...args: any) => any;
|
|
7
18
|
export type Getters<T> = {
|
|
8
19
|
[K in keyof T]: () => T[K];
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type ReadableBoxedValues, type WritableBoxedValues } from "svelte-toolbelt";
|
|
2
|
+
import type { DrawerDirection, ParentDrawerState } from "./types.js";
|
|
3
|
+
type UseDrawerRootProps = ReadableBoxedValues<{
|
|
4
|
+
closeThreshold: number;
|
|
5
|
+
shouldScaleBackground: boolean;
|
|
6
|
+
scrollLockTimeout: number;
|
|
7
|
+
snapPoints: (string | number)[] | undefined;
|
|
8
|
+
fadeFromIndex: number | undefined;
|
|
9
|
+
fixed: boolean;
|
|
10
|
+
dismissible: boolean;
|
|
11
|
+
direction: DrawerDirection;
|
|
12
|
+
onDrag: (event: PointerEvent, percentageDragged: number) => void;
|
|
13
|
+
onRelease: (event: PointerEvent, open: boolean) => void;
|
|
14
|
+
nested: boolean;
|
|
15
|
+
onClose: () => void;
|
|
16
|
+
modal: boolean;
|
|
17
|
+
handleOnly: boolean;
|
|
18
|
+
noBodyStyles: boolean;
|
|
19
|
+
preventScrollRestoration: boolean;
|
|
20
|
+
setBackgroundColorOnScale: boolean;
|
|
21
|
+
container: HTMLElement | null;
|
|
22
|
+
snapToSequentialPoint: boolean;
|
|
23
|
+
repositionInputs: boolean;
|
|
24
|
+
autoFocus: boolean;
|
|
25
|
+
disablePreventScroll: boolean;
|
|
26
|
+
onOpenChange: (o: boolean) => void;
|
|
27
|
+
onAnimationEnd: (open: boolean) => void;
|
|
28
|
+
}> & WritableBoxedValues<{
|
|
29
|
+
open: boolean;
|
|
30
|
+
activeSnapPoint: number | string | null;
|
|
31
|
+
}>;
|
|
32
|
+
export declare function useDrawerRoot(opts: UseDrawerRootProps): {
|
|
33
|
+
keyboardIsOpen: import("svelte-toolbelt").WritableBox<boolean>;
|
|
34
|
+
closeDrawer: (fromWithin?: boolean) => void;
|
|
35
|
+
setDrawerNode: (node: HTMLElement | null) => void;
|
|
36
|
+
setOverlayNode: (node: HTMLElement | null) => void;
|
|
37
|
+
onDrag: (event: PointerEvent) => void;
|
|
38
|
+
onNestedDrag: (_event: PointerEvent, percentageDragged: number) => void;
|
|
39
|
+
onNestedOpenChange: (o: boolean) => void;
|
|
40
|
+
onNestedRelease: (_event: PointerEvent, o: boolean) => void;
|
|
41
|
+
onRelease: (event: PointerEvent | null) => void;
|
|
42
|
+
onPress: (event: PointerEvent) => void;
|
|
43
|
+
onDialogOpenChange: (o: boolean) => void;
|
|
44
|
+
shouldAnimate: boolean;
|
|
45
|
+
isDragging: boolean;
|
|
46
|
+
overlayNode: HTMLElement | null;
|
|
47
|
+
drawerNode: HTMLElement | null;
|
|
48
|
+
snapPointsOffset: number[];
|
|
49
|
+
shouldFade: boolean;
|
|
50
|
+
restorePositionSetting: () => void;
|
|
51
|
+
handleOpenChange: (o: boolean) => void;
|
|
52
|
+
parentDrawerState: ParentDrawerState;
|
|
53
|
+
closeThreshold: import("svelte-toolbelt").ReadableBox<number>;
|
|
54
|
+
shouldScaleBackground: import("svelte-toolbelt").ReadableBox<boolean>;
|
|
55
|
+
scrollLockTimeout: import("svelte-toolbelt").ReadableBox<number>;
|
|
56
|
+
snapPoints: import("svelte-toolbelt").ReadableBox<(string | number)[] | undefined>;
|
|
57
|
+
fadeFromIndex: import("svelte-toolbelt").ReadableBox<number | undefined>;
|
|
58
|
+
fixed: import("svelte-toolbelt").ReadableBox<boolean>;
|
|
59
|
+
dismissible: import("svelte-toolbelt").ReadableBox<boolean>;
|
|
60
|
+
direction: import("svelte-toolbelt").ReadableBox<DrawerDirection>;
|
|
61
|
+
nested: import("svelte-toolbelt").ReadableBox<boolean>;
|
|
62
|
+
onClose: import("svelte-toolbelt").ReadableBox<() => void>;
|
|
63
|
+
modal: import("svelte-toolbelt").ReadableBox<boolean>;
|
|
64
|
+
handleOnly: import("svelte-toolbelt").ReadableBox<boolean>;
|
|
65
|
+
noBodyStyles: import("svelte-toolbelt").ReadableBox<boolean>;
|
|
66
|
+
preventScrollRestoration: import("svelte-toolbelt").ReadableBox<boolean>;
|
|
67
|
+
setBackgroundColorOnScale: import("svelte-toolbelt").ReadableBox<boolean>;
|
|
68
|
+
container: import("svelte-toolbelt").ReadableBox<HTMLElement | null>;
|
|
69
|
+
snapToSequentialPoint: import("svelte-toolbelt").ReadableBox<boolean>;
|
|
70
|
+
repositionInputs: import("svelte-toolbelt").ReadableBox<boolean>;
|
|
71
|
+
autoFocus: import("svelte-toolbelt").ReadableBox<boolean>;
|
|
72
|
+
disablePreventScroll: import("svelte-toolbelt").ReadableBox<boolean>;
|
|
73
|
+
onOpenChange: import("svelte-toolbelt").ReadableBox<(o: boolean) => void>;
|
|
74
|
+
onAnimationEnd: import("svelte-toolbelt").ReadableBox<(open: boolean) => void>;
|
|
75
|
+
open: import("svelte-toolbelt").WritableBox<boolean>;
|
|
76
|
+
activeSnapPoint: import("svelte-toolbelt").WritableBox<string | number | null>;
|
|
77
|
+
};
|
|
78
|
+
export {};
|
|
@@ -559,7 +559,7 @@ export function useDrawerRoot(opts) {
|
|
|
559
559
|
function setDrawerNode(node) {
|
|
560
560
|
drawerNode = node;
|
|
561
561
|
}
|
|
562
|
-
|
|
562
|
+
const contextValue = {
|
|
563
563
|
...opts,
|
|
564
564
|
keyboardIsOpen,
|
|
565
565
|
closeDrawer,
|
|
@@ -592,5 +592,19 @@ export function useDrawerRoot(opts) {
|
|
|
592
592
|
},
|
|
593
593
|
restorePositionSetting,
|
|
594
594
|
handleOpenChange,
|
|
595
|
-
|
|
595
|
+
/**
|
|
596
|
+
* State that can be passed to another drawer to establish a parent-child
|
|
597
|
+
* relationship without component hierarchy (for global/portal drawers).
|
|
598
|
+
*/
|
|
599
|
+
get parentDrawerState() {
|
|
600
|
+
return {
|
|
601
|
+
onNestedOpenChange,
|
|
602
|
+
onNestedDrag,
|
|
603
|
+
onNestedRelease,
|
|
604
|
+
direction: opts.direction.current,
|
|
605
|
+
};
|
|
606
|
+
},
|
|
607
|
+
};
|
|
608
|
+
DrawerContext.set(contextValue);
|
|
609
|
+
return contextValue;
|
|
596
610
|
}
|