import { TpFlowNode } from './tp-flow-node.js'; export const panning = function(superClass) { return class extends superClass { static get properties() { return { isDragging: { type: Boolean }, currentX: { type: Number }, currentY: { type: Number }, currentNodeX: { type: Number }, currentNodeY: { type: Number }, initialX: { type: Number }, initialY: { type: Number }, xOffset: { type: Number }, yOffset: { type: Number }, highestZIndex: { type: Number } }; } constructor() { super(); this.isDragging = false; this.currentX = 0; this.currentY = 0; this.currentNodeX = 0; this.currentNodeY = 0; this.initialX = 0; this.initialY = 0; this.xOffset = 0; this.yOffset = 0; this.targetElement = null; this.highestZIndex = 1; } firstUpdated() { this.canvas = this.shadowRoot.querySelector('.canvas'); this.addEventListener('mousedown', this._startDrag.bind(this)); document.addEventListener('mousemove', this._drag.bind(this)); document.addEventListener('mouseup', this._endDrag.bind(this)); super.firstUpdated(); } _getTopNodeAtPoint(x, y) { const nodes = this.shadowRoot.elementsFromPoint(x, y) .filter(el => el instanceof TpFlowNode) .sort((a, b) => { const aZ = parseInt(getComputedStyle(a).zIndex) || 0; const bZ = parseInt(getComputedStyle(b).zIndex) || 0; return bZ - aZ; }); return nodes[0] || null; } _bringToFront(element) { if (!(element instanceof TpFlowNode)) return; // Get all elements and filter for TpFlowNode instances const nodes = Array.from(this.shadowRoot.querySelector('.canvas').children) .filter(node => node instanceof TpFlowNode); for (const node of nodes) { node.style.zIndex = 1; node.classList.remove('focused'); } element.style.zIndex = 2; element.classList.add('focused'); } _startDrag(e) { const topNode = this._getTopNodeAtPoint(e.clientX, e.clientY); if (topNode) { this.targetElement = topNode; this._bringToFront(topNode); // Check if a element with the "drag-node" attribute is part of the event path. Only then we can start dragging. if (!e.composedPath().some(el => typeof el.hasAttribute === 'function' && el.hasAttribute('drag-node'))) { return; } } else { this.targetElement = this.canvas; } if (this.targetElement) { this.isDragging = true; const transform = window.getComputedStyle(this.targetElement).transform; const matrix = new DOMMatrix(transform); this.xOffset = matrix.m41; this.yOffset = matrix.m42; if (this.targetElement === this.canvas) { this.initialX = e.clientX - this.xOffset; this.initialY = e.clientY - this.yOffset; } else { // For nodes, compensate for scale in initial position too this.initialX = e.clientX - (this.xOffset * this.scale); this.initialY = e.clientY - (this.yOffset * this.scale); } } } _drag(e) { if (this.isDragging && this.targetElement) { e.preventDefault(); if (this.targetElement === this.canvas) { // For canvas panning, no scale compensation needed this.currentX = e.clientX - this.initialX; this.currentY = e.clientY - this.initialY; this.targetElement.style.transform = `translate(${this.currentX}px, ${this.currentY}px) scale(${this.scale})`; } else { // For nodes, compensate for canvas scale this.currentNodeX = (e.clientX - this.initialX) / this.scale; this.currentNodeY = (e.clientY - this.initialY) / this.scale; this.targetElement.style.transform = `translate(${this.currentNodeX}px, ${this.currentNodeY}px)`; } this.requestUpdate(); } } _endDrag() { if (this.isDragging && this.targetElement instanceof TpFlowNode) { this._dispatchChangeEvent({ type: 'node-moved', data: { nodeId: this.targetElement.id, position: { x: this.currentNodeX, y: this.currentNodeY } } }); } this.isDragging = false; this.targetElement = null; } }; };