Skip to content
Merged
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
15 changes: 15 additions & 0 deletions .changeset/rotten-jars-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"@hyperbook/markdown": patch
"@hyperbook/cloud": patch
hyperbook: patch
---

Restore pull-to-refresh and the collapsing address bar on mobile

On narrow screens the shell no longer pins itself to the viewport. It scrolled
the article pane inside a fixed grid, which left the document scroller
motionless — and a mobile browser drives both pull-to-refresh and the
auto-hiding address bar off that scroller, so neither ever fired. Below 1280px
the layout now flows normally with a sticky header, so the document scrolls and
both gestures work again. The desktop layout, where the sidebar needs to stay
put while the article moves, is unchanged.
3 changes: 3 additions & 0 deletions packages/markdown/assets/print.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
* page printed without these rules stops after the first sheet — everything
* below the fold is clipped rather than flowed onto the next page.
*
* Narrow screens already unwind that layout (see the last block in shell.css),
* but they do it behind `@media screen`, so print never inherits it.
*
* These rules unwind the layout back into normal document flow, drop the
* interactive chrome, and open anything collapsed so the printed sheet carries
* the same content the reader can see on screen.
Expand Down
73 changes: 73 additions & 0 deletions packages/markdown/assets/shell.css
Original file line number Diff line number Diff line change
Expand Up @@ -1303,3 +1303,76 @@ img.emoji {
display: inline-block;
max-width: none;
}

/*
* Narrow screens: give scrolling back to the document.
*
* The reading layout pins the shell to the viewport (`.main-grid { position:
* fixed }`, plus `html, body { overflow: hidden }` from the inline root CSS)
* and scrolls `main` on its own, so the sidebar can stay put while the article
* moves. The document itself then never scrolls — and a mobile browser drives
* both pull-to-refresh and the collapsing address bar off the *document*
* scroller, so neither gesture ever fires.
*
* Below the breakpoint none of that is being paid for: the sidebar is already
* a drawer and the grid is a single column. So unwind the shell back into
* normal flow and hold the header in place with `position: sticky` instead.
*
* The rules must come after the base layout in source order — they override it
* on equal specificity — and the shell has to be block-level for the sticky
* header: a grid item sticks only within its own grid area, which for the
* header is exactly the row it already fills.
*
* These rules live after every other breakpoint in this file on purpose. Keep
* them last.
*/
@media screen and (max-width: 1280px) {
html,
body {
overflow: visible;
height: auto;
width: auto;
}

html {
/* Jumping to a heading has to clear the sticky header. */
scroll-padding-top: var(--header-height);
}

body {
/* Rubber-banding past the end exposes the canvas, which without this
paints white behind a dark page. */
background: var(--color-background);
/* The document is the scroller now, so its scrollbar is the one worth
showing — the article pane had one here before. */
scrollbar-width: auto;
}

body::-webkit-scrollbar {
display: block;
}

.main-grid {
display: block;
position: static;
min-height: 100dvh;
}

main {
overflow: visible;
}

header {
position: sticky;
top: 0;
/* The grid row used to size the header; in flow it has to size itself. */
height: var(--header-height);
box-sizing: border-box;
}

/* The impersonation banner is fixed to the top of the viewport, so the header
sticks below it rather than sliding underneath. */
#impersonation-banner + .main-grid header {
top: 36px;
}
}
27 changes: 21 additions & 6 deletions packages/markdown/assets/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,12 +166,27 @@ hyperbook.store = (function () {
window.addEventListener("mousemove", (e) => {
db.currentState.update(1, { mouseX: e.clientX, mouseY: e.clientY });
});
window.addEventListener("scroll", (e) => {
db.currentState.update(1, {
scrollX: window.scrollX,
scrollY: window.scrollY,
});
});
// On narrow screens the document is the scroller, so this fires for the
// whole length of every fling. Coalescing to one write per frame keeps a
// scroll gesture from queueing an IndexedDB write per scroll event.
let scrollWritePending = false;
window.addEventListener(
"scroll",
() => {
if (scrollWritePending) {
return;
}
scrollWritePending = true;
requestAnimationFrame(() => {
scrollWritePending = false;
db.currentState.update(1, {
scrollX: window.scrollX,
scrollY: window.scrollY,
});
});
},
{ passive: true },
);
window.addEventListener("resize", (e) => {
db.currentState.update(1, {
windowWidth: window.innerWidth,
Expand Down
40 changes: 40 additions & 0 deletions platforms/cloud/public/stylesheets/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -344,3 +344,43 @@ nav.breadcrumb {
.data-table th, .data-table td { padding: 8px 6px; font-size: 0.85rem; }
.actions { flex-wrap: wrap; }
}

/*
* Narrow screens: give scrolling back to the document.
*
* The shell above pins itself to the viewport and scrolls `main` on its own,
* which leaves the document scroller motionless — and a mobile browser drives
* pull-to-refresh and the collapsing address bar off that scroller, so neither
* gesture ever fires. Unwind the grid into normal flow and keep the header in
* place with `position: sticky` instead. Mirrors the reading shell in
* packages/markdown/assets/shell.css.
*
* Must stay after the base layout in source order: these override it on equal
* specificity. A grid item can only stick inside its own grid area, hence the
* block-level shell.
*/
@media screen and (max-width: 1280px) {
html, body {
overflow: visible;
height: auto;
width: auto;
}

html { scroll-padding-top: var(--header-height); }

body { background: var(--color-background); }

.main-grid {
display: block;
position: static;
min-height: 100dvh;
}

main { overflow: visible; }

header {
position: sticky;
top: 0;
height: var(--header-height);
}
}