Tooltip.js 4.6 KB

12345678910111213141516171819
  1. export default class Tooltip{constructor(doc){this.element=doc.body.createChild('div');this._shadowRoot=UI.createShadowRootWithCoreStyles(this.element,'ui/tooltip.css');this._tooltipElement=this._shadowRoot.createChild('div','tooltip');doc.addEventListener('mousemove',this._mouseMove.bind(this),true);doc.addEventListener('mousedown',this._hide.bind(this,true),true);doc.addEventListener('mouseleave',this._hide.bind(this,false),true);doc.addEventListener('keydown',this._hide.bind(this,true),true);UI.zoomManager.addEventListener(UI.ZoomManager.Events.ZoomChanged,this._reset,this);doc.defaultView.addEventListener('resize',this._reset.bind(this),false);}
  2. static installHandler(doc){new Tooltip(doc);}
  3. static install(element,tooltipContent,actionId,options){if(!tooltipContent){delete element[_symbol];return;}
  4. element[_symbol]={content:tooltipContent,actionId:actionId,options:options||{}};}
  5. static addNativeOverrideContainer(element){_nativeOverrideContainer.push(element);}
  6. _mouseMove(event){const mouseEvent=(event);const path=mouseEvent.composedPath();if(!path||mouseEvent.buttons!==0||(mouseEvent.movementX===0&&mouseEvent.movementY===0)){return;}
  7. if(this._anchorElement&&path.indexOf(this._anchorElement)===-1){this._hide(false);}
  8. for(const element of path){if(element===this._anchorElement){return;}
  9. if(!(element instanceof Element)||element.offsetParent===null){continue;}
  10. if(element[_symbol]){this._show(element,mouseEvent);return;}}}
  11. _show(anchorElement,event){const tooltip=anchorElement[_symbol];this._anchorElement=anchorElement;this._tooltipElement.removeChildren();for(const element of _nativeOverrideContainer){if(this._anchorElement.isSelfOrDescendant(element)){Object.defineProperty(this._anchorElement,'title',(_nativeTitle));this._anchorElement.title=tooltip.content;return;}}
  12. if(typeof tooltip.content==='string'){this._tooltipElement.setTextContentTruncatedIfNeeded(tooltip.content);}else{this._tooltipElement.appendChild(tooltip.content);}
  13. if(tooltip.actionId){const shortcuts=UI.shortcutRegistry.shortcutDescriptorsForAction(tooltip.actionId);for(const shortcut of shortcuts){const shortcutElement=this._tooltipElement.createChild('div','tooltip-shortcut');shortcutElement.textContent=shortcut.name;}}
  14. this._tooltipElement.classList.add('shown');this._tooltipElement.positionAt(0,0);const now=Date.now();const instant=(this._tooltipLastClosed&&now-this._tooltipLastClosed<Timing.InstantThreshold);this._tooltipElement.classList.toggle('instant',instant);this._tooltipLastOpened=instant?now:now+Timing.OpeningDelay;const container=UI.GlassPane.container((anchorElement.ownerDocument));const containerBox=container.boxInWindow(this.element.window());const anchorBox=this._anchorElement.boxInWindow(this.element.window());const anchorOffset=2;const pageMargin=2;const cursorOffset=10;this._tooltipElement.classList.toggle('tooltip-breakword',!this._tooltipElement.textContent.match('\\s'));this._tooltipElement.style.maxWidth=(containerBox.width-pageMargin*2)+'px';this._tooltipElement.style.maxHeight='';const tooltipWidth=this._tooltipElement.offsetWidth;const tooltipHeight=this._tooltipElement.offsetHeight;const anchorTooltipAtElement=this._anchorElement.nodeName==='BUTTON'||this._anchorElement.nodeName==='LABEL';let tooltipX=anchorTooltipAtElement?anchorBox.x:event.x+cursorOffset;tooltipX=Number.constrain(tooltipX,containerBox.x+pageMargin,containerBox.x+containerBox.width-tooltipWidth-pageMargin);let tooltipY;if(!anchorTooltipAtElement){tooltipY=event.y+cursorOffset+tooltipHeight<containerBox.y+containerBox.height?event.y+cursorOffset:event.y-tooltipHeight-1;}else{const onBottom=anchorBox.y+anchorOffset+anchorBox.height+tooltipHeight<containerBox.y+containerBox.height;tooltipY=onBottom?anchorBox.y+anchorBox.height+anchorOffset:anchorBox.y-tooltipHeight-anchorOffset;}
  15. this._tooltipElement.positionAt(tooltipX,tooltipY);}
  16. _hide(removeInstant){delete this._anchorElement;this._tooltipElement.classList.remove('shown');if(Date.now()>this._tooltipLastOpened){this._tooltipLastClosed=Date.now();}
  17. if(removeInstant){delete this._tooltipLastClosed;}}
  18. _reset(){this._hide(true);this._tooltipElement.positionAt(0,0);this._tooltipElement.style.maxWidth='0';this._tooltipElement.style.maxHeight='0';}}
  19. const Timing={'InstantThreshold':300,'OpeningDelay':600};const _symbol=Symbol('Tooltip');const _nativeOverrideContainer=[];const _nativeTitle=Object.getOwnPropertyDescriptor(HTMLElement.prototype,'title');Object.defineProperty(HTMLElement.prototype,'title',{get:function(){const tooltip=this[UI.Tooltip._symbol];return tooltip?tooltip.content:'';},set:function(x){Tooltip.install(this,x);}});self.UI=self.UI||{};UI=UI||{};UI.Tooltip=Tooltip;UI.Tooltip._symbol=_symbol;