rustdoc: enforce BODY_MIN constraint on sidebar resize

This commit is contained in:
Michael Howell 2023-09-27 08:59:32 -07:00
parent 210c88fc7a
commit 273a302ac8
4 changed files with 155 additions and 17 deletions

View File

@ -9,6 +9,11 @@
:root { :root {
--nav-sub-mobile-padding: 8px; --nav-sub-mobile-padding: 8px;
--search-typename-width: 6.75rem; --search-typename-width: 6.75rem;
/* DEFAULT_SIDEBAR_WIDTH
see main.js for information on these values
and on the RUSTDOC_MOBILE_BREAKPOINT */
--desktop-sidebar-width: 200px;
--src-sidebar-width: 300px;
} }
/* See FiraSans-LICENSE.txt for the Fira Sans license. */ /* See FiraSans-LICENSE.txt for the Fira Sans license. */
@ -383,7 +388,7 @@ img {
.sidebar { .sidebar {
font-size: 0.875rem; font-size: 0.875rem;
flex: 0 0 var(--desktop-sidebar-width, 200px); flex: 0 0 var(--desktop-sidebar-width);
overflow-y: scroll; overflow-y: scroll;
overscroll-behavior: contain; overscroll-behavior: contain;
position: sticky; position: sticky;
@ -414,7 +419,7 @@ img {
position: absolute; position: absolute;
height: 100%; height: 100%;
/* make sure there's a 1px gap between the scrollbar and resize handle */ /* make sure there's a 1px gap between the scrollbar and resize handle */
left: calc(var(--desktop-sidebar-width, 200px) + 1px); left: calc(var(--desktop-sidebar-width) + 1px);
} }
.rustdoc.src .sidebar-resizer { .rustdoc.src .sidebar-resizer {
@ -426,7 +431,7 @@ img {
.src-sidebar-expanded .rustdoc.src .sidebar-resizer { .src-sidebar-expanded .rustdoc.src .sidebar-resizer {
/* for src sidebar, gap is already provided by 1px border on sidebar itself, so place resizer /* for src sidebar, gap is already provided by 1px border on sidebar itself, so place resizer
to right of it */ to right of it */
left: var(--src-sidebar-width, 300px); left: var(--src-sidebar-width);
} }
.sidebar-resizing { .sidebar-resizing {
@ -448,7 +453,7 @@ img {
margin: 0; margin: 0;
/* when active or hovered, place resizer glow on top of the sidebar (right next to, or even /* when active or hovered, place resizer glow on top of the sidebar (right next to, or even
on top of, the scrollbar) */ on top of, the scrollbar) */
left: var(--desktop-sidebar-width, 200px); left: var(--desktop-sidebar-width);
border-left: solid 1px var(--sidebar-resizer-hover); border-left: solid 1px var(--sidebar-resizer-hover);
} }
@ -457,7 +462,7 @@ img {
.src-sidebar-expanded .rustdoc.src .sidebar-resizer:focus, .src-sidebar-expanded .rustdoc.src .sidebar-resizer:focus,
.src-sidebar-expanded .rustdoc.src .sidebar-resizer.active { .src-sidebar-expanded .rustdoc.src .sidebar-resizer.active {
/* when active or hovered, place resizer glow on top of the normal src sidebar border */ /* when active or hovered, place resizer glow on top of the normal src sidebar border */
left: calc(var(--src-sidebar-width, 300px) - 1px); left: calc(var(--src-sidebar-width) - 1px);
} }
@media (pointer: coarse) { @media (pointer: coarse) {
@ -497,7 +502,7 @@ img {
.src-sidebar-expanded .src .sidebar { .src-sidebar-expanded .src .sidebar {
overflow-y: auto; overflow-y: auto;
flex-basis: var(--src-sidebar-width, 300px); flex-basis: var(--src-sidebar-width);
} }
.src-sidebar-expanded .src .sidebar > *:not(#src-sidebar-toggle) { .src-sidebar-expanded .src .sidebar > *:not(#src-sidebar-toggle) {
@ -1806,7 +1811,7 @@ However, it's not needed with smaller screen width because the doc/code block is
/* /*
WARNING: RUSTDOC_MOBILE_BREAKPOINT MEDIA QUERY WARNING: RUSTDOC_MOBILE_BREAKPOINT MEDIA QUERY
If you update this line, then you also need to update the line with the same warning If you update this line, then you also need to update the line with the same warning
in src-script.js in src-script.js and main.js
*/ */
@media (max-width: 700px) { @media (max-width: 700px) {
/* When linking to an item with an `id` (for instance, by clicking a link in the sidebar, /* When linking to an item with an `id` (for instance, by clicking a link in the sidebar,

View File

@ -1273,8 +1273,49 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\
searchState.setup(); searchState.setup();
}()); }());
// This section handles sidebar resizing // Hide, show, and resize the sidebar
//
// The body class and CSS variable are initially set up in storage.js,
// but in this file, we implement:
//
// * the show sidebar button, which appears if the sidebar is hidden
// and, by clicking on it, will bring it back
// * the sidebar resize handle, which appears only on large viewports
// with a [fine precision pointer] to allow the user to change
// the size of the sidebar
//
// [fine precision pointer]: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pointer
(function() { (function() {
// 100 is the size of the logo
// don't let the sidebar get smaller than that, or it'll get squished
const SIDEBAR_MIN = 100;
// Don't let the sidebar get bigger than this
const SIDEBAR_MAX = 500;
// Don't let the body (including the gutter) get smaller than this
//
// WARNING: RUSTDOC_MOBILE_BREAKPOINT MEDIA QUERY
// Acceptable values for BODY_MIN are constrained by the mobile breakpoint
// (which is the minimum size of the whole page where the sidebar exists)
// and the default sidebar width:
//
// BODY_MIN <= RUSTDOC_MOBILE_BREAKPOINT - DEFAULT_SIDEBAR_WIDTH
//
// At the time of this writing, the DEFAULT_SIDEBAR_WIDTH on src pages is
// 300px, and the RUSTDOC_MOBILE_BREAKPOINT is 700px, so BODY_MIN must be
// at most 400px. Otherwise, it would start out at the default size, then
// grabbing the resize handle would suddenly cause it to jank to
// its contraint-generated maximum.
const BODY_MIN = 400;
// At half-way past the minimum size, vanish the sidebar entirely
const SIDEBAR_VANISH_THRESHOLD = SIDEBAR_MIN / 2;
// Toolbar button to show the sidebar.
//
// On small, "mobile-sized" viewports, it's not persistent and it
// can only be activated by going into Settings and hiding the nav bar.
// On larger, "desktop-sized" viewports (though that includes many
// tablets), it's fixed-position, appears in the left side margin,
// and it can be activated by resizing the sidebar into nothing.
const sidebarButton = document.getElementById("sidebar-button"); const sidebarButton = document.getElementById("sidebar-button");
if (sidebarButton) { if (sidebarButton) {
sidebarButton.addEventListener("click", e => { sidebarButton.addEventListener("click", e => {
@ -1283,13 +1324,38 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\
e.preventDefault(); e.preventDefault();
}); });
} }
// Pointer capture.
//
// Resizing is a single-pointer gesture. Any secondary pointer is ignored
let currentPointerId = null; let currentPointerId = null;
// "Desired" sidebar size.
//
// This is stashed here for window resizing. If the sidebar gets
// shrunk to maintain BODY_MIN, and then the user grows the window again,
// it gets the sidebar to restore its size.
let desiredSidebarSize = null;
// If this page has no sidebar at all, bail out.
const resizer = document.querySelector(".sidebar-resizer"); const resizer = document.querySelector(".sidebar-resizer");
const sidebar = document.querySelector(".sidebar"); const sidebar = document.querySelector(".sidebar");
if (!resizer || !sidebar) { if (!resizer || !sidebar) {
return; return;
} }
// src page and docs page use different variables, because the contents of
// the sidebar are so different that it's reasonable to thing the user
// would want them to have different sizes
const isSrcPage = hasClass(document.body, "src"); const isSrcPage = hasClass(document.body, "src");
// Call this function to hide the sidebar when using the resize handle
//
// This function also nulls out the sidebar width CSS variable and setting,
// causing it to return to its default. This does not happen if you do it
// from settings.js, which uses a separate function. It's done here because
// the minimum sidebar size is rather uncomfortable, and it must pass
// through that size when using the shrink-to-nothing gesture.
function hideSidebar() { function hideSidebar() {
if (isSrcPage) { if (isSrcPage) {
window.rustdocCloseSourceSidebar(); window.rustdocCloseSourceSidebar();
@ -1302,6 +1368,13 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\
document.documentElement.style.removeProperty("--desktop-sidebar-width"); document.documentElement.style.removeProperty("--desktop-sidebar-width");
} }
} }
// Call this function to show the sidebar from the resize handle.
// On docs pages, this can only happen if the user has grabbed the resize
// handle, shrunk the sidebar down to nothing, and then pulls back into
// the visible range without releasing it. You can, however, grab the
// resize handle on a source page with the sidebar closed, because it
// remains visible all the time on there.
function showSidebar() { function showSidebar() {
if (isSrcPage) { if (isSrcPage) {
window.rustdocShowSourceSidebar(); window.rustdocShowSourceSidebar();
@ -1310,6 +1383,9 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\
updateLocalStorage("hide-sidebar", "false"); updateLocalStorage("hide-sidebar", "false");
} }
} }
// Call this to set the correct CSS variable and setting.
// This function doesn't enforce size constraints. Do that before calling it!
function changeSidebarSize(size) { function changeSidebarSize(size) {
if (isSrcPage) { if (isSrcPage) {
updateLocalStorage("src-sidebar-width", size); updateLocalStorage("src-sidebar-width", size);
@ -1319,34 +1395,54 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\
document.documentElement.style.setProperty("--desktop-sidebar-width", size + "px"); document.documentElement.style.setProperty("--desktop-sidebar-width", size + "px");
} }
} }
// Check if the sidebar is hidden. Since src pages and doc pages have
// different settings, this function has to check that.
function isSidebarHidden() { function isSidebarHidden() {
return isSrcPage ? return isSrcPage ?
!hasClass(document.documentElement, "src-sidebar-expanded") : !hasClass(document.documentElement, "src-sidebar-expanded") :
hasClass(document.documentElement, "hide-sidebar"); hasClass(document.documentElement, "hide-sidebar");
} }
// Respond to the resize handle event.
// This function enforces size constraints, and implements the
// shrink-to-nothing gesture based on thresholds defined above.
function resize(e) { function resize(e) {
if (currentPointerId === null || currentPointerId !== e.pointerId) { if (currentPointerId === null || currentPointerId !== e.pointerId) {
return; return;
} }
e.preventDefault(); e.preventDefault();
const pos = e.clientX - sidebar.offsetLeft - 3; const pos = e.clientX - sidebar.offsetLeft - 3;
if (pos < 50) { if (pos < SIDEBAR_VANISH_THRESHOLD) {
hideSidebar(); hideSidebar();
} else if (pos >= 100) { } else if (pos >= SIDEBAR_MIN) {
// 100 is the size of the logo
// don't let the sidebar get smaller than that, or it'll get squished
if (isSidebarHidden()) { if (isSidebarHidden()) {
showSidebar(); showSidebar();
} }
// don't let the sidebar get wider than 500 // don't let the sidebar get wider than SIDEBAR_MAX, or the body narrower
changeSidebarSize(Math.min(pos, window.innerWidth - 100, 500)); // than BODY_MIN
const constrainedPos = Math.min(pos, window.innerWidth - BODY_MIN, SIDEBAR_MAX);
changeSidebarSize(constrainedPos);
desiredSidebarSize = constrainedPos;
} }
} }
// Respond to the window resize event.
window.addEventListener("resize", () => {
stopResize();
if (desiredSidebarSize >= (window.innerWidth - BODY_MIN)) {
changeSidebarSize(window.innerWidth - BODY_MIN);
} else if (desiredSidebarSize !== null && desiredSidebarSize > SIDEBAR_MIN) {
changeSidebarSize(desiredSidebarSize);
}
});
function stopResize(e) { function stopResize(e) {
if (currentPointerId === null) { if (currentPointerId === null) {
return; return;
} }
e.preventDefault(); if (e) {
e.preventDefault();
}
desiredSidebarSize = sidebar.getBoundingClientRect().width;
removeClass(resizer, "active"); removeClass(resizer, "active");
window.removeEventListener("pointermove", resize, false); window.removeEventListener("pointermove", resize, false);
window.removeEventListener("pointerup", stopResize, false); window.removeEventListener("pointerup", stopResize, false);
@ -1376,6 +1472,7 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\
window.addEventListener("pointerup", stopResize, false); window.addEventListener("pointerup", stopResize, false);
addClass(resizer, "active"); addClass(resizer, "active");
addClass(document.documentElement, "sidebar-resizing"); addClass(document.documentElement, "sidebar-resizing");
desiredSidebarSize = null;
} }
resizer.addEventListener("pointerdown", initResize, false); resizer.addEventListener("pointerdown", initResize, false);
}()); }());

View File

@ -196,18 +196,21 @@ if (getSettingValue("use-system-theme") !== "false" && window.matchMedia) {
updateTheme(); updateTheme();
// Hide, show, and resize the sidebar at page load time
//
// This needs to be done here because this JS is render-blocking,
// so that the sidebar doesn't "jump" after appearing on screen.
// The user interaction to change this is set up in main.js.
if (getSettingValue("source-sidebar-show") === "true") { if (getSettingValue("source-sidebar-show") === "true") {
// At this point in page load, `document.body` is not available yet. // At this point in page load, `document.body` is not available yet.
// Set a class on the `<html>` element instead. // Set a class on the `<html>` element instead.
addClass(document.documentElement, "src-sidebar-expanded"); addClass(document.documentElement, "src-sidebar-expanded");
} }
if (getSettingValue("hide-sidebar") === "true") { if (getSettingValue("hide-sidebar") === "true") {
// At this point in page load, `document.body` is not available yet. // At this point in page load, `document.body` is not available yet.
// Set a class on the `<html>` element instead. // Set a class on the `<html>` element instead.
addClass(document.documentElement, "hide-sidebar"); addClass(document.documentElement, "hide-sidebar");
} }
function updateSidebarWidth() { function updateSidebarWidth() {
const desktopSidebarWidth = getSettingValue("desktop-sidebar-width"); const desktopSidebarWidth = getSettingValue("desktop-sidebar-width");
if (desktopSidebarWidth && desktopSidebarWidth !== "null") { if (desktopSidebarWidth && desktopSidebarWidth !== "null") {

View File

@ -0,0 +1,33 @@
// Checks sidebar resizing
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
set-window-size: (1280, 600)
wait-for-property: (".sidebar", {"clientWidth": 200}, [NEAR])
// resize past maximum (don't grow past 500)
drag-and-drop: ((205, 100), (600, 100))
wait-for-property: (".sidebar", {"clientWidth": 500}, [NEAR])
// make the window small enough that the sidebar has to shrink
set-window-size: (750, 600)
wait-for-property: (".sidebar", {"clientWidth": 350}, [NEAR])
// grow the window again to make the sidebar bigger
set-window-size: (1280, 600)
wait-for-property: (".sidebar", {"clientWidth": 500}, [NEAR])
// make the window small enough that the sidebar has to shrink
set-window-size: (750, 600)
wait-for-property: (".sidebar", {"clientWidth": 350}, [NEAR])
// grow the window again to make the sidebar bigger
set-window-size: (1280, 600)
wait-for-property: (".sidebar", {"clientWidth": 500}, [NEAR])
// shrink back down again, then reload the page
// the "desired size" is a bit of remembered implicit state,
// and rustdoc tries to minimize things like this
set-window-size: (800, 600)
wait-for-property: (".sidebar", {"clientWidth": 400}, [NEAR])
reload:
set-window-size: (1280, 600)
wait-for-property: (".sidebar", {"clientWidth": 400}, [NEAR])