FIxed
This commit is contained in:
+102
-116
@@ -1,4 +1,4 @@
|
||||
// calendar.js — Infinite scrolling week-by-week
|
||||
// calendar.js — Infinite scrolling week-by-week with overlay event rendering
|
||||
const monthAbbr = ['jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec']
|
||||
const DAY_MS = 86400000
|
||||
const WEEK_MS = 7 * DAY_MS
|
||||
@@ -53,10 +53,10 @@ class InfiniteCalendar {
|
||||
this.baseDate = new Date(2024, 0, 1) // 2024 begins with Monday
|
||||
|
||||
// unified selection state (single or range)
|
||||
this.selStart = null // 'YYYY-MM-DD'
|
||||
this.selEnd = null // 'YYYY-MM-DD'
|
||||
this.selStart = null
|
||||
this.selEnd = null
|
||||
this.isDragging = false
|
||||
this.dragAnchor = null // 'YYYY-MM-DD'
|
||||
this.dragAnchor = null
|
||||
|
||||
this.init()
|
||||
}
|
||||
@@ -283,8 +283,6 @@ class InfiniteCalendar {
|
||||
weekEl.style.height = `${this.rowHeight}px`
|
||||
this.content.appendChild(weekEl)
|
||||
this.visibleWeeks.set(vw, weekEl)
|
||||
|
||||
// Add events to the newly created week
|
||||
this.addEventsToWeek(weekEl, vw)
|
||||
}
|
||||
|
||||
@@ -309,6 +307,31 @@ class InfiniteCalendar {
|
||||
wkLabel.textContent = `W${pad(isoWeekInfo(monday).week)}`
|
||||
weekDiv.appendChild(wkLabel)
|
||||
|
||||
// days grid container to host cells and overlay
|
||||
const daysGrid = document.createElement('div')
|
||||
daysGrid.className = 'days-grid'
|
||||
daysGrid.style.position = 'relative'
|
||||
daysGrid.style.display = 'grid'
|
||||
daysGrid.style.gridTemplateColumns = 'repeat(7, 1fr)'
|
||||
daysGrid.style.gridAutoRows = '1fr'
|
||||
daysGrid.style.height = '100%'
|
||||
daysGrid.style.width = '100%'
|
||||
weekDiv.appendChild(daysGrid)
|
||||
|
||||
// overlay positioned above cells, same 7-col grid
|
||||
const overlay = document.createElement('div')
|
||||
overlay.className = 'week-overlay'
|
||||
overlay.style.position = 'absolute'
|
||||
overlay.style.inset = '0'
|
||||
overlay.style.pointerEvents = 'none'
|
||||
overlay.style.display = 'grid'
|
||||
overlay.style.gridTemplateColumns = 'repeat(7, 1fr)'
|
||||
overlay.style.gridAutoRows = '1fr'
|
||||
overlay.style.zIndex = '15'
|
||||
daysGrid.appendChild(overlay)
|
||||
weekDiv._overlay = overlay
|
||||
weekDiv._daysGrid = daysGrid
|
||||
|
||||
const cur = new Date(monday)
|
||||
let hasFirst = false
|
||||
let monthToLabel = null
|
||||
@@ -337,26 +360,20 @@ class InfiniteCalendar {
|
||||
day.textContent = String(cur.getDate())
|
||||
|
||||
const date = toLocalString(cur)
|
||||
console.log(cur, date)
|
||||
cell.dataset.date = date
|
||||
if (this.today && date === this.today) cell.classList.add('today')
|
||||
|
||||
if (this.config.select_days > 0) {
|
||||
// Allow selection start from anywhere in the cell
|
||||
cell.addEventListener('mousedown', e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
this.startDrag(dateStr)
|
||||
})
|
||||
|
||||
// Touch events for mobile support
|
||||
cell.addEventListener('touchstart', e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
this.startDrag(dateStr)
|
||||
})
|
||||
|
||||
// Keep cell listeners for drag continuation
|
||||
cell.addEventListener('mouseenter', () => {
|
||||
if (this.isDragging) this.updateDrag(dateStr)
|
||||
})
|
||||
@@ -364,12 +381,9 @@ class InfiniteCalendar {
|
||||
e.stopPropagation()
|
||||
if (this.isDragging) this.endDrag(dateStr)
|
||||
})
|
||||
|
||||
// Touch drag continuation
|
||||
cell.addEventListener('touchmove', e => {
|
||||
if (this.isDragging) {
|
||||
e.preventDefault()
|
||||
// Get touch position and find the element underneath
|
||||
const touch = e.touches[0]
|
||||
const elementBelow = document.elementFromPoint(touch.clientX, touch.clientY)
|
||||
if (elementBelow && elementBelow.closest('.cell[data-date]')) {
|
||||
@@ -379,7 +393,6 @@ class InfiniteCalendar {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
cell.addEventListener('touchend', e => {
|
||||
e.stopPropagation()
|
||||
if (this.isDragging) this.endDrag(dateStr)
|
||||
@@ -392,7 +405,7 @@ class InfiniteCalendar {
|
||||
}
|
||||
|
||||
cell.appendChild(day)
|
||||
weekDiv.appendChild(cell)
|
||||
daysGrid.appendChild(cell)
|
||||
cur.setDate(cur.getDate() + 1)
|
||||
}
|
||||
|
||||
@@ -539,21 +552,16 @@ class InfiniteCalendar {
|
||||
}
|
||||
|
||||
setupGlobalDragHandlers() {
|
||||
// Mouse drag handlers
|
||||
document.addEventListener('mouseup', () => {
|
||||
if (!this.isDragging) return
|
||||
this.isDragging = false
|
||||
document.body.style.cursor = 'default'
|
||||
})
|
||||
|
||||
// Touch drag handlers
|
||||
document.addEventListener('touchend', () => {
|
||||
if (!this.isDragging) return
|
||||
this.isDragging = false
|
||||
document.body.style.cursor = 'default'
|
||||
})
|
||||
|
||||
// Global touch move handler for smooth drag across elements
|
||||
document.addEventListener('touchmove', e => {
|
||||
if (!this.isDragging) return
|
||||
e.preventDefault()
|
||||
@@ -565,13 +573,9 @@ class InfiniteCalendar {
|
||||
if (touchDateStr) this.updateDrag(touchDateStr)
|
||||
}
|
||||
}, { passive: false })
|
||||
|
||||
// Prevent text selection during drag
|
||||
document.addEventListener('selectstart', e => {
|
||||
if (this.isDragging) e.preventDefault()
|
||||
})
|
||||
|
||||
// Prevent context menu on long touch during drag
|
||||
document.addEventListener('contextmenu', e => {
|
||||
if (this.isDragging) e.preventDefault()
|
||||
})
|
||||
@@ -595,31 +599,27 @@ class InfiniteCalendar {
|
||||
this.isDragging = false
|
||||
this.setSelection(this.dragAnchor, dateStr)
|
||||
document.body.style.cursor = 'default'
|
||||
|
||||
// Trigger event creation after selection with a small delay
|
||||
if (this.selStart && this.selEnd) {
|
||||
setTimeout(() => this.promptForEvent(), 100)
|
||||
}
|
||||
}
|
||||
|
||||
// -------- Event Management --------
|
||||
|
||||
// -------- Event Management (overlay-based) --------
|
||||
|
||||
promptForEvent() {
|
||||
const title = prompt('Enter event title:')
|
||||
if (!title || title.trim() === '') {
|
||||
this.clearSelection()
|
||||
return
|
||||
}
|
||||
|
||||
this.createEvent({
|
||||
title: title.trim(),
|
||||
startDate: this.selStart,
|
||||
endDate: this.selEnd
|
||||
})
|
||||
|
||||
this.clearSelection()
|
||||
}
|
||||
|
||||
|
||||
createEvent(eventData) {
|
||||
const event = {
|
||||
id: this.eventIdCounter++,
|
||||
@@ -628,23 +628,19 @@ class InfiniteCalendar {
|
||||
endDate: eventData.endDate,
|
||||
color: this.generateEventColor()
|
||||
}
|
||||
|
||||
// Add event to all dates in the range
|
||||
|
||||
const startDate = new Date(fromLocalString(event.startDate))
|
||||
const endDate = new Date(fromLocalString(event.endDate))
|
||||
|
||||
|
||||
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
|
||||
const dateStr = toLocalString(d)
|
||||
if (!this.events.has(dateStr)) {
|
||||
this.events.set(dateStr, [])
|
||||
}
|
||||
this.events.get(dateStr).push({...event, isSpanning: startDate < endDate})
|
||||
if (!this.events.has(dateStr)) this.events.set(dateStr, [])
|
||||
this.events.get(dateStr).push({ ...event, isSpanning: startDate < endDate })
|
||||
}
|
||||
|
||||
// Re-render visible weeks to show the new event
|
||||
|
||||
this.refreshEvents()
|
||||
}
|
||||
|
||||
|
||||
generateEventColor() {
|
||||
const colors = [
|
||||
'#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57',
|
||||
@@ -652,91 +648,82 @@ class InfiniteCalendar {
|
||||
]
|
||||
return colors[Math.floor(Math.random() * colors.length)]
|
||||
}
|
||||
|
||||
|
||||
refreshEvents() {
|
||||
// Re-render events for all visible weeks
|
||||
for (const [weekDateStr, weekEl] of this.visibleWeeks) {
|
||||
this.addEventsToWeek(weekEl, weekDateStr)
|
||||
for (const [, weekEl] of this.visibleWeeks) {
|
||||
this.addEventsToWeek(weekEl)
|
||||
}
|
||||
}
|
||||
|
||||
addEventsToWeek(weekEl, weekDateStr) {
|
||||
const cells = weekEl.querySelectorAll('.cell[data-date]')
|
||||
|
||||
// Remove existing event elements from both week and individual cells
|
||||
weekEl.querySelectorAll('.event-span').forEach(el => el.remove())
|
||||
cells.forEach(cell => {
|
||||
cell.querySelectorAll('.event-span').forEach(el => el.remove())
|
||||
})
|
||||
|
||||
// Group events by their date ranges within this week
|
||||
const weekEvents = new Map() // Map of event ID to event info
|
||||
|
||||
|
||||
addEventsToWeek(weekEl) {
|
||||
const daysGrid = weekEl._daysGrid || weekEl.querySelector('.days-grid')
|
||||
const overlay = weekEl._overlay || weekEl.querySelector('.week-overlay')
|
||||
if (!daysGrid || !overlay) return
|
||||
|
||||
const cells = Array.from(daysGrid.querySelectorAll('.cell[data-date]'))
|
||||
|
||||
while (overlay.firstChild) overlay.removeChild(overlay.firstChild)
|
||||
|
||||
const weekEvents = new Map()
|
||||
for (const cell of cells) {
|
||||
const dateStr = cell.dataset.date
|
||||
const events = this.events.get(dateStr) || []
|
||||
|
||||
events.forEach(event => {
|
||||
if (!weekEvents.has(event.id)) {
|
||||
weekEvents.set(event.id, {
|
||||
...event,
|
||||
for (const ev of events) {
|
||||
if (!weekEvents.has(ev.id)) {
|
||||
weekEvents.set(ev.id, {
|
||||
...ev,
|
||||
startDateInWeek: dateStr,
|
||||
endDateInWeek: dateStr,
|
||||
startCell: cell,
|
||||
endCell: cell,
|
||||
daysInWeek: 1
|
||||
startIdx: cells.indexOf(cell),
|
||||
endIdx: cells.indexOf(cell)
|
||||
})
|
||||
} else {
|
||||
const weekEvent = weekEvents.get(event.id)
|
||||
weekEvent.endDateInWeek = dateStr
|
||||
weekEvent.endCell = cell
|
||||
weekEvent.daysInWeek++
|
||||
const w = weekEvents.get(ev.id)
|
||||
w.endDateInWeek = dateStr
|
||||
w.endIdx = cells.indexOf(cell)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Create spanning elements for each event
|
||||
weekEvents.forEach((weekEvent, eventId) => {
|
||||
this.createSpanningEvent(weekEl, weekEvent)
|
||||
})
|
||||
|
||||
const spans = Array.from(weekEvents.values())
|
||||
.sort((a, b) => a.startIdx - b.startIdx || (b.endIdx - b.startIdx) - (a.endIdx - a.startIdx))
|
||||
|
||||
const rowsLastEnd = []
|
||||
for (const w of spans) {
|
||||
let placedRow = 0
|
||||
while (placedRow < rowsLastEnd.length && !(w.startIdx > rowsLastEnd[placedRow])) placedRow++
|
||||
if (placedRow === rowsLastEnd.length) rowsLastEnd.push(-1)
|
||||
rowsLastEnd[placedRow] = w.endIdx
|
||||
w._row = placedRow + 1
|
||||
}
|
||||
|
||||
overlay.style.gridTemplateRows = `repeat(${Math.max(1, rowsLastEnd.length)}, 1fr)`
|
||||
overlay.style.rowGap = '.2em'
|
||||
|
||||
for (const w of spans) this.createOverlaySpan(overlay, w)
|
||||
}
|
||||
|
||||
createSpanningEvent(weekEl, weekEvent) {
|
||||
const spanEl = document.createElement('div')
|
||||
spanEl.className = 'event-span'
|
||||
spanEl.style.backgroundColor = weekEvent.color
|
||||
spanEl.textContent = weekEvent.title
|
||||
spanEl.title = `${weekEvent.title} (${weekEvent.startDate === weekEvent.endDate ? weekEvent.startDate : weekEvent.startDate + ' - ' + weekEvent.endDate})`
|
||||
|
||||
// Get all cells in the week (excluding week label and overlay)
|
||||
const cells = Array.from(weekEl.querySelectorAll('.cell[data-date]'))
|
||||
const startCellIndex = cells.indexOf(weekEvent.startCell)
|
||||
const endCellIndex = cells.indexOf(weekEvent.endCell)
|
||||
const spanDays = endCellIndex - startCellIndex + 1
|
||||
|
||||
// Use CSS custom properties for positioning
|
||||
spanEl.style.setProperty('--start-day', startCellIndex)
|
||||
spanEl.style.setProperty('--span-days', spanDays)
|
||||
|
||||
// Style the spanning event
|
||||
spanEl.style.position = 'absolute'
|
||||
spanEl.style.top = '0.25em'
|
||||
spanEl.style.height = '1.2em'
|
||||
spanEl.style.zIndex = '15'
|
||||
spanEl.style.fontSize = '0.75em'
|
||||
spanEl.style.padding = '0.1em 0.3em'
|
||||
spanEl.style.borderRadius = '0.2em'
|
||||
spanEl.style.color = 'white'
|
||||
spanEl.style.fontWeight = '500'
|
||||
spanEl.style.whiteSpace = 'nowrap'
|
||||
spanEl.style.overflow = 'hidden'
|
||||
spanEl.style.textOverflow = 'ellipsis'
|
||||
spanEl.style.cursor = 'pointer'
|
||||
spanEl.style.lineHeight = '1.2'
|
||||
spanEl.style.pointerEvents = 'auto'
|
||||
|
||||
// Append to the week element (not individual cells)
|
||||
weekEl.appendChild(spanEl)
|
||||
|
||||
createOverlaySpan(overlay, w) {
|
||||
const span = document.createElement('div')
|
||||
span.className = 'event-span'
|
||||
span.style.gridColumn = `${w.startIdx + 1} / ${w.endIdx + 2}`
|
||||
span.style.gridRow = `${w._row}`
|
||||
span.style.height = '1.2em'
|
||||
span.style.borderRadius = '.4em'
|
||||
span.style.fontSize = '.75em'
|
||||
span.style.lineHeight = '1.2'
|
||||
span.style.padding = '0 .5em'
|
||||
span.style.whiteSpace = 'nowrap'
|
||||
span.style.overflow = 'hidden'
|
||||
span.style.textOverflow = 'ellipsis'
|
||||
span.style.background = w.color
|
||||
span.style.color = 'white'
|
||||
span.style.fontWeight = '600'
|
||||
span.style.pointerEvents = 'auto'
|
||||
span.style.zIndex = '1'
|
||||
span.textContent = w.title
|
||||
span.title = `${w.title} (${w.startDate === w.endDate ? w.startDate : w.startDate + ' - ' + w.endDate})`
|
||||
overlay.appendChild(span)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -745,4 +732,3 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
select_days: 14
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user