diff --git a/UNRELEASED.md b/UNRELEASED.md index 682b3e2bba2..d12c5dab123 100644 --- a/UNRELEASED.md +++ b/UNRELEASED.md @@ -4,6 +4,7 @@ ### Enhancements +- Updated `Popover` to focus the correct element when closed ([#2255](https://github.com/Shopify/polaris-react/pull/2255)) - Updated the type of the `title` prop in `ChoiceList` from `string` to `ReactNode` ([#2355](https://github.com/Shopify/polaris-react/pull/2355)) - Added `disabled` prop to `Filters` component ([2389](https://github.com/Shopify/polaris-react/pull/2389)) diff --git a/src/components/Popover/Popover.tsx b/src/components/Popover/Popover.tsx index 2d06a55dcb4..b2b452506bb 100644 --- a/src/components/Popover/Popover.tsx +++ b/src/components/Popover/Popover.tsx @@ -1,12 +1,11 @@ import React from 'react'; import {createUniqueIDFactory} from '@shopify/javascript-utilities/other'; -import { - focusFirstFocusableNode, - findFirstFocusableNode, -} from '@shopify/javascript-utilities/focus'; +import {findFirstFocusableNode} from '@shopify/javascript-utilities/focus'; +import {focusNextFocusableNode} from '../../utilities/focus'; import {PreferredPosition, PreferredAlignment} from '../PositionedOverlay'; import {Portal} from '../Portal'; +import {portal} from '../shared'; import {CloseSource, Pane, PopoverOverlay, Section} from './components'; export {CloseSource}; @@ -126,16 +125,25 @@ export class Popover extends React.PureComponent { } private handleClose = (source: CloseSource) => { + const {activatorNode} = this.state; this.props.onClose(source); if (this.activatorContainer == null) { return; } + if ( - source === CloseSource.FocusOut || - source === CloseSource.EscapeKeypress + (source === CloseSource.FocusOut || + source === CloseSource.EscapeKeypress) && + activatorNode ) { - focusFirstFocusableNode(this.activatorContainer, false); + const focusableActivator = + findFirstFocusableNode(activatorNode) || + findFirstFocusableNode(this.activatorContainer) || + this.activatorContainer; + if (!focusNextFocusableNode(focusableActivator, isInPortal)) { + focusableActivator.focus(); + } } }; @@ -150,3 +158,14 @@ export class Popover extends React.PureComponent { this.activatorContainer = node; }; } + +function isInPortal(element: Element) { + let parentElement = element.parentElement; + + while (parentElement) { + if (parentElement.matches(portal.selector)) return false; + parentElement = parentElement.parentElement; + } + + return true; +} diff --git a/src/components/Popover/tests/Popover.test.tsx b/src/components/Popover/tests/Popover.test.tsx index 5f930bbeef8..13daf3e5862 100644 --- a/src/components/Popover/tests/Popover.test.tsx +++ b/src/components/Popover/tests/Popover.test.tsx @@ -1,6 +1,8 @@ import React, {useState, useCallback} from 'react'; import {mountWithAppProvider, findByTestID} from 'test-utilities/legacy'; +import {mountWithApp} from 'test-utilities'; import {Popover} from '../Popover'; +import {PopoverOverlay} from '../components'; describe('', () => { const spy = jest.fn(); @@ -173,4 +175,45 @@ describe('', () => { expect(onCloseSpy).not.toHaveBeenCalled(); }); + + it('focuses the next available element when the popover is closed', () => { + const id = 'focus-target'; + function PopoverTest() { + return ( + +
+ } onClose={noop} /> +
+