Mouse/touch event handling improvement

- prefer passive handlers
- fix event moving on touch
- faster selection updates
This commit is contained in:
Leo Vasanko 2025-08-27 08:04:29 -06:00
parent ecae48fd85
commit 5a0d6804bc
6 changed files with 19 additions and 23 deletions

View File

@ -248,7 +248,7 @@ function onGlobalTouchMove(e) {
if (!isDragging.value) return if (!isDragging.value) return
const t = e.touches && e.touches[0] const t = e.touches && e.touches[0]
if (!t) return if (!t) return
e.preventDefault() if (e.cancelable) e.preventDefault()
const dateStr = getDateUnderPoint(t.clientX, t.clientY) const dateStr = getDateUnderPoint(t.clientX, t.clientY)
if (dateStr) updateDrag(dateStr) if (dateStr) updateDrag(dateStr)
} }
@ -435,10 +435,11 @@ function buildSearchResults() {
watch(searchQuery, buildSearchResults) watch(searchQuery, buildSearchResults)
watch( watch(
() => calendarStore.eventsMutation, () => calendarStore.events,
() => { () => {
if (searchOpen.value && searchQuery.value.trim()) buildSearchResults() if (searchOpen.value && searchQuery.value.trim()) buildSearchResults()
}, },
{ deep: true },
) )
function openSearch(prefill = '') { function openSearch(prefill = '') {
@ -569,19 +570,22 @@ watch(
}, },
) )
// Event changes // Event changes (optimized): react to mutation counter & targeted range payload
watch( watch(
() => calendarStore.events, () => calendarStore.events,
() => { () => refreshEvents('events'),
refreshEvents('events')
},
{ deep: true }, { deep: true },
) )
// Reflect selection & events by rebuilding day objects in-place // Reflect selection & events by rebuilding day objects in-place
watch( watch(
() => [selection.value.startDate, selection.value.dayCount], () => [selection.value.startDate, selection.value.dayCount],
() => refreshEvents('selection'), ([start, count]) => {
const hasSel = !!start && !!count && count > 0
const end = hasSel ? addDaysStr(start, count, DEFAULT_TZ) : null
for (const w of visibleWeeks.value)
for (const d of w.days) d.isSelected = hasSel && d.date >= start && d.date < end
},
) )
// Rebuild if viewport height changes (e.g., resize) // Rebuild if viewport height changes (e.g., resize)

View File

