diff --git a/package-lock.json b/package-lock.json index fc72e13..9628639 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,8 +42,8 @@ "typescript": "^5.5.4" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/@ampproject/remapping": { diff --git a/src/index.tsx b/src/index.tsx index ba327d0..d622b44 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,5 @@ import React, { + Ref, useRef, useEffect, RefCallback, @@ -25,19 +26,36 @@ interface Props extends HTMLAttributes { const eventTypeMapping = { click: 'onClick', focusin: 'onFocus', - focusout: 'onFocus', + focusout: 'onBlur', mousedown: 'onMouseDown', mouseup: 'onMouseUp', touchstart: 'onTouchStart', touchend: 'onTouchEnd' }; +const reactMajorVersion = parseInt(React.version.split('.')[0], 10); + +const mergeRefs = ( + refs: Array | undefined | null> +): RefCallback => { + return (value) => { + refs.forEach((ref) => { + if (typeof ref === 'function') { + ref(value); + } else if (ref != null) { + (ref as MutableRefObject).current = value; + } + }); + }; +}; + const ClickAwayListener: FunctionComponent = ({ children, onClickAway, focusEvent = 'focusin', mouseEvent = 'click', - touchEvent = 'touchend' + touchEvent = 'touchend', + ...rest }) => { const node = useRef(null); const bubbledEventTarget = useRef(null); @@ -69,19 +87,17 @@ const ClickAwayListener: FunctionComponent = ({ } }; - const handleChildRef = (childRef: HTMLElement) => { - node.current = childRef; + let childRef: React.Ref | null = null; - let { ref } = children as typeof children & { - ref: RefCallback | MutableRefObject; - }; + // For React 19+, we get the ref via props.ref + if (reactMajorVersion >= 19) { + childRef = children.props?.ref || null; + } else if ('ref' in children) { + childRef = (children as any).ref; + } - if (typeof ref === 'function') { - ref(childRef); - } else if (ref) { - ref.current = childRef; - } - }; + // Create a combined ref handler + const combinedRef = mergeRefs([node, childRef]); useEffect(() => { const nodeDocument = node.current?.ownerDocument ?? document; @@ -117,10 +133,11 @@ const ClickAwayListener: FunctionComponent = ({ return React.Children.only( cloneElement(children as ReactElement, { - ref: handleChildRef, + ref: combinedRef, [mappedFocusEvent]: handleBubbledEvents(mappedFocusEvent), [mappedMouseEvent]: handleBubbledEvents(mappedMouseEvent), - [mappedTouchEvent]: handleBubbledEvents(mappedTouchEvent) + [mappedTouchEvent]: handleBubbledEvents(mappedTouchEvent), + ...rest }) ); };