From 2f348fbb9cd8e414c513d7dc8a61c82bf4a0304c Mon Sep 17 00:00:00 2001 From: Shreyash Pawar Date: Thu, 18 Dec 2025 13:41:34 +0530 Subject: [PATCH] feat: move absolute/fixed positioned freely on the canvas #140 --- frontend/src/components/ComponentEditor.vue | 62 ++++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/ComponentEditor.vue b/frontend/src/components/ComponentEditor.vue index b5f9ef02..5c19989c 100644 --- a/frontend/src/components/ComponentEditor.vue +++ b/frontend/src/components/ComponentEditor.vue @@ -6,6 +6,7 @@ :data-component-id="block.componentId" :class="getStyleClasses" @click.stop="handleClick" + @mousedown.prevent="handleMove" > () const canvasProps = inject("canvasProps") as CanvasProps - +const guides = setGuides(props.target, canvasProps); +const moving = ref(false) const showMarginPaddingHandlers = computed(() => { return isBlockSelected.value && !props.block.isRoot() && !resizing.value && !canvasStore.isDragging }) @@ -190,6 +195,59 @@ const handleClick = (ev: MouseEvent) => { } } + +const handleMove = (ev: MouseEvent) => { + if (props.block.isRoot()) return; + const pauseId = canvasStore.activeCanvas?.history?.pause(); + const target = ev.target as HTMLElement; + const startX = ev.clientX; + const startY = ev.clientY; + const startLeft = (props.target as HTMLElement).offsetLeft || 0; + const startTop = (props.target as HTMLElement).offsetTop || 0; + + moving.value = true; + guides.showX(); + + // to disable cursor jitter + const docCursor = document.body.style.cursor; + document.body.style.cursor = "grabbing"; + target.style.cursor = "grabbing"; + + const mousemove = async (mouseMoveEvent: MouseEvent) => { + const scale = canvasProps.scale; + const movementX = (mouseMoveEvent.clientX - startX) / scale; + const movementY = (mouseMoveEvent.clientY - startY) / scale; + let finalLeft = startLeft + movementX; + let finalTop = startTop + movementY; + props.block.setStyle("left", numberToPx(finalLeft)); + props.block.setStyle("top", numberToPx(finalTop)); + const { leftOffset, rightOffset } = guides.getPositionOffset(); + if (leftOffset !== 0) { + props.block.setStyle("left", numberToPx(finalLeft + leftOffset)); + } + if (rightOffset !== 0) { + props.block.setStyle("left", numberToPx(finalLeft + rightOffset)); + } + + mouseMoveEvent.preventDefault(); + preventClick.value = true; + }; + document.addEventListener("mousemove", mousemove); + document.addEventListener( + "mouseup", + (mouseUpEvent) => { + moving.value = false; + document.body.style.cursor = docCursor; + target.style.cursor = "grab"; + document.removeEventListener("mousemove", mousemove); + mouseUpEvent.preventDefault(); + guides.hideX(); + canvasStore.activeCanvas?.history?.resume(pauseId, true); + }, + { once: true }, + ); +}; + watchEffect(() => { props.block.getStyle("top") props.block.getStyle("left") @@ -316,4 +374,4 @@ onMounted(() => { defineExpose({ element: editor, }) - + \ No newline at end of file