diff --git a/core/api.txt b/core/api.txt index 77c2a42746..31ba1f7658 100644 --- a/core/api.txt +++ b/core/api.txt @@ -2280,6 +2280,7 @@ ion-textarea,prop,readonly,boolean,false,false,false ion-textarea,prop,required,boolean,false,false,false ion-textarea,prop,rows,number | undefined,undefined,false,false ion-textarea,prop,shape,"round" | undefined,undefined,false,false +ion-textarea,prop,size,"large" | "medium" | "small" | undefined,'medium',false,false ion-textarea,prop,spellcheck,boolean,false,false,false ion-textarea,prop,theme,"ios" | "md" | "ionic",undefined,false,false ion-textarea,prop,value,null | string | undefined,'',false,false diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 057be08e40..55f61afc6f 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -3620,6 +3620,10 @@ export namespace Components { * The shape of the textarea. If "round" it will have an increased border radius. */ "shape"?: 'round'; + /** + * The size of the textarea. If "large", it will have an increased height. By default the size is "medium". This property only applies to the `"ionic"` theme. + */ + "size"?: 'small' | 'medium' | 'large'; /** * If `true`, the element will have its spelling and grammar checked. */ @@ -8976,6 +8980,10 @@ declare namespace LocalJSX { * The shape of the textarea. If "round" it will have an increased border radius. */ "shape"?: 'round'; + /** + * The size of the textarea. If "large", it will have an increased height. By default the size is "medium". This property only applies to the `"ionic"` theme. + */ + "size"?: 'small' | 'medium' | 'large'; /** * If `true`, the element will have its spelling and grammar checked. */ diff --git a/core/src/components/textarea/test/basic/index.html b/core/src/components/textarea/test/basic/index.html index 070e2072cb..6d73908513 100644 --- a/core/src/components/textarea/test/basic/index.html +++ b/core/src/components/textarea/test/basic/index.html @@ -22,8 +22,18 @@ - - + + + + diff --git a/core/src/components/textarea/test/size/index.html b/core/src/components/textarea/test/size/index.html new file mode 100644 index 0000000000..884371f930 --- /dev/null +++ b/core/src/components/textarea/test/size/index.html @@ -0,0 +1,75 @@ + + + + + Textarea - Size + + + + + + + + + + + + + + Textarea - Size + + + + +
+
+

No Fill: No Size

+ +
+ +
+

Outline: No Size

+ +
+ +
+

No Fill: No Size, Round Shape

+ +
+ +
+

Outline: No Size, Round Shape

+ +
+
+
+
+ + diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts b/core/src/components/textarea/test/size/textarea.e2e.ts new file mode 100644 index 0000000000..7e9c4ab81c --- /dev/null +++ b/core/src/components/textarea/test/size/textarea.e2e.ts @@ -0,0 +1,57 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +/** + * Size is only available in the Ionic theme + */ +configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('textarea: size'), () => { + test.describe('textarea: size medium', () => { + test('should not have visual regressions', async ({ page }) => { + await page.setContent( + ` + + `, + config + ); + + const textarea = page.locator('ion-textarea'); + await expect(textarea).toHaveScreenshot(screenshot(`textarea-size-medium`)); + }); + test('should render correctly with stacked label', async ({ page }) => { + await page.setContent( + ` + + `, + config + ); + + const textarea = page.locator('ion-textarea'); + await expect(textarea).toHaveScreenshot(screenshot(`textarea-size-medium-label-stacked`)); + }); + test('should not have visual regressions with fill outline', async ({ page }) => { + await page.setContent( + ` + + `, + config + ); + + const textarea = page.locator('ion-textarea'); + await expect(textarea).toHaveScreenshot(screenshot(`textarea-size-medium-outline`)); + }); + }); + }); +}); diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..6c9b5386f7 Binary files /dev/null and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..fff638eea4 Binary files /dev/null and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Safari-linux.png new file mode 100644 index 0000000000..4d82189d49 Binary files /dev/null and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..0a6965e52e Binary files /dev/null and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..f8e26fdd4f Binary files /dev/null and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Safari-linux.png new file mode 100644 index 0000000000..ccb4f07e33 Binary files /dev/null and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..fb233be320 Binary files /dev/null and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..5a1727ec70 Binary files /dev/null and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Safari-linux.png new file mode 100644 index 0000000000..83ba831bef Binary files /dev/null and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/textarea.scss b/core/src/components/textarea/textarea.common.scss similarity index 85% rename from core/src/components/textarea/textarea.scss rename to core/src/components/textarea/textarea.common.scss index ee42da9b61..68c7271a40 100644 --- a/core/src/components/textarea/textarea.scss +++ b/core/src/components/textarea/textarea.common.scss @@ -59,35 +59,11 @@ width: 100%; - min-height: 44px; - color: var(--color); - font-family: $font-family-base; - - z-index: $z-index-item-input; - box-sizing: border-box; } -// Textarea Wrapper -// ---------------------------------------------------------------- - -/** - * Since the label sits on top of the element, - * the component needs to be taller otherwise the - * label will appear too close to the textarea text. - * Also, floating and stacked labels should not - * push the label down since it it - * sits on top of the textarea. - */ -:host(.textarea-label-placement-floating), -:host(.textarea-label-placement-stacked) { - --padding-top: 0px; - - min-height: 56px; -} - /** * When the cols property is set we should * respect that width instead of defaulting @@ -279,17 +255,11 @@ caret-color: var(--highlight-color); } -.native-wrapper textarea { - @include padding(var(--padding-top), 0px, var(--padding-bottom), 0px); -} - .native-wrapper { display: grid; min-width: inherit; max-width: inherit; - min-height: inherit; - max-height: inherit; /** * This avoids a WebKit bug where @@ -375,20 +345,11 @@ // ---------------------------------------------------------------- .textarea-bottom { - /** - * The bottom content should take on the start and end - * padding so it is always aligned with either the label - * or the start of the textarea. - */ - @include padding(5px, var(--padding-end), 0, var(--padding-start)); - display: flex; justify-content: space-between; border-top: var(--border-width) var(--border-style) var(--border-color); - - font-size: dynamic-font(12px); } /** @@ -417,7 +378,7 @@ .textarea-bottom .helper-text { display: block; - color: #{$text-color-step-450}; + color: $text-color-step-450; } :host(.ion-touched.ion-invalid) .textarea-bottom .error-text { @@ -439,11 +400,7 @@ */ @include margin-horizontal(auto, null); - color: #{$text-color-step-450}; - white-space: nowrap; - - padding-inline-start: 16px; } // Textarea Label @@ -524,15 +481,6 @@ flex-direction: row; } -:host(.textarea-label-placement-start) .label-text-wrapper { - /** - * The margin between the label and - * the textarea should be on the end - * when the label sits at the start. - */ - @include margin(0, $form-control-label-margin, 0, 0); -} - // Textarea Label Placement - End // ---------------------------------------------------------------- @@ -544,27 +492,9 @@ flex-direction: row-reverse; } -/** - * The margin between the label and - * the textarea should be on the start - * when the label sits at the end. - */ -:host(.textarea-label-placement-end) .label-text-wrapper { - @include margin(0, 0, 0, $form-control-label-margin); -} - // Textarea Label Placement - Fixed // ---------------------------------------------------------------- -:host(.textarea-label-placement-fixed) .label-text-wrapper { - /** - * The margin between the label and - * the textarea should be on the end - * when the label sits at the start. - */ - @include margin(0, $form-control-label-margin, 0, 0); -} - /** * Label is on the left of the textarea in LTR and * on the right in RTL. Label also has a fixed width. @@ -624,13 +554,6 @@ @include margin(8px, 0px, 0px, 0px); } -:host(.textarea-label-placement-stacked) ::slotted([slot="start"]), -:host(.textarea-label-placement-stacked) ::slotted([slot="end"]), -:host(.textarea-label-placement-floating) ::slotted([slot="start"]), -:host(.textarea-label-placement-floating) ::slotted([slot="end"]) { - margin-top: 8px; -} - /** * This makes the label sit over the textarea * when the textarea is blurred and has no value. @@ -666,31 +589,14 @@ max-width: calc(100% / #{$form-control-label-stacked-scale}); } -// Start/End Slots +// Start / End Slots // ---------------------------------------------------------------- .start-slot-wrapper, .end-slot-wrapper { - @include padding(var(--padding-top), 0, var(--padding-bottom), 0); - display: flex; flex-shrink: 0; align-self: start; } - -::slotted([slot="start"]), -::slotted([slot="end"]) { - margin-top: 0; // ensure slot content is vertically aligned with label -} - -::slotted([slot="start"]:last-of-type) { - margin-inline-end: $form-control-label-margin; - margin-inline-start: 0; -} - -::slotted([slot="end"]:first-of-type) { - margin-inline-start: $form-control-label-margin; - margin-inline-end: 0; -} diff --git a/core/src/components/textarea/textarea.ionic.outline.scss b/core/src/components/textarea/textarea.ionic.outline.scss new file mode 100644 index 0000000000..915a1ca64e --- /dev/null +++ b/core/src/components/textarea/textarea.ionic.outline.scss @@ -0,0 +1,55 @@ +@use "../../themes/ionic/ionic.globals.scss" as globals; + +// Textarea Fill: Outline +// ---------------------------------------------------------------- + +:host(.textarea-fill-outline) { + --border-width: #{globals.$ionic-border-size-025}; + --border-color: #{globals.$ionic-color-neutral-500}; +} + +// Textarea Fill: Outline, Textarea Wrapper +// ---------------------------------------------------------------- + +:host(.textarea-fill-outline) .textarea-wrapper-inner { + /** + * The border should be relative to the inner wrapper + * so that it does not include the label. + */ + position: relative; +} + +// Textarea Fill: Outline, Outline Container +// ---------------------------------------------------------------- + +:host(.textarea-fill-outline) .textarea-outline { + @include globals.position(0, 0, 0, 0); + @include globals.border-radius(var(--border-radius)); + + position: absolute; + + width: 100%; + height: 100%; + + pointer-events: none; + + border: var(--border-width) var(--border-style) var(--border-color); +} + +// Textarea Fill: Outline, Bottom Content +// ---------------------------------------------------------------- + +/** + * The bottom content should never have + * a border with the outline style. + */ +:host(.textarea-fill-outline) .textarea-bottom { + border-top: none; +} + +// Textarea Fill: Outline, Native Textarea +// ---------------------------------------------------------------- + +:host(.textarea-fill-outline) textarea { + margin-top: globals.$ionic-space-100; +} diff --git a/core/src/components/textarea/textarea.ionic.scss b/core/src/components/textarea/textarea.ionic.scss new file mode 100644 index 0000000000..46b1801e9f --- /dev/null +++ b/core/src/components/textarea/textarea.ionic.scss @@ -0,0 +1,101 @@ +@use "../../themes/ionic/ionic.globals.scss" as globals; +@use "./textarea.common"; +@use "./textarea.ionic.outline.scss" as outline; + +// Ionic Textarea +// -------------------------------------------------- + +:host { + --border-radius: #{globals.$ionic-border-radius-400}; + --color: #{globals.$ionic-color-neutral-1200}; + --highlight-color-valid: #{globals.$ionic-color-success-base}; + --highlight-color-invalid: #{globals.$ionic-color-danger-base}; + --placeholder-color: #{globals.$ionic-color-neutral-800}; + --placeholder-opacity: 1; + --background: #{globals.$ionic-color-base-white}; + --padding-top: #{globals.$ionic-space-300}; + --padding-end: #{globals.$ionic-space-400}; + --padding-bottom: #{globals.$ionic-space-300}; + --padding-start: #{globals.$ionic-space-400}; + + @include globals.typography(globals.$ionic-body-md-regular); +} + +// Ionic Textarea Sizes +// -------------------------------------------------- + +// Setting height to 0 allows it to collapse in height +// instead of growing above the min height by default. +.textarea-wrapper-inner { + height: 0; +} + +:host(.textarea-size-medium) .textarea-wrapper-inner { + min-height: globals.$ionic-scale-3400; +} + +// Textarea Wrapper +// ---------------------------------------------------------------- + +.textarea-wrapper { + gap: globals.$ionic-space-100; +} + +.textarea-wrapper-inner { + @include globals.padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start)); +} + +// Textarea Auto Grow +// ---------------------------------------------------------------- + +// The height should be auto only when auto-grow is enabled. +:host([auto-grow]) .textarea-wrapper-inner { + height: auto; +} + +// The min and max height should be inherited if auto-grow is enabled. +// This allows the textarea to grow and shrink as needed. +:host([auto-grow]) .native-wrapper { + min-height: inherit; + max-height: inherit; +} + +// Textarea Label +// ---------------------------------------------------------------- + +.label-text-wrapper { + @include globals.typography(globals.$ionic-body-sm-medium); + + color: globals.$ionic-color-neutral-1000; +} + +:host(.label-floating) .label-text-wrapper { + @include globals.transform(none); +} + +// Textarea Slotted Content +// ---------------------------------------------------------------- + +ion-icon { + color: globals.$ionic-color-neutral-800; + + font-size: globals.$ionic-scale-400; +} + +.start-slot-wrapper, +.end-slot-wrapper { + margin-top: globals.$ionic-space-050; +} + +// Textarea Bottom Content +// ---------------------------------------------------------------- + +.textarea-bottom { + @include globals.padding(globals.$ionic-space-100, var(--padding-end), null, var(--padding-start)); + @include globals.typography(globals.$ionic-body-sm-medium); +} + +.textarea-bottom .helper-text, +.textarea-bottom .counter { + color: globals.$ionic-color-neutral-800; +} diff --git a/core/src/components/textarea/textarea.ios.scss b/core/src/components/textarea/textarea.ios.scss index 098a345a90..fa4c5d450b 100644 --- a/core/src/components/textarea/textarea.ios.scss +++ b/core/src/components/textarea/textarea.ios.scss @@ -1,4 +1,4 @@ -@import "./textarea"; +@import "./textarea.native"; @import "./textarea.vars"; @import "./textarea.ios.vars"; diff --git a/core/src/components/textarea/textarea.md.scss b/core/src/components/textarea/textarea.md.scss index eec8efd025..516fc251f7 100644 --- a/core/src/components/textarea/textarea.md.scss +++ b/core/src/components/textarea/textarea.md.scss @@ -1,4 +1,4 @@ -@import "./textarea"; +@import "./textarea.native"; @import "./textarea.vars"; @import "./textarea.md.vars"; @import "./textarea.md.solid"; diff --git a/core/src/components/textarea/textarea.native.scss b/core/src/components/textarea/textarea.native.scss new file mode 100644 index 0000000000..32cab095c8 --- /dev/null +++ b/core/src/components/textarea/textarea.native.scss @@ -0,0 +1,133 @@ +@use "../../themes/native/native.globals" as globals; +@use "./textarea.common"; + +// Textarea - iOS and Material Design +// -------------------------------------------------- + +:host { + min-height: 44px; + + font-family: globals.$font-family-base; + + z-index: globals.$z-index-item-input; +} + +// Textarea Wrapper +// ---------------------------------------------------------------- + +/** + * Since the label sits on top of the element, + * the component needs to be taller otherwise the + * label will appear too close to the textarea text. + * Also, floating and stacked labels should not + * push the label down since it it + * sits on top of the textarea. + */ +:host(.textarea-label-placement-floating), +:host(.textarea-label-placement-stacked) { + --padding-top: 0px; + + min-height: 56px; +} + +// Textarea Native +// ---------------------------------------------------------------- + +// This is required for auto-grow to work. +.native-wrapper { + min-height: inherit; + max-height: inherit; +} + +.native-wrapper textarea { + @include globals.padding(var(--padding-top), 0px, var(--padding-bottom), 0px); +} + +// Textarea Bottom Content +// ---------------------------------------------------------------- + +.textarea-bottom { + /** + * The bottom content should take on the start and end + * padding so it is always aligned with either the label + * or the start of the textarea. + */ + @include globals.padding(5px, var(--padding-end), 0, var(--padding-start)); + + font-size: globals.dynamic-font(12px); +} + +// Textarea Max Length Counter +// ---------------------------------------------------------------- + +.textarea-bottom .counter { + color: #{globals.$text-color-step-450}; + + padding-inline-start: 16px; +} + +// Textarea Label Placement - Start +// ---------------------------------------------------------------- + +:host(.textarea-label-placement-start) .label-text-wrapper { + /** + * The margin between the label and + * the textarea should be on the end + * when the label sits at the start. + */ + @include globals.margin(0, globals.$form-control-label-margin, 0, 0); +} + +// Textarea Label Placement - End +// ---------------------------------------------------------------- + +/** + * The margin between the label and + * the textarea should be on the start + * when the label sits at the end. + */ +:host(.textarea-label-placement-end) .label-text-wrapper { + @include globals.margin(0, 0, 0, globals.$form-control-label-margin); +} + +// Textarea Label Placement - Fixed +// ---------------------------------------------------------------- + +:host(.textarea-label-placement-fixed) .label-text-wrapper { + /** + * The margin between the label and + * the textarea should be on the end + * when the label sits at the start. + */ + @include globals.margin(0, globals.$form-control-label-margin, 0, 0); +} + +// Start / End Slots +// ---------------------------------------------------------------- + +.start-slot-wrapper, +.end-slot-wrapper { + @include globals.padding(var(--padding-top), 0, var(--padding-bottom), 0); +} + +:host(.textarea-label-placement-stacked) ::slotted([slot="start"]), +:host(.textarea-label-placement-stacked) ::slotted([slot="end"]), +:host(.textarea-label-placement-floating) ::slotted([slot="start"]), +:host(.textarea-label-placement-floating) ::slotted([slot="end"]) { + margin-top: 8px; +} + +::slotted([slot="start"]), +::slotted([slot="end"]) { + margin-top: 0; // ensure slot content is vertically aligned with label +} + +::slotted([slot="start"]:last-of-type) { + margin-inline-end: globals.$form-control-label-margin; + margin-inline-start: 0; +} + +::slotted([slot="end"]:first-of-type) { + margin-inline-start: globals.$form-control-label-margin; + margin-inline-end: 0; +} diff --git a/core/src/components/textarea/textarea.tsx b/core/src/components/textarea/textarea.tsx index 88d5da9245..95c1877364 100644 --- a/core/src/components/textarea/textarea.tsx +++ b/core/src/components/textarea/textarea.tsx @@ -40,7 +40,7 @@ import type { TextareaChangeEventDetail, TextareaInputEventDetail } from './text styleUrls: { ios: 'textarea.ios.scss', md: 'textarea.md.scss', - ionic: 'textarea.md.scss', + ionic: 'textarea.ionic.scss', }, scoped: true, }) @@ -248,6 +248,12 @@ export class Textarea implements ComponentInterface { */ @Prop() shape?: 'round'; + /** + * The size of the textarea. If "large", it will have an increased height. By default the + * size is "medium". This property only applies to the `"ionic"` theme. + */ + @Prop() size?: 'small' | 'medium' | 'large' = 'medium'; + /** * Update the native input element when the value changes */ @@ -619,7 +625,7 @@ export class Textarea implements ComponentInterface { } render() { - const { inputId, disabled, fill, shape, labelPlacement, el, hasFocus } = this; + const { inputId, disabled, fill, shape, size, labelPlacement, el, hasFocus } = this; const theme = getIonTheme(this); const value = this.getValue(); const inItem = hostContext('ion-item', this.el); @@ -657,6 +663,7 @@ export class Textarea implements ComponentInterface { 'label-floating': labelShouldFloat, [`textarea-fill-${fill}`]: fill !== undefined, [`textarea-shape-${shape}`]: shape !== undefined, + [`textarea-size-${size}`]: true, [`textarea-label-placement-${labelPlacement}`]: true, 'textarea-disabled': disabled, })} @@ -670,6 +677,18 @@ export class Textarea implements ComponentInterface {