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)
+ }
+ }
+}