11import type { LitElement } from 'lit' ;
2- import { property } from 'lit/decorators.js' ;
2+ import { property , state } from 'lit/decorators.js' ;
33import { addSafeEventListener , isFunction , isString } from '../../util.js' ;
44import type { Validator } from '../../validators.js' ;
55import type { Constructor } from '../constructor.js' ;
66import type { FormValue } from './form-value.js' ;
7- import type {
8- FormAssociatedCheckboxElementInterface ,
9- FormAssociatedElementInterface ,
10- FormRestoreMode ,
11- FormValueType ,
7+ import {
8+ type FormAssociatedCheckboxElementInterface ,
9+ type FormAssociatedElementInterface ,
10+ type FormRestoreMode ,
11+ type FormValueType ,
12+ InternalInvalidEvent ,
13+ InternalResetEvent ,
1214} from './types.js' ;
1315
16+ const eventOptions = {
17+ bubbles : false ,
18+ composed : false ,
19+ } ;
20+
21+ function emitFormInvalidEvent ( host : LitElement ) : void {
22+ host . dispatchEvent ( new CustomEvent ( InternalInvalidEvent , eventOptions ) ) ;
23+ }
24+
25+ function emitFormResetEvent ( host : LitElement ) : void {
26+ host . dispatchEvent ( new CustomEvent ( InternalResetEvent , eventOptions ) ) ;
27+ }
28+
1429function BaseFormAssociated < T extends Constructor < LitElement > > ( base : T ) {
1530 class BaseFormAssociatedElement extends base {
1631 public static readonly formAssociated = true ;
@@ -20,9 +35,25 @@ function BaseFormAssociated<T extends Constructor<LitElement>>(base: T) {
2035
2136 protected _disabled = false ;
2237 protected _invalid = false ;
23- protected _dirty = false ;
2438 protected _pristine = true ;
2539
40+ @state ( )
41+ private _isFormSubmit = false ;
42+
43+ @state ( )
44+ private _touched = false ;
45+
46+ @state ( )
47+ private _isInternalValidation = false ;
48+
49+ private get _shouldApplyStyles ( ) : boolean {
50+ return (
51+ this . _invalid &&
52+ ( this . _touched || this . _isFormSubmit ) &&
53+ ! this . _isInternalValidation
54+ ) ;
55+ }
56+
2657 protected get __validators ( ) : Validator [ ] {
2758 return [ ] ;
2859 }
@@ -55,13 +86,12 @@ function BaseFormAssociated<T extends Constructor<LitElement>>(base: T) {
5586 * @default false
5687 */
5788 @property ( { type : Boolean , reflect : true } )
58- public set invalid ( value : boolean ) {
59- this . _invalid = value ;
60- this . toggleAttribute ( 'invalid' , Boolean ( this . _invalid ) ) ;
89+ public set invalid ( _ : boolean ) {
90+ this . _setInvalidStyles ( ) ;
6191 }
6292
6393 public get invalid ( ) : boolean {
64- return this . _invalid ;
94+ return this . _shouldApplyStyles ;
6595 }
6696
6797 /** Returns the HTMLFormElement associated with this element. */
@@ -97,39 +127,68 @@ function BaseFormAssociated<T extends Constructor<LitElement>>(base: T) {
97127 addSafeEventListener ( this , 'invalid' , this . _handleInvalid ) ;
98128 }
99129
130+ /** @internal */
100131 public override connectedCallback ( ) : void {
101132 super . connectedCallback ( ) ;
102- this . _dirty = false ;
133+ this . _pristine = true ;
134+ this . _touched = false ;
103135 this . _updateValidity ( ) ;
136+ this . _setInvalidStyles ( ) ;
104137 }
105138
106- private _handleInvalid ( event : Event ) {
139+ private _setInvalidStyles ( ) : void {
140+ this . toggleAttribute ( 'invalid' , this . _shouldApplyStyles ) ;
141+ this . requestUpdate ( ) ;
142+ }
143+
144+ private _handleInvalid ( event : Event ) : void {
107145 event . preventDefault ( ) ;
108- this . invalid = true ;
146+ this . _invalid = true ;
147+
148+ if ( this . _isInternalValidation ) {
149+ this . _isInternalValidation = false ;
150+ } else {
151+ this . _isFormSubmit = true ;
152+ emitFormInvalidEvent ( this ) ;
153+ }
154+
155+ this . _setInvalidStyles ( ) ;
156+ this . _isFormSubmit = false ;
109157 }
110158
111- private _setInvalidState ( ) : void {
112- if ( this . _dirty || ! this . _pristine ) {
113- this . invalid = ! this . checkValidity ( ) ;
159+ protected _handleBlur ( ) : void {
160+ if ( ! this . _touched ) {
161+ this . _touched = true ;
162+ }
163+ this . _validate ( ) ;
164+ }
165+
166+ protected _setTouchedState ( ) : void {
167+ if ( ! this . _touched ) {
168+ this . _touched = true ;
114169 }
115170 }
116171
117172 private __runValidators ( ) {
118173 const validity : ValidityStateFlags = { } ;
119174 let message = '' ;
175+ let isInvalid = false ;
120176
121177 for ( const validator of this . __validators ) {
122178 const isValid = validator . isValid ( this ) ;
123179
124180 validity [ validator . key ] = ! isValid ;
125181
126182 if ( ! isValid ) {
183+ isInvalid = true ;
127184 message = isFunction ( validator . message )
128185 ? validator . message ( this )
129186 : validator . message ;
130187 }
131188 }
132189
190+ this . _invalid = isInvalid ;
191+
133192 return { validity, message } ;
134193 }
135194
@@ -145,13 +204,12 @@ function BaseFormAssociated<T extends Constructor<LitElement>>(base: T) {
145204
146205 protected _validate ( message ?: string ) : void {
147206 this . _updateValidity ( message ) ;
148- this . _setInvalidState ( ) ;
149207 }
150208
151209 /**
152210 * Executes the component validators and updates the internal validity state.
153211 */
154- protected _updateValidity ( error ?: string ) {
212+ protected _updateValidity ( error ?: string ) : void {
155213 let { validity, message } = this . __runValidators ( ) ;
156214 const hasCustomError = this . validity . customError ;
157215
@@ -178,11 +236,15 @@ function BaseFormAssociated<T extends Constructor<LitElement>>(base: T) {
178236 }
179237
180238 this . __internals . setValidity ( validity , message ) ;
239+ this . _isInternalValidation = true ;
240+ this . _invalid = ! this . __internals . checkValidity ( ) ;
181241 }
182242
183243 protected _setFormValue ( value : FormValueType , state ?: FormValueType ) : void {
184244 this . _pristine = false ;
185245 this . __internals . setFormValue ( value , state ) ;
246+ this . _updateValidity ( ) ;
247+ this . _setInvalidStyles ( ) ;
186248 }
187249
188250 protected formAssociatedCallback ( _form : HTMLFormElement ) : void { }
@@ -195,8 +257,10 @@ function BaseFormAssociated<T extends Constructor<LitElement>>(base: T) {
195257 protected formResetCallback ( ) : void {
196258 this . _restoreDefaultValue ( ) ;
197259 this . _pristine = true ;
198- this . _dirty = false ;
199- this . invalid = false ;
260+ this . _touched = false ;
261+ this . _invalid = false ;
262+ this . _setInvalidStyles ( ) ;
263+ emitFormResetEvent ( this ) ;
200264 }
201265
202266 /* c8 ignore next 4 */
@@ -206,24 +270,25 @@ function BaseFormAssociated<T extends Constructor<LitElement>>(base: T) {
206270 ) : void { }
207271
208272 /** Checks for validity of the control and shows the browser message if it invalid. */
209- public reportValidity ( ) {
273+ public reportValidity ( ) : boolean {
210274 const state = this . __internals . reportValidity ( ) ;
211- this . invalid = ! state ;
275+ this . _invalid = ! state ;
212276 return state ;
213277 }
214278
215279 /** Checks for validity of the control and emits the invalid event if it invalid. */
216- public checkValidity ( ) {
280+ public checkValidity ( ) : boolean {
281+ this . _isInternalValidation = true ;
217282 const state = this . __internals . checkValidity ( ) ;
218- this . invalid = ! state ;
283+ this . _invalid = ! state ;
219284 return state ;
220285 }
221286
222287 /**
223288 * Sets a custom validation message for the control.
224289 * As long as `message` is not empty, the control is considered invalid.
225290 */
226- public setCustomValidity ( message : string ) {
291+ public setCustomValidity ( message : string ) : void {
227292 this . _updateValidity ( message ) ;
228293 }
229294 }
@@ -250,7 +315,7 @@ export function FormAssociatedMixin<T extends Constructor<LitElement>>(
250315 }
251316 }
252317
253- public get defaultValue ( ) {
318+ public get defaultValue ( ) : unknown {
254319 return this . _formValue . defaultValue ;
255320 }
256321
0 commit comments