 80af0b7505
			
		
	
	80af0b7505
	
	
	
		
			
			The change action API provides informative descriptions of the actions. Problematically - For action buttons, these were provided in native tooltips that are hard to discover. - For overflow actions, the context was not provided at all. Include the context information in easily discoverable gr-tooltips for all actions whether they're button or overflow. Feature: Issue 6781 Change-Id: I667fe59578b57ffc88c87e77df4be991875425a4
		
			
				
	
	
		
			291 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			291 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * @license
 | |
|  * Copyright (C) 2016 The Android Open Source Project
 | |
|  *
 | |
|  * Licensed under the Apache License, Version 2.0 (the "License");
 | |
|  * you may not use this file except in compliance with the License.
 | |
|  * You may obtain a copy of the License at
 | |
|  *
 | |
|  * http://www.apache.org/licenses/LICENSE-2.0
 | |
|  *
 | |
|  * Unless required by applicable law or agreed to in writing, software
 | |
|  * distributed under the License is distributed on an "AS IS" BASIS,
 | |
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|  * See the License for the specific language governing permissions and
 | |
|  * limitations under the License.
 | |
|  */
 | |
| (function() {
 | |
|   'use strict';
 | |
| 
 | |
|   const REL_NOOPENER = 'noopener';
 | |
|   const REL_EXTERNAL = 'external';
 | |
| 
 | |
|   Polymer({
 | |
|     is: 'gr-dropdown',
 | |
| 
 | |
|     /**
 | |
|      * Fired when a non-link dropdown item with the given ID is tapped.
 | |
|      *
 | |
|      * @event tap-item-<id>
 | |
|      */
 | |
| 
 | |
|     /**
 | |
|      * Fired when a non-link dropdown item is tapped.
 | |
|      *
 | |
|      * @event tap-item
 | |
|      */
 | |
| 
 | |
|     properties: {
 | |
|       items: {
 | |
|         type: Array,
 | |
|         observer: '_resetCursorStops',
 | |
|       },
 | |
|       downArrow: Boolean,
 | |
|       topContent: Object,
 | |
|       horizontalAlign: {
 | |
|         type: String,
 | |
|         value: 'left',
 | |
|       },
 | |
| 
 | |
|       /**
 | |
|        * Style the dropdown trigger as a link (rather than a button).
 | |
|        */
 | |
|       link: {
 | |
|         type: Boolean,
 | |
|         value: false,
 | |
|       },
 | |
| 
 | |
|       verticalOffset: {
 | |
|         type: Number,
 | |
|         value: 40,
 | |
|       },
 | |
| 
 | |
|       /**
 | |
|        * List the IDs of dropdown buttons to be disabled. (Note this only
 | |
|        * diisables bittons and not link entries.)
 | |
|        */
 | |
|       disabledIds: {
 | |
|         type: Array,
 | |
|         value() { return []; },
 | |
|       },
 | |
| 
 | |
|       /**
 | |
|        * The elements of the list.
 | |
|        */
 | |
|       _listElements: {
 | |
|         type: Array,
 | |
|         value() { return []; },
 | |
|       },
 | |
|     },
 | |
| 
 | |
|     behaviors: [
 | |
|       Gerrit.BaseUrlBehavior,
 | |
|       Gerrit.KeyboardShortcutBehavior,
 | |
|     ],
 | |
| 
 | |
|     keyBindings: {
 | |
|       'down': '_handleDown',
 | |
|       'enter space': '_handleEnter',
 | |
|       'tab': '_handleTab',
 | |
|       'up': '_handleUp',
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Handle the up key.
 | |
|      * @param {!Event} e
 | |
|      */
 | |
|     _handleUp(e) {
 | |
|       if (this.$.dropdown.opened) {
 | |
|         e.preventDefault();
 | |
|         e.stopPropagation();
 | |
|         this.$.cursor.previous();
 | |
|       } else {
 | |
|         this._open();
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Handle the down key.
 | |
|      * @param {!Event} e
 | |
|      */
 | |
|     _handleDown(e) {
 | |
|       if (this.$.dropdown.opened) {
 | |
|         e.preventDefault();
 | |
|         e.stopPropagation();
 | |
|         this.$.cursor.next();
 | |
|       } else {
 | |
|         this._open();
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Handle the tab key.
 | |
|      * @param {!Event} e
 | |
|      */
 | |
|     _handleTab(e) {
 | |
|       if (this.$.dropdown.opened) {
 | |
|         // Tab in a native select is a no-op. Emulate this.
 | |
|         e.preventDefault();
 | |
|         e.stopPropagation();
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Handle the enter key.
 | |
|      * @param {!Event} e
 | |
|      */
 | |
|     _handleEnter(e) {
 | |
|       e.preventDefault();
 | |
|       e.stopPropagation();
 | |
|       if (this.$.dropdown.opened) {
 | |
|         // TODO(kaspern): This solution will not work in Shadow DOM, and
 | |
|         // is not particularly robust in general. Find a better solution
 | |
|         // when page.js has been abstracted away from components.
 | |
|         const el = this.$.cursor.target.querySelector(':not([hidden])');
 | |
|         if (el) { el.click(); }
 | |
|       } else {
 | |
|         this._open();
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Handle a click on the iron-dropdown element.
 | |
|      * @param {!Event} e
 | |
|      */
 | |
|     _handleDropdownTap(e) {
 | |
|       this._close();
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Hanlde a click on the button to open the dropdown.
 | |
|      * @param {!Event} e
 | |
|      */
 | |
|     _dropdownTriggerTapHandler(e) {
 | |
|       e.preventDefault();
 | |
|       e.stopPropagation();
 | |
|       if (this.$.dropdown.opened) {
 | |
|         this._close();
 | |
|       } else {
 | |
|         this._open();
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Open the dropdown and initialize the cursor.
 | |
|      */
 | |
|     _open() {
 | |
|       this.$.dropdown.open();
 | |
|       this.$.cursor.setCursorAtIndex(0);
 | |
|       Polymer.dom.flush();
 | |
|       this.$.cursor.target.focus();
 | |
|     },
 | |
| 
 | |
|     _close() {
 | |
|       // async is needed so that that the click event is fired before the
 | |
|       // dropdown closes (This was a bug for touch devices).
 | |
|       this.async(() => {
 | |
|         this.$.dropdown.close();
 | |
|       }, 1);
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Get the class for a top-content item based on the given boolean.
 | |
|      * @param {boolean} bold Whether the item is bold.
 | |
|      * @return {string} The class for the top-content item.
 | |
|      */
 | |
|     _getClassIfBold(bold) {
 | |
|       return bold ? 'bold-text' : '';
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Build a URL for the given host and path. If there is a base URL, it will
 | |
|      * be included between the host and the path.
 | |
|      * @param {!string} host
 | |
|      * @param {!string} path
 | |
|      * @return {!string} The scheme-relative URL.
 | |
|      */
 | |
|     _computeURLHelper(host, path) {
 | |
|       return '//' + host + this.getBaseUrl() + path;
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Build a scheme-relative URL for the current host. Will include the base
 | |
|      * URL if one is present. Note: the URL will be scheme-relative but absolute
 | |
|      * with regard to the host.
 | |
|      * @param {!string} path The path for the URL.
 | |
|      * @return {!string} The scheme-relative URL.
 | |
|      */
 | |
|     _computeRelativeURL(path) {
 | |
|       const host = window.location.host;
 | |
|       return this._computeURLHelper(host, path);
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Compute the URL for a link object.
 | |
|      * @param {!Object} link The object describing the link.
 | |
|      * @return {!string} The URL.
 | |
|      */
 | |
|     _computeLinkURL(link) {
 | |
|       if (typeof link.url === 'undefined') {
 | |
|         return '';
 | |
|       }
 | |
|       if (link.target) {
 | |
|         return link.url;
 | |
|       }
 | |
|       return this._computeRelativeURL(link.url);
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Compute the value for the rel attribute of an anchor for the given link
 | |
|      * object. If the link has a target value, then the rel must be "noopener"
 | |
|      * for security reasons.
 | |
|      * @param {!Object} link The object describing the link.
 | |
|      * @return {?string} The rel value for the link.
 | |
|      */
 | |
|     _computeLinkRel(link) {
 | |
|       // Note: noopener takes precedence over external.
 | |
|       if (link.target) { return REL_NOOPENER; }
 | |
|       if (link.external) { return REL_EXTERNAL; }
 | |
|       return null;
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Handle a click on an item of the dropdown.
 | |
|      * @param {!Event} e
 | |
|      */
 | |
|     _handleItemTap(e) {
 | |
|       const id = e.target.getAttribute('data-id');
 | |
|       const item = this.items.find(item => item.id === id);
 | |
|       if (id && !this.disabledIds.includes(id)) {
 | |
|         if (item) {
 | |
|           this.dispatchEvent(new CustomEvent('tap-item', {detail: item}));
 | |
|         }
 | |
|         this.dispatchEvent(new CustomEvent('tap-item-' + id));
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * If a dropdown item is shown as a button, get the class for the button.
 | |
|      * @param {string} id
 | |
|      * @param {!Object} disabledIdsRecord The change record for the disabled IDs
 | |
|      *     list.
 | |
|      * @return {!string} The class for the item button.
 | |
|      */
 | |
|     _computeDisabledClass(id, disabledIdsRecord) {
 | |
|       return disabledIdsRecord.base.includes(id) ? 'disabled' : '';
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Recompute the stops for the dropdown item cursor.
 | |
|      */
 | |
|     _resetCursorStops() {
 | |
|       Polymer.dom.flush();
 | |
|       this._listElements = Polymer.dom(this.root).querySelectorAll('li');
 | |
|     },
 | |
| 
 | |
|     _computeHasTooltip(tooltip) {
 | |
|       return !!tooltip;
 | |
|     },
 | |
|   });
 | |
| })();
 |