167 lines
5.0 KiB
JavaScript
167 lines
5.0 KiB
JavaScript
const MOBILE_SHEET_MQ = "(max-width: 768px)";
|
|
|
|
function getSheetParts(root) {
|
|
if (!root) return null;
|
|
const panel = root.querySelector(
|
|
".recipe-mobile-edit-sheet-panel, .ingredient-mobile-sheet-panel, .recipe-help-modal-panel"
|
|
);
|
|
const body = root.querySelector(
|
|
".recipe-mobile-edit-sheet-body, .ingredient-mobile-sheet-body, .recipe-help-modal-body"
|
|
);
|
|
const backdrop = root.querySelector(".recipe-mobile-edit-sheet-backdrop");
|
|
return panel ? { root, panel, body, backdrop } : null;
|
|
}
|
|
|
|
export function resetRecipeMobileSheetPresentation(root) {
|
|
const parts = getSheetParts(root);
|
|
if (!parts) return;
|
|
const { panel, backdrop } = parts;
|
|
panel.style.transform = "";
|
|
panel.style.transition = "";
|
|
panel.classList.remove("recipe-mobile-edit-sheet--dragging");
|
|
if (backdrop) {
|
|
backdrop.style.opacity = "";
|
|
backdrop.style.transition = "";
|
|
}
|
|
}
|
|
|
|
function canStartSheetDrag(target, body) {
|
|
if (target.closest(".recipe-mobile-edit-sheet-done")) return false;
|
|
if (target.closest("button, input, select, textarea, a, label")) {
|
|
return !!target.closest(".recipe-mobile-edit-sheet-grab, .recipe-mobile-edit-sheet-title");
|
|
}
|
|
if (target.closest(".recipe-mobile-edit-sheet-grab, .recipe-mobile-edit-sheet-header")) {
|
|
return true;
|
|
}
|
|
if (body?.contains(target) && body.scrollTop <= 0) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function bindSheetGestures(root, onClose, mobileQuery) {
|
|
const parts = getSheetParts(root);
|
|
if (!parts || typeof onClose !== "function") return;
|
|
|
|
const { panel, body, backdrop } = parts;
|
|
let dragState = null;
|
|
|
|
function clearDragState() {
|
|
dragState = null;
|
|
panel.classList.remove("recipe-mobile-edit-sheet--dragging");
|
|
}
|
|
|
|
function applyDrag(deltaY) {
|
|
const offset = Math.max(0, deltaY);
|
|
panel.style.transition = "none";
|
|
panel.style.transform = `translateY(${offset}px)`;
|
|
if (backdrop) {
|
|
backdrop.style.transition = "none";
|
|
backdrop.style.opacity = String(Math.max(0, 1 - offset / 280));
|
|
}
|
|
}
|
|
|
|
function snapBack() {
|
|
panel.style.transition = "transform 0.24s ease";
|
|
panel.style.transform = "";
|
|
if (backdrop) {
|
|
backdrop.style.transition = "opacity 0.24s ease";
|
|
backdrop.style.opacity = "";
|
|
}
|
|
window.setTimeout(() => {
|
|
panel.style.transition = "";
|
|
if (backdrop) backdrop.style.transition = "";
|
|
clearDragState();
|
|
}, 240);
|
|
}
|
|
|
|
function dismissWithAnimation() {
|
|
panel.style.transition = "transform 0.22s ease";
|
|
panel.style.transform = "translateY(100%)";
|
|
if (backdrop) {
|
|
backdrop.style.transition = "opacity 0.22s ease";
|
|
backdrop.style.opacity = "0";
|
|
}
|
|
window.setTimeout(() => {
|
|
resetRecipeMobileSheetPresentation(root);
|
|
onClose();
|
|
}, 220);
|
|
}
|
|
|
|
function onPointerDown(event) {
|
|
if (!mobileQuery.matches || root.hidden) return;
|
|
if (event.pointerType === "mouse" && event.button !== 0) return;
|
|
if (!canStartSheetDrag(event.target, body)) return;
|
|
|
|
dragState = {
|
|
pointerId: event.pointerId,
|
|
startY: event.clientY,
|
|
fromScrollableBody: !!(body && body.contains(event.target) && !event.target.closest(".recipe-mobile-edit-sheet-header, .recipe-mobile-edit-sheet-grab")),
|
|
};
|
|
panel.classList.add("recipe-mobile-edit-sheet--dragging");
|
|
panel.setPointerCapture(event.pointerId);
|
|
}
|
|
|
|
function onPointerMove(event) {
|
|
if (!dragState || event.pointerId !== dragState.pointerId) return;
|
|
|
|
const deltaY = event.clientY - dragState.startY;
|
|
if (dragState.fromScrollableBody) {
|
|
if (deltaY <= 0 || (body && body.scrollTop > 0)) {
|
|
clearDragState();
|
|
resetRecipeMobileSheetPresentation(root);
|
|
return;
|
|
}
|
|
if (deltaY > 8) {
|
|
dragState.fromScrollableBody = false;
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (deltaY > 0) {
|
|
event.preventDefault();
|
|
applyDrag(deltaY);
|
|
}
|
|
}
|
|
|
|
function onPointerEnd(event) {
|
|
if (!dragState || event.pointerId !== dragState.pointerId) return;
|
|
|
|
const deltaY = event.clientY - dragState.startY;
|
|
const threshold = Math.min(96, panel.offsetHeight * 0.2);
|
|
clearDragState();
|
|
|
|
if (deltaY > threshold) {
|
|
dismissWithAnimation();
|
|
return;
|
|
}
|
|
snapBack();
|
|
}
|
|
|
|
panel.addEventListener("pointerdown", onPointerDown);
|
|
panel.addEventListener("pointermove", onPointerMove, { passive: false });
|
|
panel.addEventListener("pointerup", onPointerEnd);
|
|
panel.addEventListener("pointercancel", onPointerEnd);
|
|
}
|
|
|
|
export function initRecipeMobileSheetGestures(handlers = {}) {
|
|
const mobileQuery = window.matchMedia(MOBILE_SHEET_MQ);
|
|
const sheets = [
|
|
{
|
|
root: document.getElementById("ingredientMobileSheet"),
|
|
onClose: handlers.closeIngredientMobileSheet,
|
|
},
|
|
{
|
|
root: document.getElementById("unloadingGroupMobileSheet"),
|
|
onClose: handlers.closeUnloadingGroupMobileSheet,
|
|
},
|
|
{
|
|
root: document.getElementById("recipeHelpModal"),
|
|
onClose: handlers.closeRecipeHelpModal,
|
|
},
|
|
];
|
|
|
|
sheets.forEach(({ root, onClose }) => bindSheetGestures(root, onClose, mobileQuery));
|
|
}
|