Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/components/graph/widgets/TextPreviewWidget.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<template>
<div
class="relative max-h-[200px] min-h-[28px] w-full overflow-y-auto rounded-lg px-4 py-2 text-xs"
@wheel="canvasInteractions.handleWheel"
@pointerdown="canvasInteractions.handlePointer"
>
Comment on lines 2 to 6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for useCanvasInteractions implementation
fd . --type=f -e ts -e tsx | xargs grep -l "useCanvasInteractions\|export.*useCanvasInteractions" | head -20

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 600


🏁 Script executed:

#!/bin/bash
# Find the TextPreviewWidget component file
find . -name "TextPreviewWidget.vue" -type f

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 122


🏁 Script executed:

#!/bin/bash
# Explore composables directory structure
fd . --type=f -e ts -path "*/composables/*" | head -30

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 238


🏁 Script executed:

#!/bin/bash
# Read the useCanvasInteractions composable
cat -n src/renderer/core/canvas/useCanvasInteractions.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 5069


🏁 Script executed:

#!/bin/bash
# Read the TextPreviewWidget component
cat -n src/components/graph/widgets/TextPreviewWidget.vue

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 3749


In legacy navigation mode, wheel events are unconditionally forwarded to canvas, blocking local scroll despite overflow-y-auto.

The handleWheel function forwards all wheel events to the canvas in legacy mode (line 50-52), calling preventDefault() unconditionally. Since TextPreviewWidget doesn't use the [data-capture-wheel="true"] opt-out attribute, users cannot scroll long previews in legacy mode. Text selection/copy are unaffected (pointer events allow normal selection unless space+dragging).

Consider either: (1) adding data-capture-wheel="true" to the preview div to preserve scroll in all modes, or (2) adjusting canvas wheel handling to not block preview scrolling.

🤖 Prompt for AI Agents
In @src/components/graph/widgets/TextPreviewWidget.vue around lines 2 - 6, The
preview div's wheel events are being unconditionally forwarded to the canvas via
canvasInteractions.handleWheel in legacy navigation mode, which prevents
vertical scrolling of long previews; to fix this either add the opt-out
attribute data-capture-wheel="true" to the TextPreviewWidget's root div so the
canvas handler will not capture wheel events for this element, or modify the
canvasInteractions.handleWheel implementation to detect and ignore wheel events
coming from elements inside TextPreviewWidget (e.g., by checking event.target or
walking up DOM for an element with a specific class or attribute) so the div's
native overflow-y-auto scrolling still works.

<div class="flex items-center gap-2">
<div class="flex flex-1 items-center gap-2 break-all">
Expand All @@ -16,6 +18,7 @@ import Skeleton from 'primevue/skeleton'
import { computed, onMounted, ref, watch } from 'vue'

import type { NodeId } from '@/lib/litegraph/src/litegraph'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import { useExecutionStore } from '@/stores/executionStore'
import { linkifyHtml, nl2br } from '@/utils/formatUtil'

Expand All @@ -25,6 +28,7 @@ const props = defineProps<{
}>()

