Skip to content

Commit 01aef36

Browse files
lorenloren
authored andcommitted
Add external validation support to BlnSelect
1 parent 0c0f095 commit 01aef36

2 files changed

Lines changed: 64 additions & 21 deletions

File tree

src/components/BlnSelect.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ export interface BlnSelectProps {
2828
ariaLabel: string;
2929
ariaLabelledby: string;
3030
ariaDescribedby: string;
31+
/** Optional externe Validierungsfunktion. Nur als Property (nicht als Attribut) setzbar. */
32+
validator?: (value: string | string[], el: BlnSelect) => boolean | { valid: boolean; message?: string };
33+
3134
}
3235

3336
@customElement("bln-select")
@@ -57,12 +60,42 @@ export class BlnSelect extends TailwindElement {
5760

5861
// Options (programmatisch gesetzt); alternativ kann Slot <option> genutzt werden
5962
@property({attribute: false}) options: BlnSelectProps["options"] = [];
63+
/** Externe Validierungsfunktion: nur als Property setzbar (attribute: false). */
64+
@property({attribute: false}) validator?: BlnSelectProps['validator'];
65+
6066

6167
// interner IDs
6268
@state() private _selectId = `bln-select-${Math.random().toString(36).slice(2)}`;
6369
@state() private _hintId = `bln-select-hint-${Math.random().toString(36).slice(2)}`;
6470
@state() private _isValidSet = false;
6571

72+
private runValidation() {
73+
if (!this.validator) return;
74+
75+
try {
76+
const result = this.validator(this.value, this);
77+
if (typeof result === 'boolean') {
78+
this.isValid = result;
79+
} else if (result && typeof result === 'object') {
80+
this.isValid = result.valid;
81+
// Optional: Fehlermeldung im hint anzeigen
82+
// if (result.message) { ... }
83+
}
84+
this.dispatchEvent(new CustomEvent('validitychange', {
85+
detail: {
86+
valid: this.isValid,
87+
value: this.value
88+
},
89+
bubbles: true,
90+
composed: true
91+
}));
92+
} catch (e) {
93+
console.error('Validation error:', e);
94+
this.isValid = false;
95+
}
96+
}
97+
98+
6699
private onChange = (e: Event) => {
67100
const sel = e.currentTarget as HTMLSelectElement;
68101
if (this.multiple) {
@@ -71,6 +104,9 @@ export class BlnSelect extends TailwindElement {
71104
} else {
72105
this.value = sel.value;
73106
}
107+
// Run external validation if provided
108+
this.runValidation();
109+
74110
// Events nach außen weiterreichen
75111
this.dispatchEvent(new Event("change", {bubbles: true, composed: true}));
76112
this.dispatchEvent(new Event("input", {bubbles: true, composed: true}));
@@ -115,8 +151,13 @@ export class BlnSelect extends TailwindElement {
115151

116152
protected willUpdate(changed: Map<string, any>) {
117153
if (changed.has('isValid')) this._isValidSet = true;
154+
if (changed.has('value')) {
155+
// When value changes programmatically, also re-run validation if provided
156+
this.runValidation();
157+
}
118158
}
119159

160+
120161
protected render() {
121162
// A11y: aria-describedby automatisch, wenn hint vorhanden
122163
const describedBy = [

src/components/FormBuilder.ts

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -296,27 +296,29 @@ class FormBuilder {
296296
}
297297

298298
// Convenience: add a BlnSelect
299-
addBlnSelect(props: Partial<BlnSelectProps> = {}) {
300-
const tpl = html`<bln-select
301-
.label=${props.label ?? ''}
302-
.cornerHint=${props.cornerHint ?? ''}
303-
.hint=${props.hint ?? ''}
304-
.name=${props.name ?? ''}
305-
.placeholder=${props.placeholder ?? ''}
306-
.value=${props.value ?? ''}
307-
.disabled=${props.disabled ?? false}
308-
.required=${props.required ?? false}
309-
.multiple=${props.multiple ?? false}
310-
.size=${props.size ?? 'medium'}
311-
.class=${props.class ?? ''}
312-
.options=${props.options ?? []}
313-
.ariaLabel=${props.ariaLabel ?? ''}
314-
.ariaLabelledby=${props.ariaLabelledby ?? ''}
315-
.ariaDescribedby=${props.ariaDescribedby ?? ''}
316-
></bln-select>`;
317-
this.fields.push(tpl);
318-
return this;
319-
}
299+
addBlnSelect(props: Partial<BlnSelectProps> = {}) {
300+
const tpl = html`<bln-select
301+
.label=${props.label ?? ''}
302+
.cornerHint=${props.cornerHint ?? ''}
303+
.hint=${props.hint ?? ''}
304+
.name=${props.name ?? ''}
305+
.placeholder=${props.placeholder ?? ''}
306+
.value=${props.value ?? ''}
307+
.disabled=${props.disabled ?? false}
308+
.required=${props.required ?? false}
309+
.multiple=${props.multiple ?? false}
310+
.size=${props.size ?? 'medium'}
311+
.class=${props.class ?? ''}
312+
.options=${props.options ?? []}
313+
.ariaLabel=${props.ariaLabel ?? ''}
314+
.ariaLabelledby=${props.ariaLabelledby ?? ''}
315+
.ariaDescribedby=${props.ariaDescribedby ?? ''}
316+
.validator=${props.validator ?? undefined}
317+
></bln-select>`;
318+
this.fields.push(tpl);
319+
return this;
320+
}
321+
320322

321323
addBlnAutocompleteSelect(props: Partial<BlnAutocompleteSelectProps> = {}) {
322324
const tpl = html`<bln-autocomplete-select

0 commit comments

Comments
 (0)