Skip to content

Commit 09cf58f

Browse files
committed
some work
1 parent aeb1be8 commit 09cf58f

File tree

7 files changed

+577
-196
lines changed

7 files changed

+577
-196
lines changed

packages/floating-ui-svelte/src/hooks/use-floating-root-context.svelte.ts

Lines changed: 81 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,118 @@
11
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";
38
import { useId } from "./use-id.js";
49
import { createPubSub } from "../internal/create-pub-sub.js";
510
import { useFloatingParentNodeId } from "../components/floating-tree/hooks.svelte.js";
611
import { DEV } from "esm-env";
712
import { isElement } from "@floating-ui/utils/dom";
813
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";
921

1022
interface UseFloatingRootContextOptions {
11-
open?: boolean;
23+
open?: MaybeGetter<boolean>;
1224
onOpenChange?: (
1325
open: boolean,
1426
event?: Event,
1527
reason?: OpenChangeReason,
1628
) => 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+
}
2182
}
2283

2384
class FloatingRootContext<RT extends ReferenceType = ReferenceType> {
2485
floatingId = useId();
2586
data: ContextData<RT> = $state({});
2687
events = createPubSub();
27-
open = $derived.by(() => this.options.open ?? false);
28-
88+
open = $derived.by(() => this.options.open.current);
2989
/** Whether the floating element is nested inside another floating element. */
3090
#nested: boolean;
3191
/** Enables the user to specify a position reference after initialization. */
3292
#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);
43101

44-
constructor(private readonly options: UseFloatingRootContextOptions) {
102+
constructor(private readonly options: FloatingRootContextOptions) {
45103
this.#nested = useFloatingParentNodeId() != null;
46104

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-
58105
if (DEV) {
59-
if (
60-
options.elements.reference &&
61-
!isElement(options.elements.reference)
62-
) {
106+
if (options.reference.current && !isElement(options.reference.current)) {
63107
error(
64108
"Cannot pass a virtual element to the `elements.reference` option,",
65109
"as it must be a real DOM element. Use `floating.setPositionReference()`",
66110
"instead.",
67111
);
68112
}
69113
}
70-
this.#positionReference = options.elements.reference;
114+
this.#positionReference = this.options.reference.current;
71115
this.onOpenChange = this.onOpenChange.bind(this);
72-
this.setPositionReference = this.setPositionReference.bind(this);
73116
}
74117

75118
onOpenChange(open: boolean, event?: Event, reason?: OpenChangeReason) {
@@ -86,32 +129,11 @@ class FloatingRootContext<RT extends ReferenceType = ReferenceType> {
86129
setPositionReference(node: ReferenceElement | null) {
87130
this.#positionReference = node;
88131
}
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-
}
111132
}
112133

113134
export function useFloatingRootContext(options: UseFloatingRootContextOptions) {
114-
return new FloatingRootContext(options);
135+
const optionsState = new FloatingRootContextOptions(options);
136+
return new FloatingRootContext(optionsState);
115137
}
116138

117139
export type { UseFloatingRootContextOptions };

0 commit comments

Comments
 (0)