UNPKG

diagram-js

Version:

A toolbox for displaying and modifying diagrams on the web

396 lines (293 loc) 7.57 kB
import { isString, assign, forEach } from 'min-dash'; import { assignStyle, domify, attr as domAttr, classes as domClasses, remove as domRemove, delegate as domDelegate } from 'min-dom'; import Ids from '../../util/IdGenerator'; /** * @typedef {import('../../core/Canvas').default} Canvas * @typedef {import('../../core/EventBus').default} EventBus * * @typedef {import('../../util/Types').RectTRBL} RectTRBL * * @typedef { { * html: string | HTMLElement; * position: RectTRBL; * show?: { * minZoom?: number; * maxZoom?: number; * }; * timeout?: number; * } } Tooltip */ // document wide unique tooltip ids var ids = new Ids('tt'); function createRoot(parentNode) { var root = domify( '<div class="djs-tooltip-container" />' ); assignStyle(root, { position: 'absolute', width: '0', height: '0' }); parentNode.insertBefore(root, parentNode.firstChild); return root; } function setPosition(el, x, y) { assignStyle(el, { left: x + 'px', top: y + 'px' }); } function setVisible(el, visible) { el.style.display = visible === false ? 'none' : ''; } var tooltipClass = 'djs-tooltip', tooltipSelector = '.' + tooltipClass; /** * A service that allows users to render tool tips on the diagram. * * The tooltip service will take care of updating the tooltip positioning * during navigation + zooming. * * @example * * ```javascript * * // add a pink badge on the top left of the shape * tooltips.add({ * position: { * x: 50, * y: 100 * }, * html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>' * }); * * // or with optional life span * tooltips.add({ * position: { * top: -5, * left: -5 * }, * html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>', * ttl: 2000 * }); * * // remove a tool tip * var id = tooltips.add(...); * * tooltips.remove(id); * ``` * * @param {EventBus} eventBus * @param {Canvas} canvas */ export default function Tooltips(eventBus, canvas) { this._eventBus = eventBus; this._canvas = canvas; this._ids = ids; this._tooltipDefaults = { show: { minZoom: 0.7, maxZoom: 5.0 } }; /** * @type {Record<string, Tooltip>} */ this._tooltips = {}; // root html element for all tooltips this._tooltipRoot = createRoot(canvas.getContainer()); var self = this; domDelegate.bind(this._tooltipRoot, tooltipSelector, 'mousedown', function(event) { event.stopPropagation(); }); domDelegate.bind(this._tooltipRoot, tooltipSelector, 'mouseover', function(event) { self.trigger('mouseover', event); }); domDelegate.bind(this._tooltipRoot, tooltipSelector, 'mouseout', function(event) { self.trigger('mouseout', event); }); this._init(); } Tooltips.$inject = [ 'eventBus', 'canvas' ]; /** * Adds an HTML tooltip to the diagram. * * @param {Tooltip} tooltip * * @return {string} ID of the tooltip. */ Tooltips.prototype.add = function(tooltip) { if (!tooltip.position) { throw new Error('must specifiy tooltip position'); } if (!tooltip.html) { throw new Error('must specifiy tooltip html'); } var id = this._ids.next(); tooltip = assign({}, this._tooltipDefaults, tooltip, { id: id }); this._addTooltip(tooltip); if (tooltip.timeout) { this.setTimeout(tooltip); } return id; }; /** * @param {string} action * @param {Event} event */ Tooltips.prototype.trigger = function(action, event) { var node = event.delegateTarget || event.target; var tooltip = this.get(domAttr(node, 'data-tooltip-id')); if (!tooltip) { return; } if (action === 'mouseover' && tooltip.timeout) { this.clearTimeout(tooltip); } if (action === 'mouseout' && tooltip.timeout) { // cut timeout after mouse out tooltip.timeout = 1000; this.setTimeout(tooltip); } }; /** * Get tooltip with given ID. * * @param {Tooltip|string} id * * @return {Tooltip|undefined} */ Tooltips.prototype.get = function(id) { if (typeof id !== 'string') { id = id.id; } return this._tooltips[id]; }; /** * @param {Tooltip} tooltip */ Tooltips.prototype.clearTimeout = function(tooltip) { tooltip = this.get(tooltip); if (!tooltip) { return; } var removeTimer = tooltip.removeTimer; if (removeTimer) { clearTimeout(removeTimer); tooltip.removeTimer = null; } }; /** * @param {Tooltip} tooltip */ Tooltips.prototype.setTimeout = function(tooltip) { tooltip = this.get(tooltip); if (!tooltip) { return; } this.clearTimeout(tooltip); var self = this; tooltip.removeTimer = setTimeout(function() { self.remove(tooltip); }, tooltip.timeout); }; /** * Remove tooltip with given ID. * * @param {string | Tooltip} id */ Tooltips.prototype.remove = function(id) { var tooltip = this.get(id); if (tooltip) { domRemove(tooltip.html); domRemove(tooltip.htmlContainer); delete tooltip.htmlContainer; delete this._tooltips[tooltip.id]; } }; Tooltips.prototype.show = function() { setVisible(this._tooltipRoot); }; Tooltips.prototype.hide = function() { setVisible(this._tooltipRoot, false); }; Tooltips.prototype._updateRoot = function(viewbox) { var a = viewbox.scale || 1; var d = viewbox.scale || 1; var matrix = 'matrix(' + a + ',0,0,' + d + ',' + (-1 * viewbox.x * a) + ',' + (-1 * viewbox.y * d) + ')'; this._tooltipRoot.style.transform = matrix; this._tooltipRoot.style['-ms-transform'] = matrix; }; Tooltips.prototype._addTooltip = function(tooltip) { var id = tooltip.id, html = tooltip.html, htmlContainer, tooltipRoot = this._tooltipRoot; // unwrap jquery (for those who need it) if (html.get && html.constructor.prototype.jquery) { html = html.get(0); } // create proper html elements from // tooltip HTML strings if (isString(html)) { html = domify(html); } htmlContainer = domify('<div data-tooltip-id="' + id + '" class="' + tooltipClass + '">'); assignStyle(htmlContainer, { position: 'absolute' }); htmlContainer.appendChild(html); if (tooltip.type) { domClasses(htmlContainer).add('djs-tooltip-' + tooltip.type); } if (tooltip.className) { domClasses(htmlContainer).add(tooltip.className); } tooltip.htmlContainer = htmlContainer; tooltipRoot.appendChild(htmlContainer); this._tooltips[id] = tooltip; this._updateTooltip(tooltip); }; Tooltips.prototype._updateTooltip = function(tooltip) { var position = tooltip.position, htmlContainer = tooltip.htmlContainer; // update overlay html based on tooltip x, y setPosition(htmlContainer, position.x, position.y); }; Tooltips.prototype._updateTooltipVisibilty = function(viewbox) { forEach(this._tooltips, function(tooltip) { var show = tooltip.show, htmlContainer = tooltip.htmlContainer, visible = true; if (show) { if (show.minZoom > viewbox.scale || show.maxZoom < viewbox.scale) { visible = false; } setVisible(htmlContainer, visible); } }); }; Tooltips.prototype._init = function() { var self = this; // scroll/zoom integration function updateViewbox(viewbox) { self._updateRoot(viewbox); self._updateTooltipVisibilty(viewbox); self.show(); } this._eventBus.on('canvas.viewbox.changing', function(event) { self.hide(); }); this._eventBus.on('canvas.viewbox.changed', function(event) { updateViewbox(event.viewbox); }); };