UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

483 lines (480 loc) • 17.2 kB
/** * DevExtreme (esm/viz/core/tooltip.js) * Version: 21.1.4 * Build date: Mon Jun 21 2021 * * Copyright (c) 2012 - 2021 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ import domAdapter from "../../core/dom_adapter"; import { getWindow } from "../../core/utils/window"; import { camelize } from "../../core/utils/inflector"; import $ from "../../core/renderer"; import { Renderer } from "./renderers/renderer"; import { isFunction, isPlainObject, isDefined } from "../../core/utils/type"; import { extend } from "../../core/utils/extend"; import { patchFontOptions, normalizeEnum } from "./utils"; import formatHelper from "../../format_helper"; import { Plaque } from "./plaque"; var format = formatHelper.format; var mathCeil = Math.ceil; var mathMax = Math.max; var mathMin = Math.min; var window = getWindow(); var DEFAULT_HTML_GROUP_WIDTH = 3e3; function hideElement($element) { $element.css({ left: "-9999px" }).detach() } function getSpecialFormatOptions(options, specialFormat) { var result = options; switch (specialFormat) { case "argument": result = { format: options.argumentFormat }; break; case "percent": result = { format: { type: "percent", precision: options.format && options.format.percentPrecision } } } return result } export var Tooltip = function(params) { var renderer; this._eventTrigger = params.eventTrigger; this._widgetRoot = params.widgetRoot; this._widget = params.widget; this._wrapper = $("<div>").css({ position: "absolute", overflow: "hidden", pointerEvents: "none" }).addClass(params.cssClass); this._renderer = renderer = new Renderer({ pathModified: params.pathModified, container: this._wrapper[0] }); var root = renderer.root; root.attr({ "pointer-events": "none" }); this._text = renderer.text(void 0, 0, 0); this._textGroupHtml = $("<div>").css({ position: "absolute", padding: 0, margin: 0, border: "0px solid transparent" }).appendTo(this._wrapper); this._textHtml = $("<div>").css({ position: "relative", display: "inline-block", padding: 0, margin: 0, border: "0px solid transparent" }).appendTo(this._textGroupHtml) }; Tooltip.prototype = { constructor: Tooltip, dispose: function() { this._wrapper.remove(); this._renderer.dispose(); this._options = this._widgetRoot = null }, _getContainer: function() { var options = this._options; var container = $(this._widgetRoot).closest(options.container); if (0 === container.length) { container = $(options.container) } return (container.length ? container : $("body")).get(0) }, setTemplate(contentTemplate) { this._template = contentTemplate ? this._widget._getTemplate(contentTemplate) : null }, setOptions: function(options) { options = options || {}; var that = this; that._options = options; that._textFontStyles = patchFontOptions(options.font); that._textFontStyles.color = that._textFontStyles.fill; that._wrapper.css({ zIndex: options.zIndex }); that._customizeTooltip = options.customizeTooltip; var textGroupHtml = that._textGroupHtml; var textHtml = that._textHtml; if (this.plaque) { this.plaque.clear() } this.setTemplate(options.contentTemplate); var pointerEvents = options.interactive ? "auto" : "none"; if (options.interactive) { this._renderer.root.css({ "-ms-user-select": "auto", "-moz-user-select": "auto", "-webkit-user-select": "auto" }) } this.plaque = new Plaque({ opacity: that._options.opacity, color: that._options.color, border: that._options.border, paddingLeftRight: that._options.paddingLeftRight, paddingTopBottom: that._options.paddingTopBottom, arrowLength: that._options.arrowLength, arrowWidth: 20, shadow: that._options.shadow, cornerRadius: that._options.cornerRadius }, that, that._renderer.root, _ref => { var { group: group, onRender: onRender, eventData: eventData, isMoving: isMoving, templateCallback: templateCallback = (() => {}) } = _ref; var state = that._state; if (!isMoving) { var template = that._template; var useTemplate = template && !state.formatObject.skipTemplate; if (state.html || useTemplate) { textGroupHtml.css({ color: state.textColor, width: DEFAULT_HTML_GROUP_WIDTH, pointerEvents: pointerEvents }); if (useTemplate) { template.render({ model: state.formatObject, container: textHtml, onRendered: () => { state.html = textHtml.html(); if (0 === textHtml.width() && 0 === textHtml.height()) { this.plaque.clear(); templateCallback(false); return } onRender(); that._riseEvents(eventData); that._moveWrapper(); that.plaque.customizeCloud({ fill: state.color, stroke: state.borderColor, "pointer-events": pointerEvents }); templateCallback(true) } }); return } else { that._text.attr({ text: "" }); textHtml.html(state.html) } } else { that._text.css({ fill: state.textColor }).attr({ text: state.text, class: options.cssClass, "pointer-events": pointerEvents }).append(group.attr({ align: options.textAlignment })) } that._riseEvents(eventData); that.plaque.customizeCloud({ fill: state.color, stroke: state.borderColor, "pointer-events": pointerEvents }) } onRender(); that._moveWrapper(); return true }, true, (tooltip, g) => { var state = tooltip._state; if (state.html) { var bBox; var getComputedStyle = window.getComputedStyle; if (getComputedStyle) { bBox = getComputedStyle(textHtml.get(0)); bBox = { x: 0, y: 0, width: mathCeil(parseFloat(bBox.width)), height: mathCeil(parseFloat(bBox.height)) } } else { bBox = textHtml.get(0).getBoundingClientRect(); bBox = { x: 0, y: 0, width: mathCeil(bBox.width ? bBox.width : bBox.right - bBox.left), height: mathCeil(bBox.height ? bBox.height : bBox.bottom - bBox.top) } } return bBox } return g.getBBox() }, (tooltip, g, x, y) => { var state = tooltip._state; if (state.html) { that._textGroupHtml.css({ left: x, top: y }) } else { g.move(x, y) } }); return that }, _riseEvents: function(eventData) { this._eventData && this._eventTrigger("tooltipHidden", this._eventData); this._eventData = eventData; this._eventTrigger("tooltipShown", this._eventData) }, setRendererOptions: function(options) { this._renderer.setOptions(options); this._textGroupHtml.css({ direction: options.rtl ? "rtl" : "ltr" }); return this }, update: function(options) { this.setOptions(options); hideElement(this._wrapper); var normalizedCSS = {}; for (var name in this._textFontStyles) { normalizedCSS[camelize(name)] = this._textFontStyles[name] } this._textGroupHtml.css(normalizedCSS); this._text.css(this._textFontStyles); this._eventData = null; return this }, _prepare: function(formatObject, state) { var customizeTooltip = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : this._customizeTooltip; var options = this._options; var customize = {}; if (isFunction(customizeTooltip)) { customize = customizeTooltip.call(formatObject, formatObject); customize = isPlainObject(customize) ? customize : {}; if ("text" in customize) { state.text = isDefined(customize.text) ? String(customize.text) : "" } if ("html" in customize) { state.html = isDefined(customize.html) ? String(customize.html) : "" } } if (!("text" in state) && !("html" in state)) { state.text = formatObject.valueText || formatObject.description || "" } state.color = customize.color || options.color; state.borderColor = customize.borderColor || (options.border || {}).color; state.textColor = customize.fontColor || (this._textFontStyles || {}).color; return !!state.text || !!state.html || !!this._template }, show: function(formatObject, params, eventData, customizeTooltip, templateCallback) { if (this._options.forceEvents) { eventData.x = params.x; eventData.y = params.y - params.offset; this._riseEvents(eventData); return true } var state = { formatObject: formatObject, eventData: eventData, templateCallback: templateCallback }; if (!this._prepare(formatObject, state, customizeTooltip)) { return false } this._state = state; this._wrapper.appendTo(this._getContainer()); this._clear(); var parameters = extend({}, this._options, { canvas: this._getCanvas() }, state, { x: params.x, y: params.y, offset: params.offset }); return this.plaque.clear().draw(parameters) }, isCursorOnTooltip: function(x, y) { if (this._options.interactive && this.isEnabled()) { var box = this.plaque.getBBox(); return x > box.x && x < box.x + box.width && y > box.y && y < box.y + box.height } return false }, hide: function() { hideElement(this._wrapper); if (this._eventData) { this._eventTrigger("tooltipHidden", this._eventData); this._clear(); this._eventData = null } }, _clear() { this._textHtml.empty() }, move: function(x, y, offset) { this.plaque.draw({ x: x, y: y, offset: offset, canvas: this._getCanvas(), isMoving: true }) }, _moveWrapper: function() { var plaqueBBox = this.plaque.getBBox(); this._renderer.resize(plaqueBBox.width, plaqueBBox.height); var offset = this._wrapper.css({ left: 0, top: 0 }).offset(); var left = plaqueBBox.x; var top = plaqueBBox.y; this._wrapper.css({ left: left - offset.left, top: top - offset.top }); this.plaque.moveRoot(-left, -top); if (this._state.html) { this._textHtml.css({ left: -left, top: -top }); this._textGroupHtml.css({ width: mathCeil(this._textHtml.width()) }) } }, formatValue: function(value, _specialFormat) { var options = _specialFormat ? getSpecialFormatOptions(this._options, _specialFormat) : this._options; return format(value, options.format) }, getLocation: function() { return normalizeEnum(this._options.location) }, isEnabled: function() { return !!this._options.enabled || !!this._options.forceEvents }, isShared: function() { return !!this._options.shared }, _getCanvas: function() { var container = this._getContainer(); var containerBox = container.getBoundingClientRect(); var html = domAdapter.getDocumentElement(); var document = domAdapter.getDocument(); var left = window.pageXOffset || html.scrollLeft || 0; var top = window.pageYOffset || html.scrollTop || 0; var box = { left: left, top: top, width: mathMax(html.clientWidth, document.body.clientWidth) + left, height: mathMax(document.body.scrollHeight, html.scrollHeight, document.body.offsetHeight, html.offsetHeight, document.body.clientHeight, html.clientHeight), right: 0, bottom: 0 }; if (container !== domAdapter.getBody()) { left = mathMax(box.left, box.left + containerBox.left); top = mathMax(box.top, box.top + containerBox.top); box.width = mathMin(containerBox.width, box.width) + left + box.left; box.height = mathMin(containerBox.height, box.height) + top + box.top; box.left = left; box.top = top } return box } }; export var plugin = { name: "tooltip", init: function() { this._initTooltip() }, dispose: function() { this._disposeTooltip() }, members: { _initTooltip: function() { this._tooltip = new Tooltip({ cssClass: this._rootClassPrefix + "-tooltip", eventTrigger: this._eventTrigger, pathModified: this.option("pathModified"), widgetRoot: this.element(), widget: this }) }, _disposeTooltip: function() { this._tooltip.dispose(); this._tooltip = null }, _setTooltipRendererOptions: function() { this._tooltip.setRendererOptions(this._getRendererOptions()) }, _setTooltipOptions: function() { this._tooltip.update(this._getOption("tooltip")) } }, extenders: { _stopCurrentHandling() { this._tooltip && this._tooltip.hide() } }, customize: function(constructor) { var proto = constructor.prototype; proto._eventsMap.onTooltipShown = { name: "tooltipShown" }; proto._eventsMap.onTooltipHidden = { name: "tooltipHidden" }; constructor.addChange({ code: "TOOLTIP_RENDERER", handler: function() { this._setTooltipRendererOptions() }, isThemeDependent: true, isOptionChange: true }); constructor.addChange({ code: "TOOLTIP", handler: function() { this._setTooltipOptions() }, isThemeDependent: true, isOptionChange: true, option: "tooltip" }) }, fontFields: ["tooltip.font"] };