From 8e49adc3218a05cc5090995ab1169c0454813230 Mon Sep 17 00:00:00 2001 From: Kael Date: Wed, 3 Sep 2025 17:37:33 +1000 Subject: [PATCH 1/2] fix(VTextarea): hide autoGrow scrollbar until maxRows is reached --- .../vuetify/src/components/VTextarea/VTextarea.sass | 6 ++++++ .../vuetify/src/components/VTextarea/VTextarea.tsx | 10 ++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/vuetify/src/components/VTextarea/VTextarea.sass b/packages/vuetify/src/components/VTextarea/VTextarea.sass index b9bce78586b..ffda1583d3f 100644 --- a/packages/vuetify/src/components/VTextarea/VTextarea.sass +++ b/packages/vuetify/src/components/VTextarea/VTextarea.sass @@ -33,6 +33,12 @@ &--no-resize .v-field__input resize: none + scrollbar-width: none + + &.v-textarea--height-capped + .v-field__input + scrollbar-gutter: stable + scrollbar-width: thin .v-field--no-label, .v-field--active diff --git a/packages/vuetify/src/components/VTextarea/VTextarea.tsx b/packages/vuetify/src/components/VTextarea/VTextarea.tsx index 1b191fd9028..3ace5b65542 100644 --- a/packages/vuetify/src/components/VTextarea/VTextarea.tsx +++ b/packages/vuetify/src/components/VTextarea/VTextarea.tsx @@ -98,6 +98,7 @@ export const VTextarea = genericComponent()({ const vInputRef = ref() const vFieldRef = ref() const controlHeight = shallowRef('') + const heightCapped = shallowRef(false) const textareaRef = ref() const isActive = computed(() => ( props.persistentPlaceholder || @@ -150,7 +151,10 @@ export const VTextarea = genericComponent()({ if (!props.autoGrow) rows.value = Number(props.rows) }) function calculateInputHeight () { - if (!props.autoGrow) return + if (!props.autoGrow) { + heightCapped.value = false + return + } nextTick(() => { if (!sizerRef.value || !vFieldRef.value) return @@ -170,9 +174,10 @@ export const VTextarea = genericComponent()({ ) const maxHeight = parseFloat(props.maxRows!) * lineHeight + padding || Infinity const newHeight = clamp(height ?? 0, minHeight, maxHeight) - rows.value = Math.floor((newHeight - padding) / lineHeight) + rows.value = Math.floor((newHeight - padding) / lineHeight) controlHeight.value = convertToUnit(newHeight) + heightCapped.value = height > maxHeight }) } @@ -221,6 +226,7 @@ export const VTextarea = genericComponent()({ 'v-text-field--suffixed': props.suffix, 'v-textarea--auto-grow': props.autoGrow, 'v-textarea--no-resize': props.noResize || props.autoGrow, + 'v-textarea--height-capped': heightCapped.value, 'v-input--plain-underlined': isPlainOrUnderlined.value, }, props.class, From 608898aa1e1e86f946ad3f8ebc4c766f89102e93 Mon Sep 17 00:00:00 2001 From: Kael Date: Wed, 3 Sep 2025 18:19:05 +1000 Subject: [PATCH 2/2] update tests --- .../VTextarea/__tests__/VTextarea.spec.browser.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/vuetify/src/components/VTextarea/__tests__/VTextarea.spec.browser.tsx b/packages/vuetify/src/components/VTextarea/__tests__/VTextarea.spec.browser.tsx index 9ac34e199f1..0d5c66a51ba 100644 --- a/packages/vuetify/src/components/VTextarea/__tests__/VTextarea.spec.browser.tsx +++ b/packages/vuetify/src/components/VTextarea/__tests__/VTextarea.spec.browser.tsx @@ -24,16 +24,16 @@ describe('VTextarea', () => { expect(el.offsetHeight).toBe(56) await userEvent.tab() - await userEvent.keyboard('sed d') + await userEvent.keyboard(' sed do ') await expect.poll(() => el.offsetHeight).toBe(56) - await userEvent.keyboard('o') + await userEvent.keyboard('e') await expect.poll(() => el.offsetHeight).toBe(80) }) it('should respect max-rows', async () => { await page.viewport(500, 500) - const model = ref('Lorem ipsum dolor sit amet, consectetur adipiscing elit') + const model = ref('Lorem ipsum dolor sit amet, consectetur adipiscing elit. ') render(() => ( @@ -57,7 +57,7 @@ describe('VTextarea', () => { it('should emit update rows', async () => { await page.viewport(500, 500) - const model = ref('Lorem ipsum dolor sit amet, consectetur adipiscing elit') + const model = ref('Lorem ipsum dolor sit amet, consectetur adipiscing elit. ') const rows = ref(1) render(() => (