Skip to content

Commit 1501937

Browse files
authored
feat: support React 19 and update focusout event mapping to onBlur 👌 (#97)
* fix(refs): add support for react19 (#96) * fix: use ref directly in react19 * fix: add console * fix: react19 support * fix: focus out should call onBlur 🫠 * fix: pass down props
1 parent de2748b commit 1501937

File tree

2 files changed

+34
-17
lines changed

2 files changed

+34
-17
lines changed

‎package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/index.tsx

+32-15
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, {
2+
Ref,
23
useRef,
34
useEffect,
45
RefCallback,
@@ -25,19 +26,36 @@ interface Props extends HTMLAttributes<HTMLElement> {
2526
const eventTypeMapping = {
2627
click: 'onClick',
2728
focusin: 'onFocus',
28-
focusout: 'onFocus',
29+
focusout: 'onBlur',
2930
mousedown: 'onMouseDown',
3031
mouseup: 'onMouseUp',
3132
touchstart: 'onTouchStart',
3233
touchend: 'onTouchEnd'
3334
};
3435

36+
const reactMajorVersion = parseInt(React.version.split('.')[0], 10);
37+
38+
const mergeRefs = <T extends any>(
39+
refs: Array<Ref<T> | undefined | null>
40+
): RefCallback<T> => {
41+
return (value) => {
42+
refs.forEach((ref) => {
43+
if (typeof ref === 'function') {
44+
ref(value);
45+
} else if (ref != null) {
46+
(ref as MutableRefObject<T | null>).current = value;
47+
}
48+
});
49+
};
50+
};
51+
3552
const ClickAwayListener: FunctionComponent<Props> = ({
3653
children,
3754
onClickAway,
3855
focusEvent = 'focusin',
3956
mouseEvent = 'click',
40-
touchEvent = 'touchend'
57+
touchEvent = 'touchend',
58+
...rest
4159
}) => {
4260
const node = useRef<HTMLElement | null>(null);
4361
const bubbledEventTarget = useRef<EventTarget | null>(null);
@@ -69,19 +87,17 @@ const ClickAwayListener: FunctionComponent<Props> = ({
6987
}
7088
};
7189

72-
const handleChildRef = (childRef: HTMLElement) => {
73-
node.current = childRef;
90+
let childRef: React.Ref<any> | null = null;
7491

75-
let { ref } = children as typeof children & {
76-
ref: RefCallback<HTMLElement> | MutableRefObject<HTMLElement>;
77-
};
92+
// For React 19+, we get the ref via props.ref
93+
if (reactMajorVersion >= 19) {
94+
childRef = children.props?.ref || null;
95+
} else if ('ref' in children) {
96+
childRef = (children as any).ref;
97+
}
7898

79-
if (typeof ref === 'function') {
80-
ref(childRef);
81-
} else if (ref) {
82-
ref.current = childRef;
83-
}
84-
};
99+
// Create a combined ref handler
100+
const combinedRef = mergeRefs([node, childRef]);
85101

86102
useEffect(() => {
87103
const nodeDocument = node.current?.ownerDocument ?? document;
@@ -117,10 +133,11 @@ const ClickAwayListener: FunctionComponent<Props> = ({
117133

118134
return React.Children.only(
119135
cloneElement(children as ReactElement<any>, {
120-
ref: handleChildRef,
136+
ref: combinedRef,
121137
[mappedFocusEvent]: handleBubbledEvents(mappedFocusEvent),
122138
[mappedMouseEvent]: handleBubbledEvents(mappedMouseEvent),
123-
[mappedTouchEvent]: handleBubbledEvents(mappedTouchEvent)
139+
[mappedTouchEvent]: handleBubbledEvents(mappedTouchEvent),
140+
...rest
124141
})
125142
);
126143
};

0 commit comments

Comments
 (0)