Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions packages/unity-bootstrap-theme/src/js/tooltips.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { EventHandler } from "./bootstrap-helper";

function initTooltips() {
/* if this value changes, update tooltips.js file */
const TOOLTIP_MAX_WIDTH = 288;

// This query selector is not just creating a List,
// it's also checking to ensure all 3 elements are present
// (container, trigger, content) and in the correct order
// (trigger immediately followed by content)
const tooltipContentList = document.querySelectorAll(
'.uds-tooltip-container > .uds-tooltip + [role="tooltip"]'
);

function show(e) {
// container or trigger
let trigger = e.target.querySelector(".uds-tooltip") || e.target;
let content = trigger.nextSibling;

if (e.type === "keypress") {
if (e.charCode !== 32) {
return;
}
}
// content.getBoundingClientRect().width +
// trigger.getBoundingClientRect().right >
// window.innerWidth

if (
trigger.getBoundingClientRect().right + TOOLTIP_MAX_WIDTH >
window.innerWidth
) {
content.classList.add("bottom-placement");
} else {
content.classList.remove("bottom-placement");
}
trigger.setAttribute("aria-expanded", "true");
}

function hide(e) {
// container or trigger
let trigger = e.target.querySelector(".uds-tooltip") || e.target;

if (e.type === "mouseleave") {
if (trigger === document.activeElement) {
return;
}
}
trigger.setAttribute("aria-expanded", "false");
}

function kbHide(e) {
if (e.key === "Escape") {
hide(e);
}
}

window.t2 = [...tooltipContentList].map(contentEl => {
const controller = new AbortController();
const { signal } = controller;
const triggerEl = contentEl.previousElementSibling;
const containerEl = triggerEl.parentElement;

// const showEvents = [
// ["mouseenter"],
// ["focus"],
// ["keypress", e => e.charCode === 32 || e.key === "Enter"],
// ];
// const hideEvents = [
// ["mouseleave", e => e.target !== document.activeElement],
// ["blur"],
// ["keydown", e => e.key === "Escape"],
// ];

// containerEl.attachShadow({ mode: "open" });
// containerEl.appendChild(document.createElement("slot"));
// shadowRoot.appendChild(contentEl.cloneNode(true));

triggerEl.addEventListener("mouseenter", show, { signal });
triggerEl.addEventListener("focus", show, { signal });
triggerEl.addEventListener("keypress", show, { signal });
triggerEl.addEventListener("blur", hide, { signal });
triggerEl.addEventListener("keydown", kbHide, { signal });
containerEl.addEventListener("mouseleave", hide, { signal });

return controller;
});
}

EventHandler.on(window, "load.uds.tooltips", initTooltips);

export { initTooltips };
163 changes: 163 additions & 0 deletions packages/unity-bootstrap-theme/src/js/tooltips2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { EventHandler } from "./bootstrap-helper";

// WeakMap for better garbage collection
const tooltipInstances = new WeakMap();
const t2 = [];
let isInitialized = false;
window.tooltipInstances = tooltipInstances; // Expose for debugging
window.t2 = t2; // Expose for debugging

function getOrCreateTooltipInstance(triggerEl, contentEl) {
if (!tooltipInstances.has(triggerEl)) {
const popperInstance = new bootstrap.Popover(triggerEl, contentEl, {
placement: "right",
modifiers: [
{
name: "flip",
options: {
fallbackPlacements: ["bottom", "left"],
},
},
{
name: "offset",
options: {
offset: [0, 8],
},
},
],
});
console.log("Creating tooltip instance for:", triggerEl, popperInstance);
tooltipInstances.set(triggerEl, popperInstance);
t2.push(popperInstance);
}
return tooltipInstances.get(triggerEl);
}

function showTooltip(triggerEl) {
const contentEl = triggerEl.nextElementSibling;
if (
!contentEl ||
!contentEl.hasAttribute("role") ||
contentEl.getAttribute("role") !== "tooltip"
) {
return;
}

const popperInstance = getOrCreateTooltipInstance(triggerEl);
triggerEl.setAttribute("aria-expanded", "true");
console.log(popperInstance);
popperInstance.setOptions &&
popperInstance.setOptions(options => ({
...options,
modifiers: [
...options.modifiers,
{ name: "eventListeners", enabled: true },
],
}));

// Update position after showing
popperInstance.update();
}

