diff --git a/.workbench/anotherresize.txt b/.workbench/anotherresize.txt deleted file mode 100644 index 01b7b0f..0000000 --- a/.workbench/anotherresize.txt +++ /dev/null @@ -1,179 +0,0 @@ -import { eventBus } from '../core/EventBus'; -import { CoreEvents } from '../constants/CoreEvents'; -import { calendarConfig } from '../core/CalendarConfig'; - -type SwpEventEl = HTMLElement & { updateHeight?: (h: number) => void }; - -export class ResizeHandleManager { - private cachedEvents: SwpEventEl[] = []; - private isResizing = false; - private targetEl: SwpEventEl | null = null; - - private startY = 0; - private startDurationMin = 0; - private direction: 'grow' | 'shrink' = 'grow'; - - private hourHeightPx: number; - private snapMin: number; - private minDurationMin: number; - private animationId: number | null = null; - private currentHeight = 0; - private targetHeight = 0; - - // cleanup - private unsubscribers: Array<() => void> = []; - private pointerCaptured = false; - private prevZ?: string; - - constructor() { - const grid = calendarConfig.getGridSettings(); - this.hourHeightPx = grid.hourHeight; - this.snapMin = grid.snapInterval; - this.minDurationMin = grid.minEventDuration ?? this.snapMin; - } - - public initialize(): void { - this.refreshEventCache(); - this.attachGlobalListeners(); - this.attachHandles(); - this.subToBus(); - } - - public destroy(): void { - document.removeEventListener('pointerdown', this.onPointerDown, true); - document.removeEventListener('pointermove', this.onPointerMove, true); - document.removeEventListener('pointerup', this.onPointerUp, true); - this.unsubscribers.forEach(u => u()); - } - - private minutesPerPx(): number { - return 60 / this.hourHeightPx; - } - - private pxFromMinutes(min: number): number { - return (min / 60) * this.hourHeightPx; - } - - private roundSnap(min: number, dir: 'grow' | 'shrink'): number { - const q = min / this.snapMin; - return (dir === 'grow' ? Math.ceil(q) : Math.floor(q)) * this.snapMin; - } - - private refreshEventCache(): void { - this.cachedEvents = Array.from( - document.querySelectorAll('swp-day-columns swp-event') - ); - } - - private attachHandles(): void { - // ensure a single handle per event - this.cachedEvents.forEach(el => { - if (!el.querySelector(':scope > swp-resize-handle')) { - const handle = document.createElement('swp-resize-handle'); - handle.setAttribute('aria-label', 'Resize event'); - handle.setAttribute('role', 'separator'); - el.appendChild(handle); - } - }); - } - - private attachGlobalListeners(): void { - document.addEventListener('pointerdown', this.onPointerDown, true); - document.addEventListener('pointermove', this.onPointerMove, true); - document.addEventListener('pointerup', this.onPointerUp, true); - } - - private subToBus(): void { - const sub = (ev: string, fn: () => void) => { - eventBus.on(ev, fn); - this.unsubscribers.push(() => eventBus.off(ev, fn)); - }; - const refresh = () => { this.refreshEventCache(); this.attachHandles(); }; - [CoreEvents.GRID_RENDERED, CoreEvents.EVENTS_RENDERED, - CoreEvents.EVENT_CREATED, CoreEvents.EVENT_UPDATED, - CoreEvents.EVENT_DELETED].forEach(ev => sub(ev, refresh)); - } - - private onPointerDown = (e: PointerEvent) => { - const handle = (e.target as HTMLElement).closest('swp-resize-handle'); - if (!handle) return; - - const el = handle.parentElement as SwpEventEl; - this.targetEl = el; - this.isResizing = true; - this.startY = e.clientY; - - // udled start-varighed fra højde - const startHeight = el.offsetHeight; - this.startDurationMin = Math.max( - this.minDurationMin, - Math.round(startHeight * this.minutesPerPx()) - ); - - this.prevZ = (el.closest('swp-event-group') ?? el).style.zIndex; - (el.closest('swp-event-group') ?? el).style.zIndex = '1000'; - - (e.target as Element).setPointerCapture?.(e.pointerId); - this.pointerCaptured = true; - document.documentElement.classList.add('swp--resizing'); // fx user-select: none; cursor: ns-resize - e.preventDefault(); - }; - - private onPointerMove = (e: PointerEvent) => { - if (!this.isResizing || !this.targetEl) return; - - const dy = e.clientY - this.startY; - this.direction = dy >= 0 ? 'grow' : 'shrink'; - - const deltaMin = dy * this.minutesPerPx(); - const rawMin = this.startDurationMin + deltaMin; - const clamped = Math.max(this.minDurationMin, rawMin); - const snappedMin = this.roundSnap(clamped, this.direction); - - this.targetHeight = this.pxFromMinutes(snappedMin); - - if (this.animationId == null) { - this.currentHeight = this.targetEl.offsetHeight; - this.animate(); - } - }; - - private animate = () => { - if (!this.isResizing || !this.targetEl) { this.animationId = null; return; } - - const diff = this.targetHeight - this.currentHeight; - if (Math.abs(diff) > 0.5) { - this.currentHeight += diff * 0.35; - this.targetEl.updateHeight?.(this.currentHeight); - this.animationId = requestAnimationFrame(this.animate); - } else { - this.currentHeight = this.targetHeight; - this.targetEl.updateHeight?.(this.currentHeight); - this.animationId = null; - } - }; - - private onPointerUp = (e: PointerEvent) => { - if (!this.isResizing || !this.targetEl) return; - - if (this.animationId != null) cancelAnimationFrame(this.animationId); - this.animationId = null; - - // sikker slut-snap - this.targetEl.updateHeight?.(this.targetHeight - 3); // lille gap til grid-linjer - - const group = this.targetEl.closest('swp-event-group') ?? this.targetEl; - group.style.zIndex = this.prevZ ?? ''; - this.prevZ = undefined; - - this.isResizing = false; - this.targetEl = null; - - if (this.pointerCaptured) { - try { (e.target as Element).releasePointerCapture?.(e.pointerId); } catch {} - this.pointerCaptured = false; - } - document.documentElement.classList.remove('swp--resizing'); - this.refreshEventCache(); - }; -} diff --git a/src/renderers/EventRenderer.ts b/src/renderers/EventRenderer.ts index e7169f5..9dd9ea0 100644 --- a/src/renderers/EventRenderer.ts +++ b/src/renderers/EventRenderer.ts @@ -60,14 +60,22 @@ export class DateEventRenderer implements EventRendererStrategy { // Use the clone from the payload instead of creating a new one this.draggedClone = payload.draggedClone; - if (this.draggedClone) { - // Apply drag styling + if (this.draggedClone && payload.columnBounds) { + // Apply drag styling this.applyDragStyling(this.draggedClone); // Add to current column's events layer (not directly to column) - const eventsLayer = payload.columnBounds?.element.querySelector('swp-events-layer'); + const eventsLayer = payload.columnBounds.element.querySelector('swp-events-layer'); if (eventsLayer) { eventsLayer.appendChild(this.draggedClone); + + // Set initial position to prevent "jump to top" effect + // Calculate absolute Y position from original element + const originalRect = this.originalEvent.getBoundingClientRect(); + const columnRect = payload.columnBounds.boundingClientRect; + const initialTop = originalRect.top - columnRect.top; + + this.draggedClone.style.top = `${initialTop}px`; } } diff --git a/wwwroot/css/calendar-events-css.css b/wwwroot/css/calendar-events-css.css index 0d11d43..c70f962 100644 --- a/wwwroot/css/calendar-events-css.css +++ b/wwwroot/css/calendar-events-css.css @@ -6,7 +6,7 @@ swp-day-columns swp-event { border-radius: 3px; overflow: hidden; cursor: pointer; - transition: box-shadow 150ms ease, transform 150ms ease; + transition: background-color 200ms ease, box-shadow 150ms ease, transform 150ms ease; z-index: 10; left: 2px; right: 2px;