mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-28 18:22:40 +00:00
allow Vue nodes to be resized from all 4 corners (#6187)
## Summary Enables Vue nodes to resize from all four corners and consolidated the interaction pipeline. ## Changes - **What**: Added four-corner handles to `LGraphNode`, wired them through the refactored `useNodeResize` composable, and centralized the math/preset helpers under `interactions/resize/` with cleaner pure functions and lint-compliant markup. ## Review Focus Corner-to-corner resizing accuracy (position + size), pinned-node guard preventing resize start, and snap-to-grid behavior at varied zoom levels. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6187-allow-Vue-nodes-to-be-resized-from-all-4-corners-2936d73d365081c8bf14e944ab24c27f) by [Unito](https://www.unito.io) --------- Co-authored-by: DrJKL <DrJKL@users.noreply.github.com> Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
:class="
|
||||
cn(
|
||||
'bg-node-component-surface',
|
||||
'lg-node absolute rounded-2xl touch-none flex flex-col',
|
||||
'lg-node absolute rounded-2xl touch-none flex flex-col group',
|
||||
'border-1 border-solid border-node-component-border',
|
||||
// hover (only when node should handle events)
|
||||
shouldHandleNodePointerEvents &&
|
||||
@@ -107,12 +107,17 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Resize handle -->
|
||||
<div
|
||||
v-if="!isCollapsed"
|
||||
class="absolute right-0 bottom-0 h-3 w-3 cursor-se-resize opacity-0 transition-opacity duration-200 hover:bg-white hover:opacity-20"
|
||||
@pointerdown.stop="startResize"
|
||||
/>
|
||||
<!-- Resize handles -->
|
||||
<template v-if="!isCollapsed">
|
||||
<div
|
||||
v-for="handle in cornerResizeHandles"
|
||||
:key="handle.id"
|
||||
role="button"
|
||||
:aria-label="handle.ariaLabel"
|
||||
:class="cn(baseResizeHandleClasses, handle.classes)"
|
||||
@pointerdown.stop="handleResizePointerDown(handle.direction)($event)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -120,6 +125,7 @@
|
||||
import { whenever } from '@vueuse/core'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, inject, onErrorCaptured, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import { toggleNodeOptions } from '@/composables/graph/useMoreOptionsMenu'
|
||||
@@ -146,7 +152,8 @@ import {
|
||||
} from '@/utils/graphTraversalUtil'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
import { useNodeResize } from '../composables/useNodeResize'
|
||||
import type { ResizeHandleDirection } from '../interactions/resize/resizeMath'
|
||||
import { useNodeResize } from '../interactions/resize/useNodeResize'
|
||||
import { calculateIntrinsicSize } from '../utils/calculateIntrinsicSize'
|
||||
import LivePreview from './LivePreview.vue'
|
||||
import NodeContent from './NodeContent.vue'
|
||||
@@ -164,6 +171,8 @@ interface LGraphNodeProps {
|
||||
|
||||
const { nodeData, error = null } = defineProps<LGraphNodeProps>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const {
|
||||
handleNodeCollapse,
|
||||
handleNodeTitleUpdate,
|
||||
@@ -242,8 +251,7 @@ onErrorCaptured((error) => {
|
||||
return false // Prevent error propagation
|
||||
})
|
||||
|
||||
// Use layout system for node position and dragging
|
||||
const { position, size, zIndex } = useNodeLayout(() => nodeData.id)
|
||||
const { position, size, zIndex, moveNodeTo } = useNodeLayout(() => nodeData.id)
|
||||
const { pointerHandlers, isDragging, dragStyle } = useNodePointerInteractions(
|
||||
() => nodeData,
|
||||
handleNodeSelect
|
||||
@@ -281,19 +289,73 @@ onMounted(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const baseResizeHandleClasses =
|
||||
'absolute h-3 w-3 opacity-0 pointer-events-auto focus-visible:outline focus-visible:outline-2 focus-visible:outline-white/40'
|
||||
const POSITION_EPSILON = 0.01
|
||||
|
||||
type CornerResizeHandle = {
|
||||
id: string
|
||||
direction: ResizeHandleDirection
|
||||
classes: string
|
||||
ariaLabel: string
|
||||
}
|
||||
|
||||
const cornerResizeHandles: CornerResizeHandle[] = [
|
||||
{
|
||||
id: 'se',
|
||||
direction: { horizontal: 'right', vertical: 'bottom' },
|
||||
classes: 'right-0 bottom-0 cursor-se-resize',
|
||||
ariaLabel: t('g.resizeFromBottomRight')
|
||||
},
|
||||
{
|
||||
id: 'ne',
|
||||
direction: { horizontal: 'right', vertical: 'top' },
|
||||
classes: 'right-0 top-0 cursor-ne-resize',
|
||||
ariaLabel: t('g.resizeFromTopRight')
|
||||
},
|
||||
{
|
||||
id: 'sw',
|
||||
direction: { horizontal: 'left', vertical: 'bottom' },
|
||||
classes: 'left-0 bottom-0 cursor-sw-resize',
|
||||
ariaLabel: t('g.resizeFromBottomLeft')
|
||||
},
|
||||
{
|
||||
id: 'nw',
|
||||
direction: { horizontal: 'left', vertical: 'top' },
|
||||
classes: 'left-0 top-0 cursor-nw-resize',
|
||||
ariaLabel: t('g.resizeFromTopLeft')
|
||||
}
|
||||
]
|
||||
|
||||
const { startResize } = useNodeResize(
|
||||
(newSize, element) => {
|
||||
// Apply size directly to DOM element - ResizeObserver will pick this up
|
||||
(result, element) => {
|
||||
if (isCollapsed.value) return
|
||||
|
||||
element.style.width = `${newSize.width}px`
|
||||
element.style.height = `${newSize.height}px`
|
||||
// Apply size directly to DOM element - ResizeObserver will pick this up
|
||||
element.style.width = `${result.size.width}px`
|
||||
element.style.height = `${result.size.height}px`
|
||||
|
||||
const currentPosition = position.value
|
||||
const deltaX = Math.abs(result.position.x - currentPosition.x)
|
||||
const deltaY = Math.abs(result.position.y - currentPosition.y)
|
||||
|
||||
if (deltaX > POSITION_EPSILON || deltaY > POSITION_EPSILON) {
|
||||
moveNodeTo(result.position)
|
||||
}
|
||||
},
|
||||
{
|
||||
transformState
|
||||
}
|
||||
)
|
||||
|
||||
const handleResizePointerDown = (direction: ResizeHandleDirection) => {
|
||||
return (event: PointerEvent) => {
|
||||
if (nodeData.flags?.pinned) return
|
||||
|
||||
startResize(event, direction, { ...position.value })
|
||||
}
|
||||
}
|
||||
|
||||
whenever(isCollapsed, () => {
|
||||
const element = nodeContainerRef.value
|
||||
if (!element) return
|
||||
|
||||
Reference in New Issue
Block a user