From 6cc9bd087119c1e57bad7dfe79060dcd5195dd13 Mon Sep 17 00:00:00 2001 From: Ferit Date: Fri, 16 Oct 2026 19:19:53 +0300 Subject: [PATCH] fix: retain tab focus across fragment-wrapped selection changes --- src/components/UncontrolledTabs.jsx | 8 ++--- src/components/__tests__/Tabs-test.jsx | 46 ++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/components/UncontrolledTabs.jsx b/src/components/UncontrolledTabs.jsx index 8e802b71d2..478ca4df61 100644 --- a/src/components/UncontrolledTabs.jsx +++ b/src/components/UncontrolledTabs.jsx @@ -4,7 +4,7 @@ import cx from 'clsx'; import { childrenPropType } from '../helpers/propTypes'; import { getTabsCount as getTabsCountHelper } from '../helpers/count'; import { deepMap } from '../helpers/childrenDeepMap'; -import { isTabList, isTabPanel, isTab } from '../helpers/elementTypes'; +import { isTabList, isTabPanel } from '../helpers/elementTypes'; function isNode(node) { return node && 'getAttribute' in node; @@ -212,9 +212,9 @@ const UncontrolledTabs = (props) => { const env = environment || (typeof window !== 'undefined' ? window : undefined); if (canUseActiveElement && env) { - wasTabFocused = React.Children.toArray(child.props.children) - .filter(isTab) - .some((tab, i) => env.document.activeElement === getTab(i)); + wasTabFocused = Object.keys(tabNodes.current).some( + (key) => env.document.activeElement === tabNodes.current[key], + ); } result = cloneElement(child, { diff --git a/src/components/__tests__/Tabs-test.jsx b/src/components/__tests__/Tabs-test.jsx index 8010cb8efd..e917844925 100644 --- a/src/components/__tests__/Tabs-test.jsx +++ b/src/components/__tests__/Tabs-test.jsx @@ -592,6 +592,52 @@ describe('', () => { expect(inputField).toHaveFocus(); }); + test('should retain tab focus when controlled tabs are inside a fragment', () => { + const renderTabs = (selectedIndex) => ( + {}}> + + <> + First + Second + + + First panel + Second panel + + ); + const { rerender } = render(renderTabs(0)); + screen.getByRole('tab', { name: 'First' }).focus(); + + rerender(renderTabs(1)); + + expect(screen.getByRole('tab', { name: 'Second' })).toHaveFocus(); + }); + + test('should preserve outside focus when controlled tabs are inside a fragment', () => { + const renderTabs = (selectedIndex) => ( + <> + + {}}> + + <> + First + Second + + + First panel + Second panel + + + ); + const { rerender } = render(renderTabs(0)); + const input = screen.getByRole('textbox', { name: 'Outside' }); + input.focus(); + + rerender(renderTabs(1)); + + expect(input).toHaveFocus(); + }); + test('should not change tabs when arrow up/down is pressed and disableUpDownKeys is passed', async () => { render( createTabs({