UNPKG

@duetds/components

Version:

This package includes Duet Core Components and related tools.

325 lines (324 loc) 13.5 kB
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"; } }