Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(417)

Unified Diff: ui/webui/resources/js/cr/ui/focus_row.js

Issue 807593005: Make downloads list keyboard shortcuts more consistent. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Apply feedback after talk Created 5 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ui/webui/resources/js/cr/ui/focus_row.js
diff --git a/ui/webui/resources/js/cr/ui/focus_row.js b/ui/webui/resources/js/cr/ui/focus_row.js
index 26c4c5567e2b673f24b19574c2b35afe6c69263f..2b5eff005acb2d2e55ceb4ade32d76f8ee2fb5b7 100644
--- a/ui/webui/resources/js/cr/ui/focus_row.js
+++ b/ui/webui/resources/js/cr/ui/focus_row.js
@@ -11,11 +11,13 @@ cr.define('cr.ui', function() {
*
* One could create a FocusRow by doing:
*
- * new cr.ui.FocusRow([checkboxEl, labelEl, buttonEl])
+ * var focusRow = new cr.ui.FocusRow(rowBoundary, rowEl);
*
- * if there are references to each node or querying them from the DOM like so:
+ * focusRow.setFocusableElement(checkboxEl);
+ * focusRow.setFocusableElement(labelEl);
+ * focusRow.setFocusableElement(buttonEl);
*
- * new cr.ui.FocusRow(dialog.querySelectorAll('list input[type=checkbox]'))
+ * focusRow.setInitialFocusability(true);
*
* Pressing left cycles backward and pressing right cycles forward in item
* order. Pressing Home goes to the beginning of the list and End goes to the
@@ -23,48 +25,31 @@ cr.define('cr.ui', function() {
*
* If an item in this row is focused, it'll stay active (accessible via tab).
* If no items in this row are focused, the row can stay active until focus
- * changes to a node inside |this.boundary_|. If opt_boundary isn't
- * specified, any focus change deactivates the row.
+ * changes to a node inside |this.boundary_|. If |boundary| isn't specified,
+ * any focus change deactivates the row.
*
- * @param {!Array.<!Element>|!NodeList} items Elements to track focus of.
- * @param {Node=} opt_boundary Focus events are ignored outside of this node.
+ * @param {Node} boundary Focus events are ignored outside of this node.
+ * @param {Element} rowElement The element representing this row.
* @param {FocusRow.Delegate=} opt_delegate A delegate to handle key events.
- * @param {FocusRow.Observer=} opt_observer An observer that's notified if
- * this focus row is added to or removed from the focus order.
* @constructor
*/
- function FocusRow(items, opt_boundary, opt_delegate, opt_observer) {
- /** @type {!Array.<!Element>} */
- this.items = Array.prototype.slice.call(items);
- assert(this.items.length > 0);
-
+ function FocusRow(boundary, rowElement, opt_delegate) {
/** @type {!Node} */
- this.boundary_ = opt_boundary || document;
+ this.boundary_ = boundary || document;
- /** @private {cr.ui.FocusRow.Delegate|undefined} */
- this.delegate_ = opt_delegate;
+ /** @type {Element} */
+ this.rowElement_ = rowElement;
- /** @private {cr.ui.FocusRow.Observer|undefined} */
- this.observer_ = opt_observer;
+ /** @type {cr.ui.FocusRow.Delegate|undefined} */
+ this.delegate = opt_delegate;
/** @private {!EventTracker} */
this.eventTracker_ = new EventTracker;
this.eventTracker_.add(cr.doc, 'focusin', this.onFocusin_.bind(this));
this.eventTracker_.add(cr.doc, 'keydown', this.onKeydown_.bind(this));
- this.items.forEach(function(item) {
- if (item != document.activeElement)
- item.tabIndex = -1;
-
- this.eventTracker_.add(item, 'mousedown', this.onMousedown_.bind(this));
- }, this);
-
- /**
- * The index that should be actively participating in the page tab order.
- * @type {number}
- * @private
- */
- this.activeIndex_ = this.items.indexOf(document.activeElement);
+ /** @type {Array<Element>} */
+ this.rowElements = [];
Evan Stade 2015/01/22 16:55:11 I can't figure out what this is or what this.rowEl
hcarmona 2015/01/24 01:24:19 |this| is now what rowElement_ used to be. Rename
}
/** @interface */
@@ -72,8 +57,8 @@ cr.define('cr.ui', function() {
FocusRow.Delegate.prototype = {
/**
- * Called when a key is pressed while an item in |this.items| is focused. If
- * |e|'s default is prevented, further processing is skipped.
+ * Called when a key is pressed while an item in |this.rowElements| is
+ * focused. If |e|'s default is prevented, further processing is skipped.
* @param {cr.ui.FocusRow} row The row that detected a keydown.
* @param {Event} e
* @return {boolean} Whether the event was handled.
@@ -88,58 +73,111 @@ cr.define('cr.ui', function() {
onMousedown: assertNotReached,
};
- /** @interface */
- FocusRow.Observer = function() {};
-
- FocusRow.Observer.prototype = {
+ FocusRow.prototype = {
/**
* Called when the row is activated (added to the focus order).
- * @param {cr.ui.FocusRow} row The row added to the focus order.
*/
- onActivate: assertNotReached,
+ onActivate: function() {},
/**
* Called when the row is deactivated (removed from the focus order).
- * @param {cr.ui.FocusRow} row The row removed from the focus order.
*/
- onDeactivate: assertNotReached,
- };
+ onDeactivate: function() {},
- FocusRow.prototype = {
- get activeIndex() {
- return this.activeIndex_;
- },
- set activeIndex(index) {
- var wasActive = this.items[this.activeIndex_];
- if (wasActive)
- wasActive.tabIndex = -1;
+ /**
+ * Find the element that best matches |sampleElement|. This function should
+ * NOT return null or undefined.
+ * @param {Element} sampleElement An element that needs an equivalent
+ * element in this row.
+ * @return {!Element} The element that best matches sampleElement.
+ */
+ getEquivalentElement: assertNotReached,
- this.items.forEach(function(item) { assert(item.tabIndex == -1); });
- this.activeIndex_ = index;
+ /**
+ * Return the only element matching the query or null if not found.
+ * @param {string} query The query to find the element in this FocusRow.
+ * @return {Element} Either null or the element matching the query.
+ */
+ getMatch: function(query) {
+ var elements = this.rowElement_.querySelectorAll(query);
- if (this.items[index])
- this.items[index].tabIndex = 0;
+ // There should be exactly 0 or 1 matches.
+ assert(elements.length == 0 || elements.length == 1);
- if (!this.observer_)
- return;
+ return elements[0] || null;
+ },
- var isActive = index >= 0 && index < this.items.length;
- if (isActive == !!wasActive)
+ /**
+ * Add an element to this FocusRow. No-op if |element| is not provided.
+ * @param {Element} element The element that should be added.
+ */
+ setFocusableElement: function(element) {
Evan Stade 2015/01/22 16:55:11 addFocusableElement
hcarmona 2015/01/24 01:24:19 Done.
+ if (!element)
return;
- if (isActive)
- this.observer_.onActivate(this);
- else
- this.observer_.onDeactivate(this);
+ assert(this.rowElements.indexOf(element) == -1);
+ assert(this.contains(element));
+
+ this.rowElements.push(element);
+ this.eventTracker_.add(element, 'mousedown',
+ this.onMousedown_.bind(this));
+ },
+
+ /**
+ * Called when focus changes to activate/deactivate the row. Focus is
+ * removed from the row when |element| is not in the FocusRow.
+ * @param {Element} element The element that has focus. null if focus should
+ * be removed.
+ * @private
+ */
+ onFocusChange_: function(element) {
+ var isActive = this.contains(element);
+ var wasActive = this.rowElement_.classList.contains('focus-row-active');
+
+ // Only send events if the active state is different for the row.
+ if (isActive != wasActive)
+ this.makeRowFocusable(isActive);
+ },
+
+ /**
+ * Enables/disables the tabIndex of the focusable elements in the FocusRow.
+ * tabIndex can be set properly.
+ * @param {bool} allow True if tab is allowed for this row.
+ */
+ makeRowFocusable: function(allow) {
Evan Stade 2015/01/22 16:55:11 nit: more like makeRowActive
hcarmona 2015/01/24 01:24:19 Done.
+ this.rowElements.forEach(function(element) {
+ element.tabIndex = allow ? 0 : -1;
+ });
+
+ if (allow) {
+ this.rowElement_.classList.add('focus-row-active');
+ this.onActivate(this);
Evan Stade 2015/01/22 16:55:10 I don't think you need to pass |this|
hcarmona 2015/01/24 01:24:19 Done.
+ }
Evan Stade 2015/01/22 16:55:11 nit: } else {
hcarmona 2015/01/24 01:24:19 Done.
+ else {
+ this.rowElement_.classList.remove('focus-row-active');
+ this.onDeactivate(this);
Evan Stade 2015/01/22 16:55:10 can you combine Activate and Deactivate? Then this
hcarmona 2015/01/24 01:24:20 Done.
+ }
+ },
+
+ /**
+ * @param {Element} target The target we need to test.
+ * @return {bool} Whether or not the target is in this FocusRow.
+ */
+ contains: function(target) {
+ return this.rowElement_.contains(target);
},
/**
- * Focuses the item at |index|.
- * @param {number} index An index to focus. Must be between 0 and
- * this.items.length - 1.
+ * Called to set focus to a given row item based on the element.
+ * @param {Element} element The element that focus should be based on.
Evan Stade 2015/01/22 16:55:11 nit: "An element from a row of the same type which
hcarmona 2015/01/24 01:24:19 Done.
*/
- focusIndex: function(index) {
- this.items[index].focus();
+ focusEquivalentElement: function(element) {
Evan Stade 2015/01/22 16:55:11 this whole function seems a bit extraneous. The ca
hcarmona 2015/01/24 01:24:19 Done.
+ var bestMatch = element;
+
+ if (!this.contains(bestMatch))
+ bestMatch = this.getEquivalentElement(element);
+
+ bestMatch.focus();
},
/** Call this to clean up event handling before dereferencing. */
@@ -153,37 +191,38 @@ cr.define('cr.ui', function() {
*/
onFocusin_: function(e) {
if (this.boundary_.contains(assertInstanceof(e.target, Node)))
- this.activeIndex = this.items.indexOf(e.target);
+ this.onFocusChange_(e.target);
},
/**
- * @param {Event} e A focus event.
+ * @param {Event} e The keydown event.
* @private
*/
onKeydown_: function(e) {
- var item = this.items.indexOf(e.target);
- if (item < 0)
+ if (!this.contains(e.target))
return;
- if (this.delegate_ && this.delegate_.onKeydown(this, e))
+ if (this.delegate && this.delegate.onKeydown(this, e))
return;
+ var element = this.getEquivalentElement(e.target);
Evan Stade 2015/01/22 16:55:11 I'm confused about what this is doing, probably be
hcarmona 2015/01/24 01:24:19 Removed element because it will always equal e.tar
+ var elementIndex = this.rowElements.indexOf(element);
var index = -1;
if (e.keyIdentifier == 'Left')
- index = item + (isRTL() ? 1 : -1);
+ index = elementIndex + (isRTL() ? 1 : -1);
else if (e.keyIdentifier == 'Right')
- index = item + (isRTL() ? -1 : 1);
+ index = elementIndex + (isRTL() ? -1 : 1);
else if (e.keyIdentifier == 'Home')
index = 0;
else if (e.keyIdentifier == 'End')
- index = this.items.length - 1;
-
- if (!this.items[index])
- return;
+ index = this.rowElements.length - 1;
- this.focusIndex(index);
- e.preventDefault();
+ element = this.rowElements[index];
+ if (element) {
+ this.focusEquivalentElement(element);
+ e.preventDefault();
+ }
},
/**
@@ -191,11 +230,14 @@ cr.define('cr.ui', function() {
* @private
*/
onMousedown_: function(e) {
- if (this.delegate_ && this.delegate_.onMousedown(this, e))
+ if (this.delegate && this.delegate.onMousedown(this, e))
return;
- if (!e.button)
- this.activeIndex = this.items.indexOf(e.currentTarget);
+ // Only accept the left mouse click.
+ if (!e.button) {
+ // Focus this row if the target is one of the elements in this row.
+ this.onFocusChange_(e.target);
+ }
},
};
« ui/webui/resources/js/cr/ui/focus_grid.js ('K') | « ui/webui/resources/js/cr/ui/focus_grid.js ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698