194 lines
No EOL
7.3 KiB
JavaScript
194 lines
No EOL
7.3 KiB
JavaScript
import { eventBus } from '../core/EventBus';
|
|
export class ResizeHandleManager {
|
|
constructor(config, positionUtils) {
|
|
this.config = config;
|
|
this.positionUtils = positionUtils;
|
|
this.isResizing = false;
|
|
this.targetEl = null;
|
|
this.startY = 0;
|
|
this.startDurationMin = 0;
|
|
this.animationId = null;
|
|
this.currentHeight = 0;
|
|
this.targetHeight = 0;
|
|
this.pointerCaptured = false;
|
|
// Constants for better maintainability
|
|
this.ANIMATION_SPEED = 0.35;
|
|
this.Z_INDEX_RESIZING = '1000';
|
|
this.EVENT_REFRESH_THRESHOLD = 0.5;
|
|
this.onMouseOver = (e) => {
|
|
const target = e.target;
|
|
const eventElement = target.closest('swp-event');
|
|
if (eventElement && !this.isResizing) {
|
|
// Check if handle already exists
|
|
if (!eventElement.querySelector(':scope > swp-resize-handle')) {
|
|
const handle = this.createResizeHandle();
|
|
eventElement.appendChild(handle);
|
|
}
|
|
}
|
|
};
|
|
this.onPointerDown = (e) => {
|
|
const handle = e.target.closest('swp-resize-handle');
|
|
if (!handle)
|
|
return;
|
|
const element = handle.parentElement;
|
|
this.startResizing(element, e);
|
|
};
|
|
this.onPointerMove = (e) => {
|
|
if (!this.isResizing || !this.targetEl)
|
|
return;
|
|
this.updateResizeHeight(e.clientY);
|
|
};
|
|
this.animate = () => {
|
|
if (!this.isResizing || !this.targetEl) {
|
|
this.animationId = null;
|
|
return;
|
|
}
|
|
const diff = this.targetHeight - this.currentHeight;
|
|
if (Math.abs(diff) > this.EVENT_REFRESH_THRESHOLD) {
|
|
this.currentHeight += diff * this.ANIMATION_SPEED;
|
|
this.targetEl.updateHeight?.(this.currentHeight);
|
|
this.animationId = requestAnimationFrame(this.animate);
|
|
}
|
|
else {
|
|
this.finalizeAnimation();
|
|
}
|
|
};
|
|
this.onPointerUp = (e) => {
|
|
if (!this.isResizing || !this.targetEl)
|
|
return;
|
|
this.cleanupAnimation();
|
|
this.snapToGrid();
|
|
this.emitResizeEndEvent();
|
|
this.cleanupResizing(e);
|
|
};
|
|
const grid = this.config.gridSettings;
|
|
this.snapMin = grid.snapInterval;
|
|
this.minDurationMin = this.snapMin;
|
|
}
|
|
initialize() {
|
|
this.attachGlobalListeners();
|
|
}
|
|
destroy() {
|
|
this.removeEventListeners();
|
|
}
|
|
removeEventListeners() {
|
|
const calendarContainer = document.querySelector('swp-calendar-container');
|
|
if (calendarContainer) {
|
|
calendarContainer.removeEventListener('mouseover', this.onMouseOver, true);
|
|
}
|
|
document.removeEventListener('pointerdown', this.onPointerDown, true);
|
|
document.removeEventListener('pointermove', this.onPointerMove, true);
|
|
document.removeEventListener('pointerup', this.onPointerUp, true);
|
|
}
|
|
createResizeHandle() {
|
|
const handle = document.createElement('swp-resize-handle');
|
|
handle.setAttribute('aria-label', 'Resize event');
|
|
handle.setAttribute('role', 'separator');
|
|
return handle;
|
|
}
|
|
attachGlobalListeners() {
|
|
const calendarContainer = document.querySelector('swp-calendar-container');
|
|
if (calendarContainer) {
|
|
calendarContainer.addEventListener('mouseover', this.onMouseOver, true);
|
|
}
|
|
document.addEventListener('pointerdown', this.onPointerDown, true);
|
|
document.addEventListener('pointermove', this.onPointerMove, true);
|
|
document.addEventListener('pointerup', this.onPointerUp, true);
|
|
}
|
|
startResizing(element, event) {
|
|
this.targetEl = element;
|
|
this.isResizing = true;
|
|
this.startY = event.clientY;
|
|
const startHeight = element.offsetHeight;
|
|
this.startDurationMin = Math.max(this.minDurationMin, Math.round(this.positionUtils.pixelsToMinutes(startHeight)));
|
|
this.setZIndexForResizing(element);
|
|
this.capturePointer(event);
|
|
document.documentElement.classList.add('swp--resizing');
|
|
event.preventDefault();
|
|
}
|
|
setZIndexForResizing(element) {
|
|
const container = element.closest('swp-event-group') ?? element;
|
|
this.prevZ = container.style.zIndex;
|
|
container.style.zIndex = this.Z_INDEX_RESIZING;
|
|
}
|
|
capturePointer(event) {
|
|
try {
|
|
event.target.setPointerCapture?.(event.pointerId);
|
|
this.pointerCaptured = true;
|
|
}
|
|
catch (error) {
|
|
console.warn('Pointer capture failed:', error);
|
|
}
|
|
}
|
|
updateResizeHeight(currentY) {
|
|
const deltaY = currentY - this.startY;
|
|
const startHeight = this.positionUtils.minutesToPixels(this.startDurationMin);
|
|
const rawHeight = startHeight + deltaY;
|
|
const minHeight = this.positionUtils.minutesToPixels(this.minDurationMin);
|
|
this.targetHeight = Math.max(minHeight, rawHeight);
|
|
if (this.animationId == null) {
|
|
this.currentHeight = this.targetEl?.offsetHeight;
|
|
this.animate();
|
|
}
|
|
}
|
|
finalizeAnimation() {
|
|
if (!this.targetEl)
|
|
return;
|
|
this.currentHeight = this.targetHeight;
|
|
this.targetEl.updateHeight?.(this.currentHeight);
|
|
this.animationId = null;
|
|
}
|
|
cleanupAnimation() {
|
|
if (this.animationId != null) {
|
|
cancelAnimationFrame(this.animationId);
|
|
this.animationId = null;
|
|
}
|
|
}
|
|
snapToGrid() {
|
|
if (!this.targetEl)
|
|
return;
|
|
const currentHeight = this.targetEl.offsetHeight;
|
|
const snapDistancePx = this.positionUtils.minutesToPixels(this.snapMin);
|
|
const snappedHeight = Math.round(currentHeight / snapDistancePx) * snapDistancePx;
|
|
const minHeight = this.positionUtils.minutesToPixels(this.minDurationMin);
|
|
const finalHeight = Math.max(minHeight, snappedHeight) - 3; // Small gap to grid lines
|
|
this.targetEl.updateHeight?.(finalHeight);
|
|
}
|
|
emitResizeEndEvent() {
|
|
if (!this.targetEl)
|
|
return;
|
|
const eventId = this.targetEl.dataset.eventId || '';
|
|
const resizeEndPayload = {
|
|
eventId,
|
|
element: this.targetEl,
|
|
finalHeight: this.targetEl.offsetHeight
|
|
};
|
|
eventBus.emit('resize:end', resizeEndPayload);
|
|
}
|
|
cleanupResizing(event) {
|
|
this.restoreZIndex();
|
|
this.releasePointer(event);
|
|
this.isResizing = false;
|
|
this.targetEl = null;
|
|
document.documentElement.classList.remove('swp--resizing');
|
|
}
|
|
restoreZIndex() {
|
|
if (!this.targetEl || this.prevZ === undefined)
|
|
return;
|
|
const container = this.targetEl.closest('swp-event-group') ?? this.targetEl;
|
|
container.style.zIndex = this.prevZ;
|
|
this.prevZ = undefined;
|
|
}
|
|
releasePointer(event) {
|
|
if (!this.pointerCaptured)
|
|
return;
|
|
try {
|
|
event.target.releasePointerCapture?.(event.pointerId);
|
|
this.pointerCaptured = false;
|
|
}
|
|
catch (error) {
|
|
console.warn('Pointer release failed:', error);
|
|
}
|
|
}
|
|
}
|
|
//# sourceMappingURL=ResizeHandleManager.js.map
|