Skip to content
This repository has been archived by the owner on Dec 19, 2024. It is now read-only.

Commit

Permalink
implement a stable sort (merge sort)
Browse files Browse the repository at this point in the history
  • Loading branch information
valdrinkoshi committed Jul 21, 2016
1 parent 4dbaebd commit 980b33a
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 27 deletions.
69 changes: 51 additions & 18 deletions iron-focusables-helper.html
Original file line number Diff line number Diff line change
Expand Up @@ -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;
},
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<Node>} nodes
* @return {Array<Node>}
* @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<Node>} left
* @param {!Array<Node>} right
* @return {Array<Node>}
* @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 `<div id="elem" contenteditable>`
var ati = Math.max(a.tabIndex, 0);
var bti = Math.max(b.tabIndex, 0);
return (ati === 0 || bti === 0) ? bti > ati : ati > bti;
}
};
})();
Expand Down
21 changes: 12 additions & 9 deletions test/iron-focusables-helper.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ <h2>Focusables (no tabindex)</h2>
<div>
<input class="focusable1" placeholder="1 (nested)">
</div>
<a href="#" class="focusable2">1</a>
<a href="#" class="focusable2">2</a>
<button disabled> disabled button</button>
<input disabled tabindex="0" value="disabled input with tabindex">
<div tabindex="-1">not focusable</div>
<div contenteditable class="focusable3">2</div>
<div contenteditable class="focusable3">3</div>
</div>
</template>
</test-fixture>
Expand All @@ -59,6 +59,12 @@ <h2>Focusables (no tabindex)</h2>
<template>
<div>
<h2>Focusables (with tabindex)</h2>
<div tabindex="0" class="focusable7">7</div>
<div tabindex="0" class="focusable8">8</div>
<div tabindex="0" class="focusable9">9</div>
<div tabindex="0" class="focusable10">10</div>
<div tabindex="0" class="focusable11">11</div>
<div tabindex="0" class="focusable12">12</div>
<div tabindex="-1">not focusable</div>
<div tabindex="3" class="focusable3">3</div>
<div tabindex="4" class="focusable4">4</div>
Expand Down Expand Up @@ -130,13 +136,10 @@ <h2>focusables in ShadowDOM</h2>
test('respects the tabindex order', function() {
var node = fixture('tabindex');
var focusableNodes = Polymer.IronFocusablesHelper.getTabbableNodes(node);
assert.equal(focusableNodes.length, 6, '6 nodes are focusable');
assert.equal(focusableNodes[0], Polymer.dom(node).querySelector('.focusable1'));
assert.equal(focusableNodes[1], Polymer.dom(node).querySelector('.focusable2'));
assert.equal(focusableNodes[2], Polymer.dom(node).querySelector('.focusable3'));
assert.equal(focusableNodes[3], Polymer.dom(node).querySelector('.focusable4'));
assert.equal(focusableNodes[4], Polymer.dom(node).querySelector('.focusable5'));
assert.equal(focusableNodes[5], Polymer.dom(node).querySelector('.focusable6'));
assert.equal(focusableNodes.length, 12, '12 nodes are focusable');
for (var i = 0; i < 12; i++) {
assert.equal(focusableNodes[i], Polymer.dom(node).querySelector('.focusable' + (i + 1)));
}
});

test('includes tabbable elements in the shadow dom', function() {
Expand Down

0 comments on commit 980b33a

Please sign in to comment.