feat(dees-dashboardgrid): enhance widget placement validation and logging for drag-and-drop interactions

This commit is contained in:
2025-09-18 10:39:11 +00:00
parent 9b39196195
commit 792e77f824
3 changed files with 187 additions and 15 deletions

View File

@@ -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>

View File

@@ -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,
);

View File

@@ -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 };