diff --git a/packages/vue/src/shared/input/auto-layout.ts b/packages/vue/src/shared/input/auto-layout.ts index 77abf9fda..554a8c32b 100644 --- a/packages/vue/src/shared/input/auto-layout.ts +++ b/packages/vue/src/shared/input/auto-layout.ts @@ -64,12 +64,17 @@ export function computeIndicatorPosition( return (prevAbs.y + prev.height + nextAbs.y) / 2 } -export function filteredToRealIndex(parentId: string, insertIndex: number, editor: Editor): number { +export function filteredToRealIndex( + parentId: string, + insertIndex: number, + editor: Editor, + movingIds = editor.state.selectedIds +): number { const allChildren = editor.graph.getChildren(parentId) let realIndex = 0 let filteredCount = 0 for (const child of allChildren) { - if (editor.state.selectedIds.has(child.id)) continue + if (movingIds.has(child.id)) continue if (child.layoutPositioning === 'ABSOLUTE') { realIndex++ continue @@ -85,11 +90,12 @@ export function computeAutoLayoutIndicatorForFrame( parent: SceneNode, cx: number, cy: number, - editor: Editor + editor: Editor, + movingIds = editor.state.selectedIds ) { const children = editor.graph .getChildren(parent.id) - .filter((c) => c.layoutPositioning !== 'ABSOLUTE' && !editor.state.selectedIds.has(c.id)) + .filter((c) => c.layoutPositioning !== 'ABSOLUTE' && !movingIds.has(c.id)) const parentAbs = editor.graph.getAbsolutePosition(parent.id) const isRow = parent.layoutMode === 'HORIZONTAL' @@ -107,6 +113,17 @@ export function computeAutoLayoutIndicatorForFrame( } } + const realIndex = filteredToRealIndex(parent.id, insertIndex, editor, movingIds) + if (movingIds.size === 1) { + const movingId = [...movingIds][0] + const movingNode = editor.graph.getNode(movingId) + const currentIndex = parent.childIds.indexOf(movingId) + if (movingNode?.parentId === parent.id && realIndex === currentIndex) { + editor.setLayoutInsertIndicator(null) + return + } + } + const indicatorPos = computeIndicatorPosition( children, insertIndex, @@ -122,7 +139,7 @@ export function computeAutoLayoutIndicatorForFrame( editor.setLayoutInsertIndicator({ parentId: parent.id, - index: filteredToRealIndex(parent.id, insertIndex, editor), + index: realIndex, x: isRow ? indicatorPos : crossStart, y: isRow ? crossStart : indicatorPos, length: crossLength, @@ -134,5 +151,5 @@ export function computeAutoLayoutIndicator(d: DragMove, cx: number, cy: number, if (!d.autoLayoutParentId) return const parent = editor.graph.getNode(d.autoLayoutParentId) if (!parent || parent.layoutMode === 'NONE') return - computeAutoLayoutIndicatorForFrame(parent, cx, cy, editor) + computeAutoLayoutIndicatorForFrame(parent, cx, cy, editor, new Set(d.originals.keys())) } diff --git a/tests/e2e/editor/auto-layout-drag.spec.ts b/tests/e2e/editor/auto-layout-drag.spec.ts index ce8e3a2dd..5d07f14cd 100644 --- a/tests/e2e/editor/auto-layout-drag.spec.ts +++ b/tests/e2e/editor/auto-layout-drag.spec.ts @@ -82,3 +82,82 @@ test('dragging selected nested instance content reorders its auto-layout item', expect(childIds).toEqual([ids.second, ids.first]) canvas.assertNoErrors() }) + +test('auto-layout drag does not show an insert indicator before order changes', async ({ page }) => { + await page.goto('/') + const canvas = new CanvasHelper(page) + await canvas.waitForInit() + canvas.errors.length = 0 + await canvas.clearCanvas() + + const ids = await page.evaluate(() => { + const store = window.openPencil?.getStore?.() + if (!store) throw new Error('OpenPencil store not initialized') + + const pageId = store.state.currentPageId + const frame = store.graph.createNode('FRAME', pageId, { + name: 'Auto Row', + x: 100, + y: 100, + width: 360, + height: 80, + layoutMode: 'HORIZONTAL', + itemSpacing: 20, + paddingTop: 10, + paddingRight: 10, + paddingBottom: 10, + paddingLeft: 10 + }) + const first = store.graph.createNode('INSTANCE', frame.id, { + name: 'First item', + x: 110, + y: 110, + width: 120, + height: 60 + }) + const second = store.graph.createNode('INSTANCE', frame.id, { + name: 'Second item', + x: 250, + y: 110, + width: 120, + height: 60 + }) + const secondText = store.graph.createNode('TEXT', second.id, { + name: 'Second label', + text: 'Second', + x: 8, + y: 8, + width: 80, + height: 24 + }) + + store.select([secondText.id]) + store.requestRender() + return { frame: frame.id, first: first.id, second: second.id } + }) + await canvas.waitForRender() + + const box = await canvas.canvas.boundingBox() + if (!box) throw new Error('Canvas has no bounding box') + await page.mouse.move(box.x + 370, box.y + 228) + await page.mouse.down() + await page.mouse.move(box.x + 365, box.y + 228, { steps: 3 }) + await canvas.waitForRender() + + const indicator = await page.evaluate(() => { + const store = window.openPencil?.getStore?.() + if (!store) throw new Error('OpenPencil store not initialized') + return store.state.layoutInsertIndicator + }) + await page.mouse.up() + + expect(indicator).toBeNull() + + const childIds = await page.evaluate((frameId) => { + const store = window.openPencil?.getStore?.() + if (!store) throw new Error('OpenPencil store not initialized') + return store.graph.getNode(frameId)?.childIds ?? [] + }, ids.frame) + expect(childIds).toEqual([ids.first, ids.second]) + canvas.assertNoErrors() +})