Commit 010a5535 authored by Jon Mann's avatar Jon Mann Committed by Commit Bot

Maintain focus when items in a bluetooth device list are updated.

Previously, focus would jump to the first element when
the underlying data was updated.

Bug: 1030825
Change-Id: I78ae75f3230e3b09c0d286677cbfbbbf6ed8bab5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1954273
Commit-Queue: Jon Mann <jonmann@chromium.org>
Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#725420}
parent e5960e2e
......@@ -39,6 +39,7 @@ js_library("bluetooth_subpage") {
"//ui/webui/resources/cr_elements:cr_scrollable_behavior",
"//ui/webui/resources/js:assert",
"//ui/webui/resources/js:i18n_behavior",
"//ui/webui/resources/js:list_property_update_behavior",
]
externs_list = [
"$externs_path/bluetooth.js",
......@@ -55,6 +56,7 @@ js_library("bluetooth_device_list_item") {
":bluetooth_system_on_extensions",
"//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu",
"//ui/webui/resources/js:i18n_behavior",
"//ui/webui/resources/js/cr/ui:focus_row_behavior",
]
externs_list = [ "$externs_path/bluetooth.js" ]
}
......
......@@ -4,6 +4,7 @@
<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
<link rel="import" href="chrome://resources/cr_elements/icons.html">
<link rel="import" href="chrome://resources/html/i18n_behavior.html">
<link rel="import" href="chrome://resources/html/cr/ui/focus_row_behavior.html">
<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
<link rel="import" href="../../chromeos/os_icons.html">
<link rel="import" href="../../icons.html">
......@@ -20,33 +21,49 @@
color: var(--google-green-500);
}
</style>
<div class="list-item">
<iron-icon id="deviceIcon" icon="[[getDeviceIcon_(device)]]">
</iron-icon>
<div class="middle">
<div class="name" connected$="[[device.connected]]" aria-hidden="true">
[[getDeviceName_(device)]]
<div focus-row-container>
<div class="list-item"
focus-row-control
focus-type="rowWrapper"
aria-label$="[[ariaLabel]]"
role="button"
selectable
on-keydown="onKeyDown_"
on-click="onClick_">
<iron-icon id="deviceIcon" icon="[[getDeviceIcon_(device)]]">
</iron-icon>
<div class="middle">
<div class="name"
connected$="[[device.connected]]"
aria-hidden="true">
[[getDeviceName_(device)]]
</div>
<div class="state secondary"
connected$="[[device.connected]]"
hidden$="[[!hasConnectionStatusText_(device)]]">
[[getConnectionStatusText_(device)]]
</div>
</div>
<div class="state secondary" connected$="[[device.connected]]"
hidden$="[[!hasConnectionStatusText_(device)]]">
[[getConnectionStatusText_(device)]]
<div hidden$="[[!device.paired]]">
<cr-icon-button id="menuButton"
class="icon-more-vert"
focus-row-control
focus-type="menuButton"
on-click="onMenuButtonTap_"
tabindex$="[[tabindex]]"
title="$i18n{moreActions}"
on-keydown="ignoreEnterKey_"></cr-icon-button>
</div>
</div>
<div hidden$="[[!device.paired]]">
<cr-icon-button class="icon-more-vert" on-click="onMenuButtonTap_"
tabindex$="[[tabindex]]" title="$i18n{moreActions}"
on-keydown="ignoreEnterKey_"></cr-icon-button>
<cr-action-menu id="dotsMenu" role-description="$i18n{menu}">
<button class="dropdown-item"
on-click="onConnectActionTap_">
[[getConnectActionText_(device.connected)]]
</button>
<button class="dropdown-item" on-click="onRemoveTap_">
$i18n{bluetoothRemove}
</button>
</cr-action-menu>
</div>
</div>
<cr-action-menu id="dotsMenu" role-description="$i18n{menu}">
<button class="dropdown-item" on-click="onConnectActionTap_">
[[getConnectActionText_(device.connected)]]
</button>
<button class="dropdown-item" on-click="onRemoveTap_">
$i18n{bluetoothRemove}
</button>
</cr-action-menu>
</template>
<script src="bluetooth_device_list_item.js"></script>
</dom-module>
......@@ -9,7 +9,7 @@
Polymer({
is: 'bluetooth-device-list-item',
behaviors: [I18nBehavior],
behaviors: [I18nBehavior, cr.ui.FocusRowBehavior],
properties: {
/**
......@@ -27,7 +27,6 @@ Polymer({
ariaLabel: {
type: String,
notify: true,
reflectToAttribute: true,
computed: 'getAriaLabel_(device)',
},
},
......@@ -44,6 +43,34 @@ Polymer({
}
},
/** @private */
tryConnect_: function() {
if (!this.isDisconnected_(this.device)) {
return;
}
this.fire('device-event', {
action: 'connect',
device: this.device,
});
},
/** @private */
onClick_: function() {
this.tryConnect_();
},
/**
* @param {!KeyboardEvent} e
* @private
*/
onKeyDown_: function(e) {
if (e.key == 'Enter' || e.key == ' ') {
this.tryConnect_();
e.preventDefault();
}
},
/**
* @param {!Event} event
* @private
......
......@@ -4,6 +4,7 @@
<link rel="import" href="chrome://resources/cr_elements/cr_scrollable_behavior.html">
<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
<link rel="import" href="chrome://resources/html/i18n_behavior.html">
<link rel="import" href="chrome://resources/html/list_property_update_behavior.html">
<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
<link rel="import" href="../../i18n_setup.html">
......@@ -60,18 +61,21 @@
</div>
</div>
<div id="noPairedDevices" class="list-frame settings-box-text"
hidden="[[!showNoDevices_(bluetoothToggleState, pairedDeviceList_)]]">
hidden="[[!showNoDevices_(bluetoothToggleState, pairedDeviceList_,
pairedDeviceList_.splices)]]">
$i18n{bluetoothNoDevices}
</div>
<div id="pairedContainer" class="container"
scrollable on-device-event="onDeviceEvent_"
hidden="[[!showDevices_(bluetoothToggleState, pairedDeviceList_)]]">
hidden="[[!showDevices_(bluetoothToggleState, pairedDeviceList_,
pairedDeviceList_.splices)]]">
<iron-list id="pairedDevices" items="[[pairedDeviceList_]]"
selection-enabled selected-item="{{selectedPairedItem_}}"
scroll-target="pairedContainer" class="cr-separators">
scroll-target="pairedContainer" class="cr-separators" preserve-focus>
<template>
<bluetooth-device-list-item actionable device="[[item]]"
first$="[[!index]]" tabindex$="[[tabIndex]]">
first$="[[!index]]" tabindex$="[[tabIndex]]"
focus-row-index="[[index]]" iron-list-tab-index="[[tabIndex]]"
last-focused="{{lastFocused_}}" list-blurred="{{listBlurred_}}">
</bluetooth-device-list-item>
</template>
</iron-list>
......@@ -86,15 +90,16 @@
</paper-spinner-lite>
</div>
<div id="noUnpairedDevices" class="list-frame settings-box-text"
hidden="[[!showNoDevices_(bluetoothToggleState, unpairedDeviceList_)]]">
hidden="[[!showNoDevices_(bluetoothToggleState, unpairedDeviceList_,
unpairedDeviceList_.splices)]]">
$i18n{bluetoothNoDevicesFound}
</div>
<div id="unpairedContainer" class="container"
scrollable on-device-event="onDeviceEvent_"
hidden="[[!showDevices_(bluetoothToggleState, unpairedDeviceList_)]]">
hidden="[[!showDevices_(bluetoothToggleState, unpairedDeviceList_,
unpairedDeviceList_.splices)]]">
<iron-list id="unpairedDevices" class="cr-separators"
items="[[unpairedDeviceList_]]"
selection-enabled selected-item="{{selectedUnpairedItem_}}"
preserve-focus items="[[unpairedDeviceList_]]"
scroll-target="unpairedContainer">
<template>
<bluetooth-device-list-item actionable device="[[item]]"
......
......@@ -20,6 +20,7 @@ Polymer({
behaviors: [
I18nBehavior,
CrScrollableBehavior,
ListPropertyUpdateBehavior,
settings.RouteObserverBehavior,
],
......@@ -69,16 +70,6 @@ Polymer({
},
},
/**
* Reflects the iron-list selecteditem property.
* @type {!chrome.bluetooth.Device}
* @private
*/
selectedPairedItem_: {
type: Object,
observer: 'selectedPairedItemChanged_',
},
/**
* The ordered list of unpaired bluetooth devices.
* @type {!Array<!chrome.bluetooth.Device>}
......@@ -90,15 +81,6 @@ Polymer({
},
},
/**
* Reflects the iron-list selecteditem property.
* @type {!chrome.bluetooth.Device}
*/
selectedUnpairedItem_: {
type: Object,
observer: 'selectedUnpairedItemChanged_',
},
/**
* Whether or not the dialog is shown.
* @private
......@@ -155,6 +137,17 @@ Polymer({
value: null,
},
/**
* Used by FocusRowBehavior to track the last focused element on a row.
* @private
*/
lastFocused_: Object,
/**
* Used by FocusRowBehavior to track if the list has been blurred.
* @private
*/
listBlurred_: Boolean,
},
observers: [
......@@ -202,21 +195,17 @@ Polymer({
/** @private */
deviceListChanged_: function() {
this.saveScroll(this.$.pairedDevices);
this.saveScroll(this.$.unpairedDevices);
this.pairedDeviceList_ = this.getUpdatedDeviceList_(
this.pairedDeviceList_,
this.deviceList_.filter(d => d.paired || d.connecting));
this.unpairedDeviceList_ = this.getUpdatedDeviceList_(
this.unpairedDeviceList_,
this.deviceList_.filter(d => !(d.paired || d.connecting)));
this.$.pairedDevices.fire('iron-resize');
this.$.unpairedDevices.fire('iron-resize');
this.updateList(
'pairedDeviceList_', item => item.address,
this.getUpdatedDeviceList_(
this.pairedDeviceList_,
this.deviceList_.filter(d => d.paired || d.connecting)));
this.updateList(
'unpairedDeviceList_', item => item.address,
this.getUpdatedDeviceList_(
this.unpairedDeviceList_,
this.deviceList_.filter(d => !(d.paired || d.connecting))));
this.updateScrollableContents();
this.restoreScroll(this.$.unpairedDevices);
this.restoreScroll(this.$.pairedDevices);
},
/**
......@@ -255,20 +244,6 @@ Polymer({
return updatedDeviceList;
},
/** @private */
selectedPairedItemChanged_: function() {
if (this.selectedPairedItem_) {
this.connectDevice_(this.selectedPairedItem_);
}
},
/** @private */
selectedUnpairedItemChanged_: function() {
if (this.selectedUnpairedItem_) {
this.connectDevice_(this.selectedUnpairedItem_);
}
},
/** @private */
updateDiscovery_: function() {
if (!this.adapterState || !this.adapterState.powered) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment