Repository: skeletonlabs/floating-ui-svelte
Pull request #27 (2024-04-27 18:00:02)

Use Classes
This is another experiment comparing #26, which uses objects with getters, to a class-based approach.

Advantages to classes:
1- better performance
2- classes are their own type, so you don't need to define a separate `FloatingContext` and `UseFloatingReturn` type. The JSDoc lives next to the implementation, which is nicer imo.

Disadvantages:
1- some boilerplate is involved with `this` and all that, but I don't think it's that much more.

vercel[bot]: [vc]: #KvKQZZdlgPIdc0omxC9jTrYH1y3xQ/4ActVaEm3r5WE=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJmbG9hdGluZy11aS1zdmVsdGUiLCJyb290RGlyZWN0b3J5IjpudWxsLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vc2tlbGV0b24tbGFicy9mbG9hdGluZy11aS1zdmVsdGUvRWtEYW9OWGZxSmhtMjJOTXVqWU5YWkJiWk5NaiIsInByZXZpZXdVcmwiOiJmbG9hdGluZy11aS1zdmVsdGUtZ2l0LWZvcmstYWJkZWwtMTctdXNlLWNsYXNzZXMtc2tlbGV0b24tbGFicy52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IlBFTkRJTkciLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiZmxvYXRpbmctdWktc3ZlbHRlLWdpdC1mb3JrLWFiZGVsLTE3LXVzZS1jbGFzc2VzLXNrZWxldG9uLWxhYnMudmVyY2VsLmFwcCJ9fV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **floating-ui-svelte** | 🔄 Building ([Inspect](https://vercel.com/skeleton-labs/floating-ui-svelte/EkDaoNXfqJhm22NMujYNXZBbZNMj)) | [Visit Preview](https://vercel.live/open-feedback/floating-ui-svelte-git-fork-abdel-17-use-classes-skeleton-labs.vercel.app?via=pr-comment-visit-preview-link&passThrough=1) | 💬 [**Add feedback**](https://vercel.live/open-feedback/floating-ui-svelte-git-fork-abdel-17-use-classes-skeleton-labs.vercel.app?via=pr-comment-feedback-link) | Apr 27, 2024 8:31pm |



