From d7b2538cfe4f8e604820c87bd7126bc608db9b4e Mon Sep 17 00:00:00 2001 From: "Ademola." Date: Sat, 12 Apr 2025 22:15:08 +0100 Subject: [PATCH 1/6] fix(refs): add support for react19 (#96) --- src/index.tsx | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index ba327d0..77c3010 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,5 @@ import React, { + Ref, useRef, useEffect, RefCallback, @@ -32,6 +33,20 @@ const eventTypeMapping = { touchend: 'onTouchEnd' }; +function useForkRef( + ...refs: Array | undefined> +): RefCallback { + return (node: T) => { + refs.forEach((ref) => { + if (typeof ref === 'function') { + ref(node); + } else if (ref != null && typeof ref === 'object') { + (ref as MutableRefObject).current = node; + } + }); + }; +} + const ClickAwayListener: FunctionComponent = ({ children, onClickAway, @@ -69,19 +84,9 @@ const ClickAwayListener: FunctionComponent = ({ } }; - const handleChildRef = (childRef: HTMLElement) => { - node.current = childRef; - - let { ref } = children as typeof children & { - ref: RefCallback | MutableRefObject; - }; - - if (typeof ref === 'function') { - ref(childRef); - } else if (ref) { - ref.current = childRef; - } - }; + const combinedRef = useForkRef((ref) => { + node.current = ref; + }, (children as any).ref); useEffect(() => { const nodeDocument = node.current?.ownerDocument ?? document; @@ -117,7 +122,7 @@ const ClickAwayListener: FunctionComponent = ({ return React.Children.only( cloneElement(children as ReactElement, { - ref: handleChildRef, + ref: combinedRef, [mappedFocusEvent]: handleBubbledEvents(mappedFocusEvent), [mappedMouseEvent]: handleBubbledEvents(mappedMouseEvent), [mappedTouchEvent]: handleBubbledEvents(mappedTouchEvent) From 97a0227a7bc375bc9c52274ebf4dc91f3c5df0e2 Mon Sep 17 00:00:00 2001 From: Ademola Adegbuyi Date: Sat, 12 Apr 2025 22:40:20 +0100 Subject: [PATCH 2/6] fix: use ref directly in react19 --- src/index.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/index.tsx b/src/index.tsx index 77c3010..9af647f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -33,6 +33,8 @@ const eventTypeMapping = { touchend: 'onTouchEnd' }; +const reactMajorVersion = parseInt(React.version.split('.')[0], 10); + function useForkRef( ...refs: Array | undefined> ): RefCallback { @@ -88,6 +90,10 @@ const ClickAwayListener: FunctionComponent = ({ node.current = ref; }, (children as any).ref); + const handleReact19ChildRef = (instance: HTMLElement | null) => { + node.current = instance; + }; + useEffect(() => { const nodeDocument = node.current?.ownerDocument ?? document; @@ -122,7 +128,7 @@ const ClickAwayListener: FunctionComponent = ({ return React.Children.only( cloneElement(children as ReactElement, { - ref: combinedRef, + ref: reactMajorVersion >= 19 ? handleReact19ChildRef : combinedRef, [mappedFocusEvent]: handleBubbledEvents(mappedFocusEvent), [mappedMouseEvent]: handleBubbledEvents(mappedMouseEvent), [mappedTouchEvent]: handleBubbledEvents(mappedTouchEvent) From 8fdd2ff5cb3c4be32ca8d50d9350f46b008ceeef Mon Sep 17 00:00:00 2001 From: Ademola Adegbuyi Date: Sat, 12 Apr 2025 22:42:53 +0100 Subject: [PATCH 3/6] fix: add console --- src/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.tsx b/src/index.tsx index 9af647f..cc3d608 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -34,6 +34,7 @@ const eventTypeMapping = { }; const reactMajorVersion = parseInt(React.version.split('.')[0], 10); +console.log({ reactMajorVersion }); function useForkRef( ...refs: Array | undefined> From a5f3cdd7a08eec59e89172c8f3b24e920d77af48 Mon Sep 17 00:00:00 2001 From: Ademola Adegbuyi Date: Sat, 12 Apr 2025 23:58:26 +0100 Subject: [PATCH 4/6] fix: react19 support --- package-lock.json | 4 ++-- src/index.tsx | 39 +++++++++++++++++++++------------------ 2 files changed, 23 insertions(+), 20 deletions(-) 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 cc3d608..3f74a45 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -33,22 +33,21 @@ const eventTypeMapping = { touchend: 'onTouchEnd' }; -const reactMajorVersion = parseInt(React.version.split('.')[0], 10); -console.log({ reactMajorVersion }); - -function useForkRef( - ...refs: Array | undefined> -): RefCallback { - return (node: T) => { +const mergeRefs = ( + refs: Array | undefined | null> +): RefCallback => { + return (value) => { refs.forEach((ref) => { if (typeof ref === 'function') { - ref(node); - } else if (ref != null && typeof ref === 'object') { - (ref as MutableRefObject).current = node; + ref(value); + } else if (ref != null) { + (ref as MutableRefObject).current = value; } }); }; -} +}; + +const reactMajorVersion = parseInt(React.version.split('.')[0], 10); const ClickAwayListener: FunctionComponent = ({ children, @@ -87,13 +86,17 @@ const ClickAwayListener: FunctionComponent = ({ } }; - const combinedRef = useForkRef((ref) => { - node.current = ref; - }, (children as any).ref); + let childRef: React.Ref | null = null; - const handleReact19ChildRef = (instance: HTMLElement | null) => { - node.current = instance; - }; + // 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; + } + + // Create a combined ref handler + const combinedRef = mergeRefs([node, childRef]); useEffect(() => { const nodeDocument = node.current?.ownerDocument ?? document; @@ -129,7 +132,7 @@ const ClickAwayListener: FunctionComponent = ({ return React.Children.only( cloneElement(children as ReactElement, { - ref: reactMajorVersion >= 19 ? handleReact19ChildRef : combinedRef, + ref: combinedRef, [mappedFocusEvent]: handleBubbledEvents(mappedFocusEvent), [mappedMouseEvent]: handleBubbledEvents(mappedMouseEvent), [mappedTouchEvent]: handleBubbledEvents(mappedTouchEvent) From a4a7b9999720ee7d129cd70100e67915fe369fcb Mon Sep 17 00:00:00 2001 From: Ademola Adegbuyi Date: Sun, 13 Apr 2025 00:01:59 +0100 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20focus=20out=20should=20call=20onBlur?= =?UTF-8?q?=20=F0=9F=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 3f74a45..5192ff8 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -26,13 +26,15 @@ 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 => { @@ -47,8 +49,6 @@ const mergeRefs = ( }; }; -const reactMajorVersion = parseInt(React.version.split('.')[0], 10); - const ClickAwayListener: FunctionComponent = ({ children, onClickAway, From 39e58e26a5cff84d600e375ffe82a30a25b388a0 Mon Sep 17 00:00:00 2001 From: Ademola Adegbuyi Date: Sun, 13 Apr 2025 00:15:05 +0100 Subject: [PATCH 6/6] fix: pass down props --- src/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 5192ff8..d622b44 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -54,7 +54,8 @@ const ClickAwayListener: FunctionComponent = ({ onClickAway, focusEvent = 'focusin', mouseEvent = 'click', - touchEvent = 'touchend' + touchEvent = 'touchend', + ...rest }) => { const node = useRef(null); const bubbledEventTarget = useRef(null); @@ -135,7 +136,8 @@ const ClickAwayListener: FunctionComponent = ({ ref: combinedRef, [mappedFocusEvent]: handleBubbledEvents(mappedFocusEvent), [mappedMouseEvent]: handleBubbledEvents(mappedMouseEvent), - [mappedTouchEvent]: handleBubbledEvents(mappedTouchEvent) + [mappedTouchEvent]: handleBubbledEvents(mappedTouchEvent), + ...rest }) ); };