components_info_widget.js


/**
 * InfoWidget — inline ⓘ icon that shows a hover tooltip and opens a docs page on click.
 *
 * Usage (HTML):
 *   <info-widget message="Explain something here" href="/docs/some-page"></info-widget>
 *
 * Usage (JS):
 *   const w = document.createElement('info-widget');
 *   w.setAttribute('message', 'Some explanation');
 *   w.setAttribute('href', '/docs/some-page');
 *   someElement.appendChild(w);
 *
 * Attributes:
 *   message  — text shown in the hover tooltip
 *   href     — URL opened in a new tab when the icon is clicked (optional)
 */
export class InfoWidget extends HTMLElement {
    #btn = null;
    #tooltip = null;

    /** Builds the button element and attaches event listeners when the element is inserted into the DOM. */
    connectedCallback() {
        this.#btn = document.createElement('button');
        this.#btn.className = 'info-widget-btn';
        this.#btn.innerHTML = '<span class="icon icon-info" style="width:13px;height:13px;"></span>';

        this.#btn.addEventListener('mouseenter', () => this.#showTooltip());
        this.#btn.addEventListener('mouseleave', () => this.#hideTooltip());
        this.#btn.addEventListener('click', () => {
            const href = this.getAttribute('href');
            if (href) window.open(href, '_blank');
        });

        this.appendChild(this.#btn);
    }

    /** Removes the tooltip when the element is removed from the DOM. */
    disconnectedCallback() {
        this.#hideTooltip();
    }

    /** Creates and positions the tooltip element near the icon button. */
    #showTooltip() {
        if (this.#tooltip) return;

        const message = this.getAttribute('message') || '';
        const href = this.getAttribute('href');

        const tip = document.createElement('div');
        tip.className = 'info-widget-tooltip';
        if (href) tip.classList.add('info-widget-tooltip--has-link');
//        tip.textContent = message;
        tip.innerHTML = message;
        document.body.appendChild(tip);
        this.#tooltip = tip;

        // Position after layout so offsetWidth/Height are available
        const rect = this.#btn.getBoundingClientRect();
        const tipW = tip.offsetWidth;
        const tipH = tip.offsetHeight;
        const gap = 6;

        // Prefer below; fall back to above if near bottom of viewport
        const top = (rect.bottom + tipH + gap <= window.innerHeight)
            ? rect.bottom + gap
            : rect.top - tipH - gap;

        // Align with left edge of icon; clamp to viewport right
        const left = Math.min(rect.left, window.innerWidth - tipW - 8);

        tip.style.top = `${top}px`;
        tip.style.left = `${left}px`;
    }

    /** Removes the tooltip element from the DOM. */
    #hideTooltip() {
        this.#tooltip?.remove();
        this.#tooltip = null;
    }
}

customElements.define('info-widget', InfoWidget);