Files
ComfyUI_frontend/src/renderer/extensions/vueNodes/components/InputSlot.vue
Simula_r 0cff8eb357 fix: arbitrary styles, min size <= content, ensure layout calc, trunc… (#6731)
## Summary

### Problem:
After [vue node compacting
PR](https://github.com/Comfy-Org/ComfyUI_frontend/pull/6687) the white
space within the node has been greatly reduced, lowering the min
intrinsic size, thus allowing us to reduce the amount we need to scale
up via ensureCorrectLayoutScale(), therefore increasing readability of
nodes. Great!

However, a side effect of reducing the scale factor means nodes with
larger min content will not be scaled up enough causing nodes to be too
large in many cases.

For example, if the min intrinsic width is very long due to input
length:
<img width="807" height="519" alt="image"
src="https://github.com/user-attachments/assets/a6ea3852-bed5-49b2-b10e-c2e65c6450b2"
/>

### Solution:
Allow for nodes to be resized less than their intrinsic min width. And
truncate widget inputs like many other node UIs do.

IMPORTANT: when a node is added via search or other, it will still get a
min size based on its intrinsic content it just wont be the min width!
So best of both worlds.

<img width="670" height="551" alt="image"
src="https://github.com/user-attachments/assets/f4f5ec8c-037e-472f-a5a1-d8a59a87c0b0"
/>


this means we choose a default min width and clamp resize to it. This
also means we have to remove the arbitrary min width values that were
sprinkled around the vue node widgets. They are not needed because
instead of min width, they can take up full width and inherit the sizing
from the node min width! This makes nodes like little browser windows
and widgets are just responsive elements with in. Much more natural imo.

### Bonus
- Set ensureCorrectLayouScale() to scale factor of 1.2 which means vue
nodes are now only being set 20% bigger than LG. That covers for the
height difference we cant change!
- Fix ensureCorrectLayouScale() to offset y position for groups / better
alignment
- Get rid of arbitrary inflexible min width like min-[417px] which
shouldnt have been used the first place
- Make Select and Input overlay portals width set to their content


## Changes

**What**: 
- Node resizing behavior
- Node widget min width
- Widget input and slot truncation
- Misc arbitrary styling that should have been fluid

## Screenshots (if applicable)


https://github.com/user-attachments/assets/3ea4b8fe-565a-47f7-b3ab-6cef56cecde5


https://github.com/user-attachments/assets/2fe1e1a0-a9dc-4000-b865-ce2d8c7f3606


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6731-fix-arbitrary-styles-min-size-content-ensure-layout-calc-trunc-2af6d73d365081eab507c2f1638a4194)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-18 13:52:23 -07:00

151 lines
4.3 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div v-if="renderError" class="node-error p-1 text-xs text-red-500"></div>
<div v-else v-tooltip.left="tooltipConfig" :class="slotWrapperClass">
<!-- Connection Dot -->
<SlotConnectionDot
ref="connectionDotRef"
:color="slotColor"
:class="cn('-translate-x-1/2', 'w-3', errorClassesDot)"
@pointerdown="onPointerDown"
/>
<!-- Slot Name -->
<div class="relative h-full flex items-center min-w-0">
<span
v-if="!dotOnly"
:class="cn('truncate text-xs font-normal lod-toggle', labelClasses)"
>
{{ slotData.localized_name || slotData.name || `Input ${index}` }}
</span>
<LODFallback />
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onErrorCaptured, ref, watchEffect } from 'vue'
import type { ComponentPublicInstance } from 'vue'
import { useErrorHandling } from '@/composables/useErrorHandling'
import { getSlotColor } from '@/constants/slotColors'
import type { INodeSlot } from '@/lib/litegraph/src/litegraph'
import { useSlotLinkDragUIState } from '@/renderer/core/canvas/links/slotLinkDragUIState'
import { getSlotKey } from '@/renderer/core/layout/slots/slotIdentifier'
import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips'
import { useSlotElementTracking } from '@/renderer/extensions/vueNodes/composables/useSlotElementTracking'
import { useSlotLinkInteraction } from '@/renderer/extensions/vueNodes/composables/useSlotLinkInteraction'
import { useExecutionStore } from '@/stores/executionStore'
import { cn } from '@/utils/tailwindUtil'
import LODFallback from './LODFallback.vue'
import SlotConnectionDot from './SlotConnectionDot.vue'
interface InputSlotProps {
nodeType?: string
nodeId?: string
slotData: INodeSlot
index: number
connected?: boolean
compatible?: boolean
dotOnly?: boolean
}
const props = defineProps<InputSlotProps>()
const executionStore = useExecutionStore()
const hasSlotError = computed(() => {
const nodeErrors = executionStore.lastNodeErrors?.[props.nodeId ?? '']
if (!nodeErrors) return false
const slotName = props.slotData.name
return nodeErrors.errors.some(
(error) => error.extra_info?.input_name === slotName
)
})
const errorClassesDot = computed(() => {
return hasSlotError.value
? 'ring-2 ring-error ring-offset-0 rounded-full'
: ''
})
const labelClasses = computed(() =>
hasSlotError.value
? 'text-error font-medium'
: 'text-node-component-slot-text'
)
const renderError = ref<string | null>(null)
const { toastErrorHandler } = useErrorHandling()
const { getInputSlotTooltip, createTooltipConfig } = useNodeTooltips(
props.nodeType || ''
)
const tooltipConfig = computed(() => {
const slotName = props.slotData.localized_name || props.slotData.name || ''
const tooltipText = getInputSlotTooltip(slotName)
const fallbackText = tooltipText || `Input: ${slotName}`
return createTooltipConfig(fallbackText)
})
onErrorCaptured((error) => {
renderError.value = error.message
toastErrorHandler(error)
return false
})
const slotColor = computed(() => {
if (hasSlotError.value) {
return 'var(--color-error)'
}
return getSlotColor(props.slotData.type)
})
const { state: dragState } = useSlotLinkDragUIState()
const slotKey = computed(() =>
getSlotKey(props.nodeId ?? '', props.index, true)
)
const shouldDim = computed(() => {
if (!dragState.active) return false
return !dragState.compatible.get(slotKey.value)
})
const slotWrapperClass = computed(() =>
cn(
'lg-slot lg-slot--input flex items-center group rounded-r-lg h-6',
'cursor-crosshair',
props.dotOnly ? 'lg-slot--dot-only' : 'pr-6',
{
'lg-slot--connected': props.connected,
'lg-slot--compatible': props.compatible,
'opacity-40': shouldDim.value
}
)
)
const connectionDotRef = ref<ComponentPublicInstance<{
slotElRef: HTMLElement | undefined
}> | null>(null)
const slotElRef = ref<HTMLElement | null>(null)
watchEffect(() => {
const el = connectionDotRef.value?.slotElRef
slotElRef.value = el || null
})
useSlotElementTracking({
nodeId: props.nodeId ?? '',
index: props.index,
type: 'input',
element: slotElRef
})
const { onPointerDown } = useSlotLinkInteraction({
nodeId: props.nodeId ?? '',
index: props.index,
type: 'input'
})
</script>