Skip to content

Commit

Permalink
Focus return: make document.activeElement relative to ref (#26814)
Browse files Browse the repository at this point in the history
* Try without activeElement

* Prepend the focus history with the active element on mount
  • Loading branch information
ellatrix authored Nov 9, 2020
1 parent 0016f2d commit b21a82d
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 49 deletions.
45 changes: 20 additions & 25 deletions packages/components/src/higher-order/with-focus-return/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { uniq } from 'lodash';
/**
* WordPress dependencies
*/
import { Component, createContext } from '@wordpress/element';
import { createContext, useEffect, useRef, useState } from '@wordpress/element';

const { Provider, Consumer } = createContext( {
focusHistory: [],
Expand All @@ -23,20 +23,19 @@ Consumer.displayName = 'FocusReturnConsumer';
*/
const MAX_STACK_LENGTH = 100;

class FocusReturnProvider extends Component {
constructor() {
super( ...arguments );
function FocusReturnProvider( { children, className } ) {
const ref = useRef();
const [ focusHistory, setFocusHistory ] = useState( [] );

this.onFocus = this.onFocus.bind( this );

this.state = {
focusHistory: [],
};
}

onFocus( event ) {
const { focusHistory } = this.state;
// Prepend the focus history with the active element on mount.
useEffect( () => {
setFocusHistory( [
ref.current.ownerDocument.activeElement,
...focusHistory,
] );
}, [] );

function onFocus( event ) {
// Push the focused element to the history stack, keeping only unique
// members but preferring the _last_ occurrence of any duplicates.
// Lodash's `uniq` behavior favors the first occurrence, so the array
Expand All @@ -51,20 +50,16 @@ class FocusReturnProvider extends Component {
.reverse()
).reverse();

this.setState( { focusHistory: nextFocusHistory } );
setFocusHistory( nextFocusHistory );
}

render() {
const { children, className } = this.props;

return (
<Provider value={ this.state }>
<div onFocus={ this.onFocus } className={ className }>
{ children }
</div>
</Provider>
);
}
return (
<Provider value={ focusHistory }>
<div ref={ ref } onFocus={ onFocus } className={ className }>
{ children }
</div>
</Provider>
);
}

export default FocusReturnProvider;
Expand Down
24 changes: 10 additions & 14 deletions packages/components/src/higher-order/with-focus-return/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ function withFocusReturn( options ) {
super( ...arguments );

this.ownFocusedElements = new Set();
this.activeElementOnMount = document.activeElement;
this.setIsFocusedFalse = () => ( this.isFocused = false );
this.setIsFocusedTrue = ( event ) => {
this.ownFocusedElements.add( event.target );
Expand All @@ -64,11 +63,7 @@ function withFocusReturn( options ) {
}

componentWillUnmount() {
const {
activeElementOnMount,
isFocused,
ownFocusedElements,
} = this;
const { isFocused, ownFocusedElements } = this;

if ( ! isFocused ) {
return;
Expand All @@ -83,15 +78,13 @@ function withFocusReturn( options ) {
return;
}

const stack = [
...without(
this.props.focus.focusHistory,
...ownFocusedElements
),
activeElementOnMount,
];
const stack = without(
this.props.focusHistory,
...ownFocusedElements
);

let candidate;

while ( ( candidate = stack.pop() ) ) {
if ( document.body.contains( candidate ) ) {
candidate.focus();
Expand All @@ -115,7 +108,10 @@ function withFocusReturn( options ) {
return ( props ) => (
<Consumer>
{ ( context ) => (
<FocusReturn childProps={ props } focus={ context } />
<FocusReturn
childProps={ props }
focusHistory={ context }
/>
) }
</Consumer>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,16 @@ describe( 'withFocusReturn()', () => {
} );

it( 'should not switch focus back to the bound focus element', () => {
const { unmount } = render( <Composite />, {
container: document.body.appendChild(
document.createElement( 'div' )
),
} );
const { unmount } = render(
<Provider>
<Composite />
</Provider>,
{
container: document.body.appendChild(
document.createElement( 'div' )
),
}
);

// Change activeElement.
switchFocusTo.focus();
Expand All @@ -86,11 +91,16 @@ describe( 'withFocusReturn()', () => {
} );

it( 'should switch focus back when unmounted while having focus', () => {
const { container, unmount } = render( <Composite />, {
container: document.body.appendChild(
document.createElement( 'div' )
),
} );
const { container, unmount } = render(
<Provider>
<Composite />
</Provider>,
{
container: document.body.appendChild(
document.createElement( 'div' )
),
}
);

const textarea = container.querySelector( 'textarea' );
textarea.focus();
Expand Down

0 comments on commit b21a82d

Please sign in to comment.