| // Copyright (c) 2012 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. |
| |
| // clang-format off |
| // #import {MenuItem} from 'chrome://resources/js/cr/ui/menu_item.m.js'; |
| // #import {util} from '../../../common/js/util.m.js'; |
| // #import {FilesMenuItem} from './files_menu.m.js'; |
| // #import {decorate} from 'chrome://resources/js/cr/ui.m.js'; |
| // #import {MultiMenuButton} from './multi_menu_button.m.js'; |
| // #import {getPropertyDescriptor, PropertyKind} from 'chrome://resources/js/cr.m.js'; |
| // clang-format on |
| |
| /** |
| * @fileoverview This implements a combobutton control. |
| */ |
| cr.define('cr.ui', () => { |
| /** |
| * Creates a new combo button element. |
| */ |
| /* #export */ class ComboButton extends cr.ui.MultiMenuButton { |
| constructor() { |
| super(); |
| |
| /** @private {?cr.ui.MenuItem} */ |
| this.defaultItem_ = null; |
| |
| /** @private {?Element} */ |
| this.trigger_ = null; |
| |
| /** @private {?Element} */ |
| this.actionNode_ = null; |
| |
| /** @private {?Element} */ |
| this.filesToggleRipple_ = null; |
| |
| /** @private {boolean} */ |
| this.disabled = false; |
| |
| /** @private {boolean} */ |
| this.multiple = false; |
| } |
| |
| /** |
| * Truncates drop-down list. |
| */ |
| clear() { |
| this.menu.clear(); |
| this.multiple = false; |
| } |
| |
| addDropDownItem(item) { |
| this.multiple = true; |
| const menuitem = this.menu.addMenuItem(item); |
| |
| // If menu is files-menu, decorate menu item as FilesMenuItem. |
| if (this.menu.classList.contains('files-menu')) { |
| cr.ui.decorate(menuitem, cr.ui.FilesMenuItem); |
| } |
| |
| menuitem.data = item; |
| if (item.iconType) { |
| menuitem.style.backgroundImage = ''; |
| menuitem.setAttribute('file-type-icon', item.iconType); |
| } |
| if (item.bold) { |
| menuitem.style.fontWeight = 'bold'; |
| } |
| return menuitem; |
| } |
| |
| /** |
| * Adds separator to drop-down list. |
| */ |
| addSeparator() { |
| this.menu.addSeparator(); |
| } |
| |
| /** |
| * Default item to fire on combobox click |
| */ |
| get defaultItem() { |
| return this.defaultItem_; |
| } |
| setDefaultItem_(defaultItem) { |
| this.defaultItem_ = defaultItem; |
| this.actionNode_.textContent = defaultItem.label || ''; |
| } |
| set defaultItem(defaultItem) { |
| this.setDefaultItem_(defaultItem); |
| } |
| |
| /** |
| * Utility function to set a boolean property get/setter. |
| * @param {string} property Name of the property. |
| */ |
| addBooleanProperty_(property) { |
| Object.defineProperty(this, property, { |
| get() { |
| return this.getAttribute(property); |
| }, |
| set(value) { |
| if (value) { |
| this.setAttribute(property, property); |
| } else { |
| this.removeAttribute(property); |
| } |
| }, |
| enumerable: true, |
| configurable: true |
| }); |
| } |
| |
| /** |
| * cr.ui.decorate expects a static |decorate| method. |
| * |
| * @param {!Element} el Element to be decorated. |
| * @return {!cr.ui.ComboButton} Decorated element. |
| * @public |
| */ |
| static decorate(el) { |
| // Add the ComboButton methods to the element we're |
| // decorating, leaving it's prototype chain intact. |
| // Don't copy 'constructor' or property get/setters. |
| Object.getOwnPropertyNames(ComboButton.prototype).forEach(name => { |
| if (name !== 'constructor' && name !== 'multiple' && |
| name !== 'disabled') { |
| el[name] = ComboButton.prototype[name]; |
| } |
| }); |
| Object.getOwnPropertyNames(cr.ui.MultiMenuButton.prototype) |
| .forEach(name => { |
| if (name !== 'constructor' && |
| !Object.getOwnPropertyDescriptor(el, name)) { |
| el[name] = cr.ui.MultiMenuButton.prototype[name]; |
| } |
| }); |
| // Set up the 'menu, defaultItem, multiple and disabled' |
| // properties & setter/getters. |
| Object.defineProperty(el, 'menu', { |
| get() { |
| return this.menu_; |
| }, |
| set(menu) { |
| this.setMenu_(menu); |
| }, |
| enumerable: true, |
| configurable: true |
| }); |
| Object.defineProperty(el, 'defaultItem', { |
| get() { |
| return this.defaultItem_; |
| }, |
| set(defaultItem) { |
| this.setDefaultItem_(defaultItem); |
| }, |
| enumerable: true, |
| configurable: true |
| }); |
| el.addBooleanProperty_('multiple'); |
| el.addBooleanProperty_('disabled'); |
| el = /** @type {!cr.ui.ComboButton} */ (el); |
| el.decorate(); |
| return el; |
| } |
| |
| /** |
| * Initializes the element. |
| */ |
| decorate() { |
| cr.ui.MultiMenuButton.prototype.decorate.call(this); |
| |
| this.classList.add('combobutton'); |
| |
| const buttonLayer = this.ownerDocument.createElement('div'); |
| buttonLayer.classList.add('button'); |
| this.appendChild(buttonLayer); |
| |
| this.actionNode_ = this.ownerDocument.createElement('div'); |
| this.actionNode_.classList.add('action'); |
| buttonLayer.appendChild(this.actionNode_); |
| |
| const triggerIcon = this.ownerDocument.createElement('iron-icon'); |
| triggerIcon.setAttribute('icon', 'files:arrow-drop-down'); |
| this.trigger_ = this.ownerDocument.createElement('div'); |
| this.trigger_.classList.add('trigger'); |
| this.trigger_.appendChild(triggerIcon); |
| |
| buttonLayer.appendChild(this.trigger_); |
| |
| const ripplesLayer = this.ownerDocument.createElement('div'); |
| ripplesLayer.classList.add('ripples'); |
| this.appendChild(ripplesLayer); |
| if (util.isFilesNg()) { |
| ripplesLayer.setAttribute('hidden', ''); |
| } |
| |
| this.filesToggleRipple_ = |
| this.ownerDocument.createElement('files-toggle-ripple'); |
| ripplesLayer.appendChild(this.filesToggleRipple_); |
| |
| /** @private {!PaperRipple} */ |
| this.paperRipple_ = /** @type {!PaperRipple} */ |
| (this.ownerDocument.createElement('paper-ripple')); |
| ripplesLayer.appendChild(this.paperRipple_); |
| |
| this.addEventListener('click', this.handleButtonClick_.bind(this)); |
| this.addEventListener('menushow', this.handleMenuShow_.bind(this)); |
| this.addEventListener('menuhide', this.handleMenuHide_.bind(this)); |
| |
| this.trigger_.addEventListener( |
| 'click', this.handleTriggerClicked_.bind(this)); |
| |
| this.menu.addEventListener( |
| 'activate', this.handleMenuActivate_.bind(this)); |
| |
| // Remove mousedown event listener created by MultiMenuButton::decorate, |
| // and move it down to trigger_. |
| this.removeEventListener('mousedown', this); |
| this.trigger_.addEventListener('mousedown', this); |
| } |
| |
| /** |
| * Handles the keydown event for the menu button. |
| */ |
| handleKeyDown(e) { |
| switch (e.key) { |
| case 'ArrowDown': |
| case 'ArrowUp': |
| if (!this.isMenuShown()) { |
| this.showMenu(false); |
| } |
| e.preventDefault(); |
| break; |
| case 'Escape': // Maybe this is remote desktop playing a prank? |
| this.hideMenu(); |
| break; |
| } |
| } |
| |
| handleTriggerClicked_(event) { |
| event.stopPropagation(); |
| } |
| |
| handleMenuActivate_(event) { |
| this.dispatchSelectEvent(event.target.data); |
| } |
| |
| handleButtonClick_(event) { |
| if (this.multiple) { |
| // When there are multiple choices just show/hide menu. |
| if (this.isMenuShown()) { |
| this.hideMenu(); |
| } else { |
| this.showMenu(true); |
| } |
| } else { |
| // When there is only 1 choice, just dispatch to open. |
| this.paperRipple_.simulatedRipple(); |
| this.blur(); |
| this.dispatchSelectEvent(this.defaultItem_); |
| } |
| } |
| |
| handleMenuShow_() { |
| this.filesToggleRipple_.activated = true; |
| } |
| |
| handleMenuHide_() { |
| this.filesToggleRipple_.activated = false; |
| } |
| |
| dispatchSelectEvent(item) { |
| const selectEvent = new Event('select'); |
| selectEvent.item = item; |
| this.dispatchEvent(selectEvent); |
| } |
| } |
| |
| // #cr_define_end |
| return { |
| ComboButton: ComboButton, |
| }; |
| }); |