diff --git a/src/Calendar.jsx b/src/Calendar.jsx index c872f9a40..5fd8cede2 100644 --- a/src/Calendar.jsx +++ b/src/Calendar.jsx @@ -105,7 +105,13 @@ let Calendar = React.createClass({ require('./mixins/TimeoutMixin'), require('./mixins/PureRenderMixin'), require('./mixins/RtlParentContextMixin'), - require('./mixins/AriaDescendantMixin')() + require('./mixins/AriaDescendantMixin')(), + require('./mixins/FocusMixin')({ + willHandle() { + if (+this.props.tabIndex === -1) + return false + } + }) ], propTypes, @@ -188,8 +194,8 @@ let Calendar = React.createClass({
-1) compat.findDOMNode(this).focus() - - //console.log(document.activeElement) }, - - @widgetEnabled - _focus(focused, e){ - if (+this.props.tabIndex === -1) - return - - this.setTimeout('focus', () => { - if( focused !== this.state.focused){ - notify(this.props[focused ? 'onFocus' : 'onBlur'], e) - this.setState({ focused }) - } - }) - }, - + @widgetEditable change(date){ if (this.state.view === this.props.initialView){ diff --git a/src/Combobox.jsx b/src/Combobox.jsx index 6b3fe9007..5f4140fbd 100644 --- a/src/Combobox.jsx +++ b/src/Combobox.jsx @@ -12,7 +12,7 @@ import GroupableList from './ListGroupable'; import validateList from './util/validateListInterface'; import createUncontrolledWidget from 'uncontrollable'; import { dataItem, dataText, dataIndexOf } from './util/dataHelpers'; -import { widgetEditable, widgetEnabled, isDisabled, isReadOnly } from './util/interaction'; +import { widgetEditable, isDisabled, isReadOnly } from './util/interaction'; import { instanceId, notify, isFirstFocusedRender } from './util/widgetHelpers'; let defaultSuggest = f => f === true ? 'startsWith' : f ? f : 'eq' @@ -70,7 +70,16 @@ var ComboBox = React.createClass({ require('./mixins/DataFilterMixin'), require('./mixins/PopupScrollToMixin'), require('./mixins/RtlParentContextMixin'), - require('./mixins/AriaDescendantMixin')('input') + require('./mixins/AriaDescendantMixin')('input'), + require('./mixins/FocusMixin')({ + willHandle(focused) { + // not suggesting anymore + !focused && this.refs.input.accept() + }, + didHandle(focused) { + if (!focused) this.close() + } + }) ], propTypes: propTypes, @@ -174,8 +183,8 @@ var ComboBox = React.createClass({ {...elementProps} ref="element" onKeyDown={this._keyDown} - onFocus={this._focus.bind(null, true)} - onBlur ={this._focus.bind(null, false)} + onBlur={this.handleBlur} + onFocus={this.handleFocus} tabIndex={'-1'} className={cx(className, 'rw-combobox', 'rw-widget', { 'rw-state-focus': focused, @@ -287,22 +296,6 @@ var ComboBox = React.createClass({ this.refs.input.focus() }, - @widgetEnabled - _focus(focused, e){ - - !focused && this.refs.input.accept() //not suggesting anymore - - this.setTimeout('focus', () => { - - if( !focused) this.close() - - if( focused !== this.state.focused) { - notify(this.props[focused ? 'onFocus' : 'onBlur'], e) - this.setState({ focused: focused }) - } - }) - }, - @widgetEditable _keyDown(e){ var self = this diff --git a/src/DateTimePicker.jsx b/src/DateTimePicker.jsx index b00411208..dfbddc1f0 100644 --- a/src/DateTimePicker.jsx +++ b/src/DateTimePicker.jsx @@ -16,7 +16,7 @@ import DateInput from './DateInput'; import Btn from './WidgetButton'; import CustomPropTypes from './util/propTypes'; import createUncontrolledWidget from 'uncontrollable'; -import { widgetEditable, widgetEnabled } from './util/interaction'; +import { widgetEditable } from './util/interaction'; import { instanceId, notify, isFirstFocusedRender } from './util/widgetHelpers'; let { calendarViews: views, datePopups: popups } = constants; @@ -90,6 +90,11 @@ var DateTimePicker = React.createClass({ require('./mixins/PureRenderMixin'), require('./mixins/PopupScrollToMixin'), require('./mixins/RtlParentContextMixin'), + require('./mixins/FocusMixin')({ + didHandle(focused) { + if (!focused) this.close() + } + }), require('./mixins/AriaDescendantMixin')('valueInput', function(key, id){ var { open } = this.props , current = this.ariaActiveDescendant() @@ -168,8 +173,8 @@ var DateTimePicker = React.createClass({ tabIndex={'-1'} onKeyDown={this._keyDown} onKeyPress={this._keyPress} - onFocus={this._focus.bind(null, true)} - onBlur={this._focus.bind(null, false)} + onBlur={this.handleBlur} + onFocus={this.handleFocus} className={cx(className, 'rw-datetimepicker', 'rw-widget', { 'rw-state-focus': focused, 'rw-state-disabled': disabled, @@ -356,19 +361,6 @@ var DateTimePicker = React.createClass({ this.refs.timePopup._keyPress(e) }, - @widgetEnabled - _focus(focused, e){ - - this.setTimeout('focus', () => { - if (!focused) this.close() - - if (focused !== this.state.focused){ - notify(this.props[focused ? 'onFocus' : 'onBlur'], e) - this.setState({ focused }) - } - }) - }, - focus(){ if (activeElement() !== compat.findDOMNode(this.refs.valueInput)) this.refs.valueInput.focus() diff --git a/src/DropdownList.jsx b/src/DropdownList.jsx index e1ef94fa1..46010b522 100644 --- a/src/DropdownList.jsx +++ b/src/DropdownList.jsx @@ -69,7 +69,12 @@ var DropdownList = React.createClass({ require('./mixins/DataFilterMixin'), require('./mixins/PopupScrollToMixin'), require('./mixins/RtlParentContextMixin'), - require('./mixins/AriaDescendantMixin')() + require('./mixins/AriaDescendantMixin')(), + require('./mixins/FocusMixin')({ + didHandle(focused) { + if (!focused) this.close() + } + }) ], propTypes: propTypes, @@ -160,8 +165,8 @@ var DropdownList = React.createClass({ onKeyDown={this._keyDown} onKeyPress={this._keyPress} onClick={this._click} - onFocus={this._focus.bind(null, true)} - onBlur ={this._focus.bind(null, false)} + onBlur={this.handleBlur} + onFocus={this.handleFocus} className={cx(className, 'rw-dropdownlist', 'rw-widget', { 'rw-state-disabled': disabled, 'rw-state-readonly': readOnly, @@ -231,19 +236,6 @@ var DropdownList = React.createClass({ ) }, - @widgetEnabled - _focus(focused, e){ - - this.setTimeout('focus', () => { - if (!focused) this.close() - - if (focused !== this.state.focused) { - notify(this.props[focused ? 'onFocus' : 'onBlur'], e) - this.setState({ focused: focused }) - } - }) - }, - @widgetEditable _onSelect(data){ this.close() diff --git a/src/Multiselect.jsx b/src/Multiselect.jsx index a7c603287..5390dbcb7 100644 --- a/src/Multiselect.jsx +++ b/src/Multiselect.jsx @@ -10,7 +10,7 @@ import GroupableList from './ListGroupable'; import validateList from './util/validateListInterface'; import createUncontrolledWidget from 'uncontrollable'; import { dataItem, dataText, valueMatcher } from './util/dataHelpers'; -import { widgetEditable, widgetEnabled } from './util/interaction'; +import { widgetEditable } from './util/interaction'; import { instanceId, notify, isFirstFocusedRender } from './util/widgetHelpers'; var compatCreate = (props, msgs) => typeof msgs.createNew === 'function' @@ -76,6 +76,20 @@ var Multiselect = React.createClass({ require('./mixins/DataFilterMixin'), require('./mixins/PopupScrollToMixin'), require('./mixins/RtlParentContextMixin'), + require('./mixins/FocusMixin')({ + willHandle(focused) { + focused && this.focus() + }, + didHandle(focused) { + if (!focused) this.close() + + if (!focused && this.refs.tagList) + this.setState({ focusedTag: null }) + + if (focused && !this.props.open) + this.open() + } + }), require('./mixins/AriaDescendantMixin')('input', function(key, id){ let { ariaActiveDescendantKey: myKey } = this.props; @@ -191,9 +205,9 @@ var Multiselect = React.createClass({ ref="element" id={instanceId(this)} onKeyDown={this._keyDown} - onFocus={this._focus.bind(null, true)} - onBlur ={this._focus.bind(null, false)} - onTouchEnd={this._focus.bind(null, true)} + onBlur={this.handleBlur} + onFocus={this.handleFocus} + onTouchEnd={this.handleFocus} tabIndex={'-1'} className={cx(className, 'rw-widget', 'rw-multiselect', { 'rw-state-focus': focused, @@ -255,9 +269,8 @@ var Multiselect = React.createClass({ onKeyDown={this._searchKeyDown} onKeyUp={this._searchgKeyUp} onChange={this._typing} - onFocus={this._inputFocus} - onClick={this._inputFocus} - onTouchEnd={this._inputFocus} + onClick={this.handleInputInteraction} + onTouchEnd={this.handleInputInteraction} />
d !== value)) }, - _inputFocus(){ - this._focus(true) - !this.props.open && this.open() - }, - - @widgetEnabled - _focus(focused, e){ - if (this.props.disabled === true ) - return - - if(focused) this.refs.input.focus() - - this.setTimeout('focus', () => { - if(!focused) - this.refs.tagList && this.setState({ focusedTag: null }) - - if(focused !== this.state.focused) { - focused - ? this.open() - : this.close(); - - notify(this.props[focused ? 'onFocus' : 'onBlur'], e) - this.setState({ focused: focused }) - } - }) - }, - - _searchKeyDown(e){ + _searchKeyDown(e) { if (e.key === 'Backspace' && e.target.value && !this._deletingText) this._deletingText = true }, - _searchgKeyUp(e){ + _searchgKeyUp(e) { if (e.key === 'Backspace' && this._deletingText) this._deletingText = false }, - _typing(e){ + _typing(e) { notify(this.props.onSearch, [ e.target.value ]) this.open() }, + @widgetEditable + handleInputInteraction() { + this.open() + }, + @widgetEditable _onSelect(data){ @@ -371,7 +362,7 @@ var Multiselect = React.createClass({ this.change(this.state.dataItems.concat(data)) this.close() - this._focus(true) + this.focus() }, @widgetEditable @@ -384,7 +375,7 @@ var Multiselect = React.createClass({ && notify(this.props.onSearch, [ '' ]) this.close() - this._focus(true) + this.focus() }, @widgetEditable @@ -461,8 +452,12 @@ var Multiselect = React.createClass({ notify(this.props.onSearch, [ '' ]) }, - open(){ - if (!(this.props.disabled === true || this.props.readOnly === true)) + focus() { + this.refs.input.focus() + }, + + open() { + if (!this.props.open) notify(this.props.onToggle, true) }, @@ -521,4 +516,4 @@ function msgs(msgs){ } export default createUncontrolledWidget(Multiselect - , { open: 'onToggle', value: 'onChange', searchTerm: 'onSearch' }); + , { open: 'onToggle', value: 'onChange', searchTerm: 'onSearch' }, ['focus']); diff --git a/src/NumberPicker.jsx b/src/NumberPicker.jsx index 388391d73..cd89a1a69 100644 --- a/src/NumberPicker.jsx +++ b/src/NumberPicker.jsx @@ -57,7 +57,12 @@ let NumberPicker = React.createClass({ mixins: [ require('./mixins/TimeoutMixin'), require('./mixins/PureRenderMixin'), - require('./mixins/RtlParentContextMixin') + require('./mixins/RtlParentContextMixin'), + require('./mixins/FocusMixin')({ + willHandle(focused) { + if (focused) this.focus() + } + }) ], propTypes: propTypes, @@ -98,8 +103,8 @@ let NumberPicker = React.createClass({
@@ -129,7 +134,7 @@ let NumberPicker = React.createClass({ onMouseDown={this._mouseDown.bind(null, directions.DOWN)} onMouseUp={this._mouseUp.bind(null, directions.DOWN)} onMouseLeave={this._mouseUp.bind(null, directions.DOWN)} - onClick={this._focus.bind(null, true)} + onClick={this.handleFocus} disabled={val === this.props.min || this.props.disabled} aria-disabled={val === this.props.min || this.props.disabled}> @@ -193,20 +198,6 @@ let NumberPicker = React.createClass({ this._cancelRepeater = null; }, - @widgetEnabled - _focus(focused, e) { - - focused && compat.findDOMNode(this.refs.input).focus() - - this.setTimeout('focus', () => { - if( focused !== this.state.focused){ - notify(this.props[focused ? 'onFocus' : 'onBlur'], e) - this.setState({ focused: focused }) - } - - }, 0) - }, - @widgetEditable _keyDown(e) { var key = e.key; @@ -232,6 +223,10 @@ let NumberPicker = React.createClass({ } }, + focus() { + compat.findDOMNode(this.refs.input).focus() + }, + increment() { return this.step(this.props.step) }, diff --git a/src/SelectList.jsx b/src/SelectList.jsx index 1b8a7b02d..7540320d6 100644 --- a/src/SelectList.jsx +++ b/src/SelectList.jsx @@ -13,7 +13,7 @@ import validateList from './util/validateListInterface'; import scrollTo from 'dom-helpers/util/scrollTo'; import { dataItem } from './util/dataHelpers'; -import { widgetEditable, widgetEnabled } from './util/interaction'; +import { widgetEditable } from './util/interaction'; import { instanceId, notify } from './util/widgetHelpers'; import { isDisabled, isReadOnly, contains } from './util/interaction'; @@ -60,7 +60,13 @@ var SelectList = React.createClass({ mixins: [ require('./mixins/TimeoutMixin'), require('./mixins/RtlParentContextMixin'), - require('./mixins/AriaDescendantMixin')() + require('./mixins/AriaDescendantMixin')(), + require('./mixins/FocusMixin')({ + willHandle(focused) { + if (focused) + this.focus() + } + }) ], getDefaultProps(){ @@ -131,8 +137,8 @@ var SelectList = React.createClass({
{ - if (focused !== this.state.focused) { - notify(this.props[focused ? 'onFocus' : 'onBlur'], e) - this.setState({ focused: focused }) - } - }) - }, - search(character) { var word = ((this._searchTerm || '') + character).toLowerCase() , list = this.refs.list diff --git a/src/mixins/FocusMixin.js b/src/mixins/FocusMixin.js new file mode 100644 index 000000000..7d7e7dda8 --- /dev/null +++ b/src/mixins/FocusMixin.js @@ -0,0 +1,39 @@ +import { notify } from '../util/widgetHelpers'; +import { widgetEnabled } from '../util/interaction'; +import compat from '../util/compat'; + +export default function FocusMixin({ willHandle, didHandle }) { + function handleFocus(inst, focused, event) { + let handler = inst.props[focused ? 'onFocus' : 'onBlur'] + + if (handler && event) + event.persist() + + if (willHandle && willHandle.call(inst, focused, event) === false) + return + + inst.setTimeout('focus', () => { + //compat.batchedUpdates(() => { + if (didHandle) didHandle.call(inst, focused, event) + + if (focused !== inst.state.focused) { + notify(handler, event) + inst.setState({ focused }) + } + //}) + }) + } + + return { + + @widgetEnabled + handleBlur(event) { + handleFocus(this, false, event) + }, + + @widgetEnabled + handleFocus(event) { + handleFocus(this, true, event) + } + } +}