diff --git a/iron-focusables-helper.html b/iron-focusables-helper.html index 075ff74..e634118 100644 --- a/iron-focusables-helper.html +++ b/iron-focusables-helper.html @@ -29,7 +29,7 @@ // the final array by tabindex. var needsSortByTabIndex = this._collectTabbableNodes(node, result); if (needsSortByTabIndex) { - result.sort(this._tabIndexSort); + return this._sortByTabIndex(result); } return result; }, @@ -110,12 +110,6 @@ } var tabIndex = this._normalizedTabIndex(node); - // TODO(valdrin) uncomment this when delegatesFocus is implemented. - // if delegatesFocus and tabindex = -1, should skip its children. - // if (tabIndex < 0 && node.delegatesFocus) { - // return false; - // } - var needsSortByTabIndex = tabIndex > 0; if (tabIndex >= 0) { result.push(node); @@ -153,21 +147,60 @@ }, /** - * Sort by tabindex. Move elements with tabindex = 0 to the end to match - * browser behavior, e.g. [0, 3, 1, 2] --> [1, 2, 3, 0] + * Sorts an array of nodes by tabindex. Returns a new array. + * @param {!Array} nodes + * @return {Array} + * @private + */ + _sortByTabIndex: function(nodes) { + // Implement a merge sort as Array.prototype.sort does a non-stable sort + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort + var len = nodes.length; + if (len < 2) { + return nodes; + } + var pivot = Math.ceil(len / 2); + var left = this._sortByTabIndex(nodes.slice(0, pivot)); + var right = this._sortByTabIndex(nodes.slice(pivot)); + return this._mergeSortByTabIndex(left, right); + }, + + /** + * @param {!Array} left + * @param {!Array} right + * @return {Array} + * @private + */ + _mergeSortByTabIndex: function(left, right) { + var result = []; + while ((left.length > 0) && (right.length > 0)) { + if (this._hasLowerTabOrder(left[0], right[0])) { + result.push(right.shift()); + } else { + result.push(left.shift()); + } + } + + return result.concat(left, right); + }, + + /** + * Returns if a node has lower tab order compared to another node (both + * nodes are assumed to be focusable and tabbable). + * Elements with tabindex = 0 have lower tab order compared to elements + * with tabindex > 0. + * If both have same tabindex, it returns false. * @param {!Node} a * @param {!Node} b - * @return {Number} + * @return {boolean} * @private */ - _tabIndexSort: function(a, b) { - // Move tabindex = 0 elements to the end of the list. - if (a.tabIndex === 0 || b.tabIndex === 0) { - // The one with bigger `tabindex` goes on top of the list. - return b.tabIndex - a.tabIndex; - } - // The one with smaller `tabindex` goes on top of the list. - return a.tabIndex - b.tabIndex; + _hasLowerTabOrder: function(a, b) { + // Normalize tabIndexes e.g. in Firefox + // `elem.tabIndex` is `-1` for `
` + var ati = Math.max(a.tabIndex, 0); + var bti = Math.max(b.tabIndex, 0); + return (ati === 0 || bti === 0) ? bti > ati : ati > bti; } }; })(); diff --git a/test/iron-focusables-helper.html b/test/iron-focusables-helper.html index 51f1015..a400b4c 100644 --- a/test/iron-focusables-helper.html +++ b/test/iron-focusables-helper.html @@ -46,11 +46,11 @@

Focusables (no tabindex)

- 1 + 2
not focusable
-
2
+
3
@@ -59,6 +59,12 @@

Focusables (no tabindex)