Skip to content

Commit 99a46a6

Browse files
committed
feat: add primitives module compose-refs subpkg
1 parent c449195 commit 99a46a6

File tree

5 files changed

+134
-0
lines changed

5 files changed

+134
-0
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"name": "soybean-react-ui/compose-refs",
3+
"version": "1.1.2",
4+
"license": "MIT",
5+
"homepage": "https://radix-ui.com/primitives",
6+
"repository": {
7+
"type": "git",
8+
"url": "git+https://github.com/radix-ui/primitives.git"
9+
},
10+
"bugs": {
11+
"url": "https://github.com/radix-ui/primitives/issues"
12+
},
13+
"publishConfig": {
14+
"main": "./dist/index.js",
15+
"module": "./dist/index.mjs",
16+
"types": "./dist/index.d.ts",
17+
"exports": {
18+
".": {
19+
"import": {
20+
"types": "./dist/index.d.mts",
21+
"default": "./dist/index.mjs"
22+
},
23+
"require": {
24+
"types": "./dist/index.d.ts",
25+
"default": "./dist/index.js"
26+
}
27+
}
28+
}
29+
},
30+
"sideEffects": false,
31+
"main": "./src/index.ts",
32+
"module": "./src/index.ts",
33+
"files": ["dist", "README.md"],
34+
"scripts": {
35+
"build": "radix-build",
36+
"clean": "rm -rf dist",
37+
"lint": "eslint --max-warnings 0 src",
38+
"typecheck": "tsc --noEmit"
39+
},
40+
"peerDependencies": {
41+
"@types/react": "*",
42+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
43+
},
44+
"peerDependenciesMeta": {
45+
"@types/react": {
46+
"optional": true
47+
}
48+
},
49+
"devDependencies": {
50+
"@types/react": "19.0.7",
51+
"@types/react-dom": "19.0.3",
52+
"eslint": "9.18.0",
53+
"react": "19.1.0",
54+
"react-dom": "19.1.0",
55+
"typescript": "5.7.3"
56+
},
57+
"source": "./src/index.ts"
58+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { Ref, RefCallback } from 'react';
2+
3+
export type PossibleRef<T> = Ref<T> | undefined;
4+
5+
/**
6+
* Set a given ref to a given value
7+
* This utility takes care of different types of refs: callback refs and RefObject(s)
8+
*/
9+
export function setRef<T>(ref: PossibleRef<T>, value: T) {
10+
if (typeof ref === 'function') {
11+
return ref(value);
12+
} else if (ref !== null && ref !== undefined) {
13+
ref.current = value;
14+
}
15+
return null;
16+
}
17+
18+
/**
19+
* A utility to compose multiple refs together
20+
* Accepts callback refs and RefObject(s)
21+
*/
22+
export function composeRefs<T>(...refs: PossibleRef<T>[]): RefCallback<T> {
23+
return node => {
24+
let hasCleanup = false;
25+
const cleanups = refs.map(ref => {
26+
const cleanup = setRef(ref, node);
27+
if (!hasCleanup && typeof cleanup === 'function') {
28+
hasCleanup = true;
29+
}
30+
return cleanup;
31+
});
32+
33+
// React <19 will log an error to the console if a callback ref returns a
34+
// value. We don't use ref cleanups internally so this will only happen if a
35+
// user's ref callback returns a value, which we only expect if they are
36+
// using the cleanup functionality added in React 19.
37+
if (hasCleanup) {
38+
return () => {
39+
for (let i = 0; i < cleanups.length; i += 1) {
40+
const cleanup = cleanups[i];
41+
if (typeof cleanup === 'function') {
42+
cleanup();
43+
} else {
44+
setRef(refs[i], null);
45+
}
46+
}
47+
};
48+
}
49+
return undefined;
50+
};
51+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { composeRefs, type PossibleRef, setRef } from './compose-refs';
2+
3+
export { useComposedRefs } from './use-composed-refs';
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { PossibleRef } from "./compose-refs";
2+
import { composeRefs } from "./compose-refs";
3+
import type { RefCallback } from "react";
4+
import { useCallback } from 'react'
5+
6+
7+
8+
/**
9+
* A custom hook that composes multiple refs
10+
* Accepts callback refs and RefObject(s)
11+
*/
12+
export function useComposedRefs<T>(...refs: PossibleRef<T>[]): RefCallback<T> {
13+
return useCallback(composeRefs(...refs), refs);
14+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "../../tsconfig.base.json",
3+
"compilerOptions": {
4+
"outDir": "dist"
5+
},
6+
"include": ["src"],
7+
"exclude": ["node_modules", "dist"]
8+
}

0 commit comments

Comments
 (0)