@duetds/components
Version:
This package includes Duet Core Components and related tools.
325 lines (324 loc) • 13.5 kB
JavaScript
import { Host, h } from "@stencil/core";
import { createID } from "@duetds/js-utils";
export class DuetTooltip {
constructor() {
this.tooltipId = createID("DuetTooltip");
/**
* Own Properties
*/
this.tooltip = null;
this.scrollable = null;
this.lastScrollTop = 0;
this.timer = null;
/**
* State() variables
* Inlined decorator, alphabetical order.
*/
this.openDown = false;
this.visible = true;
/**
* Theme of the tooltip. Can be one of: "default", "turva".
*/
this.theme = "";
/**
* Adds accessible label for the info icon that is only shown for screen readers. This property is always required to create an accessibly interface!
*/
this.accessibleLabel = "Näytä lisätietoja";
/**
* Label of the tooltip. If used, replaces the accessibleLabel option.
*/
this.label = "";
/**
* Adjust the vertical positioning of the tooltip toggle in pixels. Can be a negative value as well. E.g. "-13px".
*/
this.positionVertical = "0";
/**
* Adjust the horizontal positioning of the tooltip toggle in pixels. Can be a negative value as well. E.g. "-13px".
*/
this.positionHorizontal = "0";
/**
* Toggle whether the tooltip is initially visible or not. This property should not be used in production in most cases.
*/
this.active = false;
/**
* Local methods
*/
this.toggleTooltip = ev => {
ev.preventDefault();
if (!this.active) {
this.active = true;
this.visible = this.isElementInViewport(this.element.shadowRoot.querySelector(".duet-tooltip"));
if (!this.visible) {
this.openDown = true;
}
// This is needed to that the active tooltip stacks on
// top of other tooltips and the header.
this.element.style.zIndex = "201";
window.addEventListener("scroll", this.onScroll, false);
window.addEventListener("resize", this.closeTooltip, false);
document.addEventListener("mousedown", this.handleMouseDown, false);
setTimeout(() => {
this.scrollable.focus({
preventScroll: true,
});
window.addEventListener("keyup", this.handleKeyUp, false);
window.addEventListener("keydown", this.handleKeyDown, false);
if (window.parent) {
window.parent.addEventListener("keyup", this.handleKeyUp, false);
window.parent.addEventListener("keydown", this.handleKeyDown, false);
}
this.scrollable.addEventListener("blur", this.closeTooltip, false);
}, 305);
}
else {
this.closeTooltip();
}
};
this.handleKeyUp = ev => {
if (this.active) {
// When Esc is pressed
if (ev.key === "Escape" || ev.key === "Esc" || ev.keyCode === 27) {
this.closeTooltip();
}
// When Space is pressed
if (ev.key === "Space" || ev.keyCode === 32) {
this.closeTooltip();
}
// When Enter is pressed
if (ev.key === "Enter" || ev.keyCode === 13) {
this.closeTooltip();
}
}
};
this.handleKeyDown = ev => {
if (this.active) {
// When Space is pressed
if (ev.key === "Space" || ev.keyCode === 32) {
ev.preventDefault();
}
}
};
this.handleMouseDown = ev => {
if (ev.target === this.element) {
ev.preventDefault();
ev.stopPropagation();
}
};
this.closeTooltip = () => {
this.active = false;
window.removeEventListener("keyup", this.handleKeyUp);
window.removeEventListener("keydown", this.handleKeyDown);
window.removeEventListener("scroll", this.onScroll);
window.removeEventListener("resize", this.closeTooltip);
document.removeEventListener("mousedown", this.handleMouseDown);
this.scrollable.removeEventListener("blur", this.closeTooltip);
if (window.parent) {
window.parent.removeEventListener("keyup", this.handleKeyUp);
window.parent.removeEventListener("keydown", this.handleKeyDown);
}
setTimeout(() => {
this.openDown = false;
this.tooltip.classList.remove("duet-tooltip-down");
this.element.style.zIndex = "200";
const button = this.element.shadowRoot.querySelector(".duet-tooltip-button");
button.focus({
preventScroll: true,
});
}, 300);
};
this.isElementInViewport = el => {
var rect = el.getBoundingClientRect();
return (
// This takes the possible Nav component into account
rect.top >= 100 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth));
};
this.onScroll = () => {
this.visible = this.isElementInViewport(this.tooltip);
clearTimeout(this.timer);
const st = window.pageYOffset || document.documentElement.scrollTop;
this.timer = setTimeout(() => {
// If not visible anymore, continue with the logic
if (!this.visible) {
// Scrolling down
if (st > this.lastScrollTop) {
this.tooltip.classList.add("duet-tooltip-down");
this.openDown = true;
// Scrolling up
}
else {
this.tooltip.classList.remove("duet-tooltip-down");
this.openDown = false;
}
}
this.lastScrollTop = st <= 0 ? 0 : st;
}, 100);
};
}
/**
* Component lifecycle events.
*/
componentWillLoad() {
if (this.theme !== "default" && document.documentElement.classList.contains("duet-theme-turva")) {
this.theme = "turva";
}
}
componentDidLoad() {
this.tooltip = this.element.shadowRoot.querySelector(".duet-tooltip");
this.scrollable = this.element.shadowRoot.querySelector(".duet-tooltip-scrollable");
}
/**
* render() function.
* Always the last one in the class.
*/
render() {
const tooltipStyles = {
transform: "translateY(" + this.positionVertical + ") translateX(" + this.positionHorizontal + ")",
};
return (h(Host, { style: tooltipStyles },
h("button", { "aria-controls": this.tooltipId, "aria-expanded": this.active, onClick: ev => this.toggleTooltip(ev), class: {
"duet-tooltip-button": true,
"duet-tooltip-button-has-label": this.label !== "",
"duet-theme-turva": this.theme === "turva",
} },
h("svg", { "aria-hidden": "true", role: "img", fill: "currentColor", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" },
h("path", { d: "m13.5 17.25c-1.241 0-2.25-1.009-2.25-2.25v-3.75h-.75c-.414 0-.75-.336-.75-.75s.336-.75.75-.75h.75c.827 0 1.5.673 1.5 1.5v3.75c0 .414.336.75.75.75h.75c.414 0 .75.336.75.75s-.336.75-.75.75z" }),
h("circle", { cx: "11.625", cy: "7.125", r: "1.125" }),
h("path", { d: "m12 24c-6.617 0-12-5.383-12-12s5.383-12 12-12 12 5.383 12 12-5.383 12-12 12zm0-22.5c-5.79 0-10.5 4.71-10.5 10.5s4.71 10.5 10.5 10.5 10.5-4.71 10.5-10.5-4.71-10.5-10.5-10.5z" })),
this.label !== "" ? (h("span", { class: "duet-tooltip-label" }, this.label)) : (h("duet-visually-hidden", null, this.accessibleLabel))),
h("div", { class: {
"duet-tooltip": true,
"duet-theme-turva": this.theme === "turva",
"duet-tooltip-down": this.openDown,
"duet-tooltip-has-label": this.label !== "",
active: this.active === true,
} },
h("div", { class: "duet-tooltip-scrollable", tabindex: "0", role: "tooltip", id: this.tooltipId },
h("slot", null)))));
}
static get is() { return "duet-tooltip"; }
static get encapsulation() { return "shadow"; }
static get originalStyleUrls() { return {
"$": ["duet-tooltip.scss"]
}; }
static get styleUrls() { return {
"$": ["duet-tooltip.css"]
}; }
static get properties() { return {
"theme": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Theme of the tooltip. Can be one of: \"default\", \"turva\"."
},
"attribute": "theme",
"reflect": false,
"defaultValue": "\"\""
},
"accessibleLabel": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Adds accessible label for the info icon that is only shown for screen readers. This property is always required to create an accessibly interface!"
},
"attribute": "accessible-label",
"reflect": false,
"defaultValue": "\"N\u00E4yt\u00E4 lis\u00E4tietoja\""
},
"label": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Label of the tooltip. If used, replaces the accessibleLabel option."
},
"attribute": "label",
"reflect": false,
"defaultValue": "\"\""
},
"positionVertical": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Adjust the vertical positioning of the tooltip toggle in pixels. Can be a negative value as well. E.g. \"-13px\"."
},
"attribute": "position-vertical",
"reflect": true,
"defaultValue": "\"0\""
},
"positionHorizontal": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Adjust the horizontal positioning of the tooltip toggle in pixels. Can be a negative value as well. E.g. \"-13px\"."
},
"attribute": "position-horizontal",
"reflect": true,
"defaultValue": "\"0\""
},
"active": {
"type": "boolean",
"mutable": true,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Toggle whether the tooltip is initially visible or not. This property should not be used in production in most cases."
},
"attribute": "active",
"reflect": true,
"defaultValue": "false"
}
}; }
static get states() { return {
"openDown": {},
"visible": {}
}; }
static get elementRef() { return "element"; }
}