mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-26 01:09:46 +00:00
Compare commits
1 Commits
sno-licens
...
vue-node/f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
baa2e3dc81 |
@@ -352,22 +352,57 @@ const cornerResizeHandles: CornerResizeHandle[] = [
|
|||||||
|
|
||||||
const MIN_NODE_WIDTH = 225
|
const MIN_NODE_WIDTH = 225
|
||||||
|
|
||||||
const { startResize } = useNodeResize((result, element) => {
|
// Track the actual DOM size to detect when we've hit min size constraints
|
||||||
|
let lastActualHeight: number | null = null
|
||||||
|
let lastActualWidth: number | null = null
|
||||||
|
|
||||||
|
const { startResize, isResizing } = useNodeResize((result, element) => {
|
||||||
if (isCollapsed.value) return
|
if (isCollapsed.value) return
|
||||||
|
|
||||||
// Clamp width to minimum to avoid conflicts with CSS min-width
|
// Clamp width to minimum to avoid conflicts with CSS min-width
|
||||||
const clampedWidth = Math.max(result.size.width, MIN_NODE_WIDTH)
|
const clampedWidth = Math.max(result.size.width, MIN_NODE_WIDTH)
|
||||||
|
const requestedHeight = result.size.height
|
||||||
|
|
||||||
// Apply size directly to DOM element - ResizeObserver will pick this up
|
// Capture current actual size before applying (uses cached offsetWidth/Height, no layout thrash)
|
||||||
|
const prevActualWidth = element.offsetWidth
|
||||||
|
const prevActualHeight = element.offsetHeight
|
||||||
|
|
||||||
|
// Apply size directly to DOM element
|
||||||
element.style.setProperty('--node-width', `${clampedWidth}px`)
|
element.style.setProperty('--node-width', `${clampedWidth}px`)
|
||||||
element.style.setProperty('--node-height', `${result.size.height}px`)
|
element.style.setProperty('--node-height', `${requestedHeight}px`)
|
||||||
|
|
||||||
|
// Check if actual size changed from last frame (not this frame - avoid layout thrash)
|
||||||
|
// If actual size stopped changing while we're still trying to shrink, we've hit the floor
|
||||||
|
const widthHitFloor =
|
||||||
|
lastActualWidth !== null &&
|
||||||
|
Math.abs(prevActualWidth - lastActualWidth) < POSITION_EPSILON &&
|
||||||
|
clampedWidth < prevActualWidth
|
||||||
|
|
||||||
|
const heightHitFloor =
|
||||||
|
lastActualHeight !== null &&
|
||||||
|
Math.abs(prevActualHeight - lastActualHeight) < POSITION_EPSILON &&
|
||||||
|
requestedHeight < prevActualHeight
|
||||||
|
|
||||||
|
lastActualWidth = prevActualWidth
|
||||||
|
lastActualHeight = prevActualHeight
|
||||||
|
|
||||||
const currentPosition = position.value
|
const currentPosition = position.value
|
||||||
const deltaX = Math.abs(result.position.x - currentPosition.x)
|
const newX = widthHitFloor ? currentPosition.x : result.position.x
|
||||||
const deltaY = Math.abs(result.position.y - currentPosition.y)
|
const newY = heightHitFloor ? currentPosition.y : result.position.y
|
||||||
|
|
||||||
|
const deltaX = Math.abs(newX - currentPosition.x)
|
||||||
|
const deltaY = Math.abs(newY - currentPosition.y)
|
||||||
|
|
||||||
if (deltaX > POSITION_EPSILON || deltaY > POSITION_EPSILON) {
|
if (deltaX > POSITION_EPSILON || deltaY > POSITION_EPSILON) {
|
||||||
moveNodeTo(result.position)
|
moveNodeTo({ x: newX, y: newY })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Reset tracking when resize ends
|
||||||
|
watch(isResizing, (resizing) => {
|
||||||
|
if (!resizing) {
|
||||||
|
lastActualWidth = null
|
||||||
|
lastActualHeight = null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ export function useNodePointerInteractions(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const startPosition = ref({ x: 0, y: 0 })
|
// null means pointerdown hasn't happened yet on this node
|
||||||
|
const startPosition = ref<{ x: number; y: number } | null>(null)
|
||||||
|
|
||||||
const DRAG_THRESHOLD = 3 // pixels
|
const DRAG_THRESHOLD = 3 // pixels
|
||||||
|
|
||||||
@@ -60,6 +61,10 @@ export function useNodePointerInteractions(
|
|||||||
startDrag(event, nodeId)
|
startDrag(event, nodeId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearStartPosition() {
|
||||||
|
startPosition.value = null
|
||||||
|
}
|
||||||
|
|
||||||
function onPointermove(event: PointerEvent) {
|
function onPointermove(event: PointerEvent) {
|
||||||
if (forwardMiddlePointerIfNeeded(event)) return
|
if (forwardMiddlePointerIfNeeded(event)) return
|
||||||
|
|
||||||
@@ -72,6 +77,13 @@ export function useNodePointerInteractions(
|
|||||||
const multiSelect = isMultiSelectKey(event)
|
const multiSelect = isMultiSelectKey(event)
|
||||||
|
|
||||||
const lmbDown = event.buttons & 1
|
const lmbDown = event.buttons & 1
|
||||||
|
|
||||||
|
// If we don't have a start position, pointerdown was handled elsewhere (e.g., resize handle)
|
||||||
|
// Don't start dragging in this case
|
||||||
|
if (!startPosition.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (lmbDown && multiSelect && !layoutStore.isDraggingVueNodes.value) {
|
if (lmbDown && multiSelect && !layoutStore.isDraggingVueNodes.value) {
|
||||||
layoutStore.isDraggingVueNodes.value = true
|
layoutStore.isDraggingVueNodes.value = true
|
||||||
handleNodeSelect(event, nodeId)
|
handleNodeSelect(event, nodeId)
|
||||||
@@ -122,6 +134,7 @@ export function useNodePointerInteractions(
|
|||||||
|
|
||||||
if (wasDragging) {
|
if (wasDragging) {
|
||||||
safeDragEnd(event)
|
safeDragEnd(event)
|
||||||
|
clearStartPosition()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const multiSelect = isMultiSelectKey(event)
|
const multiSelect = isMultiSelectKey(event)
|
||||||
@@ -130,6 +143,8 @@ export function useNodePointerInteractions(
|
|||||||
if (nodeId) {
|
if (nodeId) {
|
||||||
toggleNodeSelectionAfterPointerUp(nodeId, multiSelect)
|
toggleNodeSelectionAfterPointerUp(nodeId, multiSelect)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearStartPosition()
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPointercancel(event: PointerEvent) {
|
function onPointercancel(event: PointerEvent) {
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
|||||||
import type { Bounds, NodeId } from '@/renderer/core/layout/types'
|
import type { Bounds, NodeId } from '@/renderer/core/layout/types'
|
||||||
import { LayoutSource } from '@/renderer/core/layout/types'
|
import { LayoutSource } from '@/renderer/core/layout/types'
|
||||||
|
|
||||||
|
// Set of node IDs currently being resized via handles (not ResizeObserver)
|
||||||
|
export const nodesBeingResized = new Set<string>()
|
||||||
|
|
||||||
import { syncNodeSlotLayoutsFromDOM } from './useSlotElementTracking'
|
import { syncNodeSlotLayoutsFromDOM } from './useSlotElementTracking'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -110,17 +113,28 @@ const resizeObserver = new ResizeObserver((entries) => {
|
|||||||
height: Math.max(0, height)
|
height: Math.max(0, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this entry is a node, mark it for slot layout resync (even during resize)
|
||||||
|
if (elementType === 'node' && elementId) {
|
||||||
|
nodesNeedingSlotResync.add(elementId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For nodes being actively resized via handles, only update size (not position)
|
||||||
|
// The position is managed by the resize callback to avoid stale DOM reads overwriting it
|
||||||
|
if (elementType === 'node' && nodesBeingResized.has(elementId)) {
|
||||||
|
const currentLayout = layoutStore.getNodeLayoutRef(elementId).value
|
||||||
|
if (currentLayout) {
|
||||||
|
// Keep current position, only update size
|
||||||
|
bounds.x = currentLayout.position.x
|
||||||
|
bounds.y = currentLayout.position.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let updates = updatesByType.get(elementType)
|
let updates = updatesByType.get(elementType)
|
||||||
if (!updates) {
|
if (!updates) {
|
||||||
updates = []
|
updates = []
|
||||||
updatesByType.set(elementType, updates)
|
updatesByType.set(elementType, updates)
|
||||||
}
|
}
|
||||||
updates.push({ id: elementId, bounds })
|
updates.push({ id: elementId, bounds })
|
||||||
|
|
||||||
// If this entry is a node, mark it for slot layout resync
|
|
||||||
if (elementType === 'node' && elementId) {
|
|
||||||
nodesNeedingSlotResync.add(elementId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
layoutStore.setSource(LayoutSource.DOM)
|
layoutStore.setSource(LayoutSource.DOM)
|
||||||
@@ -128,7 +142,9 @@ const resizeObserver = new ResizeObserver((entries) => {
|
|||||||
// Flush per-type
|
// Flush per-type
|
||||||
for (const [type, updates] of updatesByType) {
|
for (const [type, updates] of updatesByType) {
|
||||||
const config = trackingConfigs.get(type)
|
const config = trackingConfigs.get(type)
|
||||||
if (config && updates.length) config.updateHandler(updates)
|
if (config && updates.length) {
|
||||||
|
config.updateHandler(updates)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// After node bounds are updated, refresh slot cached offsets and layouts
|
// After node bounds are updated, refresh slot cached offsets and layouts
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { ref } from 'vue'
|
|||||||
import type { Point, Size } from '@/renderer/core/layout/types'
|
import type { Point, Size } from '@/renderer/core/layout/types'
|
||||||
import { useNodeSnap } from '@/renderer/extensions/vueNodes/composables/useNodeSnap'
|
import { useNodeSnap } from '@/renderer/extensions/vueNodes/composables/useNodeSnap'
|
||||||
import { useShiftKeySync } from '@/renderer/extensions/vueNodes/composables/useShiftKeySync'
|
import { useShiftKeySync } from '@/renderer/extensions/vueNodes/composables/useShiftKeySync'
|
||||||
|
import { nodesBeingResized } from '@/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking'
|
||||||
|
|
||||||
import type { ResizeHandleDirection } from './resizeMath'
|
import type { ResizeHandleDirection } from './resizeMath'
|
||||||
import { createResizeSession, toCanvasDelta } from './resizeMath'
|
import { createResizeSession, toCanvasDelta } from './resizeMath'
|
||||||
@@ -58,6 +59,11 @@ export function useNodeResize(
|
|||||||
const nodeElement = target.closest('[data-node-id]')
|
const nodeElement = target.closest('[data-node-id]')
|
||||||
if (!(nodeElement instanceof HTMLElement)) return
|
if (!(nodeElement instanceof HTMLElement)) return
|
||||||
|
|
||||||
|
const nodeId = nodeElement.dataset.nodeId
|
||||||
|
if (nodeId) {
|
||||||
|
nodesBeingResized.add(nodeId)
|
||||||
|
}
|
||||||
|
|
||||||
const rect = nodeElement.getBoundingClientRect()
|
const rect = nodeElement.getBoundingClientRect()
|
||||||
const scale = transformState.camera.z
|
const scale = transformState.camera.z
|
||||||
|
|
||||||
@@ -74,6 +80,7 @@ export function useNodeResize(
|
|||||||
|
|
||||||
isResizing.value = true
|
isResizing.value = true
|
||||||
resizeStartPointer.value = { x: event.clientX, y: event.clientY }
|
resizeStartPointer.value = { x: event.clientX, y: event.clientY }
|
||||||
|
|
||||||
resizeSession.value = createResizeSession({
|
resizeSession.value = createResizeSession({
|
||||||
startSize,
|
startSize,
|
||||||
startPosition: { ...startPosition },
|
startPosition: { ...startPosition },
|
||||||
@@ -85,8 +92,9 @@ export function useNodeResize(
|
|||||||
!isResizing.value ||
|
!isResizing.value ||
|
||||||
!resizeStartPointer.value ||
|
!resizeStartPointer.value ||
|
||||||
!resizeSession.value
|
!resizeSession.value
|
||||||
)
|
) {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const startPointer = resizeStartPointer.value
|
const startPointer = resizeStartPointer.value
|
||||||
const session = resizeSession.value
|
const session = resizeSession.value
|
||||||
@@ -117,6 +125,11 @@ export function useNodeResize(
|
|||||||
// Stop tracking shift key state
|
// Stop tracking shift key state
|
||||||
stopShiftSync()
|
stopShiftSync()
|
||||||
|
|
||||||
|
// Allow ResizeObserver to update this node again
|
||||||
|
if (nodeId) {
|
||||||
|
nodesBeingResized.delete(nodeId)
|
||||||
|
}
|
||||||
|
|
||||||
target.releasePointerCapture(upEvent.pointerId)
|
target.releasePointerCapture(upEvent.pointerId)
|
||||||
stopMoveListen()
|
stopMoveListen()
|
||||||
stopUpListen()
|
stopUpListen()
|
||||||
|
|||||||
Reference in New Issue
Block a user