mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-02 06:19:58 +00:00
Forward scroll unless focused (#6597)
## Summary Forward wheel events to the canvas unless a wheel-capturing element is focused, and ensure the Load3D scene becomes focusable on pointer interaction so its wheel zoom/pan works after the user clicks into it. ## Changes - **What**: gate wheel forwarding on focused capture elements; focus the Load3D scene container on pointerdown to opt into wheel capture. - **Dependencies**: none ## Review Focus - Validate wheel forwarding behavior across focusable inputs vs. non-focusable capture zones. - Confirm Load3D zoom/pan only captures wheel after a user click (canvas pan should still work when merely hovering). ## Screenshots (if applicable) N/A --------- Co-authored-by: Christian Byrne <cbyrne@comfy.org> Co-authored-by: Subagent 5 <subagent@example.com> Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
ref="container"
|
||||
class="relative h-full w-full min-h-[200px]"
|
||||
data-capture-wheel="true"
|
||||
@pointerdown.stop
|
||||
tabindex="-1"
|
||||
@pointerdown.stop="focusContainer"
|
||||
@pointermove.stop
|
||||
@pointerup.stop
|
||||
@mousedown.stop
|
||||
@@ -45,6 +46,10 @@ const props = defineProps<{
|
||||
|
||||
const container = ref<HTMLElement | null>(null)
|
||||
|
||||
function focusContainer() {
|
||||
container.value?.focus()
|
||||
}
|
||||
|
||||
const { isDragging, dragMessage, handleDragOver, handleDragLeave, handleDrop } =
|
||||
useLoad3dDrag({
|
||||
onModelDrop: async (file) => {
|
||||
|
||||
@@ -155,5 +155,72 @@ describe('useCanvasInteractions', () => {
|
||||
expect(mockEvent.preventDefault).not.toHaveBeenCalled()
|
||||
expect(mockEvent.stopPropagation).not.toHaveBeenCalled()
|
||||
})
|
||||
it('should forward wheel events to canvas when capture element is NOT focused', () => {
|
||||
const { get } = useSettingStore()
|
||||
vi.mocked(get).mockReturnValue('legacy')
|
||||
|
||||
const captureElement = document.createElement('div')
|
||||
captureElement.setAttribute('data-capture-wheel', 'true')
|
||||
const textarea = document.createElement('textarea')
|
||||
captureElement.appendChild(textarea)
|
||||
document.body.appendChild(captureElement)
|
||||
|
||||
const { handleWheel } = useCanvasInteractions()
|
||||
const mockEvent = createMockWheelEvent()
|
||||
Object.defineProperty(mockEvent, 'target', { value: textarea })
|
||||
|
||||
handleWheel(mockEvent)
|
||||
|
||||
expect(mockEvent.preventDefault).toHaveBeenCalled()
|
||||
expect(mockEvent.stopPropagation).toHaveBeenCalled()
|
||||
|
||||
document.body.removeChild(captureElement)
|
||||
})
|
||||
|
||||
it('should NOT forward wheel events when capture element IS focused', () => {
|
||||
const { get } = useSettingStore()
|
||||
vi.mocked(get).mockReturnValue('legacy')
|
||||
|
||||
const captureElement = document.createElement('div')
|
||||
captureElement.setAttribute('data-capture-wheel', 'true')
|
||||
const textarea = document.createElement('textarea')
|
||||
captureElement.appendChild(textarea)
|
||||
document.body.appendChild(captureElement)
|
||||
textarea.focus()
|
||||
|
||||
const { handleWheel } = useCanvasInteractions()
|
||||
const mockEvent = createMockWheelEvent()
|
||||
Object.defineProperty(mockEvent, 'target', { value: textarea })
|
||||
|
||||
handleWheel(mockEvent)
|
||||
|
||||
expect(mockEvent.preventDefault).not.toHaveBeenCalled()
|
||||
expect(mockEvent.stopPropagation).not.toHaveBeenCalled()
|
||||
|
||||
document.body.removeChild(captureElement)
|
||||
})
|
||||
|
||||
it('should forward ctrl+wheel to canvas when capture element IS focused in standard mode', () => {
|
||||
const { get } = useSettingStore()
|
||||
vi.mocked(get).mockReturnValue('standard')
|
||||
|
||||
const captureElement = document.createElement('div')
|
||||
captureElement.setAttribute('data-capture-wheel', 'true')
|
||||
const textarea = document.createElement('textarea')
|
||||
captureElement.appendChild(textarea)
|
||||
document.body.appendChild(captureElement)
|
||||
textarea.focus()
|
||||
|
||||
const { handleWheel } = useCanvasInteractions()
|
||||
const mockEvent = createMockWheelEvent(true)
|
||||
Object.defineProperty(mockEvent, 'target', { value: textarea })
|
||||
|
||||
handleWheel(mockEvent)
|
||||
|
||||
expect(mockEvent.preventDefault).toHaveBeenCalled()
|
||||
expect(mockEvent.stopPropagation).toHaveBeenCalled()
|
||||
|
||||
document.body.removeChild(captureElement)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -26,19 +26,31 @@ export function useCanvasInteractions() {
|
||||
() => !(canvasStore.canvas?.read_only ?? false)
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns true if the wheel event target is inside an element that should
|
||||
* capture wheel events AND that element (or a descendant) currently has focus.
|
||||
*
|
||||
* This allows two-finger panning to continue over inputs until the user has
|
||||
* actively focused the widget, at which point the widget can consume scroll.
|
||||
*/
|
||||
const wheelCapturedByFocusedElement = (event: WheelEvent): boolean => {
|
||||
const target = event.target as HTMLElement | null
|
||||
const captureElement = target?.closest('[data-capture-wheel="true"]')
|
||||
const active = document.activeElement as Element | null
|
||||
|
||||
return !!(captureElement && active && captureElement.contains(active))
|
||||
}
|
||||
|
||||
const shouldForwardWheelEvent = (event: WheelEvent): boolean =>
|
||||
!wheelCapturedByFocusedElement(event) ||
|
||||
(isStandardNavMode.value && (event.ctrlKey || event.metaKey))
|
||||
|
||||
/**
|
||||
* Handles wheel events from UI components that should be forwarded to canvas
|
||||
* when appropriate (e.g., Ctrl+wheel for zoom in standard mode)
|
||||
*/
|
||||
const handleWheel = (event: WheelEvent) => {
|
||||
// Check if the wheel event is from an element that wants to capture wheel events
|
||||
const target = event.target as HTMLElement
|
||||
const captureElement = target?.closest('[data-capture-wheel="true"]')
|
||||
|
||||
if (captureElement) {
|
||||
// Element wants to capture wheel events, don't forward to canvas
|
||||
return
|
||||
}
|
||||
if (!shouldForwardWheelEvent(event)) return
|
||||
|
||||
// In standard mode, Ctrl+wheel should go to canvas for zoom
|
||||
if (isStandardNavMode.value && (event.ctrlKey || event.metaKey)) {
|
||||
@@ -87,14 +99,8 @@ export function useCanvasInteractions() {
|
||||
const forwardEventToCanvas = (
|
||||
event: WheelEvent | PointerEvent | MouseEvent
|
||||
) => {
|
||||
// Check if the wheel event is from an element that wants to capture wheel events
|
||||
const target = event.target as HTMLElement
|
||||
const captureElement = target?.closest('[data-capture-wheel="true"]')
|
||||
|
||||
if (captureElement) {
|
||||
// Element wants to capture wheel events, don't forward to canvas
|
||||
return
|
||||
}
|
||||
// Honor wheel capture only when the element is focused
|
||||
if (event instanceof WheelEvent && !shouldForwardWheelEvent(event)) return
|
||||
|
||||
const canvasEl = app.canvas?.canvas
|
||||
if (!canvasEl) return
|
||||
|
||||
Reference in New Issue
Block a user