Skip to content

Commit

Permalink
feat(tooltip): add target attribute (#848)
Browse files Browse the repository at this point in the history
Closes #678
  • Loading branch information
Enes5519 committed May 16, 2024
1 parent e691c7a commit f2deab2
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 9 deletions.
12 changes: 7 additions & 5 deletions src/components/popover/bl-popover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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;
}

/**
Expand Down
15 changes: 15 additions & 0 deletions src/components/tooltip/bl-tooltip.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ export const PlacementTemplate = (args) => html`
You can use this section to cancel your order.
</bl-tooltip>`

export const TargetAttrTemplate = (args) => html`
<bl-tooltip placement="${ifDefined(args.placement)}" target="trigger-btn">
Target Attribute
</bl-tooltip>
<bl-button id="trigger-btn">With Target</bl-button>
`;

# Tooltip

<bl-badge icon="document">ADR</bl-badge>
Expand Down Expand Up @@ -153,6 +160,14 @@ For example, if there is not enough room on the top, the tooltip is shown on the
</Story>
</Canvas>

## Target Attribute

By using the target attribute, we can add a tooltip to the element.

<Canvas>
<Story name="Usage With Target Attribute" play={tooltipOpener}>{TargetAttrTemplate.bind({})}</Story>
</Canvas>

## Reference

<ArgsTable of="bl-tooltip" />
50 changes: 50 additions & 0 deletions src/components/tooltip/bl-tooltip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
<div><bl-tooltip target="btn">Tooltip Text</bl-tooltip><button type="button" id="btn">Test</button></div>`);

const tooltipEl = el.querySelector("bl-tooltip")!;
const trigger = el.querySelector<HTMLButtonElement>("#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`
<div><bl-tooltip target="btn">Tooltip Text</bl-tooltip><button type="button" id="btn">Test</button><button type="button" id="new-btn">Test</button></div>`);

const tooltipEl = el.querySelector("bl-tooltip")!;
const triggerPrev = el.querySelector<HTMLButtonElement>("#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);
});
});
62 changes: 58 additions & 4 deletions src/components/tooltip/bl-tooltip.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -39,11 +40,64 @@ export default class BlTooltip extends LitElement {
*/
@event("bl-tooltip-hide") private onHide: EventDispatcher<string>;

@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("");
}
Expand Down Expand Up @@ -78,9 +132,9 @@ export default class BlTooltip extends LitElement {

render(): TemplateResult {
return html`
${this.triggerTemplate()}
${this.target ? "" : this.triggerTemplate()}
<bl-popover
.target="${this.trigger}"
.target="${this.target ?? this.trigger}"
placement="${ifDefined(this.placement)}"
@bl-popover-hide="${() => this.onHide("")}"
>
Expand Down
10 changes: 10 additions & 0 deletions src/utilities/elements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

0 comments on commit f2deab2

Please sign in to comment.