blob: cc8b55a5cf7dd0a4dcb33ccbcfa17657f9f075e1 [file] [log] [blame]
/**
* @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;