Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a05d34c
feat: bring node to front when clicking on any widget
Myestery Dec 6, 2025
d1705fc
Merge branch 'main' into feat/bring-node-to-front-on-widget-click
Myestery Dec 8, 2025
901c9f4
fix: bring node to front when selected
Myestery Dec 8, 2025
6eb8f1c
test: add browser tests for bring-node-to-front behavior
Myestery Dec 8, 2025
adda138
[automated] Update test expectations
invalid-email-address Dec 8, 2025
61ba522
refactor: simplify ComfyPage type usage in helpers and remove an unus…
Myestery Dec 8, 2025
a15430c
fix: compare post-action z-indices in bring-to-front tests
Myestery Dec 8, 2025
bd1c6d1
Merge branch 'main' into feat/bring-node-to-front-on-widget-click
Myestery Dec 8, 2025
013aea6
Merge branch 'main' into feat/bring-node-to-front-on-widget-click
Myestery Dec 8, 2025
8e7bf44
Merge branch 'main' into feat/bring-node-to-front-on-widget-click
Myestery Dec 8, 2025
0f8a600
Merge branch 'main' into feat/bring-node-to-front-on-widget-click
Myestery Dec 8, 2025
8f80a45
Merge branch 'feat/bring-node-to-front-on-widget-click' of github.com…
Myestery Dec 8, 2025
15ab9e5
Merge branch 'main' into feat/bring-node-to-front-on-widget-click
Myestery Dec 8, 2025
e1dd8a9
[automated] Update test expectations
invalid-email-address Dec 9, 2025
54e77ad
Merge branch 'main' into feat/bring-node-to-front-on-widget-click
Myestery Dec 9, 2025
3b7065a
Merge branch 'main' into feat/bring-node-to-front-on-widget-click
Myestery Dec 9, 2025
1a73974
test: throw descriptive errors in getNodeZIndex helper
Myestery Dec 9, 2025
bd497b1
[automated] Update test expectations
invalid-email-address Dec 9, 2025
6382530
Merge branch 'main' into feat/bring-node-to-front-on-widget-click
Myestery Dec 9, 2025
69b97d6
merge: resolve screenshot conflicts by keeping ours
Myestery Dec 10, 2025
05c95d2
Merge branch 'main' into feat/bring-node-to-front-on-widget-click
Myestery Dec 10, 2025
2ff663e
[automated] Update test expectations
invalid-email-address Dec 10, 2025
2a0ea7d
merge: resolve conflict by accepting main version of snapshot
Myestery Dec 11, 2025
dcee42a
Merge branch 'main' into feat/bring-node-to-front-on-widget-click
Myestery Dec 11, 2025
ec14c8b
[automated] Update test expectations
invalid-email-address Dec 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
144 changes: 144 additions & 0 deletions browser_tests/tests/vueNodes/interactions/node/bringToFront.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import {
comfyExpect as expect,
comfyPageFixture as test
} from '../../../../fixtures/ComfyPage'
import type { ComfyPage } from '../../../../fixtures/ComfyPage'
import { fitToViewInstant } from '../../../../helpers/fitToView'

test.describe('Vue Node Bring to Front', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.loadWorkflow('vueNodes/simple-triple')
await comfyPage.vueNodes.waitForNodes()
await fitToViewInstant(comfyPage)
})

/**
* Helper to get the z-index of a node by its title
*/
async function getNodeZIndex(
comfyPage: ComfyPage,
title: string
): Promise<number> {
const node = comfyPage.vueNodes.getNodeByTitle(title)
const style = await node.getAttribute('style')
if (!style) {
throw new Error(
`Node "${title}" has no style attribute (observed: ${style})`
)
}
const match = style.match(/z-index:\s*(\d+)/)
if (!match) {
throw new Error(
`Node "${title}" has no z-index in style (observed: "${style}")`
)
}
return parseInt(match[1], 10)
}

/**
* Helper to get the bounding box center of a node
*/
async function getNodeCenter(
comfyPage: ComfyPage,
title: string
): Promise<{ x: number; y: number }> {
const node = comfyPage.vueNodes.getNodeByTitle(title)
const box = await node.boundingBox()
if (!box) throw new Error(`Node "${title}" not found`)
return { x: box.x + box.width / 2, y: box.y + box.height / 2 }
}