function hideTooltip(triggerEl) {
triggerEl.setAttribute("aria-expanded", "false");

// Disable the event listeners
if (!getOrCreateTooltipInstance(triggerEl).setOptions) return;
getOrCreateTooltipInstance(triggerEl).setOptions(options => ({
...options,
modifiers: [
...options.modifiers,
{ name: "eventListeners", enabled: false },
],
}));
}

function tryGetTriggerElement(element) {
return element.closest ? element.closest(".uds-tooltip") : element;
}

function isTooltipTrigger(element) {
return (
element?.classList?.contains("uds-tooltip") &&
element.nextElementSibling?.getAttribute("role") === "tooltip"
);
}

// Event delegation handlers
function handleShowEvent(event) {
const triggerEl = tryGetTriggerElement(event.target);
if (!isTooltipTrigger(triggerEl)) return;

// Handle specific event conditions
if (event.type === "keypress") {
if (!(event.charCode === 32 || event.key === "Enter")) return;
}

showTooltip(triggerEl);
}

function handleHideEvent(event) {
const triggerEl = tryGetTriggerElement(event.target);
if (!isTooltipTrigger(triggerEl)) return;

// Handle specific event conditions
if (event.type === "mouseleave") {
if (triggerEl === document.activeElement) return;
}

if (event.target !== triggerEl) return;
hideTooltip(triggerEl);
}

function handleEscapeKey(event) {
if (event.key === "Escape") {
const triggerEl = tryGetTriggerElement(event.target);
if (!isTooltipTrigger(triggerEl)) return;
hideTooltip(triggerEl);
}
}

function initTooltips() {
if (isInitialized) return;

// Check if tooltips exist before setting up listeners
const hasTooltips = document.querySelector(
'.uds-tooltip-container .uds-tooltip + [role="tooltip"]'
);
if (!hasTooltips) return;

// Event delegation - single listeners for all tooltips
document.addEventListener("mouseenter", handleShowEvent, true);
document.addEventListener("focus", handleShowEvent, true);
document.addEventListener("keypress", handleShowEvent, true);

document.addEventListener("mouseleave", handleHideEvent, true);
document.addEventListener("blur", handleHideEvent, true);
document.addEventListener("keydown", handleEscapeKey, true);

isInitialized = true;
}

function destroyTooltips() {
if (!isInitialized) return;

// Remove event listeners
document.removeEventListener("mouseenter", handleShowEvent, true);
document.removeEventListener("focus", handleShowEvent, true);
document.removeEventListener("keypress", handleShowEvent, true);
document.removeEventListener("mouseleave", handleHideEvent, true);
document.removeEventListener("blur", handleHideEvent, true);
document.removeEventListener("keydown", handleEscapeKey, true);

// Clear instances - WeakMap will handle garbage collection
if (tooltipInstances.clear) {
tooltipInstances.clear();
}

isInitialized = false;
}

EventHandler.on(window, "load.uds.tooltips", initTooltips);

export { initTooltips, destroyTooltips };
3 changes: 2 additions & 1 deletion packages/unity-bootstrap-theme/src/js/unity-bootstrap.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// import Banner from "./banner.js";
import { initAnchorMenu } from "./anchor-menu.js";
import { initBlockquoteAnimation } from "./blockquote-animated.js";
import { initCalendar } from "./calendar.js";
import { initCardBodies } from "./card-bodies.js";
Expand All @@ -13,6 +12,7 @@ import { initImageParallax } from "./image-parallax.js";
import { initModals } from "./modals.js";
import { initTabbedPanels } from "./tabbed-panels.js";
import { initFixedTable } from "./tables.js";
import { initTooltips } from "./tooltips.js";
import { initVideo } from "./video.js";

const unityBootstrap = {
Expand All @@ -30,6 +30,7 @@ const unityBootstrap = {
initModals,
initRankingCard,
initTabbedPanels,
initTooltips,
initVideo,
initCardBodies,
};
Expand Down
Loading