Skip to content

Commit 7cd42b1

Browse files
committed
fix: Trigger onFocus/onBlur
1 parent 0b8d9b7 commit 7cd42b1

File tree

6 files changed

+73
-25
lines changed

6 files changed

+73
-25
lines changed

__snapshots__/src/index.test.js.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Generated by [AVA](https://ava.li).
1111
<div
1212
className="react-dropdown-tree-select"
1313
id="rdts"
14+
onBlur={Function {}}
15+
onFocus={Function {}}
1416
>
1517
<div
1618
className="dropdown"
@@ -180,6 +182,8 @@ Generated by [AVA](https://ava.li).
180182
<div
181183
className="react-dropdown-tree-select"
182184
id="rdts"
185+
onBlur={Function {}}
186+
onFocus={Function {}}
183187
>
184188
<div
185189
className="dropdown"
@@ -348,6 +352,8 @@ Generated by [AVA](https://ava.li).
348352
<div
349353
className="react-dropdown-tree-select"
350354
id="rdts"
355+
onBlur={Function {}}
356+
onFocus={Function {}}
351357
>
352358
<div
353359
className="dropdown"
@@ -469,6 +475,8 @@ Generated by [AVA](https://ava.li).
469475
<div
470476
className="react-dropdown-tree-select"
471477
id="rdts"
478+
onBlur={Function {}}
479+
onFocus={Function {}}
472480
>
473481
<div
474482
className="dropdown radio-select"
@@ -620,6 +628,8 @@ Generated by [AVA](https://ava.li).
620628
<div
621629
className="react-dropdown-tree-select"
622630
id="rdts"
631+
onBlur={Function {}}
632+
onFocus={Function {}}
623633
>
624634
<div
625635
className="dropdown"
@@ -689,6 +699,8 @@ Generated by [AVA](https://ava.li).
689699
<div
690700
className="react-dropdown-tree-select"
691701
id="rdts"
702+
onBlur={Function {}}
703+
onFocus={Function {}}
692704
>
693705
<div
694706
className="dropdown"
81 Bytes
Binary file not shown.

docs/src/stories/Options/index.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ class WithOptions extends PureComponent {
3232
onNodeToggle = curNode => {
3333
console.log('onNodeToggle::', curNode)
3434
}
35+
onFocus = (node, action) => {
36+
console.log('onFocus::', action, node)
37+
}
38+
onBlur = (node, action) => {
39+
console.log('onBlur::', action, node)
40+
}
3541

3642
onOptionsChange = value => {
3743
this.setState({ [value]: !this.state[value] })
@@ -121,6 +127,8 @@ class WithOptions extends PureComponent {
121127
id="rdts"
122128
data={data}
123129
onChange={this.onChange}
130+
onBlur={this.onBlur}
131+
onFocus={this.onFocus}
124132
onAction={this.onAction}
125133
onNodeToggle={this.onNodeToggle}
126134
clearSearchOnChange={clearSearchOnChange}

src/index.js

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class DropdownTreeSelect extends Component {
6464
this.state = {
6565
searchModeOn: false,
6666
currentFocus: undefined,
67+
isManagingFocus: false,
6768
}
6869
this.clientId = props.id || clientIdGenerator.get(this)
6970
}
@@ -106,40 +107,61 @@ class DropdownTreeSelect extends Component {
106107
}
107108

108109
componentWillUnmount() {
109-
document.removeEventListener('click', this.handleOutsideClick, false)
110+
document.removeEventListener('click', this.handleDropdownCollapse, false)
110111
}
111112

112113
componentWillReceiveProps(nextProps) {
113114
this.initNewProps(nextProps)
114115
}
115116

117+
onBlur = () => {
118+
console.log(this._timeoutID)
119+
120+
// setTimeout runs afterwards in the event.
121+
// If onFocus is triggered immediately in a child component, clearTimeout will stop setTimeout to run.
122+
this._timeoutID = setTimeout(() => {
123+
if (this.state.isManagingFocus) {
124+
this.props.onBlur()
125+
this.setState({
126+
isManagingFocus: false,
127+
})
128+
}
129+
}, 0)
130+
}
131+
132+
onFocus = () => {
133+
clearTimeout(this._timeoutID)
134+
if (!this.state.isManagingFocus) {
135+
this.props.onFocus()
136+
this.setState({
137+
isManagingFocus: true,
138+
})
139+
}
140+
}
141+
116142
handleClick = (e, callback) => {
117143
this.setState(prevState => {
118144
// keep dropdown active when typing in search box
119145
const showDropdown = this.props.showDropdown === 'always' || this.keepDropdownActive || !prevState.showDropdown
120146

121-
// register event listeners only if there is a state change
122-
if (showDropdown !== prevState.showDropdown) {
123-
if (showDropdown) {
124-
document.addEventListener('click', this.handleOutsideClick, false)
125-
} else {
126-
document.removeEventListener('click', this.handleOutsideClick, false)
127-
}
128-
}
147+
const searchStateReset = !showDropdown ? this.resetSearchState() : {}
129148

130-
if (showDropdown) this.props.onFocus()
131-
else this.props.onBlur()
149+
if (this.state.isManagingFocus && this.props.showDropdown !== 'always') {
150+
document.addEventListener('click', this.handleDropdownCollapse, false)
151+
}
132152

133-
return !showDropdown ? { showDropdown, ...this.resetSearchState() } : { showDropdown }
153+
return {
154+
showDropdown,
155+
searchStateReset,
156+
}
134157
}, callback)
135158
}
136159

137-
handleOutsideClick = e => {
138-
if (this.props.showDropdown === 'always' || !isOutsideClick(e, this.node)) {
139-
return
140-
}
141-
142-
this.handleClick()
160+
handleDropdownCollapse = e => {
161+
if (!isOutsideClick(e, this.node)) return
162+
document.removeEventListener('click', this.handleDropdownCollapse, false)
163+
const showDropdown = this.props.showDropdown === 'always'
164+
this.setState({ showDropdown: showDropdown })
143165
}
144166

145167
onInputChange = value => {
@@ -157,12 +179,15 @@ class DropdownTreeSelect extends Component {
157179
})
158180
}
159181

160-
onTagRemove = (id, isKeyboardEvent) => {
182+
onTagRemove = id => {
161183
const { tags: prevTags } = this.state
162184
this.onCheckboxChange(id, false, tags => {
163-
if (!isKeyboardEvent) return
164-
165-
keyboardNavigation.getNextFocusAfterTagDelete(id, prevTags, tags, this.searchInput).focus()
185+
const nextFocus = keyboardNavigation.getNextFocusAfterTagDelete(id, prevTags, tags, this.searchInput)
186+
if (nextFocus) {
187+
nextFocus.focus()
188+
} else {
189+
this.onBlur()
190+
}
166191
})
167192
}
168193

@@ -201,7 +226,7 @@ class DropdownTreeSelect extends Component {
201226
}
202227

203228
if (isSingleSelect && !showDropdown) {
204-
document.removeEventListener('click', this.handleOutsideClick, false)
229+
document.removeEventListener('click', this.handleDropdownCollapse, false)
205230
}
206231

207232
keyboardNavigation.adjustFocusedProps(currentFocusNode, node)
@@ -311,6 +336,8 @@ class DropdownTreeSelect extends Component {
311336
return (
312337
<div
313338
id={this.clientId}
339+
onBlur={this.onBlur}
340+
onFocus={this.onFocus}
314341
className={[this.props.className && this.props.className, 'react-dropdown-tree-select']
315342
.filter(Boolean)
316343
.join(' ')}

src/index.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ test('detects click inside', t => {
270270
target: checkboxItem,
271271
})
272272
Object.defineProperty(event, 'target', { value: checkboxItem, enumerable: true })
273-
wrapper.instance().handleOutsideClick(event)
273+
wrapper.instance().handleDropdownCollapse(event)
274274

275275
t.true(wrapper.state().showDropdown)
276276
})
@@ -291,7 +291,7 @@ test('detects click outside when other dropdown instance', t => {
291291
target: searchInput,
292292
})
293293
Object.defineProperty(event, 'target', { value: searchInput, enumerable: true })
294-
wrapper1.instance().handleOutsideClick(event)
294+
wrapper1.instance().handleDropdownCollapse(event)
295295

296296
t.false(wrapper1.state().showDropdown)
297297
})

types/react-dropdown-tree-select.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ declare module 'react-dropdown-tree-select' {
114114
searchInput: HTMLInputElement
115115
keepDropdownActive: boolean
116116
handleClick(): void
117+
_timeoutID: number
117118
}
118119

119120
export interface TreeNode {

0 commit comments

Comments
 (0)