@@ -9,8 +9,11 @@ namespace BitBlazor.Form;
99/// Represents the base class for the form components
1010/// </summary>
1111/// <typeparam name="T">The data type supported by the component</typeparam>
12- public abstract class BitFormComponentBase < T > : BitComponentBase
12+ public abstract class BitFormComponentBase < T > : BitComponentBase , IDisposable
1313{
14+ private string validationCssClass = string . Empty ;
15+ private bool disposedValue ;
16+
1417 /// <summary>
1518 /// Gets or sets the <see cref="EditContext"/> instance in case of use of an <see cref="EditForm"/>
1619 /// </summary>
@@ -112,6 +115,30 @@ protected BitFormComponentBase()
112115 /// </remarks>
113116 protected Type ComponentType => Nullable . GetUnderlyingType ( typeof ( T ) ) ?? typeof ( T ) ;
114117
118+ protected override void OnInitialized ( )
119+ {
120+ base . OnInitialized ( ) ;
121+ if ( CurrentEditContext is not null )
122+ {
123+ CurrentEditContext . OnValidationStateChanged += OnFieldValidationStateChanged ;
124+ }
125+ }
126+
127+ private void OnFieldValidationStateChanged ( object ? sender , ValidationStateChangedEventArgs e )
128+ {
129+ if ( ValueExpression is not null )
130+ {
131+ var fieldIdentifier = FieldIdentifier . Create ( ValueExpression ) ;
132+ var fieldValidationCssClass = CurrentEditContext ! . IsValid ( fieldIdentifier ) ? "just-validate-success-field" : "is-invalid" ;
133+
134+ if ( fieldValidationCssClass != validationCssClass )
135+ {
136+ validationCssClass = fieldValidationCssClass ;
137+ InvokeAsync ( StateHasChanged ) ;
138+ }
139+ }
140+ }
141+
115142 /// <inheritdoc/>
116143 protected override void OnParametersSet ( )
117144 {
@@ -157,6 +184,44 @@ private void SetAdditionalTextAttributes()
157184 }
158185 }
159186
187+ /// <summary>
188+ /// Updates the validation CSS class based on the current validation state of the field.
189+ /// </summary>
190+ /// <remarks>
191+ /// This method checks the EditContext for validation state and applies the appropriate CSS class:
192+ /// <list type="bullet">
193+ /// <item><description>"is-invalid" - field has validation errors (shown immediately when validation runs)</description></item>
194+ /// <item><description>"just-validate-success-field" - field is modified and is valid</description></item>
195+ /// <item><description>Empty string - field is valid and has not been modified</description></item>
196+ /// </list>
197+ /// </remarks>
198+ private void UpdateValidationCssClass ( )
199+ {
200+ if ( CurrentEditContext is null || ValueExpression is null )
201+ {
202+ validationCssClass = string . Empty ;
203+ return ;
204+ }
205+
206+ var fieldIdentifier = FieldIdentifier . Create ( ValueExpression ) ;
207+
208+ validationCssClass = CurrentEditContext . IsValid ( fieldIdentifier ) ? "just-validate-success-field" : "is-invalid" ;
209+ }
210+
211+ /// <summary>
212+ /// Adds the Bootstrap Italia validation CSS class to the provided <see cref="CssClassBuilder"/>.
213+ /// </summary>
214+ /// <param name="builder">The CSS class builder to add the validation class to.</param>
215+ /// <remarks>
216+ /// Adds "is-invalid" for invalid fields, "just-validate-success-field" for valid modified fields,
217+ /// or nothing for unmodified fields. This method should be called when building the CSS classes
218+ /// for form input elements.
219+ /// </remarks>
220+ protected void AddValidationCssClass ( CssClassBuilder builder )
221+ {
222+ builder . Add ( validationCssClass ) ;
223+ }
224+
160225 /// <summary>
161226 /// Renders a validation message for the specified field.
162227 /// </summary>
@@ -209,4 +274,27 @@ private void SetAdditionalTextAttributes()
209274 builder . CloseElement ( ) ;
210275 } ;
211276 }
277+
278+ protected virtual void Dispose ( bool disposing )
279+ {
280+ if ( ! disposedValue )
281+ {
282+ if ( disposing )
283+ {
284+ if ( CurrentEditContext is not null )
285+ {
286+ CurrentEditContext . OnValidationStateChanged -= OnFieldValidationStateChanged ;
287+ }
288+ }
289+
290+ disposedValue = true ;
291+ }
292+ }
293+
294+ void IDisposable . Dispose ( )
295+ {
296+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
297+ Dispose ( disposing : true ) ;
298+ GC . SuppressFinalize ( this ) ;
299+ }
212300}
0 commit comments