| /** |
| * @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. |
| */ |
| import '../../elements/shared/gr-tooltip/gr-tooltip.js'; |
| import {flush} from '@polymer/polymer/lib/legacy/polymer.dom.js'; |
| import {getRootElement} from '../../scripts/rootElement.js'; |
| |
| const BOTTOM_OFFSET = 7.2; // Height of the arrow in tooltip. |
| |
| /** @polymerBehavior Gerrit.TooltipBehavior */ |
| export const TooltipBehavior = { |
| |
| properties: { |
| hasTooltip: { |
| type: Boolean, |
| observer: '_setupTooltipListeners', |
| }, |
| positionBelow: { |
| type: Boolean, |
| value: false, |
| reflectToAttribute: true, |
| }, |
| |
| _isTouchDevice: { |
| type: Boolean, |
| value() { |
| return 'ontouchstart' in document.documentElement; |
| }, |
| }, |
| _tooltip: Object, |
| _titleText: String, |
| _hasSetupTooltipListeners: { |
| type: Boolean, |
| value: false, |
| }, |
| }, |
| |
| /** @override */ |
| detached() { |
| // NOTE: if you define your own `detached` in your component |
| // then this won't take affect (as its not a class yet) |
| this._handleHideTooltip(); |
| this.removeEventListener('mouseenter', this._mouseenterHandler); |
| }, |
| |
| _setupTooltipListeners() { |
| if (!this._mouseenterHandler) { |
| this._mouseenterHandler = this._handleShowTooltip.bind(this); |
| } |
| |
| if (!this.hasTooltip) { |
| // if attribute set to false, remove the listener |
| this.removeEventListener('mouseenter', this._mouseenterHandler); |
| this._hasSetupTooltipListeners = false; |
| return; |
| } |
| |
| if (this._hasSetupTooltipListeners) { |
| return; |
| } |
| this._hasSetupTooltipListeners = true; |
| |
| this.addEventListener('mouseenter', this._mouseenterHandler); |
| }, |
| |
| _handleShowTooltip(e) { |
| if (this._isTouchDevice) { return; } |
| |
| if (!this.hasAttribute('title') || |
| this.getAttribute('title') === '' || |
| this._tooltip) { |
| return; |
| } |
| |
| // Store the title attribute text then set it to an empty string to |
| // prevent it from showing natively. |
| this._titleText = this.getAttribute('title'); |
| this.setAttribute('title', ''); |
| |
| const tooltip = document.createElement('gr-tooltip'); |
| tooltip.text = this._titleText; |
| tooltip.maxWidth = this.getAttribute('max-width'); |
| tooltip.positionBelow = this.getAttribute('position-below'); |
| |
| // Set visibility to hidden before appending to the DOM so that |
| // calculations can be made based on the element’s size. |
| tooltip.style.visibility = 'hidden'; |
| getRootElement().appendChild(tooltip); |
| this._positionTooltip(tooltip); |
| tooltip.style.visibility = null; |
| |
| this._tooltip = tooltip; |
| this.listen(window, 'scroll', '_handleWindowScroll'); |
| this.listen(this, 'mouseleave', '_handleHideTooltip'); |
| this.listen(this, 'click', '_handleHideTooltip'); |
| }, |
| |
| _handleHideTooltip(e) { |
| if (this._isTouchDevice) { return; } |
| if (!this.hasAttribute('title') || |
| this._titleText == null) { |
| return; |
| } |
| |
| this.unlisten(window, 'scroll', '_handleWindowScroll'); |
| this.unlisten(this, 'mouseleave', '_handleHideTooltip'); |
| this.unlisten(this, 'click', '_handleHideTooltip'); |
| this.setAttribute('title', this._titleText); |
| if (this._tooltip && this._tooltip.parentNode) { |
| this._tooltip.parentNode.removeChild(this._tooltip); |
| } |
| this._tooltip = null; |
| }, |
| |
| _handleWindowScroll(e) { |
| if (!this._tooltip) { return; } |
| |
| this._positionTooltip(this._tooltip); |
| }, |
| |
| _positionTooltip(tooltip) { |
| // This flush is needed for tooltips to be positioned correctly in Firefox |
| // and Safari. |
| flush(); |
| const rect = this.getBoundingClientRect(); |
| const boxRect = tooltip.getBoundingClientRect(); |
| const parentRect = tooltip.parentElement.getBoundingClientRect(); |
| const top = rect.top - parentRect.top; |
| const left = |
| rect.left - parentRect.left + (rect.width - boxRect.width) / 2; |
| const right = parentRect.width - left - boxRect.width; |
| if (left < 0) { |
| tooltip.updateStyles({ |
| '--gr-tooltip-arrow-center-offset': left + 'px', |
| }); |
| } else if (right < 0) { |
| tooltip.updateStyles({ |
| '--gr-tooltip-arrow-center-offset': (-0.5 * right) + 'px', |
| }); |
| } |
| tooltip.style.left = Math.max(0, left) + 'px'; |
| |
| if (!this.positionBelow) { |
| tooltip.style.top = Math.max(0, top) + 'px'; |
| tooltip.style.transform = 'translateY(calc(-100% - ' + BOTTOM_OFFSET + |
| 'px))'; |
| } else { |
| tooltip.style.top = top + rect.height + BOTTOM_OFFSET + 'px'; |
| } |
| }, |
| }; |
| |
| // TODO(dmfilippov) Remove the following lines with assignments |
| // Plugins can use the behavior because it was accessible with |
| // the global Gerrit... variable. To avoid breaking changes in plugins |
| // temporary assign global variables. |
| window.Gerrit = window.Gerrit || {}; |
| window.Gerrit.TooltipBehavior = TooltipBehavior; |