fix(02-04): improve swipe-to-delete with direct touch handlers
- Replace svelte-gestures with native touch event handlers - Add invalidateAll() after save/delete for seamless list updates - Add $effect to sync edit state when entry prop changes - Improve swipe animation with isSwiping state tracking
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import type { Entry } from '$lib/server/db/schema';
|
||||
import { enhance } from '$app/forms';
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { slide } from 'svelte/transition';
|
||||
import { useSwipe, type SwipeCustomEvent } from 'svelte-gestures';
|
||||
|
||||
interface Props {
|
||||
entry: Entry;
|
||||
@@ -13,11 +13,20 @@
|
||||
// Expand/collapse state
|
||||
let expanded = $state(false);
|
||||
|
||||
// Edit state - initialize from entry
|
||||
// Edit state - use $derived to stay in sync with entry prop
|
||||
let editTitle = $state(entry.title || '');
|
||||
let editContent = $state(entry.content);
|
||||
let editType = $state(entry.type);
|
||||
|
||||
// Sync edit state when entry changes (after invalidateAll)
|
||||
$effect(() => {
|
||||
if (!expanded) {
|
||||
editTitle = entry.title || '';
|
||||
editContent = entry.content;
|
||||
editType = entry.type;
|
||||
}
|
||||
});
|
||||
|
||||
// Debounced auto-save
|
||||
let saveTimeout: ReturnType<typeof setTimeout>;
|
||||
let isSaving = $state(false);
|
||||
@@ -25,20 +34,34 @@
|
||||
// Swipe state
|
||||
let swipeOffset = $state(0);
|
||||
let isConfirmingDelete = $state(false);
|
||||
let touchStartX = 0;
|
||||
let isSwiping = $state(false);
|
||||
|
||||
function handleSwipe(event: SwipeCustomEvent) {
|
||||
const { direction } = event.detail;
|
||||
if (direction === 'left') {
|
||||
isConfirmingDelete = true;
|
||||
function handleTouchStart(e: TouchEvent) {
|
||||
touchStartX = e.touches[0].clientX;
|
||||
isSwiping = true;
|
||||
}
|
||||
|
||||
function handleTouchMove(e: TouchEvent) {
|
||||
if (!isSwiping) return;
|
||||
const currentX = e.touches[0].clientX;
|
||||
const diff = currentX - touchStartX;
|
||||
// Only allow left swipe (negative diff), cap at -100px
|
||||
if (diff < 0) {
|
||||
swipeOffset = Math.max(diff, -100);
|
||||
}
|
||||
}
|
||||
|
||||
// Create swipe gesture using useSwipe hook
|
||||
const swipeGesture = useSwipe(handleSwipe, () => ({
|
||||
timeframe: 300,
|
||||
minSwipeDistance: 60,
|
||||
touchAction: 'pan-y'
|
||||
}));
|
||||
function handleTouchEnd() {
|
||||
isSwiping = false;
|
||||
// If swiped far enough, show confirmation
|
||||
if (swipeOffset < -60) {
|
||||
isConfirmingDelete = true;
|
||||
swipeOffset = -100; // Snap to reveal delete area
|
||||
} else {
|
||||
swipeOffset = 0; // Snap back
|
||||
}
|
||||
}
|
||||
|
||||
function cancelDelete() {
|
||||
isConfirmingDelete = false;
|
||||
@@ -54,7 +77,7 @@
|
||||
body: formData
|
||||
});
|
||||
|
||||
window.location.reload();
|
||||
await invalidateAll();
|
||||
}
|
||||
|
||||
async function debouncedSave() {
|
||||
@@ -72,6 +95,8 @@
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
// Refresh data so list shows updated values
|
||||
await invalidateAll();
|
||||
} finally {
|
||||
isSaving = false;
|
||||
}
|
||||
@@ -118,10 +143,12 @@
|
||||
|
||||
<!-- Swipeable entry card -->
|
||||
<article
|
||||
{...swipeGesture}
|
||||
style="transform: translateX({swipeOffset}px); transition: {swipeOffset === 0
|
||||
? 'transform 0.2s'
|
||||
: 'none'}"
|
||||
ontouchstart={handleTouchStart}
|
||||
ontouchmove={handleTouchMove}
|
||||
ontouchend={handleTouchEnd}
|
||||
style="transform: translateX({swipeOffset}px); transition: {isSwiping
|
||||
? 'none'
|
||||
: 'transform 0.2s'}"
|
||||
class="p-4 border-b border-gray-100 md:border md:rounded-lg md:shadow-sm md:mb-3 bg-white relative"
|
||||
>
|
||||
<!-- Collapsed view - clickable header -->
|
||||
|
||||
Reference in New Issue
Block a user