bump
This commit is contained in:
255
daemon/drag_drop_helpers.js
Normal file
255
daemon/drag_drop_helpers.js
Normal file
@@ -0,0 +1,255 @@
|
||||
// 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<boolean>} - 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<object>} - 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<object>} - 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');
|
||||
})();
|
||||
Reference in New Issue
Block a user