Open Event dialog right below the day pointed to.

This commit is contained in:
Leo Vasanko 2025-08-25 11:02:37 -06:00
parent 7ed6fd9b29
commit 6a31f30e6c
2 changed files with 31 additions and 6 deletions

View File

@ -6,6 +6,9 @@ const props = defineProps({
title: { type: String, default: '' }, title: { type: String, default: '' },
draggable: { type: Boolean, default: true }, draggable: { type: Boolean, default: true },
autoFocus: { type: Boolean, default: true }, autoFocus: { type: Boolean, default: true },
// Optional external anchor element (e.g., a day cell) to position the dialog below.
// If not provided, falls back to internal anchorRef span (original behavior).
anchorEl: { type: Object, default: null },
}) })
const emit = defineEmits(['update:modelValue', 'opened', 'closed', 'submit']) const emit = defineEmits(['update:modelValue', 'opened', 'closed', 'submit'])
@ -97,17 +100,17 @@ onMounted(() => document.addEventListener('keydown', handleKeydown))
onUnmounted(() => document.removeEventListener('keydown', handleKeydown)) onUnmounted(() => document.removeEventListener('keydown', handleKeydown))
function positionNearAnchor() { function positionNearAnchor() {
if (!anchorRef.value) return const anchor = props.anchorEl || anchorRef.value
const rect = anchorRef.value.getBoundingClientRect() if (!anchor) return
// Place dialog below anchor with small vertical offset const rect = anchor.getBoundingClientRect()
const offsetY = 8 const offsetY = 8 // vertical gap below the anchor
// Need dialog dimensions to clamp correctly; measure current or fallback estimates
const w = modalRef.value?.offsetWidth || dialogWidth.value || 320 const w = modalRef.value?.offsetWidth || dialogWidth.value || 320
const h = modalRef.value?.offsetHeight || dialogHeight.value || 200 const h = modalRef.value?.offsetHeight || dialogHeight.value || 200
const vw = window.innerWidth const vw = window.innerWidth
const vh = window.innerHeight const vh = window.innerHeight
let x = rect.left let x = rect.left
let y = rect.bottom + offsetY let y = rect.bottom + offsetY
// If anchor is wider than dialog and would overflow right edge, clamp; otherwise keep left align
x = clamp(x, margin, Math.max(margin, vw - w - margin)) x = clamp(x, margin, Math.max(margin, vw - w - margin))
y = clamp(y, margin, Math.max(margin, vh - h - margin)) y = clamp(y, margin, Math.max(margin, vh - h - margin))
modalPosition.value = { x, y } modalPosition.value = { x, y }
@ -132,6 +135,16 @@ watch(
}, },
) )
// Reposition if anchorEl changes while open and user hasn't dragged dialog yet
watch(
() => props.anchorEl,
() => {
if (props.modelValue && !hasMoved.value) {
nextTick(() => positionNearAnchor())
}
},
)
function handleResize() { function handleResize() {
if (!props.modelValue) return if (!props.modelValue) return
// Re-clamp current position, and if not moved recalc near anchor // Re-clamp current position, and if not moved recalc near anchor

View File

@ -23,6 +23,8 @@ const emit = defineEmits(['clear-selection'])
const calendarStore = useCalendarStore() const calendarStore = useCalendarStore()
const showDialog = ref(false) const showDialog = ref(false)
// Anchoring: element of the DayCell representing the event's start date.
const anchorElement = ref(null)
const dialogMode = ref('create') // 'create' or 'edit' const dialogMode = ref('create') // 'create' or 'edit'
const editingEventId = ref(null) const editingEventId = ref(null)
const unsavedCreateId = ref(null) const unsavedCreateId = ref(null)
@ -191,6 +193,12 @@ function loadWeekdayPatternFromStore(storePattern) {
recurrenceWeekdays.value = [...storePattern] recurrenceWeekdays.value = [...storePattern]
} }
function resolveAnchorFromDate(dateStr) {
if (!dateStr) return null
// Expect day cells to have data-date attribute (see CalendarDay / DayCell components)
return document.querySelector(`[data-date='${dateStr}']`)
}
function openCreateDialog(selectionData = null) { function openCreateDialog(selectionData = null) {
calendarStore.$history?.beginCompound() calendarStore.$history?.beginCompound()
if (unsavedCreateId.value && !eventSaved.value) { if (unsavedCreateId.value && !eventSaved.value) {
@ -240,6 +248,8 @@ function openCreateDialog(selectionData = null) {
}) })
unsavedCreateId.value = editingEventId.value unsavedCreateId.value = editingEventId.value
// anchor to the starting day cell
anchorElement.value = resolveAnchorFromDate(start)
showDialog.value = true showDialog.value = true
nextTick(() => { nextTick(() => {
@ -344,6 +354,8 @@ function openEditDialog(payload) {
occurrenceContext.value = { baseId, occurrenceIndex, weekday: null, occurrenceDate } occurrenceContext.value = { baseId, occurrenceIndex, weekday: null, occurrenceDate }
} }
} }
// anchor to base event start date
anchorElement.value = resolveAnchorFromDate(event.startDate)
showDialog.value = true showDialog.value = true
nextTick(() => { nextTick(() => {
@ -553,7 +565,7 @@ const recurrenceSummary = computed(() => {
</script> </script>
<template> <template>
<BaseDialog v-model="showDialog" @submit="saveEvent"> <BaseDialog v-model="showDialog" :anchor-el="anchorElement" @submit="saveEvent">
<template #title> <template #title>
{{ dialogMode === 'create' ? 'Create Event' : 'Edit Event' {{ dialogMode === 'create' ? 'Create Event' : 'Edit Event'
}}<template v-if="headerDateShort"> · {{ headerDateShort }}</template> }}<template v-if="headerDateShort"> · {{ headerDateShort }}</template>