Chromium Code Reviews| OLD | NEW |
|---|---|
| 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. | 20 * The contextual item that this menu was clicked for. |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 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 /** | 87 /** |
| 94 * The first focusable child in the menu's light DOM. | 88 * The first focusable child in the menu's light DOM. |
| 95 * @private {?Element} | 89 * @private {?Element} |
| 96 */ | 90 */ |
| 97 firstFocus_: null, | 91 firstFocus_: null, |
| 98 | 92 |
| 99 /** | 93 /** |
| 100 * The last focusable child in the menu's light DOM. | 94 * The last focusable child in the menu's light DOM. |
| 101 * @private {?Element} | 95 * @private {?Element} |
| 102 */ | 96 */ |
| 103 lastFocus_: null, | 97 lastFocus_: null, |
| 104 | 98 |
| 105 /** @override */ | 99 /** @override */ |
| 106 attached: function() { | 100 attached: function() { |
| 107 window.addEventListener('resize', this.closeMenu.bind(this)); | 101 window.addEventListener('resize', this.closeMenu.bind(this)); |
| 102 this.$.menu.addEventListener( | |
| 103 'keydown', this.onCaptureKeyDown_.bind(this), true); | |
| 108 }, | 104 }, |
| 109 | 105 |
| 110 /** Closes the menu. */ | 106 /** Closes the menu. */ |
| 111 closeMenu: function() { | 107 closeMenu: function() { |
| 112 if (this.root.activeElement == null) { | 108 if (this.root.activeElement == null) { |
| 113 // Something else has taken focus away from the menu. Do not attempt to | 109 // Something else has taken focus away from the menu. Do not attempt to |
| 114 // restore focus to the button which opened the menu. | 110 // restore focus to the button which opened the menu. |
| 115 this.$.dropdown.restoreFocusOnClose = false; | 111 this.$.dropdown.restoreFocusOnClose = false; |
| 116 } | 112 } |
| 117 this.menuOpen = false; | 113 this.menuOpen = false; |
| 118 }, | 114 }, |
| 119 | 115 |
| 120 /** | 116 /** |
| 121 * Opens the menu at the anchor location. | 117 * Opens the menu at the anchor location. |
| 122 * @param {!Element} anchor The location to display the menu. | 118 * @param {!Element} anchor The location to display the menu. |
| 123 * @param {!Object} itemData The contextual item's data. | 119 * @param {!Object} itemData The contextual item's data. |
| 124 */ | 120 */ |
| 125 openMenu: function(anchor, itemData) { | 121 openMenu: function(anchor, itemData) { |
| 126 if (this.lastAnchor_ == anchor && this.menuOpen) | 122 if (this.lastAnchor_ == anchor && this.menuOpen) |
| 127 return; | 123 return; |
| 128 | 124 |
| 129 if (this.menuOpen) | 125 if (this.menuOpen) |
| 130 this.closeMenu(); | 126 this.closeMenu(); |
| 131 | 127 |
| 132 this.itemData = itemData; | 128 this.itemData = itemData; |
| 133 this.lastAnchor_ = anchor; | 129 this.lastAnchor_ = anchor; |
| 134 this.$.dropdown.restoreFocusOnClose = true; | 130 this.$.dropdown.restoreFocusOnClose = true; |
| 135 | 131 this.$.menu.selected = 0; |
| 136 var focusableChildren = Polymer.dom(this).querySelectorAll( | |
| 137 '[tabindex]:not([hidden]),button:not([hidden])'); | |
| 138 if (focusableChildren.length > 0) { | |
| 139 this.$.dropdown.focusTarget = focusableChildren[0]; | |
| 140 this.firstFocus_ = focusableChildren[0]; | |
| 141 this.lastFocus_ = focusableChildren[focusableChildren.length - 1]; | |
| 142 } | |
| 143 | 132 |
| 144 // Move the menu to the anchor. | 133 // Move the menu to the anchor. |
| 145 this.$.dropdown.positionTarget = anchor; | 134 this.$.dropdown.positionTarget = anchor; |
| 146 this.menuOpen = true; | 135 this.menuOpen = true; |
| 147 }, | 136 }, |
| 148 | 137 |
| 149 /** | 138 /** |
| 150 * Toggles the menu for the anchor that is passed in. | 139 * Toggles the menu for the anchor that is passed in. |
| 151 * @param {!Element} anchor The location to display the menu. | 140 * @param {!Element} anchor The location to display the menu. |
| 152 * @param {!Object} itemData The contextual item's data. | 141 * @param {!Object} itemData The contextual item's data. |
| 153 */ | 142 */ |
| 154 toggleMenu: function(anchor, itemData) { | 143 toggleMenu: function(anchor, itemData) { |
| 155 if (anchor == this.lastAnchor_ && this.menuOpen) | 144 if (anchor == this.lastAnchor_ && this.menuOpen) |
| 156 this.closeMenu(); | 145 this.closeMenu(); |
| 157 else | 146 else |
| 158 this.openMenu(anchor, itemData); | 147 this.openMenu(anchor, itemData); |
| 159 }, | 148 }, |
| 160 | 149 |
| 161 /** | 150 /** |
| 162 * Trap focus inside the menu. As a very basic heuristic, will wrap focus from | 151 * Close the menu when tab is pressed. Note that we must |
| 163 * the first element with a nonzero tabindex to the last such element. | 152 * explicitly add a capture event listener to do this as iron-menu-behavior |
| 164 * TODO(tsergeant): Use iron-focus-wrap-behavior once it is available | 153 * eats all key events during bubbling. See |
| 165 * (https://github.com/PolymerElements/iron-overlay-behavior/issues/179). | 154 * https://github.com/PolymerElements/iron-menu-behavior/issues/56. |
| 166 * @param {CustomEvent} e | 155 * @private |
| 167 */ | 156 */ |
| 168 onTabPressed_: function(e) { | 157 onCaptureKeyDown_: function(e) { |
| 169 if (!this.firstFocus_ || !this.lastFocus_) | 158 if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(e, 'tab')) { |
| 170 return; | 159 this.closeMenu(); |
| 171 | 160 e.preventDefault(); |
|
Dan Beam
2016/08/23 05:50:40
er, wat? so you want to stop tab from changing foc
tsergeant
2016/08/23 07:08:40
It's complicated. I probably should put a comment
Dan Beam
2016/08/23 20:45:37
don't we want focus to go to the next focusable th
| |
| 172 var toFocus; | 161 } |
| 173 var keyEvent = e.detail.keyboardEvent; | |
| 174 if (keyEvent.shiftKey && keyEvent.target == this.firstFocus_) | |
| 175 toFocus = this.lastFocus_; | |
| 176 else if (keyEvent.target == this.lastFocus_) | |
| 177 toFocus = this.firstFocus_; | |
| 178 | |
| 179 if (!toFocus) | |
| 180 return; | |
| 181 | |
| 182 e.preventDefault(); | |
| 183 toFocus.focus(); | |
| 184 }, | 162 }, |
| 185 | 163 |
| 186 /** | 164 /** |
| 187 * Ensure the menu is reset properly when it is closed by the dropdown (eg, | 165 * Ensure the menu is reset properly when it is closed by the dropdown (eg, |
| 188 * clicking outside). | 166 * clicking outside). |
| 189 * @private | 167 * @private |
| 190 */ | 168 */ |
| 191 menuOpenChanged_: function() { | 169 menuOpenChanged_: function() { |
| 192 if (!this.menuOpen) { | 170 if (!this.menuOpen) { |
| 193 this.itemData = null; | 171 this.itemData = null; |
| 194 this.lastAnchor_ = null; | 172 this.lastAnchor_ = null; |
| 195 } | 173 } |
| 196 }, | 174 }, |
| 197 | 175 |
| 198 /** | 176 /** |
| 199 * Prevent focus restoring when tapping outside the menu. This stops the | 177 * Prevent focus restoring when tapping outside the menu. This stops the |
| 200 * focus moving around unexpectedly when closing the menu with the mouse. | 178 * focus moving around unexpectedly when closing the menu with the mouse. |
| 201 * @param {CustomEvent} e | 179 * @param {CustomEvent} e |
| 202 * @private | 180 * @private |
| 203 */ | 181 */ |
| 204 onOverlayCanceled_: function(e) { | 182 onOverlayCanceled_: function(e) { |
| 205 if (e.detail.type == 'tap') | 183 if (e.detail.type == 'tap') |
| 206 this.$.dropdown.restoreFocusOnClose = false; | 184 this.$.dropdown.restoreFocusOnClose = false; |
| 207 }, | 185 }, |
| 208 }); | 186 }); |
| OLD | NEW |