feat: unify draggable creation button
ci / docker (push) Successful in 3m23s

This commit is contained in:
2026-09-07 12:04:34 +08:00
parent 373c038fe7
commit acab663226
6 changed files with 161 additions and 114 deletions
@@ -0,0 +1,55 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { Plus } from 'lucide-vue-next'
import { clampFabPosition, isFabDrag } from '../lib/mvp-utils'
const props = withDefaults(defineProps<{ label?: string }>(), { label: '添加' })
const emit = defineEmits<{ activate: [origin: { x: number; y: number }] }>()
const dragging = ref(false)
const position = ref<{ x: number; y: number } | null>(null)
const pointer = ref<{ id: number; startX: number; startY: number; originX: number; originY: number } | null>(null)
let suppressClick = false
const buttonStyle = computed(() => position.value
? { left: `${position.value.x}px`, top: `${position.value.y}px`, right: 'auto', bottom: 'auto' }
: {})
function startDrag(event: PointerEvent) {
const target = event.currentTarget as HTMLElement
const rect = target.getBoundingClientRect()
pointer.value = { id: event.pointerId, startX: event.clientX, startY: event.clientY, originX: rect.left, originY: rect.top }
try { target.setPointerCapture?.(event.pointerId) } catch { /* pointer capture unavailable in synthetic events */ }
}
function moveDrag(event: PointerEvent) {
const start = pointer.value
if (!start || start.id !== event.pointerId) return
const deltaX = event.clientX - start.startX
const deltaY = event.clientY - start.startY
if (!dragging.value && !isFabDrag(deltaX, deltaY)) return
dragging.value = true
position.value = clampFabPosition(start.originX + deltaX, start.originY + deltaY, window.innerWidth, window.innerHeight, 58, 14, 84)
}
function finishDrag(event: PointerEvent) {
const start = pointer.value
if (!start || start.id !== event.pointerId) return
const dragged = dragging.value || isFabDrag(event.clientX - start.startX, event.clientY - start.startY)
pointer.value = null
dragging.value = false
if (dragged) {
suppressClick = true
window.setTimeout(() => { suppressClick = false }, 180)
}
}
function activate(event: MouseEvent) {
if (suppressClick) return
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect()
emit('activate', { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 })
}
</script>
<template>
<button class="unified-fab" :class="{ dragging }" :style="buttonStyle" :aria-label="props.label" @pointerdown="startDrag" @pointermove="moveDrag" @pointerup="finishDrag" @pointercancel="finishDrag" @click="activate">
<span class="unified-fab-aura" aria-hidden="true" />
<span class="unified-fab-core"><Plus /></span>
</button>
</template>