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

Unified Diff: chrome/browser/resources/md_history/app.crisper.js

Issue 2334553002: [MD History] Add keyboard navigation to the main history list. (Closed)
Patch Set: address comments Created 4 years, 3 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: chrome/browser/resources/md_history/app.crisper.js
diff --git a/chrome/browser/resources/md_history/app.crisper.js b/chrome/browser/resources/md_history/app.crisper.js
index 5c59999cf99ed85493d2ec41e31eb44ffd69c83d..310f6efa59947330a053614c348340b69d259a96 100644
--- a/chrome/browser/resources/md_history/app.crisper.js
+++ b/chrome/browser/resources/md_history/app.crisper.js
@@ -6616,6 +6616,164 @@ Polymer({
}
});
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+var EventTrackerEntry;
+
+function EventTracker() {
+ this.listeners_ = [];
+}
+
+EventTracker.prototype = {
+ add: function(target, eventType, listener, opt_capture) {
+ var capture = !!opt_capture;
+ var h = {
+ target: target,
+ eventType: eventType,
+ listener: listener,
+ capture: capture
+ };
+ this.listeners_.push(h);
+ target.addEventListener(eventType, listener, capture);
+ },
+ remove: function(target, eventType) {
+ this.listeners_ = this.listeners_.filter(function(h) {
+ if (h.target == target && (!eventType || h.eventType == eventType)) {
+ EventTracker.removeEventListener_(h);
+ return false;
+ }
+ return true;
+ });
+ },
+ removeAll: function() {
+ this.listeners_.forEach(EventTracker.removeEventListener_);
+ this.listeners_ = [];
+ }
+};
+
+EventTracker.removeEventListener_ = function(h) {
+ h.target.removeEventListener(h.eventType, h.listener, h.capture);
+};
+
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+cr.define('cr.ui', function() {
+ function FocusRow(root, boundary, opt_delegate) {
+ this.root = root;
+ this.boundary_ = boundary || document.documentElement;
+ this.delegate = opt_delegate;
+ this.eventTracker = new EventTracker();
+ }
+ FocusRow.Delegate = function() {};
+ FocusRow.Delegate.prototype = {
+ onKeydown: assertNotReached,
+ onFocus: assertNotReached
+ };
+ FocusRow.ACTIVE_CLASS = 'focus-row-active';
+ FocusRow.isFocusable = function(element) {
+ if (!element || element.disabled) return false;
+ function isVisible(element) {
+ assertInstanceof(element, Element);
+ var style = window.getComputedStyle(element);
+ if (style.visibility == 'hidden' || style.display == 'none') return false;
+ var parent = element.parentNode;
+ if (!parent) return false;
+ if (parent == element.ownerDocument || parent instanceof DocumentFragment) return true;
+ return isVisible(parent);
+ }
+ return isVisible(element);
+ };
+ FocusRow.prototype = {
+ addItem: function(type, query) {
+ assert(type);
+ var element = this.root.querySelector(query);
+ if (!element) return false;
+ element.setAttribute('focus-type', type);
+ element.tabIndex = this.isActive() ? 0 : -1;
+ this.eventTracker.add(element, 'blur', this.onBlur_.bind(this));
+ this.eventTracker.add(element, 'focus', this.onFocus_.bind(this));
+ this.eventTracker.add(element, 'keydown', this.onKeydown_.bind(this));
+ this.eventTracker.add(element, 'mousedown', this.onMousedown_.bind(this));
+ return true;
+ },
+ destroy: function() {
+ this.eventTracker.removeAll();
+ },
+ getCustomEquivalent: function(sampleElement) {
+ return assert(this.getFirstFocusable());
+ },
+ getElements: function() {
+ var elements = this.root.querySelectorAll('[focus-type]');
+ return Array.prototype.slice.call(elements);
+ },
+ getEquivalentElement: function(sampleElement) {
+ if (this.getFocusableElements().indexOf(sampleElement) >= 0) return sampleElement;
+ var sampleFocusType = this.getTypeForElement(sampleElement);
+ if (sampleFocusType) {
+ var sameType = this.getFirstFocusable(sampleFocusType);
+ if (sameType) return sameType;
+ }
+ return this.getCustomEquivalent(sampleElement);
+ },
+ getFirstFocusable: function(opt_type) {
+ var filter = opt_type ? '="' + opt_type + '"' : '';
+ var elements = this.root.querySelectorAll('[focus-type' + filter + ']');
+ for (var i = 0; i < elements.length; ++i) {
+ if (cr.ui.FocusRow.isFocusable(elements[i])) return elements[i];
+ }
+ return null;
+ },
+ getFocusableElements: function() {
+ return this.getElements().filter(cr.ui.FocusRow.isFocusable);
+ },
+ getTypeForElement: function(element) {
+ return element.getAttribute('focus-type') || '';
+ },
+ isActive: function() {
+ return this.root.classList.contains(FocusRow.ACTIVE_CLASS);
+ },
+ makeActive: function(active) {
+ if (active == this.isActive()) return;
+ this.getElements().forEach(function(element) {
+ element.tabIndex = active ? 0 : -1;
+ });
+ this.root.classList.toggle(FocusRow.ACTIVE_CLASS, active);
+ },
+ onBlur_: function(e) {
+ if (!this.boundary_.contains(e.relatedTarget)) return;
+ var currentTarget = e.currentTarget;
+ if (this.getFocusableElements().indexOf(currentTarget) >= 0) this.makeActive(false);
+ },
+ onFocus_: function(e) {
+ if (this.delegate) this.delegate.onFocus(this, e);
+ },
+ onMousedown_: function(e) {
+ if (e.button) return;
+ if (!e.currentTarget.disabled) e.currentTarget.tabIndex = 0;
+ },
+ onKeydown_: function(e) {
+ var elements = this.getFocusableElements();
+ var currentElement = e.currentTarget;
+ var elementIndex = elements.indexOf(currentElement);
+ assert(elementIndex >= 0);
+ if (this.delegate && this.delegate.onKeydown(this, e)) return;
+ if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
+ var index = -1;
+ if (e.key == 'ArrowLeft') index = elementIndex + (isRTL() ? 1 : -1); else if (e.key == 'ArrowRight') index = elementIndex + (isRTL() ? -1 : 1); else if (e.key == 'Home') index = 0; else if (e.key == 'End') index = elements.length - 1;
+ var elementToFocus = elements[index];
+ if (elementToFocus) {
+ this.getEquivalentElement(elementToFocus).focus();
+ e.preventDefault();
+ }
+ }
+ };
+ return {
+ FocusRow: FocusRow
+ };
+});
+
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -6696,7 +6854,40 @@ Polymer({
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+function HistoryFocusRow(root, boundary, delegate) {
+ cr.ui.FocusRow.call(this, root, boundary, delegate);
+ this.addItems();
+}
+
+HistoryFocusRow.prototype = {
+ __proto__: cr.ui.FocusRow.prototype,
+ getCustomEquivalent: function(sampleElement) {
+ var equivalent;
+ if (this.getTypeForElement(sampleElement) == 'star') equivalent = this.getFirstFocusable('title');
+ return equivalent || cr.ui.FocusRow.prototype.getCustomEquivalent.call(this, sampleElement);
+ },
+ addItems: function() {
+ this.destroy();
+ assert(this.addItem('checkbox', '#checkbox'));
+ assert(this.addItem('title', '#title'));
+ assert(this.addItem('menu-button', '#menu-button'));
+ this.addItem('star', '#bookmark-star');
+ }
+};
+
cr.define('md_history', function() {
+ function FocusRowDelegate(historyItemElement) {
+ this.historyItemElement = historyItemElement;
+ }
+ FocusRowDelegate.prototype = {
+ onFocus: function(row, e) {
+ this.historyItemElement.lastFocused = e.path[0];
+ },
+ onKeydown: function(row, e) {
+ if (e.key == 'Enter') e.stopPropagation();
+ return false;
+ }
+ };
var HistoryItem = Polymer({
is: 'history-item',
properties: {
@@ -6734,7 +6925,38 @@ cr.define('md_history', function() {
type: Number
},
path: String,
- index: Number
+ index: Number,
+ lastFocused: {
+ type: Object,
+ notify: true
+ },
+ tabIndex: {
+ type: Number,
+ observer: 'tabIndexChanged_'
+ }
+ },
+ row_: null,
+ attached: function() {
+ Polymer.RenderStatus.afterNextRender(this, function() {
+ this.row_ = new HistoryFocusRow(this.$['sizing-container'], null, new FocusRowDelegate(this));
+ this.row_.makeActive(this.tabIndex == 0);
+ this.listen(this, 'focus', 'onFocus_');
+ this.listen(this, 'dom-change', 'onDomChange_');
+ });
+ },
+ detached: function() {
+ this.unlisten(this, 'focus', 'onFocus_');
+ this.unlisten(this, 'dom-change', 'onDomChange_');
+ this.row_.destroy();
+ },
+ onFocus_: function() {
+ if (this.lastFocused) this.row_.getEquivalentElement(this.lastFocused).focus(); else this.row_.getFirstFocusable().focus();
+ },
+ tabIndexChanged_: function() {
+ if (this.row_) this.row_.makeActive(this.tabIndex == 0);
+ },
+ onDomChange_: function() {
+ if (this.row_) this.row_.addItems();
},
onCheckboxSelected_: function(e) {
this.fire('history-checkbox-select', {
@@ -8007,7 +8229,8 @@ Polymer({
resultLoadingDisabled_: {
type: Boolean,
value: false
- }
+ },
+ lastFocused_: Object
},
listeners: {
scroll: 'notifyListScroll_',

Powered by Google App Engine
This is Rietveld 408576698