Make account list navigable by keyboard
Makes account chips focusable via tabindex attribute, and responds to keypresses while they're focused. Feature: Issue 4703 Change-Id: Id8f73e73b91afa0582f06cb981fd117d021accff
This commit is contained in:

committed by
Andrew Bonventre

parent
0a59ac2565
commit
6df7fbf431
@@ -42,7 +42,9 @@ limitations under the License.
|
||||
account="[[account]]"
|
||||
class$="[[_computeChipClass(account)]]"
|
||||
data-account-id$="[[account._account_id]]"
|
||||
removable="[[_computeRemovable(account)]]">
|
||||
removable="[[_computeRemovable(account)]]"
|
||||
on-keydown="_handleChipKeydown"
|
||||
tabindex$="[[index]]">
|
||||
</gr-account-chip>
|
||||
</template>
|
||||
<gr-account-entry
|
||||
|
@@ -90,6 +90,10 @@
|
||||
|
||||
_handleRemove: function(e) {
|
||||
var toRemove = e.detail.account;
|
||||
this._removeAccount(toRemove);
|
||||
},
|
||||
|
||||
_removeAccount: function(toRemove) {
|
||||
for (var i = 0; i < this.accounts.length; i++) {
|
||||
var matches;
|
||||
var account = this.accounts[i];
|
||||
@@ -104,8 +108,7 @@
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.warn('received remove event for missing account',
|
||||
e.detail.account);
|
||||
console.warn('received remove event for missing account', toRemove);
|
||||
},
|
||||
|
||||
_handleInputKeydown: function(e) {
|
||||
@@ -118,6 +121,50 @@
|
||||
case 8: // Backspace
|
||||
this.splice('accounts', this.accounts.length - 1, 1);
|
||||
break;
|
||||
case 37: // Left arrow
|
||||
var chips = this.accountChips;
|
||||
if (chips[chips.length - 1]) {
|
||||
chips[chips.length - 1].focus();
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_handleChipKeydown: function(e) {
|
||||
var chip = e.target;
|
||||
var chips = this.accountChips;
|
||||
var index = chips.indexOf(chip);
|
||||
switch (e.keyCode) {
|
||||
case 8: // Backspace
|
||||
case 13: // Enter
|
||||
case 32: // Spacebar
|
||||
case 46: // Delete
|
||||
this._removeAccount(chip.account);
|
||||
// Splice from this array to avoid inconsistent ordering of
|
||||
// event handling.
|
||||
chips.splice(index, 1);
|
||||
if (index < chips.length) {
|
||||
chips[index].focus();
|
||||
} else if (index > 0) {
|
||||
chips[index - 1].focus();
|
||||
} else {
|
||||
this.$.entry.focus();
|
||||
}
|
||||
break;
|
||||
case 37: // Left arrow
|
||||
if (index > 0) {
|
||||
chip.blur();
|
||||
chips[index - 1].focus();
|
||||
}
|
||||
break;
|
||||
case 39: // Right arrow
|
||||
chip.blur();
|
||||
if (index < chips.length - 1) {
|
||||
chips[index + 1].focus();
|
||||
} else {
|
||||
this.$.entry.focus();
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
|
@@ -256,6 +256,45 @@ limitations under the License.
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('arrow key navigation', function(done) {
|
||||
var input = element.$.entry.$.input;
|
||||
input.text = '';
|
||||
element.accounts = [makeAccount(), makeAccount()];
|
||||
MockInteractions.focus(input.$.input);
|
||||
flush(function() {
|
||||
var chips = element.accountChips;
|
||||
var chipsOneSpy = sandbox.spy(chips[1], 'focus');
|
||||
MockInteractions.pressAndReleaseKeyOn(input.$.input, 37); // Left
|
||||
assert.isTrue(chipsOneSpy.called);
|
||||
var chipsZeroSpy = sandbox.spy(chips[0], 'focus');
|
||||
MockInteractions.pressAndReleaseKeyOn(chips[1], 37); // Left
|
||||
assert.isTrue(chipsZeroSpy.called);
|
||||
MockInteractions.pressAndReleaseKeyOn(chips[0], 37); // Left
|
||||
assert.isTrue(chipsZeroSpy.calledOnce);
|
||||
MockInteractions.pressAndReleaseKeyOn(chips[0], 39); // Right
|
||||
assert.isTrue(chipsOneSpy.calledTwice);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('delete', function(done) {
|
||||
element.accounts = [makeAccount(), makeAccount()];
|
||||
flush(function() {
|
||||
var chips = element.accountChips;
|
||||
var focusSpy = sandbox.spy(element.accountChips[1], 'focus');
|
||||
var removeSpy = sandbox.spy(element, '_removeAccount');
|
||||
MockInteractions.pressAndReleaseKeyOn(
|
||||
element.accountChips[0], 8); // Backspace
|
||||
assert.isTrue(focusSpy.called);
|
||||
assert.isTrue(removeSpy.calledOnce);
|
||||
|
||||
MockInteractions.pressAndReleaseKeyOn(
|
||||
element.accountChips[1], 46); // Delete
|
||||
assert.isTrue(removeSpy.calledTwice);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@@ -53,6 +53,15 @@ limitations under the License.
|
||||
padding: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
:host:focus {
|
||||
border-color: transparent;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
}
|
||||
:host:focus .container,
|
||||
:host:focus gr-button {
|
||||
background: #ccc;
|
||||
}
|
||||
.transparentBackground,
|
||||
gr-button.transparentBackground {
|
||||
background-color: transparent;
|
||||
@@ -61,6 +70,7 @@ limitations under the License.
|
||||
<div class$="container [[_getBackgroundClass(transparentBackground)]]">
|
||||
<gr-account-link account="[[account]]"></gr-account-link>
|
||||
<gr-button
|
||||
id="remove"
|
||||
hidden$="[[!removable]]" hidden
|
||||
class$="remove [[_getBackgroundClass(transparentBackground)]]"
|
||||
on-tap="_handleRemoveTap">×</gr-button>
|
||||
|
@@ -18,6 +18,19 @@
|
||||
Polymer({
|
||||
is: 'gr-account-chip',
|
||||
|
||||
/**
|
||||
* Fired to indicate a key was pressed while this chip was focused.
|
||||
*
|
||||
* @event account-chip-keydown
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired to indicate this chip should be removed, i.e. when the x button is
|
||||
* clicked or when the remove function is called.
|
||||
*
|
||||
* @event remove
|
||||
*/
|
||||
|
||||
properties: {
|
||||
account: Object,
|
||||
removable: {
|
||||
|
Reference in New Issue
Block a user