From c7716d1b8f20d65a2ac70ae62ff299cd8518e6d1 Mon Sep 17 00:00:00 2001 From: Janus Knudsen Date: Thu, 4 Sep 2025 23:51:39 +0200 Subject: [PATCH] Improves event overlap stacking logic Ensures event stacking only occurs when events actually overlap in pixel space, preventing visual artifacts. Breaks stacking chains when events no longer overlap due to middle element removal, and correctly resets styling. --- src/managers/SimpleEventOverlapManager.ts | 95 ++++++++++++++++++----- 1 file changed, 75 insertions(+), 20 deletions(-) diff --git a/src/managers/SimpleEventOverlapManager.ts b/src/managers/SimpleEventOverlapManager.ts index f670527..bfca267 100644 --- a/src/managers/SimpleEventOverlapManager.ts +++ b/src/managers/SimpleEventOverlapManager.ts @@ -203,23 +203,58 @@ export class SimpleEventOverlapManager { const prevLink = this.getStackLink(prevElement); const nextLink = this.getStackLink(nextElement); - this.setStackLink(prevElement, { - ...prevLink!, - next: link.next - }); + // CRITICAL: Check if prev and next actually overlap without the middle element + const actuallyOverlap = this.checkPixelOverlap(prevElement, nextElement); - // FIXED: Use prev's stackLevel + 1 instead of subtracting 1 - const correctStackLevel = (prevLink?.stackLevel ?? 0) + 1; - this.setStackLink(nextElement, { - ...nextLink!, - prev: link.prev, - stackLevel: correctStackLevel - }); - - // CRITICAL: Update visual styling to match new stackLevel - const marginLeft = correctStackLevel * SimpleEventOverlapManager.STACKING_WIDTH_REDUCTION_PX; - nextElement.style.marginLeft = `${marginLeft}px`; - nextElement.style.zIndex = `${100 + correctStackLevel}`; + if (!actuallyOverlap) { + // CHAIN BREAKING: prev and next don't overlap - break the chain + console.log('Breaking stack chain - events do not overlap directly'); + + // Prev element: remove next link (becomes end of its own chain) + this.setStackLink(prevElement, { + ...prevLink!, + next: undefined + }); + + // Next element: becomes standalone (remove all stack links and styling) + this.setStackLink(nextElement, null); + nextElement.style.marginLeft = ''; + nextElement.style.zIndex = ''; + + // If next element had subsequent events, they also become standalone + if (nextLink?.next) { + let subsequentId: string | undefined = nextLink.next; + while (subsequentId) { + const subsequentElement = this.findElementById(subsequentId); + if (!subsequentElement) break; + + const subsequentLink = this.getStackLink(subsequentElement); + this.setStackLink(subsequentElement, null); + subsequentElement.style.marginLeft = ''; + subsequentElement.style.zIndex = ''; + + subsequentId = subsequentLink?.next; + } + } + } else { + // NORMAL STACKING: they overlap, maintain the chain + this.setStackLink(prevElement, { + ...prevLink!, + next: link.next + }); + + const correctStackLevel = (prevLink?.stackLevel ?? 0) + 1; + this.setStackLink(nextElement, { + ...nextLink!, + prev: link.prev, + stackLevel: correctStackLevel + }); + + // Update visual styling to match new stackLevel + const marginLeft = correctStackLevel * SimpleEventOverlapManager.STACKING_WIDTH_REDUCTION_PX; + nextElement.style.marginLeft = `${marginLeft}px`; + nextElement.style.zIndex = `${100 + correctStackLevel}`; + } } } else if (link.prev) { // Last element - remove next link from prev @@ -244,15 +279,16 @@ export class SimpleEventOverlapManager { } } - // For middle element removal, we've already set the correct stackLevel for next element - // Only update subsequent elements after the next one + // Only update subsequent stack levels if we didn't break the chain if (link.prev && link.next) { - // Middle removal - update elements after the next one const nextElement = this.findElementById(link.next); const nextLink = nextElement ? this.getStackLink(nextElement) : null; - if (nextLink?.next) { + + // If next element still has a stack link, the chain wasn't broken + if (nextLink && nextLink.next) { this.updateSubsequentStackLevels(nextLink.next, -1); } + // If nextLink is null, chain was broken - no subsequent updates needed } else { // First or last removal - update all subsequent this.updateSubsequentStackLevels(link.next, -1); @@ -500,4 +536,23 @@ export class SimpleEventOverlapManager { private findElementById(eventId: string): HTMLElement | null { return document.querySelector(`swp-event[data-event-id="${eventId}"]`) as HTMLElement; } + + /** + * Check if two elements overlap in pixel space + */ + private checkPixelOverlap(element1: HTMLElement, element2: HTMLElement): boolean { + if (!element1 || !element2) return false; + + const top1 = parseFloat(element1.style.top) || 0; + const height1 = parseFloat(element1.style.height) || 0; + const bottom1 = top1 + height1; + + const top2 = parseFloat(element2.style.top) || 0; + const height2 = parseFloat(element2.style.height) || 0; + const bottom2 = top2 + height2; + + // Add tolerance for small gaps (borders, etc) + const tolerance = 2; + return !(bottom1 <= (top2 + tolerance) || bottom2 <= (top1 + tolerance)); + } } \ No newline at end of file