mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-02 14:27:40 +00:00
feat: node resize less than min content
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="editable-text">
|
||||
<span v-if="!isEditing">
|
||||
<div class="editable-text min-w-0">
|
||||
<span v-if="!isEditing" class="block truncate">
|
||||
{{ modelValue }}
|
||||
</span>
|
||||
<!-- Avoid double triggering finishEditing event when keyup.enter is triggered -->
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
|
||||
<!-- Node Body - rendered based on LOD level and collapsed state -->
|
||||
<div
|
||||
class="flex min-h-0 flex-1 flex-col gap-4 pb-4"
|
||||
class="flex min-h-0 flex-1 flex-col gap-4 overflow-hidden pb-4"
|
||||
:data-testid="`node-body-${nodeData.id}`"
|
||||
>
|
||||
<!-- Slots only rendered at full detail -->
|
||||
@@ -266,14 +266,15 @@ const handleContextMenu = (event: MouseEvent) => {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// Set initial DOM size from layout store, but respect intrinsic content minimum
|
||||
// Set initial DOM size from layout store, respecting intrinsic minimum for initial render
|
||||
// Note: Once manually resized, users can go smaller than intrinsic size for responsive behavior
|
||||
if (size.value && nodeContainerRef.value && transformState) {
|
||||
const intrinsicMin = calculateIntrinsicSize(
|
||||
nodeContainerRef.value,
|
||||
transformState.camera.z
|
||||
)
|
||||
|
||||
// Use the larger of stored size or intrinsic minimum
|
||||
// Use the larger of stored size or intrinsic minimum for initial render
|
||||
const finalWidth = Math.max(size.value.width, intrinsicMin.width)
|
||||
const finalHeight = Math.max(size.value.height, intrinsicMin.height)
|
||||
|
||||
@@ -405,3 +406,10 @@ const nodeMedia = computed(() => {
|
||||
|
||||
const nodeContainerRef = ref<HTMLDivElement>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.lg-node {
|
||||
/* Minimum width to ensure widgets have enough space for responsive truncation */
|
||||
min-width: 200px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
:data-testid="`node-header-${nodeData?.id || ''}`"
|
||||
@dblclick="handleDoubleClick"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-2.5">
|
||||
<div class="flex min-w-0 items-center justify-between gap-2.5">
|
||||
<!-- Collapse/Expand Button -->
|
||||
<div class="relative flex items-center gap-2.5">
|
||||
<div class="relative flex min-w-0 flex-1 items-center gap-2.5">
|
||||
<div class="lod-toggle flex shrink-0 items-center px-0.5">
|
||||
<IconButton
|
||||
size="fit-content"
|
||||
@@ -40,7 +40,7 @@
|
||||
<!-- Node Title -->
|
||||
<div
|
||||
v-tooltip.top="tooltipConfig"
|
||||
class="lod-toggle flex flex-1 items-center gap-2 truncate text-sm font-bold"
|
||||
class="lod-toggle flex min-w-0 flex-1 items-center gap-2 truncate text-sm font-bold"
|
||||
data-testid="node-title"
|
||||
>
|
||||
<EditableText
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
v-else
|
||||
:class="
|
||||
cn(
|
||||
'lg-node-widgets flex flex-col gap-2 pr-3',
|
||||
'lg-node-widgets flex flex-col gap-2 pr-3 min-w-0',
|
||||
shouldHandleNodePointerEvents
|
||||
? 'pointer-events-auto'
|
||||
: 'pointer-events-none'
|
||||
@@ -19,7 +19,7 @@
|
||||
<div
|
||||
v-for="(widget, index) in processedWidgets"
|
||||
:key="`widget-${index}-${widget.name}`"
|
||||
class="lg-widget-container group flex items-center"
|
||||
class="lg-widget-container group flex min-w-0 items-center"
|
||||
>
|
||||
<!-- Widget Input Slot Dot -->
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
:widget="widget.simplified"
|
||||
:model-value="widget.value"
|
||||
:node-id="nodeData?.id != null ? String(nodeData.id) : ''"
|
||||
class="flex-1"
|
||||
class="min-w-0 flex-1"
|
||||
@update:model-value="widget.updateHandler"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -36,7 +36,7 @@ export function useNodeResize(
|
||||
const isResizing = ref(false)
|
||||
const resizeStartPos = ref<Position | null>(null)
|
||||
const resizeStartSize = ref<Size | null>(null)
|
||||
const intrinsicMinSize = ref<Size | null>(null)
|
||||
const intrinsicMinHeight = ref<number | null>(null)
|
||||
|
||||
// Snap-to-grid functionality
|
||||
const { shouldSnap, applySnapToSize } = useNodeSnap()
|
||||
@@ -60,7 +60,7 @@ export function useNodeResize(
|
||||
isResizing.value = true
|
||||
resizeStartPos.value = { x: event.clientX, y: event.clientY }
|
||||
|
||||
// Get current node size from the DOM and calculate intrinsic min size
|
||||
// Get current node size from the DOM
|
||||
const nodeElement = target.closest('[data-node-id]')
|
||||
if (!(nodeElement instanceof HTMLElement)) return
|
||||
|
||||
@@ -73,15 +73,16 @@ export function useNodeResize(
|
||||
height: rect.height / scale
|
||||
}
|
||||
|
||||
// Calculate intrinsic content size (minimum based on content)
|
||||
intrinsicMinSize.value = calculateIntrinsicSize(nodeElement, scale)
|
||||
// Calculate intrinsic height to prevent resizing shorter than content
|
||||
const intrinsicSize = calculateIntrinsicSize(nodeElement, scale)
|
||||
intrinsicMinHeight.value = intrinsicSize.height
|
||||
|
||||
const handlePointerMove = (moveEvent: PointerEvent) => {
|
||||
if (
|
||||
!isResizing.value ||
|
||||
!resizeStartPos.value ||
|
||||
!resizeStartSize.value ||
|
||||
!intrinsicMinSize.value
|
||||
intrinsicMinHeight.value === null
|
||||
)
|
||||
return
|
||||
|
||||
@@ -93,16 +94,16 @@ export function useNodeResize(
|
||||
const scaledDx = dx / scale
|
||||
const scaledDy = dy / scale
|
||||
|
||||
// Apply constraints: only minimum size based on content, no maximum
|
||||
// Calculate new size
|
||||
const newWidth = resizeStartSize.value.width + scaledDx
|
||||
const newHeight = resizeStartSize.value.height + scaledDy
|
||||
|
||||
// Apply constraints:
|
||||
// - Width: No constraint - let CSS handle minimum via widget min-width
|
||||
// - Height: Respect intrinsic height to keep all content visible
|
||||
const constrainedSize = {
|
||||
width: Math.max(
|
||||
intrinsicMinSize.value.width,
|
||||
resizeStartSize.value.width + scaledDx
|
||||
),
|
||||
height: Math.max(
|
||||
intrinsicMinSize.value.height,
|
||||
resizeStartSize.value.height + scaledDy
|
||||
)
|
||||
width: newWidth,
|
||||
height: Math.max(intrinsicMinHeight.value, newHeight)
|
||||
}
|
||||
|
||||
// Apply snap-to-grid if shift is held or always snap is enabled
|
||||
@@ -122,7 +123,7 @@ export function useNodeResize(
|
||||
isResizing.value = false
|
||||
resizeStartPos.value = null
|
||||
resizeStartSize.value = null
|
||||
intrinsicMinSize.value = null
|
||||
intrinsicMinHeight.value = null
|
||||
|
||||
// Stop tracking shift key state
|
||||
stopShiftSync()
|
||||
|
||||
@@ -16,9 +16,12 @@
|
||||
}"
|
||||
@update:model-value="onPickerUpdate"
|
||||
/>
|
||||
<span class="text-xs" data-testid="widget-color-text">{{
|
||||
toHexFromFormat(localValue, format)
|
||||
}}</span>
|
||||
<span
|
||||
class="min-w-0 truncate text-xs"
|
||||
style="min-width: 3ch"
|
||||
data-testid="widget-color-text"
|
||||
>{{ toHexFromFormat(localValue, format) }}</span
|
||||
>
|
||||
</label>
|
||||
</WidgetLayoutField>
|
||||
</template>
|
||||
|
||||
@@ -114,6 +114,10 @@ const inputNumberPt = useNumberWidgetButtonPt({
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.p-inputnumber) {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
:deep(.p-inputnumber-input) {
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--node-stroke);
|
||||
@@ -122,6 +126,8 @@ const inputNumberPt = useNumberWidgetButtonPt({
|
||||
height: 1.625rem;
|
||||
margin: 1px 0;
|
||||
box-shadow: none;
|
||||
min-width: 3ch;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
:deep(.p-inputnumber-button.p-disabled .pi),
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
<InputText
|
||||
v-model="localValue"
|
||||
v-bind="filteredProps"
|
||||
:class="cn(WidgetInputBaseClass, 'w-full text-xs py-2 px-4')"
|
||||
:class="cn(WidgetInputBaseClass, 'w-full text-xs py-2 px-4 min-w-0')"
|
||||
:aria-label="widget.name"
|
||||
size="small"
|
||||
style="min-width: 3ch"
|
||||
@update:model-value="onChange"
|
||||
/>
|
||||
</WidgetLayoutField>
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
v-model="localValue"
|
||||
:options="multiSelectOptions"
|
||||
v-bind="combinedProps"
|
||||
class="w-full text-xs"
|
||||
class="w-full min-w-0 text-xs"
|
||||
:aria-label="widget.name"
|
||||
size="small"
|
||||
display="chip"
|
||||
:pt="{
|
||||
option: 'text-xs'
|
||||
option: 'text-xs',
|
||||
label: 'truncate min-w-0',
|
||||
root: 'min-w-0'
|
||||
}"
|
||||
style="min-width: 3ch"
|
||||
@update:model-value="onChange"
|
||||
/>
|
||||
</WidgetLayoutField>
|
||||
@@ -72,3 +75,16 @@ const multiSelectOptions = computed((): T[] => {
|
||||
return []
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.p-multiselect) {
|
||||
min-width: 3ch !important;
|
||||
}
|
||||
|
||||
:deep(.p-multiselect-label) {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,12 +4,15 @@
|
||||
v-model="localValue"
|
||||
:options="selectOptions"
|
||||
v-bind="combinedProps"
|
||||
:class="cn(WidgetInputBaseClass, 'w-full text-xs')"
|
||||
:class="cn(WidgetInputBaseClass, 'w-full text-xs truncate min-w-0')"
|
||||
:aria-label="widget.name"
|
||||
size="small"
|
||||
:pt="{
|
||||
option: 'text-xs'
|
||||
option: 'text-xs',
|
||||
label: 'truncate min-w-0',
|
||||
root: 'min-w-0'
|
||||
}"
|
||||
style="min-width: 3ch"
|
||||
data-capture-wheel="true"
|
||||
@update:model-value="onChange"
|
||||
/>
|
||||
@@ -68,3 +71,16 @@ const selectOptions = computed(() => {
|
||||
return []
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.p-select) {
|
||||
min-width: 3ch !important;
|
||||
}
|
||||
|
||||
:deep(.p-select-label) {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,21 +11,19 @@ defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex h-[30px] items-center justify-between gap-2 overscroll-contain"
|
||||
>
|
||||
<div class="relative flex h-6 items-center">
|
||||
<div class="flex h-[30px] min-w-0 items-center justify-between gap-2">
|
||||
<div class="relative flex h-6 min-w-0 items-center" style="min-width: 3ch">
|
||||
<p
|
||||
v-if="widget.name"
|
||||
class="lod-toggle w-28 flex-1 truncate text-sm font-normal text-node-component-slot-text"
|
||||
class="lod-toggle w-16 min-w-0 truncate text-sm font-normal text-node-component-slot-text"
|
||||
>
|
||||
{{ widget.label || widget.name }}
|
||||
</p>
|
||||
<LODFallback />
|
||||
</div>
|
||||
<div class="relative">
|
||||
<div class="relative min-w-0 flex-1" style="min-width: 3ch">
|
||||
<div
|
||||
class="lod-toggle w-75 cursor-default"
|
||||
class="lod-toggle min-w-0 cursor-default"
|
||||
@pointerdown.stop="noop"
|
||||
@pointermove.stop="noop"
|
||||
@pointerup.stop="noop"
|
||||
|
||||
Reference in New Issue
Block a user