// HTML5 Drag and Drop Helper Functions for Cremote // These functions are injected into web pages to provide reliable drag and drop functionality (function() { 'use strict'; // Create a namespace to avoid conflicts window.cremoteDragDrop = window.cremoteDragDrop || {}; /** * Simulates HTML5 drag and drop between two elements * @param {string} sourceSelector - CSS selector for source element * @param {string} targetSelector - CSS selector for target element * @returns {Promise} - Success status */ window.cremoteDragDrop.dragElementToElement = async function(sourceSelector, targetSelector) { const sourceElement = document.querySelector(sourceSelector); const targetElement = document.querySelector(targetSelector); if (!sourceElement) { throw new Error(`Source element not found: ${sourceSelector}`); } if (!targetElement) { throw new Error(`Target element not found: ${targetSelector}`); } // Make source draggable if not already if (!sourceElement.draggable) { sourceElement.draggable = true; } // Create and dispatch dragstart event const dragStartEvent = new DragEvent('dragstart', { bubbles: true, cancelable: true, dataTransfer: new DataTransfer() }); // Set drag data dragStartEvent.dataTransfer.setData('text/plain', sourceElement.id || sourceSelector); dragStartEvent.dataTransfer.effectAllowed = 'all'; const dragStartResult = sourceElement.dispatchEvent(dragStartEvent); if (!dragStartResult) { console.log('Dragstart was cancelled'); return false; } // Small delay to simulate realistic drag timing await new Promise(resolve => setTimeout(resolve, 50)); // Create and dispatch dragover event on target const dragOverEvent = new DragEvent('dragover', { bubbles: true, cancelable: true, dataTransfer: dragStartEvent.dataTransfer }); const dragOverResult = targetElement.dispatchEvent(dragOverEvent); // Create and dispatch drop event on target const dropEvent = new DragEvent('drop', { bubbles: true, cancelable: true, dataTransfer: dragStartEvent.dataTransfer }); const dropResult = targetElement.dispatchEvent(dropEvent); // Create and dispatch dragend event on source const dragEndEvent = new DragEvent('dragend', { bubbles: true, cancelable: true, dataTransfer: dragStartEvent.dataTransfer }); sourceElement.dispatchEvent(dragEndEvent); return dropResult; }; /** * Simulates HTML5 drag and drop from element to coordinates * @param {string} sourceSelector - CSS selector for source element * @param {number} x - Target X coordinate * @param {number} y - Target Y coordinate * @returns {Promise} - Result with success status and target element info */ window.cremoteDragDrop.dragElementToCoordinates = async function(sourceSelector, x, y) { const sourceElement = document.querySelector(sourceSelector); if (!sourceElement) { throw new Error(`Source element not found: ${sourceSelector}`); } // Find element at target coordinates const targetElement = document.elementFromPoint(x, y); if (!targetElement) { throw new Error(`No element found at coordinates (${x}, ${y})`); } // Make source draggable if not already if (!sourceElement.draggable) { sourceElement.draggable = true; } // Create and dispatch dragstart event const dragStartEvent = new DragEvent('dragstart', { bubbles: true, cancelable: true, dataTransfer: new DataTransfer() }); dragStartEvent.dataTransfer.setData('text/plain', sourceElement.id || sourceSelector); dragStartEvent.dataTransfer.effectAllowed = 'all'; const dragStartResult = sourceElement.dispatchEvent(dragStartEvent); if (!dragStartResult) { return { success: false, reason: 'Dragstart was cancelled', targetElement: null }; } await new Promise(resolve => setTimeout(resolve, 50)); // Create dragover event with coordinates const dragOverEvent = new DragEvent('dragover', { bubbles: true, cancelable: true, clientX: x, clientY: y, dataTransfer: dragStartEvent.dataTransfer }); targetElement.dispatchEvent(dragOverEvent); // Create drop event with coordinates const dropEvent = new DragEvent('drop', { bubbles: true, cancelable: true, clientX: x, clientY: y, dataTransfer: dragStartEvent.dataTransfer }); const dropResult = targetElement.dispatchEvent(dropEvent); // Dispatch dragend const dragEndEvent = new DragEvent('dragend', { bubbles: true, cancelable: true, dataTransfer: dragStartEvent.dataTransfer }); sourceElement.dispatchEvent(dragEndEvent); return { success: dropResult, targetElement: { tagName: targetElement.tagName, id: targetElement.id, className: targetElement.className, hasDropListener: this.hasDropEventListener(targetElement) } }; }; /** * Checks if an element can receive drop events * @param {Element} element - Element to check * @returns {boolean} - Whether element can receive drops */ window.cremoteDragDrop.hasDropEventListener = function(element) { // Check for ondrop attribute if (element.ondrop) return true; // Check for drop event listeners (this is limited but covers common cases) if (element.getAttribute('ondrop')) return true; // Check if element has dragover listeners (usually indicates drop capability) if (element.ondragover || element.getAttribute('ondragover')) return true; // Check for common drop zone classes/attributes const dropIndicators = ['drop-zone', 'dropzone', 'droppable', 'drop-target']; const className = element.className.toLowerCase(); const hasDropClass = dropIndicators.some(indicator => className.includes(indicator)); return hasDropClass; }; /** * Finds the best drop target at given coordinates * @param {number} x - X coordinate * @param {number} y - Y coordinate * @returns {Element|null} - Best drop target element or null */ window.cremoteDragDrop.findDropTargetAtCoordinates = function(x, y) { const elements = document.elementsFromPoint(x, y); // Look for elements that can receive drops, starting from topmost for (const element of elements) { if (this.hasDropEventListener(element)) { return element; } } // If no explicit drop target found, return the topmost element return elements[0] || null; }; /** * Enhanced drag to coordinates that finds the best drop target * @param {string} sourceSelector - CSS selector for source element * @param {number} x - Target X coordinate * @param {number} y - Target Y coordinate * @returns {Promise} - Enhanced result with target analysis */ window.cremoteDragDrop.smartDragToCoordinates = async function(sourceSelector, x, y) { const sourceElement = document.querySelector(sourceSelector); if (!sourceElement) { throw new Error(`Source element not found: ${sourceSelector}`); } // Find the best drop target at coordinates const targetElement = this.findDropTargetAtCoordinates(x, y); if (!targetElement) { throw new Error(`No suitable drop target found at coordinates (${x}, ${y})`); } const canReceiveDrops = this.hasDropEventListener(targetElement); // If we found a proper drop target, use element-to-element drag if (canReceiveDrops) { const success = await this.dragElementToElement(sourceSelector, targetElement.id ? `#${targetElement.id}` : targetElement.tagName.toLowerCase()); return { success: success, method: 'element-to-element', targetElement: { tagName: targetElement.tagName, id: targetElement.id, className: targetElement.className, hasDropListener: true } }; } else { // Fall back to coordinate-based drag const result = await this.dragElementToCoordinates(sourceSelector, x, y); result.method = 'coordinate-based'; return result; } }; console.log('Cremote drag and drop helpers loaded successfully'); })();