Skip to content

Commit f2deab2

Browse files
authored
feat(tooltip): add target attribute (#848)
Closes #678
1 parent e691c7a commit f2deab2

File tree

5 files changed

+140
-9
lines changed

5 files changed

+140
-9
lines changed

src/components/popover/bl-popover.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
Middleware,
1414
MiddlewareState,
1515
} from "@floating-ui/dom";
16+
import { getTarget } from "../../utilities/elements";
1617
import { event, EventDispatcher } from "../../utilities/event";
1718
import style from "./bl-popover.css";
1819

@@ -176,15 +177,16 @@ export default class BlPopover extends LitElement {
176177
}
177178

178179
set target(value: string | Element) {
179-
if (typeof value === "string") {
180-
this._target = document.getElementById(value) as Element;
181-
} else if (value instanceof Element) {
182-
this._target = value;
183-
} else {
180+
const target = getTarget(value);
181+
182+
if (!target) {
184183
console.warn(
185184
"BlPopover target only accepts an Element instance or a string id of a DOM element."
186185
);
186+
return;
187187
}
188+
189+
this._target = target;
188190
}
189191

190192
/**

src/components/tooltip/bl-tooltip.stories.mdx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ export const PlacementTemplate = (args) => html`
4444
You can use this section to cancel your order.
4545
</bl-tooltip>`
4646

47+
export const TargetAttrTemplate = (args) => html`
48+
<bl-tooltip placement="${ifDefined(args.placement)}" target="trigger-btn">
49+
Target Attribute
50+
</bl-tooltip>
51+
<bl-button id="trigger-btn">With Target</bl-button>
52+
`;
53+
4754
# Tooltip
4855

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

163+
## Target Attribute
164+
165+
By using the target attribute, we can add a tooltip to the element.
166+
167+
<Canvas>
168+
<Story name="Usage With Target Attribute" play={tooltipOpener}>{TargetAttrTemplate.bind({})}</Story>
169+
</Canvas>
170+
156171
## Reference
157172

158173
<ArgsTable of="bl-tooltip" />

src/components/tooltip/bl-tooltip.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,4 +202,54 @@ describe("bl-tooltip", () => {
202202
expect(ev).to.exist;
203203
expect(el.visible).to.be.false;
204204
});
205+
206+
it("should work with target attribute", async () => {
207+
// given
208+
const el = await fixture(html`
209+
<div><bl-tooltip target="btn">Tooltip Text</bl-tooltip><button type="button" id="btn">Test</button></div>`);
210+
211+
const tooltipEl = el.querySelector("bl-tooltip")!;
212+
const trigger = el.querySelector<HTMLButtonElement>("#btn")!;
213+
214+
// when
215+
const { x, y } = getMiddleOfElement(trigger);
216+
217+
setTimeout(() => sendMouse({ type: "move", position: [x, y] }));
218+
219+
// then
220+
const ev = await oneEvent(tooltipEl, "bl-tooltip-show");
221+
222+
expect(ev).to.exist;
223+
expect(ev.detail).to.be.equal("");
224+
});
225+
226+
it("should remove previous target elements", async () => {
227+
// given
228+
const el = await fixture(html`
229+
<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>`);
230+
231+
const tooltipEl = el.querySelector("bl-tooltip")!;
232+
const triggerPrev = el.querySelector<HTMLButtonElement>("#btn")!;
233+
234+
// when
235+
tooltipEl.target = "new-btn";
236+
const { x, y } = getMiddleOfElement(triggerPrev);
237+
238+
setTimeout(() => sendMouse({ type: "move", position: [x, y] }));
239+
240+
// then
241+
const ev = await new Promise(resolve => {
242+
function listener(ev: Event) {
243+
resolve(ev);
244+
tooltipEl.removeEventListener("bl-tooltip-show", listener);
245+
}
246+
tooltipEl.addEventListener("bl-tooltip-show", listener);
247+
setTimeout(() => {
248+
resolve(null);
249+
tooltipEl.removeEventListener("bl-tooltip-show", listener);
250+
}, 200);
251+
});
252+
253+
expect(ev).to.be.eq(null);
254+
});
205255
});

src/components/tooltip/bl-tooltip.ts

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
1+
import { CSSResultGroup, html, LitElement, PropertyValues, TemplateResult } from "lit";
22
import { customElement, property, query } from "lit/decorators.js";
33
import { ifDefined } from "lit/directives/if-defined.js";
44
import { ReferenceElement } from "@floating-ui/core";
5+
import { getTarget } from "../../utilities/elements";
56
import { event, EventDispatcher } from "../../utilities/event";
67
import "../popover/bl-popover";
78
import BlPopover, { Placement } from "../popover/bl-popover";
@@ -39,11 +40,64 @@ export default class BlTooltip extends LitElement {
3940
*/
4041
@event("bl-tooltip-hide") private onHide: EventDispatcher<string>;
4142

43+
@property() target: string | Element;
44+
45+
protected update(changedProperties: PropertyValues) {
46+
if (changedProperties.has("target")) {
47+
const prev = changedProperties.get("target");
48+
49+
if (prev) {
50+
this._removeEvents(prev);
51+
}
52+
53+
this._addEvents();
54+
}
55+
56+
super.update(changedProperties);
57+
}
58+
59+
private _addEvents() {
60+
const target = getTarget(this.target);
61+
62+
if (target) {
63+
target.addEventListener("focus", this.show, { capture: true });
64+
target.addEventListener("mouseover", this.show);
65+
target.addEventListener("blur", this.hide, { capture: true });
66+
target.addEventListener("mouseleave", this.hide);
67+
}
68+
}
69+
70+
private _removeEvents(value: string | Element) {
71+
const target = getTarget(value);
72+
73+
if (target) {
74+
target.removeEventListener("focus", this.show, { capture: true });
75+
target.removeEventListener("mouseover", this.show);
76+
target.removeEventListener("blur", this.hide, { capture: true });
77+
target.removeEventListener("mouseleave", this.hide);
78+
}
79+
}
80+
81+
connectedCallback() {
82+
super.connectedCallback();
83+
84+
this.show = this.show.bind(this);
85+
this.hide = this.hide.bind(this);
86+
87+
this._addEvents();
88+
}
89+
90+
disconnectedCallback() {
91+
super.disconnectedCallback();
92+
93+
this._removeEvents(this.target);
94+
}
95+
4296
/**
4397
* Shows tooltip
4498
*/
4599
show() {
46-
this._popover.target = this.trigger;
100+
this._popover.target = this.target ?? this.trigger;
47101
this._popover.show();
48102
this.onShow("");
49103
}
@@ -78,9 +132,9 @@ export default class BlTooltip extends LitElement {
78132

79133
render(): TemplateResult {
80134
return html`
81-
${this.triggerTemplate()}
135+
${this.target ? "" : this.triggerTemplate()}
82136
<bl-popover
83-
.target="${this.trigger}"
137+
.target="${this.target ?? this.trigger}"
84138
placement="${ifDefined(this.placement)}"
85139
@bl-popover-hide="${() => this.onHide("")}"
86140
>

src/utilities/elements.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,13 @@ export function getMiddleOfElement(element: Element) {
66
y: Math.floor(y + window.pageYOffset + height / 2),
77
};
88
}
9+
10+
export function getTarget(value: string | Element): Element | null {
11+
if (typeof value === "string") {
12+
return document.getElementById(value) as Element;
13+
} else if (value instanceof Element) {
14+
return value;
15+
}
16+
17+
return null;
18+
}

0 commit comments

Comments
 (0)