From 2a61db5fed6cd09f548521ba6a799c05ca02f2b4 Mon Sep 17 00:00:00 2001 From: Sahil Gupta Date: Sat, 25 Oct 2025 22:44:43 +0530 Subject: [PATCH 1/8] disallow invalid layer drop --- frontend/src/components/panels/Layers.svelte | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/panels/Layers.svelte b/frontend/src/components/panels/Layers.svelte index 82c8a5f068..4610ba9a4b 100644 --- a/frontend/src/components/panels/Layers.svelte +++ b/frontend/src/components/panels/Layers.svelte @@ -308,16 +308,25 @@ let markerHeight = 0; const layerPanel = document.querySelector("[data-layer-panel]"); // Selects the element with the data-layer-panel attribute if (layerPanel !== null && treeChildren !== undefined && treeOffset !== undefined) { + const lastChild = treeChildren[treeChildren.length - 1]; + if (lastChild.getBoundingClientRect().bottom < clientY) { + return { select, insertParentId: undefined, insertDepth: 0, insertIndex: undefined, highlightFolder: false, markerHeight: 0 }; + } + let layerPanelTop = layerPanel.getBoundingClientRect().top; Array.from(treeChildren).forEach((treeChild) => { - const indexAttribute = treeChild.getAttribute("data-index"); + const indexAttribute = parseInt(treeChild.getAttribute("data-index") ?? "0", 10); if (!indexAttribute) return; - const { folderIndex, entry: layer } = layers[parseInt(indexAttribute, 10)]; + const { folderIndex, entry: layer } = layers[indexAttribute]; const rect = treeChild.getBoundingClientRect(); if (rect.top > clientY || rect.bottom < clientY) { return; } + + const isDraggingBtnArtBoards = layers[indexAttribute]?.entry?.depth === 1 && layers[indexAttribute + 1]?.entry?.depth === 1; + if (isDraggingBtnArtBoards) return; + const pointerPercentage = (clientY - rect.top) / rect.height; if (layer.childrenAllowed) { if (pointerPercentage < 0.25) { From f1912387ad532ed7aae51cd784b2961b93b5450b Mon Sep 17 00:00:00 2001 From: Sahil Gupta Date: Fri, 31 Oct 2025 22:35:06 +0530 Subject: [PATCH 2/8] improve invalidDrag indicator behaviour --- frontend/src/components/panels/Layers.svelte | 37 +++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/panels/Layers.svelte b/frontend/src/components/panels/Layers.svelte index 4610ba9a4b..83e43de04a 100644 --- a/frontend/src/components/panels/Layers.svelte +++ b/frontend/src/components/panels/Layers.svelte @@ -291,7 +291,7 @@ editor.handle.deselectAllLayers(); } - function calculateDragIndex(tree: LayoutCol, clientY: number, select?: () => void): DraggingData { + function calculateDragIndex(tree: LayoutCol, clientY: number, select?: () => void): DraggingData | undefined { const treeChildren = tree.div()?.children; const treeOffset = tree.div()?.getBoundingClientRect().top; @@ -307,25 +307,42 @@ let markerHeight = 0; const layerPanel = document.querySelector("[data-layer-panel]"); // Selects the element with the data-layer-panel attribute + let isInvalidDrag = false; + if (layerPanel !== null && treeChildren !== undefined && treeOffset !== undefined) { const lastChild = treeChildren[treeChildren.length - 1]; - if (lastChild.getBoundingClientRect().bottom < clientY) { - return { select, insertParentId: undefined, insertDepth: 0, insertIndex: undefined, highlightFolder: false, markerHeight: 0 }; + if (clientY + 10 > lastChild.getBoundingClientRect().bottom) { + return; } let layerPanelTop = layerPanel.getBoundingClientRect().top; - Array.from(treeChildren).forEach((treeChild) => { + + for (const treeChild of Array.from(treeChildren)) { + if (isInvalidDrag) break; const indexAttribute = parseInt(treeChild.getAttribute("data-index") ?? "0", 10); - if (!indexAttribute) return; + if (!indexAttribute) continue; const { folderIndex, entry: layer } = layers[indexAttribute]; const rect = treeChild.getBoundingClientRect(); if (rect.top > clientY || rect.bottom < clientY) { - return; + continue; + } + + const prevLayer = layers[indexAttribute]; + const nextLayer = layers[indexAttribute + 1]; + if (prevLayer?.entry?.depth === 1) { + const prevRectTop = treeChildren?.[indexAttribute].getBoundingClientRect().top; + if (prevLayer?.entry?.depth === 1 && prevRectTop + 10 > clientY) { + isInvalidDrag = true; + break; + } } - const isDraggingBtnArtBoards = layers[indexAttribute]?.entry?.depth === 1 && layers[indexAttribute + 1]?.entry?.depth === 1; - if (isDraggingBtnArtBoards) return; + const isDraggingBtnArtBoards = nextLayer?.entry?.depth === 1 && prevLayer?.entry?.depth === 1; + + if (isDraggingBtnArtBoards) { + isInvalidDrag = true; + } const pointerPercentage = (clientY - rect.top) / rect.height; if (layer.childrenAllowed) { @@ -358,7 +375,8 @@ markerHeight = rect.bottom - layerPanelTop; } } - }); + break; + } // Dragging to the empty space below all layers let lastLayer = treeChildren[treeChildren.length - 1]; if (lastLayer.getBoundingClientRect().bottom < clientY) { @@ -368,6 +386,7 @@ insertIndex = numberRootLayers; markerHeight = lastLayer.getBoundingClientRect().bottom - layerPanelTop; } + if (isInvalidDrag) return; } return { From 87aec62240c50631bf31c4c7ff8a396adc99a11d Mon Sep 17 00:00:00 2001 From: Sahil Gupta Date: Sat, 1 Nov 2025 21:45:57 +0530 Subject: [PATCH 3/8] overall layer,artboard drag behaviour impr --- frontend/src/components/panels/Layers.svelte | 52 +++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/frontend/src/components/panels/Layers.svelte b/frontend/src/components/panels/Layers.svelte index 83e43de04a..a048a7bf8c 100644 --- a/frontend/src/components/panels/Layers.svelte +++ b/frontend/src/components/panels/Layers.svelte @@ -291,7 +291,7 @@ editor.handle.deselectAllLayers(); } - function calculateDragIndex(tree: LayoutCol, clientY: number, select?: () => void): DraggingData | undefined { + function calculateDragIndex(tree: LayoutCol, clientY: number, dataIndex: number, select?: () => void): DraggingData | undefined { const treeChildren = tree.div()?.children; const treeOffset = tree.div()?.getBoundingClientRect().top; @@ -310,9 +310,11 @@ let isInvalidDrag = false; if (layerPanel !== null && treeChildren !== undefined && treeOffset !== undefined) { - const lastChild = treeChildren[treeChildren.length - 1]; - if (clientY + 10 > lastChild.getBoundingClientRect().bottom) { - return; + const lastLayerDepth = layers[layers.length - 1]?.entry?.depth; + const draggingLayerDepth = layers[dataIndex]?.entry?.depth; + + if (clientY > treeChildren[layers.length - 1].getBoundingClientRect().bottom - 10) { + if (lastLayerDepth === 1 && draggingLayerDepth > 1) isInvalidDrag = true; } let layerPanelTop = layerPanel.getBoundingClientRect().top; @@ -327,23 +329,25 @@ if (rect.top > clientY || rect.bottom < clientY) { continue; } + if (draggingLayerDepth > 1) { + const prevLayer = layers[indexAttribute]; + const nextLayer = layers[indexAttribute + 1]; + if (prevLayer?.entry?.depth === 1) { + const prevRectTop = treeChildren?.[indexAttribute].getBoundingClientRect().top; + if (prevLayer?.entry?.depth === 1 && prevRectTop + 10 > clientY) { + isInvalidDrag = true; + break; + } + } - const prevLayer = layers[indexAttribute]; - const nextLayer = layers[indexAttribute + 1]; - if (prevLayer?.entry?.depth === 1) { - const prevRectTop = treeChildren?.[indexAttribute].getBoundingClientRect().top; - if (prevLayer?.entry?.depth === 1 && prevRectTop + 10 > clientY) { + const isDraggingBtnArtBoards = nextLayer?.entry?.depth === 1 && prevLayer?.entry?.depth === 1; + + if (isDraggingBtnArtBoards) { isInvalidDrag = true; break; } } - const isDraggingBtnArtBoards = nextLayer?.entry?.depth === 1 && prevLayer?.entry?.depth === 1; - - if (isDraggingBtnArtBoards) { - isInvalidDrag = true; - } - const pointerPercentage = (clientY - rect.top) / rect.height; if (layer.childrenAllowed) { if (pointerPercentage < 0.25) { @@ -412,7 +416,11 @@ const target = (event.target instanceof HTMLElement && event.target) || undefined; const closest = target?.closest("[data-layer]") || undefined; const draggingELement = (closest instanceof HTMLElement && closest) || undefined; - if (draggingELement) beginDraggingElement(draggingELement); + if (draggingELement) { + beginDraggingElement(draggingELement); + // Store the index of the layer being dragged + draggedLayerIndex = parseInt(draggingELement.getAttribute("data-index") ?? "0", 10); + } // Set style of cursor for drag if (event.dataTransfer) { @@ -420,9 +428,13 @@ event.dataTransfer.effectAllowed = "move"; } - if (list) draggingData = calculateDragIndex(list, event.clientY, select); + if (list && draggedLayerIndex !== undefined) { + draggingData = calculateDragIndex(list, event.clientY, draggedLayerIndex, select); + } } + let draggedLayerIndex: number | undefined; + function updateInsertLine(event: DragEvent) { if (!draggable) return; @@ -430,7 +442,10 @@ event.preventDefault(); dragInPanel = true; - if (list) draggingData = calculateDragIndex(list, event.clientY, draggingData?.select); + // Use the stored index from dragStart + if (list && draggedLayerIndex !== undefined) { + draggingData = calculateDragIndex(list, event.clientY, draggedLayerIndex, draggingData?.select); + } } function drop(e: DragEvent) { @@ -480,6 +495,7 @@ draggingData = undefined; fakeHighlightOfNotYetSelectedLayerBeingDragged = undefined; dragInPanel = false; + draggedLayerIndex = undefined; } function rebuildLayerHierarchy(updateDocumentLayerStructure: DocumentLayerStructure) { From 44b52709295a69a655ba018072d88fb19c45df0e Mon Sep 17 00:00:00 2001 From: Sahil Gupta Date: Thu, 1 Jan 2026 17:11:28 +0530 Subject: [PATCH 4/8] fixing my implmentation --- frontend/src/components/panels/Layers.svelte | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/panels/Layers.svelte b/frontend/src/components/panels/Layers.svelte index bc0e686eb1..0d43115b5b 100644 --- a/frontend/src/components/panels/Layers.svelte +++ b/frontend/src/components/panels/Layers.svelte @@ -458,6 +458,7 @@ if (distance > DRAG_THRESHOLD) { internalDragState.active = true; + draggedLayerIndex = internalDragState.listing.folderIndex; dragInPanel = true; const layer = internalDragState.listing.entry; @@ -468,14 +469,14 @@ } // Perform drag calculations if a drag is occurring - if (internalDragState.active) { + if (internalDragState.active && draggedLayerIndex !== undefined) { const select = () => { if (internalDragState && !$nodeGraph.selected.includes(internalDragState.layerId)) { selectLayer(internalDragState.listing, false, false); } }; - draggingData = calculateDragIndex(list, e.clientY, select); + draggingData = calculateDragIndex(list, e.clientY,draggedLayerIndex, select); } } From 43e47d5551e3d6c7b244bedac52096f88fa2ed40 Mon Sep 17 00:00:00 2001 From: Sahil Gupta Date: Mon, 16 Feb 2026 13:16:42 +0530 Subject: [PATCH 5/8] review fixes --- frontend/src/components/panels/Layers.svelte | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/panels/Layers.svelte b/frontend/src/components/panels/Layers.svelte index 0d43115b5b..7b559dc889 100644 --- a/frontend/src/components/panels/Layers.svelte +++ b/frontend/src/components/panels/Layers.svelte @@ -73,7 +73,7 @@ let layersPanelControlBarLeftLayout: Layout = []; let layersPanelControlBarRightLayout: Layout = []; let layersPanelBottomBarLayout: Layout = []; - + const EDGE_BUFFER_PX = 10; onMount(() => { editor.subscriptions.subscribeJsMessage(UpdateLayersPanelControlBarLeftLayout, (data) => { patchLayout(layersPanelControlBarLeftLayout, data); @@ -345,7 +345,7 @@ const lastLayerDepth = layers[layers.length - 1]?.entry?.depth; const draggingLayerDepth = layers[dataIndex]?.entry?.depth; - if (clientY > treeChildren[layers.length - 1].getBoundingClientRect().bottom - 10) { + if (clientY > treeChildren[layers.length - 1].getBoundingClientRect().bottom - EDGE_BUFFER_PX) { if (lastLayerDepth === 1 && draggingLayerDepth > 1) isInvalidDrag = true; } @@ -366,15 +366,15 @@ const nextLayer = layers[indexAttribute + 1]; if (prevLayer?.entry?.depth === 1) { const prevRectTop = treeChildren?.[indexAttribute].getBoundingClientRect().top; - if (prevLayer?.entry?.depth === 1 && prevRectTop + 10 > clientY) { + if (prevLayer?.entry?.depth === 1 && prevRectTop + EDGE_BUFFER_PX > clientY) { isInvalidDrag = true; break; } } - const isDraggingBtnArtBoards = nextLayer?.entry?.depth === 1 && prevLayer?.entry?.depth === 1; + const isDraggingBetweenArtboards = nextLayer?.entry?.depth === 1 && prevLayer?.entry?.depth === 1; - if (isDraggingBtnArtBoards) { + if (isDraggingBetweenArtboards) { isInvalidDrag = true; break; } From a8cdf906bce4d9fb99b446da6144cdf1c5ef4fa0 Mon Sep 17 00:00:00 2001 From: Sahil Gupta Date: Tue, 17 Feb 2026 13:21:52 +0530 Subject: [PATCH 6/8] using insertDepth for indentification --- frontend/src/components/panels/Layers.svelte | 39 +++++++------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/frontend/src/components/panels/Layers.svelte b/frontend/src/components/panels/Layers.svelte index b9f3c36342..ac2fb3c869 100644 --- a/frontend/src/components/panels/Layers.svelte +++ b/frontend/src/components/panels/Layers.svelte @@ -74,7 +74,7 @@ let layersPanelControlBarLeftLayout: Layout = []; let layersPanelControlBarRightLayout: Layout = []; let layersPanelBottomBarLayout: Layout = []; - const EDGE_BUFFER_PX = 10; + onMount(() => { editor.subscriptions.subscribeJsMessage(UpdateLayersPanelControlBarLeftLayout, (data) => { patchLayout(layersPanelControlBarLeftLayout, data); @@ -285,10 +285,9 @@ if (layerPanel !== null && treeChildren !== undefined && treeOffset !== undefined) { const lastLayerDepth = layers[layers.length - 1]?.entry?.depth; const draggingLayerDepth = layers[dataIndex]?.entry?.depth; + + if (draggingLayerDepth === undefined) return undefined; - if (clientY > treeChildren[layers.length - 1].getBoundingClientRect().bottom - EDGE_BUFFER_PX) { - if (lastLayerDepth === 1 && draggingLayerDepth > 1) isInvalidDrag = true; - } let layerPanelTop = layerPanel.getBoundingClientRect().top; @@ -302,25 +301,6 @@ if (rect.top > clientY || rect.bottom < clientY) { continue; } - if (draggingLayerDepth > 1) { - const prevLayer = layers[indexAttribute]; - const nextLayer = layers[indexAttribute + 1]; - if (prevLayer?.entry?.depth === 1) { - const prevRectTop = treeChildren?.[indexAttribute].getBoundingClientRect().top; - if (prevLayer?.entry?.depth === 1 && prevRectTop + EDGE_BUFFER_PX > clientY) { - isInvalidDrag = true; - break; - } - } - - const isDraggingBetweenArtboards = nextLayer?.entry?.depth === 1 && prevLayer?.entry?.depth === 1; - - if (isDraggingBetweenArtboards) { - isInvalidDrag = true; - break; - } - } - const pointerPercentage = (clientY - rect.top) / rect.height; if (layer.childrenAllowed) { if (pointerPercentage < 0.25) { @@ -352,6 +332,7 @@ markerHeight = rect.bottom - layerPanelTop; } } + break; } // Dragging to the empty space below all layers @@ -363,6 +344,14 @@ insertIndex = numberRootLayers; markerHeight = lastLayer.getBoundingClientRect().bottom - layerPanelTop; } + + const isDraggingRootToNested = draggingLayerDepth === 1 && insertDepth > 0; + const isDraggingNestedToRoot = draggingLayerDepth > 1 && insertDepth === 0; + + if (isDraggingRootToNested || isDraggingNestedToRoot) { + isInvalidDrag = true; + } + if (isInvalidDrag) return; } @@ -397,9 +386,9 @@ const distance = Math.hypot(e.clientX - internalDragState.startX, e.clientY - internalDragState.startY); const DRAG_THRESHOLD = 5; - if (distance > DRAG_THRESHOLD) { + if (distance > DRAG_THRESHOLD && internalDragState) { internalDragState.active = true; - draggedLayerIndex = internalDragState.listing.folderIndex; + draggedLayerIndex = layers.findIndex((layer) => layer.entry.id === internalDragState?.layerId); dragInPanel = true; const layer = internalDragState.listing.entry; From c853bf06f218be4a9955e357acdebae0da5f8dc8 Mon Sep 17 00:00:00 2001 From: Sahil Gupta Date: Tue, 17 Feb 2026 13:28:34 +0530 Subject: [PATCH 7/8] little format --- frontend/src/components/panels/Layers.svelte | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/components/panels/Layers.svelte b/frontend/src/components/panels/Layers.svelte index ac2fb3c869..0650d4f2d8 100644 --- a/frontend/src/components/panels/Layers.svelte +++ b/frontend/src/components/panels/Layers.svelte @@ -283,11 +283,9 @@ let isInvalidDrag = false; if (layerPanel !== null && treeChildren !== undefined && treeOffset !== undefined) { - const lastLayerDepth = layers[layers.length - 1]?.entry?.depth; const draggingLayerDepth = layers[dataIndex]?.entry?.depth; - if (draggingLayerDepth === undefined) return undefined; - + if (!draggingLayerDepth) return; let layerPanelTop = layerPanel.getBoundingClientRect().top; From a910803820581cffce5e69c46176777fad255e84 Mon Sep 17 00:00:00 2001 From: Sahil Gupta Date: Wed, 18 Feb 2026 10:48:41 +0530 Subject: [PATCH 8/8] lint fix --- frontend/src/components/panels/Layers.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/panels/Layers.svelte b/frontend/src/components/panels/Layers.svelte index 0650d4f2d8..52110c9aa3 100644 --- a/frontend/src/components/panels/Layers.svelte +++ b/frontend/src/components/panels/Layers.svelte @@ -284,7 +284,7 @@ if (layerPanel !== null && treeChildren !== undefined && treeOffset !== undefined) { const draggingLayerDepth = layers[dataIndex]?.entry?.depth; - + if (!draggingLayerDepth) return; let layerPanelTop = layerPanel.getBoundingClientRect().top; @@ -349,7 +349,7 @@ if (isDraggingRootToNested || isDraggingNestedToRoot) { isInvalidDrag = true; } - + if (isInvalidDrag) return; }