feat(dees-dashboardgrid): enhance widget placement validation and logging for drag-and-drop interactions
This commit is contained in:
		| @@ -159,21 +159,169 @@ export const demoFunc = () => { | ||||
|         } | ||||
|       }); | ||||
|  | ||||
|       // Enhanced logging for reflow events | ||||
|       let lastPlaceholderPosition = null; | ||||
|       let moveEventCounter = 0; | ||||
|  | ||||
|       // Helper function to log grid state | ||||
|       const logGridState = (eventName: string, details?: any) => { | ||||
|         const layout = grid.getLayout(); | ||||
|         console.group(`🔄 ${eventName} [Event #${++moveEventCounter}]`); | ||||
|         console.log('Timestamp:', new Date().toISOString()); | ||||
|         console.log('Grid Configuration:', { | ||||
|           columns: grid.columns, | ||||
|           cellHeight: grid.cellHeight, | ||||
|           margin: grid.margin, | ||||
|           editable: grid.editable, | ||||
|           activeBreakpoint: grid.activeBreakpoint | ||||
|         }); | ||||
|         console.log('Current Layout:', layout); | ||||
|         console.log('Widget Count:', layout.length); | ||||
|         console.log('Grid Bounds:', { | ||||
|           totalWidgets: grid.widgets.length, | ||||
|           maxY: Math.max(...layout.map(w => w.y + w.h)), | ||||
|           occupied: layout.map(w => `${w.id}: (${w.x},${w.y}) ${w.w}x${w.h}`).join(', ') | ||||
|         }); | ||||
|         if (details) { | ||||
|           console.log('Event Details:', details); | ||||
|         } | ||||
|         console.groupEnd(); | ||||
|       }; | ||||
|  | ||||
|       // Monitor placeholder position changes using MutationObserver | ||||
|       const placeholderObserver = new MutationObserver(() => { | ||||
|         const placeholder = grid.shadowRoot?.querySelector('.placeholder') as HTMLElement; | ||||
|         if (placeholder) { | ||||
|           const currentPosition = { | ||||
|             left: placeholder.style.left, | ||||
|             top: placeholder.style.top, | ||||
|             width: placeholder.style.width, | ||||
|             height: placeholder.style.height | ||||
|           }; | ||||
|  | ||||
|           if (JSON.stringify(currentPosition) !== JSON.stringify(lastPlaceholderPosition)) { | ||||
|             console.group('📍 Placeholder Position Changed'); | ||||
|             console.log('Previous:', lastPlaceholderPosition); | ||||
|             console.log('Current:', currentPosition); | ||||
|  | ||||
|             // Extract grid coordinates from style | ||||
|             const gridInfo = grid.shadowRoot?.querySelector('.grid-container'); | ||||
|             if (gridInfo) { | ||||
|               console.log('Grid Container Dimensions:', { | ||||
|                 width: gridInfo.clientWidth, | ||||
|                 height: gridInfo.clientHeight | ||||
|               }); | ||||
|             } | ||||
|             console.groupEnd(); | ||||
|             lastPlaceholderPosition = currentPosition; | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
|  | ||||
|       // Start observing the shadow DOM for placeholder changes | ||||
|       if (grid.shadowRoot) { | ||||
|         placeholderObserver.observe(grid.shadowRoot, { | ||||
|           childList: true, | ||||
|           subtree: true, | ||||
|           attributes: true, | ||||
|           attributeFilter: ['style'] | ||||
|         }); | ||||
|       } | ||||
|  | ||||
|       // Log initial state | ||||
|       logGridState('Initial Grid State'); | ||||
|  | ||||
|       grid.addEventListener('widget-move', (e: CustomEvent) => { | ||||
|         console.log('Widget moved:', e.detail.widget, 'Displaced:', e.detail.displaced); | ||||
|         logGridState('Widget Move', { | ||||
|           widget: e.detail.widget, | ||||
|           displaced: e.detail.displaced, | ||||
|           swappedWith: e.detail.swappedWith | ||||
|         }); | ||||
|       }); | ||||
|  | ||||
|       grid.addEventListener('widget-resize', (e: CustomEvent) => { | ||||
|         console.log('Widget resized:', e.detail.widget, 'Displaced:', e.detail.displaced); | ||||
|         logGridState('Widget Resize', { | ||||
|           widget: e.detail.widget, | ||||
|           displaced: e.detail.displaced, | ||||
|           swappedWith: e.detail.swappedWith | ||||
|         }); | ||||
|       }); | ||||
|  | ||||
|       grid.addEventListener('widget-remove', (e: CustomEvent) => { | ||||
|         console.log('Widget removed:', e.detail.widget); | ||||
|         logGridState('Widget Remove', { | ||||
|           removedWidget: e.detail.widget | ||||
|         }); | ||||
|         updateStatus(); | ||||
|       }); | ||||
|  | ||||
|       grid.addEventListener('layout-change', () => { | ||||
|         console.log('Layout changed:', grid.getLayout()); | ||||
|         logGridState('Layout Change'); | ||||
|         updateStatus(); | ||||
|       }); | ||||
|  | ||||
|       // Monitor during drag/resize operations using pointer events | ||||
|       grid.addEventListener('pointerdown', (e: PointerEvent) => { | ||||
|         const isHeader = (e.target as HTMLElement).closest('.widget-header'); | ||||
|         const isResizeHandle = (e.target as HTMLElement).closest('.resize-handle'); | ||||
|  | ||||
|         if (isHeader || isResizeHandle) { | ||||
|           console.group(`🎯 Interaction Started: ${isHeader ? 'Drag' : 'Resize'}`); | ||||
|           console.log('Target Widget:', (e.target as HTMLElement).closest('.widget')?.getAttribute('data-widget-id')); | ||||
|           console.log('Pointer Position:', { x: e.clientX, y: e.clientY }); | ||||
|           console.groupEnd(); | ||||
|  | ||||
|           // Track pointer move during interaction | ||||
|           const handlePointerMove = (moveEvent: PointerEvent) => { | ||||
|             const widget = (e.target as HTMLElement).closest('.widget'); | ||||
|             if (widget) { | ||||
|               console.log(`↔️ Pointer Move:`, { | ||||
|                 widgetId: widget.getAttribute('data-widget-id'), | ||||
|                 position: { x: moveEvent.clientX, y: moveEvent.clientY }, | ||||
|                 delta: { | ||||
|                   x: moveEvent.clientX - e.clientX, | ||||
|                   y: moveEvent.clientY - e.clientY | ||||
|                 } | ||||
|               }); | ||||
|             } | ||||
|           }; | ||||
|  | ||||
|           const handlePointerUp = () => { | ||||
|             console.group('🏁 Interaction Ended'); | ||||
|             logGridState('Final State After Interaction'); | ||||
|             console.groupEnd(); | ||||
|             document.removeEventListener('pointermove', handlePointerMove); | ||||
|             document.removeEventListener('pointerup', handlePointerUp); | ||||
|           }; | ||||
|  | ||||
|           document.addEventListener('pointermove', handlePointerMove); | ||||
|           document.addEventListener('pointerup', handlePointerUp); | ||||
|         } | ||||
|       }); | ||||
|  | ||||
|       // Log when widgets are added | ||||
|       const originalAddWidget = grid.addWidget.bind(grid); | ||||
|       grid.addWidget = (widget: any, autoPosition?: boolean) => { | ||||
|         console.group('➕ Adding Widget'); | ||||
|         console.log('New Widget:', widget); | ||||
|         console.log('Auto Position:', autoPosition); | ||||
|         const result = originalAddWidget(widget, autoPosition); | ||||
|         logGridState('After Widget Added'); | ||||
|         console.groupEnd(); | ||||
|         return result; | ||||
|       }; | ||||
|  | ||||
|       // Log compact operations | ||||
|       const originalCompact = grid.compact.bind(grid); | ||||
|       grid.compact = (direction?: string) => { | ||||
|         console.group('🗜️ Compacting Grid'); | ||||
|         console.log('Direction:', direction || 'vertical'); | ||||
|         logGridState('Before Compact'); | ||||
|         const result = originalCompact(direction); | ||||
|         logGridState('After Compact'); | ||||
|         console.groupEnd(); | ||||
|         return result; | ||||
|       }; | ||||
|  | ||||
|       updateStatus(); | ||||
|     }}> | ||||
|       <style> | ||||
|   | ||||
| @@ -413,20 +413,42 @@ export class DeesDashboardgrid extends DeesElement { | ||||
|  | ||||
|     const layoutSource = this.widgets; | ||||
|     this.previewWidgets = null; | ||||
|  | ||||
|     // Always validate the final position, don't rely on lastPlacement from drag | ||||
|     const target = this.placeholderPosition ?? dragState.start; | ||||
|     const placement = | ||||
|       dragState.lastPlacement ?? | ||||
|       resolveWidgetPlacement( | ||||
|         layoutSource, | ||||
|         dragState.widgetId, | ||||
|         { x: target.x, y: target.y }, | ||||
|         this.columns, | ||||
|         dragState.previousPosition, | ||||
|       ); | ||||
|     const placement = resolveWidgetPlacement( | ||||
|       layoutSource, | ||||
|       dragState.widgetId, | ||||
|       { x: target.x, y: target.y }, | ||||
|       this.columns, | ||||
|       dragState.previousPosition, | ||||
|     ); | ||||
|  | ||||
|     if (placement) { | ||||
|       this.commitPlacement(placement, dragState.widgetId, 'widget-move'); | ||||
|       // Verify that the placement doesn't result in overlapping widgets | ||||
|       const finalWidget = placement.widgets.find(w => w.id === dragState.widgetId); | ||||
|       if (finalWidget) { | ||||
|         const hasOverlap = placement.widgets.some(w => { | ||||
|           if (w.id === dragState.widgetId) return false; | ||||
|           return ( | ||||
|             finalWidget.x < w.x + w.w && | ||||
|             finalWidget.x + finalWidget.w > w.x && | ||||
|             finalWidget.y < w.y + w.h && | ||||
|             finalWidget.y + finalWidget.h > w.y | ||||
|           ); | ||||
|         }); | ||||
|  | ||||
|         if (!hasOverlap) { | ||||
|           this.commitPlacement(placement, dragState.widgetId, 'widget-move'); | ||||
|         } else { | ||||
|           // Return to start position if overlap detected | ||||
|           this.widgets = this.widgets.map(widget => | ||||
|             widget.id === dragState.widgetId ? { ...widget, x: dragState.start.x, y: dragState.start.y } : widget, | ||||
|           ); | ||||
|         } | ||||
|       } | ||||
|     } else { | ||||
|       // Return to start position if no valid placement | ||||
|       this.widgets = this.widgets.map(widget => | ||||
|         widget.id === dragState.widgetId ? { ...widget, x: dragState.start.x, y: dragState.start.y } : widget, | ||||
|       ); | ||||
|   | ||||
| @@ -161,7 +161,9 @@ export const resolveWidgetPlacement = ( | ||||
|     if (!other.locked && !other.noMove && other.w === moving.w && other.h === moving.h) { | ||||
|       const otherClone = sourceWidgets.find(widget => widget.id === other.id); | ||||
|       if (otherClone) { | ||||
|         const swapTarget = previousPosition ?? original; | ||||
|         // Use the original position of the moving widget for a clean swap | ||||
|         // This prevents the "snapping together" issue where both widgets end up at the same position | ||||
|         const swapTarget = original; | ||||
|         otherClone.x = swapTarget.x; | ||||
|         otherClone.y = swapTarget.y; | ||||
|         return { widgets: sourceWidgets, movedWidgets: [moving.id, otherClone.id], swappedWith: otherClone.id }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user