diff --git a/src/composables/graph/useGraphNodeManager.ts b/src/composables/graph/useGraphNodeManager.ts index 2e9c54a2cf..24b2b1a64f 100644 --- a/src/composables/graph/useGraphNodeManager.ts +++ b/src/composables/graph/useGraphNodeManager.ts @@ -269,10 +269,13 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager { const updatedWidgets = currentData.widgets.map((w) => w.name === widgetName ? { ...w, value: validateWidgetValue(value) } : w ) - vueNodeData.set(nodeId, { + // Create a completely new object to ensure Vue reactivity triggers + const updatedData = { ...currentData, widgets: updatedWidgets - }) + } + + vueNodeData.set(nodeId, updatedData) } catch (error) { // Ignore widget update errors to prevent cascade failures } diff --git a/src/composables/graph/useWidgetValue.ts b/src/composables/graph/useWidgetValue.ts index 87658f83f7..20e018881a 100644 --- a/src/composables/graph/useWidgetValue.ts +++ b/src/composables/graph/useWidgetValue.ts @@ -2,16 +2,17 @@ * Composable for managing widget value synchronization between Vue and LiteGraph * Provides consistent pattern for immediate UI updates and LiteGraph callbacks */ -import { ref, watch } from 'vue' +import { computed, toValue, ref, watch } from 'vue' import type { Ref } from 'vue' import type { SimplifiedWidget, WidgetValue } from '@/types/simplifiedWidget' +import type { MaybeRefOrGetter } from '@vueuse/core' interface UseWidgetValueOptions { /** The widget configuration from LiteGraph */ widget: SimplifiedWidget - /** The current value from parent component */ - modelValue: T + /** The current value from parent component (can be a value or a getter function) */ + modelValue: MaybeRefOrGetter /** Default value if modelValue is null/undefined */ defaultValue: T /** Emit function from component setup */ @@ -46,8 +47,21 @@ export function useWidgetValue({ emit, transform }: UseWidgetValueOptions): UseWidgetValueReturn { - // Local value for immediate UI updates - const localValue = ref(modelValue ?? defaultValue) + // Ref for immediate UI feedback before value flows back through modelValue + const newProcessedValue = ref(null) + + // Computed that prefers the immediately processed value, then falls back to modelValue + const localValue = computed( + () => newProcessedValue.value ?? toValue(modelValue) ?? defaultValue + ) + + // Clear newProcessedValue when modelValue updates (allowing external changes to flow through) + watch( + () => toValue(modelValue), + () => { + newProcessedValue.value = null + } + ) // Handle user changes const onChange = (newValue: U) => { @@ -71,21 +85,13 @@ export function useWidgetValue({ } } - // 1. Update local state for immediate UI feedback - localValue.value = processedValue + // Set for immediate UI feedback + newProcessedValue.value = processedValue - // 2. Emit to parent component + // Emit to parent component emit('update:modelValue', processedValue) } - // Watch for external updates from LiteGraph - watch( - () => modelValue, - (newValue) => { - localValue.value = newValue ?? defaultValue - } - ) - return { localValue: localValue as Ref, onChange @@ -97,7 +103,7 @@ export function useWidgetValue({ */ export function useStringWidgetValue( widget: SimplifiedWidget, - modelValue: string, + modelValue: string | (() => string), emit: (event: 'update:modelValue', value: string) => void ) { return useWidgetValue({ @@ -114,7 +120,7 @@ export function useStringWidgetValue( */ export function useNumberWidgetValue( widget: SimplifiedWidget, - modelValue: number, + modelValue: number | (() => number), emit: (event: 'update:modelValue', value: number) => void ) { return useWidgetValue({ @@ -137,7 +143,7 @@ export function useNumberWidgetValue( */ export function useBooleanWidgetValue( widget: SimplifiedWidget, - modelValue: boolean, + modelValue: boolean | (() => boolean), emit: (event: 'update:modelValue', value: boolean) => void ) { return useWidgetValue({ diff --git a/src/extensions/core/uploadAudio.ts b/src/extensions/core/uploadAudio.ts index cd2ff97e1e..dfefc88a0f 100644 --- a/src/extensions/core/uploadAudio.ts +++ b/src/extensions/core/uploadAudio.ts @@ -58,6 +58,9 @@ async function uploadFile( getResourceURL(...splitFilePath(path)) ) audioWidget.value = path + + // Manually trigger the callback to update VueNodes + audioWidget.callback?.(path) } } else { useToastStore().addAlert(resp.status + ' - ' + resp.statusText) diff --git a/src/renderer/extensions/vueNodes/components/LGraphNode.vue b/src/renderer/extensions/vueNodes/components/LGraphNode.vue index 88d8cebce2..6f5b126dec 100644 --- a/src/renderer/extensions/vueNodes/components/LGraphNode.vue +++ b/src/renderer/extensions/vueNodes/components/LGraphNode.vue @@ -23,7 +23,8 @@ bypassed, 'before:rounded-2xl before:pointer-events-none before:absolute before:inset-0': muted, - 'will-change-transform': isDragging + 'will-change-transform': isDragging, + 'ring-4 ring-primary-500 bg-primary-500/10': isDraggingOver }, shouldHandleNodePointerEvents @@ -43,6 +44,9 @@ v-bind="pointerHandlers" @wheel="handleWheel" @contextmenu="handleContextMenu" + @dragover.prevent="handleDragOver" + @dragleave="handleDragLeave" + @drop="handleDrop" >
diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue index bb5accb81d..1d0b139ee7 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue @@ -44,7 +44,7 @@ const emit = defineEmits<{ const { localValue, onChange } = useWidgetValue({ widget: props.widget, - modelValue: props.modelValue, + modelValue: () => props.modelValue, defaultValue: props.widget.options?.values?.[0] || '', emit }) diff --git a/src/renderer/extensions/vueNodes/widgets/composables/useImageUploadWidget.ts b/src/renderer/extensions/vueNodes/widgets/composables/useImageUploadWidget.ts index c8c4f575f1..78f9d0c97d 100644 --- a/src/renderer/extensions/vueNodes/widgets/composables/useImageUploadWidget.ts +++ b/src/renderer/extensions/vueNodes/widgets/composables/useImageUploadWidget.ts @@ -78,9 +78,13 @@ export const useImageUploadWidget = () => { folder, onUploadComplete: (output) => { output.forEach((path) => addToComboValues(fileComboWidget, path)) + + // Create a NEW array to ensure Vue reactivity detects the change + const newValue = allow_batch ? [...output] : output[0] + // @ts-expect-error litegraph combo value type does not support arrays yet - fileComboWidget.value = output - fileComboWidget.callback?.(output) + fileComboWidget.value = newValue + fileComboWidget.callback?.(newValue) } })