11import type { ComponentInterface , EventEmitter } from '@stencil/core' ;
22import {
3+ AttachInternals ,
34 Build ,
45 Component ,
56 Element ,
@@ -15,7 +16,7 @@ import {
1516 writeTask ,
1617} from '@stencil/core' ;
1718import type { NotchController } from '@utils/forms' ;
18- import { createNotchController } from '@utils/forms' ;
19+ import { createNotchController , reportValidityToElementInternals } from '@utils/forms' ;
1920import type { Attributes } from '@utils/helpers' ;
2021import { inheritAriaAttributes , debounceEvent , inheritAttributes , componentOnReady } from '@utils/helpers' ;
2122import { createSlotMutationController } from '@utils/slot-mutation-controller' ;
@@ -43,7 +44,8 @@ import type { TextareaChangeEventDetail, TextareaInputEventDetail } from './text
4344 md : 'textarea.md.scss' ,
4445 ionic : 'textarea.ionic.scss' ,
4546 } ,
46- scoped : true ,
47+ shadow : true ,
48+ formAssociated : true
4749} )
4850export class Textarea implements ComponentInterface {
4951 private nativeInput ?: HTMLTextAreaElement ;
@@ -73,6 +75,8 @@ export class Textarea implements ComponentInterface {
7375
7476 @Element ( ) el ! : HTMLIonTextareaElement ;
7577
78+ @AttachInternals ( ) internals ! : ElementInternals ;
79+
7680 /**
7781 * The `hasFocus` state ensures the focus class is
7882 * added regardless of how the element is focused.
@@ -184,7 +188,7 @@ export class Textarea implements ComponentInterface {
184188 /**
185189 * If `true`, the user must fill in a value before submitting a form.
186190 */
187- @Prop ( ) required = false ;
191+ @Prop ( { reflect : true } ) required = false ;
188192
189193 /**
190194 * If `true`, the element will have its spelling and grammar checked.
@@ -288,6 +292,15 @@ export class Textarea implements ComponentInterface {
288292 nativeInput . value = value ;
289293 }
290294 this . runAutoGrow ( ) ;
295+ this . reportValidity ( ) ;
296+ }
297+
298+ /**
299+ * Update validation state when required prop changes
300+ */
301+ @Watch ( 'required' )
302+ protected requiredChanged ( ) {
303+ this . reportValidity ( ) ;
291304 }
292305
293306 /**
@@ -433,6 +446,7 @@ export class Textarea implements ComponentInterface {
433446 componentDidLoad ( ) {
434447 this . originalIonInput = this . ionInput ;
435448 this . runAutoGrow ( ) ;
449+ this . reportValidity ( ) ;
436450 }
437451
438452 componentDidRender ( ) {
@@ -554,6 +568,15 @@ export class Textarea implements ComponentInterface {
554568 return this . value || '' ;
555569 }
556570
571+ /**
572+ * Reports the validity state to the browser via ElementInternals.
573+ * This delegates to the native textarea's built-in validation,
574+ * which automatically handles the required prop and other constraints.
575+ */
576+ private reportValidity ( ) {
577+ reportValidityToElementInternals ( this . nativeInput , this . internals ) ;
578+ }
579+
557580 // `Event` type is used instead of `InputEvent`
558581 // since the types from Stencil are not derived
559582 // from the element (e.g. textarea and input
@@ -568,6 +591,11 @@ export class Textarea implements ComponentInterface {
568591 } ;
569592
570593 private onChange = ( ev : Event ) => {
594+ const input = ev . target as HTMLTextAreaElement | null ;
595+ if ( input ) {
596+ this . internals . setFormValue ( input . value ) ;
597+ this . reportValidity ( ) ;
598+ }
571599 this . emitValueChange ( ev ) ;
572600 } ;
573601
0 commit comments