From 466b985183a042812b82f67969b16859a516c687 Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Thu, 16 Oct 2014 12:19:18 -0700 Subject: [PATCH 1/2] only focus ComboboxOption elements Instead of stepping over all of the DOM nodes in a Combobox, step instead over only the Options. This allows one to insert seperators or dividers into the Combobox without adversely affecting its use. --- lib/combobox.js | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/combobox.js b/lib/combobox.js index df19cfe4..b4377ec7 100644 --- a/lib/combobox.js +++ b/lib/combobox.js @@ -96,17 +96,19 @@ module.exports = React.createClass({ var activedescendant; var isEmpty = true; children = children || this.props.children; + optionIndexes = []; React.Children.forEach(children, function(child, index) { if (child.type !== ComboboxOption.type) // allow random elements to live in this list return; - isEmpty = false; + isEmpty = false; // TODO: cloneWithProps and map instead of altering the children in-place var props = child.props; + optionIndexes.push(index); if (this.state.value === props.value) { // need an ID for WAI-ARIA props.id = props.id || 'rf-combobox-selected-'+(++guid); - props.isSelected = true + props.isSelected = true; activedescendant = props.id; } props.onBlur = this.handleOptionBlur; @@ -116,6 +118,7 @@ module.exports = React.createClass({ props.onMouseEnter = this.handleOptionMouseEnter.bind(this, index); }.bind(this)); return { + optionIndexes: optionIndexes, children: children, activedescendant: activedescendant, isEmpty: isEmpty @@ -315,14 +318,21 @@ module.exports = React.createClass({ }, focusSelectedOption: function() { - var selectedIndex; + var selectedDomIndex; React.Children.forEach(this.props.children, function(child, index) { if (child.props.value === this.state.value) - selectedIndex = index; + selectedDomIndex = index; }.bind(this)); + + // find the option index + var selectedOptionIndex; + this.state.menu.optionIndexes.forEach(function(domIndex, optIndex) { + if (domIndex === selectedDomIndex) selectedOptionIndex = optIndex; + }); + this.showList(); this.setState({ - focusedIndex: selectedIndex + focusedIndex: selectedOptionIndex }, this.focusOption); }, @@ -341,7 +351,7 @@ module.exports = React.createClass({ if (!this.state.isOpen && this.state.value) return this.focusSelectedOption(); this.showList(); - var length = this.props.children.length; + var length = this.state.menu.optionIndexes.length; if (index === -1) index = length - 1; else if (index === length) @@ -352,8 +362,9 @@ module.exports = React.createClass({ }, focusOption: function() { - var index = this.state.focusedIndex; - this.refs.list.getDOMNode().childNodes[index].focus(); + var optIndex = this.state.focusedIndex; + var domIndex = this.state.menu.optionIndexes[optIndex]; + this.refs.list.getDOMNode().childNodes[domIndex].focus(); }, render: function() { From 0baec39a09c2772ebfa4a90bd9d2e44944a2c1f1 Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Mon, 20 Oct 2014 14:24:59 -0700 Subject: [PATCH 2/2] fix mouse focus issue with optionIndexes --- lib/combobox.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/combobox.js b/lib/combobox.js index b4377ec7..0bf0998c 100644 --- a/lib/combobox.js +++ b/lib/combobox.js @@ -115,7 +115,8 @@ module.exports = React.createClass({ props.onClick = this.selectOption.bind(this, child); props.onFocus = this.handleOptionFocus; props.onKeyDown = this.handleOptionKeyDown.bind(this, child); - props.onMouseEnter = this.handleOptionMouseEnter.bind(this, index); + props.onMouseEnter = this.handleOptionMouseEnter.bind( + this, optionIndexes.length - 1); }.bind(this)); return { optionIndexes: optionIndexes, @@ -311,20 +312,23 @@ module.exports = React.createClass({ focusPrevious: function() { if (this.state.menu.isEmpty) return; - var last = this.props.children.length - 1; + var last = this.state.menu.optionIndexes.length - 1; var index = this.state.focusedIndex == null ? last : this.state.focusedIndex - 1; this.focusOptionAtIndex(index); }, focusSelectedOption: function() { + // we have to do two hops to go from the DOM index to option index var selectedDomIndex; + + // hop 1: dom index React.Children.forEach(this.props.children, function(child, index) { if (child.props.value === this.state.value) selectedDomIndex = index; }.bind(this)); - // find the option index + // hop 2: option index var selectedOptionIndex; this.state.menu.optionIndexes.forEach(function(domIndex, optIndex) { if (domIndex === selectedDomIndex) selectedOptionIndex = optIndex; @@ -347,6 +351,8 @@ module.exports = React.createClass({ return inputValue || value; }, + // index is the index of the option among the list of only options, + // NOT among all elements in the DOM focusOptionAtIndex: function(index) { if (!this.state.isOpen && this.state.value) return this.focusSelectedOption();