mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-05 13:10:24 +00:00
fix resizing from top
This commit is contained in:
@@ -352,22 +352,57 @@ const cornerResizeHandles: CornerResizeHandle[] = [
|
||||
|
||||
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
|
||||
|
||||
// Clamp width to minimum to avoid conflicts with CSS min-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-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 deltaX = Math.abs(result.position.x - currentPosition.x)
|
||||
const deltaY = Math.abs(result.position.y - currentPosition.y)
|
||||
const newX = widthHitFloor ? currentPosition.x : result.position.x
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -60,6 +61,10 @@ export function useNodePointerInteractions(
|
||||
startDrag(event, nodeId)
|
||||
}
|
||||
|
||||
function clearStartPosition() {
|
||||
startPosition.value = null
|
||||
}
|
||||
|
||||
function onPointermove(event: PointerEvent) {
|
||||
if (forwardMiddlePointerIfNeeded(event)) return
|
||||
|
||||
@@ -72,6 +77,13 @@ export function useNodePointerInteractions(
|
||||
const multiSelect = isMultiSelectKey(event)
|
||||
|
||||
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) {
|
||||
layoutStore.isDraggingVueNodes.value = true
|
||||
handleNodeSelect(event, nodeId)
|
||||
@@ -122,6 +134,7 @@ export function useNodePointerInteractions(
|
||||
|
||||
if (wasDragging) {
|
||||
safeDragEnd(event)
|
||||
clearStartPosition()
|
||||
return
|
||||
}
|
||||
const multiSelect = isMultiSelectKey(event)
|
||||
@@ -130,6 +143,8 @@ export function useNodePointerInteractions(
|
||||
if (nodeId) {
|
||||
toggleNodeSelectionAfterPointerUp(nodeId, multiSelect)
|
||||
}
|
||||
|
||||
clearStartPosition()
|
||||
}
|
||||
|
||||
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 { 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'
|
||||
|
||||
/**
|
||||
@@ -110,17 +113,28 @@ const resizeObserver = new ResizeObserver((entries) => {
|
||||
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)
|
||||
if (!updates) {
|
||||
updates = []
|
||||
updatesByType.set(elementType, updates)
|
||||
}
|
||||
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)
|
||||
@@ -128,7 +142,9 @@ const resizeObserver = new ResizeObserver((entries) => {
|
||||
// Flush per-type
|
||||
for (const [type, updates] of updatesByType) {
|
||||
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
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ref } from 'vue'
|
||||
import type { Point, Size } from '@/renderer/core/layout/types'
|
||||
import { useNodeSnap } from '@/renderer/extensions/vueNodes/composables/useNodeSnap'
|
||||
import { useShiftKeySync } from '@/renderer/extensions/vueNodes/composables/useShiftKeySync'
|
||||
import { nodesBeingResized } from '@/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking'
|
||||
|
||||
import type { ResizeHandleDirection } from './resizeMath'
|
||||
import { createResizeSession, toCanvasDelta } from './resizeMath'
|
||||
@@ -58,6 +59,11 @@ export function useNodeResize(
|
||||
const nodeElement = target.closest('[data-node-id]')
|
||||
if (!(nodeElement instanceof HTMLElement)) return
|
||||
|
||||
const nodeId = nodeElement.dataset.nodeId
|
||||
if (nodeId) {
|
||||
nodesBeingResized.add(nodeId)
|
||||
}
|
||||
|
||||
const rect = nodeElement.getBoundingClientRect()
|
||||
const scale = transformState.camera.z
|
||||
|
||||
@@ -74,6 +80,7 @@ export function useNodeResize(
|
||||
|
||||
isResizing.value = true
|
||||
resizeStartPointer.value = { x: event.clientX, y: event.clientY }
|
||||
|
||||
resizeSession.value = createResizeSession({
|
||||
startSize,
|
||||
startPosition: { ...startPosition },
|
||||
@@ -85,8 +92,9 @@ export function useNodeResize(
|
||||
!isResizing.value ||
|
||||
!resizeStartPointer.value ||
|
||||
!resizeSession.value
|
||||
)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const startPointer = resizeStartPointer.value
|
||||
const session = resizeSession.value
|
||||
@@ -117,6 +125,11 @@ export function useNodeResize(
|
||||
// Stop tracking shift key state
|
||||
stopShiftSync()
|
||||
|
||||
// Allow ResizeObserver to update this node again
|
||||
if (nodeId) {
|
||||
nodesBeingResized.delete(nodeId)
|
||||
}
|
||||
|
||||
target.releasePointerCapture(upEvent.pointerId)
|
||||
stopMoveListen()
|
||||
stopUpListen()
|
||||
|
||||
Reference in New Issue
Block a user