Openstatus
www.openstatus.dev
1import * as React from "react";
2
3/**
4 * A utility to compose multiple event handlers into a single event handler.
5 * Run originalEventHandler first, then ourEventHandler unless prevented.
6 */
7function composeEventHandlers<E>(
8 originalEventHandler?: (event: E) => void,
9 ourEventHandler?: (event: E) => void,
10 { checkForDefaultPrevented = true } = {},
11) {
12 return function handleEvent(event: E) {
13 originalEventHandler?.(event);
14
15 if (
16 checkForDefaultPrevented === false ||
17 !(event as unknown as Event).defaultPrevented
18 ) {
19 return ourEventHandler?.(event);
20 }
21 };
22}
23
24/**
25 * @see https://github.com/radix-ui/primitives/blob/main/packages/react/compose-refs/src/compose-refs.tsx
26 */
27
28type PossibleRef<T> = React.Ref<T> | undefined;
29
30/**
31 * Set a given ref to a given value.
32 * This utility takes care of different types of refs: callback refs and RefObject(s).
33 */
34function setRef<T>(ref: PossibleRef<T>, value: T) {
35 if (typeof ref === "function") {
36 return ref(value);
37 }
38
39 if (ref !== null && ref !== undefined) {
40 ref.current = value;
41 }
42}
43
44/**
45 * A utility to compose multiple refs together.
46 * Accepts callback refs and RefObject(s).
47 */
48function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {
49 return (node) => {
50 let hasCleanup = false;
51 const cleanups = refs.map((ref) => {
52 const cleanup = setRef(ref, node);
53 if (!hasCleanup && typeof cleanup === "function") {
54 hasCleanup = true;
55 }
56 return cleanup;
57 });
58
59 // React <19 will log an error to the console if a callback ref returns a
60 // value. We don't use ref cleanups internally so this will only happen if a
61 // user's ref callback returns a value, which we only expect if they are
62 // using the cleanup functionality added in React 19.
63 if (hasCleanup) {
64 return () => {
65 for (let i = 0; i < cleanups.length; i++) {
66 const cleanup = cleanups[i];
67 if (typeof cleanup === "function") {
68 cleanup();
69 } else {
70 setRef(refs[i], null);
71 }
72 }
73 };
74 }
75 };
76}
77
78/**
79 * A custom hook that composes multiple refs.
80 * Accepts callback refs and RefObject(s).
81 */
82function useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {
83 // eslint-disable-next-line react-hooks/exhaustive-deps
84 return React.useCallback(composeRefs(...refs), refs);
85}
86
87export { composeEventHandlers, composeRefs, useComposedRefs };