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

Side by Side Diff: ui/webui/resources/cr_elements/cr_shared_menu/cr_shared_menu.js

Issue 2272553002: MD WebUI: Use arrow keys for navigation in cr-shared-menu, close on tab (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Change behavior, rebase Created 4 years, 2 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 unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** Same as paper-menu-button's custom easing cubic-bezier param. */ 5 /** Same as paper-menu-button's custom easing cubic-bezier param. */
6 var SLIDE_CUBIC_BEZIER = 'cubic-bezier(0.3, 0.95, 0.5, 1)'; 6 var SLIDE_CUBIC_BEZIER = 'cubic-bezier(0.3, 0.95, 0.5, 1)';
7 7
8 Polymer({ 8 Polymer({
9 is: 'cr-shared-menu', 9 is: 'cr-shared-menu',
10 10
11 behaviors: [Polymer.IronA11yKeysBehavior],
12
13 properties: { 11 properties: {
14 menuOpen: { 12 menuOpen: {
15 type: Boolean, 13 type: Boolean,
16 observer: 'menuOpenChanged_', 14 observer: 'menuOpenChanged_',
17 value: false, 15 value: false,
18 notify: true, 16 notify: true,
19 }, 17 },
20 18
21 /** 19 /**
22 * The contextual item that this menu was clicked for, e.g. the data used to 20 * The contextual item that this menu was clicked for, e.g. the data used to
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
69 return [{ 67 return [{
70 name: 'fade-out-animation', 68 name: 'fade-out-animation',
71 timing: { 69 timing: {
72 duration: 150 70 duration: 150
73 } 71 }
74 }]; 72 }];
75 } 73 }
76 } 74 }
77 }, 75 },
78 76
79 keyBindings: {
80 'tab': 'onTabPressed_',
81 },
82
83 listeners: { 77 listeners: {
84 'dropdown.iron-overlay-canceled': 'onOverlayCanceled_', 78 'dropdown.iron-overlay-canceled': 'onOverlayCanceled_',
85 }, 79 },
86 80
87 /** 81 /**
88 * The last anchor that was used to open a menu. It's necessary for toggling. 82 * The last anchor that was used to open a menu. It's necessary for toggling.
89 * @private {?Element} 83 * @private {?Element}
90 */ 84 */
91 lastAnchor_: null, 85 lastAnchor_: null,
92 86
93 /**
94 * The first focusable child in the menu's light DOM.
95 * @private {?Element}
96 */
97 firstFocus_: null,
98
99 /**
100 * The last focusable child in the menu's light DOM.
101 * @private {?Element}
102 */
103 lastFocus_: null,
104
105 /** @override */ 87 /** @override */
106 attached: function() { 88 attached: function() {
107 window.addEventListener('resize', this.closeMenu.bind(this)); 89 window.addEventListener('resize', this.closeMenu.bind(this));
90 this.$.menu.addEventListener(
91 'keydown', this.onCaptureKeyDown_.bind(this), true);
108 }, 92 },
109 93
110 /** Closes the menu. */ 94 /** Closes the menu. */
111 closeMenu: function() { 95 closeMenu: function() {
112 if (this.root.activeElement == null) { 96 if (this.root.activeElement == null) {
113 // Something else has taken focus away from the menu. Do not attempt to 97 // Something else has taken focus away from the menu. Do not attempt to
114 // restore focus to the button which opened the menu. 98 // restore focus to the button which opened the menu.
115 this.$.dropdown.restoreFocusOnClose = false; 99 this.$.dropdown.restoreFocusOnClose = false;
116 } 100 }
117 this.menuOpen = false; 101 this.menuOpen = false;
118 }, 102 },
119 103
120 /** 104 /**
121 * Opens the menu at the anchor location. 105 * Opens the menu at the anchor location.
122 * @param {!Element} anchor The location to display the menu. 106 * @param {!Element} anchor The location to display the menu.
123 * @param {!Object=} opt_itemData The contextual item's data. 107 * @param {!Object=} opt_itemData The contextual item's data.
124 */ 108 */
125 openMenu: function(anchor, opt_itemData) { 109 openMenu: function(anchor, opt_itemData) {
126 if (this.lastAnchor_ == anchor && this.menuOpen) 110 if (this.lastAnchor_ == anchor && this.menuOpen)
127 return; 111 return;
128 112
129 if (this.menuOpen) 113 if (this.menuOpen)
130 this.closeMenu(); 114 this.closeMenu();
131 115
132 this.itemData = opt_itemData || null; 116 this.itemData = opt_itemData || null;
133 this.lastAnchor_ = anchor; 117 this.lastAnchor_ = anchor;
134 this.$.dropdown.restoreFocusOnClose = true; 118 this.$.dropdown.restoreFocusOnClose = true;
135 119 this.$.menu.selected = -1;
136 var focusableChildren = Polymer.dom(this).querySelectorAll(
137 '[tabindex]:not([disabled]):not([hidden]),' +
138 'button:not([disabled]):not([hidden])');
139 if (focusableChildren.length > 0) {
140 this.$.dropdown.focusTarget = focusableChildren[0];
141 this.firstFocus_ = focusableChildren[0];
142 this.lastFocus_ = focusableChildren[focusableChildren.length - 1];
143 }
144 120
145 // Move the menu to the anchor. 121 // Move the menu to the anchor.
146 this.$.dropdown.positionTarget = anchor; 122 this.$.dropdown.positionTarget = anchor;
147 this.menuOpen = true; 123 this.menuOpen = true;
148 }, 124 },
149 125
150 /** 126 /**
151 * Toggles the menu for the anchor that is passed in. 127 * Toggles the menu for the anchor that is passed in.
152 * @param {!Element} anchor The location to display the menu. 128 * @param {!Element} anchor The location to display the menu.
153 * @param {!Object=} opt_itemData The contextual item's data. 129 * @param {!Object=} opt_itemData The contextual item's data.
154 */ 130 */
155 toggleMenu: function(anchor, opt_itemData) { 131 toggleMenu: function(anchor, opt_itemData) {
156 if (anchor == this.lastAnchor_ && this.menuOpen) 132 if (anchor == this.lastAnchor_ && this.menuOpen)
157 this.closeMenu(); 133 this.closeMenu();
158 else 134 else
159 this.openMenu(anchor, opt_itemData); 135 this.openMenu(anchor, opt_itemData);
160 }, 136 },
161 137
162 /** 138 /**
163 * Trap focus inside the menu. As a very basic heuristic, will wrap focus from 139 * Close the menu when tab is pressed. Note that we must
164 * the first element with a nonzero tabindex to the last such element. 140 * explicitly add a capture event listener to do this as iron-menu-behavior
165 * TODO(tsergeant): Use iron-focus-wrap-behavior once it is available 141 * eats all key events during bubbling. See
166 * (https://github.com/PolymerElements/iron-overlay-behavior/issues/179). 142 * https://github.com/PolymerElements/iron-menu-behavior/issues/56.
167 * @param {CustomEvent} e 143 * This will move focus to the next focusable element before/after the
144 * anchor.
145 * @private
168 */ 146 */
169 onTabPressed_: function(e) { 147 onCaptureKeyDown_: function(e) {
170 if (!this.firstFocus_ || !this.lastFocus_) 148 if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(e, 'tab')) {
Dan Beam 2016/09/23 06:25:32 what does this do on shift tab?
tsergeant 2016/09/26 01:51:01 The conditional matches on shift tab, so the menu
171 return; 149 // Need to refocus the anchor synchronously so that the tab event takes
172 150 // effect on it.
173 var toFocus; 151 this.$.dropdown.restoreFocusOnClose = false;
174 var keyEvent = e.detail.keyboardEvent; 152 this.lastAnchor_.focus();
175 if (keyEvent.shiftKey && keyEvent.target == this.firstFocus_) 153 this.closeMenu();
176 toFocus = this.lastFocus_; 154 }
177 else if (!keyEvent.shiftKey && keyEvent.target == this.lastFocus_)
178 toFocus = this.firstFocus_;
179
180 if (!toFocus)
181 return;
182
183 e.preventDefault();
184 toFocus.focus();
185 }, 155 },
186 156
187 /** 157 /**
188 * Ensure the menu is reset properly when it is closed by the dropdown (eg, 158 * Ensure the menu is reset properly when it is closed by the dropdown (eg,
189 * clicking outside). 159 * clicking outside).
190 * @private 160 * @private
191 */ 161 */
192 menuOpenChanged_: function() { 162 menuOpenChanged_: function() {
193 if (!this.menuOpen) { 163 if (!this.menuOpen) {
194 this.itemData = null; 164 this.itemData = null;
195 this.lastAnchor_ = null; 165 this.lastAnchor_ = null;
196 } 166 }
197 }, 167 },
198 168
199 /** 169 /**
200 * Prevent focus restoring when tapping outside the menu. This stops the 170 * Prevent focus restoring when tapping outside the menu. This stops the
201 * focus moving around unexpectedly when closing the menu with the mouse. 171 * focus moving around unexpectedly when closing the menu with the mouse.
202 * @param {CustomEvent} e 172 * @param {CustomEvent} e
203 * @private 173 * @private
204 */ 174 */
205 onOverlayCanceled_: function(e) { 175 onOverlayCanceled_: function(e) {
206 if (e.detail.type == 'tap') 176 if (e.detail.type == 'tap')
207 this.$.dropdown.restoreFocusOnClose = false; 177 this.$.dropdown.restoreFocusOnClose = false;
208 }, 178 },
209 }); 179 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698