mirror of
https://github.com/PR0M3TH3AN/unredact.git
synced 2026-10-27 08:40:29 +00:00
feat: sheet drag and snap behavior for mobile
Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
1 parent
6075a4f047
commit
1eaf411847
1 file changed
+87
@@ -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();
|
||||||
|
}
|
||||||
Reference in new issue
Block a user