mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
fix: make sliders compatible with Vue DOM render mode
Add data-capture-wheel attribute to SingleSlider and DualRangeSlider components to prevent wheel events from being intercepted by the canvas in ComfyUI's new Vue DOM render mode. This allows mouse wheel to work for adjusting slider values while still enabling workflow zoom on non-interactive widget areas. Also update event handling to use pointer events with proper stop propagation and pointer capture for reliable drag operations in both rendering modes. Update development guide with Section 8 documenting Vue DOM render mode event handling patterns and best practices.
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="dual-range-slider" :class="{ disabled, 'is-dragging': dragging !== null, 'has-segments': scaleMode === 'segmented' && effectiveSegments.length > 0 }" @wheel="onWheel">
|
||||
<div class="dual-range-slider" :class="{ disabled, 'is-dragging': dragging !== null, 'has-segments': scaleMode === 'segmented' && effectiveSegments.length > 0 }" data-capture-wheel="true" @wheel="onWheel">
|
||||
<div class="slider-track" ref="trackEl">
|
||||
<!-- Background track -->
|
||||
<div class="slider-track__bg"></div>
|
||||
|
||||
|
||||
<!-- Segment backgrounds for segmented scale mode -->
|
||||
<template v-if="scaleMode === 'segmented' && effectiveSegments.length > 0">
|
||||
<div
|
||||
@@ -17,7 +17,7 @@
|
||||
:style="getSegmentStyle(seg, index)"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Active track (colored range between handles) -->
|
||||
<div
|
||||
class="slider-track__active"
|
||||
@@ -38,8 +38,10 @@
|
||||
<div
|
||||
class="slider-handle slider-handle--min"
|
||||
:style="{ left: minPercent + '%' }"
|
||||
@mousedown="startDrag('min', $event)"
|
||||
@touchstart="startDrag('min', $event)"
|
||||
@pointerdown.stop="startDrag('min', $event)"
|
||||
@pointermove.stop="onDrag"
|
||||
@pointerup.stop="stopDrag"
|
||||
@pointercancel.stop="stopDrag"
|
||||
>
|
||||
<div class="slider-handle__thumb"></div>
|
||||
<div class="slider-handle__value">{{ formatValue(valueMin) }}</div>
|
||||
@@ -48,8 +50,10 @@
|
||||
<div
|
||||
class="slider-handle slider-handle--max"
|
||||
:style="{ left: maxPercent + '%' }"
|
||||
@mousedown="startDrag('max', $event)"
|
||||
@touchstart="startDrag('max', $event)"
|
||||
@pointerdown.stop="startDrag('max', $event)"
|
||||
@pointermove.stop="onDrag"
|
||||
@pointerup.stop="stopDrag"
|
||||
@pointercancel.stop="stopDrag"
|
||||
>
|
||||
<div class="slider-handle__thumb"></div>
|
||||
<div class="slider-handle__value">{{ formatValue(valueMax) }}</div>
|
||||
@@ -58,7 +62,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onUnmounted } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
type ScaleMode = 'linear' | 'segmented'
|
||||
|
||||
@@ -92,6 +96,7 @@ const emit = defineEmits<{
|
||||
|
||||
const trackEl = ref<HTMLElement | null>(null)
|
||||
const dragging = ref<'min' | 'max' | null>(null)
|
||||
const activePointerId = ref<number | null>(null)
|
||||
|
||||
const effectiveSegments = computed<Segment[]>(() => {
|
||||
if (props.scaleMode === 'segmented' && props.segments.length > 0) {
|
||||
@@ -202,26 +207,34 @@ const snapToStep = (value: number, segmentMultiplier?: number): number => {
|
||||
return Math.max(props.min, Math.min(props.max, props.min + steps * effectiveStep))
|
||||
}
|
||||
|
||||
const startDrag = (handle: 'min' | 'max', event: MouseEvent | TouchEvent) => {
|
||||
const startDrag = (handle: 'min' | 'max', event: PointerEvent) => {
|
||||
if (props.disabled) return
|
||||
|
||||
event.preventDefault()
|
||||
dragging.value = handle
|
||||
event.stopPropagation()
|
||||
|
||||
document.addEventListener('mousemove', onDrag)
|
||||
document.addEventListener('mouseup', stopDrag)
|
||||
document.addEventListener('touchmove', onDrag, { passive: false })
|
||||
document.addEventListener('touchend', stopDrag)
|
||||
dragging.value = handle
|
||||
activePointerId.value = event.pointerId
|
||||
|
||||
// Capture pointer to receive all subsequent events regardless of stopPropagation
|
||||
const target = event.currentTarget as HTMLElement
|
||||
target.setPointerCapture(event.pointerId)
|
||||
|
||||
// Process initial position
|
||||
updateValue(event)
|
||||
}
|
||||
|
||||
const onDrag = (event: MouseEvent | TouchEvent) => {
|
||||
const onDrag = (event: PointerEvent) => {
|
||||
if (!dragging.value) return
|
||||
event.stopPropagation()
|
||||
updateValue(event)
|
||||
}
|
||||
|
||||
const updateValue = (event: PointerEvent) => {
|
||||
if (!trackEl.value || !dragging.value) return
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
const clientX = 'touches' in event ? event.touches[0].clientX : event.clientX
|
||||
const rect = trackEl.value.getBoundingClientRect()
|
||||
const percent = Math.max(0, Math.min(100, (clientX - rect.left) / rect.width * 100))
|
||||
const percent = Math.max(0, Math.min(100, (event.clientX - rect.left) / rect.width * 100))
|
||||
|
||||
const rawValue = percentToValue(percent)
|
||||
const multiplier = getSegmentStepMultiplier(rawValue)
|
||||
@@ -305,17 +318,21 @@ const onWheel = (event: WheelEvent) => {
|
||||
}
|
||||
}
|
||||
|
||||
const stopDrag = () => {
|
||||
dragging.value = null
|
||||
document.removeEventListener('mousemove', onDrag)
|
||||
document.removeEventListener('mouseup', stopDrag)
|
||||
document.removeEventListener('touchmove', onDrag)
|
||||
document.removeEventListener('touchend', stopDrag)
|
||||
}
|
||||
const stopDrag = (event?: PointerEvent) => {
|
||||
if (!dragging.value) return
|
||||
|
||||
onUnmounted(() => {
|
||||
stopDrag()
|
||||
})
|
||||
if (event) {
|
||||
event.stopPropagation()
|
||||
// Release pointer capture
|
||||
const target = event.currentTarget as HTMLElement
|
||||
if (activePointerId.value !== null) {
|
||||
target.releasePointerCapture(activePointerId.value)
|
||||
}
|
||||
}
|
||||
|
||||
dragging.value = null
|
||||
activePointerId.value = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -324,6 +341,8 @@ onUnmounted(() => {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
user-select: none;
|
||||
cursor: default !important;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.dual-range-slider.disabled {
|
||||
@@ -332,7 +351,7 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.dual-range-slider.is-dragging {
|
||||
cursor: grabbing;
|
||||
cursor: ew-resize !important;
|
||||
}
|
||||
|
||||
.slider-track {
|
||||
@@ -343,6 +362,7 @@ onUnmounted(() => {
|
||||
height: 4px;
|
||||
background: var(--comfy-input-bg, #333);
|
||||
border-radius: 2px;
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
.slider-track__bg {
|
||||
@@ -395,12 +415,9 @@ onUnmounted(() => {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
transform: translateX(-50%);
|
||||
cursor: grab;
|
||||
cursor: ew-resize !important;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.slider-handle:active {
|
||||
cursor: grabbing;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.slider-handle__thumb {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="single-slider" :class="{ disabled, 'is-dragging': dragging }" @wheel="onWheel">
|
||||
<div class="single-slider" :class="{ disabled, 'is-dragging': dragging }" data-capture-wheel="true" @wheel="onWheel">
|
||||
<div class="slider-track" ref="trackEl">
|
||||
<div class="slider-track__bg"></div>
|
||||
<div
|
||||
@@ -19,8 +19,10 @@
|
||||
<div
|
||||
class="slider-handle"
|
||||
:style="{ left: percent + '%' }"
|
||||
@mousedown="startDrag($event)"
|
||||
@touchstart="startDrag($event)"
|
||||
@pointerdown.stop="startDrag"
|
||||
@pointermove.stop="onDrag"
|
||||
@pointerup.stop="stopDrag"
|
||||
@pointercancel.stop="stopDrag"
|
||||
>
|
||||
<div class="slider-handle__thumb"></div>
|
||||
<div class="slider-handle__value">{{ formatValue(value) }}</div>
|
||||
@@ -29,7 +31,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onUnmounted } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
min: number
|
||||
@@ -48,6 +50,7 @@ const emit = defineEmits<{
|
||||
|
||||
const trackEl = ref<HTMLElement | null>(null)
|
||||
const dragging = ref(false)
|
||||
const activePointerId = ref<number | null>(null)
|
||||
|
||||
const percent = computed(() => {
|
||||
const range = props.max - props.min
|
||||
@@ -82,28 +85,34 @@ const snapToStep = (value: number): number => {
|
||||
return Math.max(props.min, Math.min(props.max, props.min + steps * props.step))
|
||||
}
|
||||
|
||||
const startDrag = (event: MouseEvent | TouchEvent) => {
|
||||
const startDrag = (event: PointerEvent) => {
|
||||
if (props.disabled) return
|
||||
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
dragging.value = true
|
||||
activePointerId.value = event.pointerId
|
||||
|
||||
document.addEventListener('mousemove', onDrag)
|
||||
document.addEventListener('mouseup', stopDrag)
|
||||
document.addEventListener('touchmove', onDrag, { passive: false })
|
||||
document.addEventListener('touchend', stopDrag)
|
||||
// Capture pointer to receive all subsequent events regardless of stopPropagation
|
||||
const target = event.currentTarget as HTMLElement
|
||||
target.setPointerCapture(event.pointerId)
|
||||
|
||||
onDrag(event)
|
||||
// Process initial position
|
||||
updateValue(event)
|
||||
}
|
||||
|
||||
const onDrag = (event: MouseEvent | TouchEvent) => {
|
||||
const onDrag = (event: PointerEvent) => {
|
||||
if (!dragging.value) return
|
||||
event.stopPropagation()
|
||||
updateValue(event)
|
||||
}
|
||||
|
||||
const updateValue = (event: PointerEvent) => {
|
||||
if (!trackEl.value || !dragging.value) return
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
const clientX = 'touches' in event ? event.touches[0].clientX : event.clientX
|
||||
const rect = trackEl.value.getBoundingClientRect()
|
||||
const percent = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width))
|
||||
const percent = Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width))
|
||||
const rawValue = props.min + percent * (props.max - props.min)
|
||||
const value = snapToStep(rawValue)
|
||||
|
||||
@@ -127,17 +136,21 @@ const onWheel = (event: WheelEvent) => {
|
||||
emit('update:value', newValue)
|
||||
}
|
||||
|
||||
const stopDrag = () => {
|
||||
dragging.value = false
|
||||
document.removeEventListener('mousemove', onDrag)
|
||||
document.removeEventListener('mouseup', stopDrag)
|
||||
document.removeEventListener('touchmove', onDrag)
|
||||
document.removeEventListener('touchend', stopDrag)
|
||||
}
|
||||
const stopDrag = (event?: PointerEvent) => {
|
||||
if (!dragging.value) return
|
||||
|
||||
onUnmounted(() => {
|
||||
stopDrag()
|
||||
})
|
||||
if (event) {
|
||||
event.stopPropagation()
|
||||
// Release pointer capture
|
||||
const target = event.currentTarget as HTMLElement
|
||||
if (activePointerId.value !== null) {
|
||||
target.releasePointerCapture(activePointerId.value)
|
||||
}
|
||||
}
|
||||
|
||||
dragging.value = false
|
||||
activePointerId.value = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -146,6 +159,8 @@ onUnmounted(() => {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
user-select: none;
|
||||
cursor: default !important;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.single-slider.disabled {
|
||||
@@ -154,7 +169,7 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.single-slider.is-dragging {
|
||||
cursor: grabbing;
|
||||
cursor: ew-resize !important;
|
||||
}
|
||||
|
||||
.slider-track {
|
||||
@@ -165,6 +180,7 @@ onUnmounted(() => {
|
||||
height: 4px;
|
||||
background: var(--comfy-input-bg, #333);
|
||||
border-radius: 2px;
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
.slider-track__bg {
|
||||
@@ -196,12 +212,9 @@ onUnmounted(() => {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
transform: translateX(-50%);
|
||||
cursor: grab;
|
||||
cursor: ew-resize !important;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.slider-handle:active {
|
||||
cursor: grabbing;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.slider-handle__thumb {
|
||||
|
||||
Reference in New Issue
Block a user