mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 13:12:12 -03:00
fix(autocomplete): add wheel event handler for canvas zoom support
Add @wheel event listener to AutocompleteTextWidget textarea to enable canvas zoom when textarea has no scrollbar. The onWheel handler: - Forwards pinch-to-zoom (ctrl+wheel) to canvas - Passes horizontal scroll to canvas - When textarea has vertical scrollbar: lets textarea scroll - When textarea has NO scrollbar: forwards to canvas for zoom Behavior now matches ComfyUI built-in multiline widget. Fixes #850
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
:spellcheck="spellcheck ?? false"
|
||||
:class="['text-input', { 'vue-dom-mode': isVueDomMode }]"
|
||||
@input="onInput"
|
||||
@wheel="onWheel"
|
||||
/>
|
||||
<button
|
||||
v-if="showClearButton"
|
||||
@@ -82,6 +83,59 @@ const onInput = () => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle mouse wheel events on the textarea.
|
||||
* Forwards the event to the ComfyUI canvas for zooming when the textarea has no scrollbar,
|
||||
* or handles pinch-to-zoom gestures.
|
||||
*
|
||||
* Logic aligns with ComfyUI's built-in multiline widget:
|
||||
* src/renderer/extensions/vueNodes/widgets/composables/useStringWidget.ts
|
||||
*/
|
||||
const onWheel = (event: WheelEvent) => {
|
||||
const textarea = textareaRef.value
|
||||
if (!textarea) return
|
||||
|
||||
// Track if we have a vertical scrollbar
|
||||
const canScrollY = textarea.scrollHeight > textarea.clientHeight
|
||||
const deltaX = event.deltaX
|
||||
const deltaY = event.deltaY
|
||||
const isHorizontal = Math.abs(deltaX) > Math.abs(deltaY)
|
||||
|
||||
// Access ComfyUI app from global window
|
||||
const app = (window as any).app
|
||||
if (!app || !app.canvas || typeof app.canvas.processMouseWheel !== 'function') {
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Handle pinch-to-zoom (ctrlKey is true for pinch-to-zoom on most browsers)
|
||||
if (event.ctrlKey) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
app.canvas.processMouseWheel(event)
|
||||
return
|
||||
}
|
||||
|
||||
// 2. Horizontal scroll: pass to canvas (textareas usually don't scroll horizontally)
|
||||
if (isHorizontal) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
app.canvas.processMouseWheel(event)
|
||||
return
|
||||
}
|
||||
|
||||
// 3. Vertical scrolling:
|
||||
if (canScrollY) {
|
||||
// If the textarea is scrollable, let it handle the wheel event but stop propagation
|
||||
// to prevent the canvas from zooming while the user is trying to scroll the text
|
||||
event.stopPropagation()
|
||||
} else {
|
||||
// If the textarea is NOT scrollable, forward the wheel event to the canvas
|
||||
// so it can trigger zoom in/out
|
||||
event.preventDefault()
|
||||
app.canvas.processMouseWheel(event)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle external value changes (e.g., from "send lora to workflow")
|
||||
const onExternalValueChange = (event: CustomEvent<{ value: string }>) => {
|
||||
updateHasTextState()
|
||||
|
||||
@@ -1988,14 +1988,14 @@ to { transform: rotate(360deg);
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.autocomplete-text-widget[data-v-7fb96eed] {
|
||||
.autocomplete-text-widget[data-v-b3b00fdd] {
|
||||
background: transparent;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.input-wrapper[data-v-7fb96eed] {
|
||||
.input-wrapper[data-v-b3b00fdd] {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
@@ -2003,7 +2003,7 @@ to { transform: rotate(360deg);
|
||||
}
|
||||
|
||||
/* Canvas mode styles (default) - matches built-in comfy-multiline-input */
|
||||
.text-input[data-v-7fb96eed] {
|
||||
.text-input[data-v-b3b00fdd] {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
background-color: var(--comfy-input-bg, #222);
|
||||
@@ -2020,7 +2020,7 @@ to { transform: rotate(360deg);
|
||||
}
|
||||
|
||||
/* Vue DOM mode styles - matches built-in p-textarea in Vue DOM mode */
|
||||
.text-input.vue-dom-mode[data-v-7fb96eed] {
|
||||
.text-input.vue-dom-mode[data-v-b3b00fdd] {
|
||||
background-color: var(--color-charcoal-400, #313235);
|
||||
color: #fff;
|
||||
padding: 8px 12px 30px 12px; /* Reserve bottom space for clear button */
|
||||
@@ -2029,12 +2029,12 @@ to { transform: rotate(360deg);
|
||||
font-size: 12px;
|
||||
font-family: inherit;
|
||||
}
|
||||
.text-input[data-v-7fb96eed]:focus {
|
||||
.text-input[data-v-b3b00fdd]:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Clear button styles */
|
||||
.clear-button[data-v-7fb96eed] {
|
||||
.clear-button[data-v-b3b00fdd] {
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
bottom: 6px; /* Changed from top to bottom */
|
||||
@@ -2057,31 +2057,31 @@ to { transform: rotate(360deg);
|
||||
}
|
||||
|
||||
/* Show clear button when hovering over input wrapper */
|
||||
.input-wrapper:hover .clear-button[data-v-7fb96eed] {
|
||||
.input-wrapper:hover .clear-button[data-v-b3b00fdd] {
|
||||
opacity: 0.7;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.clear-button[data-v-7fb96eed]:hover {
|
||||
.clear-button[data-v-b3b00fdd]:hover {
|
||||
opacity: 1;
|
||||
background: rgba(255, 100, 100, 0.8);
|
||||
}
|
||||
.clear-button svg[data-v-7fb96eed] {
|
||||
.clear-button svg[data-v-b3b00fdd] {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
/* Vue DOM mode adjustments for clear button */
|
||||
.text-input.vue-dom-mode ~ .clear-button[data-v-7fb96eed] {
|
||||
.text-input.vue-dom-mode ~ .clear-button[data-v-b3b00fdd] {
|
||||
right: 8px;
|
||||
bottom: 10px; /* Changed from top to bottom, adjusted for Vue DOM padding */
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: rgba(107, 114, 128, 0.6);
|
||||
}
|
||||
.text-input.vue-dom-mode ~ .clear-button[data-v-7fb96eed]:hover {
|
||||
.text-input.vue-dom-mode ~ .clear-button[data-v-b3b00fdd]:hover {
|
||||
background: oklch(62% 0.18 25);
|
||||
}
|
||||
.text-input.vue-dom-mode ~ .clear-button svg[data-v-7fb96eed] {
|
||||
.text-input.vue-dom-mode ~ .clear-button svg[data-v-b3b00fdd] {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}`));
|
||||
@@ -14239,6 +14239,36 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
||||
props.widget.callback(textareaRef.value.value);
|
||||
}
|
||||
};
|
||||
const onWheel = (event) => {
|
||||
const textarea = textareaRef.value;
|
||||
if (!textarea) return;
|
||||
const canScrollY = textarea.scrollHeight > textarea.clientHeight;
|
||||
const deltaX = event.deltaX;
|
||||
const deltaY = event.deltaY;
|
||||
const isHorizontal = Math.abs(deltaX) > Math.abs(deltaY);
|
||||
const app2 = window.app;
|
||||
if (!app2 || !app2.canvas || typeof app2.canvas.processMouseWheel !== "function") {
|
||||
return;
|
||||
}
|
||||
if (event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
app2.canvas.processMouseWheel(event);
|
||||
return;
|
||||
}
|
||||
if (isHorizontal) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
app2.canvas.processMouseWheel(event);
|
||||
return;
|
||||
}
|
||||
if (canScrollY) {
|
||||
event.stopPropagation();
|
||||
} else {
|
||||
event.preventDefault();
|
||||
app2.canvas.processMouseWheel(event);
|
||||
}
|
||||
};
|
||||
const onExternalValueChange = (event) => {
|
||||
updateHasTextState();
|
||||
};
|
||||
@@ -14297,7 +14327,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
||||
placeholder: __props.placeholder,
|
||||
spellcheck: __props.spellcheck ?? false,
|
||||
class: normalizeClass(["text-input", { "vue-dom-mode": isVueDomMode.value }]),
|
||||
onInput
|
||||
onInput,
|
||||
onWheel
|
||||
}, null, 42, _hoisted_3),
|
||||
showClearButton.value ? (openBlock(), createElementBlock("button", {
|
||||
key: 0,
|
||||
@@ -14331,7 +14362,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
||||
};
|
||||
}
|
||||
});
|
||||
const AutocompleteTextWidget = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-7fb96eed"]]);
|
||||
const AutocompleteTextWidget = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-b3b00fdd"]]);
|
||||
const LORA_PROVIDER_NODE_TYPES$1 = [
|
||||
"Lora Stacker (LoraManager)",
|
||||
"Lora Randomizer (LoraManager)",
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user