25-02-2025
This commit is contained in:
parent
98084de7d0
commit
70a7776da5
53 changed files with 6719 additions and 833 deletions
137
frontend/src/composables/usePanelDrag.js
Normal file
137
frontend/src/composables/usePanelDrag.js
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
import { ref, onBeforeUnmount } from 'vue'
|
||||
|
||||
/**
|
||||
* Composable for draggable bottom-sheet panels with snap points.
|
||||
*
|
||||
* Snap stops (in dvh): 100, 75, 50
|
||||
* Close threshold: below 25dvh
|
||||
*
|
||||
* @param {Function} onClose - called when panel is dragged below threshold
|
||||
* @returns {{ panelHeight, handleListeners, resetHeight }}
|
||||
*/
|
||||
export function usePanelDrag(onClose) {
|
||||
const SNAP_POINTS = [100, 75, 50, 25] // dvh values
|
||||
const CLOSE_THRESHOLD = 15 // below this → close
|
||||
|
||||
// Current panel height in dvh (null = use CSS default)
|
||||
const panelHeight = ref(null)
|
||||
const isDragging = ref(false)
|
||||
|
||||
let dragging = false
|
||||
let startY = 0
|
||||
let startHeight = 0
|
||||
|
||||
function getViewportHeight() {
|
||||
return window.innerHeight
|
||||
}
|
||||
|
||||
function pxToDvh(px) {
|
||||
return (px / getViewportHeight()) * 100
|
||||
}
|
||||
|
||||
function findNearestSnap(dvh) {
|
||||
let nearest = SNAP_POINTS[0]
|
||||
let minDist = Infinity
|
||||
for (const snap of SNAP_POINTS) {
|
||||
const dist = Math.abs(dvh - snap)
|
||||
if (dist < minDist) {
|
||||
minDist = dist
|
||||
nearest = snap
|
||||
}
|
||||
}
|
||||
return nearest
|
||||
}
|
||||
|
||||
function onPointerDown(e) {
|
||||
// Only primary button / single touch
|
||||
if (e.button && e.button !== 0) return
|
||||
dragging = true
|
||||
isDragging.value = true
|
||||
|
||||
const clientY = e.touches ? e.touches[0].clientY : e.clientY
|
||||
startY = clientY
|
||||
|
||||
// Current height: if panelHeight is set use it, else measure from CSS
|
||||
const currentDvh = panelHeight.value ?? 75
|
||||
startHeight = currentDvh
|
||||
|
||||
document.addEventListener('pointermove', onPointerMove, { passive: false })
|
||||
document.addEventListener('pointerup', onPointerUp)
|
||||
document.addEventListener('touchmove', onTouchMove, { passive: false })
|
||||
document.addEventListener('touchend', onTouchEnd)
|
||||
|
||||
// Prevent text selection
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
function onPointerMove(e) {
|
||||
if (!dragging) return
|
||||
const clientY = e.clientY
|
||||
handleMove(clientY)
|
||||
}
|
||||
|
||||
function onTouchMove(e) {
|
||||
if (!dragging) return
|
||||
if (e.touches.length !== 1) return
|
||||
handleMove(e.touches[0].clientY)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
function handleMove(clientY) {
|
||||
const deltaY = clientY - startY
|
||||
const deltaDvh = pxToDvh(deltaY)
|
||||
const newHeight = Math.max(10, Math.min(100, startHeight - deltaDvh))
|
||||
panelHeight.value = newHeight
|
||||
}
|
||||
|
||||
function onPointerUp() {
|
||||
finishDrag()
|
||||
}
|
||||
|
||||
function onTouchEnd() {
|
||||
finishDrag()
|
||||
}
|
||||
|
||||
function finishDrag() {
|
||||
if (!dragging) return
|
||||
dragging = false
|
||||
isDragging.value = false
|
||||
|
||||
cleanup()
|
||||
|
||||
const currentHeight = panelHeight.value ?? 75
|
||||
if (currentHeight < CLOSE_THRESHOLD) {
|
||||
panelHeight.value = null
|
||||
onClose()
|
||||
} else {
|
||||
// Snap to nearest point
|
||||
panelHeight.value = findNearestSnap(currentHeight)
|
||||
}
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
document.removeEventListener('pointermove', onPointerMove)
|
||||
document.removeEventListener('pointerup', onPointerUp)
|
||||
document.removeEventListener('touchmove', onTouchMove)
|
||||
document.removeEventListener('touchend', onTouchEnd)
|
||||
}
|
||||
|
||||
function resetHeight() {
|
||||
panelHeight.value = null
|
||||
}
|
||||
|
||||
onBeforeUnmount(cleanup)
|
||||
|
||||
// Event listeners to bind on the handle element
|
||||
const handleListeners = {
|
||||
pointerdown: onPointerDown,
|
||||
touchstart: onPointerDown,
|
||||
}
|
||||
|
||||
return {
|
||||
panelHeight,
|
||||
isDragging,
|
||||
handleListeners,
|
||||
resetHeight,
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue