Repository: skeletonlabs/floating-ui-svelte
Pull request #76 (2024-05-10 07:57:22)

Chore/use floating missing internals
Adds a missing `onOpenChange` interceptor function that was causin some bugs with `useHover` (and probably other hooks in the future). Also added `nodeId` as an option.

vercel[bot]: [vc]: #hjRvkbJVUmDKSoeFugwyM3CvRGETl0tVYffK/XtgxNE=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJmbG9hdGluZy11aS1zdmVsdGUiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiZmxvYXRpbmctdWktc3ZlbHRlLWdpdC1jaG9yZS11c2UtZmxvYXRpbmctZmNhODkxLXNrZWxldG9uLWxhYnMudmVyY2VsLmFwcCJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vc2tlbGV0b24tbGFicy9mbG9hdGluZy11aS1zdmVsdGUvODJGYmhUS1d0eEdnV3JIb25CYWVjenhIRTRCdCIsInByZXZpZXdVcmwiOiJmbG9hdGluZy11aS1zdmVsdGUtZ2l0LWNob3JlLXVzZS1mbG9hdGluZy1mY2E4OTEtc2tlbGV0b24tbGFicy52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIn1dfQ==
**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** | ✅ Ready ([Inspect](https://vercel.com/skeleton-labs/floating-ui-svelte/82FbhTKWtxGgWrHonBaeczxHE4Bt)) | [Visit Preview](https://vercel.live/open-feedback/floating-ui-svelte-git-chore-use-floating-fca891-skeleton-labs.vercel.app?via=pr-comment-visit-preview-link&passThrough=1) | 💬 [**Add feedback**](https://vercel.live/open-feedback/floating-ui-svelte-git-chore-use-floating-fca891-skeleton-labs.vercel.app?via=pr-comment-feedback-link) | May 10, 2024 7:57am |



Patch:
diff --git a/src/lib/hooks/useFloating/index.svelte.ts b/src/lib/hooks/useFloating/index.svelte.ts
index 8da9e69..b2863f4 100644
--- a/src/lib/hooks/useFloating/index.svelte.ts
+++ b/src/lib/hooks/useFloating/index.svelte.ts
@@ -79,6 +79,12 @@ interface UseFloatingOptions {
 		floating: FloatingElement,
 		update: () => void,
 	) => () => void;
+
+	/**
+	 * Unique node id when using `FloatingTree`.
+	 * @default undefined
+	 */
+	nodeId?: string;
 }
 
 interface UseFloatingData {
@@ -197,16 +203,17 @@ interface UseFloatingReturn extends UseFloatingData {
  * Hook for managing floating elements.
  */
 function useFloating(options: UseFloatingOptions = {}): UseFloatingReturn {
+	const elements = $state(options.elements ?? {});
 	const {
 		placement = 'bottom',
 		strategy = 'absolute',
 		middleware = [],
 		transform = true,
 		open = true,
-		onOpenChange = noop,
+		onOpenChange: unstableOnOpenChange = noop,
 		whileElementsMounted,
+		nodeId,
 	} = $derived(options);
-	const elements = $state(options.elements ?? {});
 	const floatingStyles = $derived.by(() => {
 		const initialStyles = {
 			position: strategy,
@@ -236,6 +243,15 @@ function useFloating(options: UseFloatingOptions = {}): UseFloatingReturn {
 		});
 	});
 
+	const events = createPubSub();
+	const data: ContextData = $state({});
+
+	const onOpenChange = (open: boolean, event?: Event, reason?: OpenChangeReason) => {
+		data.openEvent = open ? event : undefined;
+		events.emit('openchange', { open, event, reason });
+		unstableOnOpenChange(open, event, reason);
+	};
+
 	const state: UseFloatingData = $state({
 		x: 0,
 		y: 0,
@@ -246,6 +262,14 @@ function useFloating(options: UseFloatingOptions = {}): UseFloatingReturn {
 	});
 
 	const context: FloatingContext = $state({
+		data,
+		events,
+		elements,
+		onOpenChange,
+		floatingId: useId(),
+		get nodeId() {
+			return nodeId;
+		},
 		get x() {
 			return state.x;
 		},
@@ -267,15 +291,6 @@ function useFloating(options: UseFloatingOptions = {}): UseFloatingReturn {
 		get open() {
 			return open;
 		},
-		get onOpenChange() {
-			return onOpenChange;
-		},
-		events: createPubSub(),
-		data: {},
-		// TODO: Ensure nodeId works the same way as in @floating-ui/react
-		nodeId: undefined,
-		floatingId: useId(),
-		elements,
 	});
 
 	const update = async () => {
@@ -329,6 +344,9 @@ function useFloating(options: UseFloatingOptions = {}): UseFloatingReturn {
 	});
 
 	return {
+		update,
+		context,
+		elements,
 		get x() {
 			return state.x;
 		},
@@ -353,9 +371,6 @@ function useFloating(options: UseFloatingOptions = {}): UseFloatingReturn {
 		get floatingStyles() {
 			return floatingStyles;
 		},
-		elements,
-		update,
-		context,
 	};
 }
 
diff --git a/src/routes/(inner)/api/use-floating/data.ts b/src/routes/(inner)/api/use-floating/data.ts
index 48743d5..461b9b3 100644
--- a/src/routes/(inner)/api/use-floating/data.ts
+++ b/src/routes/(inner)/api/use-floating/data.ts
@@ -44,13 +44,19 @@ export const tableOptions: TableData[] = [
 		property: `elements`,
 		description: `The reference and floating elements.`,
 		type: `FloatingElements`,
-		default: ``,
+		default: `{}`,
 	},
 	{
 		property: `whileElementsMounted`,
 		description: `Callback to handle mounting/unmounting of the elements.`,
-		type: `(reference: ReferenceElement, floating: FloatingElement, update: () => void) => () => void`,
-		default: ``,
+		type: `((reference: ReferenceElement, floating: FloatingElement, update: () => void) => () => void) | undefined`,
+		default: `undefined`,
+	},
+	{
+		property: 'nodeId',
+		description: 'A unique node ID for the floating element when using a `FloatingTree`.',
+		type: 'string | undefined',
+		default: 'undefined',
 	},
 ];
 


Test patch:
diff --git a/src/lib/hooks/useFloating/index.test.svelte.ts b/src/lib/hooks/useFloating/index.test.svelte.ts
index fad5e2e..7b9ab88 100644
--- a/src/lib/hooks/useFloating/index.test.svelte.ts
+++ b/src/lib/hooks/useFloating/index.test.svelte.ts
@@ -873,5 +873,24 @@ describe('useFloating', () => {
 				});
 			}),
 		);
+		it(
+			'updates nodeId reactively',
+			withEffect(async () => {
+				let nodeId = $state(useId());
+				const floating = useFloating({
+					get nodeId() {
+						return nodeId;
+					},
+				});
+
+				expect(floating.context.nodeId).toBe(nodeId);
+
+				nodeId = useId();
+
+				await vi.waitFor(() => {
+					expect(floating.context.nodeId).toBe(nodeId);
+				});
+			}),
+		);
 	});
 });