Refactors drag and drop logic
Improves drag and drop initialization and handling by extracting methods for clarity. This enhances code readability and maintainability.
This commit is contained in:
parent
b6f2aba398
commit
5d406201b8
2 changed files with 125 additions and 96 deletions
|
|
@ -214,88 +214,111 @@ export class DragDropManager {
|
||||||
if (event.buttons === 1) {
|
if (event.buttons === 1) {
|
||||||
const currentPosition: MousePosition = { x: event.clientX, y: event.clientY };
|
const currentPosition: MousePosition = { x: event.clientX, y: event.clientY };
|
||||||
|
|
||||||
// Check if we need to start drag (movement threshold)
|
// Try to initialize drag if not started
|
||||||
if (!this.isDragStarted && this.draggedElement) {
|
if (!this.isDragStarted && this.draggedElement) {
|
||||||
const deltaX = Math.abs(currentPosition.x - this.initialMousePosition.x);
|
if (!this.tryInitializeDrag(currentPosition)) {
|
||||||
const deltaY = Math.abs(currentPosition.y - this.initialMousePosition.y);
|
return; // Not enough movement yet
|
||||||
const totalMovement = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
||||||
|
|
||||||
if (totalMovement >= this.dragThreshold) {
|
|
||||||
// Start drag - emit drag:start event
|
|
||||||
this.isDragStarted = true;
|
|
||||||
|
|
||||||
// Set high z-index on event-group if exists, otherwise on event itself
|
|
||||||
const eventGroup = this.draggedElement.closest<HTMLElement>('swp-event-group');
|
|
||||||
if (eventGroup) {
|
|
||||||
eventGroup.style.zIndex = '9999';
|
|
||||||
} else {
|
|
||||||
this.draggedElement.style.zIndex = '9999';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect current column and save as initial source column
|
|
||||||
this.currentColumnBounds = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
|
||||||
this.initialColumnBounds = this.currentColumnBounds; // Save source column
|
|
||||||
|
|
||||||
// Cast to BaseSwpEventElement and create clone (works for both SwpEventElement and SwpAllDayEventElement)
|
|
||||||
const originalElement = this.draggedElement as BaseSwpEventElement;
|
|
||||||
this.draggedClone = originalElement.createClone();
|
|
||||||
|
|
||||||
const dragStartPayload: DragStartEventPayload = {
|
|
||||||
draggedElement: this.draggedElement,
|
|
||||||
draggedClone: this.draggedClone,
|
|
||||||
mousePosition: this.initialMousePosition,
|
|
||||||
mouseOffset: this.mouseOffset,
|
|
||||||
columnBounds: this.currentColumnBounds
|
|
||||||
};
|
|
||||||
this.eventBus.emit('drag:start', dragStartPayload);
|
|
||||||
} else {
|
|
||||||
// Not enough movement yet - don't start drag
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue with normal drag behavior only if drag has started
|
// Continue drag if started
|
||||||
if (this.isDragStarted && this.draggedElement && this.draggedClone) {
|
if (this.isDragStarted && this.draggedElement && this.draggedClone) {
|
||||||
if (!this.draggedElement.hasAttribute("data-allday")) {
|
this.continueDrag(currentPosition);
|
||||||
// Calculate raw position from mouse (no snapping)
|
this.detectAndEmitColumnChange(currentPosition);
|
||||||
const column = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (column) {
|
/**
|
||||||
// Calculate raw Y position relative to column (accounting for mouse offset)
|
* Try to initialize drag based on movement threshold
|
||||||
const columnRect = column.boundingClientRect;
|
* Returns true if drag was initialized, false if not enough movement
|
||||||
const eventTopY = currentPosition.y - columnRect.top - this.mouseOffset.y;
|
*/
|
||||||
this.targetY = Math.max(0, eventTopY); // Store raw Y as target (no snapping)
|
private tryInitializeDrag(currentPosition: MousePosition): boolean {
|
||||||
this.targetColumn = column;
|
const deltaX = Math.abs(currentPosition.x - this.initialMousePosition.x);
|
||||||
|
const deltaY = Math.abs(currentPosition.y - this.initialMousePosition.y);
|
||||||
|
const totalMovement = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||||
|
|
||||||
// Start animation loop if not already running
|
if (totalMovement < this.dragThreshold) {
|
||||||
if (this.dragAnimationId === null) {
|
return false; // Not enough movement
|
||||||
this.currentY = parseFloat(this.draggedClone.style.top) || 0;
|
}
|
||||||
this.animateDrag();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for auto-scroll
|
// Start drag
|
||||||
this.checkAutoScroll(currentPosition);
|
this.isDragStarted = true;
|
||||||
}
|
|
||||||
|
|
||||||
const newColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
// Set high z-index on event-group if exists, otherwise on event itself
|
||||||
if (newColumn == null)
|
const eventGroup = this.draggedElement!.closest<HTMLElement>('swp-event-group');
|
||||||
return;
|
if (eventGroup) {
|
||||||
|
eventGroup.style.zIndex = '9999';
|
||||||
|
} else {
|
||||||
|
this.draggedElement!.style.zIndex = '9999';
|
||||||
|
}
|
||||||
|
|
||||||
if (newColumn?.index !== this.currentColumnBounds?.index) {
|
// Detect current column and save as initial source column
|
||||||
const previousColumn = this.currentColumnBounds;
|
this.currentColumnBounds = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
||||||
this.currentColumnBounds = newColumn;
|
this.initialColumnBounds = this.currentColumnBounds;
|
||||||
|
|
||||||
const dragColumnChangePayload: DragColumnChangeEventPayload = {
|
// Cast to BaseSwpEventElement and create clone
|
||||||
originalElement: this.draggedElement,
|
const originalElement = this.draggedElement as BaseSwpEventElement;
|
||||||
draggedClone: this.draggedClone,
|
this.draggedClone = originalElement.createClone();
|
||||||
previousColumn,
|
|
||||||
newColumn,
|
const dragStartPayload: DragStartEventPayload = {
|
||||||
mousePosition: currentPosition
|
draggedElement: this.draggedElement!,
|
||||||
};
|
draggedClone: this.draggedClone,
|
||||||
this.eventBus.emit('drag:column-change', dragColumnChangePayload);
|
mousePosition: this.initialMousePosition,
|
||||||
|
mouseOffset: this.mouseOffset,
|
||||||
|
columnBounds: this.currentColumnBounds
|
||||||
|
};
|
||||||
|
this.eventBus.emit('drag:start', dragStartPayload);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Continue drag movement - update position and auto-scroll
|
||||||
|
*/
|
||||||
|
private continueDrag(currentPosition: MousePosition): void {
|
||||||
|
if (!this.draggedElement!.hasAttribute("data-allday")) {
|
||||||
|
// Calculate raw position from mouse (no snapping)
|
||||||
|
const column = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
||||||
|
|
||||||
|
if (column) {
|
||||||
|
// Calculate raw Y position relative to column (accounting for mouse offset)
|
||||||
|
const columnRect = column.boundingClientRect;
|
||||||
|
const eventTopY = currentPosition.y - columnRect.top - this.mouseOffset.y;
|
||||||
|
this.targetY = Math.max(0, eventTopY);
|
||||||
|
this.targetColumn = column;
|
||||||
|
|
||||||
|
// Start animation loop if not already running
|
||||||
|
if (this.dragAnimationId === null) {
|
||||||
|
this.currentY = parseFloat(this.draggedClone!.style.top) || 0;
|
||||||
|
this.animateDrag();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for auto-scroll
|
||||||
|
this.checkAutoScroll(currentPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect column change and emit event
|
||||||
|
*/
|
||||||
|
private detectAndEmitColumnChange(currentPosition: MousePosition): void {
|
||||||
|
const newColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
||||||
|
if (newColumn == null) return;
|
||||||
|
|
||||||
|
if (newColumn.index !== this.currentColumnBounds?.index) {
|
||||||
|
const previousColumn = this.currentColumnBounds;
|
||||||
|
this.currentColumnBounds = newColumn;
|
||||||
|
|
||||||
|
const dragColumnChangePayload: DragColumnChangeEventPayload = {
|
||||||
|
originalElement: this.draggedElement!,
|
||||||
|
draggedClone: this.draggedClone!,
|
||||||
|
previousColumn,
|
||||||
|
newColumn,
|
||||||
|
mousePosition: currentPosition
|
||||||
|
};
|
||||||
|
this.eventBus.emit('drag:column-change', dragColumnChangePayload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,30 +106,16 @@ export class EventRenderingService {
|
||||||
* Handle GRID_RENDERED event - render events in the current grid
|
* Handle GRID_RENDERED event - render events in the current grid
|
||||||
*/
|
*/
|
||||||
private handleGridRendered(event: CustomEvent): void {
|
private handleGridRendered(event: CustomEvent): void {
|
||||||
const { container, startDate, endDate, currentDate, isNavigation } = event.detail;
|
const { container, startDate, endDate } = event.detail;
|
||||||
|
|
||||||
if (!container) {
|
if (!container || !startDate || !endDate) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let periodStart: Date;
|
|
||||||
let periodEnd: Date;
|
|
||||||
|
|
||||||
if (startDate && endDate) {
|
|
||||||
// Direct date format - use as provided
|
|
||||||
periodStart = startDate;
|
|
||||||
periodEnd = endDate;
|
|
||||||
} else if (currentDate) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.renderEvents({
|
this.renderEvents({
|
||||||
container: container,
|
container,
|
||||||
startDate: periodStart,
|
startDate,
|
||||||
endDate: periodEnd
|
endDate
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,29 +135,44 @@ export class EventRenderingService {
|
||||||
* Setup all drag event listeners - moved from EventRenderer for better separation of concerns
|
* Setup all drag event listeners - moved from EventRenderer for better separation of concerns
|
||||||
*/
|
*/
|
||||||
private setupDragEventListeners(): void {
|
private setupDragEventListeners(): void {
|
||||||
|
this.setupDragStartListener();
|
||||||
|
this.setupDragMoveListener();
|
||||||
|
this.setupDragAutoScrollListener();
|
||||||
|
this.setupDragEndListener();
|
||||||
|
this.setupDragColumnChangeListener();
|
||||||
|
this.setupDragMouseLeaveHeaderListener();
|
||||||
|
this.setupResizeEndListener();
|
||||||
|
this.setupNavigationCompletedListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupDragStartListener(): void {
|
||||||
this.eventBus.on('drag:start', (event: Event) => {
|
this.eventBus.on('drag:start', (event: Event) => {
|
||||||
const dragStartPayload = (event as CustomEvent<DragStartEventPayload>).detail;
|
const dragStartPayload = (event as CustomEvent<DragStartEventPayload>).detail;
|
||||||
|
|
||||||
if (dragStartPayload.draggedElement.hasAttribute('data-allday')) {
|
if (dragStartPayload.draggedElement.hasAttribute('data-allday')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dragStartPayload.draggedElement && this.strategy.handleDragStart && dragStartPayload.columnBounds) {
|
if (dragStartPayload.draggedElement && this.strategy.handleDragStart && dragStartPayload.columnBounds) {
|
||||||
this.strategy.handleDragStart(dragStartPayload);
|
this.strategy.handleDragStart(dragStartPayload);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupDragMoveListener(): void {
|
||||||
this.eventBus.on('drag:move', (event: Event) => {
|
this.eventBus.on('drag:move', (event: Event) => {
|
||||||
let dragEvent = (event as CustomEvent<DragMoveEventPayload>).detail;
|
let dragEvent = (event as CustomEvent<DragMoveEventPayload>).detail;
|
||||||
|
|
||||||
if (dragEvent.draggedElement.hasAttribute('data-allday')) {
|
if (dragEvent.draggedElement.hasAttribute('data-allday')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.strategy.handleDragMove) {
|
if (this.strategy.handleDragMove) {
|
||||||
this.strategy.handleDragMove(dragEvent);
|
this.strategy.handleDragMove(dragEvent);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupDragAutoScrollListener(): void {
|
||||||
this.eventBus.on('drag:auto-scroll', (event: Event) => {
|
this.eventBus.on('drag:auto-scroll', (event: Event) => {
|
||||||
const { draggedElement, snappedY } = (event as CustomEvent).detail;
|
const { draggedElement, snappedY } = (event as CustomEvent).detail;
|
||||||
if (this.strategy.handleDragAutoScroll) {
|
if (this.strategy.handleDragAutoScroll) {
|
||||||
|
|
@ -179,8 +180,9 @@ export class EventRenderingService {
|
||||||
this.strategy.handleDragAutoScroll(eventId, snappedY);
|
this.strategy.handleDragAutoScroll(eventId, snappedY);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Handle drag end events and delegate to appropriate renderer
|
private setupDragEndListener(): void {
|
||||||
this.eventBus.on('drag:end', (event: Event) => {
|
this.eventBus.on('drag:end', (event: Event) => {
|
||||||
const { originalElement: draggedElement, sourceColumn, 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;
|
||||||
|
|
@ -224,8 +226,9 @@ export class EventRenderingService {
|
||||||
dayEventClone.remove();
|
dayEventClone.remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Handle column change
|
private setupDragColumnChangeListener(): void {
|
||||||
this.eventBus.on('drag:column-change', (event: Event) => {
|
this.eventBus.on('drag:column-change', (event: Event) => {
|
||||||
let columnChangeEvent = (event as CustomEvent<DragColumnChangeEventPayload>).detail;
|
let columnChangeEvent = (event as CustomEvent<DragColumnChangeEventPayload>).detail;
|
||||||
|
|
||||||
|
|
@ -238,8 +241,9 @@ export class EventRenderingService {
|
||||||
this.strategy.handleColumnChange(columnChangeEvent);
|
this.strategy.handleColumnChange(columnChangeEvent);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupDragMouseLeaveHeaderListener(): void {
|
||||||
this.dragMouseLeaveHeaderListener = (event: Event) => {
|
this.dragMouseLeaveHeaderListener = (event: Event) => {
|
||||||
const { targetDate, mousePosition, originalElement, draggedClone: cloneElement } = (event as CustomEvent<DragMouseLeaveHeaderEventPayload>).detail;
|
const { targetDate, mousePosition, originalElement, draggedClone: cloneElement } = (event as CustomEvent<DragMouseLeaveHeaderEventPayload>).detail;
|
||||||
|
|
||||||
|
|
@ -255,8 +259,9 @@ export class EventRenderingService {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.eventBus.on('drag:mouseleave-header', this.dragMouseLeaveHeaderListener);
|
this.eventBus.on('drag:mouseleave-header', this.dragMouseLeaveHeaderListener);
|
||||||
|
}
|
||||||
|
|
||||||
// Handle resize end events
|
private setupResizeEndListener(): void {
|
||||||
this.eventBus.on('resize:end', (event: Event) => {
|
this.eventBus.on('resize:end', (event: Event) => {
|
||||||
const { eventId, element } = (event as CustomEvent<ResizeEndEventPayload>).detail;
|
const { eventId, element } = (event as CustomEvent<ResizeEndEventPayload>).detail;
|
||||||
|
|
||||||
|
|
@ -286,8 +291,9 @@ export class EventRenderingService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Handle navigation period change
|
private setupNavigationCompletedListener(): void {
|
||||||
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
|
||||||
if (this.strategy.handleNavigationCompleted) {
|
if (this.strategy.handleNavigationCompleted) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue