Enhances event drag and resize functionality
Improves event dragging by tracking the source column and using the updated event data for re-rendering affected columns. Also, enhances event resizing by updating the event data and re-rendering the column to recalculate stacking/grouping. Uses snap interval as minimum duration when resizing.
This commit is contained in:
parent
e83753a7d2
commit
ecb1729c28
4 changed files with 154 additions and 7 deletions
|
|
@ -40,6 +40,7 @@ export class DragDropManager {
|
||||||
private draggedElement!: HTMLElement | null;
|
private draggedElement!: HTMLElement | null;
|
||||||
private draggedClone!: HTMLElement | null;
|
private draggedClone!: HTMLElement | null;
|
||||||
private currentColumnBounds: ColumnBounds | null = null;
|
private currentColumnBounds: ColumnBounds | null = null;
|
||||||
|
private initialColumnBounds: ColumnBounds | null = null; // Track source column
|
||||||
private isDragStarted = false;
|
private isDragStarted = false;
|
||||||
|
|
||||||
// Hover state
|
// Hover state
|
||||||
|
|
@ -231,8 +232,9 @@ export class DragDropManager {
|
||||||
this.draggedElement.style.zIndex = '9999';
|
this.draggedElement.style.zIndex = '9999';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect current column
|
// Detect current column and save as initial source column
|
||||||
this.currentColumnBounds = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
this.currentColumnBounds = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
||||||
|
this.initialColumnBounds = this.currentColumnBounds; // Save source column
|
||||||
|
|
||||||
// Cast to BaseSwpEventElement and create clone (works for both SwpEventElement and SwpAllDayEventElement)
|
// Cast to BaseSwpEventElement and create clone (works for both SwpEventElement and SwpAllDayEventElement)
|
||||||
const originalElement = this.draggedElement as BaseSwpEventElement;
|
const originalElement = this.draggedElement as BaseSwpEventElement;
|
||||||
|
|
@ -344,8 +346,9 @@ export class DragDropManager {
|
||||||
const dragEndPayload: DragEndEventPayload = {
|
const dragEndPayload: DragEndEventPayload = {
|
||||||
originalElement: this.draggedElement,
|
originalElement: this.draggedElement,
|
||||||
draggedClone: this.draggedClone,
|
draggedClone: this.draggedClone,
|
||||||
|
sourceColumn: this.initialColumnBounds, // Where drag started
|
||||||
mousePosition,
|
mousePosition,
|
||||||
finalPosition: { column, snappedY },
|
finalPosition: { column, snappedY }, // Where drag ended
|
||||||
target: dropTarget
|
target: dropTarget
|
||||||
};
|
};
|
||||||
this.eventBus.emit('drag:end', dragEndPayload);
|
this.eventBus.emit('drag:end', dragEndPayload);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { eventBus } from '../core/EventBus';
|
import { eventBus } from '../core/EventBus';
|
||||||
import { CoreEvents } from '../constants/CoreEvents';
|
import { CoreEvents } from '../constants/CoreEvents';
|
||||||
import { calendarConfig } from '../core/CalendarConfig';
|
import { calendarConfig } from '../core/CalendarConfig';
|
||||||
|
import { ResizeEndEventPayload } from '../types/EventTypes';
|
||||||
|
|
||||||
type SwpEventEl = HTMLElement & { updateHeight?: (h: number) => void };
|
type SwpEventEl = HTMLElement & { updateHeight?: (h: number) => void };
|
||||||
|
|
||||||
|
|
@ -33,7 +34,7 @@ export class ResizeHandleManager {
|
||||||
const grid = calendarConfig.getGridSettings();
|
const grid = calendarConfig.getGridSettings();
|
||||||
this.hourHeightPx = grid.hourHeight;
|
this.hourHeightPx = grid.hourHeight;
|
||||||
this.snapMin = grid.snapInterval;
|
this.snapMin = grid.snapInterval;
|
||||||
this.minDurationMin = grid.minEventDuration ?? this.snapMin;
|
this.minDurationMin = this.snapMin; // Use snap interval as minimum duration
|
||||||
}
|
}
|
||||||
|
|
||||||
public initialize(): void {
|
public initialize(): void {
|
||||||
|
|
@ -232,6 +233,15 @@ export class ResizeHandleManager {
|
||||||
|
|
||||||
this.targetEl.updateHeight?.(finalHeight);
|
this.targetEl.updateHeight?.(finalHeight);
|
||||||
|
|
||||||
|
// Emit resize:end event for re-stacking
|
||||||
|
const eventId = this.targetEl.dataset.eventId || '';
|
||||||
|
const resizeEndPayload: ResizeEndEventPayload = {
|
||||||
|
eventId,
|
||||||
|
element: this.targetEl,
|
||||||
|
finalHeight
|
||||||
|
};
|
||||||
|
eventBus.emit('resize:end', resizeEndPayload);
|
||||||
|
|
||||||
const group = this.targetEl.closest<HTMLElement>('swp-event-group') ?? this.targetEl;
|
const group = this.targetEl.closest<HTMLElement>('swp-event-group') ?? this.targetEl;
|
||||||
group.style.zIndex = this.prevZ ?? '';
|
group.style.zIndex = this.prevZ ?? '';
|
||||||
this.prevZ = undefined;
|
this.prevZ = undefined;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ import { CalendarTypeFactory } from '../factories/CalendarTypeFactory';
|
||||||
import { EventManager } from '../managers/EventManager';
|
import { EventManager } from '../managers/EventManager';
|
||||||
import { EventRendererStrategy } from './EventRenderer';
|
import { EventRendererStrategy } from './EventRenderer';
|
||||||
import { SwpEventElement } from '../elements/SwpEventElement';
|
import { SwpEventElement } from '../elements/SwpEventElement';
|
||||||
import { DragStartEventPayload, DragMoveEventPayload, DragEndEventPayload, DragMouseEnterHeaderEventPayload, DragMouseLeaveHeaderEventPayload, DragColumnChangeEventPayload, HeaderReadyEventPayload } from '../types/EventTypes';
|
import { DragStartEventPayload, DragMoveEventPayload, DragEndEventPayload, DragMouseEnterHeaderEventPayload, DragMouseLeaveHeaderEventPayload, DragColumnChangeEventPayload, HeaderReadyEventPayload, ResizeEndEventPayload } from '../types/EventTypes';
|
||||||
|
import { DateService } from '../utils/DateService';
|
||||||
|
import { ColumnBounds } from '../utils/ColumnDetectionUtils';
|
||||||
/**
|
/**
|
||||||
* EventRenderingService - Render events i DOM med positionering using Strategy Pattern
|
* EventRenderingService - Render events i DOM med positionering using Strategy Pattern
|
||||||
* Håndterer event positioning og overlap detection
|
* Håndterer event positioning og overlap detection
|
||||||
|
|
@ -15,6 +17,7 @@ export class EventRenderingService {
|
||||||
private eventBus: IEventBus;
|
private eventBus: IEventBus;
|
||||||
private eventManager: EventManager;
|
private eventManager: EventManager;
|
||||||
private strategy: EventRendererStrategy;
|
private strategy: EventRendererStrategy;
|
||||||
|
private dateService: DateService;
|
||||||
|
|
||||||
private dragMouseLeaveHeaderListener: ((event: Event) => void) | null = null;
|
private dragMouseLeaveHeaderListener: ((event: Event) => void) | null = null;
|
||||||
|
|
||||||
|
|
@ -26,6 +29,10 @@ export class EventRenderingService {
|
||||||
const calendarType = calendarConfig.getCalendarMode();
|
const calendarType = calendarConfig.getCalendarMode();
|
||||||
this.strategy = CalendarTypeFactory.getEventRenderer(calendarType);
|
this.strategy = CalendarTypeFactory.getEventRenderer(calendarType);
|
||||||
|
|
||||||
|
// Initialize DateService
|
||||||
|
const timezone = calendarConfig.getTimezone?.();
|
||||||
|
this.dateService = new DateService(timezone);
|
||||||
|
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,7 +182,7 @@ export class EventRenderingService {
|
||||||
|
|
||||||
// Handle drag end events and delegate to appropriate renderer
|
// Handle drag end events and delegate to appropriate renderer
|
||||||
this.eventBus.on('drag:end', (event: Event) => {
|
this.eventBus.on('drag:end', (event: Event) => {
|
||||||
const { originalElement: draggedElement, finalPosition, target } = (event as CustomEvent<DragEndEventPayload>).detail;
|
const { originalElement: draggedElement, sourceColumn, finalPosition, target } = (event as CustomEvent<DragEndEventPayload>).detail;
|
||||||
const finalColumn = finalPosition.column;
|
const finalColumn = finalPosition.column;
|
||||||
const finalY = finalPosition.snappedY;
|
const finalY = finalPosition.snappedY;
|
||||||
const eventId = draggedElement.dataset.eventId || '';
|
const eventId = draggedElement.dataset.eventId || '';
|
||||||
|
|
@ -188,6 +195,27 @@ export class EventRenderingService {
|
||||||
if (draggedElement && draggedClone && this.strategy.handleDragEnd) {
|
if (draggedElement && draggedClone && this.strategy.handleDragEnd) {
|
||||||
this.strategy.handleDragEnd(eventId, draggedElement, draggedClone, finalColumn, finalY);
|
this.strategy.handleDragEnd(eventId, draggedElement, draggedClone, finalColumn, finalY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update event data in EventManager with new position from clone
|
||||||
|
if (draggedClone) {
|
||||||
|
const swpEvent = draggedClone as SwpEventElement;
|
||||||
|
const newStart = swpEvent.start;
|
||||||
|
const newEnd = swpEvent.end;
|
||||||
|
|
||||||
|
this.eventManager.updateEvent(eventId, {
|
||||||
|
start: newStart,
|
||||||
|
end: newEnd
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('📝 EventRendererManager: Updated event in EventManager', {
|
||||||
|
eventId,
|
||||||
|
newStart,
|
||||||
|
newEnd
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-render affected columns for stacking/grouping (now with updated data)
|
||||||
|
this.reRenderAffectedColumns(sourceColumn, finalColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up any remaining day event clones
|
// Clean up any remaining day event clones
|
||||||
|
|
@ -228,6 +256,37 @@ export class EventRenderingService {
|
||||||
|
|
||||||
this.eventBus.on('drag:mouseleave-header', this.dragMouseLeaveHeaderListener);
|
this.eventBus.on('drag:mouseleave-header', this.dragMouseLeaveHeaderListener);
|
||||||
|
|
||||||
|
// Handle resize end events
|
||||||
|
this.eventBus.on('resize:end', (event: Event) => {
|
||||||
|
const { eventId, element } = (event as CustomEvent<ResizeEndEventPayload>).detail;
|
||||||
|
|
||||||
|
// Update event data in EventManager with new end time from resized element
|
||||||
|
const swpEvent = element as SwpEventElement;
|
||||||
|
const newStart = swpEvent.start;
|
||||||
|
const newEnd = swpEvent.end;
|
||||||
|
|
||||||
|
this.eventManager.updateEvent(eventId, {
|
||||||
|
start: newStart,
|
||||||
|
end: newEnd
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('📝 EventRendererManager: Updated event after resize', {
|
||||||
|
eventId,
|
||||||
|
newStart,
|
||||||
|
newEnd
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find the column for this event
|
||||||
|
const columnElement = element.closest('swp-day-column') as HTMLElement;
|
||||||
|
if (columnElement) {
|
||||||
|
const columnDate = columnElement.dataset.date;
|
||||||
|
if (columnDate) {
|
||||||
|
// Re-render the column to recalculate stacking/grouping
|
||||||
|
this.renderSingleColumn(columnDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Handle navigation period change
|
// Handle navigation period change
|
||||||
this.eventBus.on(CoreEvents.NAVIGATION_COMPLETED, () => {
|
this.eventBus.on(CoreEvents.NAVIGATION_COMPLETED, () => {
|
||||||
// Delegate to strategy if it handles navigation
|
// Delegate to strategy if it handles navigation
|
||||||
|
|
@ -291,6 +350,73 @@ export class EventRenderingService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-render affected columns after drag to recalculate stacking/grouping
|
||||||
|
*/
|
||||||
|
private reRenderAffectedColumns(sourceColumn: ColumnBounds | null, targetColumn: ColumnBounds | null): void {
|
||||||
|
const columnsToRender = new Set<string>();
|
||||||
|
|
||||||
|
// Add source column if exists
|
||||||
|
if (sourceColumn) {
|
||||||
|
columnsToRender.add(sourceColumn.date);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add target column if exists and different from source
|
||||||
|
if (targetColumn && targetColumn.date !== sourceColumn?.date) {
|
||||||
|
columnsToRender.add(targetColumn.date);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-render each affected column
|
||||||
|
columnsToRender.forEach(columnDate => {
|
||||||
|
this.renderSingleColumn(columnDate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render events for a single column by re-rendering entire container
|
||||||
|
*/
|
||||||
|
private renderSingleColumn(columnDate: string): void {
|
||||||
|
// Find the column element
|
||||||
|
const columnElement = document.querySelector(`swp-day-column[data-date="${columnDate}"]`) as HTMLElement;
|
||||||
|
if (!columnElement) {
|
||||||
|
console.warn('EventRendererManager: Column not found', { columnDate });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the parent container (swp-day-columns)
|
||||||
|
const container = columnElement.closest('swp-day-columns') as HTMLElement;
|
||||||
|
if (!container) {
|
||||||
|
console.warn('EventRendererManager: Container not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all columns in container to determine date range
|
||||||
|
const allColumns = Array.from(container.querySelectorAll<HTMLElement>('swp-day-column'));
|
||||||
|
if (allColumns.length === 0) return;
|
||||||
|
|
||||||
|
// Get date range from first and last column
|
||||||
|
const firstColumnDate = allColumns[0].dataset.date;
|
||||||
|
const lastColumnDate = allColumns[allColumns.length - 1].dataset.date;
|
||||||
|
|
||||||
|
if (!firstColumnDate || !lastColumnDate) return;
|
||||||
|
|
||||||
|
const startDate = this.dateService.parseISO(`${firstColumnDate}T00:00:00`);
|
||||||
|
const endDate = this.dateService.parseISO(`${lastColumnDate}T23:59:59.999`);
|
||||||
|
|
||||||
|
// Re-render entire container (this will recalculate stacking for all columns)
|
||||||
|
this.renderEvents({
|
||||||
|
container,
|
||||||
|
startDate,
|
||||||
|
endDate
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('🔄 EventRendererManager: Re-rendered container for column', {
|
||||||
|
columnDate,
|
||||||
|
startDate: firstColumnDate,
|
||||||
|
endDate: lastColumnDate
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private clearEvents(container?: HTMLElement): void {
|
private clearEvents(container?: HTMLElement): void {
|
||||||
this.strategy.clearEvents(container);
|
this.strategy.clearEvents(container);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,10 @@ export interface DragMoveEventPayload {
|
||||||
export interface DragEndEventPayload {
|
export interface DragEndEventPayload {
|
||||||
originalElement: HTMLElement;
|
originalElement: HTMLElement;
|
||||||
draggedClone: HTMLElement | null;
|
draggedClone: HTMLElement | null;
|
||||||
|
sourceColumn: ColumnBounds | null; // Where drag started
|
||||||
mousePosition: MousePosition;
|
mousePosition: MousePosition;
|
||||||
finalPosition: {
|
finalPosition: {
|
||||||
column: ColumnBounds | null;
|
column: ColumnBounds | null; // Where drag ended
|
||||||
snappedY: number;
|
snappedY: number;
|
||||||
};
|
};
|
||||||
target: 'swp-day-column' | 'swp-day-header' | null;
|
target: 'swp-day-column' | 'swp-day-header' | null;
|
||||||
|
|
@ -78,5 +79,12 @@ export interface DragColumnChangeEventPayload {
|
||||||
// Header ready event payload
|
// Header ready event payload
|
||||||
export interface HeaderReadyEventPayload {
|
export interface HeaderReadyEventPayload {
|
||||||
headerElements: ColumnBounds[];
|
headerElements: ColumnBounds[];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize end event payload
|
||||||
|
export interface ResizeEndEventPayload {
|
||||||
|
eventId: string;
|
||||||
|
element: HTMLElement;
|
||||||
|
finalHeight: number;
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue