Files
member-console/internal/embeds/static/help-init.js
T
cgalo5758 fe19ee415c Add CLA and SPDX headers, fix docs
- Pin Dockerfile to Go 1.23 to match go.mod
- Record README front-door audit findings
2026-09-07 21:32:14 -05:00

141 lines
5.2 KiB
JavaScript

// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
// Bootstrap tooltip and help-popover initializer (help-init.js; design D23
// "Help is disclosed by click-opened popovers"; acceptance-fixes,
// 2026-09-02, replacing tooltip-init.js).
//
// Two Bootstrap primitives, one initializer:
//
// [data-bs-toggle="tooltip"] — a hover/focus tooltip. Bootstrap's data
// API auto-wires dropdowns, offcanvas, collapse, and modals, but NOT
// tooltips or popovers: each needs an explicit instance, or the browser
// shows only the native `title` on hover and nothing on keyboard focus.
// The disabled-control part (ui_disabled_control.html; design-system §3
// "Disabled controls") relies on the tooltip appearing on focus so a
// keyboard user reads the reason a control is unavailable — this is a
// reason on a control, the one case hover tooltips remain for.
//
// [data-bs-toggle="popover"] — the helpIcon part's click-opened help
// popover (ui_help_icon.html; design D23). Titled with the field or
// column it explains (its data-bs-title), with a close button in the
// header built as a real DOM element — never string concatenation of
// the label or the close button's markup, which would run whatever a
// label might carry through innerHTML. Escape and a click outside any
// open popover or its own trigger also close it; Bootstrap's
// click-trigger popovers do not dismiss on an outside click on their
// own.
//
// Re-runs after every htmx swap via htmx.onLoad; getOrCreateInstance is
// idempotent, so re-processing an already-wired node is a no-op. CSP-safe:
// Bootstrap and Popper position tooltips/popovers through CSSOM
// (element.style), never inline style attributes, so the strict style-src
// holds; the popover title is assembled with document.createElement, never
// innerHTML, so the strict script-src holds too.
(function () {
"use strict";
function initTooltips(root) {
if (!window.bootstrap || !window.bootstrap.Tooltip) {
return;
}
var nodes = (root || document).querySelectorAll('[data-bs-toggle="tooltip"]');
Array.prototype.forEach.call(nodes, function (el) {
window.bootstrap.Tooltip.getOrCreateInstance(el);
});
}
// popoverTitle returns the title-builder Bootstrap calls each time it
// renders this trigger's popover: a span holding the label text and a
// close button that hides this same trigger's popover instance. Built
// as DOM nodes (never a string), so the label's text can never be read
// back as markup.
function popoverTitle(el, label) {
return function () {
var wrap = document.createElement("span");
wrap.className = "d-flex align-items-center justify-content-between gap-2";
var text = document.createElement("span");
text.textContent = label;
wrap.appendChild(text);
var close = document.createElement("button");
close.type = "button";
close.className = "btn-close";
close.setAttribute("aria-label", "Close");
close.addEventListener("click", function () {
var instance = window.bootstrap.Popover.getInstance(el);
if (instance) {
instance.hide();
}
});
wrap.appendChild(close);
return wrap;
};
}
function initPopovers(root) {
if (!window.bootstrap || !window.bootstrap.Popover) {
return;
}
var nodes = (root || document).querySelectorAll('[data-bs-toggle="popover"]');
Array.prototype.forEach.call(nodes, function (el) {
var label = el.getAttribute("data-bs-title") || "";
window.bootstrap.Popover.getOrCreateInstance(el, {
html: true,
title: popoverTitle(el, label),
});
});
}
function hideAllPopovers() {
if (!window.bootstrap || !window.bootstrap.Popover) {
return;
}
var triggers = document.querySelectorAll('[data-bs-toggle="popover"]');
Array.prototype.forEach.call(triggers, function (el) {
var instance = window.bootstrap.Popover.getInstance(el);
if (instance) {
instance.hide();
}
});
}
// Escape closes every open popover, wherever focus is.
document.addEventListener("keydown", function (event) {
if (event.key === "Escape") {
hideAllPopovers();
}
});
// A click outside any popover and outside any popover trigger closes
// every open popover. A click on a trigger (this one's or another's) is
// left to Bootstrap's own click-toggle handling, and a click inside an
// open popover (e.g. selecting its text) must not close it.
document.addEventListener("click", function (event) {
var target = event.target;
if (target && target.closest && (target.closest('[data-bs-toggle="popover"]') || target.closest(".popover"))) {
return;
}
hideAllPopovers();
});
function init(root) {
initTooltips(root);
initPopovers(root);
}
if (window.htmx && typeof window.htmx.onLoad === "function") {
window.htmx.onLoad(function (content) {
init(content);
});
} else if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", function () {
init(document);
});
} else {
init(document);
}
})();