Files
ComfyUI_frontend/src/renderer/extensions/vueNodes/composables/useNodeSnap.ts
Simula_r 1455845a30 Fix/vue nodes snap to grid (#5973)
## Summary

Enable node snap to grid in vue nodes mirroring the same behavior as
litegraph.

- Show node snap preview (semi transparent white box target behind node)
- Resize snap to grid
- Shift + drag / Auto snap 
- Multi select + group snap

## Changes

- **What**: useNodeSnap.ts useShifyKeySync.ts setups the core hooks into
both the vue node positioning/resizing system and the event forwarding
technique for communicating to litegraph.

## Review Focus

Both new composables and specifically the useNodeLayout modifications to
batch the mutations when snapping.
A key tradeoff/note is why we are using the useShifyKeySync.ts which
dispatches a new shift event to the canvas layer. This approach is the
cleaner / more declaritive method mimicking how other vue node ->
litegraph realtime events are passed.

<!-- If this PR fixes an issue, uncomment and update the line below -->
<!-- Fixes #ISSUE_NUMBER -->

## Screenshots (if applicable)

<!-- Add screenshots or video recording to help explain your changes -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5973-Fix-vue-nodes-snap-to-grid-2866d73d365081c1a058d223c8c52576)
by [Unito](https://www.unito.io)
2025-10-09 11:27:18 -07:00

74 lines
2.1 KiB
TypeScript

import { computed } from 'vue'
import { snapPoint } from '@/lib/litegraph/src/measure'
import { useSettingStore } from '@/platform/settings/settingStore'
/**
* Composable for node snap-to-grid functionality
*
* Provides reactive access to snap settings and utilities for applying
* snap-to-grid behavior to Vue nodes during drag and resize operations.
*/
export function useNodeSnap() {
const settingStore = useSettingStore()
// Reactive snap settings
const gridSize = computed(() => settingStore.get('Comfy.SnapToGrid.GridSize'))
const alwaysSnap = computed(() => settingStore.get('pysssss.SnapToGrid'))
/**
* Determines if snap-to-grid should be applied based on shift key and settings
* @param event - The pointer event to check for shift key
* @returns true if snapping should be applied
*/
function shouldSnap(event: PointerEvent): boolean {
return event.shiftKey || alwaysSnap.value
}
/**
* Applies snap-to-grid to a position
* @param position - Position object with x, y coordinates
* @returns The snapped position as a new object
*/
function applySnapToPosition(position: { x: number; y: number }): {
x: number
y: number
} {
const size = gridSize.value
if (!size) return { ...position }
const posArray: [number, number] = [position.x, position.y]
if (snapPoint(posArray, size)) {
return { x: posArray[0], y: posArray[1] }
}
return { ...position }
}
/**
* Applies snap-to-grid to a size (width/height)
* @param size - Size object with width, height
* @returns The snapped size as a new object
*/
function applySnapToSize(size: { width: number; height: number }): {
width: number
height: number
} {
const gridSizeValue = gridSize.value
if (!gridSizeValue) return { ...size }
const sizeArray: [number, number] = [size.width, size.height]
if (snapPoint(sizeArray, gridSizeValue)) {
return { width: sizeArray[0], height: sizeArray[1] }
}
return { ...size }
}
return {
gridSize,
alwaysSnap,
shouldSnap,
applySnapToPosition,
applySnapToSize
}
}