blob: dbc8317aefa3f9d479e48d5fc71deab379a9303f [file] [log] [blame]
/**
* @module modules/tooltip-sk
* @description <h2><code>tooltip-sk</code></h2>
*
* Known limitations: You can only have one tooltip for an element. While multiple tooltips
* will work, the aria-describedby attribute will only be correct for one of the tooltips.
*
* The displayed tooltip can be styled by targeting `tooltip-sk .content`.
*
* @attr target - The id of the element that this tooltip is for.
*
* @attr value - The text to display.
*
*/
import { html } from 'lit-html';
import { define } from '../../../elements-sk/modules/define';
import { ElementSk } from '../ElementSk';
export const targetAriaAttribute = 'aria-describedby';
export const hiddenClassName = 'hidden';
export class TooltipSk extends ElementSk {
private hide = () => this.classList.add(hiddenClassName);
private show = () => this.classList.remove(hiddenClassName);
/** The element this tooltip is for. */
private targetElement: HTMLElement | null = null;
private static template = (ele: TooltipSk) =>
html`<div class="content">${ele.value}</div>`;
constructor() {
super(TooltipSk.template);
}
connectedCallback(): void {
super.connectedCallback();
this._upgradeProperty('value');
this._upgradeProperty('target');
this._render();
this.hide();
if (!this.hasAttribute('role')) {
this.setAttribute('role', 'tooltip');
}
if (!this.hasAttribute('tabindex')) {
this.setAttribute('tabindex', '-1');
}
// This element sets the 'aria-describedby' attribute on the target to point
// back to this element. We require an id for this to work, so assign a
// random id if one hasn't been set.
if (!this.id) {
this.id = `x${`${Math.random()}`.slice(2)}`;
}
this.connectToTarget();
}
disconnectedCallback(): void {
this.disconnectTarget();
}
private connectToTarget() {
this.targetElement = document.querySelector(`#${this.target}`);
if (!this.targetElement) {
return;
}
this.targetElement.setAttribute(targetAriaAttribute, this.id);
this.targetElement.addEventListener('focus', this.show);
this.targetElement.addEventListener('blur', this.hide);
this.targetElement.addEventListener('mouseenter', this.show);
this.targetElement.addEventListener('mouseleave', this.hide);
}
private disconnectTarget() {
if (!this.targetElement) {
return;
}
this.targetElement.removeAttribute(targetAriaAttribute);
this.targetElement.removeEventListener('focus', this.show);
this.targetElement.removeEventListener('blur', this.hide);
this.targetElement.removeEventListener('mouseenter', this.show);
this.targetElement.removeEventListener('mouseleave', this.hide);
this.targetElement = null;
}
static get observedAttributes(): string[] {
return ['target', 'value'];
}
/** @prop target {string} The target this tooltip is for. */
get target(): string {
return this.getAttribute('target') || '';
}
set target(val: string) {
this.setAttribute('target', val);
}
/** @prop value {string} The value to display in the tooltip. */
get value(): string {
return this.getAttribute('value') || '';
}
set value(val: string) {
this.setAttribute('value', val);
}
attributeChangedCallback(name: string): void {
switch (name) {
case 'target':
this.connectToTarget();
break;
case 'value':
this._render();
break;
default:
break;
}
}
}
define('tooltip-sk', TooltipSk);