feat(mobile): pull a sheet down to put it away

Sheets showed a grabber but only closed from their × or the dimmed screen
around them. Pulled down — from the top, or from anywhere while their
content is scrolled to the start — they go; let go short of the way,
they settle back.
This commit is contained in:
l0ng-ai
2026-10-03 20:27:43 +08:00
parent 67e4b22412
commit 3ded29b032
+42
View File
@@ -1686,6 +1686,48 @@ function openSheet(title: string, ...content: Child[]) {
scrim.classList.add("leaving");
setTimeout(() => scrim.remove(), still.matches ? 0 : 220);
};
// Pulled down, it goes, as the phone's own sheets do: from its top, or
// from anywhere while what it holds is scrolled to the start. Let go
// short of the way, it settles back.
let pull: { y: number; t: number; dy: number; claimed: boolean } | null = null;
sheet.addEventListener(
"touchstart",
(e) => {
const target = e.target as Element;
const scroller = target.closest?.(".sheet-body, .diff-code, input, textarea");
if (e.touches.length !== 1 || (scroller && scroller.scrollTop > 0) || target.closest?.("input, textarea")) return;
pull = { y: e.touches[0].clientY, t: e.timeStamp, dy: 0, claimed: false };
},
{ passive: true },
);
sheet.addEventListener(
"touchmove",
(e) => {
if (!pull) return;
const dy = e.touches[0].clientY - pull.y;
if (!pull.claimed) {
if (Math.abs(dy) < 8) return;
if (dy < 0) return void (pull = null);
pull.claimed = true;
sheet.style.transition = "none";
}
e.preventDefault();
pull.dy = Math.max(0, dy);
sheet.style.transform = `translateY(${pull.dy}px)`;
},
{ passive: false },
);
sheet.addEventListener("touchend", (e) => {
const done = pull;
pull = null;
if (!done?.claimed) return;
const fast = done.dy / Math.max(1, e.timeStamp - done.t) > 0.5;
if (done.dy > Math.min(140, sheet.offsetHeight / 3) || fast) close();
else {
sheet.style.transition = "transform 0.25s var(--ease)";
sheet.style.transform = "";
}
});
document.body.append(scrim);
return { close, remove: () => scrim.remove() };
}