const executionStore = useExecutionStore()
const canvasInteractions = useCanvasInteractions()
const isParentNodeExecuting = ref(true)
const formattedText = computed(() => {
const src = modelValue.value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ function addMarkdownWidget(
inputEl.classList.add('comfy-markdown')
const textarea = document.createElement('textarea')
inputEl.append(textarea)
const editorDom: HTMLElement = editor.view.dom

const widget = node.addDOMWidget(name, 'MARKDOWN', inputEl, {
getValue(): string {
Expand Down Expand Up @@ -91,6 +92,42 @@ function addMarkdownWidget(
}
})

inputEl.addEventListener('wheel', (event: WheelEvent) => {
const deltaX = event.deltaX
const deltaY = event.deltaY

const canScrollYMarkdown = editorDom.scrollHeight > editorDom.clientHeight
const canScrollYTextarea = textarea.scrollHeight > textarea.clientHeight
const isHorizontal = Math.abs(deltaX) > Math.abs(deltaY)

// Prevent pinch zoom from zooming the page
if (event.ctrlKey) {
event.preventDefault()
event.stopPropagation()
app.canvas.processMouseWheel(event)
return
}

// When gestures disabled: horizontal always goes to canvas (no horizontal scroll in textarea)
if (isHorizontal) {
event.preventDefault()
event.stopPropagation()
app.canvas.processMouseWheel(event)
return
}

// Vertical scrolling when gestures disabled: let textarea scroll if scrollable
const isEditing = inputEl.classList.contains('editing')
if (isEditing ? canScrollYTextarea : canScrollYMarkdown) {
event.stopPropagation()
return
}

// If textarea can't scroll vertically, pass to canvas
event.preventDefault()
app.canvas.processMouseWheel(event)
})
Comment on lines +95 to +129

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider refactoring the wheel handler for better maintainability.

The wheel event handler implements sophisticated logic that handles pinch-zoom, trackpad detection, horizontal/vertical scrolling, and canvas delegation. While the logic appears correct, the 50-line handler with multiple conditional branches could benefit from extraction into smaller, testable helper functions.

Consider extracting decision logic into focused helper functions:

♻️ Suggested refactoring approach
// Helper functions for wheel event routing decisions
function shouldPreventPinchZoom(event: WheelEvent): boolean {
  return event.ctrlKey
}

function isLikelyTrackpadGesture(deltaX: number, deltaY: number): boolean {
  return Math.abs(deltaX) > 0 || Math.abs(deltaY) < TRACKPAD_DETECTION_THRESHOLD
}

function shouldRouteToCanvas(
  event: WheelEvent,
  gesturesEnabled: boolean,
  isEditing: boolean,
  canScrollYMarkdown: boolean,
  canScrollYTextarea: boolean
): { route: boolean; allowDefault: boolean } {
  const { deltaX, deltaY } = event
  const isHorizontal = Math.abs(deltaX) > Math.abs(deltaY)
  const isLikelyTrackpad = isLikelyTrackpadGesture(deltaX, deltaY)

  // Pinch zoom always goes to canvas
  if (event.ctrlKey) {
    return { route: true, allowDefault: false }
  }

  // Trackpad gestures when enabled
  if (gesturesEnabled && isLikelyTrackpad) {
    return { route: true, allowDefault: false }
  }

  // Horizontal scrolling
  if (isHorizontal) {
    return { route: true, allowDefault: false }
  }

  // Vertical: allow local scroll if possible
  const canScroll = isEditing ? canScrollYTextarea : canScrollYMarkdown
  if (canScroll) {
    return { route: false, allowDefault: true }
  }

  // Default: route to canvas
  return { route: true, allowDefault: false }
}

// Then in the event handler:
inputEl.addEventListener('wheel', (event: WheelEvent) => {
  const gesturesEnabled = useSettingStore().get('LiteGraph.Pointer.TrackpadGestures')
  const canScrollYMarkdown = editorDom.scrollHeight > editorDom.clientHeight
  const canScrollYTextarea = textarea.scrollHeight > textarea.clientHeight
  const isEditing = inputEl.classList.contains('editing')

  const { route, allowDefault } = shouldRouteToCanvas(
    event,
    gesturesEnabled,
    isEditing,
    canScrollYMarkdown,
    canScrollYTextarea
  )

  if (!allowDefault) {
    event.preventDefault()
  }

  event.stopPropagation()

  if (route) {
    app.canvas.processMouseWheel(event)
  }
})

Benefits:

  • Each helper function is testable in isolation
  • The main event handler reads more declaratively
  • Edge cases are easier to identify and fix
  • Reduced cognitive complexity
🤖 Prompt for AI Agents
In @src/renderer/extensions/vueNodes/widgets/composables/useMarkdownWidget.ts
around lines 98 - 149, The wheel handler attached in inputEl.addEventListener is
large and should be split: extract shouldPreventPinchZoom(event: WheelEvent),
isLikelyTrackpadGesture(deltaX, deltaY) (using TRACKPAD_DETECTION_THRESHOLD),
and a shouldRouteToCanvas(event, gesturesEnabled, isEditing, canScrollYMarkdown,
canScrollYTextarea) that returns { route, allowDefault }; then replace the
inline logic with calls to these helpers and centralize
event.preventDefault()/event.stopPropagation() and the call to
app.canvas.processMouseWheel(event) based on the helpers' result so the main
handler is declarative and each helper is small and testable.


return widget
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import { app } from '@/scripts/app'
import type { ComfyWidgetConstructorV2 } from '@/scripts/widgets'

const TRACKPAD_DETECTION_THRESHOLD = 50

function addMultilineWidget(
node: LGraphNode,
name: string,
Expand Down Expand Up @@ -54,9 +52,6 @@ function addMultilineWidget(
})

inputEl.addEventListener('wheel', (event: WheelEvent) => {
const gesturesEnabled = useSettingStore().get(
'LiteGraph.Pointer.TrackpadGestures'
)
const deltaX = event.deltaX
const deltaY = event.deltaY

Expand All @@ -71,20 +66,6 @@ function addMultilineWidget(
return
}

// Detect if this is likely a trackpad gesture vs mouse wheel
// Trackpads usually have deltaX or smaller deltaY values (< TRACKPAD_DETECTION_THRESHOLD)
// Mouse wheels typically have larger discrete deltaY values (>= TRACKPAD_DETECTION_THRESHOLD)
const isLikelyTrackpad =
Math.abs(deltaX) > 0 || Math.abs(deltaY) < TRACKPAD_DETECTION_THRESHOLD

// Trackpad gestures: when enabled, trackpad panning goes to canvas
if (gesturesEnabled && isLikelyTrackpad) {
event.preventDefault()
event.stopPropagation()
app.canvas.processMouseWheel(event)
return
}

// When gestures disabled: horizontal always goes to canvas (no horizontal scroll in textarea)
if (isHorizontal) {
event.preventDefault()
Expand Down