1// Copyright 2020 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import {DOM, V8CustomElement} from './helper.mjs'; 6 7DOM.defineCustomElement( 8 'view/tool-tip', (templateText) => class Tooltip extends V8CustomElement { 9 _targetNode; 10 _content; 11 _isHidden = true; 12 13 constructor() { 14 super(templateText); 15 this._intersectionObserver = new IntersectionObserver((entries) => { 16 if (entries[0].intersectionRatio <= 0) { 17 this.hide(); 18 } else { 19 this.show(); 20 this.requestUpdate(true); 21 } 22 }); 23 document.addEventListener('click', (event) => { 24 // Only hide the tooltip if we click anywhere outside of it. 25 let target = event.target; 26 while (target) { 27 if (target == this) return; 28 target = target.parentNode; 29 } 30 this.hide() 31 }); 32 } 33 34 _update() { 35 if (!this._targetNode || this._isHidden) return; 36 const rect = this._targetNode.getBoundingClientRect(); 37 rect.x += rect.width / 2; 38 let atRight = this._useRight(rect.x); 39 let atBottom = this._useBottom(rect.y); 40 if (atBottom) rect.y += rect.height; 41 this._setPosition(rect, atRight, atBottom); 42 this.requestUpdate(true); 43 } 44 45 set positionOrTargetNode(positionOrTargetNode) { 46 if (positionOrTargetNode.nodeType === undefined) { 47 this.position = positionOrTargetNode; 48 } else { 49 this.targetNode = positionOrTargetNode; 50 } 51 } 52 53 set targetNode(targetNode) { 54 this._intersectionObserver.disconnect(); 55 this._targetNode = targetNode; 56 if (targetNode === undefined) return; 57 if (!(targetNode instanceof SVGElement)) { 58 this._intersectionObserver.observe(targetNode); 59 } 60 this.requestUpdate(true); 61 } 62 63 set position(position) { 64 this._targetNode = undefined; 65 this._setPosition( 66 position, this._useRight(position.x), this._useBottom(position.y)); 67 } 68 69 _setPosition(viewportPosition, atRight, atBottom) { 70 const horizontalMode = atRight ? 'right' : 'left'; 71 const verticalMode = atBottom ? 'bottom' : 'top'; 72 this.bodyNode.className = horizontalMode + ' ' + verticalMode; 73 const pageX = viewportPosition.x + window.scrollX; 74 this.style.left = `${pageX}px`; 75 const pageY = viewportPosition.y + window.scrollY; 76 this.style.top = `${pageY}px`; 77 } 78 79 _useBottom(viewportY) { 80 return viewportY <= 400; 81 } 82 83 _useRight(viewportX) { 84 return viewportX < document.documentElement.clientWidth / 2; 85 } 86 87 set content(content) { 88 if (!content) return this.hide(); 89 this.show(); 90 if (this._content === content) return; 91 this._content = content; 92 93 if (typeof content === 'string') { 94 this.contentNode.innerHTML = content; 95 this.contentNode.className = 'textContent'; 96 } else if (content?.nodeType && content?.nodeName) { 97 this._setContentNode(content); 98 } else { 99 if (this.contentNode.firstChild?.localName == 'property-link-table') { 100 this.contentNode.firstChild.propertyDict = content; 101 } else { 102 const node = DOM.element('property-link-table'); 103 node.instanceLinkButtons = true; 104 node.propertyDict = content; 105 this._setContentNode(node); 106 } 107 } 108 } 109 110 _setContentNode(content) { 111 const newContent = DOM.div(); 112 newContent.appendChild(content); 113 this.contentNode.replaceWith(newContent); 114 newContent.id = 'content'; 115 } 116 117 hide() { 118 this._content = undefined; 119 if (this._isHidden) return; 120 this._isHidden = true; 121 this.bodyNode.style.display = 'none'; 122 this.targetNode = undefined; 123 } 124 125 show() { 126 if (!this._isHidden) return; 127 this.bodyNode.style.display = 'block'; 128 this._isHidden = false; 129 } 130 131 get bodyNode() { 132 return this.$('#body'); 133 } 134 135 get contentNode() { 136 return this.$('#content'); 137 } 138 }); 139