Add touch drag-to-dismiss gesture to BottomSheet

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 23:15:30 +01:00
parent 96044ae1ed
commit 6d51327e56

View File

@@ -2,7 +2,18 @@
<Teleport to="body"> <Teleport to="body">
<Transition name="sheet"> <Transition name="sheet">
<div v-if="open" class="sheet-backdrop" @click.self="$emit('close')" @keydown.escape="$emit('close')"> <div v-if="open" class="sheet-backdrop" @click.self="$emit('close')" @keydown.escape="$emit('close')">
<div class="sheet" role="dialog" aria-modal="true" :aria-label="label" ref="sheetEl" tabindex="-1"> <div
class="sheet"
role="dialog"
aria-modal="true"
:aria-label="label"
ref="sheetEl"
tabindex="-1"
:style="dragStyle"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@touchend="onTouchEnd"
>
<div class="sheet__handle" aria-hidden="true" /> <div class="sheet__handle" aria-hidden="true" />
<slot /> <slot />
</div> </div>
@@ -12,14 +23,14 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch, nextTick } from 'vue' import { ref, computed, watch, nextTick } from 'vue'
defineProps<{ defineProps<{
open: boolean open: boolean
label: string label: string
}>() }>()
defineEmits<{ const emit = defineEmits<{
close: [] close: []
}>() }>()
@@ -39,6 +50,45 @@ watch(
} }
}, },
) )
/* ── Drag-to-dismiss ── */
const DISMISS_THRESHOLD = 100
const dragY = ref(0)
const dragging = ref(false)
let startY = 0
const dragStyle = computed(() => {
if (!dragging.value || dragY.value <= 0) return undefined
return {
transform: `translateY(${dragY.value}px)`,
transition: 'none',
}
})
function onTouchStart(e: TouchEvent) {
const touch = e.touches[0]
if (!touch) return
startY = touch.clientY
dragging.value = true
dragY.value = 0
}
function onTouchMove(e: TouchEvent) {
if (!dragging.value) return
const touch = e.touches[0]
if (!touch) return
const delta = touch.clientY - startY
if (delta > 0) e.preventDefault()
dragY.value = Math.max(0, delta)
}
function onTouchEnd() {
if (dragY.value >= DISMISS_THRESHOLD) {
emit('close')
}
dragging.value = false
dragY.value = 0
}
</script> </script>
<style scoped> <style scoped>