From e3c812367e02cfb44641aaed5cdfeb04d4887557 Mon Sep 17 00:00:00 2001 From: Will Miao Date: Mon, 8 Jun 2026 16:19:08 +0800 Subject: [PATCH] fix(ui): cap lora widget height and enable wheel scroll in Node 2.0 mode (#959) - Add 'Node 2.0: Maximum visible LoRA entries' setting (default 12) - Apply max-height to loras container in Vue mode to prevent unbounded growth - Add enableListWheelScroll: window capture-phase wheel hook so scroll inside the widget scrolls the list instead of zooming the canvas --- web/comfyui/loras_widget.js | 18 +++++++++++++-- web/comfyui/settings.js | 43 +++++++++++++++++++++++++++++++++++ web/comfyui/utils.js | 45 +++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 2 deletions(-) diff --git a/web/comfyui/loras_widget.js b/web/comfyui/loras_widget.js index a6db8a52..bbabb1c8 100644 --- a/web/comfyui/loras_widget.js +++ b/web/comfyui/loras_widget.js @@ -11,10 +11,10 @@ import { EMPTY_CONTAINER_HEIGHT } from "./loras_widget_utils.js"; import { initDrag, createContextMenu, initHeaderDrag, initReorderDrag, handleKeyboardNavigation } from "./loras_widget_events.js"; -import { forwardMiddleMouseToCanvas, forwardWheelToCanvas } from "./utils.js"; +import { forwardMiddleMouseToCanvas, forwardWheelToCanvas, enableListWheelScroll } from "./utils.js"; import { PreviewTooltip } from "./preview_tooltip.js"; import { ensureLmStyles } from "./lm_styles_loader.js"; -import { getStrengthStepPreference } from "./settings.js"; +import { getStrengthStepPreference, getLoraWidgetMaxVisibleLoras } from "./settings.js"; export function addLorasWidget(node, name, opts, callback) { ensureLmStyles(); @@ -29,6 +29,20 @@ export function addLorasWidget(node, name, opts, callback) { // Set initial height using CSS variables approach const defaultHeight = 200; + // In Vue/node-2.0 mode, cap the widget height so it shows at most N entries. + // This prevents content from driving the node size beyond the cap. + // canvas/legacy mode is unaffected. + if (typeof LiteGraph !== 'undefined' && LiteGraph.vueNodesMode) { + const maxLoras = getLoraWidgetMaxVisibleLoras(); + const gap = 5; // flex gap from .lm-loras-container CSS + const maxH = CONTAINER_PADDING + HEADER_HEIGHT + maxLoras * LORA_ENTRY_HEIGHT + maxLoras * gap; + container.style.maxHeight = `${maxH}px`; + container.style.setProperty('--comfy-widget-max-height', `${maxH}px`); + // Window capture-phase hook: scroll the widget instead of zooming the canvas + // when the wheel is over a scrollable loras list. + enableListWheelScroll(container); + } + // Check if this is a randomizer node (lock button instead of drag handle) const isRandomizerNode = opts?.isRandomizerNode === true; diff --git a/web/comfyui/settings.js b/web/comfyui/settings.js index fedd4957..05b1f63d 100644 --- a/web/comfyui/settings.js +++ b/web/comfyui/settings.js @@ -39,6 +39,9 @@ const NEW_TAB_ZOOM_LEVEL = 0.8; const STRENGTH_STEP_SETTING_ID = "loramanager.strength_step"; const STRENGTH_STEP_DEFAULT = 0.05; +const LORA_WIDGET_MAX_VISIBLE_SETTING_ID = "loramanager.lora_widget_max_visible_loras"; +const LORA_WIDGET_MAX_VISIBLE_DEFAULT = 12; + // ============================================================================ // Helper Functions // ============================================================================ @@ -360,6 +363,32 @@ const getStrengthStepPreference = (() => { }; })(); +const getLoraWidgetMaxVisibleLoras = (() => { + let settingsUnavailableLogged = false; + + return () => { + const settingManager = app?.extensionManager?.setting; + if (!settingManager || typeof settingManager.get !== "function") { + if (!settingsUnavailableLogged) { + console.warn("LoRA Manager: settings API unavailable, using default max visible loras."); + settingsUnavailableLogged = true; + } + return LORA_WIDGET_MAX_VISIBLE_DEFAULT; + } + + try { + const value = settingManager.get(LORA_WIDGET_MAX_VISIBLE_SETTING_ID); + return value ?? LORA_WIDGET_MAX_VISIBLE_DEFAULT; + } catch (error) { + if (!settingsUnavailableLogged) { + console.warn("LoRA Manager: unable to read max visible loras setting, using default.", error); + settingsUnavailableLogged = true; + } + return LORA_WIDGET_MAX_VISIBLE_DEFAULT; + } + }; +})(); + // ============================================================================ // Register Extension with All Settings // ============================================================================ @@ -463,6 +492,19 @@ app.registerExtension({ tooltip: "Step size for adjusting LoRA strength via arrow buttons or keyboard (default: 0.05)", category: ["LoRA Manager", "LoRA Widget", "Strength Step"], }, + { + id: LORA_WIDGET_MAX_VISIBLE_SETTING_ID, + name: "Node 2.0: Maximum visible LoRA entries", + type: "slider", + attrs: { + min: 3, + max: 50, + step: 1, + }, + defaultValue: LORA_WIDGET_MAX_VISIBLE_DEFAULT, + tooltip: "When using Node 2.0 rendering, limit the loras widget height to show at most this many entries (default: 12). Excess entries are accessible via scrollbar.", + category: ["LoRA Manager", "LoRA Widget", "Max Visible"], + }, ], async setup() { await loadWorkflowOptions(); @@ -549,4 +591,5 @@ export { getUsageStatisticsPreference, getNewTabTemplatePreference, getStrengthStepPreference, + getLoraWidgetMaxVisibleLoras, }; diff --git a/web/comfyui/utils.js b/web/comfyui/utils.js index 34d3acb1..367812ae 100644 --- a/web/comfyui/utils.js +++ b/web/comfyui/utils.js @@ -784,6 +784,51 @@ export function forwardWheelToCanvas(container, options = {}) { }, { passive: false }); } +// Marks scrollable containers whose wheel scrolling must win over canvas zoom. +const LM_WHEEL_CLASS = 'lm-wheel-scrollable'; +let lmWheelHookInstalled = false; + +/** + * Keep vertical wheel scrolling inside a scrollable widget container, even in + * Nodes 2.0 / Vue mode where ComfyUI's wheel→zoom handler runs on the document + * in the capture phase (outer than any container-level listener). + * Installs a single capture-phase hook on `window` (the outermost dispatch + * point). When the wheel is over a marked, scrollable element, we manually + * scroll it and fully consume the event so canvas zoom never sees it. + */ +export function enableListWheelScroll(container) { + if (!container) return; + container.classList.add(LM_WHEEL_CLASS); + + if (lmWheelHookInstalled) return; + lmWheelHookInstalled = true; + + window.addEventListener('wheel', (event) => { + // Let pinch/zoom and horizontal gestures pass through. + if (event.ctrlKey) return; + if (Math.abs(event.deltaX) > Math.abs(event.deltaY)) return; + + const target = event.target; + if (!target || typeof target.closest !== 'function') return; + const el = target.closest(`.${LM_WHEEL_CLASS}`); + if (!el) return; + + const canScrollY = el.scrollHeight > el.clientHeight + 1; + if (!canScrollY) return; + + // Translate deltaMode to approximate pixels. + const unit = event.deltaMode === 1 ? 16 + : event.deltaMode === 2 ? el.clientHeight + : 1; + + el.scrollTop += event.deltaY * unit; + + event.preventDefault(); + event.stopPropagation(); + event.stopImmediatePropagation(); + }, { capture: true, passive: false }); +} + // Get connected Lora Pool node from pool_config input export function getConnectedPoolConfigNode(node) { if (!node?.inputs) {