From 1d70aff1520545d8c32cfeb5faae780f6ba79c7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Fri, 6 Feb 2026 16:10:14 +0800 Subject: [PATCH 1/2] fix(focus): add ignoreElement support and update deps - Update @rc-component/util to 1.9.0 - Add ignoreElement to handle focus elements from Portal - Remove console.log in Panel component - Add test case for focus handling with Portal Co-Authored-By: Claude Opus 4.6 --- docs/demo/with-portal.md | 8 ++++++ docs/examples/with-portal.tsx | 53 +++++++++++++++++++++++++++++++++++ package.json | 4 +-- src/Dialog/Content/Panel.tsx | 8 +++++- tests/focus.spec.tsx | 27 +++++++++++++++++- 5 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 docs/demo/with-portal.md create mode 100644 docs/examples/with-portal.tsx diff --git a/docs/demo/with-portal.md b/docs/demo/with-portal.md new file mode 100644 index 00000000..652b8f1f --- /dev/null +++ b/docs/demo/with-portal.md @@ -0,0 +1,8 @@ +--- +title: with-portal +nav: + title: Demo + path: /demo +--- + + \ No newline at end of file diff --git a/docs/examples/with-portal.tsx b/docs/examples/with-portal.tsx new file mode 100644 index 00000000..39c61cde --- /dev/null +++ b/docs/examples/with-portal.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Dialog from '@rc-component/dialog'; + +const DivPortal: React.FC = () => { + return ReactDOM.createPortal( +
+ +
, + document.body + ); +}; + +const MyControl: React.FC = () => { + const [visible, setVisible] = React.useState(false); + + const onClick = () => { + setVisible(true); + }; + + const onClose = () => { + setVisible(false); + }; + + return ( +
+

+ +

+ + hello world + + + + +
+ ); +}; + +export default MyControl; diff --git a/package.json b/package.json index a26ecbdc..77eda6db 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "dependencies": { "@rc-component/motion": "^1.1.3", "@rc-component/portal": "^2.1.0", - "@rc-component/util": "^1.7.0", + "@rc-component/util": "^1.9.0", "clsx": "^2.1.1" }, "devDependencies": { @@ -87,4 +87,4 @@ "react": ">=18.0.0", "react-dom": ">=18.0.0" } -} +} \ No newline at end of file diff --git a/src/Dialog/Content/Panel.tsx b/src/Dialog/Content/Panel.tsx index 9e1185b7..d8caf667 100644 --- a/src/Dialog/Content/Panel.tsx +++ b/src/Dialog/Content/Panel.tsx @@ -54,7 +54,10 @@ const Panel = React.forwardRef((props, ref) => { const internalRef = useRef(null); const mergedRef = useComposeRef(holderRef, panelRef, internalRef); - useLockFocus(visible && isFixedPos && focusTrap !== false, () => internalRef.current); + const [ignoreElement] = useLockFocus( + visible && isFixedPos && focusTrap !== false, + () => internalRef.current, + ); React.useImperativeHandle(ref, () => ({ focus: () => { @@ -152,6 +155,9 @@ const Panel = React.forwardRef((props, ref) => { onMouseDown={onMouseDown} onMouseUp={onMouseUp} tabIndex={-1} + onFocus={(e) => { + ignoreElement(e.target); + }} > {modalRender ? modalRender(content) : content} diff --git a/tests/focus.spec.tsx b/tests/focus.spec.tsx index 4f8dc44b..f58fcd45 100644 --- a/tests/focus.spec.tsx +++ b/tests/focus.spec.tsx @@ -1,5 +1,6 @@ /* eslint-disable react/no-render-return-value, max-classes-per-file, func-names, no-console */ import React from 'react'; +import ReactDOM from 'react-dom'; import { act, render } from '@testing-library/react'; import Dialog from '../src'; @@ -9,7 +10,12 @@ jest.mock('@rc-component/util/lib/Dom/focus', () => { const useLockFocus = (visible: boolean, ...rest: any[]) => { globalThis.__useLockFocusVisible = visible; - return actual.useLockFocus(visible, ...rest); + const hooks = actual.useLockFocus(visible, ...rest); + const proxyIgnoreElement = (ele) => { + globalThis.__ignoredElement = ele; + hooks[0](ele); + }; + return [proxyIgnoreElement, ...hooks.slice(1)] as ReturnType; }; return { @@ -82,4 +88,23 @@ describe('Dialog.Focus', () => { expect(globalThis.__useLockFocusVisible).toBe(false); }); + + it('should call ignoreElement when input in portal is focused', () => { + render( + + {ReactDOM.createPortal(, document.body)} + , + ); + + act(() => { + jest.runAllTimers(); + }); + + const input = document.getElementById('portal-input') as HTMLElement; + act(() => { + input.focus(); + }); + + expect(globalThis.__ignoredElement).toBe(input); + }); }); From 89e1027e474eadd2628210f80730209323b67031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E7=88=B1=E5=90=83=E7=99=BD=E8=90=9D?= =?UTF-8?q?=E5=8D=9C?= Date: Fri, 6 Feb 2026 16:18:28 +0800 Subject: [PATCH 2/2] Update tests/focus.spec.tsx Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- tests/focus.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/focus.spec.tsx b/tests/focus.spec.tsx index f58fcd45..9e2449de 100644 --- a/tests/focus.spec.tsx +++ b/tests/focus.spec.tsx @@ -11,7 +11,7 @@ jest.mock('@rc-component/util/lib/Dom/focus', () => { const useLockFocus = (visible: boolean, ...rest: any[]) => { globalThis.__useLockFocusVisible = visible; const hooks = actual.useLockFocus(visible, ...rest); - const proxyIgnoreElement = (ele) => { + const proxyIgnoreElement = (ele: HTMLElement) => { globalThis.__ignoredElement = ele; hooks[0](ele); };