From f2deab2eeb3fb2c537ba0d6aab7b2cee4ebde04b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Y=C4=B1ld=C4=B1r=C4=B1m?= Date: Thu, 16 May 2024 14:19:33 +0300 Subject: [PATCH] feat(tooltip): add target attribute (#848) Closes #678 --- src/components/popover/bl-popover.ts | 12 ++-- src/components/tooltip/bl-tooltip.stories.mdx | 15 +++++ src/components/tooltip/bl-tooltip.test.ts | 50 +++++++++++++++ src/components/tooltip/bl-tooltip.ts | 62 +++++++++++++++++-- src/utilities/elements.ts | 10 +++ 5 files changed, 140 insertions(+), 9 deletions(-) diff --git a/src/components/popover/bl-popover.ts b/src/components/popover/bl-popover.ts index f2e04b9b..a783e1f2 100644 --- a/src/components/popover/bl-popover.ts +++ b/src/components/popover/bl-popover.ts @@ -13,6 +13,7 @@ import { Middleware, MiddlewareState, } from "@floating-ui/dom"; +import { getTarget } from "../../utilities/elements"; import { event, EventDispatcher } from "../../utilities/event"; import style from "./bl-popover.css"; @@ -176,15 +177,16 @@ export default class BlPopover extends LitElement { } set target(value: string | Element) { - if (typeof value === "string") { - this._target = document.getElementById(value) as Element; - } else if (value instanceof Element) { - this._target = value; - } else { + const target = getTarget(value); + + if (!target) { console.warn( "BlPopover target only accepts an Element instance or a string id of a DOM element." ); + return; } + + this._target = target; } /** diff --git a/src/components/tooltip/bl-tooltip.stories.mdx b/src/components/tooltip/bl-tooltip.stories.mdx index 4ed8732a..eea19f01 100644 --- a/src/components/tooltip/bl-tooltip.stories.mdx +++ b/src/components/tooltip/bl-tooltip.stories.mdx @@ -44,6 +44,13 @@ export const PlacementTemplate = (args) => html` You can use this section to cancel your order. ` +export const TargetAttrTemplate = (args) => html` + + Target Attribute + +With Target +`; + # Tooltip ADR @@ -153,6 +160,14 @@ For example, if there is not enough room on the top, the tooltip is shown on the +## Target Attribute + +By using the target attribute, we can add a tooltip to the element. + + + {TargetAttrTemplate.bind({})} + + ## Reference diff --git a/src/components/tooltip/bl-tooltip.test.ts b/src/components/tooltip/bl-tooltip.test.ts index 92b62f0f..55add7bc 100644 --- a/src/components/tooltip/bl-tooltip.test.ts +++ b/src/components/tooltip/bl-tooltip.test.ts @@ -202,4 +202,54 @@ describe("bl-tooltip", () => { expect(ev).to.exist; expect(el.visible).to.be.false; }); + + it("should work with target attribute", async () => { + // given + const el = await fixture(html` +
Tooltip Text
`); + + const tooltipEl = el.querySelector("bl-tooltip")!; + const trigger = el.querySelector("#btn")!; + + // when + const { x, y } = getMiddleOfElement(trigger); + + setTimeout(() => sendMouse({ type: "move", position: [x, y] })); + + // then + const ev = await oneEvent(tooltipEl, "bl-tooltip-show"); + + expect(ev).to.exist; + expect(ev.detail).to.be.equal(""); + }); + + it("should remove previous target elements", async () => { + // given + const el = await fixture(html` +
Tooltip Text
`); + + const tooltipEl = el.querySelector("bl-tooltip")!; + const triggerPrev = el.querySelector("#btn")!; + + // when + tooltipEl.target = "new-btn"; + const { x, y } = getMiddleOfElement(triggerPrev); + + setTimeout(() => sendMouse({ type: "move", position: [x, y] })); + + // then + const ev = await new Promise(resolve => { + function listener(ev: Event) { + resolve(ev); + tooltipEl.removeEventListener("bl-tooltip-show", listener); + } + tooltipEl.addEventListener("bl-tooltip-show", listener); + setTimeout(() => { + resolve(null); + tooltipEl.removeEventListener("bl-tooltip-show", listener); + }, 200); + }); + + expect(ev).to.be.eq(null); + }); }); diff --git a/src/components/tooltip/bl-tooltip.ts b/src/components/tooltip/bl-tooltip.ts index c3f2cbb8..e2bbe22e 100644 --- a/src/components/tooltip/bl-tooltip.ts +++ b/src/components/tooltip/bl-tooltip.ts @@ -1,7 +1,8 @@ -import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { CSSResultGroup, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, query } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import { ReferenceElement } from "@floating-ui/core"; +import { getTarget } from "../../utilities/elements"; import { event, EventDispatcher } from "../../utilities/event"; import "../popover/bl-popover"; import BlPopover, { Placement } from "../popover/bl-popover"; @@ -39,11 +40,64 @@ export default class BlTooltip extends LitElement { */ @event("bl-tooltip-hide") private onHide: EventDispatcher; + @property() target: string | Element; + + protected update(changedProperties: PropertyValues) { + if (changedProperties.has("target")) { + const prev = changedProperties.get("target"); + + if (prev) { + this._removeEvents(prev); + } + + this._addEvents(); + } + + super.update(changedProperties); + } + + private _addEvents() { + const target = getTarget(this.target); + + if (target) { + target.addEventListener("focus", this.show, { capture: true }); + target.addEventListener("mouseover", this.show); + target.addEventListener("blur", this.hide, { capture: true }); + target.addEventListener("mouseleave", this.hide); + } + } + + private _removeEvents(value: string | Element) { + const target = getTarget(value); + + if (target) { + target.removeEventListener("focus", this.show, { capture: true }); + target.removeEventListener("mouseover", this.show); + target.removeEventListener("blur", this.hide, { capture: true }); + target.removeEventListener("mouseleave", this.hide); + } + } + + connectedCallback() { + super.connectedCallback(); + + this.show = this.show.bind(this); + this.hide = this.hide.bind(this); + + this._addEvents(); + } + + disconnectedCallback() { + super.disconnectedCallback(); + + this._removeEvents(this.target); + } + /** * Shows tooltip */ show() { - this._popover.target = this.trigger; + this._popover.target = this.target ?? this.trigger; this._popover.show(); this.onShow(""); } @@ -78,9 +132,9 @@ export default class BlTooltip extends LitElement { render(): TemplateResult { return html` - ${this.triggerTemplate()} + ${this.target ? "" : this.triggerTemplate()} diff --git a/src/utilities/elements.ts b/src/utilities/elements.ts index 8c7d2669..169d8ce2 100644 --- a/src/utilities/elements.ts +++ b/src/utilities/elements.ts @@ -6,3 +6,13 @@ export function getMiddleOfElement(element: Element) { y: Math.floor(y + window.pageYOffset + height / 2), }; } + +export function getTarget(value: string | Element): Element | null { + if (typeof value === "string") { + return document.getElementById(value) as Element; + } else if (value instanceof Element) { + return value; + } + + return null; +}