/
index.js
127 lines (112 loc) · 3.32 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/**
* External dependencies
*/
import { stubTrue, without } from 'lodash';
/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import { createHigherOrderComponent } from '@wordpress/compose';
/**
* Internal dependencies
*/
import Provider, { Consumer } from './context';
/**
* Returns true if the given object is component-like. An object is component-
* like if it is an instance of wp.element.Component, or is a function.
*
* @param {*} object Object to test.
*
* @return {boolean} Whether object is component-like.
*/
function isComponentLike( object ) {
return (
object instanceof Component ||
typeof object === 'function'
);
}
/**
* Higher Order Component used to be used to wrap disposable elements like
* sidebars, modals, dropdowns. When mounting the wrapped component, we track a
* reference to the current active element so we know where to restore focus
* when the component is unmounted.
*
* @param {(WPComponent|Object)} options The component to be enhanced with
* focus return behavior, or an object
* describing the component and the
* focus return characteristics.
*
* @return {Component} Component with the focus restauration behaviour.
*/
function withFocusReturn( options ) {
// Normalize as overloaded form `withFocusReturn( options )( Component )`
// or as `withFocusReturn( Component )`.
if ( isComponentLike( options ) ) {
const WrappedComponent = options;
return withFocusReturn( {} )( WrappedComponent );
}
const { onFocusReturn = stubTrue } = options;
return function( WrappedComponent ) {
class FocusReturn extends Component {
constructor() {
super( ...arguments );
this.ownFocusedElements = new Set;
this.activeElementOnMount = document.activeElement;
this.setIsFocusedFalse = () => this.isFocused = false;
this.setIsFocusedTrue = ( event ) => {
this.ownFocusedElements.add( event.target );
this.isFocused = true;
};
}
componentWillUnmount() {
const {
activeElementOnMount,
isFocused,
ownFocusedElements,
} = this;
if ( ! isFocused ) {
return;
}
// Defer to the component's own explicit focus return behavior,
// if specified. The function should return `false` to prevent
// the default behavior otherwise occurring here. This allows
// for support that the `onFocusReturn` decides to allow the
// default behavior to occur under some conditions.
if ( onFocusReturn() === false ) {
return;
}
const stack = [
...without(
this.props.focus.focusHistory,
...ownFocusedElements
),
activeElementOnMount,
];
let candidate;
while ( ( candidate = stack.pop() ) ) {
if ( document.body.contains( candidate ) ) {
candidate.focus();
return;
}
}
}
render() {
return (
<div
onFocus={ this.setIsFocusedTrue }
onBlur={ this.setIsFocusedFalse }
>
<WrappedComponent { ...this.props.childProps } />
</div>
);
}
}
return ( props ) => (
<Consumer>
{ ( context ) => <FocusReturn childProps={ props } focus={ context } /> }
</Consumer>
);
};
}
export default createHigherOrderComponent( withFocusReturn, 'withFocusReturn' );
export { Provider };