Skip to content

Commit 844e211

Browse files
authored
Provide natural blur command in FocusManager (#131)
This diff fixes a scenario with autocomplete when clicking on item should close menu. After calling blur input focus is still on and since menu state depends on t menu can't be enabled again without clicking outside of it. ```js <FocusManager> {manager => <> <Input {...manager.bind} /> <Menu {...manager.bind} onItemClick={manager.blur} /> </> } </FocusManager> ```
1 parent d9c9cf5 commit 844e211

File tree

3 files changed

+49
-38
lines changed

3 files changed

+49
-38
lines changed

.size-snapshot.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
{
22
"dist/react-powerplug.umd.js": {
3-
"bundled": 23752,
4-
"minified": 9415,
5-
"gzipped": 2597
3+
"bundled": 23776,
4+
"minified": 9440,
5+
"gzipped": 2612
66
},
77
"dist/react-powerplug.cjs.js": {
8-
"bundled": 20814,
9-
"minified": 10827,
10-
"gzipped": 2486
8+
"bundled": 20838,
9+
"minified": 10852,
10+
"gzipped": 2501
1111
},
1212
"dist/react-powerplug.esm.js": {
13-
"bundled": 20152,
14-
"minified": 10267,
15-
"gzipped": 2349,
13+
"bundled": 20176,
14+
"minified": 10292,
15+
"gzipped": 2366,
1616
"treeshaked": {
1717
"rollup": {
1818
"code": 365,

src/components/FocusManager.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ const FocusManager = ({ onChange, ...props }) => {
1414
renderProps(props, {
1515
focused: state.focused,
1616
blur: () => {
17-
setState({ focused: false })
17+
if (state.focused) {
18+
document.activeElement.blur()
19+
}
1820
},
1921
bind: {
2022
tabIndex: -1,

tests/components/FocusManager.test.js

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -78,33 +78,24 @@ test('keep focus when click on menu', async () => {
7878
expect(renderFn).lastCalledWith({ focused: false })
7979
})
8080

81-
test('restore focus after calling blur on inner component', async () => {
81+
test('remove focus and state after calling blur', async () => {
8282
const page = await bootstrap()
83-
const renderFn = jest.fn()
84-
const onChangeFn = jest.fn()
85-
await page.exposeFunction('renderFn', renderFn)
86-
await page.exposeFunction('onChangeFn', onChangeFn)
8783

8884
await page.evaluate(() => {
8985
const React = window.React
90-
const FocusManager = window.ReactPowerPlug.FocusManager
86+
const { FocusManager } = window.ReactPowerPlug
9187

9288
const App = () => (
93-
<FocusManager onChange={window.onChangeFn}>
89+
<FocusManager>
9490
{({ focused, blur, bind }) => {
95-
window.renderFn({ focused })
96-
const stopPropagation = e => e.stopPropagation()
91+
window.blurFocusManager = blur
92+
const style = { width: 100, height: 100 }
9793
return (
9894
<>
99-
<div id="outer" style={{ width: 100, height: 100 }} {...bind}>
100-
<div
101-
id="inner"
102-
style={{ width: 50, height: 50 }}
103-
onClick={blur}
104-
tabIndex={-1}
105-
onFocus={stopPropagation}
106-
/>
107-
</div>
95+
<div id="result">{focused ? 'focused' : 'blured'}</div>
96+
<div id="item1" style={style} {...bind} />
97+
<div id="item2" style={style} {...bind} onClick={blur} />
98+
<div id="item3" style={style} tabIndex={-1} />
10899
</>
109100
)
110101
}}
@@ -114,14 +105,32 @@ test('restore focus after calling blur on inner component', async () => {
114105
window.render(<App />)
115106
})
116107

117-
expect(renderFn).lastCalledWith({ focused: false })
118-
await page.click('#outer')
119-
expect(renderFn).lastCalledWith({ focused: true })
120-
expect(onChangeFn).lastCalledWith(true)
121-
await page.click('#inner')
122-
expect(renderFn).lastCalledWith({ focused: false })
123-
expect(onChangeFn).lastCalledWith(false)
124-
await page.click('#outer')
125-
expect(renderFn).lastCalledWith({ focused: true })
126-
expect(onChangeFn).lastCalledWith(true)
108+
const getTextContent = node => node.textContent
109+
const isActiveElement = node => node === document.activeElement
110+
111+
expect(await page.$eval('#result', getTextContent)).toEqual('blured')
112+
113+
// focused on click
114+
await page.click('#item1')
115+
expect(await page.$eval('#result', getTextContent)).toEqual('focused')
116+
expect(await page.$eval('#item1', isActiveElement)).toEqual(true)
117+
expect(await page.$eval('#item2', isActiveElement)).toEqual(false)
118+
119+
// blured on blur()
120+
await page.evaluate(() => window.blurFocusManager())
121+
expect(await page.$eval('#result', getTextContent)).toEqual('blured')
122+
expect(await page.$eval('#item1', isActiveElement)).toEqual(false)
123+
expect(await page.$eval('#item2', isActiveElement)).toEqual(false)
124+
125+
// focused and immediately blured on click with blur() in it
126+
await page.click('#item2')
127+
expect(await page.$eval('#result', getTextContent)).toEqual('blured')
128+
expect(await page.$eval('#item1', isActiveElement)).toEqual(false)
129+
expect(await page.$eval('#item2', isActiveElement)).toEqual(false)
130+
131+
// keep focus not registered in manager after blur()
132+
await page.click('#item3')
133+
expect(await page.$eval('#item3', isActiveElement)).toEqual(true)
134+
await page.evaluate(() => window.blurFocusManager())
135+
expect(await page.$eval('#item3', isActiveElement)).toEqual(true)
127136
})

0 commit comments

Comments
 (0)