test('should bring overlapped node to front when clicking on it', async ({
comfyPage
}) => {
// Get initial positions
const clipCenter = await getNodeCenter(comfyPage, 'CLIP Text Encode')
const ksamplerHeader = await comfyPage.page
.getByText('KSampler')
.boundingBox()
if (!ksamplerHeader) throw new Error('KSampler header not found')

// Drag KSampler on top of CLIP Text Encode
await comfyPage.dragAndDrop(
{ x: ksamplerHeader.x + 50, y: ksamplerHeader.y + 10 },
clipCenter
)
await comfyPage.nextFrame()

// Screenshot showing KSampler on top of CLIP
await expect(comfyPage.canvas).toHaveScreenshot(
'bring-to-front-overlapped-before.png'
)

// KSampler should be on top (higher z-index) after being dragged
const ksamplerZIndexBefore = await getNodeZIndex(comfyPage, 'KSampler')
const clipZIndexBefore = await getNodeZIndex(comfyPage, 'CLIP Text Encode')
expect(ksamplerZIndexBefore).toBeGreaterThan(clipZIndexBefore)

// Click on CLIP Text Encode (underneath) - need to click on a visible part
// Since KSampler is on top, we click on the edge of CLIP that should still be visible
const clipNode = comfyPage.vueNodes.getNodeByTitle('CLIP Text Encode')
const clipBox = await clipNode.boundingBox()
if (!clipBox) throw new Error('CLIP node not found')

// Click on a visible edge of CLIP
await comfyPage.page.mouse.click(clipBox.x + 30, clipBox.y + 10)
await comfyPage.nextFrame()

// CLIP should now be on top - compare post-action z-indices
const clipZIndexAfter = await getNodeZIndex(comfyPage, 'CLIP Text Encode')
const ksamplerZIndexAfter = await getNodeZIndex(comfyPage, 'KSampler')
expect(clipZIndexAfter).toBeGreaterThan(ksamplerZIndexAfter)

// Screenshot showing CLIP now on top
await expect(comfyPage.canvas).toHaveScreenshot(
'bring-to-front-overlapped-after.png'
)
})

test('should bring overlapped node to front when clicking on its widget', async ({
comfyPage
}) => {
// Get CLIP Text Encode position (it has a text widget)
const clipCenter = await getNodeCenter(comfyPage, 'CLIP Text Encode')

// Get VAE Decode position and drag it on top of CLIP
const vaeHeader = await comfyPage.page.getByText('VAE Decode').boundingBox()
if (!vaeHeader) throw new Error('VAE Decode header not found')

await comfyPage.dragAndDrop(
{ x: vaeHeader.x + 50, y: vaeHeader.y + 10 },
{ x: clipCenter.x - 50, y: clipCenter.y }
)
await comfyPage.nextFrame()

// VAE should be on top after drag
const vaeZIndexBefore = await getNodeZIndex(comfyPage, 'VAE Decode')
const clipZIndexBefore = await getNodeZIndex(comfyPage, 'CLIP Text Encode')
expect(vaeZIndexBefore).toBeGreaterThan(clipZIndexBefore)

// Screenshot showing VAE on top
await expect(comfyPage.canvas).toHaveScreenshot(
'bring-to-front-widget-overlapped-before.png'
)

// Click on the text widget of CLIP Text Encode
const clipNode = comfyPage.vueNodes.getNodeByTitle('CLIP Text Encode')
const clipBox = await clipNode.boundingBox()
if (!clipBox) throw new Error('CLIP node not found')
await comfyPage.page.mouse.click(clipBox.x + 170, clipBox.y + 80)
await comfyPage.nextFrame()

// CLIP should now be on top - compare post-action z-indices
const clipZIndexAfter = await getNodeZIndex(comfyPage, 'CLIP Text Encode')
const vaeZIndexAfter = await getNodeZIndex(comfyPage, 'VAE Decode')
expect(clipZIndexAfter).toBeGreaterThan(vaeZIndexAfter)

// Screenshot showing CLIP now on top after widget click
await expect(comfyPage.canvas).toHaveScreenshot(
'bring-to-front-widget-overlapped-after.png'
)
})
})
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions src/renderer/extensions/vueNodes/components/NodeWidgets.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
:style="{
'grid-template-rows': gridTemplateRows
}"
@pointerdown.capture="handleBringToFront"
@pointerdown="handleWidgetPointerEvent"
@pointermove="handleWidgetPointerEvent"
@pointerup="handleWidgetPointerEvent"
Expand Down Expand Up @@ -78,6 +79,7 @@ import { useErrorHandling } from '@/composables/useErrorHandling'
import { st } from '@/i18n'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips'
import { useNodeZIndex } from '@/renderer/extensions/vueNodes/composables/useNodeZIndex'
import WidgetDOM from '@/renderer/extensions/vueNodes/widgets/components/WidgetDOM.vue'
// Import widget components directly
import WidgetLegacy from '@/renderer/extensions/vueNodes/widgets/components/WidgetLegacy.vue'
Expand All @@ -99,12 +101,20 @@ const { nodeData } = defineProps<NodeWidgetsProps>()

const { shouldHandleNodePointerEvents, forwardEventToCanvas } =
useCanvasInteractions()
const { bringNodeToFront } = useNodeZIndex()

function handleWidgetPointerEvent(event: PointerEvent) {
if (shouldHandleNodePointerEvents.value) return
event.stopPropagation()
forwardEventToCanvas(event)
}

function handleBringToFront() {
if (nodeData?.id != null) {
bringNodeToFront(String(nodeData.id))
}
}

// Error boundary implementation
const renderError = ref<string | null>(null)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,21 @@ function useNodeEventHandlersIndividual() {
canvasStore.canvas.deselectAll()
canvasStore.canvas.select(node)
canvasStore.updateSelectedItems()
// Bring node to front when selected (unless pinned)
if (!node.flags?.pinned) {
bringNodeToFront(nodeId)
}
return
}

if (node.selected) {
canvasStore.canvas.deselect(node)
} else {
canvasStore.canvas.select(node)
// Bring node to front when selected (unless pinned)
if (!node.flags?.pinned) {
bringNodeToFront(nodeId)
}
}

canvasStore.updateSelectedItems()
Expand Down
Loading