tags with a hash property), we expand the section we're
// jumping to *before* jumping there. We can't do this in onHashChange, because it changes
// the height of the document so we wind up scrolled to the wrong place.
el.addEventListener("click", () => {
expandSection(el.hash.slice(1));
hideSidebar();
});
});
onEachLazy(document.querySelectorAll(".toggle > summary:not(.hideme)"), el => {
// @ts-expect-error
el.addEventListener("click", e => {
if (e.target.tagName !== "SUMMARY" && e.target.tagName !== "A") {
e.preventDefault();
}
});
});
/**
* Show a tooltip immediately.
*
* @param {HTMLElement} e - The tooltip's anchor point. The DOM is consulted to figure
* out what the tooltip should contain, and where it should be
* positioned.
*/
function showTooltip(e) {
const notable_ty = e.getAttribute("data-notable-ty");
// @ts-expect-error
if (!window.NOTABLE_TRAITS && notable_ty) {
const data = document.getElementById("notable-traits-data");
if (data) {
// @ts-expect-error
window.NOTABLE_TRAITS = JSON.parse(data.innerText);
} else {
throw new Error("showTooltip() called with notable without any notable traits!");
}
}
// Make this function idempotent. If the tooltip is already shown, avoid doing extra work
// and leave it alone.
// @ts-expect-error
if (window.CURRENT_TOOLTIP_ELEMENT && window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE === e) {
// @ts-expect-error
clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);
return;
}
window.hideAllModals(false);
const wrapper = document.createElement("div");
if (notable_ty) {
wrapper.innerHTML = "" +
// @ts-expect-error
window.NOTABLE_TRAITS[notable_ty] + "
";
} else {
// Replace any `title` attribute with `data-title` to avoid double tooltips.
const ttl = e.getAttribute("title");
if (ttl !== null) {
e.setAttribute("data-title", ttl);
e.removeAttribute("title");
}
const dttl = e.getAttribute("data-title");
if (dttl !== null) {
const titleContent = document.createElement("div");
titleContent.className = "content";
titleContent.appendChild(document.createTextNode(dttl));
wrapper.appendChild(titleContent);
}
}
wrapper.className = "tooltip popover";
const focusCatcher = document.createElement("div");
focusCatcher.setAttribute("tabindex", "0");
// @ts-expect-error
focusCatcher.onfocus = hideTooltip;
wrapper.appendChild(focusCatcher);
const pos = e.getBoundingClientRect();
// 5px overlap so that the mouse can easily travel from place to place
wrapper.style.top = (pos.top + window.scrollY + pos.height) + "px";
// @ts-expect-error
wrapper.style.left = 0;
wrapper.style.right = "auto";
wrapper.style.visibility = "hidden";
document.body.appendChild(wrapper);
const wrapperPos = wrapper.getBoundingClientRect();
// offset so that the arrow points at the center of the "(i)"
const finalPos = pos.left + window.scrollX - wrapperPos.width + 24;
if (finalPos > 0) {
wrapper.style.left = finalPos + "px";
} else {
wrapper.style.setProperty(
"--popover-arrow-offset",
(wrapperPos.right - pos.right + 4) + "px",
);
}
wrapper.style.visibility = "";
// @ts-expect-error
window.CURRENT_TOOLTIP_ELEMENT = wrapper;
// @ts-expect-error
window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE = e;
// @ts-expect-error
clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);
wrapper.onpointerenter = ev => {
// If this is a synthetic touch event, ignore it. A click event will be along shortly.
if (ev.pointerType !== "mouse") {
return;
}
clearTooltipHoverTimeout(e);
};
wrapper.onpointerleave = ev => {
// If this is a synthetic touch event, ignore it. A click event will be along shortly.
if (ev.pointerType !== "mouse" || !(ev.relatedTarget instanceof HTMLElement)) {
return;
}
if (!e.TOOLTIP_FORCE_VISIBLE && !e.contains(ev.relatedTarget)) {
// See "Tooltip pointer leave gesture" below.
setTooltipHoverTimeout(e, false);
addClass(wrapper, "fade-out");
}
};
}
/**
* Show or hide the tooltip after a timeout. If a timeout was already set before this function
* was called, that timeout gets cleared. If the tooltip is already in the requested state,
* this function will still clear any pending timeout, but otherwise do nothing.
*
* @param {HTMLElement} element - The tooltip's anchor point. The DOM is consulted to figure
* out what the tooltip should contain, and where it should be
* positioned.
* @param {boolean} show - If true, the tooltip will be made visible. If false, it will
* be hidden.
*/
function setTooltipHoverTimeout(element, show) {
clearTooltipHoverTimeout(element);
// @ts-expect-error
if (!show && !window.CURRENT_TOOLTIP_ELEMENT) {
// To "hide" an already hidden element, just cancel its timeout.
return;
}
// @ts-expect-error
if (show && window.CURRENT_TOOLTIP_ELEMENT) {
// To "show" an already visible element, just cancel its timeout.
return;
}
// @ts-expect-error
if (window.CURRENT_TOOLTIP_ELEMENT &&
// @ts-expect-error
window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE !== element) {
// Don't do anything if another tooltip is already visible.
return;
}
element.TOOLTIP_HOVER_TIMEOUT = setTimeout(() => {
if (show) {
showTooltip(element);
} else if (!element.TOOLTIP_FORCE_VISIBLE) {
hideTooltip(false);
}
}, show ? window.RUSTDOC_TOOLTIP_HOVER_MS : window.RUSTDOC_TOOLTIP_HOVER_EXIT_MS);
}
/**
* If a show/hide timeout was set by `setTooltipHoverTimeout`, cancel it. If none exists,
* do nothing.
*
* @param {HTMLElement} element - The tooltip's anchor point,
* as passed to `setTooltipHoverTimeout`.
*/
function clearTooltipHoverTimeout(element) {
if (element.TOOLTIP_HOVER_TIMEOUT !== undefined) {
// @ts-expect-error
removeClass(window.CURRENT_TOOLTIP_ELEMENT, "fade-out");
clearTimeout(element.TOOLTIP_HOVER_TIMEOUT);
delete element.TOOLTIP_HOVER_TIMEOUT;
}
}
// @ts-expect-error
function tooltipBlurHandler(event) {
// @ts-expect-error
if (window.CURRENT_TOOLTIP_ELEMENT &&
// @ts-expect-error
!window.CURRENT_TOOLTIP_ELEMENT.contains(document.activeElement) &&
// @ts-expect-error
!window.CURRENT_TOOLTIP_ELEMENT.contains(event.relatedTarget) &&
// @ts-expect-error
!window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.contains(document.activeElement) &&
// @ts-expect-error
!window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.contains(event.relatedTarget)
) {
// Work around a difference in the focus behaviour between Firefox, Chrome, and Safari.
// When I click the button on an already-opened tooltip popover, Safari
// hides the popover and then immediately shows it again, while everyone else hides it
// and it stays hidden.
//
// To work around this, make sure the click finishes being dispatched before
// hiding the popover. Since `hideTooltip()` is idempotent, this makes Safari behave
// consistently with the other two.
setTimeout(() => hideTooltip(false), 0);
}
}
/**
* Hide the current tooltip immediately.
*
* @param {boolean} focus - If set to `true`, move keyboard focus to the tooltip anchor point.
* If set to `false`, leave keyboard focus alone.
*/
function hideTooltip(focus) {
// @ts-expect-error
if (window.CURRENT_TOOLTIP_ELEMENT) {
// @ts-expect-error
if (window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.TOOLTIP_FORCE_VISIBLE) {
if (focus) {
// @ts-expect-error
window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.focus();
}
// @ts-expect-error
window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.TOOLTIP_FORCE_VISIBLE = false;
}
// @ts-expect-error
document.body.removeChild(window.CURRENT_TOOLTIP_ELEMENT);
// @ts-expect-error
clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);
// @ts-expect-error
window.CURRENT_TOOLTIP_ELEMENT = null;
}
}
onEachLazy(document.getElementsByClassName("tooltip"), e => {
e.onclick = () => {
e.TOOLTIP_FORCE_VISIBLE = e.TOOLTIP_FORCE_VISIBLE ? false : true;
// @ts-expect-error
if (window.CURRENT_TOOLTIP_ELEMENT && !e.TOOLTIP_FORCE_VISIBLE) {
hideTooltip(true);
} else {
showTooltip(e);
// @ts-expect-error
window.CURRENT_TOOLTIP_ELEMENT.setAttribute("tabindex", "0");
// @ts-expect-error
window.CURRENT_TOOLTIP_ELEMENT.focus();
// @ts-expect-error
window.CURRENT_TOOLTIP_ELEMENT.onblur = tooltipBlurHandler;
}
return false;
};
// @ts-expect-error
e.onpointerenter = ev => {
// If this is a synthetic touch event, ignore it. A click event will be along shortly.
if (ev.pointerType !== "mouse") {
return;
}
setTooltipHoverTimeout(e, true);
};
// @ts-expect-error
e.onpointermove = ev => {
// If this is a synthetic touch event, ignore it. A click event will be along shortly.
if (ev.pointerType !== "mouse") {
return;
}
setTooltipHoverTimeout(e, true);
};
// @ts-expect-error
e.onpointerleave = ev => {
// If this is a synthetic touch event, ignore it. A click event will be along shortly.
if (ev.pointerType !== "mouse") {
return;
}
// @ts-expect-error
if (!e.TOOLTIP_FORCE_VISIBLE && window.CURRENT_TOOLTIP_ELEMENT &&
// @ts-expect-error
!window.CURRENT_TOOLTIP_ELEMENT.contains(ev.relatedTarget)) {
// Tooltip pointer leave gesture:
//
// Designing a good hover microinteraction is a matter of guessing user
// intent from what are, literally, vague gestures. In this case, guessing if
// hovering in or out of the tooltip base is intentional or not.
//
// To figure this out, a few different techniques are used:
//
// * When the mouse pointer enters a tooltip anchor point, its hitbox is grown
// on the bottom, where the popover is/will appear. Search "hover tunnel" in
// rustdoc.css for the implementation.
// * There's a delay when the mouse pointer enters the popover base anchor, in
// case the mouse pointer was just passing through and the user didn't want
// to open it.
// * Similarly, a delay is added when exiting the anchor, or the popover
// itself, before hiding it.
// * A fade-out animation is layered onto the pointer exit delay to immediately
// inform the user that they successfully dismissed the popover, while still
// providing a way for them to cancel it if it was a mistake and they still
// wanted to interact with it.
// * No animation is used for revealing it, because we don't want people to try
// to interact with an element while it's in the middle of fading in: either
// they're allowed to interact with it while it's fading in, meaning it can't
// serve as mistake-proofing for the popover, or they can't, but
// they might try and be frustrated.
//
// See also:
// * https://www.nngroup.com/articles/timing-exposing-content/
// * https://www.nngroup.com/articles/tooltip-guidelines/
// * https://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown
setTooltipHoverTimeout(e, false);
// @ts-expect-error
addClass(window.CURRENT_TOOLTIP_ELEMENT, "fade-out");
}
};
});
const sidebar_menu_toggle = document.getElementsByClassName("sidebar-menu-toggle")[0];
if (sidebar_menu_toggle) {
sidebar_menu_toggle.addEventListener("click", () => {
const sidebar = document.getElementsByClassName("sidebar")[0];
// @ts-expect-error
if (!hasClass(sidebar, "shown")) {
showSidebar();
} else {
hideSidebar();
}
});
}
// @ts-expect-error
function helpBlurHandler(event) {
// @ts-expect-error
if (!getHelpButton().contains(document.activeElement) &&
// @ts-expect-error
!getHelpButton().contains(event.relatedTarget) &&
// @ts-expect-error
!getSettingsButton().contains(document.activeElement) &&
// @ts-expect-error
!getSettingsButton().contains(event.relatedTarget)
) {
window.hidePopoverMenus();
}
}
function buildHelpMenu() {
const book_info = document.createElement("span");
const drloChannel = `https://doc.rust-lang.org/${getVar("channel")}`;
book_info.className = "top";
book_info.innerHTML = `You can find more information in \