@amcharts/amcharts4
Version:
amCharts 4
673 lines • 19.5 kB
JavaScript
/**
* A collection of DOM-related functions.
*/
import { __extends } from "tslib";
/**
* ============================================================================
* IMPORTS
* ============================================================================
* @hidden
*/
import { Disposer } from "./Disposer";
import { readFrame, writeFrame } from "./AsyncPending";
import { options } from "../Options";
import * as $object from "./Object";
import * as $array from "./Array";
import * as $type from "./Type";
/**
* SVG namespace.
*
* @ignore Exclude from docs
*/
export var SVGNS = "http://www.w3.org/2000/svg";
/**
* XML namespace.
*
* @ignore Exclude from docs
*/
export var XMLNS = "http://www.w3.org/2000/xmlns/";
/**
* XLINK namespace.
*
* @ignore Exclude from docs
*/
export var XLINK = "http://www.w3.org/1999/xlink";
/**
* Function that adds a disposable event listener directly to a DOM element.
*
* @ignore Exclude from docs
* @param dom A DOM element to add event to
* @param type Event type
* @param listener Event listener
* @returns Disposable event
*/
export function addEventListener(dom, type, listener, options) {
//@todo proper type check for options: EventListenerOptions | boolean (TS for some reason gives error on passive parameter)
//console.log(type, dom);
dom.addEventListener(type, listener, options || false);
return new Disposer(function () {
dom.removeEventListener(type, listener, options || false);
});
}
/**
* Finds and returns an element reference using following logic:
* * If we pass in an element instance, we just return it back.
* * If we pass in a string, the function looks for an element with such id.
* * If no element with such id is found, we grab the first element with a tag name like this.
*
* @ignore Exclude from docs
* @param el Element definition (reference, or id, or tag name)
* @return Element reference
* @todo Review this function as it seems pretty fuzzy and hacky
*/
export function getElement(el) {
if ($type.isString(el)) {
var e = document.getElementById(el);
if (e == null) {
e = document.getElementsByClassName(el)[0];
}
if (e instanceof HTMLElement) {
return e;
}
}
else if (el instanceof HTMLElement) {
return el;
}
}
/**
* Adds a class name to an HTML or SVG element.
*
* @ignore Exclude from docs
* @param element Element
* @param className Class name to add
*/
export function addClass(element, className) {
if (!element) {
return;
}
if (element.classList) {
var classes = className.split(" ");
$array.each(classes, function (name) {
element.classList.add(name);
});
}
else {
var currentClassName = element.getAttribute("class");
if (currentClassName) {
element.setAttribute("class", currentClassName.split(" ").filter(function (item) {
return item !== className;
}).join(" ") + " " + className);
}
else {
element.setAttribute("class", className);
}
//element.className = element.className.replace(new RegExp("^" + className + "| " + className), "") + " " + className;
}
}
/**
* Removes a class name from an HTML or SVG element.
*
* @ignore Exclude from docs
* @param element Element
* @param className Class name to add
*/
export function removeClass(element, className) {
if (!element) {
return;
}
if (element.classList) {
element.classList.remove(className);
}
else {
var currentClassName = element.getAttribute("class");
if (currentClassName) {
element.setAttribute("class", currentClassName.split(" ").filter(function (item) {
return item !== className;
}).join(" "));
}
//element.className = element.className.replace(new RegExp("^" + className + "| " + className), "");
}
}
/**
* Sets style property on DOM element.
*
* @ignore Exclude from docs
* @todo Still needed?
*/
export function setStyle(element, property, value) {
element.style[property] = value;
}
/**
* Gets the computed style value for an element.
*
* @ignore Exclude from docs
*/
export function getComputedStyle(element, property) {
if (element.currentStyle) {
return element.currentStyle[property];
}
return document.defaultView.getComputedStyle(element, null).getPropertyValue(property);
}
/**
* Removes focus from any element by shifting focus to body.
*
* @ignore Exclude from docs
*/
export function blur() {
if (document.activeElement && document.activeElement != document.body) {
if (document.activeElement.blur) {
document.activeElement.blur();
}
else {
var input = document.createElement("button");
input.style.position = "fixed";
input.style.top = "0px";
input.style.left = "-10000px";
document.body.appendChild(input);
input.focus();
input.blur();
document.body.removeChild(input);
}
}
}
/**
* Tries to focus the element.
*
* @ignore Exlude from docs
* @param element Element to focus
*/
export function focus(element) {
if (element instanceof HTMLElement) {
element.focus();
}
else {
var input = document.createElement("input");
var fo = document.createElementNS(SVGNS, "foreignObject");
fo.appendChild(input);
element.appendChild(fo);
input.focus();
input.disabled = true;
fo.remove();
}
/*if ((<any>element).focus != undefined) {
(<any>element).focus();
}
else if (element instanceof SVGSVGElement) {
// Not implemented
// @todo implement focus fallback
}*/
}
/**
* Returns markup for the element including the element tag itself.
* SVG elements do not support `outerHTML` so this functions applies of
* a workaround which creates a new temporary wrapper, clones element and uses
* wrapper's `innerHTML`.
*
* @ignore Exclude from docs
* @param element Element to get full markup for
* @return Markup
* @deprecated Not in use anywhere
*/
export function outerHTML(element) {
if (element.outerHTML) {
return element.outerHTML;
}
else {
var twrap = document.createElement("div");
var tnode = element.cloneNode(true);
twrap.appendChild(tnode);
var content = twrap.innerHTML;
return content;
}
}
/**
* Checks if element is a valid DOM node.
*
* @ignore Exclude from docs
* @param el Element
* @return `true` if element is a valid DOM node
*/
export function isElement(el) {
return el instanceof Object && el && el.nodeType === 1;
}
/**
* Checks of element `a` contains element `b`.
*
* @param a Aleged ascendant
* @param b Aleged descendant
* @return Contains?
*/
export function contains(a, b) {
var cursor = b;
while (true) {
if (a === cursor) {
return true;
}
else if (cursor.parentNode == null) {
// TODO better ShadowRoot detection
if (cursor.host == null) {
return false;
}
else {
cursor = cursor.host;
}
}
else {
cursor = cursor.parentNode;
}
}
}
/**
* Returns the shadow root of the element or null
*
* @param a Node
* @return Root
*/
export function getShadowRoot(a) {
var cursor = a;
while (true) {
if (cursor.parentNode == null) {
// TODO better ShadowRoot detection
if (cursor.host != null) {
return cursor;
}
else {
return null;
}
}
else {
cursor = cursor.parentNode;
}
}
}
/**
* Returns the root of the element (either the Document or the ShadowRoot)
*
* @param a Node
* @return Root
*/
export function getRoot(a) {
// TODO replace with Node.prototype.getRootNode
var owner = a.ownerDocument;
var cursor = a;
while (true) {
if (cursor.parentNode == null) {
// If the cursor is the document, or it is a ShadowRoot
// TODO better ShadowRoot detection
if (cursor === owner || cursor.host != null) {
return cursor;
}
else {
return null;
}
}
else {
cursor = cursor.parentNode;
}
}
}
/**
* Gets the true target of the Event.
*
* This is needed to make events work with the shadow DOM.
*
* @param event Event
* @return EventTarget
*/
export function eventTarget(event) {
if (typeof event.composedPath === "function") {
return event.composedPath()[0];
}
else {
return event.target;
}
}
/**
* Copies attributes from one element to another.
*
* @ignore Exclude from docs
* @param source Element to copy attributes from
* @param target Element to copy attributes to
*/
export function copyAttributes(source, target) {
$array.each(source.attributes, function (attr) {
// TODO what if it's null ?
if (attr.value != null) {
target.setAttribute(attr.name, attr.value);
}
});
}
/**
* [fixPixelPerfect description]
*
* @ignore Exclude from docs
* @todo Description
* @param el Element
*/
export function fixPixelPerfect(el) {
readFrame(function () {
// sometimes IE doesn't like this
// TODO figure out a way to remove this
try {
var rect = el.getBoundingClientRect();
var left_1 = rect.left - Math.round(rect.left);
var top_1 = rect.top - Math.round(rect.top);
if (left_1 !== 0) {
writeFrame(function () {
el.style.left = left_1 + "px";
});
}
if (top_1 !== 0) {
writeFrame(function () {
el.style.top = top_1 + "px";
});
}
}
catch (e) { }
});
}
/**
* [rootStylesheet description]
*
* @ignore Exclude from docs
* @todo Description
*/
var rootStylesheet;
/**
* [getStylesheet description]
*
* @ignore Exclude from docs
* @todo Description
* @return [description]
*/
function getStylesheet(element) {
if (element == null) {
if (!$type.hasValue(rootStylesheet)) {
// TODO use createElementNS ?
var e = document.createElement("style");
e.type = "text/css";
if (options.nonce != "") {
e.setAttribute("nonce", options.nonce);
}
document.head.appendChild(e);
rootStylesheet = e.sheet;
}
return rootStylesheet;
}
else {
// TODO use createElementNS ?
var e = document.createElement("style");
e.type = "text/css";
if (options.nonce != "") {
e.setAttribute("nonce", options.nonce);
}
element.appendChild(e);
return e.sheet;
}
}
/**
* [makeStylesheet description]
*
* @ignore Exclude from docs
* @todo Description
* @param selector [description]
* @return [description]
*/
function appendStylesheet(root, selector) {
var index = root.cssRules.length;
root.insertRule(selector + "{}", index);
return root.cssRules[index];
}
/**
* Defines a class for a CSS rule.
*
* Can be used to dynamically add CSS to the document.
*/
var StyleRule = /** @class */ (function (_super) {
__extends(StyleRule, _super);
/**
* Constructor.
*
* @param selector CSS selector
* @param styles An object of style attribute - value pairs
*/
function StyleRule(element, selector, styles) {
var _this = this;
var root = getStylesheet(element);
// TODO test this
_this = _super.call(this, function () {
// TODO a bit hacky
var index = $array.indexOf(root.cssRules, _this._rule);
if (index === -1) {
throw new Error("Could not dispose StyleRule");
}
else {
// TODO if it's empty remove it from the DOM ?
root.deleteRule(index);
}
}) || this;
_this._rule = appendStylesheet(root, selector);
$object.each(styles, function (key, value) {
_this.setStyle(key, value);
});
return _this;
}
Object.defineProperty(StyleRule.prototype, "selector", {
/**
* @return CSS selector
*/
get: function () {
return this._rule.selectorText;
},
/**
* A CSS selector text.
*
* E.g.: `.myClass p`
*
* @param selector CSS selector
*/
set: function (selector) {
this._rule.selectorText = selector;
},
enumerable: true,
configurable: true
});
/**
* Sets the same style properties with browser-specific prefixes.
*
* @param name Attribute name
* @param value Attribute value
*/
StyleRule.prototype._setVendorPrefixName = function (name, value) {
var style = this._rule.style;
style.setProperty("-webkit-" + name, value, "");
style.setProperty("-moz-" + name, value, "");
style.setProperty("-ms-" + name, value, "");
style.setProperty("-o-" + name, value, "");
style.setProperty(name, value, "");
};
/**
* Sets a value for specific style attribute.
*
* @param name Attribute
* @param value Value
*/
StyleRule.prototype.setStyle = function (name, value) {
if (name === "transition") {
this._setVendorPrefixName(name, value);
}
else {
this._rule.style.setProperty(name, value, "");
}
};
return StyleRule;
}(Disposer));
export { StyleRule };
/**
* An internal counter for unique style ids.
*
* @ignore Exclude from docs
*/
var styleId = 0;
/**
* @ignore Exclude from docs
* @todo Description
*/
var StyleClass = /** @class */ (function (_super) {
__extends(StyleClass, _super);
/**
* Constructor.
*
* @param styles An object of style attribute - value pairs
* @param name Class name
*/
function StyleClass(element, styles, name) {
var _this = this;
var className = (!$type.hasValue(name)
// TODO generate the classname randomly
? "__style_" + (++styleId) + "__"
: name);
_this = _super.call(this, element, "." + className, styles) || this;
_this._className = className;
return _this;
}
Object.defineProperty(StyleClass.prototype, "className", {
/**
* @return Class name
*/
get: function () {
return this._className;
},
/**
* Class name.
*
* @param name Class name
*/
set: function (name) {
this._className = name;
this.selector = "." + name;
},
enumerable: true,
configurable: true
});
/**
* Converts the whole class to
* @ignore Exclude from docs
*/
StyleClass.prototype.toString = function () {
return this._className;
};
return StyleClass;
}(StyleRule));
export { StyleClass };
export function ready(f) {
if (document.readyState !== "loading") {
f();
}
else {
var listener_1 = function () {
if (document.readyState !== "loading") {
document.removeEventListener("readystatechange", listener_1);
f();
}
};
document.addEventListener("readystatechange", listener_1);
}
}
/**
* Returns a font fmaily name for the element (directly set or
* computed/inherited).
*
* @ignore Exclude from docs
* @param element Element
* @return Font family
*/
export function findFont(element) {
// Check if element has styles set
var font = getComputedStyle(element, "font-family");
if (!font) {
// Completely transparent. Look for a parent
var parent_1 = element.parentElement || element.parentNode;
if (parent_1) {
return findFont(parent_1);
}
else {
return undefined;
}
}
else {
return font;
}
}
/**
* Returns a font fmaily name for the element (directly set or
* computed/inherited).
*
* @ignore Exclude from docs
* @param element Element
* @return Font family
*/
export function findFontSize(element) {
// Check if element has styles set
var font = getComputedStyle(element, "font-size");
if (!font) {
// Completely transparent. Look for a parent
var parent_2 = element.parentElement || element.parentNode;
if (parent_2) {
return findFontSize(parent_2);
}
else {
return undefined;
}
}
else {
return font;
}
}
/**
* Checks whether element is not visible, whether directly or via its
* ascendants.
*
* @param element Target element
* @return Hidden?
*/
export function isHidden(element) {
return (element.offsetParent === null);
}
/**
* Checks wthether element is in the current viewport.
*
* @since 2.5.5
* @param el Element
* @return Within viewport?
*/
export function isElementInViewport(el, viewportTarget) {
// Get position data of the element
var rect = el.getBoundingClientRect();
// Convert to array
var targets = $type.isArray(viewportTarget) ? viewportTarget : viewportTarget ? [viewportTarget] : [];
// Should we measure against specific viewport element?
if (targets.length) {
for (var i = 0; i < targets.length; i++) {
var target = targets[i];
// Check if viewport itself is visible
if (!isElementInViewport(target)) {
return false;
}
// Check if element is visible within the viewport
var viewportRect = target.getBoundingClientRect();
if (rect.top >= 0 &&
rect.left >= 0 &&
rect.top <= (viewportRect.top + viewportRect.height) &&
rect.left <= (viewportRect.left + viewportRect.width)) {
return true;
}
}
return false;
}
return (rect.top >= 0 &&
rect.left >= 0 &&
rect.top <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.left <= (window.innerWidth || document.documentElement.clientWidth));
}
//# sourceMappingURL=DOM.js.map