@ -57,7 +57,7 @@ function shouldRotateMonth(label) {
@mousedown="handleDayMouseDown(day.date)" @mousedown="handleDayMouseDown(day.date)"
@mouseenter="handleDayMouseEnter(day.date)" @mouseenter="handleDayMouseEnter(day.date)"
@mouseup="handleDayMouseUp(day.date)" @mouseup="handleDayMouseUp(day.date)"
@touchstart="handleDayTouchStart(day.date)" @touchstart.passive="handleDayTouchStart(day.date)"
/> />
<EventOverlay :week="props.week" @event-click="handleEventClick" /> <EventOverlay :week="props.week" @event-click="handleEventClick" />
</div> </div>

View File

@ -278,9 +278,7 @@ function startLocalDrag(init, evt) {
} }
} }
if (!(evt.pointerType === 'touch')) { if (evt.cancelable) evt.preventDefault()
evt.preventDefault()
}
window.addEventListener('pointermove', onDragPointerMove, { passive: false }) window.addEventListener('pointermove', onDragPointerMove, { passive: false })
window.addEventListener('pointerup', onDragPointerUp, { passive: false }) window.addEventListener('pointerup', onDragPointerUp, { passive: false })
@ -486,6 +484,8 @@ function applyRangeDuringDrag(st, startDate, endDate) {
user-select: none; user-select: none;
z-index: 1; z-index: 1;
text-align: center; text-align: center;
/* Ensure touch pointer events aren't turned into a scroll gesture; needed for reliable drag on mobile */
touch-action: none;
} }
/* Inner title wrapper ensures proper ellipsis within flex/grid constraints */ /* Inner title wrapper ensures proper ellipsis within flex/grid constraints */
@ -510,6 +510,7 @@ function applyRangeDuringDrag(st, startDate, endDate) {
background: transparent; background: transparent;
z-index: 2; z-index: 2;
cursor: ew-resize; cursor: ew-resize;
touch-action: none; /* Allow touch resizing without scroll */
} }
.event-span .resize-handle.left { .event-span .resize-handle.left {

View File

@ -18,7 +18,6 @@ function restoreCalendarState(store, snap) {
store.weekend = Array.isArray(snap.weekend) ? [...snap.weekend] : snap.weekend store.weekend = Array.isArray(snap.weekend) ? [...snap.weekend] : snap.weekend
store.config = JSON.parse(JSON.stringify(snap.config)) store.config = JSON.parse(JSON.stringify(snap.config))
store.events = new Map([...snap.events].map(([k, v]) => [k, { ...v }])) store.events = new Map([...snap.events].map(([k, v]) => [k, { ...v }]))
store.eventsMutation = (store.eventsMutation + 1) % 1_000_000_000
} }
export function calendarHistory({ store }) { export function calendarHistory({ store }) {
@ -36,8 +35,7 @@ export function calendarHistory({ store }) {
function serializeForComparison() { function serializeForComparison() {
const evCount = store.events instanceof Map ? store.events.size : 0 const evCount = store.events instanceof Map ? store.events.size : 0
const em = store.eventsMutation || 0 return `${evCount}|${store.today}|${JSON.stringify(store.config)}`
return `${em}|${evCount}|${store.today}|${JSON.stringify(store.config)}`
} }
function pushSnapshot() { function pushSnapshot() {

View File

@ -124,7 +124,6 @@ function createMomentumDrag({
return return
} }
applyDragPosition(e.touches[0].clientY, reasonDragTouch) applyDragPosition(e.touches[0].clientY, reasonDragTouch)
e.preventDefault()
} }
function handlePointerDown(e) { function handlePointerDown(e) {
if (e.button !== undefined && e.button !== 0) return if (e.button !== undefined && e.button !== 0) return
@ -158,7 +157,7 @@ function createMomentumDrag({
window.addEventListener('touchmove', onTouchMove, { passive: false }) window.addEventListener('touchmove', onTouchMove, { passive: false })
window.addEventListener('touchend', endDrag, { passive: false }) window.addEventListener('touchend', endDrag, { passive: false })
window.addEventListener('touchcancel', endDrag, { passive: false }) window.addEventListener('touchcancel', endDrag, { passive: false })
e.preventDefault() if (e.cancelable) e.preventDefault()
} }
function onPointerLockChange() { function onPointerLockChange() {
const lockedEl = document.pointerLockElement const lockedEl = document.pointerLockElement

View File

@ -14,9 +14,6 @@ export const useCalendarStore = defineStore('calendar', {
today: toLocalString(new Date(), DEFAULT_TZ), today: toLocalString(new Date(), DEFAULT_TZ),
now: new Date().toISOString(), now: new Date().toISOString(),
events: new Map(), events: new Map(),
// Lightweight mutation counter so views can rebuild in a throttled / idle way
// without tracking deep reactivity on every event object.
eventsMutation: 0,
// Incremented internally by history plugin to force reactive updates for canUndo/canRedo // Incremented internally by history plugin to force reactive updates for canUndo/canRedo
historyTick: 0, historyTick: 0,
historyCanUndo: false, historyCanUndo: false,
@ -117,10 +114,7 @@ export const useCalendarStore = defineStore('calendar', {
return 'e-' + Math.random().toString(36).slice(2, 10) + '-' + Date.now().toString(36) return 'e-' + Math.random().toString(36).slice(2, 10) + '-' + Date.now().toString(36)
}, },
notifyEventsChanged() { notifyEventsChanged() {},
// Bump simple counter (wrapping to avoid overflow in extreme long sessions)
this.eventsMutation = (this.eventsMutation + 1) % 1_000_000_000
},
touchEvents() { touchEvents() {
this.notifyEventsChanged() this.notifyEventsChanged()
}, },