unified gost cards (mouse + touch)
All checks were successful
Deploy App / build (push) Successful in 31s
Deploy App / deploy (push) Successful in 15s

This commit is contained in:
valere
2025-12-24 12:25:58 +01:00
parent ad938abf79
commit d8fe645e5c

View File

@@ -1,5 +1,6 @@
<template> <template>
<article role="button" @click.stop="cardClick" @keydown.enter.stop="cardClick" @keydown.space.prevent.stop="cardClick" <article :role="props.role" @click.stop="cardClick" @keydown.enter.stop="cardClick" draggable="true"
@dragstart="dragStart" @dragend="dragEnd" @drag="dragMove" @keydown.space.prevent.stop="cardClick"
@touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd" class="card" :class="[ @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd" class="card" :class="[
isFaceUp ? 'face-up' : 'face-down', isFaceUp ? 'face-up' : 'face-down',
{ 'current-track': playerStore.currentTrack?.id === track.id }, { 'current-track': playerStore.currentTrack?.id === track.id },
@@ -7,7 +8,7 @@
]"> ]">
<div class="flip-inner" ref="cardElement"> <div class="flip-inner" ref="cardElement">
<!-- Face-Up --> <!-- Face-Up -->
<main draggable="true" @dragstart="dragStart" @dragend="dragEnd" <main
class="face-up backdrop-blur-sm border-2 z-10 card w-56 h-80 p-3 hover:shadow-xl hover:scale-110 transition-all rounded-2xl shadow-lg flex flex-col overflow-hidden"> class="face-up backdrop-blur-sm border-2 z-10 card w-56 h-80 p-3 hover:shadow-xl hover:scale-110 transition-all rounded-2xl shadow-lg flex flex-col overflow-hidden">
<div v-if="isPlaylistTrack" class="flex items-center justify-center size-7 absolute top-7 left-7"> <div v-if="isPlaylistTrack" class="flex items-center justify-center size-7 absolute top-7 left-7">
@@ -60,7 +61,7 @@
</div> </div>
</article> </article>
<!-- Clone fantôme pour le drag tactile --> <!-- Clone fantôme unifié pour drag souris ET tactile -->
<Teleport to="body"> <Teleport to="body">
<div v-if="isDragging && touchClone" ref="ghostElement" <div v-if="isDragging && touchClone" ref="ghostElement"
class="ghost-card fixed pointer-events-none z-[9999] w-56 h-80" :style="{ class="ghost-card fixed pointer-events-none z-[9999] w-56 h-80" :style="{
@@ -99,12 +100,14 @@ import { useDataStore } from '~/store/data';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
track?: Track; track?: Track;
isFaceUp?: boolean; isFaceUp?: boolean;
role?: string;
}>(), { }>(), {
track: () => { track: () => {
const dataStore = useDataStore(); const dataStore = useDataStore();
return dataStore.getRandomPlaylistTrack() || {} as Track; return dataStore.getRandomPlaylistTrack() || {} as Track;
}, },
isFaceUp: true, isFaceUp: true,
role: 'button'
}) })
const playerStore = usePlayerStore() const playerStore = usePlayerStore()
@@ -120,7 +123,7 @@ const isDragging = ref(false)
const cardElement = ref<HTMLElement | null>(null) const cardElement = ref<HTMLElement | null>(null)
const ghostElement = ref<HTMLElement | null>(null) const ghostElement = ref<HTMLElement | null>(null)
// État pour le tactile // État unifié pour souris et tactile
const touchClone = ref<{ x: number, y: number } | null>(null) const touchClone = ref<{ x: number, y: number } | null>(null)
const touchStartPos = ref<{ x: number, y: number } | null>(null) const touchStartPos = ref<{ x: number, y: number } | null>(null)
const longPressTimer = ref<number | null>(null) const longPressTimer = ref<number | null>(null)
@@ -135,18 +138,40 @@ const cardClick = () => {
hasMovedDuringPress.value = false hasMovedDuringPress.value = false
} }
// Drag desktop // Drag desktop - utilise maintenant ghostElement
const dragStart = (event: DragEvent) => { const dragStart = (event: DragEvent) => {
if (event.dataTransfer && cardElement.value) { if (event.dataTransfer && cardElement.value) {
event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('application/json', JSON.stringify(props.track)); event.dataTransfer.setData('application/json', JSON.stringify(props.track));
// Créer une image transparente pour masquer l'image par défaut du navigateur
const img = new Image();
img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
event.dataTransfer.setDragImage(img, 0, 0);
// Activer le clone fantôme
isDragging.value = true isDragging.value = true
touchClone.value = {
x: event.clientX,
y: event.clientY
}
} }
}; };
// Nouveau: suivre le mouvement de la souris pendant le drag
const dragMove = (event: DragEvent) => {
if (isDragging.value && touchClone.value && event.clientX !== 0 && event.clientY !== 0) {
touchClone.value = {
x: event.clientX,
y: event.clientY
}
}
}
const instance = getCurrentInstance(); const instance = getCurrentInstance();
const dragEnd = (event: DragEvent) => { const dragEnd = (event: DragEvent) => {
isDragging.value = false isDragging.value = false
touchClone.value = null
if (event.dataTransfer?.dropEffect === 'move' && instance?.vnode?.el?.parentNode) { if (event.dataTransfer?.dropEffect === 'move' && instance?.vnode?.el?.parentNode) {
instance.vnode.el.parentNode.removeChild(instance.vnode.el); instance.vnode.el.parentNode.removeChild(instance.vnode.el);
@@ -400,7 +425,7 @@ onUnmounted(() => {
} }
} }
/* Ghost card styles */ /* Ghost card styles - maintenant unifié pour souris et tactile */
.ghost-card { .ghost-card {
transition: none; transition: none;
@@ -417,4 +442,4 @@ onUnmounted(() => {
perspective: 1000px; perspective: 1000px;
} }
} }
</style> </style>