Skip to content

Commit 77403d1

Browse files
wip
1 parent 5fe7d2e commit 77403d1

6 files changed

Lines changed: 306 additions & 68 deletions

File tree

packages/unity-bootstrap-theme/src/js/key-events.js

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { EventHandler } from "./bootstrap-helper";
2+
3+
function initTooltips() {
4+
/* if this value changes, update tooltips.js file */
5+
const TOOLTIP_MAX_WIDTH = 288;
6+
7+
// This query selector is not just creating a List,
8+
// it's also checking to ensure all 3 elements are present
9+
// (container, trigger, content) and in the correct order
10+
// (trigger immediately followed by content)
11+
const tooltipContentList = document.querySelectorAll(
12+
'.uds-tooltip-container > .uds-tooltip + [role="tooltip"]'
13+
);
14+
15+
function show(e) {
16+
// container or trigger
17+
let trigger = e.target.querySelector(".uds-tooltip") || e.target;
18+
let content = trigger.nextSibling;
19+
20+
if (e.type === "keypress") {
21+
if (e.charCode !== 32) {
22+
return;
23+
}
24+
}
25+
// content.getBoundingClientRect().width +
26+
// trigger.getBoundingClientRect().right >
27+
// window.innerWidth
28+
29+
if (
30+
trigger.getBoundingClientRect().right + TOOLTIP_MAX_WIDTH >
31+
window.innerWidth
32+
) {
33+
content.classList.add("bottom-placement");
34+
} else {
35+
content.classList.remove("bottom-placement");
36+
}
37+
trigger.setAttribute("aria-expanded", "true");
38+
}
39+
40+
function hide(e) {
41+
// container or trigger
42+
let trigger = e.target.querySelector(".uds-tooltip") || e.target;
43+
44+
if (e.type === "mouseleave") {
45+
if (trigger === document.activeElement) {
46+
return;
47+
}
48+
}
49+
trigger.setAttribute("aria-expanded", "false");
50+
}
51+
52+
function kbHide(e) {
53+
if (e.key === "Escape") {
54+
hide(e);
55+
}
56+
}
57+
58+
window.t2 = [...tooltipContentList].map(contentEl => {
59+
const controller = new AbortController();
60+
const { signal } = controller;
61+
const triggerEl = contentEl.previousElementSibling;
62+
const containerEl = triggerEl.parentElement;
63+
64+
// const showEvents = [
65+
// ["mouseenter"],
66+
// ["focus"],
67+
// ["keypress", e => e.charCode === 32 || e.key === "Enter"],
68+
// ];
69+
// const hideEvents = [
70+
// ["mouseleave", e => e.target !== document.activeElement],
71+
// ["blur"],
72+
// ["keydown", e => e.key === "Escape"],
73+
// ];
74+
75+
// containerEl.attachShadow({ mode: "open" });
76+
// containerEl.appendChild(document.createElement("slot"));
77+
// shadowRoot.appendChild(contentEl.cloneNode(true));
78+
79+
triggerEl.addEventListener("mouseenter", show, { signal });
80+
triggerEl.addEventListener("focus", show, { signal });
81+
triggerEl.addEventListener("keypress", show, { signal });
82+
triggerEl.addEventListener("blur", hide, { signal });
83+
triggerEl.addEventListener("keydown", kbHide, { signal });
84+
containerEl.addEventListener("mouseleave", hide, { signal });
85+
86+
return controller;
87+
});
88+
}
89+
90+
EventHandler.on(window, "load.uds.tooltips", initTooltips);
91+
92+
export { initTooltips };
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import { EventHandler } from "./bootstrap-helper";
2+
3+
// WeakMap for better garbage collection
4+
const tooltipInstances = new WeakMap();
5+
const t2 = [];
6+
let isInitialized = false;
7+
window.tooltipInstances = tooltipInstances; // Expose for debugging
8+
window.t2 = t2; // Expose for debugging
9+
10+
function getOrCreateTooltipInstance(triggerEl, contentEl) {
11+
if (!tooltipInstances.has(triggerEl)) {
12+
const popperInstance = new bootstrap.Popover(triggerEl, contentEl, {
13+
placement: "right",
14+
modifiers: [
15+
{
16+
name: "flip",
17+
options: {
18+
fallbackPlacements: ["bottom", "left"],
19+
},
20+
},
21+
{
22+
name: "offset",
23+
options: {
24+
offset: [0, 8],
25+
},
26+
},
27+
],
28+
});
29+
console.log("Creating tooltip instance for:", triggerEl, popperInstance);
30+
tooltipInstances.set(triggerEl, popperInstance);
31+
t2.push(popperInstance);
32+
}
33+
return tooltipInstances.get(triggerEl);
34+
}
35+
36+
function showTooltip(triggerEl) {
37+
const contentEl = triggerEl.nextElementSibling;
38+
if (
39+
!contentEl ||
40+
!contentEl.hasAttribute("role") ||
41+
contentEl.getAttribute("role") !== "tooltip"
42+
) {
43+
return;
44+
}
45+
46+
const popperInstance = getOrCreateTooltipInstance(triggerEl);
47+
triggerEl.setAttribute("aria-expanded", "true");
48+
console.log(popperInstance);
49+
popperInstance.setOptions &&
50+
popperInstance.setOptions(options => ({
51+
...options,
52+
modifiers: [
53+
...options.modifiers,
54+
{ name: "eventListeners", enabled: true },
55+
],
56+
}));
57+
58+
// Update position after showing
59+
popperInstance.update();
60+
}
61+
62+
function hideTooltip(triggerEl) {
63+
triggerEl.setAttribute("aria-expanded", "false");
64+
65+
// Disable the event listeners
66+
if (!getOrCreateTooltipInstance(triggerEl).setOptions) return;
67+
getOrCreateTooltipInstance(triggerEl).setOptions(options => ({
68+
...options,
69+
modifiers: [
70+
...options.modifiers,
71+
{ name: "eventListeners", enabled: false },
72+
],
73+
}));
74+
}
75+
76+
function tryGetTriggerElement(element) {
77+
return element.closest ? element.closest(".uds-tooltip") : element;
78+
}
79+
80+
function isTooltipTrigger(element) {
81+
return (
82+
element?.classList?.contains("uds-tooltip") &&
83+
element.nextElementSibling?.getAttribute("role") === "tooltip"
84+
);
85+
}
86+
87+
// Event delegation handlers
88+
function handleShowEvent(event) {
89+
const triggerEl = tryGetTriggerElement(event.target);
90+
if (!isTooltipTrigger(triggerEl)) return;
91+
92+
// Handle specific event conditions
93+
if (event.type === "keypress") {
94+
if (!(event.charCode === 32 || event.key === "Enter")) return;
95+
}
96+
97+
showTooltip(triggerEl);
98+
}
99+
100+
function handleHideEvent(event) {
101+
const triggerEl = tryGetTriggerElement(event.target);
102+
if (!isTooltipTrigger(triggerEl)) return;
103+
104+
// Handle specific event conditions
105+
if (event.type === "mouseleave") {
106+
if (triggerEl === document.activeElement) return;
107+
}
108+
109+
if (event.target !== triggerEl) return;
110+
hideTooltip(triggerEl);
111+
}
112+
113+
function handleEscapeKey(event) {
114+
if (event.key === "Escape") {
115+
const triggerEl = tryGetTriggerElement(event.target);
116+
if (!isTooltipTrigger(triggerEl)) return;
117+
hideTooltip(triggerEl);
118+
}
119+
}
120+
121+
function initTooltips() {
122+
if (isInitialized) return;
123+
124+
// Check if tooltips exist before setting up listeners
125+
const hasTooltips = document.querySelector(
126+
'.uds-tooltip-container .uds-tooltip + [role="tooltip"]'
127+
);
128+
if (!hasTooltips) return;
129+
130+
// Event delegation - single listeners for all tooltips
131+
document.addEventListener("mouseenter", handleShowEvent, true);
132+
document.addEventListener("focus", handleShowEvent, true);
133+
document.addEventListener("keypress", handleShowEvent, true);
134+
135+
document.addEventListener("mouseleave", handleHideEvent, true);
136+
document.addEventListener("blur", handleHideEvent, true);
137+
document.addEventListener("keydown", handleEscapeKey, true);
138+
139+
isInitialized = true;
140+
}
141+
142+
function destroyTooltips() {
143+
if (!isInitialized) return;
144+
145+
// Remove event listeners
146+
document.removeEventListener("mouseenter", handleShowEvent, true);
147+
document.removeEventListener("focus", handleShowEvent, true);
148+
document.removeEventListener("keypress", handleShowEvent, true);
149+
document.removeEventListener("mouseleave", handleHideEvent, true);
150+
document.removeEventListener("blur", handleHideEvent, true);
151+
document.removeEventListener("keydown", handleEscapeKey, true);
152+
153+
// Clear instances - WeakMap will handle garbage collection
154+
if (tooltipInstances.clear) {
155+
tooltipInstances.clear();
156+
}
157+
158+
isInitialized = false;
159+
}
160+
161+
EventHandler.on(window, "load.uds.tooltips", initTooltips);
162+
163+
export { initTooltips, destroyTooltips };

packages/unity-bootstrap-theme/src/js/unity-bootstrap.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
// import Banner from "./banner.js";
2-
import { initAnchorMenu } from "./anchor-menu.js";
32
import { initBlockquoteAnimation } from "./blockquote-animated.js";
43
import { initCalendar } from "./calendar.js";
54
import { initCardBodies } from "./card-bodies.js";
@@ -13,7 +12,7 @@ import { initImageParallax } from "./image-parallax.js";
1312
import { initModals } from "./modals.js";
1413
import { initTabbedPanels } from "./tabbed-panels.js";
1514
import { initFixedTable } from "./tables.js";
16-
import { initKeyEvents } from "./key-events.js";
15+
import { initTooltips } from "./tooltips.js";
1716
import { initVideo } from "./video.js";
1817

1918
const unityBootstrap = {
@@ -31,7 +30,7 @@ const unityBootstrap = {
3130
initModals,
3231
initRankingCard,
3332
initTabbedPanels,
34-
initKeyEvents,
33+
initTooltips,
3534
initVideo,
3635
initCardBodies,
3736
};

0 commit comments

Comments
 (0)