fix(rcal): eliminate event overlap at non-standard zoom resolutions
Week view: replace calc()-based absolute positioning with a mirror grid overlay (grid-template-columns: 44px repeat(7,1fr)) so event columns align pixel-perfectly with the underlying CSS grid at any browser zoom. Fix min-interval padding to match min display size in week view (20→23 min for 18px height) and day-horizontal view (30→45 min for 60px width), preventing visual bleed between adjacent short events. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
03b1bdf2f1
commit
9eca86f83b
|
|
@ -1689,11 +1689,12 @@ class FolkCalendarView extends HTMLElement {
|
|||
}
|
||||
|
||||
// Compute row assignments for overlapping horizontal events
|
||||
const MIN_DH_INTERVAL = Math.ceil(60 / HOUR_WIDTH * 60); // min display width (60px) → min interval
|
||||
const dhIntervals = timed.map(ev => {
|
||||
const s = new Date(ev.start_time), e = new Date(ev.end_time);
|
||||
const startMin = s.getHours() * 60 + s.getMinutes();
|
||||
const endMin = e.getHours() * 60 + e.getMinutes();
|
||||
return { startMin, endMin: Math.max(endMin, startMin + 30) };
|
||||
return { startMin, endMin: Math.max(endMin, startMin + MIN_DH_INTERVAL) };
|
||||
});
|
||||
const dhLayout = this.computeEventColumns(dhIntervals);
|
||||
const DH_ROW_HEIGHT = 56; // min-height 48 + 8px gap
|
||||
|
|
@ -2152,7 +2153,9 @@ class FolkCalendarView extends HTMLElement {
|
|||
}
|
||||
}
|
||||
|
||||
let eventsOverlay = "";
|
||||
// Build per-day event overlays (grid-aligned to avoid calc() rounding vs 1fr drift)
|
||||
const MIN_INTERVAL_WEEK = Math.ceil(18 / HOUR_HEIGHT * 60); // min display height → min interval
|
||||
const dayOverlays: string[] = [];
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const ds = this.dateStr(days[i]);
|
||||
const dayTimed = this.getEventsForDate(ds).filter(ev => {
|
||||
|
|
@ -2160,15 +2163,15 @@ class FolkCalendarView extends HTMLElement {
|
|||
return (en.getTime() - s.getTime()) < 86400000;
|
||||
}).sort((a, b) => a.start_time.localeCompare(b.start_time));
|
||||
|
||||
// Compute sub-columns for overlapping events within this day
|
||||
const intervals = dayTimed.map(ev => {
|
||||
const s = new Date(ev.start_time), e = new Date(ev.end_time);
|
||||
const startMin = s.getHours() * 60 + s.getMinutes();
|
||||
const endMin = e.getHours() * 60 + e.getMinutes();
|
||||
return { startMin, endMin: Math.max(endMin, startMin + 20) };
|
||||
return { startMin, endMin: Math.max(endMin, startMin + MIN_INTERVAL_WEEK) };
|
||||
});
|
||||
const dayLayout = this.computeEventColumns(intervals);
|
||||
|
||||
let dayHtml = "";
|
||||
for (let j = 0; j < dayTimed.length; j++) {
|
||||
const ev = dayTimed[j];
|
||||
const { startMin, endMin } = intervals[j];
|
||||
|
|
@ -2177,21 +2180,28 @@ class FolkCalendarView extends HTMLElement {
|
|||
const topPx = ((startMin - START_HOUR * 60) / 60) * HOUR_HEIGHT;
|
||||
const heightPx = Math.max((duration / 60) * HOUR_HEIGHT, 18);
|
||||
const es = this.getEventStyles(ev);
|
||||
const colLeft = `calc(44px + ${i} * ((100% - 44px) / 7) + 2px + ${col} * ((100% - 44px) / 7 - 4px) / ${totalCols})`;
|
||||
const colWidth = `calc(((100% - 44px) / 7 - 4px) / ${totalCols} - 1px)`;
|
||||
const evLeft = totalCols > 1 ? `calc(${(col / totalCols * 100).toFixed(2)}% + 1px)` : "1px";
|
||||
const evWidth = totalCols > 1 ? `calc(${(100 / totalCols).toFixed(2)}% - 2px)` : "calc(100% - 2px)";
|
||||
const showMeta = heightPx >= 36;
|
||||
const timeStr = `${this.formatTime(ev.start_time)}\u2013${this.formatTime(ev.end_time)}`;
|
||||
const locName = ev.location_name ? this.esc(ev.location_name) : "";
|
||||
const virtualBadge = ev.is_virtual ? `<span class="wk-virtual" title="Virtual">\u{1F4BB}</span>` : "";
|
||||
eventsOverlay += `<div class="week-event" data-event-id="${ev.id}" style="
|
||||
top:${topPx}px;height:${heightPx}px;left:${colLeft};width:${colWidth};
|
||||
dayHtml += `<div class="week-event" data-event-id="${ev.id}" style="
|
||||
top:${topPx}px;height:${heightPx}px;left:${evLeft};width:${evWidth};
|
||||
background:${es.bgColor};border-left-color:${ev.source_color || "#6366f1"};border-left-style:${es.borderStyle};opacity:${es.opacity}">
|
||||
<div class="week-event-title">${virtualBadge}${this.esc(ev.title)}</div>
|
||||
${showMeta ? `<div class="week-event-meta"><span class="week-event-time">${timeStr}</span>${locName ? `<span class="week-event-loc">${locName}</span>` : ""}</div>` : ""}
|
||||
</div>`;
|
||||
}
|
||||
dayOverlays.push(dayHtml);
|
||||
}
|
||||
|
||||
// Grid-aligned overlay mirrors week-grid columns exactly (no calc drift)
|
||||
const overlayDivs = dayOverlays.map(h =>
|
||||
`<div style="position:relative;pointer-events:auto;">${h}</div>`
|
||||
).join("");
|
||||
const eventsOverlay = `<div style="position:absolute;inset:0;display:grid;grid-template-columns:44px repeat(7,1fr);pointer-events:none;"><div></div>${overlayDivs}</div>`;
|
||||
|
||||
let nowHtml = "";
|
||||
const nowDay = days.findIndex(day => this.dateStr(day) === todayStr);
|
||||
if (nowDay >= 0) {
|
||||
|
|
@ -2205,10 +2215,12 @@ class FolkCalendarView extends HTMLElement {
|
|||
return `
|
||||
<div class="week-view">
|
||||
<div class="week-header">${headerHtml}</div>
|
||||
<div style="position:relative;overflow-y:auto;max-height:600px;">
|
||||
<div class="week-grid" style="position:relative;height:${totalHeight}px">${gridHtml}</div>
|
||||
<div style="overflow-y:auto;max-height:600px;">
|
||||
<div style="position:relative;height:${totalHeight}px;">
|
||||
<div class="week-grid" style="height:100%">${gridHtml}</div>
|
||||
${eventsOverlay}${nowHtml}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue