diff --git a/vue-widgets/src/components/AutocompleteTextWidget.vue b/vue-widgets/src/components/AutocompleteTextWidget.vue index a9010188..d1f6947c 100644 --- a/vue-widgets/src/components/AutocompleteTextWidget.vue +++ b/vue-widgets/src/components/AutocompleteTextWidget.vue @@ -200,6 +200,9 @@ const onWheel = (event: WheelEvent) => { // Handle external value changes (e.g., from "send lora to workflow") const onExternalValueChange = () => { + // The DOM value was set synchronously by the caller; the new content may + // have toggled the vertical scrollbar on/off. + updateVScrollbarWidth() updateHasTextState() } @@ -209,6 +212,9 @@ const setupWidgetOnSetValue = () => { props.widget.onSetValue = (value: string) => { // The DOM value is already set by setValue, just update our state hasText.value = value.length > 0 + // Programmatic sets can toggle the scrollbar; re-measure so the + // corner clear button stays clear of it. + updateVScrollbarWidth() } } } diff --git a/vue-widgets/tests/components/AutocompleteTextWidget.test.ts b/vue-widgets/tests/components/AutocompleteTextWidget.test.ts index 941cda0a..84aaee47 100644 --- a/vue-widgets/tests/components/AutocompleteTextWidget.test.ts +++ b/vue-widgets/tests/components/AutocompleteTextWidget.test.ts @@ -16,7 +16,7 @@ import AutocompleteTextWidget from '@/components/AutocompleteTextWidget.vue' function createMockWidget() { return { callback: vi.fn(), - onSetValue: undefined, + onSetValue: undefined as ((v: string) => void) | undefined, inputEl: undefined, metadataWidget: undefined, name: 'text', @@ -217,4 +217,65 @@ describe('AutocompleteTextWidget vertical scrollbar inset', () => { expect(wrapperEl.style.getPropertyValue('--lm-vscrollbar-width')).toBe('0px') }) + it('re-measures the inset when the value is set programmatically via onSetValue', async () => { + const { wrapper, widget } = mountWidget() + const textarea = wrapper.find('textarea').element as HTMLTextAreaElement + + // Start with no overflow + overrideTextareaMetrics(textarea, { + scrollHeight: 100, + clientHeight: 100, + offsetWidth: 320, + clientWidth: 320, + }) + await nextTick() + + // Simulate an external setValue (e.g. "send lora to workflow"): the DOM + // value is set by the caller and widget.onSetValue fires without an + // input event. Content now overflows → inset must be re-measured. + overrideTextareaMetrics(textarea, { + scrollHeight: 200, + clientHeight: 100, + offsetWidth: 320, + clientWidth: 305, // 15px scrollbar gutter + }) + if (!widget.onSetValue) throw new Error('onSetValue not installed by component') + widget.onSetValue('long content') + await nextTick() + + const wrapperEl = wrapper.find('.input-wrapper').element as HTMLElement + expect(wrapperEl.style.getPropertyValue('--lm-vscrollbar-width')).toBe('15px') + }) + + it('re-measures the inset on external value-change events', async () => { + const { wrapper } = mountWidget() + const textarea = wrapper.find('textarea').element as HTMLTextAreaElement + + // Start with no overflow + overrideTextareaMetrics(textarea, { + scrollHeight: 100, + clientHeight: 100, + offsetWidth: 320, + clientWidth: 320, + }) + await nextTick() + + // The lora-manager:autocomplete-value-changed event fires when the + // widget value is set externally; content now overflows → re-measure. + overrideTextareaMetrics(textarea, { + scrollHeight: 200, + clientHeight: 100, + offsetWidth: 320, + clientWidth: 305, // 15px scrollbar gutter + }) + textarea.dispatchEvent( + new CustomEvent('lora-manager:autocomplete-value-changed', { + detail: { value: 'long content' }, + }) + ) + await nextTick() + + const wrapperEl = wrapper.find('.input-wrapper').element as HTMLElement + expect(wrapperEl.style.getPropertyValue('--lm-vscrollbar-width')).toBe('15px') + }) })