| 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 Polymer({ | 5 Polymer({ |
| 6 is: 'cr-action-menu', | 6 is: 'cr-action-menu', |
| 7 extends: 'dialog', | 7 extends: 'dialog', |
| 8 | 8 |
| 9 /** | 9 /** |
| 10 * List of all options in this action menu. | 10 * List of all options in this action menu. |
| 11 * @private {?NodeList<!Element>} | 11 * @private {?NodeList<!Element>} |
| 12 */ | 12 */ |
| 13 options_: null, | 13 options_: null, |
| 14 | 14 |
| 15 /** | 15 /** |
| 16 * The element which the action menu will be anchored to. Also the element | 16 * The element which the action menu will be anchored to. Also the element |
| 17 * where focus will be returned after the menu is closed. | 17 * where focus will be returned after the menu is closed. |
| 18 * @private {?Element} | 18 * @private {?Element} |
| 19 */ | 19 */ |
| 20 anchorElement_: null, | 20 anchorElement_: null, |
| 21 | 21 |
| 22 /** | 22 /** |
| 23 * Bound reference to an event listener function such that it can be removed | 23 * Bound reference to an event listener function such that it can be removed |
| 24 * on detach. | 24 * on detach. |
| 25 * @private {?Function} | 25 * @private {?Function} |
| 26 */ | 26 */ |
| 27 boundClose_: null, | 27 boundClose_: null, |
| 28 | 28 |
| 29 /** @private {boolean} */ |
| 30 hasMousemoveListener_: false, |
| 31 |
| 29 hostAttributes: { | 32 hostAttributes: { |
| 30 tabindex: 0, | 33 tabindex: 0, |
| 31 }, | 34 }, |
| 32 | 35 |
| 33 listeners: { | 36 listeners: { |
| 34 'keydown': 'onKeyDown_', | 37 'keydown': 'onKeyDown_', |
| 38 'mouseover': 'onMouseover_', |
| 35 'tap': 'onTap_', | 39 'tap': 'onTap_', |
| 36 }, | 40 }, |
| 37 | 41 |
| 38 /** override */ | 42 /** override */ |
| 39 attached: function() { | 43 attached: function() { |
| 40 this.options_ = this.querySelectorAll('.dropdown-item'); | 44 this.options_ = this.querySelectorAll('.dropdown-item'); |
| 41 }, | 45 }, |
| 42 | 46 |
| 43 /** override */ | 47 /** override */ |
| 44 detached: function() { | 48 detached: function() { |
| (...skipping 25 matching lines...) Expand all Loading... |
| 70 if (e.key == 'Tab' || e.key == 'Escape') { | 74 if (e.key == 'Tab' || e.key == 'Escape') { |
| 71 this.close(); | 75 this.close(); |
| 72 e.preventDefault(); | 76 e.preventDefault(); |
| 73 return; | 77 return; |
| 74 } | 78 } |
| 75 | 79 |
| 76 if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') | 80 if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') |
| 77 return; | 81 return; |
| 78 | 82 |
| 79 var nextOption = this.getNextOption_(e.key == 'ArrowDown' ? 1 : -1); | 83 var nextOption = this.getNextOption_(e.key == 'ArrowDown' ? 1 : -1); |
| 80 if (nextOption) | 84 if (nextOption) { |
| 85 if (!this.hasMousemoveListener_) { |
| 86 this.hasMousemoveListener_ = true; |
| 87 listenOnce(this, 'mousemove', function(e) { |
| 88 this.onMouseover_(e); |
| 89 this.hasMousemoveListener_ = false; |
| 90 }.bind(this)); |
| 91 } |
| 81 nextOption.focus(); | 92 nextOption.focus(); |
| 93 } |
| 82 | 94 |
| 83 e.preventDefault(); | 95 e.preventDefault(); |
| 84 }, | 96 }, |
| 85 | 97 |
| 86 /** | 98 /** |
| 99 * @param {!Event} e |
| 100 * @private |
| 101 */ |
| 102 onMouseover_: function(e) { |
| 103 // TODO(scottchen): Using "focus" to determine selected item might mess |
| 104 // with screen readers in some edge cases. |
| 105 var i = 0; |
| 106 do { |
| 107 var target = e.path[i++]; |
| 108 if (target.classList && target.classList.contains('dropdown-item')) { |
| 109 target.focus(); |
| 110 return; |
| 111 } |
| 112 } while (this != target); |
| 113 |
| 114 // The user moved the mouse off the options. Reset focus to the dialog. |
| 115 this.focus(); |
| 116 }, |
| 117 |
| 118 /** |
| 87 * @param {number} step -1 for getting previous option (up), 1 for getting | 119 * @param {number} step -1 for getting previous option (up), 1 for getting |
| 88 * next option (down). | 120 * next option (down). |
| 89 * @return {?Element} The next focusable option, taking into account | 121 * @return {?Element} The next focusable option, taking into account |
| 90 * disabled/hidden attributes, or null if no focusable option exists. | 122 * disabled/hidden attributes, or null if no focusable option exists. |
| 91 * @private | 123 * @private |
| 92 */ | 124 */ |
| 93 getNextOption_: function(step) { | 125 getNextOption_: function(step) { |
| 94 // Using a counter to ensure no infinite loop occurs if all elements are | 126 // Using a counter to ensure no infinite loop occurs if all elements are |
| 95 // hidden/disabled. | 127 // hidden/disabled. |
| 96 var counter = 0; | 128 var counter = 0; |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 151 // Attempt to show the menu starting from the top of the rectangle and | 183 // Attempt to show the menu starting from the top of the rectangle and |
| 152 // extending downwards. If that does not fit within the window, fallback to | 184 // extending downwards. If that does not fit within the window, fallback to |
| 153 // starting from the bottom and extending upwards. | 185 // starting from the bottom and extending upwards. |
| 154 var top = rect.top + this.offsetHeight <= window.innerHeight ? rect.top : | 186 var top = rect.top + this.offsetHeight <= window.innerHeight ? rect.top : |
| 155 rect.bottom - | 187 rect.bottom - |
| 156 this.offsetHeight - Math.max(rect.bottom - window.innerHeight, 0); | 188 this.offsetHeight - Math.max(rect.bottom - window.innerHeight, 0); |
| 157 | 189 |
| 158 this.style.top = top + 'px'; | 190 this.style.top = top + 'px'; |
| 159 }, | 191 }, |
| 160 }); | 192 }); |
| OLD | NEW |