mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
feat: add clear button to autocomplete text widget and fix external value change sync
- Add clear button inside autocomplete text widget that shows when text exists - Support both Canvas mode and Vue DOM mode with appropriate styling - Fix clear button visibility when value is changed externally (e.g., via 'send lora to workflow') - Implement dual notification mechanism: CustomEvent + onSetValue callback - Update widget interface to include onSetValue property
This commit is contained in:
@@ -1,17 +1,31 @@
|
||||
<template>
|
||||
<div class="autocomplete-text-widget">
|
||||
<textarea
|
||||
ref="textareaRef"
|
||||
:placeholder="placeholder"
|
||||
:spellcheck="spellcheck ?? false"
|
||||
:class="['text-input', { 'vue-dom-mode': isVueDomMode }]"
|
||||
@input="onInput"
|
||||
/>
|
||||
<div class="input-wrapper">
|
||||
<textarea
|
||||
ref="textareaRef"
|
||||
:placeholder="placeholder"
|
||||
:spellcheck="spellcheck ?? false"
|
||||
:class="['text-input', { 'vue-dom-mode': isVueDomMode }]"
|
||||
@input="onInput"
|
||||
/>
|
||||
<button
|
||||
v-if="showClearButton"
|
||||
type="button"
|
||||
class="clear-button"
|
||||
title="Clear text"
|
||||
@click="clearText"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18" />
|
||||
<line x1="6" y1="6" x2="18" y2="18" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||
import { useAutocomplete } from '@/composables/useAutocomplete'
|
||||
|
||||
// Access LiteGraph global for initial mode detection
|
||||
@@ -20,6 +34,7 @@ declare const LiteGraph: { vueNodesMode?: boolean } | undefined
|
||||
export interface AutocompleteTextWidgetInterface {
|
||||
inputEl?: HTMLTextAreaElement
|
||||
callback?: (v: string) => void
|
||||
onSetValue?: (v: string) => void
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -41,6 +56,10 @@ const onModeChange = (event: Event) => {
|
||||
}
|
||||
|
||||
const textareaRef = ref<HTMLTextAreaElement | null>(null)
|
||||
const hasText = ref(false)
|
||||
|
||||
// Show clear button when there is text
|
||||
const showClearButton = computed(() => hasText.value)
|
||||
|
||||
// Initialize autocomplete with direct ref access
|
||||
useAutocomplete(
|
||||
@@ -49,17 +68,60 @@ useAutocomplete(
|
||||
{ showPreview: props.showPreview ?? true }
|
||||
)
|
||||
|
||||
const updateHasTextState = () => {
|
||||
hasText.value = textareaRef.value ? textareaRef.value.value.length > 0 : false
|
||||
}
|
||||
|
||||
const onInput = () => {
|
||||
// Update hasText state
|
||||
updateHasTextState()
|
||||
|
||||
// Call widget callback when text changes
|
||||
if (textareaRef.value && typeof props.widget.callback === 'function') {
|
||||
props.widget.callback(textareaRef.value.value)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle external value changes (e.g., from "send lora to workflow")
|
||||
const onExternalValueChange = (event: CustomEvent<{ value: string }>) => {
|
||||
updateHasTextState()
|
||||
}
|
||||
|
||||
// Setup widget.onSetValue callback for external value changes
|
||||
const setupWidgetOnSetValue = () => {
|
||||
if (props.widget) {
|
||||
props.widget.onSetValue = (value: string) => {
|
||||
// The DOM value is already set by setValue, just update our state
|
||||
hasText.value = value.length > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const clearText = () => {
|
||||
if (textareaRef.value) {
|
||||
textareaRef.value.value = ''
|
||||
hasText.value = false
|
||||
textareaRef.value.focus()
|
||||
|
||||
// Trigger callback with empty value
|
||||
if (typeof props.widget.callback === 'function') {
|
||||
props.widget.callback('')
|
||||
}
|
||||
|
||||
// Dispatch input event to ensure autocomplete handles the change
|
||||
textareaRef.value.dispatchEvent(new Event('input'))
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// Register textarea reference with widget
|
||||
if (textareaRef.value) {
|
||||
props.widget.inputEl = textareaRef.value
|
||||
// Initialize hasText state
|
||||
hasText.value = textareaRef.value.value.length > 0
|
||||
|
||||
// Listen for external value change events from setValue
|
||||
textareaRef.value.addEventListener('lora-manager:autocomplete-value-changed', onExternalValueChange as EventListener)
|
||||
}
|
||||
|
||||
// Setup callback for input changes
|
||||
@@ -67,6 +129,9 @@ onMounted(() => {
|
||||
props.widget.callback(textareaRef.value.value)
|
||||
}
|
||||
|
||||
// Setup widget.onSetValue callback
|
||||
setupWidgetOnSetValue()
|
||||
|
||||
// Listen for custom event dispatched by main.ts
|
||||
document.addEventListener('lora-manager:vue-mode-change', onModeChange)
|
||||
})
|
||||
@@ -76,6 +141,16 @@ onUnmounted(() => {
|
||||
if (props.widget.inputEl === textareaRef.value) {
|
||||
props.widget.inputEl = undefined
|
||||
}
|
||||
|
||||
// Remove external value change event listener
|
||||
if (textareaRef.value) {
|
||||
textareaRef.value.removeEventListener('lora-manager:autocomplete-value-changed', onExternalValueChange as EventListener)
|
||||
}
|
||||
|
||||
// Clean up onSetValue callback
|
||||
if (props.widget) {
|
||||
props.widget.onSetValue = undefined
|
||||
}
|
||||
|
||||
// Remove event listener
|
||||
document.removeEventListener('lora-manager:vue-mode-change', onModeChange)
|
||||
@@ -91,6 +166,13 @@ onUnmounted(() => {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Canvas mode styles (default) - matches built-in comfy-multiline-input */
|
||||
.text-input {
|
||||
flex: 1;
|
||||
@@ -122,4 +204,54 @@ onUnmounted(() => {
|
||||
.text-input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Clear button styles */
|
||||
.clear-button {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 4px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: rgba(128, 128, 128, 0.5);
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s ease, background-color 0.2s ease;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.clear-button:hover {
|
||||
opacity: 1;
|
||||
background: rgba(255, 100, 100, 0.8);
|
||||
}
|
||||
|
||||
.clear-button svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
/* Vue DOM mode adjustments for clear button */
|
||||
.text-input.vue-dom-mode ~ .clear-button {
|
||||
right: 8px;
|
||||
top: 8px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: rgba(107, 114, 128, 0.6);
|
||||
}
|
||||
|
||||
.text-input.vue-dom-mode ~ .clear-button:hover {
|
||||
background: oklch(62% 0.18 25);
|
||||
}
|
||||
|
||||
.text-input.vue-dom-mode ~ .clear-button svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -416,6 +416,14 @@ function createAutocompleteTextWidgetFactory(
|
||||
setValue(v: string) {
|
||||
if (widget.inputEl) {
|
||||
widget.inputEl.value = v ?? ''
|
||||
// Notify Vue component of value change via custom event
|
||||
widget.inputEl.dispatchEvent(new CustomEvent('lora-manager:autocomplete-value-changed', {
|
||||
detail: { value: v ?? '' }
|
||||
}))
|
||||
}
|
||||
// Also call onSetValue if defined (for Vue component integration)
|
||||
if (typeof widget.onSetValue === 'function') {
|
||||
widget.onSetValue(v ?? '')
|
||||
}
|
||||
},
|
||||
serialize: true,
|
||||
|
||||
Reference in New Issue
Block a user