From 2656bae054d676a78ed66889e7d5d72fc118f47e Mon Sep 17 00:00:00 2001 From: "Janus C. H. Knudsen" Date: Tue, 11 Nov 2025 18:08:48 +0100 Subject: [PATCH] Refactors all-day event layout tracking logic Removes redundant state tracking for all-day event layouts Shifts from maintaining internal layout state to reading directly from DOM elements Simplifies event handling and updates by using DOM as the source of truth Improves performance by reducing unnecessary state management --- src/managers/AllDayManager.ts | 143 +++++++++++++----------- wwwroot/css/calendar-layout-css.css | 2 +- wwwroot/css/src/calendar-layout-css.css | 5 + 3 files changed, 83 insertions(+), 67 deletions(-) diff --git a/src/managers/AllDayManager.ts b/src/managers/AllDayManager.ts index e796d19..5e3c6cf 100644 --- a/src/managers/AllDayManager.ts +++ b/src/managers/AllDayManager.ts @@ -33,11 +33,9 @@ export class AllDayManager { private layoutEngine: AllDayLayoutEngine | null = null; - // State tracking for differential updates - private currentLayouts: IEventLayout[] = []; + // State tracking for layout calculation private currentAllDayEvents: ICalendarEvent[] = []; private currentWeekDates: IColumnBounds[] = []; - private newLayouts: IEventLayout[] = []; // Expand/collapse state private isExpanded: boolean = false; @@ -128,23 +126,12 @@ export class AllDayManager { if (dragEndPayload.target === 'swp-day-column' && dragEndPayload.originalElement?.hasAttribute('data-allday')) { const eventId = dragEndPayload.originalElement.dataset.eventId; - console.log('🔄 AllDayManager: All-day → timed conversion', { - eventId, - currentLayoutsCount: this.currentLayouts.length, - layoutsBeforeFilter: this.currentLayouts.map(l => l.calenderEvent.id) - }); + console.log('🔄 AllDayManager: All-day → timed conversion', { eventId }); - // Remove event from currentLayouts since it's now a timed event - this.currentLayouts = this.currentLayouts.filter( - layout => layout.calenderEvent.id !== eventId - ); + // No need to filter currentLayouts - DOM is source of truth! + // The element is already removed from all-day container by DragDropManager - console.log('📊 AllDayManager: After filter', { - currentLayoutsCount: this.currentLayouts.length, - layoutsAfterFilter: this.currentLayouts.map(l => l.calenderEvent.id) - }); - - // Recalculate and animate header height + // Recalculate and animate header height based on remaining DOM elements this.checkAndAnimateAllDayHeight(); } }); @@ -171,9 +158,9 @@ export class AllDayManager { // Filter for all-day events const allDayEvents = events.filter(event => event.allDay); - this.currentLayouts = this.calculateAllDayEventsLayout(allDayEvents, headerReadyEventPayload.headerElements) + const layouts = this.calculateAllDayEventsLayout(allDayEvents, headerReadyEventPayload.headerElements); - this.allDayEventRenderer.renderAllDayEventsForPeriod(this.currentLayouts); + this.allDayEventRenderer.renderAllDayEventsForPeriod(layouts); this.checkAndAnimateAllDayHeight(); }); @@ -194,6 +181,65 @@ export class AllDayManager { return document.querySelector('swp-header-spacer'); } + /** + * Read current max row from DOM elements + */ + private getMaxRowFromDOM(): number { + const container = this.getAllDayContainer(); + if (!container) return 0; + + let maxRow = 0; + const allDayEvents = container.querySelectorAll('swp-allday-event:not(.max-event-indicator)'); + + allDayEvents.forEach((element: Element) => { + const htmlElement = element as HTMLElement; + const row = parseInt(htmlElement.style.gridRow) || 1; + maxRow = Math.max(maxRow, row); + }); + + return maxRow; + } + + /** + * Get current gridArea for an event from DOM + */ + private getGridAreaFromDOM(eventId: string): string | null { + const container = this.getAllDayContainer(); + if (!container) return null; + + const element = container.querySelector(`[data-event-id="${eventId}"]`) as HTMLElement; + return element?.style.gridArea || null; + } + + /** + * Count events in a specific column by reading DOM + */ + private countEventsInColumnFromDOM(columnIndex: number): number { + const container = this.getAllDayContainer(); + if (!container) return 0; + + let count = 0; + const allDayEvents = container.querySelectorAll('swp-allday-event:not(.max-event-indicator)'); + + allDayEvents.forEach((element: Element) => { + const htmlElement = element as HTMLElement; + const gridColumn = htmlElement.style.gridColumn; + + // Parse "1 / 3" format + const match = gridColumn.match(/(\d+)\s*\/\s*(\d+)/); + if (match) { + const startCol = parseInt(match[1]); + const endCol = parseInt(match[2]) - 1; // End is exclusive in CSS + + if (startCol <= columnIndex && endCol >= columnIndex) { + count++; + } + } + }); + + return count; + } + /** * Calculate all-day height based on number of rows */ @@ -221,36 +267,14 @@ export class AllDayManager { /** * Check current all-day events and animate to correct height + * Reads max row directly from DOM elements */ public checkAndAnimateAllDayHeight(): void { - console.log('📏 AllDayManager: checkAndAnimateAllDayHeight called', { - currentLayoutsCount: this.currentLayouts.length, - layouts: this.currentLayouts.map(l => ({ - id: l.calenderEvent.id, - row: l.row, - title: l.calenderEvent.title - })) - }); - - // Calculate required rows - 0 if no events (will collapse) - let maxRows = 0; - - if (this.currentLayouts.length > 0) { - // Find the HIGHEST row number in use from currentLayouts - let highestRow = 0; - - this.currentLayouts.forEach((layout) => { - highestRow = Math.max(highestRow, layout.row); - }); - - // Max rows = highest row number (e.g. if row 3 is used, height = 3 rows) - maxRows = highestRow; - - } + // Read max row directly from DOM + const maxRows = this.getMaxRowFromDOM(); console.log('📊 AllDayManager: Height calculation', { maxRows, - currentLayoutsLength: this.currentLayouts.length, isExpanded: this.isExpanded }); @@ -508,17 +532,15 @@ export class AllDayManager { ]; // 4. Calculate new layouts for ALL events - this.newLayouts = this.calculateAllDayEventsLayout(tempEvents, this.currentWeekDates); + const newLayouts = this.calculateAllDayEventsLayout(tempEvents, this.currentWeekDates); - // 5. Apply differential updates - only update events that changed - let changedCount = 0; + // 5. Apply differential updates - compare with DOM instead of currentLayouts let container = this.getAllDayContainer(); - this.newLayouts.forEach((layout) => { - // Find current layout for this event - let currentLayout = this.currentLayouts.find(old => old.calenderEvent.id === layout.calenderEvent.id); + newLayouts.forEach((layout) => { + // Get current gridArea from DOM + const currentGridArea = this.getGridAreaFromDOM(layout.calenderEvent.id); - if (currentLayout?.gridArea !== layout.gridArea) { - changedCount++; + if (currentGridArea !== layout.gridArea) { let element = container?.querySelector(`[data-event-id="${layout.calenderEvent.id}"]`) as HTMLElement; if (element) { @@ -542,9 +564,6 @@ export class AllDayManager { } }); - if (changedCount > 0) - this.currentLayouts = this.newLayouts; - // 6. Clean up drag styles from the dropped clone dragEndEvent.draggedClone.classList.remove('dragging'); dragEndEvent.draggedClone.style.zIndex = ''; @@ -625,18 +644,10 @@ export class AllDayManager { } /** * Count number of events in a specific column using IColumnBounds + * Reads directly from DOM elements */ private countEventsInColumn(columnBounds: IColumnBounds): number { - let columnIndex = columnBounds.index; - let count = 0; - - this.currentLayouts.forEach((layout) => { - // Check if event spans this column - if (layout.startColumn <= columnIndex && layout.endColumn >= columnIndex) { - count++; - } - }); - return count; + return this.countEventsInColumnFromDOM(columnBounds.index); } /** diff --git a/wwwroot/css/calendar-layout-css.css b/wwwroot/css/calendar-layout-css.css index d54b69c..c244ae0 100644 --- a/wwwroot/css/calendar-layout-css.css +++ b/wwwroot/css/calendar-layout-css.css @@ -1 +1 @@ -.calendar-wrapper{box-sizing:border-box;display:flex;flex-direction:column;height:100vh;margin:0;overflow:hidden;padding:0;width:100vw}swp-calendar{background:var(--color-background);display:grid;grid-template-rows:auto 1fr;height:100vh;overflow:hidden;position:relative;width:100%}swp-calendar[data-fit-to-width=true] swp-scrollable-content{overflow-x:hidden}swp-calendar-nav{align-items:center;background:var(--color-background);border-bottom:1px solid var(--color-border);box-shadow:var(--shadow-sm);display:grid;gap:20px;grid-template-columns:auto 1fr auto auto;padding:12px 16px}swp-calendar-container{display:grid;grid-template-columns:60px 1fr;grid-template-rows:auto 1fr;height:100%;overflow:hidden;position:relative}swp-calendar-container.week-transition{transition:opacity .3s ease}swp-calendar-container.week-transition:is(-out){opacity:.5}swp-header-spacer{background:var(--color-surface);border-bottom:1px solid var(--color-border);border-right:1px solid var(--color-border);grid-column:1;grid-row:1;height:calc(var(--header-height) + var(--all-day-row-height));position:relative;z-index:5}.allday-chevron{background:none;border:none;border-radius:4px;bottom:2px;color:#666;cursor:pointer;left:50%;padding:4px 8px;position:absolute;transform:translateX(-50%);transition:transform .3s ease,color .2s ease}.allday-chevron:hover{background-color:rgba(0,0,0,.05);color:#000}.allday-chevron.collapsed{transform:translateX(-50%) rotate(0deg)}.allday-chevron.expanded{transform:translateX(-50%) rotate(180deg)}.allday-chevron svg{display:block;height:8px;width:12px}swp-grid-container{display:grid;grid-column:2;grid-row:1/3;grid-template-rows:auto 1fr;transition:transform .4s cubic-bezier(.4,0,.2,1);width:100%}swp-grid-container,swp-time-axis{overflow:hidden;position:relative}swp-time-axis{background:var(--color-surface);border-right:1px solid var(--color-border);grid-column:1;grid-row:2;height:100%;left:0;width:60px;z-index:3}swp-time-axis-content{display:flex;flex-direction:column;position:relative}swp-hour-marker{align-items:flex-start;color:var(--color-text-secondary);display:flex;font-size:.75rem;height:var(--hour-height);padding:0 8px 8px 15px;position:relative}swp-hour-marker:before{background:var(--color-hour-line);content:"";height:1px;left:50px;position:absolute;top:-1px;width:calc(100vw - 60px);z-index:2}swp-calendar-header{background:var(--color-surface);display:grid;grid-template-columns:repeat(var(--grid-columns,7),minmax(var(--day-column-min-width),1fr));grid-template-rows:var(--header-height) auto;height:calc(var(--header-height) + var(--all-day-row-height));min-width:calc(var(--grid-columns, 7)*var(--day-column-min-width));overflow-x:hidden;overflow-y:scroll;position:sticky;top:0;z-index:3}swp-calendar-header::-webkit-scrollbar{background:transparent;width:17px}swp-calendar-header::-webkit-scrollbar-thumb,swp-calendar-header::-webkit-scrollbar-track{background:transparent}swp-calendar-header swp-allday-container{align-items:center;display:grid;gap:2px 0;grid-auto-rows:var(--single-row-height);grid-column:1/-1;grid-row:2;grid-template-columns:repeat(var(--grid-columns,7),minmax(var(--day-column-min-width),1fr));overflow:hidden}swp-day-header{align-items:center;border-bottom:1px solid var(--color-grid-line);border-right:1px solid var(--color-grid-line);display:flex;flex-direction:column;grid-row:1;justify-content:center;padding-top:3px;text-align:center}swp-day-header:last-child{border-right:none}swp-day-header[data-today=true]{background:rgba(33,150,243,.1)}swp-day-header[data-today=true] swp-day-name{color:var(--color-primary);font-weight:600}swp-day-header[data-today=true] swp-day-date{color:var(--color-primary)}swp-day-name{color:var(--color-text-secondary);display:block;font-size:12px;font-weight:500;letter-spacing:.1em}swp-day-date{display:block;font-size:30px;margin-top:4px}swp-resource-header{align-items:center;background:var(--color-surface);border-bottom:1px solid var(--color-grid-line);border-right:1px solid var(--color-grid-line);display:flex;flex-direction:column;justify-content:center;padding:12px;text-align:center}swp-resource-header:last-child{border-right:none}swp-resource-avatar{background:var(--color-border);border-radius:50%;display:block;height:40px;margin-bottom:8px;overflow:hidden;width:40px}swp-resource-avatar img{height:100%;-o-object-fit:cover;object-fit:cover;width:100%}swp-resource-name{color:var(--color-text);display:block;font-size:.875rem;font-weight:500;text-align:center}swp-allday-column{background:transparent;height:100%;opacity:0;position:relative;z-index:1}swp-allday-container swp-allday-event{align-items:center;background:#08f;border-radius:3px;color:#fff;display:flex;font-size:.75rem;height:22px!important;justify-content:flex-start;left:auto!important;margin:1px;overflow:hidden;padding:2px 4px;position:relative!important;right:auto!important;text-overflow:ellipsis;top:auto!important;white-space:nowrap;width:auto!important;z-index:2}[data-type=meeting]:is(swp-allday-container swp-allday-event){background:var(--color-event-meeting);color:var(--color-text)}[data-type=meal]:is(swp-allday-container swp-allday-event){background:var(--color-event-meal);color:var(--color-text)}[data-type=work]:is(swp-allday-container swp-allday-event){background:var(--color-event-work);color:var(--color-text)}[data-type=milestone]:is(swp-allday-container swp-allday-event){background:var(--color-event-milestone);color:var(--color-text)}[data-type=personal]:is(swp-allday-container swp-allday-event){background:var(--color-event-personal);color:var(--color-text)}[data-type=deadline]:is(swp-allday-container swp-allday-event){background:var(--color-event-milestone);color:var(--color-text)}.dragging:is(swp-allday-container swp-allday-event){opacity:1}.highlight[data-type=meeting]:is(swp-allday-container swp-allday-event){background:var(--color-event-meeting-hl)!important}.highlight[data-type=meal]:is(swp-allday-container swp-allday-event){background:var(--color-event-meal-hl)!important}.highlight[data-type=work]:is(swp-allday-container swp-allday-event){background:var(--color-event-work-hl)!important}.highlight[data-type=milestone]:is(swp-allday-container swp-allday-event){background:var(--color-event-milestone-hl)!important}.highlight[data-type=personal]:is(swp-allday-container swp-allday-event){background:var(--color-event-personal-hl)!important}.highlight[data-type=deadline]:is(swp-allday-container swp-allday-event){background:var(--color-event-milestone-hl)!important}.max-event-indicator:is(swp-allday-container swp-allday-event){background:#e0e0e0!important;border:1px dashed #999!important;color:#666!important;cursor:pointer!important;font-style:italic;justify-content:center;opacity:.8;text-align:center!important}.max-event-indicator:is(swp-allday-container swp-allday-event):hover{background:#d0d0d0!important;color:#333!important;opacity:1}.max-event-indicator:is(swp-allday-container swp-allday-event) span{display:block;font-size:11px;font-weight:400;text-align:center;width:100%}.max-event-overflow-show:is(swp-allday-container swp-allday-event){opacity:1;transition:opacity .3s ease-in-out}.max-event-overflow-hide:is(swp-allday-container swp-allday-event){opacity:0;transition:opacity .3s ease-in-out}:is(swp-allday-container swp-allday-event) swp-event-time{display:none}:is(swp-allday-container swp-allday-event) swp-event-title{display:block;font-size:12px;line-height:18px}.transitioning:is(swp-allday-container swp-allday-event){transition:grid-area .2s ease-out,grid-row .2s ease-out,grid-column .2s ease-out}swp-scrollable-content{display:grid;overflow-x:auto;overflow-y:auto;position:relative;scroll-behavior:smooth;top:-1px}swp-scrollable-content::-webkit-scrollbar{height:var(--scrollbar-width,12px);width:var(--scrollbar-width,12px)}swp-scrollable-content::-webkit-scrollbar-track{background:var(--scrollbar-track-color,#f0f0f0)}swp-scrollable-content::-webkit-scrollbar-thumb{background:var(--scrollbar-color,#666);border-radius:var(--scrollbar-border-radius,6px)}:is(swp-scrollable-content::-webkit-scrollbar-thumb):hover{background:var(--scrollbar-hover-color,#333)}swp-scrollable-content{scrollbar-color:var(--scrollbar-color,#666) var(--scrollbar-track-color,#f0f0f0);scrollbar-width:auto}swp-time-grid{height:calc((var(--day-end-hour) - var(--day-start-hour))*var(--hour-height));position:relative}swp-time-grid:before{background:transparent;display:none;height:0}swp-time-grid:after,swp-time-grid:before{content:"";left:0;min-width:calc(var(--grid-columns, 7)*var(--day-column-min-width));position:absolute;right:0;top:0}swp-time-grid:after{background-image:repeating-linear-gradient(to bottom,transparent,transparent calc(var(--hour-height) - 1px),var(--color-hour-line) calc(var(--hour-height) - 1px),var(--color-hour-line) var(--hour-height));bottom:0;z-index:1}swp-grid-lines{background-image:repeating-linear-gradient(to bottom,transparent,transparent calc(var(--hour-height)/4 - 1px),var(--color-grid-line-light) calc(var(--hour-height)/4 - 1px),var(--color-grid-line-light) calc(var(--hour-height)/4));bottom:0;left:0;right:0;top:0;z-index:var(--z-grid)}swp-day-columns,swp-grid-lines{min-width:calc(var(--grid-columns, 7)*var(--day-column-min-width));position:absolute}swp-day-columns{display:grid;grid-template-columns:repeat(var(--grid-columns,7),minmax(var(--day-column-min-width),1fr));inset:0}swp-day-column{background:var(--color-event-grid);border-right:1px solid var(--color-grid-line);min-width:var(--day-column-min-width);position:relative}swp-day-column:last-child{border-right:none}swp-day-column:after,swp-day-column:before{background:var(--color-non-work-hours);content:"";left:0;opacity:.3;position:absolute;right:0;z-index:2}swp-day-column:before{height:var(--before-work-height,0);top:0}swp-day-column:after{bottom:0;top:var(--after-work-top,100%)}swp-day-column[data-work-hours=off]{background:var(--color-non-work-hours)}swp-day-column[data-work-hours=off]:after,swp-day-column[data-work-hours=off]:before{display:none}swp-resource-column{background:var(--color-event-grid);border-right:1px solid var(--color-grid-line);min-width:var(--day-column-min-width);position:relative}swp-resource-column:last-child{border-right:none}swp-events-layer{display:block;inset:0;position:absolute;z-index:var(--z-event)}swp-current-time-indicator{background:var(--color-current-time);height:2px;left:0;position:absolute;right:0;z-index:var(--z-current-time)}swp-current-time-indicator:before{background:var(--color-current-time);border-radius:3px;color:#fff;content:attr(data-time);font-size:.75rem;left:-55px;padding:2px 6px;position:absolute;top:-10px;white-space:nowrap}swp-current-time-indicator:after{background:var(--color-current-time);border-radius:50%;box-shadow:0 0 0 2px rgba(255,0,0,.3);content:"";height:10px;position:absolute;right:-4px;top:-4px;width:10px} \ No newline at end of file +.calendar-wrapper{box-sizing:border-box;display:flex;flex-direction:column;height:100vh;margin:0;overflow:hidden;padding:0;width:100vw}swp-calendar{background:var(--color-background);display:grid;grid-template-rows:auto 1fr;height:100vh;overflow:hidden;position:relative;width:100%}swp-calendar[data-fit-to-width=true] swp-scrollable-content{overflow-x:hidden}swp-calendar-nav{align-items:center;background:var(--color-background);border-bottom:1px solid var(--color-border);box-shadow:var(--shadow-sm);display:grid;gap:20px;grid-template-columns:auto 1fr auto auto;padding:12px 16px}swp-calendar-container{display:grid;grid-template-columns:60px 1fr;grid-template-rows:auto 1fr;height:100%;overflow:hidden;position:relative}swp-calendar-container.week-transition{transition:opacity .3s ease}swp-calendar-container.week-transition:is(-out){opacity:.5}swp-header-spacer{background:var(--color-surface);border-bottom:1px solid var(--color-border);border-right:1px solid var(--color-border);grid-column:1;grid-row:1;height:calc(var(--header-height) + var(--all-day-row-height));position:relative;z-index:5}.allday-chevron{background:none;border:none;border-radius:4px;bottom:2px;color:#666;cursor:pointer;left:50%;padding:4px 8px;position:absolute;transform:translateX(-50%);transition:transform .3s ease,color .2s ease}.allday-chevron:hover{background-color:rgba(0,0,0,.05);color:#000}.allday-chevron.collapsed{transform:translateX(-50%) rotate(0deg)}.allday-chevron.expanded{transform:translateX(-50%) rotate(180deg)}.allday-chevron svg{display:block;height:8px;width:12px}swp-grid-container{display:grid;grid-column:2;grid-row:1/3;grid-template-rows:auto 1fr;transition:transform .4s cubic-bezier(.4,0,.2,1);width:100%}swp-grid-container,swp-time-axis{overflow:hidden;position:relative}swp-time-axis{background:var(--color-surface);border-right:1px solid var(--color-border);grid-column:1;grid-row:2;height:100%;left:0;width:60px;z-index:3}swp-time-axis-content{display:flex;flex-direction:column;position:relative}swp-hour-marker{align-items:flex-start;color:var(--color-text-secondary);display:flex;font-size:.75rem;height:var(--hour-height);padding:0 8px 8px 15px;position:relative}swp-hour-marker:before{background:var(--color-hour-line);content:"";height:1px;left:50px;position:absolute;top:-1px;width:calc(100vw - 60px);z-index:2}swp-calendar-header{background:var(--color-surface);display:grid;grid-template-columns:repeat(var(--grid-columns,7),minmax(var(--day-column-min-width),1fr));grid-template-rows:var(--header-height) auto;height:calc(var(--header-height) + var(--all-day-row-height));min-width:calc(var(--grid-columns, 7)*var(--day-column-min-width));overflow-x:hidden;overflow-y:scroll;position:sticky;top:0;z-index:3}swp-calendar-header::-webkit-scrollbar{background:transparent;width:17px}swp-calendar-header::-webkit-scrollbar-thumb,swp-calendar-header::-webkit-scrollbar-track{background:transparent}swp-calendar-header swp-allday-container{align-items:center;display:grid;gap:2px 0;grid-auto-rows:var(--single-row-height);grid-column:1/-1;grid-row:2;grid-template-columns:repeat(var(--grid-columns,7),minmax(var(--day-column-min-width),1fr));overflow:hidden}:is(swp-calendar-header swp-allday-container):has(swp-allday-event){border-bottom:1px solid var(--color-grid-line)}swp-day-header{align-items:center;border-bottom:1px solid var(--color-grid-line);border-right:1px solid var(--color-grid-line);display:flex;flex-direction:column;grid-row:1;justify-content:center;padding-top:3px;text-align:center}swp-day-header:last-child{border-right:none}swp-day-header[data-today=true]{background:rgba(33,150,243,.1)}swp-day-header[data-today=true] swp-day-name{color:var(--color-primary);font-weight:600}swp-day-header[data-today=true] swp-day-date{color:var(--color-primary)}swp-day-name{color:var(--color-text-secondary);display:block;font-size:12px;font-weight:500;letter-spacing:.1em}swp-day-date{display:block;font-size:30px;margin-top:4px}swp-resource-header{align-items:center;background:var(--color-surface);border-bottom:1px solid var(--color-grid-line);border-right:1px solid var(--color-grid-line);display:flex;flex-direction:column;justify-content:center;padding:12px;text-align:center}swp-resource-header:last-child{border-right:none}swp-resource-avatar{background:var(--color-border);border-radius:50%;display:block;height:40px;margin-bottom:8px;overflow:hidden;width:40px}swp-resource-avatar img{height:100%;-o-object-fit:cover;object-fit:cover;width:100%}swp-resource-name{color:var(--color-text);display:block;font-size:.875rem;font-weight:500;text-align:center}swp-allday-column{background:transparent;height:100%;opacity:0;position:relative;z-index:1}swp-allday-container swp-allday-event{align-items:center;background:#08f;border-radius:3px;color:#fff;display:flex;font-size:.75rem;height:22px!important;justify-content:flex-start;left:auto!important;margin:1px;overflow:hidden;padding:2px 4px;position:relative!important;right:auto!important;text-overflow:ellipsis;top:auto!important;white-space:nowrap;width:auto!important;z-index:2}[data-type=meeting]:is(swp-allday-container swp-allday-event){background:var(--color-event-meeting);color:var(--color-text)}[data-type=meal]:is(swp-allday-container swp-allday-event){background:var(--color-event-meal);color:var(--color-text)}[data-type=work]:is(swp-allday-container swp-allday-event){background:var(--color-event-work);color:var(--color-text)}[data-type=milestone]:is(swp-allday-container swp-allday-event){background:var(--color-event-milestone);color:var(--color-text)}[data-type=personal]:is(swp-allday-container swp-allday-event){background:var(--color-event-personal);color:var(--color-text)}[data-type=deadline]:is(swp-allday-container swp-allday-event){background:var(--color-event-milestone);color:var(--color-text)}.dragging:is(swp-allday-container swp-allday-event){opacity:1}.highlight[data-type=meeting]:is(swp-allday-container swp-allday-event){background:var(--color-event-meeting-hl)!important}.highlight[data-type=meal]:is(swp-allday-container swp-allday-event){background:var(--color-event-meal-hl)!important}.highlight[data-type=work]:is(swp-allday-container swp-allday-event){background:var(--color-event-work-hl)!important}.highlight[data-type=milestone]:is(swp-allday-container swp-allday-event){background:var(--color-event-milestone-hl)!important}.highlight[data-type=personal]:is(swp-allday-container swp-allday-event){background:var(--color-event-personal-hl)!important}.highlight[data-type=deadline]:is(swp-allday-container swp-allday-event){background:var(--color-event-milestone-hl)!important}.max-event-indicator:is(swp-allday-container swp-allday-event){background:#e0e0e0!important;border:1px dashed #999!important;color:#666!important;cursor:pointer!important;font-style:italic;justify-content:center;opacity:.8;text-align:center!important}.max-event-indicator:is(swp-allday-container swp-allday-event):hover{background:#d0d0d0!important;color:#333!important;opacity:1}.max-event-indicator:is(swp-allday-container swp-allday-event) span{display:block;font-size:11px;font-weight:400;text-align:center;width:100%}.max-event-overflow-show:is(swp-allday-container swp-allday-event){opacity:1;transition:opacity .3s ease-in-out}.max-event-overflow-hide:is(swp-allday-container swp-allday-event){opacity:0;transition:opacity .3s ease-in-out}:is(swp-allday-container swp-allday-event) swp-event-time{display:none}:is(swp-allday-container swp-allday-event) swp-event-title{display:block;font-size:12px;line-height:18px}.transitioning:is(swp-allday-container swp-allday-event){transition:grid-area .2s ease-out,grid-row .2s ease-out,grid-column .2s ease-out}swp-scrollable-content{display:grid;overflow-x:auto;overflow-y:auto;position:relative;scroll-behavior:smooth;top:-1px}swp-scrollable-content::-webkit-scrollbar{height:var(--scrollbar-width,12px);width:var(--scrollbar-width,12px)}swp-scrollable-content::-webkit-scrollbar-track{background:var(--scrollbar-track-color,#f0f0f0)}swp-scrollable-content::-webkit-scrollbar-thumb{background:var(--scrollbar-color,#666);border-radius:var(--scrollbar-border-radius,6px)}:is(swp-scrollable-content::-webkit-scrollbar-thumb):hover{background:var(--scrollbar-hover-color,#333)}swp-scrollable-content{scrollbar-color:var(--scrollbar-color,#666) var(--scrollbar-track-color,#f0f0f0);scrollbar-width:auto}swp-time-grid{height:calc((var(--day-end-hour) - var(--day-start-hour))*var(--hour-height));position:relative}swp-time-grid:before{background:transparent;display:none;height:0}swp-time-grid:after,swp-time-grid:before{content:"";left:0;min-width:calc(var(--grid-columns, 7)*var(--day-column-min-width));position:absolute;right:0;top:0}swp-time-grid:after{background-image:repeating-linear-gradient(to bottom,transparent,transparent calc(var(--hour-height) - 1px),var(--color-hour-line) calc(var(--hour-height) - 1px),var(--color-hour-line) var(--hour-height));bottom:0;z-index:1}swp-grid-lines{background-image:repeating-linear-gradient(to bottom,transparent,transparent calc(var(--hour-height)/4 - 1px),var(--color-grid-line-light) calc(var(--hour-height)/4 - 1px),var(--color-grid-line-light) calc(var(--hour-height)/4));bottom:0;left:0;right:0;top:0;z-index:var(--z-grid)}swp-day-columns,swp-grid-lines{min-width:calc(var(--grid-columns, 7)*var(--day-column-min-width));position:absolute}swp-day-columns{display:grid;grid-template-columns:repeat(var(--grid-columns,7),minmax(var(--day-column-min-width),1fr));inset:0}swp-day-column{background:var(--color-event-grid);border-right:1px solid var(--color-grid-line);min-width:var(--day-column-min-width);position:relative}swp-day-column:last-child{border-right:none}swp-day-column:after,swp-day-column:before{background:var(--color-non-work-hours);content:"";left:0;opacity:.3;position:absolute;right:0;z-index:2}swp-day-column:before{height:var(--before-work-height,0);top:0}swp-day-column:after{bottom:0;top:var(--after-work-top,100%)}swp-day-column[data-work-hours=off]{background:var(--color-non-work-hours)}swp-day-column[data-work-hours=off]:after,swp-day-column[data-work-hours=off]:before{display:none}swp-resource-column{background:var(--color-event-grid);border-right:1px solid var(--color-grid-line);min-width:var(--day-column-min-width);position:relative}swp-resource-column:last-child{border-right:none}swp-events-layer{display:block;inset:0;position:absolute;z-index:var(--z-event)}swp-current-time-indicator{background:var(--color-current-time);height:2px;left:0;position:absolute;right:0;z-index:var(--z-current-time)}swp-current-time-indicator:before{background:var(--color-current-time);border-radius:3px;color:#fff;content:attr(data-time);font-size:.75rem;left:-55px;padding:2px 6px;position:absolute;top:-10px;white-space:nowrap}swp-current-time-indicator:after{background:var(--color-current-time);border-radius:50%;box-shadow:0 0 0 2px rgba(255,0,0,.3);content:"";height:10px;position:absolute;right:-4px;top:-4px;width:10px} \ No newline at end of file diff --git a/wwwroot/css/src/calendar-layout-css.css b/wwwroot/css/src/calendar-layout-css.css index c1a1ab4..aca2407 100644 --- a/wwwroot/css/src/calendar-layout-css.css +++ b/wwwroot/css/src/calendar-layout-css.css @@ -197,6 +197,11 @@ swp-calendar-header { gap: 2px 0px; align-items: center; overflow: hidden; + + /* Border only when events exist */ + &:has(swp-allday-event) { + border-bottom: 1px solid var(--color-grid-line); + } } }