feat: sheet drag and snap behavior for mobile

Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Alex-GilbertandClaude Opus 4.6 committed 2026-02-21 15:55:00 -06:00
1 parent 6075a4f047
commit 1eaf411847
1 file changed
+87
+87
View File
@@ -14,6 +14,85 @@ import { openPopover, closePopover, setOnPopoverClose, updatePosDisplay, initPop
import { stopSolve, acceptSolution, initSolver } from './solver.js'; import { stopSolve, acceptSolution, initSolver } from './solver.js';
// ── Sheet snap management ──
const SNAP_PEEK = 60;
const SNAP_HALF_RATIO = 0.45;
const SNAP_FULL_RATIO = 0.9;
/** @type {'peek'|'half'|'full'} */
let sheetSnap = 'peek';
function getSnapHeight(snap) {
const vh = window.innerHeight;
switch (snap) {
case 'peek': return SNAP_PEEK;
case 'half': return Math.round(vh * SNAP_HALF_RATIO);
case 'full': return Math.round(vh * SNAP_FULL_RATIO);
default: return SNAP_PEEK;
}
}
function setSheetSnap(snap) {
sheetSnap = snap;
const h = getSnapHeight(snap);
document.documentElement.style.setProperty('--sheet-height', h + 'px');
bottomSheet.style.height = h + 'px';
}
function initSheetDrag() {
const handle = document.getElementById('sheet-handle');
if (!handle) return;
let dragState = null;
handle.addEventListener('touchstart', (e) => {
if (e.touches.length !== 1) return;
dragState = {
startY: e.touches[0].clientY,
startHeight: bottomSheet.offsetHeight,
};
bottomSheet.style.transition = 'none';
}, { passive: true });
handle.addEventListener('touchmove', (e) => {
if (!dragState) return;
e.preventDefault();
const dy = dragState.startY - e.touches[0].clientY;
const newH = Math.max(SNAP_PEEK, Math.min(
getSnapHeight('full'),
dragState.startHeight + dy
));
bottomSheet.style.height = newH + 'px';
document.documentElement.style.setProperty('--sheet-height', newH + 'px');
}, { passive: false });
const endDrag = () => {
if (!dragState) return;
bottomSheet.style.transition = '';
const currentH = bottomSheet.offsetHeight;
// Find nearest snap point
const peekH = getSnapHeight('peek');
const halfH = getSnapHeight('half');
const fullH = getSnapHeight('full');
const peekDist = Math.abs(currentH - peekH);
const halfDist = Math.abs(currentH - halfH);
const fullDist = Math.abs(currentH - fullH);
if (peekDist <= halfDist && peekDist <= fullDist) setSheetSnap('peek');
else if (halfDist <= fullDist) setSheetSnap('half');
else setSheetSnap('full');
dragState = null;
};
handle.addEventListener('touchend', endDrag);
handle.addEventListener('touchcancel', endDrag);
}
// ── Font loading ── // ── Font loading ──
async function loadFonts() { async function loadFonts() {
@@ -613,3 +692,11 @@ setOnPopoverClose(stopSolve);
initViewport(); initViewport();
initPopover(); initPopover();
initSolver(); initSolver();
// ── Initialize sheet (mobile only) ──
const isMobile = () => window.matchMedia('(max-width: 768px)').matches;
if (isMobile()) {
setSheetSnap('peek');
initSheetDrag();
}