diff --git a/src/components/EventDialog.vue b/src/components/EventDialog.vue index d7eca9c..3d4c133 100644 --- a/src/components/EventDialog.vue +++ b/src/components/EventDialog.vue @@ -16,6 +16,8 @@ const calendarStore = useCalendarStore() const showDialog = ref(false) const dialogMode = ref('create') // 'create' or 'edit' const editingEventId = ref(null) // base event id if repeating occurrence clicked +// Track the id of an unsaved event created in the current create dialog session +const unsavedCreateId = ref(null) const occurrenceContext = ref(null) // { baseId, occurrenceIndex, weekday, occurrenceDate } const title = ref('') const recurrenceEnabled = ref(false) @@ -26,6 +28,66 @@ const recurrenceOccurrences = ref(0) // 0 = unlimited const colorId = ref(0) const eventSaved = ref(false) const titleInput = ref(null) +const modalRef = ref(null) + +// Drag functionality +const isDragging = ref(false) +const dragOffset = ref({ x: 0, y: 0 }) +const modalPosition = ref({ x: 0, y: 0 }) + +function startDrag(event) { + if (!modalRef.value) return + + isDragging.value = true + const rect = modalRef.value.getBoundingClientRect() + dragOffset.value = { + x: event.clientX - rect.left, + y: event.clientY - rect.top, + } + + // Set pointer capture for better touch handling + if (event.pointerId !== undefined) { + try { + event.target.setPointerCapture(event.pointerId) + } catch (e) { + console.warn('Could not set pointer capture:', e) + } + } + + document.addEventListener('pointermove', handleDrag, { passive: false }) + document.addEventListener('pointerup', stopDrag) + document.addEventListener('pointercancel', stopDrag) + event.preventDefault() +} + +function handleDrag(event) { + if (!isDragging.value || !modalRef.value) return + + modalPosition.value = { + x: event.clientX - dragOffset.value.x, + y: event.clientY - dragOffset.value.y, + } + + event.preventDefault() +} + +function stopDrag() { + isDragging.value = false + document.removeEventListener('pointermove', handleDrag) + document.removeEventListener('pointerup', stopDrag) + document.removeEventListener('pointercancel', stopDrag) +} + +const modalStyle = computed(() => { + if (modalPosition.value.x !== 0 || modalPosition.value.y !== 0) { + return { + transform: 'none', + left: `${modalPosition.value.x}px`, + top: `${modalPosition.value.y}px`, + } + } + return {} +}) // Helper to get starting weekday (Sunday-first index) function getStartingWeekday(selectionData = null) { @@ -94,6 +156,14 @@ const selectedColor = computed({ }) function openCreateDialog(selectionData = null) { + // Start a fresh create dialog; delete any prior unsaved create + if (unsavedCreateId.value && !eventSaved.value) { + if (calendarStore.events?.has(unsavedCreateId.value)) { + calendarStore.deleteEvent(unsavedCreateId.value) + } + unsavedCreateId.value = null + } + // Reset saved flag for new create const currentSelection = selectionData || props.selection // Convert new format to start/end for compatibility with existing logic @@ -135,6 +205,8 @@ function openCreateDialog(selectionData = null) { recurrenceOccurrences.value === 0 ? 'for now' : String(recurrenceOccurrences.value), repeatWeekdays: buildStoreWeekdayPattern(), }) + // Track unsaved create id until saved or discarded + unsavedCreateId.value = editingEventId.value showDialog.value = true @@ -150,6 +222,20 @@ function openCreateDialog(selectionData = null) { } function openEditDialog(payload) { + // If we are switching away from an unsaved create to a DIFFERENT event, remove the temporary one. + // If user clicked the same newly created event again, keep it (no deletion, no dialog flicker). + if ( + dialogMode.value === 'create' && + unsavedCreateId.value && + !eventSaved.value && + payload && + unsavedCreateId.value !== payload.id + ) { + if (calendarStore.events?.has(unsavedCreateId.value)) { + calendarStore.deleteEvent(unsavedCreateId.value) + } + unsavedCreateId.value = null + } occurrenceContext.value = null if (!payload) return // Payload expected: { id: baseId, instanceId, occurrenceIndex } @@ -236,9 +322,13 @@ function openEditDialog(payload) { function closeDialog() { showDialog.value = false // If we were creating a new event and user cancels (didn't save), delete it - if (dialogMode.value === 'create' && editingEventId.value && !eventSaved.value) { - calendarStore.deleteEvent(editingEventId.value) + if (unsavedCreateId.value && !eventSaved.value) { + if (calendarStore.events?.has(unsavedCreateId.value)) { + calendarStore.deleteEvent(unsavedCreateId.value) + } } + editingEventId.value = null + unsavedCreateId.value = null } function updateEventInStore() { @@ -260,16 +350,13 @@ function updateEventInStore() { } function saveEvent() { - if (editingEventId.value) { - updateEventInStore() - } - + if (editingEventId.value) updateEventInStore() eventSaved.value = true - if (dialogMode.value === 'create') { - emit('clear-selection') + // This create is now saved; clear tracking so it won't be auto-deleted + unsavedCreateId.value = null } - + if (dialogMode.value === 'create') emit('clear-selection') closeDialog() } @@ -304,6 +391,23 @@ watch(title, (newTitle) => { } }) +// Watch for dialog being closed unexpectedly and cleanup unsaved events +// Removed placeholder watcher + +// Watch for editingEventId changes (switching to a different event while create in progress) +// Removed editingEventId placeholder cleanup watcher + +// Cleanup drag listeners on unmount +onUnmounted(() => { + if (unsavedCreateId.value && !eventSaved.value) { + if (calendarStore.events?.has(unsavedCreateId.value)) + calendarStore.deleteEvent(unsavedCreateId.value) + } + document.removeEventListener('pointermove', handleDrag) + document.removeEventListener('pointerup', stopDrag) + document.removeEventListener('pointercancel', stopDrag) +}) + watch([recurrenceEnabled, recurrenceInterval, recurrenceFrequency], () => { if (editingEventId.value && showDialog.value) updateEventInStore() }) @@ -440,139 +544,127 @@ const recurrenceSummary = computed(() => {