to accomplish this, I generalised emoji tooltips to a shared Tooltip class. in the long run I'd like to transform all existing `title=""` tooltips into these for stylistic consistency with the rest of the website, but this is good enough for now. I also ended up cleaning up some old code from before the /b rework.
116 lines
3.2 KiB
JavaScript
116 lines
3.2 KiB
JavaScript
export class Overlay extends HTMLElement {}
|
|
|
|
/** @type Overlays */
|
|
export let overlays = null;
|
|
|
|
export class Overlays extends HTMLElement {
|
|
overlays = new Set();
|
|
|
|
connectedCallback() {
|
|
overlays = this;
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
overlays = null;
|
|
}
|
|
|
|
open(overlay) {
|
|
this.appendChild(overlay);
|
|
this.overlays.add(overlay);
|
|
return overlay;
|
|
}
|
|
|
|
close(overlay) {
|
|
this.removeChild(overlay);
|
|
this.overlays.delete(overlay);
|
|
}
|
|
}
|
|
|
|
customElements.define("th-overlays", Overlays);
|
|
|
|
export class Tooltip extends Overlay {
|
|
constructor(element, side) {
|
|
super();
|
|
|
|
this.element = element;
|
|
this.side = side;
|
|
}
|
|
|
|
connectedCallback() {
|
|
this.role = "tooltip";
|
|
this.setAttribute("th-side", this.side);
|
|
|
|
let bb = this.element.getBoundingClientRect();
|
|
switch (this.side) {
|
|
// NOTE: The elements are positioned directly at (width / 2) or (height / 2), because
|
|
// they are transformed to the centre over on the CSS side.
|
|
|
|
case "bottom":
|
|
this.style.left = `${bb.left + bb.width / 2}px`;
|
|
this.style.top = `${bb.bottom}px`;
|
|
break;
|
|
|
|
case "left":
|
|
this.style.left = `${bb.left}px`;
|
|
this.style.top = `${bb.top + bb.height / 2}px`;
|
|
break;
|
|
|
|
default:
|
|
console.error(`th-tooltip: unknown attachment side ${this.side}`);
|
|
break;
|
|
}
|
|
|
|
this.addEventListener("transitionend", (event) => {
|
|
if (event.propertyName == "opacity") {
|
|
let style = getComputedStyle(this);
|
|
if (style.opacity < 0.01) {
|
|
this.dispatchEvent(new Event(".close"));
|
|
}
|
|
}
|
|
});
|
|
// Timeout is zero because we just want to execute this later, to be definitely sure
|
|
// the transition plays out.
|
|
setTimeout(() => this.classList.add("transitioned-in"), 0);
|
|
}
|
|
|
|
close() {
|
|
this.classList.remove("transitioned-in");
|
|
|
|
// NOTE: In case there is no transition, we may need to trigger the close event immediately.
|
|
let style = getComputedStyle(this);
|
|
if (style.opacity < 0.01) {
|
|
this.dispatchEvent(new Event(".close"));
|
|
}
|
|
}
|
|
}
|
|
|
|
customElements.define("th-tooltip", Tooltip);
|
|
|
|
export function attachTooltip(element, makeTooltip) {
|
|
let show = () => {
|
|
let tooltip = overlays.open(makeTooltip(element));
|
|
let abortController = new AbortController();
|
|
|
|
tooltip.addEventListener(".close", () => {
|
|
overlays.close(tooltip);
|
|
abortController.abort();
|
|
console.log("closing tooltip");
|
|
});
|
|
|
|
window.addEventListener("wheel", () => tooltip.close(), {
|
|
signal: abortController.signal,
|
|
passive: true,
|
|
});
|
|
element.addEventListener("mouseleave", () => tooltip.close(), {
|
|
signal: abortController.signal,
|
|
});
|
|
};
|
|
|
|
return {
|
|
show,
|
|
showOnHover() {
|
|
element.addEventListener("mouseenter", show);
|
|
return this;
|
|
},
|
|
};
|
|
}
|