Add touch drag-to-dismiss gesture to BottomSheet
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user