Skip to content
This repository was archived by the owner on Oct 14, 2025. It is now read-only.

Commit bacb86d

Browse files
committed
useMergeRefs
1 parent 3b647ee commit bacb86d

File tree

20 files changed

+233
-114
lines changed

20 files changed

+233
-114
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import type { ReferenceType } from "../types.js";
2+
import { FloatingState } from "./use-floating.svelte.js";
3+
4+
interface BoxedRef {
5+
current: ReferenceType | null;
6+
}
7+
8+
/**
9+
* Merges the references of either floating instances or refs into a single reference
10+
* that can be accessed and set via the `.current` property.
11+
*/
12+
class MergeRefs {
13+
#current = $state<ReferenceType | null>(null);
14+
constructor(
15+
private readonly floatingOrRef: Array<FloatingState | BoxedRef>,
16+
) {}
17+
18+
get current() {
19+
return this.#current;
20+
}
21+
22+
set current(node: ReferenceType | null) {
23+
for (const arg of this.floatingOrRef) {
24+
if (arg instanceof FloatingState) {
25+
arg.reference = node;
26+
continue;
27+
}
28+
arg.current = node;
29+
}
30+
this.#current = node;
31+
}
32+
}
33+
34+
/**
35+
* Use the same reference for multiple floating instances at once.
36+
*
37+
* @example
38+
* ```svelte
39+
* <script lang="ts">
40+
* import { useMergeRefs, useFloating, type BoxedRef } from "@skeletonlabs/floating-ui-svelte";
41+
* const tooltip = useFloating();
42+
* const menu = useFloating()
43+
*
44+
* let someOtherRef: BoxedRef = $state({ current: null })
45+
*
46+
* const tooltipInt = useInteractions([])
47+
* const menuInt = useInteractions([])
48+
*
49+
* const ref = useMergeRefs([tooltip, menu, someOtherRef])
50+
* const props = $derived(tooltipInt.getReferenceProps(menuInt.getReferenceProps()))
51+
* </script>
52+
*
53+
* <button bind:this={ref.current} {...props}>
54+
* <!-- ... -->
55+
* </button>
56+
*```
57+
*
58+
*
59+
* @param floatingInstances
60+
* @returns
61+
*/
62+
function useMergeRefs(refLikes: Array<FloatingState | BoxedRef>) {
63+
return new MergeRefs(refLikes);
64+
}
65+
66+
export { MergeRefs, useMergeRefs };
67+
export type { BoxedRef };

packages/floating-ui-svelte/test/hooks/use-click.ts renamed to packages/floating-ui-svelte/test/hooks/use-click.test.ts

File renamed without changes.

packages/floating-ui-svelte/test/hooks/use-dismiss.ts renamed to packages/floating-ui-svelte/test/hooks/use-dismiss.test.ts

File renamed without changes.

packages/floating-ui-svelte/test/hooks/use-floating.svelte.ts renamed to packages/floating-ui-svelte/test/hooks/use-floating.test.svelte.ts

File renamed without changes.

packages/floating-ui-svelte/test/hooks/use-focus.ts renamed to packages/floating-ui-svelte/test/hooks/use-focus.test.ts

File renamed without changes.

packages/floating-ui-svelte/test/hooks/use-hover.ts renamed to packages/floating-ui-svelte/test/hooks/use-hover.test.ts

File renamed without changes.
File renamed without changes.

packages/floating-ui-svelte/test/hooks/use-interactions.svelte.ts renamed to packages/floating-ui-svelte/test/hooks/use-interactions.test.svelte.ts

File renamed without changes.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { describe, expect, it, vi } from "vitest";
2+
import { useFloating } from "../../src/index.js";
3+
import { withRunes } from "../internal/with-runes.svelte.js";
4+
import {
5+
useMergeRefs,
6+
type BoxedRef,
7+
} from "../../src/hooks/use-merge-refs.svelte.js";
8+
9+
describe("useMergeRefs", () => {
10+
vi.mock(import("svelte"), async (importOriginal) => {
11+
const actual = await importOriginal();
12+
return {
13+
...actual,
14+
getContext: vi.fn().mockReturnValue(null),
15+
};
16+
});
17+
18+
it(
19+
"merges the references of multiple floating instances or other boxed elements",
20+
withRunes(() => {
21+
const ref1 = useFloating();
22+
const ref2 = useFloating();
23+
const ref3: BoxedRef = $state({ current: null });
24+
25+
const mergedRef = useMergeRefs([ref1, ref2, ref3]);
26+
expect(mergedRef.current).toBe(null);
27+
expect(ref1.reference).toBe(null);
28+
expect(ref2.reference).toBe(null);
29+
expect(ref3.current).toBe(null);
30+
31+
const node = document.createElement("div");
32+
mergedRef.current = node;
33+
expect(mergedRef.current).toBe(node);
34+
expect(ref1.reference).toBe(node);
35+
expect(ref2.reference).toBe(node);
36+
expect(ref3.current).toBe(node);
37+
mergedRef.current = null;
38+
expect(mergedRef.current).toBe(null);
39+
expect(ref1.reference).toBe(null);
40+
expect(ref2.reference).toBe(null);
41+
expect(ref3.current).toBe(null);
42+
}),
43+
);
44+
});
File renamed without changes.

0 commit comments

Comments
 (0)