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) => {
|
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) => {
|
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) => {
|
grid.addEventListener('widget-remove', (e: CustomEvent) => {
|
||||||
console.log('Widget removed:', e.detail.widget);
|
logGridState('Widget Remove', {
|
||||||
|
removedWidget: e.detail.widget
|
||||||
|
});
|
||||||
updateStatus();
|
updateStatus();
|
||||||
});
|
});
|
||||||
|
|
||||||
grid.addEventListener('layout-change', () => {
|
grid.addEventListener('layout-change', () => {
|
||||||
console.log('Layout changed:', grid.getLayout());
|
logGridState('Layout Change');
|
||||||
updateStatus();
|
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();
|
updateStatus();
|
||||||
}}>
|
}}>
|
||||||
<style>
|
<style>
|
||||||
|
@@ -413,10 +413,10 @@ export class DeesDashboardgrid extends DeesElement {
|
|||||||
|
|
||||||
const layoutSource = this.widgets;
|
const layoutSource = this.widgets;
|
||||||
this.previewWidgets = null;
|
this.previewWidgets = null;
|
||||||
|
|
||||||
|
// Always validate the final position, don't rely on lastPlacement from drag
|
||||||
const target = this.placeholderPosition ?? dragState.start;
|
const target = this.placeholderPosition ?? dragState.start;
|
||||||
const placement =
|
const placement = resolveWidgetPlacement(
|
||||||
dragState.lastPlacement ??
|
|
||||||
resolveWidgetPlacement(
|
|
||||||
layoutSource,
|
layoutSource,
|
||||||
dragState.widgetId,
|
dragState.widgetId,
|
||||||
{ x: target.x, y: target.y },
|
{ x: target.x, y: target.y },
|
||||||
@@ -425,8 +425,30 @@ export class DeesDashboardgrid extends DeesElement {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (placement) {
|
if (placement) {
|
||||||
|
// 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');
|
this.commitPlacement(placement, dragState.widgetId, 'widget-move');
|
||||||
} else {
|
} 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 =>
|
this.widgets = this.widgets.map(widget =>
|
||||||
widget.id === dragState.widgetId ? { ...widget, x: dragState.start.x, y: dragState.start.y } : 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) {
|
if (!other.locked && !other.noMove && other.w === moving.w && other.h === moving.h) {
|
||||||
const otherClone = sourceWidgets.find(widget => widget.id === other.id);
|
const otherClone = sourceWidgets.find(widget => widget.id === other.id);
|
||||||
if (otherClone) {
|
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.x = swapTarget.x;
|
||||||
otherClone.y = swapTarget.y;
|
otherClone.y = swapTarget.y;
|
||||||
return { widgets: sourceWidgets, movedWidgets: [moving.id, otherClone.id], swappedWith: otherClone.id };
|
return { widgets: sourceWidgets, movedWidgets: [moving.id, otherClone.id], swappedWith: otherClone.id };
|
||||||
|
Reference in New Issue
Block a user