1
1
import type { ReferenceElement } from "@floating-ui/dom" ;
2
- import type { ContextData , OpenChangeReason , ReferenceType } from "../types.js" ;
2
+ import type {
3
+ ContextData ,
4
+ MaybeGetter ,
5
+ OpenChangeReason ,
6
+ ReferenceType ,
7
+ } from "../types.js" ;
3
8
import { useId } from "./use-id.js" ;
4
9
import { createPubSub } from "../internal/create-pub-sub.js" ;
5
10
import { useFloatingParentNodeId } from "../components/floating-tree/hooks.svelte.js" ;
6
11
import { DEV } from "esm-env" ;
7
12
import { isElement } from "@floating-ui/utils/dom" ;
8
13
import { error } from "../internal/log.js" ;
14
+ import {
15
+ box ,
16
+ type ReadableBox ,
17
+ type WritableBox ,
18
+ } from "../internal/box.svelte.js" ;
19
+ import { extract } from "../internal/extract.js" ;
20
+ import { noop } from "../internal/noop.js" ;
9
21
10
22
interface UseFloatingRootContextOptions {
11
- open ?: boolean ;
23
+ open ?: MaybeGetter < boolean > ;
12
24
onOpenChange ?: (
13
25
open : boolean ,
14
26
event ?: Event ,
15
27
reason ?: OpenChangeReason ,
16
28
) => void ;
17
- elements : {
18
- reference : Element | null ;
19
- floating : HTMLElement | null ;
20
- } ;
29
+ reference : MaybeGetter < Element | null > ;
30
+ floating : MaybeGetter < HTMLElement | null > ;
31
+ onReferenceChange ?: ( node : Element | null ) => void ;
32
+ onFloatingChange ?: ( node : HTMLElement | null ) => void ;
33
+ }
34
+
35
+ class FloatingRootContextOptions {
36
+ open : ReadableBox < boolean > ;
37
+ onOpenChange : (
38
+ open : boolean ,
39
+ event ?: Event ,
40
+ reason ?: OpenChangeReason ,
41
+ ) => void ;
42
+ onReferenceChange : ( node : Element | null ) => void ;
43
+ onFloatingChange : ( node : HTMLElement | null ) => void ;
44
+ #stableReference = $state < Element | null > ( null ) ;
45
+ #stableFloating = $state < HTMLElement | null > ( null ) ;
46
+ reference : WritableBox < Element | null > ;
47
+ floating : WritableBox < HTMLElement | null > ;
48
+
49
+ constructor ( options : UseFloatingRootContextOptions ) {
50
+ const floatingProp = $derived . by ( ( ) => extract ( options . floating , null ) ) ;
51
+ const referenceProp = $derived . by ( ( ) => extract ( options . reference , null ) ) ;
52
+ this . open = box . with ( ( ) => extract ( options . open , false ) ) ;
53
+ this . onOpenChange = options . onOpenChange ?? noop ;
54
+ this . onReferenceChange = options . onReferenceChange ?? noop ;
55
+ this . onFloatingChange = options . onFloatingChange ?? noop ;
56
+ this . reference = box . with (
57
+ ( ) => this . #stableReference,
58
+ ( node ) => {
59
+ this . #stableReference = node ;
60
+ this . onReferenceChange ( node as Element | null ) ;
61
+ } ,
62
+ ) ;
63
+ this . floating = box . with (
64
+ ( ) => this . #stableFloating,
65
+ ( node ) => {
66
+ this . #stableFloating = node ;
67
+ this . onFloatingChange ( node ) ;
68
+ } ,
69
+ ) ;
70
+
71
+ this . reference . current = referenceProp ;
72
+ this . floating . current = floatingProp ;
73
+
74
+ $effect . pre ( ( ) => {
75
+ this . reference . current = referenceProp ;
76
+ } ) ;
77
+
78
+ $effect . pre ( ( ) => {
79
+ this . floating . current = floatingProp ;
80
+ } ) ;
81
+ }
21
82
}
22
83
23
84
class FloatingRootContext < RT extends ReferenceType = ReferenceType > {
24
85
floatingId = useId ( ) ;
25
86
data : ContextData < RT > = $state ( { } ) ;
26
87
events = createPubSub ( ) ;
27
- open = $derived . by ( ( ) => this . options . open ?? false ) ;
28
-
88
+ open = $derived . by ( ( ) => this . options . open . current ) ;
29
89
/** Whether the floating element is nested inside another floating element. */
30
90
#nested: boolean ;
31
91
/** Enables the user to specify a position reference after initialization. */
32
92
#positionReference = $state < ReferenceElement | null > ( null ) ;
33
- #referenceElement = $state < Element | null > ( null ) ;
34
- #floatingElement = $state < HTMLElement | null > ( null ) ;
35
-
36
- #elements = $derived . by ( ( ) => ( {
37
- reference : ( this . #positionReference ||
38
- this . #referenceElement ||
39
- null ) as RT | null ,
40
- floating : this . #floatingElement || null ,
41
- domReference : this . #referenceElement as Element | null ,
42
- } ) ) ;
93
+ reference = $derived . by (
94
+ ( ) =>
95
+ ( this . #positionReference ||
96
+ this . options . reference . current ||
97
+ null ) as RT | null ,
98
+ ) ;
99
+ floating = $derived . by ( ( ) => this . options . floating . current ) ;
100
+ domReference = $derived . by ( ( ) => this . options . reference . current ) ;
43
101
44
- constructor ( private readonly options : UseFloatingRootContextOptions ) {
102
+ constructor ( private readonly options : FloatingRootContextOptions ) {
45
103
this . #nested = useFloatingParentNodeId ( ) != null ;
46
104
47
- this . #referenceElement = this . options . elements . reference ;
48
- this . #floatingElement = this . options . elements . floating ;
49
-
50
- $effect . pre ( ( ) => {
51
- this . #referenceElement = this . options . elements . reference ;
52
- } ) ;
53
-
54
- $effect . pre ( ( ) => {
55
- this . #floatingElement = this . options . elements . floating ;
56
- } ) ;
57
-
58
105
if ( DEV ) {
59
- if (
60
- options . elements . reference &&
61
- ! isElement ( options . elements . reference )
62
- ) {
106
+ if ( options . reference . current && ! isElement ( options . reference . current ) ) {
63
107
error (
64
108
"Cannot pass a virtual element to the `elements.reference` option," ,
65
109
"as it must be a real DOM element. Use `floating.setPositionReference()`" ,
66
110
"instead." ,
67
111
) ;
68
112
}
69
113
}
70
- this . #positionReference = options . elements . reference ;
114
+ this . #positionReference = this . options . reference . current ;
71
115
this . onOpenChange = this . onOpenChange . bind ( this ) ;
72
- this . setPositionReference = this . setPositionReference . bind ( this ) ;
73
116
}
74
117
75
118
onOpenChange ( open : boolean , event ?: Event , reason ?: OpenChangeReason ) {
@@ -86,32 +129,11 @@ class FloatingRootContext<RT extends ReferenceType = ReferenceType> {
86
129
setPositionReference ( node : ReferenceElement | null ) {
87
130
this . #positionReference = node ;
88
131
}
89
-
90
- setFloatingElement ( node : HTMLElement | null ) {
91
- this . #floatingElement = node ;
92
- }
93
-
94
- get elements ( ) {
95
- const _this = this ;
96
- return {
97
- get reference ( ) {
98
- return _this . #elements. reference ;
99
- } ,
100
- get floating ( ) {
101
- return _this . #elements. floating ;
102
- } ,
103
- set floating ( node : HTMLElement | null ) {
104
- _this . setFloatingElement ( node ) ;
105
- } ,
106
- get domReference ( ) {
107
- return _this . #referenceElement;
108
- } ,
109
- } ;
110
- }
111
132
}
112
133
113
134
export function useFloatingRootContext ( options : UseFloatingRootContextOptions ) {
114
- return new FloatingRootContext ( options ) ;
135
+ const optionsState = new FloatingRootContextOptions ( options ) ;
136
+ return new FloatingRootContext ( optionsState ) ;
115
137
}
116
138
117
139
export type { UseFloatingRootContextOptions } ;
0 commit comments