devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
552 lines (551 loc) • 22.2 kB
JavaScript
/**
* DevExtreme (esm/renovation/viz/common/tooltip.js)
* Version: 22.1.9
* Build date: Tue Apr 18 2023
*
* Copyright (c) 2012 - 2023 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
import _extends from "@babel/runtime/helpers/esm/extends";
var _excluded = ["argumentFormat", "arrowLength", "arrowWidth", "border", "className", "color", "container", "contentTemplate", "cornerRadius", "customizeTooltip", "data", "enabled", "eventData", "font", "format", "interactive", "location", "offset", "onTooltipHidden", "onTooltipShown", "opacity", "paddingLeftRight", "paddingTopBottom", "rootWidget", "rtl", "shadow", "shared", "visible", "x", "y", "zIndex"];
import {
createVNode,
createComponentVNode,
normalizeProps
} from "inferno";
import {
Portal,
InfernoEffect,
InfernoComponent
} from "@devextreme/runtime/inferno";
import {
normalizeStyles
} from "@devextreme/runtime/inferno";
import {
combineClasses
} from "../../utils/combine_classes";
import {
PathSvgElement
} from "./renderers/svg_path";
import {
TextSvgElement
} from "./renderers/svg_text";
import {
ShadowFilter
} from "./renderers/shadow_filter";
import {
getNextDefsSvgId,
getFuncIri
} from "./renderers/utils";
import {
RootSvgElement
} from "./renderers/svg_root";
import {
isDefined
} from "../../../core/utils/type";
import {
getCloudPoints,
recalculateCoordinates,
getCloudAngle,
prepareData,
getCanvas,
isTextEmpty
} from "./tooltip_utils";
import {
normalizeEnum
} from "../../../viz/core/utils";
import domAdapter from "../../../core/dom_adapter";
import {
isUpdatedFlatObject
} from "./utils";
var DEFAULT_CANVAS = {
left: 0,
top: 0,
right: 0,
bottom: 0,
width: 0,
height: 0
};
var DEFAULT_FONT = {
color: "#232323",
family: "Segoe UI",
opacity: 1,
size: 12,
weight: 400
};
var DEFAULT_SHADOW = {
blur: 2,
color: "#000",
offsetX: 0,
offsetY: 4,
opacity: .4
};
var DEFAULT_BORDER = {
color: "#d3d3d3",
width: 1,
dashStyle: "solid",
visible: true
};
var DEFAULT_SIZE = {
x: 0,
y: 0,
width: 0,
height: 0
};
export var viewFunction = _ref => {
var {
border: border,
cloudRef: cloudRef,
cloudSize: cloudSize,
container: container,
correctedCoordinates: correctedCoordinates,
cssClassName: cssClassName,
customizedOptions: customizedOptions,
filterId: filterId,
htmlRef: htmlRef,
isEmptyContainer: isEmptyContainer,
pointerEvents: pointerEvents,
props: {
arrowWidth: arrowWidth,
contentTemplate: TooltipTemplate,
cornerRadius: cornerRadius,
data: data,
font: font,
interactive: interactive,
opacity: opacity,
rtl: rtl,
shadow: shadow,
visible: visible,
zIndex: zIndex
},
textRef: textRef,
textSize: textSize,
textSizeWithPaddings: textSizeWithPaddings,
textSvgElementStyles: textSvgElementStyles
} = _ref;
if (!visible || !correctedCoordinates || isTextEmpty(customizedOptions) || isEmptyContainer) {
return createVNode(1, "div")
}
var angle = getCloudAngle(textSizeWithPaddings, correctedCoordinates);
var d = getCloudPoints(textSizeWithPaddings, correctedCoordinates, angle, {
cornerRadius: cornerRadius,
arrowWidth: arrowWidth
}, true);
var styles = interactive ? {
msUserSelect: "text",
MozUserSelect: "auto",
WebkitUserSelect: "auto"
} : {};
styles = _extends({}, styles, {
position: "absolute"
});
return createComponentVNode(2, Portal, {
container: container,
children: createVNode(1, "div", cssClassName, [createComponentVNode(2, RootSvgElement, {
width: cloudSize.width,
height: cloudSize.height,
styles: styles,
children: [createVNode(32, "defs", null, createComponentVNode(2, ShadowFilter, {
id: filterId,
x: "-50%",
y: "-50%",
width: "200%",
height: "200%",
blur: shadow.blur,
color: shadow.color,
offsetX: shadow.offsetX,
offsetY: shadow.offsetY,
opacity: shadow.opacity
}), 2), createVNode(32, "g", null, [createComponentVNode(2, PathSvgElement, {
pointerEvents: pointerEvents,
d: d,
fill: customizedOptions.color,
stroke: customizedOptions.borderColor,
strokeWidth: border.strokeWidth,
strokeOpacity: border.strokeOpacity,
dashStyle: border.dashStyle,
opacity: opacity,
rotate: angle,
rotateX: correctedCoordinates.x,
rotateY: correctedCoordinates.y
}), customizedOptions.html || TooltipTemplate ? null : createVNode(32, "g", null, createComponentVNode(2, TextSvgElement, {
text: customizedOptions.text,
styles: textSvgElementStyles
}), 2, {
"text-anchor": "middle",
transform: "translate(".concat(correctedCoordinates.x, ", ").concat(correctedCoordinates.y - textSize.height / 2 - textSize.y, ")")
}, null, textRef)], 0, {
filter: getFuncIri(filterId),
transform: "translate(".concat(-cloudSize.x, ", ").concat(-cloudSize.y, ")")
}, null, cloudRef)]
}), !(customizedOptions.html || TooltipTemplate) ? null : createVNode(1, "div", null, TooltipTemplate && TooltipTemplate(_extends({}, data)), 0, {
style: normalizeStyles({
position: "relative",
display: "inline-block",
left: correctedCoordinates.x - cloudSize.x - textSize.width / 2,
top: correctedCoordinates.y - cloudSize.y - textSize.height / 2,
fill: customizedOptions.fontColor,
fontFamily: font.family,
fontSize: font.size,
fontWeight: font.weight,
opacity: font.opacity,
pointerEvents: pointerEvents,
direction: rtl ? "rtl" : "ltr"
})
}, null, htmlRef)], 0, {
style: normalizeStyles({
position: "absolute",
pointerEvents: "none",
left: cloudSize.x,
top: cloudSize.y,
zIndex: zIndex
})
})
})
};
export var TooltipProps = {
color: "#fff",
border: DEFAULT_BORDER,
data: Object.freeze({}),
paddingLeftRight: 18,
paddingTopBottom: 15,
x: 0,
y: 0,
cornerRadius: 0,
arrowWidth: 20,
arrowLength: 10,
offset: 0,
font: DEFAULT_FONT,
shadow: DEFAULT_SHADOW,
interactive: false,
enabled: true,
shared: false,
location: "center",
visible: false,
rtl: false
};
import {
createRef as infernoCreateRef
} from "inferno";
var getTemplate = TemplateProp => TemplateProp && (TemplateProp.defaultProps ? props => normalizeProps(createComponentVNode(2, TemplateProp, _extends({}, props))) : TemplateProp);
export class Tooltip extends InfernoComponent {
constructor(props) {
super(props);
this.cloudRef = infernoCreateRef();
this.textRef = infernoCreateRef();
this.htmlRef = infernoCreateRef();
this.__getterCache = {};
this.state = {
filterId: getNextDefsSvgId(),
textSize: DEFAULT_SIZE,
cloudSize: DEFAULT_SIZE,
currentEventData: void 0,
isEmptyContainer: false,
canvas: DEFAULT_CANVAS
};
this.setHtmlText = this.setHtmlText.bind(this);
this.calculateSize = this.calculateSize.bind(this);
this.eventsEffect = this.eventsEffect.bind(this);
this.checkContainer = this.checkContainer.bind(this);
this.setCanvas = this.setCanvas.bind(this);
this.getLocation = this.getLocation.bind(this);
this.calculateContentSize = this.calculateContentSize.bind(this);
this.calculateCloudSize = this.calculateCloudSize.bind(this)
}
createEffects() {
var _this$props$rootWidge;
return [new InfernoEffect(this.setHtmlText, [this.props.border, this.props.color, this.props.customizeTooltip, this.props.data, this.props.font, this.props.visible]), new InfernoEffect(this.calculateSize, [this.props.visible, this.props.x, this.props.y, this.props.shadow, this.state.textSize, this.state.cloudSize]), new InfernoEffect(this.eventsEffect, [this.props.eventData, this.props.onTooltipHidden, this.props.onTooltipShown, this.props.visible, this.props.arrowLength, this.props.offset, this.props.x, this.props.y, this.state.canvas, this.props.paddingLeftRight, this.props.paddingTopBottom, this.state.textSize, this.state.currentEventData]), new InfernoEffect(this.checkContainer, [this.props.visible]), new InfernoEffect(this.setCanvas, [this.props.container, null === (_this$props$rootWidge = this.props.rootWidget) || void 0 === _this$props$rootWidge ? void 0 : _this$props$rootWidge.current])]
}
updateEffects() {
var _this$_effects$, _this$_effects$2, _this$_effects$3, _this$_effects$4, _this$_effects$5, _this$props$rootWidge2;
null === (_this$_effects$ = this._effects[0]) || void 0 === _this$_effects$ ? void 0 : _this$_effects$.update([this.props.border, this.props.color, this.props.customizeTooltip, this.props.data, this.props.font, this.props.visible]);
null === (_this$_effects$2 = this._effects[1]) || void 0 === _this$_effects$2 ? void 0 : _this$_effects$2.update([this.props.visible, this.props.x, this.props.y, this.props.shadow, this.state.textSize, this.state.cloudSize]);
null === (_this$_effects$3 = this._effects[2]) || void 0 === _this$_effects$3 ? void 0 : _this$_effects$3.update([this.props.eventData, this.props.onTooltipHidden, this.props.onTooltipShown, this.props.visible, this.props.arrowLength, this.props.offset, this.props.x, this.props.y, this.state.canvas, this.props.paddingLeftRight, this.props.paddingTopBottom, this.state.textSize, this.state.currentEventData]);
null === (_this$_effects$4 = this._effects[3]) || void 0 === _this$_effects$4 ? void 0 : _this$_effects$4.update([this.props.visible]);
null === (_this$_effects$5 = this._effects[4]) || void 0 === _this$_effects$5 ? void 0 : _this$_effects$5.update([this.props.container, null === (_this$props$rootWidge2 = this.props.rootWidget) || void 0 === _this$props$rootWidge2 ? void 0 : _this$props$rootWidge2.current])
}
setHtmlText() {
var htmlText = this.customizedOptions.html;
if (htmlText && this.htmlRef.current && this.props.visible) {
this.htmlRef.current.innerHTML = htmlText
}
}
calculateSize() {
var contentSize = this.calculateContentSize();
var cloudSize = this.calculateCloudSize();
if (isUpdatedFlatObject(this.state.textSize, contentSize)) {
this.setState(__state_argument => ({
textSize: contentSize
}))
}
if (isUpdatedFlatObject(this.state.cloudSize, cloudSize)) {
this.setState(__state_argument => ({
cloudSize: cloudSize
}))
}
}
eventsEffect() {
var {
eventData: eventData,
onTooltipHidden: onTooltipHidden,
onTooltipShown: onTooltipShown,
visible: visible
} = this.props;
if (visible && this.correctedCoordinates && !((object1, object2) => {
if (!object1) {
return false
}
return JSON.stringify(object1.target) === JSON.stringify(object2.target)
})(this.state.currentEventData, eventData)) {
this.state.currentEventData && (null === onTooltipHidden || void 0 === onTooltipHidden ? void 0 : onTooltipHidden(this.state.currentEventData));
null === onTooltipShown || void 0 === onTooltipShown ? void 0 : onTooltipShown(eventData);
this.setState(__state_argument => ({
currentEventData: eventData
}))
}
if (!visible && this.state.currentEventData) {
null === onTooltipHidden || void 0 === onTooltipHidden ? void 0 : onTooltipHidden(this.state.currentEventData);
this.setState(__state_argument => ({
currentEventData: void 0
}))
}
}
checkContainer() {
if (this.htmlRef.current && this.props.visible) {
var htmlTextSize = this.htmlRef.current.getBoundingClientRect();
if (!htmlTextSize.width && !htmlTextSize.height) {
this.setState(__state_argument => ({
isEmptyContainer: true
}))
}
}
}
setCanvas() {
this.setState(__state_argument => ({
canvas: getCanvas(this.container)
}))
}
get textSvgElementStyles() {
return _extends({}, this.fontStyles, {
pointerEvents: this.pointerEvents
})
}
get textSizeWithPaddings() {
var {
paddingLeftRight: paddingLeftRight,
paddingTopBottom: paddingTopBottom
} = this.props;
return {
width: this.state.textSize.width + 2 * paddingLeftRight,
height: this.state.textSize.height + 2 * paddingTopBottom
}
}
get border() {
if (void 0 !== this.__getterCache.border) {
return this.__getterCache.border
}
return this.__getterCache.border = (() => {
var {
border: border
} = this.props;
if (border.visible) {
return {
stroke: border.color,
strokeWidth: border.width,
strokeOpacity: border.opacity,
dashStyle: border.dashStyle
}
}
return {}
})()
}
get container() {
var propsContainer = this.props.container;
if (propsContainer) {
if ("string" === typeof propsContainer) {
var _this$props$rootWidge3;
var tmp = null === (_this$props$rootWidge3 = this.props.rootWidget) || void 0 === _this$props$rootWidge3 ? void 0 : _this$props$rootWidge3.current;
var node = null === tmp || void 0 === tmp ? void 0 : tmp.closest(propsContainer);
if (!node) {
node = domAdapter.getDocument().querySelector(propsContainer)
}
if (node) {
return node
}
} else {
return propsContainer
}
}
return domAdapter.getBody()
}
get customizedOptions() {
if (void 0 !== this.__getterCache.customizedOptions) {
return this.__getterCache.customizedOptions
}
return this.__getterCache.customizedOptions = (() => {
var {
border: border,
color: color,
customizeTooltip: customizeTooltip,
data: data,
font: font
} = this.props;
return prepareData(data, color, border, font, customizeTooltip)
})()
}
get margins() {
if (void 0 !== this.__getterCache.margins) {
return this.__getterCache.margins
}
return this.__getterCache.margins = (() => {
var {
max: max
} = Math;
var {
shadow: shadow
} = this.props;
var xOff = shadow.offsetX;
var yOff = shadow.offsetY;
var blur = 2 * shadow.blur + 1;
return {
lm: max(blur - xOff, 0),
rm: max(blur + xOff, 0),
tm: max(blur - yOff, 0),
bm: max(blur + yOff, 0)
}
})()
}
get pointerEvents() {
var {
interactive: interactive
} = this.props;
return interactive ? "auto" : "none"
}
get cssClassName() {
var {
className: className
} = this.props;
var classesMap = {
[String(className)]: !!className
};
return combineClasses(classesMap)
}
get fontStyles() {
var {
font: font
} = this.props;
var result = {};
void 0 !== font.family && (result.fontFamily = font.family);
void 0 !== font.size && (result.fontSize = String(font.size));
void 0 !== font.weight && (result.fontWeight = String(font.weight));
void 0 !== font.opacity && (result.opacity = String(font.opacity));
void 0 !== this.customizedOptions.fontColor && (result.fill = this.customizedOptions.fontColor);
return result
}
get correctedCoordinates() {
if (void 0 !== this.__getterCache.correctedCoordinates) {
return this.__getterCache.correctedCoordinates
}
return this.__getterCache.correctedCoordinates = (() => {
var {
arrowLength: arrowLength,
offset: offset,
x: x,
y: y
} = this.props;
return recalculateCoordinates({
canvas: this.state.canvas,
anchorX: x,
anchorY: y,
size: this.textSizeWithPaddings,
offset: offset,
arrowLength: arrowLength
})
})()
}
calculateContentSize() {
var size = DEFAULT_SIZE;
if (this.props.visible) {
if (this.textRef.current) {
size = this.textRef.current.getBBox()
} else if (this.htmlRef.current) {
size = this.htmlRef.current.getBoundingClientRect()
}
}
return size
}
calculateCloudSize() {
var cloudSize = DEFAULT_SIZE;
if (isDefined(this.props.x) && isDefined(this.props.y) && this.props.visible && this.cloudRef.current) {
var size = this.cloudRef.current.getBBox();
var {
bm: bm,
lm: lm,
rm: rm,
tm: tm
} = this.margins;
cloudSize = {
x: Math.floor(size.x - lm),
y: Math.floor(size.y - tm),
width: size.width + lm + rm,
height: size.height + tm + bm
}
}
return cloudSize
}
get restAttributes() {
var _this$props = this.props,
restProps = _objectWithoutPropertiesLoose(_this$props, _excluded);
return restProps
}
getLocation() {
return normalizeEnum(this.props.location)
}
componentWillUpdate(nextProps, nextState, context) {
super.componentWillUpdate();
if (this.props.border !== nextProps.border) {
this.__getterCache.border = void 0
}
if (this.props.border !== nextProps.border || this.props.color !== nextProps.color || this.props.customizeTooltip !== nextProps.customizeTooltip || this.props.data !== nextProps.data || this.props.font !== nextProps.font) {
this.__getterCache.customizedOptions = void 0
}
if (this.props.shadow !== nextProps.shadow) {
this.__getterCache.margins = void 0
}
if (this.props.arrowLength !== nextProps.arrowLength || this.props.offset !== nextProps.offset || this.props.x !== nextProps.x || this.props.y !== nextProps.y || this.state.canvas !== nextState.canvas || this.props.paddingLeftRight !== nextProps.paddingLeftRight || this.props.paddingTopBottom !== nextProps.paddingTopBottom || this.state.textSize !== nextState.textSize) {
this.__getterCache.correctedCoordinates = void 0
}
}
render() {
var props = this.props;
return viewFunction({
props: _extends({}, props, {
contentTemplate: getTemplate(props.contentTemplate)
}),
filterId: this.state.filterId,
textSize: this.state.textSize,
cloudSize: this.state.cloudSize,
currentEventData: this.state.currentEventData,
isEmptyContainer: this.state.isEmptyContainer,
canvas: this.state.canvas,
cloudRef: this.cloudRef,
textRef: this.textRef,
htmlRef: this.htmlRef,
textSvgElementStyles: this.textSvgElementStyles,
textSizeWithPaddings: this.textSizeWithPaddings,
border: this.border,
container: this.container,
customizedOptions: this.customizedOptions,
margins: this.margins,
pointerEvents: this.pointerEvents,
cssClassName: this.cssClassName,
fontStyles: this.fontStyles,
correctedCoordinates: this.correctedCoordinates,
calculateContentSize: this.calculateContentSize,
calculateCloudSize: this.calculateCloudSize,
restAttributes: this.restAttributes
})
}
}
Tooltip.defaultProps = TooltipProps;