diff --git a/readme.plan.md b/readme.plan.md index d0d0bc5..aa7a75e 100644 Binary files a/readme.plan.md and b/readme.plan.md differ diff --git a/test/test.dashboardgrid-layout.node.ts b/test/test.dashboardgrid-layout.node.ts new file mode 100644 index 0000000..1e56ab5 --- /dev/null +++ b/test/test.dashboardgrid-layout.node.ts @@ -0,0 +1,28 @@ +import { tap, expect } from '@push.rocks/tapbundle'; + +import { + resolveWidgetPlacement, + collectCollisions, +} from '../ts_web/elements/dees-dashboardgrid/layout.ts'; +import type { DashboardWidget } from '../ts_web/elements/dees-dashboardgrid/types.ts'; + +tap.test('dashboardgrid does not overlap widgets after swap attempt', async () => { + const widgets: DashboardWidget[] = [ + { id: 'w0', x: 6, y: 5, w: 1, h: 3 }, + { id: 'w1', x: 6, y: 1, w: 1, h: 3 }, + { id: 'w2', x: 3, y: 0, w: 2, h: 2 }, + { id: 'w3', x: 9, y: 0, w: 1, h: 2 }, + { id: 'w4', x: 4, y: 3, w: 1, h: 2 }, + ]; + + const placement = resolveWidgetPlacement(widgets, 'w0', { x: 6, y: 3 }, 12); + expect(placement).toBeTruthy(); + + const layout = placement!.widgets; + for (const widget of layout) { + const collisions = collectCollisions(layout, widget, widget.x, widget.y, widget.w, widget.h); + expect(collisions).toBeEmptyArray(); + } +}); + +export default tap.start(); diff --git a/ts_web/elements/dees-dashboardgrid/layout.ts b/ts_web/elements/dees-dashboardgrid/layout.ts index 5c81473..696b922 100644 --- a/ts_web/elements/dees-dashboardgrid/layout.ts +++ b/ts_web/elements/dees-dashboardgrid/layout.ts @@ -164,9 +164,20 @@ export const resolveWidgetPlacement = ( // 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; + const previousOtherPosition = { x: otherClone.x, y: otherClone.y }; otherClone.x = swapTarget.x; otherClone.y = swapTarget.y; - return { widgets: sourceWidgets, movedWidgets: [moving.id, otherClone.id], swappedWith: otherClone.id }; + + const swapValid = + collectCollisions(sourceWidgets, moving, moving.x, moving.y, moving.w, moving.h).length === 0 && + collectCollisions(sourceWidgets, otherClone, otherClone.x, otherClone.y, otherClone.w, otherClone.h).length === 0; + + if (swapValid) { + return { widgets: sourceWidgets, movedWidgets: [moving.id, otherClone.id], swappedWith: otherClone.id }; + } + + otherClone.x = previousOtherPosition.x; + otherClone.y = previousOtherPosition.y; } } }