@@ -34,6 +34,8 @@ export interface BlnInputProps {
3434 ariaLabel : string ;
3535 ariaLabelledby : string ;
3636 ariaDescribedby : string ;
37+ /** Optional externe Validierungsfunktion. Nur als Property (nicht als Attribut) setzbar. */
38+ validator ?: ( value : string , el : BlnInput ) => boolean | { valid : boolean ; message ?: string } ;
3739}
3840
3941@customElement ( 'bln-input' )
@@ -60,6 +62,8 @@ export class BlnInput extends TailwindElement {
6062 @property ( ) step : BlnInputProps [ 'step' ] = undefined as any ;
6163 @property ( ) inputmode : BlnInputProps [ 'inputmode' ] = undefined as any ;
6264 @property ( ) autocomplete : BlnInputProps [ 'autocomplete' ] = "" ;
65+ /** Externe Validierungsfunktion: nur als Property setzbar (attribute: false). */
66+ @property ( { attribute : false } ) validator ?: BlnInputProps [ 'validator' ] ;
6367
6468 // Sizing/Styles
6569 @property ( ) size : BlnInputProps [ 'size' ] = "medium" ;
@@ -80,15 +84,22 @@ export class BlnInput extends TailwindElement {
8084 private onInput = ( e : Event ) => {
8185 const input = e . currentTarget as HTMLInputElement ;
8286 this . value = input . value ;
87+ // Run external validation if provided
88+ this . runValidation ( ) ;
8389 this . dispatchEvent ( new Event ( 'input' , { bubbles : true , composed : true } ) ) ;
8490 } ;
8591
8692 private onChange = ( _e : Event ) => {
93+ this . runValidation ( ) ;
8794 this . dispatchEvent ( new Event ( 'change' , { bubbles : true , composed : true } ) ) ;
8895 } ;
8996
9097 protected willUpdate ( changed : Map < string , any > ) {
9198 if ( changed . has ( 'isValid' ) ) this . _isValidSet = true ;
99+ if ( changed . has ( 'value' ) ) {
100+ // When value changes programmatically, also re-run validation if provided
101+ this . runValidation ( ) ;
102+ }
92103 }
93104
94105 protected render ( ) {
@@ -174,6 +185,32 @@ export class BlnInput extends TailwindElement {
174185 ${ this . error ? html `< span id ="${ this . _errorId } " class ="mt-1 text-sm text-red-600 " role ="alert " aria-live ="polite "> ${ this . error } </ span > ` : '' }
175186 </ div > ` ;
176187 }
188+ private runValidation ( ) {
189+ if ( ! this . validator ) return ;
190+ const v = ( this . value ?? '' ) ;
191+ // Neutral state for empty unless validator explicitly handles it
192+ if ( v === '' ) {
193+ // Clear validity and error for neutral display
194+ this . isValid = undefined as any ;
195+ this . error = '' as any ;
196+ return ;
197+ }
198+ try {
199+ const res = this . validator ( v , this as any ) ;
200+ const result = typeof res === 'boolean' ? { valid : res } : res ?? { valid : true } ;
201+ this . isValid = result . valid as any ;
202+ // If invalid and message provided, set error; otherwise clear error to avoid stale messages
203+ this . error = ( ! result . valid && result . message ) ? result . message : ( ! result . valid ? ( this . error || '' ) : '' ) ;
204+ // Ensure we show validity icons once validator ran
205+ this . _isValidSet = true ;
206+ this . dispatchEvent ( new CustomEvent ( 'validitychange' , { detail : { isValid : this . isValid , message : this . error } , bubbles : true , composed : true } ) ) ;
207+ } catch ( e ) {
208+ // On validator error, do not break UI; mark invalid with generic message
209+ this . isValid = false as any ;
210+ this . error = this . error || 'Ungültiger Wert' ;
211+ this . _isValidSet = true ;
212+ }
213+ }
177214}
178215
179216const nothing = undefined ;
0 commit comments