diff --git a/packages/core/src/view/handler/TooltipHandler.ts b/packages/core/src/view/handler/TooltipHandler.ts index 47ec21923a..5c6fff1f70 100644 --- a/packages/core/src/view/handler/TooltipHandler.ts +++ b/packages/core/src/view/handler/TooltipHandler.ts @@ -41,6 +41,38 @@ import EventSource from '../event/EventSource'; class TooltipHandler implements GraphPlugin { static pluginId = 'TooltipHandler'; + /** + * Creates the tooltip element and appends it to the document body. + * + */ + init() { + if (document.body) { + this.div = document.createElement('div'); + this.div.className = 'mxTooltip'; + this.div.style.visibility = 'hidden'; + document.body.appendChild(this.div); + + InternalEvent.addGestureListeners(this.div, (evt) => { + const source = getSource(evt); + // @ts-ignore nodeName may exist + if (source && source.nodeName !== 'A') { + this.hideTooltip(); + } + }); + + // Hides tooltips and resets tooltip timer if mouse leaves container + InternalEvent.addListener( + this.graph.getContainer(), + 'mouseleave', + (evt: MouseEvent) => { + if (this.div !== evt.relatedTarget) { + this.hide(); + } + } + ); + } + } + /** * Constructs an event handler that displays tooltips. * @@ -49,35 +81,9 @@ class TooltipHandler implements GraphPlugin { constructor(graph: Graph) { this.graph = graph; this.graph.addMouseListener(this); - - this.div = document.createElement('div'); - this.div.className = 'mxTooltip'; - this.div.style.visibility = 'hidden'; - - document.body.appendChild(this.div); - - InternalEvent.addGestureListeners(this.div, (evt) => { - const source = getSource(evt); - - // @ts-ignore nodeName may exist - if (source && source.nodeName !== 'A') { - this.hideTooltip(); - } - }); - - // Hides tooltips and resets tooltip timer if mouse leaves container - InternalEvent.addListener( - this.graph.getContainer(), - 'mouseleave', - (evt: MouseEvent) => { - if (this.div !== evt.relatedTarget) { - this.hide(); - } - } - ); } - div: HTMLElement; + div?: HTMLElement | null; /** * Specifies the zIndex for the tooltip and its shadow. @@ -231,7 +237,7 @@ class TooltipHandler implements GraphPlugin { restart && this.isEnabled() && state && - this.div.style.visibility === 'hidden' + (!this.div || this.div.style.visibility == 'hidden') ) { const node = me.getSource(); const x = me.getX(); @@ -280,8 +286,10 @@ class TooltipHandler implements GraphPlugin { * Hides the tooltip. */ hideTooltip() { - this.div.style.visibility = 'hidden'; - this.div.innerHTML = ''; + if (this.div) { + this.div.style.visibility = 'hidden'; + this.div.innerHTML = ''; + } } /** @@ -291,20 +299,22 @@ class TooltipHandler implements GraphPlugin { show(tip: HTMLElement | string | null, x: number, y: number) { if (!this.destroyed && tip && tip !== '') { const origin = getScrollOrigin(); - - this.div.style.zIndex = String(this.zIndex); - this.div.style.left = `${x + origin.x}px`; - this.div.style.top = `${y + TOOLTIP_VERTICAL_OFFSET + origin.y}px`; + if (!this.div) { + this.init(); + } + this.div!.style.zIndex = String(this.zIndex); + this.div!.style.left = `${x + origin.x}px`; + this.div!.style.top = `${y + TOOLTIP_VERTICAL_OFFSET + origin.y}px`; if (!isNode(tip)) { - this.div.innerHTML = (tip as string).replace(/\n/g, '
'); + this.div!.innerHTML = (tip as string).replace(/\n/g, '
'); } else { - this.div.innerHTML = ''; - this.div.appendChild(tip as HTMLElement); + this.div!.innerHTML = ''; + this.div!.appendChild(tip as HTMLElement); } - this.div.style.visibility = ''; - fit(this.div); + this.div!.style.visibility = ''; + fit(this.div!); } } @@ -313,14 +323,18 @@ class TooltipHandler implements GraphPlugin { */ onDestroy() { if (!this.destroyed) { + this.resetTimer(); this.graph.removeMouseListener(this); - InternalEvent.release(this.div); + if (this.div) { + InternalEvent.release(this.div); + } - if (this.div.parentNode) { + if (this.div?.parentNode) { this.div.parentNode.removeChild(this.div); } this.destroyed = true; + this.div = null; } } } diff --git a/packages/html/.storybook/preview.ts b/packages/html/.storybook/preview.ts index 7265443ea2..f28b6cb4c7 100644 --- a/packages/html/.storybook/preview.ts +++ b/packages/html/.storybook/preview.ts @@ -27,7 +27,9 @@ const resetMaxGraphConfigs = (): void => { // This function is a workaround to destroy mxGraph elements that are not released by the previous story. // See https://github.com/maxGraph/maxGraph/issues/400 function destroyUnreleasedElements() { - document.querySelectorAll('.mxPopupMenu,.mxWindow').forEach((e) => e.remove()); + document + .querySelectorAll('.mxPopupMenu,.mxTooltip,.mxWindow') + .forEach((e) => e.remove()); } const preview: Preview = {