From 30f9e3e2ec0367aa98ef6871b52bc757bcd7829a Mon Sep 17 00:00:00 2001 From: Will Miao Date: Mon, 3 Nov 2025 12:18:59 +0800 Subject: [PATCH] feat(loras): add drag event callbacks and preview suppression - Add onDragStart and onDragEnd callbacks to initDrag function - Implement preview suppression during and briefly after strength dragging - Clear preview timer on drag start/end to prevent tooltip conflicts - Update tests to verify drag callbacks are properly triggered This prevents tooltip previews from interfering with drag interactions and provides better control over drag lifecycle events. --- .../lorasWidgetEvents.interactions.test.js | 9 ++- web/comfyui/loras_widget.js | 61 +++++++++++++++++-- web/comfyui/loras_widget_events.js | 23 ++++++- 3 files changed, 83 insertions(+), 10 deletions(-) diff --git a/tests/frontend/components/lorasWidgetEvents.interactions.test.js b/tests/frontend/components/lorasWidgetEvents.interactions.test.js index 3a34faee..5c797f69 100644 --- a/tests/frontend/components/lorasWidgetEvents.interactions.test.js +++ b/tests/frontend/components/lorasWidgetEvents.interactions.test.js @@ -82,6 +82,8 @@ describe('LoRA widget drag interactions', () => { const module = await import(EVENTS_MODULE); const renderSpy = vi.fn(); const previewSpy = { hide: vi.fn() }; + const onDragStart = vi.fn(); + const onDragEnd = vi.fn(); const dragEl = document.createElement('div'); dragEl.className = 'lm-lora-entry'; @@ -92,10 +94,14 @@ describe('LoRA widget drag interactions', () => { callback: vi.fn(), }; - module.initDrag(dragEl, 'Test', widget, false, previewSpy, renderSpy); + module.initDrag(dragEl, 'Test', widget, false, previewSpy, renderSpy, { + onDragStart, + onDragEnd, + }); dragEl.dispatchEvent(new MouseEvent('mousedown', { clientX: 50, bubbles: true })); expect(document.body.classList.contains('lm-lora-strength-dragging')).toBe(true); + expect(onDragStart).toHaveBeenCalledTimes(1); document.dispatchEvent(new MouseEvent('mousemove', { clientX: 70, bubbles: true })); expect(renderSpy).toHaveBeenCalledWith(widget.value, widget); @@ -104,6 +110,7 @@ describe('LoRA widget drag interactions', () => { document.dispatchEvent(new MouseEvent('mouseup')); expect(document.body.classList.contains('lm-lora-strength-dragging')).toBe(false); + expect(onDragEnd).toHaveBeenCalledTimes(1); }); it('deletes the selected LoRA when backspace is pressed outside of strength inputs', async () => { diff --git a/web/comfyui/loras_widget.js b/web/comfyui/loras_widget.js index 32dc3cbc..af875a15 100644 --- a/web/comfyui/loras_widget.js +++ b/web/comfyui/loras_widget.js @@ -36,6 +36,28 @@ export function addLorasWidget(node, name, opts, callback) { // Selection state - only one LoRA can be selected at a time let selectedLora = null; let pendingFocusTarget = null; + + const PREVIEW_SUPPRESSION_AFTER_DRAG_MS = 500; + let strengthDragActive = false; + let lastStrengthDragEndAt = 0; + + const shouldSuppressPreview = () => { + if (strengthDragActive) { + return true; + } + return Date.now() - lastStrengthDragEndAt < PREVIEW_SUPPRESSION_AFTER_DRAG_MS; + }; + + const markStrengthDragStart = () => { + strengthDragActive = true; + previewTooltip.hide(); + }; + + const markStrengthDragEnd = () => { + strengthDragActive = false; + lastStrengthDragEndAt = Date.now(); + previewTooltip.hide(); + }; // Function to select a LoRA const selectLora = (loraName) => { @@ -259,23 +281,47 @@ export function addLorasWidget(node, name, opts, callback) { nameEl.className = "lm-lora-name"; // Move preview tooltip events to nameEl instead of loraEl - let previewTimer; // Timer for delayed preview - nameEl.addEventListener('mouseenter', async (e) => { + let previewTimer = null; // Timer for delayed preview + + const clearPreviewTimer = () => { + if (previewTimer) { + clearTimeout(previewTimer); + previewTimer = null; + } + }; + + nameEl.addEventListener('mouseenter', (e) => { e.stopPropagation(); - const rect = nameEl.getBoundingClientRect(); + if (shouldSuppressPreview()) { + return; + } previewTimer = setTimeout(async () => { + previewTimer = null; + if (shouldSuppressPreview()) { + return; + } + const rect = nameEl.getBoundingClientRect(); await previewTooltip.show(name, rect.right, rect.top); }, 400); // 400ms delay }); nameEl.addEventListener('mouseleave', (e) => { e.stopPropagation(); - clearTimeout(previewTimer); // Cancel if not triggered + clearPreviewTimer(); // Cancel if not triggered previewTooltip.hide(); }); // Initialize drag functionality for strength adjustment - initDrag(loraEl, name, widget, false, previewTooltip, renderLoras); + initDrag(loraEl, name, widget, false, previewTooltip, renderLoras, { + onDragStart: () => { + clearPreviewTimer(); + markStrengthDragStart(); + }, + onDragEnd: () => { + clearPreviewTimer(); + markStrengthDragEnd(); + } + }); // Add context menu event loraEl.addEventListener('contextmenu', (e) => { @@ -511,7 +557,10 @@ export function addLorasWidget(node, name, opts, callback) { clipEl.appendChild(clipStrengthControl); // Add drag functionality to clip entry - initDrag(clipEl, name, widget, true, previewTooltip, renderLoras); + initDrag(clipEl, name, widget, true, previewTooltip, renderLoras, { + onDragStart: markStrengthDragStart, + onDragEnd: markStrengthDragEnd + }); container.appendChild(clipEl); } diff --git a/web/comfyui/loras_widget_events.js b/web/comfyui/loras_widget_events.js index 4c074a26..97b246d8 100644 --- a/web/comfyui/loras_widget_events.js +++ b/web/comfyui/loras_widget_events.js @@ -97,10 +97,19 @@ export function handleAllStrengthsDrag(initialStrengths, initialX, event, widget } // Function to initialize drag operation -export function initDrag(dragEl, name, widget, isClipStrength = false, previewTooltip, renderFunction) { +export function initDrag( + dragEl, + name, + widget, + isClipStrength = false, + previewTooltip, + renderFunction, + dragCallbacks = {} +) { let isDragging = false; let initialX = 0; let initialStrength = 0; + const { onDragStart, onDragEnd } = dragCallbacks; // Create a drag handler dragEl.addEventListener('mousedown', (e) => { @@ -122,10 +131,14 @@ export function initDrag(dragEl, name, widget, isClipStrength = false, previewTo initialX = e.clientX; initialStrength = isClipStrength ? loraData.clipStrength : loraData.strength; isDragging = true; - + // Add class to body to enforce cursor style globally document.body.classList.add('lm-lora-strength-dragging'); - + + if (typeof onDragStart === 'function') { + onDragStart(); + } + // Prevent text selection during drag e.preventDefault(); }); @@ -154,6 +167,10 @@ export function initDrag(dragEl, name, widget, isClipStrength = false, previewTo isDragging = false; // Remove the class to restore normal cursor behavior document.body.classList.remove('lm-lora-strength-dragging'); + + if (typeof onDragEnd === 'function') { + onDragEnd(); + } } }); }