Patch:
diff --git a/src/lib/box.svelte.ts b/src/lib/box.svelte.ts
deleted file mode 100644
index c36d314..0000000
--- a/src/lib/box.svelte.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import type { Getter } from './types.js';
-
-export type ReadableBox<T> = {
-	readonly value: T;
-};
-
-export type WritableBox<T> = {
-	value: T;
-};
-
-class StateBox<T> {
-	value = $state<T>();
-
-	constructor(initialValue: T) {
-		this.value = initialValue;
-	}
-}
-
-class DerivedBox<T> {
-	#getter: Getter<T>;
-
-	constructor(getter: Getter<T>) {
-		this.#getter = getter;
-	}
-
-	readonly value = $derived.by(() => this.#getter());
-}
-
-class ReadonlyBox<T> {
-	#box: ReadableBox<T>;
-
-	constructor(box: ReadableBox<T>) {
-		this.#box = box;
-	}
-
-	get value() {
-		return this.#box.value;
-	}
-}
-
-/**
- * Creates a writable box.
- *
- * @returns A box with a `value` property which can be set to a new value.
- * Useful to pass state to other functions.
- */
-export function box<T>(): WritableBox<T | undefined>;
-
-/**
- * Creates a writable box with an initial value.
- *
- * @param initialValue The initial value of the box.
- * @returns A box with a `value` property which can be set to a new value.
- * Useful to pass state to other functions.
- */
-export function box<T>(initialValue: T): WritableBox<T>;
-
-export function box<T>(initialValue?: T): WritableBox<T | undefined> {
-	return new StateBox(initialValue);
-}
-
-box.derived = function <T>(getter: Getter<T>): ReadableBox<T> {
-	return new DerivedBox(getter);
-};
-
-box.readonly = function <T>(box: ReadableBox<T>): ReadableBox<T> {
-	return new ReadonlyBox(box);
-};
diff --git a/src/lib/hooks/use-floating.svelte.ts b/src/lib/hooks/use-floating.svelte.ts
index 8207c79..aa14363 100644
--- a/src/lib/hooks/use-floating.svelte.ts
+++ b/src/lib/hooks/use-floating.svelte.ts
@@ -1,95 +1,214 @@
-import { box } from '$lib/box.svelte.js';
-import type { UseFloatingOptions, UseFloatingReturn } from '$lib/types.js';
+import type { FloatingElements, OpenChangeReason, UseFloatingOptions } from '$lib/types.js';
 import { getDPR, noop, roundByDPR, styleObjectToString } from '$lib/utils.js';
-import type { MiddlewareData, ReferenceElement } from '@floating-ui/dom';
-import { computePosition } from '@floating-ui/dom';
+import {
+	computePosition,
+	type Strategy,
+	type Placement,
+	type MiddlewareData
+} from '@floating-ui/dom';
 
-/**
- * Hook for managing floating elements.
- * Aims to keep as much parity with `@floating-ui/react` as possible.
- * For now see: https://floating-ui.com/docs/useFloating for API documentation.
- */
-export function useFloating<T extends ReferenceElement = ReferenceElement>(
-	options: UseFloatingOptions<T> = {}
-): UseFloatingReturn {
-	const openOption = box.derived(() => options.open ?? true);
-	const onOpenChangeOption = options.onOpenChange ?? noop;
-	const placementOption = box.derived(() => options.placement ?? 'bottom');
-	const strategyOption = box.derived(() => options.strategy ?? 'absolute');
-	const middlewareOption = box.derived(() => options.middleware);
-	const transformOption = box.derived(() => options.transform ?? true);
-	const referenceElement = box.derived(() => options.elements?.reference);
-	const floatingElement = box.derived(() => options.elements?.floating);
-	const whileElementsMountedOption = options.whileElementsMounted;
-
-	const x = box(0);
-	const y = box(0);
-	const strategy = box(strategyOption.value);
-	const placement = box(placementOption.value);
-	const middlewareData = box<MiddlewareData>({});
-	const isPositioned = box(false);
-	const floatingStyles = box.derived(() => {
+class FloatingState {
+	readonly #options: UseFloatingOptions;
+
+	constructor(options: UseFloatingOptions) {
+		this.#options = options;
+		this.placement = this.placementOption;
+		this.strategy = this.strategyOption;
+	}
+
+	open = $derived.by(() => this.#options.open ?? true);
+	onOpenChange = $derived.by(() => this.#options.onOpenChange ?? noop);
+	placementOption = $derived.by(() => this.#options.placement ?? 'bottom');
+	strategyOption = $derived.by(() => this.#options.strategy ?? 'absolute');
+	middleware = $derived.by(() => this.#options.middleware);
+	transform = $derived.by(() => this.#options.transform ?? true);
+	elements = $derived.by(() => this.#options.elements ?? {});
+	whileElementsMounted = $derived.by(() => this.#options.whileElementsMounted);
+
+	x = $state(0);
+	y = $state(0);
+	placement: Placement = $state('bottom');
+	strategy: Strategy = $state('absolute');
+	middlewareData: MiddlewareData = $state.frozen({});
+	isPositioned = $state(false);
+	floatingStyles = $derived.by(() => {
 		const initialStyles = {
-			position: strategy.value,
+			position: this.strategy,
 			left: '0',
 			top: '0'
 		};
 
-		if (!floatingElement.value) {
+		const { floating } = this.elements;
+		if (floating == null) {
 			return styleObjectToString(initialStyles);
 		}
 
-		const xVal = roundByDPR(floatingElement.value, x.value);
-		const yVal = roundByDPR(floatingElement.value, y.value);
+		const xVal = roundByDPR(floating, this.x);
+		const yVal = roundByDPR(floating, this.y);
 
-		if (transformOption.value) {
+		if (this.transform) {
 			return styleObjectToString({
 				...initialStyles,
 				transform: `translate(${xVal}px, ${yVal}px)`,
-				...(getDPR(floatingElement.value) >= 1.5 && { willChange: 'transform' })
+				...(getDPR(floating) >= 1.5 && { willChange: 'transform' })
 			});
 		}
 
 		return styleObjectToString({
-			position: strategy.value,
+			position: this.strategyOption,
 			left: `${xVal}px`,
 			top: `${yVal}px`
 		});
 	});
+}
+
+export class FloatingContext {
+	readonly #state: FloatingState;
+
+	constructor(state: FloatingState) {
+		this.#state = state;
+	}
+
+	/**
+	 * Represents the open/close state of the floating element.
+	 * @default true
+	 */
+	get open(): boolean {
+		return this.#state.open;
+	}
+
+	/**
+	 * Event handler that can be invoked whenever the open state changes.
+	 */
+	get onOpenChange(): (open: boolean, event?: Event, reason?: OpenChangeReason) => void {
+		return this.#state.onOpenChange;
+	}
+
+	/**
+	 * The reference and floating elements.
+	 */
+	get elements(): FloatingElements {
+		return this.#state.elements;
+	}
+}
+
+export class UseFloatingReturn {
+	readonly #state: FloatingState;
+	readonly #context: FloatingContext;
+	readonly #update: () => void;
+
+	constructor(state: FloatingState, update: () => void) {
+		this.#state = state;
+		this.#context = new FloatingContext(state);
+		this.#update = update;
+	}
+
+	/**
+	 * The x-coord of the floating element.
+	 */
+	get x(): number {
+		return this.#state.x;
+	}
+
+	/**
+	 * The y-coord of the floating element.
+	 */
+	get y(): number {
+		return this.#state.y;
+	}
+
+	/**
+	 * The stateful placement, which can be different from the initial `placement` passed as options.
+	 */
+	get placement(): Placement {
+		return this.#state.placement;
+	}
+
+	/**
+	 * The type of CSS position property to use.
+	 */
+	get strategy(): Strategy {
+		return this.#state.strategy;
+	}
+
+	/**
+	 * Additional data from middleware.
+	 */
+	get middlewareData(): MiddlewareData {
+		return this.#state.middlewareData;
+	}
+
+	/**
+	 * The boolean that let you know if the floating element has been positioned.
+	 */
+	get isPositioned(): boolean {
+		return this.#state.isPositioned;
+	}
+
+	/**
+	 * CSS styles to apply to the floating element to position it.
+	 */
+	get floatingStyles(): string {
+		return this.#state.floatingStyles;
+	}
+
+	/**
+	 * The function to update floating position manually.
+	 */
+	get update(): () => void {
+		return this.#update;
+	}
+
+	/**
+	 * Context object containing internal logic to alter the behavior of the floating element.
+	 * Commonly used to inject into others hooks.
+	 */
+	get context(): FloatingContext {
+		return this.#context;
+	}
+}
+
+/**
+ * Hook for managing floating elements.
+ */
+export function useFloating(options: UseFloatingOptions = {}): UseFloatingReturn {
+	const state = new FloatingState(options);
 
 	function update() {
-		if (referenceElement.value == null || floatingElement.value == null) {
+		const { reference, floating } = state.elements;
+		if (reference == null || floating == null) {
 			return;
 		}
 
-		computePosition(referenceElement.value, floatingElement.value, {
-			middleware: middlewareOption.value,
-			placement: placementOption.value,
-			strategy: strategyOption.value
+		computePosition(reference, floating, {
+			middleware: state.middleware,
+			placement: state.placementOption,
+			strategy: state.strategyOption
 		}).then((position) => {
-			x.value = position.x;
-			y.value = position.y;
-			strategy.value = position.strategy;
-			placement.value = position.placement;
-			middlewareData.value = position.middlewareData;
-			isPositioned.value = true;
+			state.x = position.x;
+			state.y = position.y;
+			state.strategy = position.strategy;
+			state.placement = position.placement;
+			state.middlewareData = position.middlewareData;
+			state.isPositioned = true;
 		});
 	}
 
 	function attach() {
-		if (whileElementsMountedOption === undefined) {
+		if (state.whileElementsMounted === undefined) {
 			update();
 			return;
 		}
 
-		if (referenceElement.value != null && floatingElement.value != null) {
-			return whileElementsMountedOption(referenceElement.value, floatingElement.value, update);
+		const { floating, reference } = state.elements;
+		if (reference != null && floating != null) {
+			return state.whileElementsMounted(reference, floating, update);
 		}
 	}
 
 	function reset() {
-		if (!openOption.value) {
-			isPositioned.value = false;
+		if (!state.open) {
+			state.isPositioned = false;
 		}
 	}
 
@@ -97,22 +216,5 @@ export function useFloating<T extends ReferenceElement = ReferenceElement>(
 	$effect.pre(attach);
 	$effect.pre(reset);
 
-	return {
-		x: box.readonly(x),
-		y: box.readonly(y),
-		strategy: box.readonly(strategy),
-		placement: box.readonly(placement),
-		middlewareData: box.readonly(middlewareData),
-		isPositioned: box.readonly(isPositioned),
-		floatingStyles,
-		update,
-		context: {
-			open: openOption,
-			onOpenChange: onOpenChangeOption,
-			elements: {
-				reference: box.readonly(referenceElement),
-				floating: box.readonly(floatingElement)
-			}
-		}
-	};
+	return new UseFloatingReturn(state, update);
 }
diff --git a/src/lib/index.ts b/src/lib/index.ts
index 2582045..7c24333 100644
--- a/src/lib/index.ts
+++ b/src/lib/index.ts
@@ -1,3 +1,3 @@
 export * from '@floating-ui/dom';
-export { useFloating } from '$lib/hooks/use-floating.svelte.js';
-export { type UseFloatingOptions, type UseFloatingReturn } from '$lib/types.js';
+export { useFloating, type UseFloatingReturn } from '$lib/hooks/use-floating.svelte.js';
+export { type UseFloatingOptions } from '$lib/types.js';
diff --git a/src/lib/types.ts b/src/lib/types.ts
index 089e846..aa90bf8 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -1,81 +1,67 @@
-import type { ReadableBox } from '$lib/box.svelte.js';
 import type {
 	FloatingElement,
 	Middleware,
-	MiddlewareData,
 	Placement,
 	ReferenceElement,
 	Strategy
 } from '@floating-ui/dom';
 
-export type Getter<T> = () => T;
-
 export type Expand<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
 
-export interface UseFloatingOptions<T extends ReferenceElement = ReferenceElement> {
+export interface UseFloatingOptions {
 	/**
 	 * Represents the open/close state of the floating element.
 	 * @default true
 	 */
-	open?: boolean;
+	readonly open?: boolean;
 
 	/**
 	 * Event handler that can be invoked whenever the open state changes.
 	 */
-	onOpenChange?: (open: boolean, event?: Event, reason?: OpenChangeReason) => void;
+	readonly onOpenChange?: (open: boolean, event?: Event, reason?: OpenChangeReason) => void;
 
 	/**
 	 * Where to place the floating element relative to its reference element.
 	 * @default 'bottom'
 	 */
-	placement?: Placement;
+	readonly placement?: Placement;
 
 	/**
 	 * The type of CSS position property to use.
 	 * @default 'absolute'
 	 */
-	strategy?: Strategy;
+	readonly strategy?: Strategy;
 
 	/**
 	 * These are plain objects that modify the positioning coordinates in some fashion, or provide useful data for the consumer to use.
 	 * @default undefined
 	 */
-	middleware?: Array<Middleware | undefined | null | false>;
+	readonly middleware?: Array<Middleware | undefined | null | false>;
 
 	/**
 	 * Whether to use `transform` instead of `top` and `left` styles to
 	 * position the floating element (`floatingStyles`).
 	 * @default true
 	 */
-	transform?: boolean;
+	readonly transform?: boolean;
 
 	/**
 	 * The reference and floating elements.
 	 */
-	elements?: {
-		/**
-		 * The reference element.
-		 */
-		reference?: T | null;
-
-		/**
-		 * The floating element which is anchored to the reference element.
-		 */
-		floating?: FloatingElement | null;
-	};
+	readonly elements?: FloatingElements;
 
 	/**
 	 * Callback to handle mounting/unmounting of the elements.
 	 * @default undefined
 	 */
-	whileElementsMounted?: (
-		reference: T,
+	readonly whileElementsMounted?: (
+		reference: ReferenceElement,
 		floating: FloatingElement,
 		update: () => void
 	) => () => void;
 }
 
-type OpenChangeReason =
+export type OpenChangeReason =
 	| 'outside-press'
 	| 'escape-key'
 	| 'ancestor-scroll'
@@ -86,77 +72,14 @@ type OpenChangeReason =
 	| 'list-navigation'
 	| 'safe-polygon';
 
-export interface FloatingContext {
-	/**
-	 * Represents the open/close state of the floating element.
-	 */
-	open: ReadableBox<boolean>;
-
-	/**
-	 * Event handler that can be invoked whenever the open state changes.
-	 */
-	onOpenChange: (open: boolean, event?: Event, reason?: OpenChangeReason) => void;
-
+export type FloatingElements = {
 	/**
-	 * The reference and floating elements.
-	 */
-	elements: {
-		/**
-		 * The reference element.
-		 */
-		reference: ReadableBox<ReferenceElement | null | undefined>;
-
-		/**
-		 * The floating element which is anchored to the reference element.
-		 */
-		floating: ReadableBox<FloatingElement | null | undefined>;
-	};
-}
-
-export interface UseFloatingReturn {
-	/**
-	 * The x-coord of the floating element.
+	 * The reference element.
 	 */
-	x: ReadableBox<number>;
+	readonly reference?: ReferenceElement | null;
 
 	/**
-	 * The y-coord of the floating element.
+	 * The floating element which is anchored to the reference element.
 	 */
-	y: ReadableBox<number>;
-
-	/**
-	 * The stateful placement, which can be different from the initial `placement` passed as options.
-	 */
-	placement: ReadableBox<Placement>;
-
-	/**
-	 * The type of CSS position property to use.
-	 */
-	strategy: ReadableBox<Strategy>;
-
-	/**
-	 * Additional data from middleware.
-	 */
-	middlewareData: ReadableBox<MiddlewareData>;
-
-	/**
-	 * The boolean that let you know if the floating element has been positioned.
-	 */
-	isPositioned: ReadableBox<boolean>;
-
-	/**
-	 * CSS styles to apply to the floating element to position it.
-	 */
-	floatingStyles: ReadableBox<string>;
-
-	/**
-	 * The function to update floating position manually.
-	 */
-	update: () => void;
-
-	/**
-	 * Context object containing internal logic to alter the behavior of the floating element.
-	 * Commonly used to inject into others hooks.
-	 */
-	context: FloatingContext;
-}
+	readonly floating?: FloatingElement | null;
+};
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 25d5f8b..2c3a6dc 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -1,27 +1,27 @@
 <script lang="ts">
-	import IconArrowRight from 'lucide-svelte/icons/arrow-right';
-	import Logo from '$docs/components/Logo/Logo.svelte';
+	import { useFloating, type Placement, autoUpdate, offset } from '$lib/index.js';
+
+	const elements = $state<{ reference?: HTMLElement; floating?: HTMLElement }>({});
+	let placement = $state<Placement>('bottom');
+	const floating = useFloating({
+		get placement() {
+			return placement;
+		},
+		middleware: [offset(5)],
+		elements,
+		whileElementsMounted: autoUpdate
+	});
 </script>
 
-<div class="space-y-10">
-	<header
-		class="h-[75vh] flex flex-col justify-center items-center space-y-8 text-center text-balance"
-	>
-		<Logo />
-		<h1 class="h1 text-black dark:text-white">Floating UI Svelte</h1>
-		<p>A Svelte library for position floating elements and create interactions for them.</p>
-		<div class="flex gap-4">
-			<a href="/tooltips" class="btn-cta">
-				<span>Get Started</span>
-				<IconArrowRight />
-			</a>
-			<a
-				href="https://github.com/skeletonlabs/floating-ui-svelte"
-				target="_blank"
-				class="btn-neutral"
-			>
-				GitHub
-			</a>
-		</div>
-	</header>
-</div>
+<select bind:value={placement}>
+	<option value="top">Top</option>
+	<option value="right">Right</option>
+	<option value="bottom">Bottom</option>
+	<option value="left">Left</option>
+</select>
+
+<p data-testid="x">{floating.x}</p>
+<p data-testid="y">{floating.y}</p>
+
+<button bind:this={elements.reference}>Reference</button>
+<div bind:this={elements.floating} style={floating.floatingStyles}>Floating</div>


Test patch:
diff --git a/src/lib/hooks/use-floating.test.svelte.ts b/src/lib/hooks/use-floating.test.svelte.ts
index 8743944..4eaba8f 100644
--- a/src/lib/hooks/use-floating.test.svelte.ts
+++ b/src/lib/hooks/use-floating.test.svelte.ts
@@ -34,7 +34,7 @@ describe('useFloating', () => {
 	it_in_effect('updates floating coordinates on middleware change', async () => {
 		const middleware: Middleware[] = $state([]);
 
-		const { x, y } = useFloating({
+		const floating = useFloating({
 			...test_config(),
 			get middleware() {
 				return middleware;
@@ -42,21 +42,21 @@ describe('useFloating', () => {
 		});
 
 		await vi.waitFor(() => {
-			expect(x.value).toBe(0);
-			expect(y.value).toBe(0);
+			expect(floating.x).toBe(0);
+			expect(floating.y).toBe(0);
 		});
 
 		middleware.push(offset(5));
 
 		await vi.waitFor(() => {
-			expect(x.value).toBe(0);
-			expect(y.value).toBe(5);
+			expect(floating.x).toBe(0);
+			expect(floating.y).toBe(5);
 		});
 	});
 	it_in_effect('updates floating coordinates on placement change', async () => {
 		let placement: Placement = $state('bottom');
 
-		const { x, y } = useFloating({
+		const floating = useFloating({
 			...test_config(),
 			middleware: [offset(5)],
 			get placement() {
@@ -65,21 +65,21 @@ describe('useFloating', () => {
 		});
 
 		await vi.waitFor(() => {
-			expect(x.value).toBe(0);
-			expect(y.value).toBe(5);
+			expect(floating.x).toBe(0);
+			expect(floating.y).toBe(5);
 		});
 
 		placement = 'top';
 
 		await vi.waitFor(() => {
-			expect(x.value).toBe(0);
-			expect(y.value).toBe(-5);
+			expect(floating.x).toBe(0);
+			expect(floating.y).toBe(-5);
 		});
 	});
 	it_in_effect('updates `floatingStyles` on strategy change', async () => {
 		let strategy: Strategy = $state('absolute');
 
-		const { floatingStyles } = useFloating({
+		const floating = useFloating({
 			...test_config(),
 			get strategy() {
 				return strategy;
@@ -87,51 +87,51 @@ describe('useFloating', () => {
 		});
 
 		await vi.waitFor(() => {
-			expect(floatingStyles.value).toContain('position: absolute');
+			expect(floating.floatingStyles).toContain('position: absolute');
 		});
 
 		strategy = 'fixed';
 
 		await vi.waitFor(() => {
-			expect(floatingStyles.value).toContain('position: fixed');
+			expect(floating.floatingStyles).toContain('position: fixed');
 		});
 	});
 	it_in_effect('updates `isPositioned` when position is computed', async () => {
-		const { x, y, isPositioned } = useFloating({
+		const floating = useFloating({
 			...test_config(),
 			middleware: [offset(5)]
 		});
 
-		expect(x.value).toBe(0);
-		expect(y.value).toBe(0);
-		expect(isPositioned.value).toBe(false);
+		expect(floating.x).toBe(0);
+		expect(floating.y).toBe(0);
+		expect(floating.isPositioned).toBe(false);
 
 		await vi.waitFor(() => {
-			expect(x.value).toBe(0);
-			expect(y.value).toBe(5);
-			expect(isPositioned.value).toBe(true);
+			expect(floating.x).toBe(0);
+			expect(floating.y).toBe(5);
+			expect(floating.isPositioned).toBe(true);
 		});
 	});
 	it_in_effect('updates `isPositioned` to `false` when `open` is set to `false`', async () => {
 		let open = $state(true);
 
-		const { isPositioned } = useFloating({
+		const floating = useFloating({
 			...test_config(),
 			get open() {
 				return open;
 			}
 		});
 
-		expect(isPositioned.value).toBe(false);
+		expect(floating.isPositioned).toBe(false);
 
 		await vi.waitFor(() => {
-			expect(isPositioned.value).toBe(true);
+			expect(floating.isPositioned).toBe(true);
 		});
 
 		open = false;
 
 		await vi.waitFor(() => {
-			expect(isPositioned.value).toBe(false);
+			expect(floating.isPositioned).toBe(false);
 		});
 	});
 	it_in_effect(
@@ -139,7 +139,7 @@ describe('useFloating', () => {
 		async () => {
 			let placement: Placement | undefined = $state('top');
 
-			const { x, y } = useFloating({
+			const floating = useFloating({
 				...test_config(),
 				middleware: [offset(5)],
 				get placement() {
@@ -148,15 +148,15 @@ describe('useFloating', () => {
 			});
 
 			await vi.waitFor(() => {
-				expect(x.value).toBe(0);
-				expect(y.value).toBe(-5);
+				expect(floating.x).toBe(0);
+				expect(floating.y).toBe(-5);
 			});
 
 			placement = undefined;
 
 			await vi.waitFor(() => {
-				expect(x.value).toBe(0);
-				expect(y.value).toBe(5);
+				expect(floating.x).toBe(0);
+				expect(floating.y).toBe(5);
 			});
 		}
 	);
@@ -165,7 +165,7 @@ describe('useFloating', () => {
 		async () => {
 			let strategy: Strategy | undefined = $state('fixed');
 
-			const { floatingStyles } = useFloating({
+			const floating = useFloating({
 				...test_config(),
 				get strategy() {
 					return strategy;
@@ -173,13 +173,13 @@ describe('useFloating', () => {
 			});
 
 			await vi.waitFor(() => {
-				expect(floatingStyles.value).toContain('position: fixed');
+				expect(floating.floatingStyles).toContain('position: fixed');
 			});
 
 			strategy = undefined;
 
 			await vi.waitFor(() => {
-				expect(floatingStyles.value).toContain('position: absolute');
+				expect(floating.floatingStyles).toContain('position: absolute');
 			});
 		}
 	);
@@ -239,7 +239,7 @@ describe('useFloating', () => {
 		expect(whileElementsMountedCleanup).toHaveBeenCalledTimes(1);
 	});
 	it_in_effect('correctly assigns `middlewareData` from `middleware`', async () => {
-		const { middlewareData } = useFloating({
+		const floating = useFloating({
 			...test_config(),
 			middleware: [
 				{
@@ -250,7 +250,7 @@ describe('useFloating', () => {
 		});
 
 		await vi.waitFor(() => {
-			expect(middlewareData.value).toEqual({ test: { content: 'Content' } });
+			expect(floating.middlewareData).toEqual({ test: { content: 'Content' } });
 		});
 	});
 });