Make OneOrAny work as expected, with correct on blur sequencing

pull/10616/head
Jared Scheib 2017-08-07 17:06:52 -07:00
parent 428fc017b8
commit 5c0c457645
1 changed files with 88 additions and 58 deletions

View File

@ -1,91 +1,122 @@
import React, {Component, PropTypes} from 'react' import React, {Component, PropTypes} from 'react'
import classnames from 'classnames' import classnames from 'classnames'
// these help ensure that blur and toggle events don't both change knob's side // these help ensure that blur and toggle events don't both change toggle's side
const RESET_TIMEOUT = 300
const TOGGLE_CLICKED_TIMEOUT = 20 const TOGGLE_CLICKED_TIMEOUT = 20
const BLUR_FOCUS_GAP_TIMEOUT = 10 const BLUR_FOCUS_GAP_TIMEOUT = 10
// TODO: separate useInput from useValue in order to perform focus() correctly
class OneOrAny extends Component { class OneOrAny extends Component {
constructor(props) { constructor(props) {
super(props) super(props)
const {useRightValue, leftValue, rightValue} = props const {value, leftValue} = props
const useRightValue = value !== ''
this.state = { this.state = {
useRightValue, useRightValue,
leftValue, leftValue,
rightValue, rightValue: value || '',
toggleWasClicked: false, leftValueFieldClicked: false,
toggleClicked: false,
rightValueInputBlurred: false,
// rightValueInputClicked: false, // TODO: implement right input clickability
} }
this.handleToggleLeftValue = ::this.handleToggleLeftValue this.useLeftValue = ::this.useLeftValue
this.handleToggleValue = ::this.handleToggleValue this.toggleValue = ::this.toggleValue
this.handleToggleRightValue = ::this.handleToggleRightValue this.useRightValue = ::this.useRightValue
this.handleBlurRight = ::this.handleBlurRight this.handleClickLeftValueField = ::this.handleClickLeftValueField
this.handleSetRightValue = ::this.handleSetRightValue this.handleClickToggle = ::this.handleClickToggle
this.handleSetValue = ::this.handleSetValue this.handleBlurRightValueInput = ::this.handleBlurRightValueInput
this.handleChangeRightValue = ::this.handleChangeRightValue
this.setRightValue = ::this.setRightValue
this.setValue = ::this.setValue
} }
handleToggleLeftValue() { useLeftValue() {
return () => { this.setState({useRightValue: false}, this.setValue)
this.setState({useRightValue: false}, this.handleSetValue) }
toggleValue() {
const useRightValueNext = !this.state.useRightValue
if (useRightValueNext && !this.state.rightValueInputBlurred) {
this.useRightValue()
} else {
this.useLeftValue()
} }
} }
handleToggleValue() { useRightValue() {
this.setState({useRightValue: true}, () => {
if (this.state.toggleClicked && !this.state.rightValueInputBlurred) {
// TODO: || if this.state.rightValueInputClicked
this.rightValueInput.focus()
}
this.setValue()
})
}
handleClickLeftValueField() {
return () => { return () => {
const {useRightValue} = this.state this.setState({leftValueFieldClicked: true}, this.useLeftValue)
this.setState( }
{useRightValue: !useRightValue, toggleWasClicked: true}, }
this.handleSetValue
) handleClickToggle() {
// this helps ensure that when the toggle is clicked, if the rightValue return () => {
// input's blur event also fires, that only the expected behavior happens this.setState({toggleClicked: true}, () => {
this.toggleValue()
})
setTimeout(() => { setTimeout(() => {
this.setState({toggleWasClicked: false}) this.setState({toggleClicked: false})
}, TOGGLE_CLICKED_TIMEOUT) }, TOGGLE_CLICKED_TIMEOUT)
} }
} }
handleToggleRightValue() { handleBlurRightValueInput() {
return () => {
this.setState({useRightValue: true}, this.handleSetValue)
}
}
handleBlurRight() {
return e => { return e => {
const rightValue = e.target.value.trim() this.setState(
this.setState({rightValue}, () => { {rightValueInputBlurred: true, rightValue: e.target.value.trim()},
if (rightValue === '') { () => {
// this helps ensure that when the toggle is clicked, if the rightValue if (this.state.rightValue === '') {
// input's blur event also fires, that only the expected behavior happens setTimeout(() => {
setTimeout(() => { if (!this.state.toggleClicked) {
if (!this.state.toggleWasClicked) { this.useLeftValue()
this.handleToggleLeftValue()() }
} }, BLUR_FOCUS_GAP_TIMEOUT)
}, BLUR_FOCUS_GAP_TIMEOUT) }
} }
}) )
} }
} }
handleSetRightValue() { handleChangeRightValue() {
return e => { return e => {
this.setState({rightValue: e.target.value}, this.handleSetValue) this.setRightValue(e.target.value)
} }
} }
handleSetValue() { setRightValue(value) {
this.setState({rightValue: value}, this.setValue)
}
setValue() {
const {onSetValue} = this.props const {onSetValue} = this.props
const {useRightValue, leftValue, rightValue} = this.state const {useRightValue, leftValue, rightValue} = this.state
if (!useRightValue) { if (useRightValue) {
onSetValue({value: rightValue})
} else {
this.setState({rightValue: ''}) this.setState({rightValue: ''})
onSetValue({value: leftValue})
} }
onSetValue({value: useRightValue ? rightValue : leftValue}) // reset UI interaction state-tracking values & prevent blur + click
setTimeout(() => {
this.setState({toggleClicked: false, rightValueInputBlurred: false})
}, RESET_TIMEOUT)
} }
render() { render() {
@ -98,28 +129,29 @@ class OneOrAny extends Component {
> >
<div <div
className="one-or-any--left-label" className="one-or-any--left-label"
onClick={this.handleToggleLeftValue()} onClick={this.handleClickLeftValueField()}
> >
{leftLabel} {leftLabel}
</div> </div>
<div <div
className="one-or-any--groove-knob-container" className="one-or-any--groove-knob-container"
onClick={this.handleToggleValue()} onClick={this.handleClickToggle()}
> >
<div className="one-or-any--groove-knob" /> <div className="one-or-any--groove-knob" />
</div> </div>
<input <input
className="form-control input-sm" className="form-control input-sm"
type="number" type="number"
name="rightValue" name="rightValueInput"
id="rightValue" id="rightValueInput"
ref={el => (this.rightValueInput = el)}
value={rightValue} value={rightValue}
onClick={() => { onClick={() => {
// TODO: you can't 'click' a disabled button -- find another solution // TODO: you can't 'click' a disabled button -- find another solution
// this.handleToggleRightValue() // this.useRightValue()
}} }}
onBlur={this.handleBlurRight()} onBlur={this.handleBlurRightValueInput()}
onChange={this.handleSetRightValue()} onChange={this.handleChangeRightValue()}
placeholder={rightLabel} placeholder={rightLabel}
disabled={!useRightValue} disabled={!useRightValue}
/> />
@ -132,17 +164,15 @@ OneOrAny.defaultProps = {
leftLabel: 'auto', leftLabel: 'auto',
leftValue: '', leftValue: '',
rightLabel: 'Custom Value', rightLabel: 'Custom Value',
rightValue: '', value: '',
useRightValue: false,
} }
const {bool, func, string} = PropTypes const {func, string} = PropTypes
OneOrAny.propTypes = { OneOrAny.propTypes = {
useRightValue: bool,
leftLabel: string, leftLabel: string,
leftValue: string, leftValue: string,
rightLabel: string, rightLabel: string,
rightValue: string, value: string,
onSetValue: func.isRequired, onSetValue: func.isRequired,
} }