Adds header drawer and event drag interactions

Introduces HeaderDrawerRenderer and HeaderDrawerLayoutEngine to support dragging events into an all-day header drawer

Enables dynamic event placement and conversion between timed and all-day events through new drag interactions
Implements flexible layout calculation for header items with column and row management

Extends DragDropManager to handle header zone interactions
Adds new event types for header drag events
This commit is contained in:
Janus C. H. Knudsen 2025-12-10 23:11:11 +01:00
parent 026d83eb32
commit 6723658fd9
11 changed files with 850 additions and 4 deletions

View file

@ -8,7 +8,10 @@ import {
IDragMovePayload,
IDragEndPayload,
IDragCancelPayload,
IDragColumnChangePayload
IDragColumnChangePayload,
IDragEnterHeaderPayload,
IDragMoveHeaderPayload,
IDragLeaveHeaderPayload
} from '../types/DragTypes';
interface DragState {
@ -39,6 +42,7 @@ export class DragDropManager {
private pendingElement: HTMLElement | null = null;
private pendingMouseOffset: IMousePosition | null = null;
private container: HTMLElement | null = null;
private inHeader = false;
private readonly DRAG_THRESHOLD = 5;
private readonly INTERPOLATION_FACTOR = 0.3;
@ -159,6 +163,7 @@ export class DragDropManager {
// Cleanup
this.dragState.element.classList.remove('dragging');
this.dragState = null;
this.inHeader = false;
};
private initializeDrag(element: HTMLElement, mouseOffset: IMousePosition, e: PointerEvent): void {
@ -217,6 +222,12 @@ export class DragDropManager {
private updateDragTarget(e: PointerEvent): void {
if (!this.dragState) return;
// Check header zone first
this.checkHeaderZone(e);
// Skip normal grid handling if in header
if (this.inHeader) return;
// Check for column change
const columnAtPoint = this.getColumnAtPoint(e.clientX);
if (columnAtPoint && columnAtPoint !== this.dragState.currentColumn) {
@ -244,6 +255,74 @@ export class DragDropManager {
}
}
/**
* Check if pointer is in header zone and emit appropriate events
*/
private checkHeaderZone(e: PointerEvent): void {
if (!this.dragState) return;
const headerViewport = document.querySelector('swp-header-viewport');
if (!headerViewport) return;
const rect = headerViewport.getBoundingClientRect();
const isInHeader = e.clientY < rect.bottom;
if (isInHeader && !this.inHeader) {
// Entered header
this.inHeader = true;
const payload: IDragEnterHeaderPayload = {
eventId: this.dragState.eventId,
element: this.dragState.element,
sourceColumnIndex: this.getColumnIndex(this.dragState.columnElement),
sourceDate: this.dragState.columnElement.dataset.date || '',
title: this.dragState.element.querySelector('swp-event-title')?.textContent || '',
colorClass: [...this.dragState.element.classList].find(c => c.startsWith('is-')),
itemType: 'event',
duration: 1
};
this.eventBus.emit(CoreEvents.EVENT_DRAG_ENTER_HEADER, payload);
} else if (!isInHeader && this.inHeader) {
// Left header
this.inHeader = false;
const payload: IDragLeaveHeaderPayload = {
eventId: this.dragState.eventId
};
this.eventBus.emit(CoreEvents.EVENT_DRAG_LEAVE_HEADER, payload);
} else if (isInHeader) {
// Moving within header
const column = this.getColumnAtX(e.clientX);
if (column) {
const payload: IDragMoveHeaderPayload = {
eventId: this.dragState.eventId,
columnIndex: this.getColumnIndex(column),
dateKey: column.dataset.date || ''
};
this.eventBus.emit(CoreEvents.EVENT_DRAG_MOVE_HEADER, payload);
}
}
}
/**
* Get column index (0-based) for a column element
*/
private getColumnIndex(column: HTMLElement): number {
if (!this.container) return 0;
const columns = Array.from(this.container.querySelectorAll('swp-day-column'));
return columns.indexOf(column);
}
/**
* Get column at X coordinate (alias for getColumnAtPoint)
*/
private getColumnAtX(clientX: number): HTMLElement | null {
return this.getColumnAtPoint(clientX);
}
/**
* Find column element at given X coordinate
*/
@ -323,5 +402,6 @@ export class DragDropManager {
this.eventBus.emit(CoreEvents.EVENT_DRAG_CANCEL, payload);
this.dragState = null;
this.inHeader = false;
}
}