/** * @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(window) { 'use strict'; const BOTTOM_OFFSET = 7.2; // Height of the arrow in tooltip. window.Gerrit = window.Gerrit || {}; /** @polymerBehavior Gerrit.TooltipBehavior */ Gerrit.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, }, }, detached() { this._handleHideTooltip(); }, _setupTooltipListeners() { if (this._hasSetupTooltipListeners || !this.hasTooltip) { return; } this._hasSetupTooltipListeners = true; this.addEventListener('mouseenter', this._handleShowTooltip.bind(this)); }, _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'; Gerrit.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, 'tap', '_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, 'tap', '_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. Polymer.dom.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'; } }, }; })(window);