diff --git a/core/api.txt b/core/api.txt
index e4597ef5934..57d77a242c0 100644
--- a/core/api.txt
+++ b/core/api.txt
@@ -2423,7 +2423,7 @@ ion-text,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "second
ion-text,prop,mode,"ios" | "md",undefined,false,false
ion-text,prop,theme,"ios" | "md" | "ionic",undefined,false,false
-ion-textarea,scoped
+ion-textarea,shadow
ion-textarea,prop,autoGrow,boolean,false,false,true
ion-textarea,prop,autocapitalize,string,'none',false,false
ion-textarea,prop,autofocus,boolean,false,false,false
@@ -2447,7 +2447,7 @@ ion-textarea,prop,mode,"ios" | "md",undefined,false,false
ion-textarea,prop,name,string,this.inputId,false,false
ion-textarea,prop,placeholder,string | undefined,undefined,false,false
ion-textarea,prop,readonly,boolean,false,false,false
-ion-textarea,prop,required,boolean,false,false,false
+ion-textarea,prop,required,boolean,false,false,true
ion-textarea,prop,rows,number | undefined,undefined,false,false
ion-textarea,prop,shape,"rectangular" | "round" | "soft" | undefined,undefined,false,false
ion-textarea,prop,size,"large" | "medium" | "small" | undefined,'medium',false,false
@@ -2515,6 +2515,14 @@ ion-textarea,css-prop,--placeholder-font-weight,md
ion-textarea,css-prop,--placeholder-opacity,ionic
ion-textarea,css-prop,--placeholder-opacity,ios
ion-textarea,css-prop,--placeholder-opacity,md
+ion-textarea,part,bottom
+ion-textarea,part,container
+ion-textarea,part,counter
+ion-textarea,part,error-text
+ion-textarea,part,helper-text
+ion-textarea,part,label
+ion-textarea,part,native
+ion-textarea,part,supporting-text
ion-thumbnail,shadow
ion-thumbnail,prop,mode,"ios" | "md",undefined,false,false
diff --git a/core/src/components/textarea/test/bottom-content/textarea.e2e.ts b/core/src/components/textarea/test/bottom-content/textarea.e2e.ts
index 7c7315c4857..6f9f357706c 100644
--- a/core/src/components/textarea/test/bottom-content/textarea.e2e.ts
+++ b/core/src/components/textarea/test/bottom-content/textarea.e2e.ts
@@ -157,12 +157,12 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
await page.setContent(
`
-
+
`,
config
);
@@ -174,12 +174,12 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
await page.setContent(
`
-
+
`,
config
);
@@ -193,11 +193,11 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
await page.setContent(
`
-
+
`,
config
);
diff --git a/core/src/components/textarea/test/custom/textarea.e2e.ts b/core/src/components/textarea/test/custom/textarea.e2e.ts
new file mode 100644
index 00000000000..cdab1bd151d
--- /dev/null
+++ b/core/src/components/textarea/test/custom/textarea.e2e.ts
@@ -0,0 +1,222 @@
+import { expect } from '@playwright/test';
+import { configs, test } from '@utils/test/playwright';
+
+/**
+ * This behavior does not vary across modes/directions
+ */
+configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
+ test.describe(title('textarea: custom'), () => {
+ test('should allow styling the container part', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+
+ `,
+ config
+ );
+
+ const textarea = await page.locator('ion-textarea');
+ const container = await textarea.evaluate((el: HTMLIonTextareaElement) => {
+ const containerEl = el.shadowRoot?.querySelector('[part="container"]') as HTMLElement | null;
+ if (!containerEl) {
+ return '';
+ }
+ return getComputedStyle(containerEl).backgroundColor;
+ });
+
+ expect(container).toBe('rgb(0, 0, 255)');
+ });
+
+ test('should allow styling the label part', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+
+ `,
+ config
+ );
+
+ const textarea = await page.locator('ion-textarea');
+ const labelColor = await textarea.evaluate((el: HTMLIonTextareaElement) => {
+ const labelEl = el.shadowRoot?.querySelector('[part="label"]') as HTMLElement | null;
+ if (!labelEl) {
+ return '';
+ }
+ return getComputedStyle(labelEl).color;
+ });
+
+ expect(labelColor).toBe('rgb(0, 128, 0)');
+ });
+
+ test('should allow styling the native textarea', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+
+ `,
+ config
+ );
+
+ const textarea = await page.locator('ion-textarea');
+ const color = await textarea.evaluate(
+ (el: HTMLIonTextareaElement) =>
+ getComputedStyle(el.shadowRoot?.querySelector('textarea') as HTMLTextAreaElement).color
+ );
+
+ expect(color).toBe('rgb(255, 0, 0)');
+ });
+
+ test('should allow styling the supporting-text part', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+
+ `,
+ config
+ );
+
+ const textarea = await page.locator('ion-textarea');
+ await textarea.waitFor();
+
+ const supportingTextColor = await textarea.evaluate((el: HTMLIonTextareaElement) => {
+ // Query for the visible helper-text element which has the supporting-text part
+ // Use attribute selector that matches space-separated part values
+ const helperTextEl = el.shadowRoot?.querySelector('[part~="helper-text"]') as HTMLElement | null;
+ if (!helperTextEl) {
+ return '';
+ }
+ return getComputedStyle(helperTextEl).color;
+ });
+
+ expect(supportingTextColor).toBe('rgb(0, 0, 255)');
+ });
+
+ test('should allow styling the helper-text part', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+
+ `,
+ config
+ );
+
+ const textarea = await page.locator('ion-textarea');
+ await textarea.waitFor();
+
+ const helperTextColor = await textarea.evaluate((el: HTMLIonTextareaElement) => {
+ const helperTextEl = el.shadowRoot?.querySelector('[part~="helper-text"]') as HTMLElement | null;
+ if (!helperTextEl) {
+ return '';
+ }
+ return getComputedStyle(helperTextEl).color;
+ });
+
+ expect(helperTextColor).toBe('rgb(255, 0, 0)');
+ });
+
+ test('should allow styling the error-text part', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+
+ `,
+ config
+ );
+
+ const textarea = await page.locator('ion-textarea');
+ await textarea.waitFor();
+
+ const errorTextColor = await textarea.evaluate((el: HTMLIonTextareaElement) => {
+ const errorTextEl = el.shadowRoot?.querySelector('[part~="error-text"]') as HTMLElement | null;
+ if (!errorTextEl) {
+ return '';
+ }
+ return getComputedStyle(errorTextEl).color;
+ });
+
+ expect(errorTextColor).toBe('rgb(255, 0, 0)');
+ });
+
+ test('should allow styling the counter part', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+
+ `,
+ config
+ );
+
+ const textarea = await page.locator('ion-textarea');
+ const counterColor = await textarea.evaluate((el: HTMLIonTextareaElement) => {
+ const counterEl = el.shadowRoot?.querySelector('[part="counter"]') as HTMLElement | null;
+ if (!counterEl) {
+ return '';
+ }
+ return getComputedStyle(counterEl).color;
+ });
+
+ expect(counterColor).toBe('rgb(0, 128, 0)');
+ });
+
+ test('should allow styling the bottom part', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+
+ `,
+ config
+ );
+
+ const textarea = await page.locator('ion-textarea');
+ const bottomBgColor = await textarea.evaluate((el: HTMLIonTextareaElement) => {
+ const bottomEl = el.shadowRoot?.querySelector('[part="bottom"]') as HTMLElement | null;
+ if (!bottomEl) {
+ return '';
+ }
+ return getComputedStyle(bottomEl).backgroundColor;
+ });
+
+ expect(bottomBgColor).toBe('rgb(0, 0, 255)');
+ });
+ });
+});
diff --git a/core/src/components/textarea/test/form/index.html b/core/src/components/textarea/test/form/index.html
new file mode 100644
index 00000000000..e64e6561aaf
--- /dev/null
+++ b/core/src/components/textarea/test/form/index.html
@@ -0,0 +1,77 @@
+
+
+
+
+ Textarea - Form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Textarea - Form
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/src/components/textarea/test/form/textarea.e2e.ts b/core/src/components/textarea/test/form/textarea.e2e.ts
new file mode 100644
index 00000000000..72e4cee93b7
--- /dev/null
+++ b/core/src/components/textarea/test/form/textarea.e2e.ts
@@ -0,0 +1,139 @@
+import { expect } from '@playwright/test';
+import { configs, test } from '@utils/test/playwright';
+
+/**
+ * This behavior does not vary across modes/directions
+ */
+configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
+ test.describe(title('textarea: form'), () => {
+ test('should be marked as invalid when required and empty', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+ `,
+ config
+ );
+
+ let formSubmitted = false;
+
+ const textarea = page.locator('ion-textarea');
+ const submitButton = page.locator('button[type="submit"]');
+
+ // Check that the textarea's browser validation is working before submission
+ const validationInfo = await textarea.evaluate((el: HTMLIonTextareaElement) => {
+ const nativeTextarea = el.shadowRoot?.querySelector('textarea') as HTMLTextAreaElement | null;
+ if (!nativeTextarea) {
+ return { isValid: false, willValidate: false, validationMessage: '', checkValidity: false };
+ }
+ return {
+ isValid: nativeTextarea.validity.valid,
+ willValidate: nativeTextarea.willValidate,
+ validationMessage: nativeTextarea.validationMessage,
+ checkValidity: nativeTextarea.checkValidity(),
+ };
+ });
+
+ expect(validationInfo.willValidate).toBe(true);
+ expect(validationInfo.isValid).toBe(false);
+ expect(validationInfo.checkValidity).toBe(false);
+ expect(validationInfo.validationMessage.length).toBeGreaterThan(0);
+
+ // Click submit button - browser validation should prevent form submission
+ // and show the native validation popup
+ await submitButton.click();
+
+ // Wait for any async operations to complete
+ await page.waitForChanges();
+
+ // Check that form was not submitted (browser validation prevented it)
+ formSubmitted = await page.evaluate(() => (window as any).formSubmitted ?? false);
+ expect(formSubmitted).toBe(false);
+
+ // Verify that the form's validation was triggered and it's invalid
+ const formValidity = await page.evaluate(() => {
+ const form = document.querySelector('form');
+ return form ? form.checkValidity() : null;
+ });
+ expect(formValidity).toBe(false);
+
+ // Verify the textarea's validity is still false after submit attempt
+ const isValidAfterSubmit = await textarea.evaluate((el: HTMLIonTextareaElement) => {
+ const nativeTextarea = el.shadowRoot?.querySelector('textarea') as HTMLTextAreaElement | null;
+ return nativeTextarea?.validity.valid ?? false;
+ });
+ expect(isValidAfterSubmit).toBe(false);
+ });
+
+ test('should be marked as valid when required and filled', async ({ page }) => {
+ await page.setContent(
+ `
+
+
+ `,
+ config
+ );
+
+ const textarea = page.locator('ion-textarea');
+ const submitButton = page.locator('button[type="submit"]');
+
+ // Type into the native textarea in the shadow DOM
+ await textarea.evaluate((el: HTMLIonTextareaElement) => {
+ const nativeTextarea = el.shadowRoot?.querySelector('textarea') as HTMLTextAreaElement | null;
+ if (nativeTextarea) {
+ nativeTextarea.value = 'Test value';
+ nativeTextarea.dispatchEvent(new Event('input', { bubbles: true }));
+ }
+ });
+
+ // Check that the textarea's browser validation is working before submission
+ const isValidBeforeSubmit = await textarea.evaluate((el: HTMLIonTextareaElement) => {
+ const nativeTextarea = el.shadowRoot?.querySelector('textarea') as HTMLTextAreaElement | null;
+ return nativeTextarea?.validity.valid ?? false;
+ });
+ expect(isValidBeforeSubmit).toBe(true);
+
+ // Click submit button - form should submit since validation passes
+ await submitButton.click();
+
+ // Wait for any async operations to complete
+ await page.waitForChanges();
+
+ // Check that form was submitted (validation passed)
+ const formSubmitted = await page.evaluate(() => (window as any).formSubmitted ?? false);
+ expect(formSubmitted).toBe(true);
+
+ // Verify that the form's validation passed
+ const formValidity = await page.evaluate(() => {
+ const form = document.querySelector('form');
+ return form ? form.checkValidity() : null;
+ });
+ expect(formValidity).toBe(true);
+
+ // Verify the textarea's validity is still true after submit
+ const isValidAfterSubmit = await textarea.evaluate((el: HTMLIonTextareaElement) => {
+ const nativeTextarea = el.shadowRoot?.querySelector('textarea') as HTMLTextAreaElement | null;
+ return nativeTextarea?.validity.valid ?? false;
+ });
+ expect(isValidAfterSubmit).toBe(true);
+ });
+ });
+});
diff --git a/core/src/components/textarea/test/textarea.spec.ts b/core/src/components/textarea/test/textarea.spec.ts
index f1611a3e291..b0eeb949ba5 100644
--- a/core/src/components/textarea/test/textarea.spec.ts
+++ b/core/src/components/textarea/test/textarea.spec.ts
@@ -8,7 +8,8 @@ it('should inherit attributes', async () => {
html: '',
});
- const nativeEl = page.body.querySelector('ion-textarea textarea')!;
+ const textareaEl = page.body.querySelector('ion-textarea')!;
+ const nativeEl = textareaEl.shadowRoot!.querySelector('textarea')!;
expect(nativeEl.getAttribute('title')).toBe('my title');
expect(nativeEl.getAttribute('tabindex')).toBe('-1');
expect(nativeEl.getAttribute('data-form-type')).toBe('password');
@@ -21,7 +22,7 @@ it('should inherit watched attributes', async () => {
});
const textareaEl = page.body.querySelector('ion-textarea')!;
- const nativeEl = textareaEl.querySelector('textarea')!;
+ const nativeEl = textareaEl.shadowRoot!.querySelector('textarea')!;
expect(nativeEl.getAttribute('dir')).toBe('ltr');
@@ -52,7 +53,7 @@ describe('textarea: label rendering', () => {
const textarea = page.body.querySelector('ion-textarea')!;
- const labelText = textarea.querySelector('.label-text-wrapper')!;
+ const labelText = textarea.shadowRoot!.querySelector('.label-text-wrapper')!;
expect(labelText.textContent).toBe('Label Prop Text');
});
@@ -66,9 +67,16 @@ describe('textarea: label rendering', () => {
const textarea = page.body.querySelector('ion-textarea')!;
- const labelText = textarea.querySelector('.label-text-wrapper')!;
+ // When using a slot, the content is in the light DOM, not directly
+ // accessible via textContent. Check that the slot element exists and
+ // the slotted content is in the light DOM.
+ const slotEl = textarea.shadowRoot!.querySelector('slot[name="label"]');
+ const propEl = textarea.shadowRoot!.querySelector('.label-text');
+ const slottedContent = textarea.querySelector('[slot="label"]');
- expect(labelText.textContent).toBe('Label Prop Slot');
+ expect(slotEl).not.toBe(null);
+ expect(propEl).toBe(null);
+ expect(slottedContent?.textContent).toBe('Label Prop Slot');
});
it('should render label prop if both prop and slot provided', async () => {
const page = await newSpecPage({
@@ -80,7 +88,7 @@ describe('textarea: label rendering', () => {
const textarea = page.body.querySelector('ion-textarea')!;
- const labelText = textarea.querySelector('.label-text-wrapper')!;
+ const labelText = textarea.shadowRoot!.querySelector('.label-text-wrapper')!;
expect(labelText.textContent).toBe('Label Prop Text');
});
diff --git a/core/src/components/textarea/test/textarea.spec.tsx b/core/src/components/textarea/test/textarea.spec.tsx
index e0ed6363f58..e7b0230756f 100644
--- a/core/src/components/textarea/test/textarea.spec.tsx
+++ b/core/src/components/textarea/test/textarea.spec.tsx
@@ -7,7 +7,8 @@ it('should render bottom content when helper text is defined', async () => {
html: ``,
});
- const bottomContent = page.body.querySelector('ion-textarea .textarea-bottom');
+ const textarea = page.body.querySelector('ion-textarea')!;
+ const bottomContent = textarea.shadowRoot!.querySelector('.textarea-bottom');
expect(bottomContent).not.toBe(null);
});
@@ -17,7 +18,8 @@ it('should render bottom content when helper text is undefined', async () => {
html: ``,
});
- const bottomContent = page.body.querySelector('ion-textarea .textarea-bottom');
+ const textarea = page.body.querySelector('ion-textarea')!;
+ const bottomContent = textarea.shadowRoot!.querySelector('.textarea-bottom');
expect(bottomContent).toBe(null);
});
@@ -27,6 +29,7 @@ it('should render bottom content when helper text is empty string', async () =>
html: ``,
});
- const bottomContent = page.body.querySelector('ion-textarea .textarea-bottom');
+ const textarea = page.body.querySelector('ion-textarea')!;
+ const bottomContent = textarea.shadowRoot!.querySelector('.textarea-bottom');
expect(bottomContent).toBe(null);
});
diff --git a/core/src/components/textarea/textarea.common.scss b/core/src/components/textarea/textarea.common.scss
index 0fbc42e1754..e25866fbe36 100644
--- a/core/src/components/textarea/textarea.common.scss
+++ b/core/src/components/textarea/textarea.common.scss
@@ -82,12 +82,12 @@
// Textarea Within An Item
// --------------------------------------------------
-:host-context(ion-item) {
+:host(.in-item) {
align-self: baseline;
}
-:host-context(ion-item)[slot="start"],
-:host-context(ion-item)[slot="end"] {
+:host(.in-item[slot="start"]),
+:host(.in-item[slot="end"]) {
width: auto;
}
@@ -311,6 +311,8 @@
width: 100%;
min-height: inherit;
+
+ box-sizing: border-box;
}
// Textarea Highlight
@@ -432,6 +434,10 @@
overflow: hidden;
}
+.textarea-outline {
+ box-sizing: border-box;
+}
+
/**
* If no label text is placed into the slot
* then the element should be hidden otherwise
diff --git a/core/src/components/textarea/textarea.tsx b/core/src/components/textarea/textarea.tsx
index 272b21f3077..2c6ad74ad6b 100644
--- a/core/src/components/textarea/textarea.tsx
+++ b/core/src/components/textarea/textarea.tsx
@@ -1,5 +1,6 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import {
+ AttachInternals,
Build,
Component,
Element,
@@ -15,7 +16,7 @@ import {
writeTask,
} from '@stencil/core';
import type { NotchController } from '@utils/forms';
-import { createNotchController, checkInvalidState } from '@utils/forms';
+import { createNotchController, checkInvalidState, reportValidityToElementInternals } from '@utils/forms';
import type { Attributes } from '@utils/helpers';
import { inheritAriaAttributes, debounceEvent, inheritAttributes, componentOnReady } from '@utils/helpers';
import { createSlotMutationController } from '@utils/slot-mutation-controller';
@@ -35,6 +36,15 @@ import type { TextareaChangeEventDetail, TextareaInputEventDetail } from './text
* @slot label - The label text to associate with the textarea. Use the `labelPlacement` property to control where the label is placed relative to the textarea. Use this if you need to render a label with custom HTML. (EXPERIMENTAL)
* @slot start - Content to display at the leading edge of the textarea. (EXPERIMENTAL)
* @slot end - Content to display at the trailing edge of the textarea. (EXPERIMENTAL)
+ *
+ * @part container - The wrapper element for the textarea.
+ * @part label - The label text describing the textarea.
+ * @part native - The native textarea element.
+ * @part supporting-text - Supporting text displayed beneath the textarea label.
+ * @part helper-text - Supporting text displayed beneath the textarea label when the textarea is valid.
+ * @part error-text - Supporting text displayed beneath the textarea label when the textarea is invalid and touched.
+ * @part counter - The character counter displayed when the counter property is set.
+ * @part bottom - The container element for helper text, error text, and counter.
*/
@Component({
tag: 'ion-textarea',
@@ -43,7 +53,8 @@ import type { TextareaChangeEventDetail, TextareaInputEventDetail } from './text
md: 'textarea.md.scss',
ionic: 'textarea.ionic.scss',
},
- scoped: true,
+ shadow: true,
+ formAssociated: true,
})
export class Textarea implements ComponentInterface {
private nativeInput?: HTMLTextAreaElement;
@@ -73,6 +84,8 @@ export class Textarea implements ComponentInterface {
@Element() el!: HTMLIonTextareaElement;
+ @AttachInternals() internals!: ElementInternals;
+
/**
* The `hasFocus` state ensures the focus class is
* added regardless of how the element is focused.
@@ -136,6 +149,14 @@ export class Textarea implements ComponentInterface {
*/
@Prop() disabled = false;
+ /**
+ * Update element internals when disabled prop changes
+ */
+ @Watch('disabled')
+ protected disabledChanged() {
+ this.updateElementInternals();
+ }
+
/**
* The fill for the item. If `"solid"` the item will have a background. If
* `"outline"` the item will be transparent with a border. Only available when the theme is `"md"`.
@@ -184,7 +205,7 @@ export class Textarea implements ComponentInterface {
/**
* If `true`, the user must fill in a value before submitting a form.
*/
- @Prop() required = false;
+ @Prop({ reflect: true }) required = false;
/**
* If `true`, the element will have its spelling and grammar checked.
@@ -287,9 +308,25 @@ export class Textarea implements ComponentInterface {
if (nativeInput && nativeInput.value !== value) {
nativeInput.value = value;
}
+ this.updateElementInternals();
this.runAutoGrow();
}
+ /**
+ * Update native input and element internals when required prop changes
+ */
+ @Watch('required')
+ protected requiredChanged() {
+ // Explicitly update the native element's required attribute to ensure
+ // browser validation works correctly when required changes dynamically.
+ // While the template binding should handle this, we need to update it
+ // synchronously for the browser's validation to recognize the change.
+ if (this.nativeInput) {
+ this.nativeInput.required = this.required;
+ }
+ this.updateElementInternals();
+ }
+
/**
* dir is a globally enumerated attribute.
* As a result, creating these as properties
@@ -422,6 +459,7 @@ export class Textarea implements ComponentInterface {
componentDidLoad() {
this.originalIonInput = this.ionInput;
+ this.updateElementInternals();
this.runAutoGrow();
}
@@ -544,6 +582,24 @@ export class Textarea implements ComponentInterface {
return this.value || '';
}
+ /**
+ * Updates the form value and reports validity state to the browser via
+ * ElementInternals. This should be called when the component loads, when
+ * the required prop changes, when the disabled prop changes, and when the value
+ * changes to ensure the form value stays in sync and validation state is updated.
+ */
+ private updateElementInternals() {
+ // Disabled form controls should not be included in form data
+ // Pass null to setFormValue when disabled to exclude it from form submission
+ const value = this.disabled ? null : this.getValue();
+ // ElementInternals may not be fully available in test environments
+ // so we need to check if the method exists before calling it
+ if (typeof this.internals.setFormValue === 'function') {
+ this.internals.setFormValue(value);
+ }
+ reportValidityToElementInternals(this.nativeInput, this.internals);
+ }
+
// `Event` type is used instead of `InputEvent`
// since the types from Stencil are not derived
// from the element (e.g. textarea and input
@@ -595,6 +651,7 @@ export class Textarea implements ComponentInterface {
'label-text-wrapper': true,
'label-text-wrapper-hidden': !this.hasLabel,
}}
+ part="label"
>
{label === undefined ? : {label}
}
@@ -701,10 +758,10 @@ export class Textarea implements ComponentInterface {
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
return [
-
+
{!isInvalid ? helperText : null}
,
-
+
{isInvalid ? errorText : null}
,
];
@@ -730,7 +787,11 @@ export class Textarea implements ComponentInterface {
return;
}
- return
{getCounterText(value, maxlength, counterFormatter)}
;
+ return (
+
+ {getCounterText(value, maxlength, counterFormatter)}
+
+ );
}
/**
@@ -752,7 +813,7 @@ export class Textarea implements ComponentInterface {
}
return (
-
+
{this.renderHintText()}
{this.renderCounter()}
@@ -802,6 +863,7 @@ export class Textarea implements ComponentInterface {
[`textarea-shape-${shape}`]: shape !== undefined,
[`textarea-size-${size}`]: true,
[`textarea-label-placement-${labelPlacement}`]: true,
+ 'in-item': inItem,
'textarea-disabled': disabled,
'textarea-readonly': readonly,
})}
@@ -837,9 +899,10 @@ export class Textarea implements ComponentInterface {
-
(this.textareaWrapper = el)}>
+
(this.textareaWrapper = el)} part="container">