fix(forms): surface validation and recover schema drift
Quality / quality (ubuntu-latest) (push) Failing after 10m23s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-15 13:01:10 +05:30
parent 1d16ef1e82
commit d78707be9f
10 changed files with 128 additions and 19 deletions
+18 -4
View File
@@ -33,6 +33,7 @@ size: string = "default"
required: boolean = false
class: string = ""
}
state validationError: string = ""
functions {
shared function selected(option) {
if (multiple) return values.includes(option.value)
@@ -40,34 +41,47 @@ size: string = "default"
}
client function emitField(nameEvent, sourceEvent) {
sourceEvent.stopPropagation()
if (sourceEvent.currentTarget.validity.valid) { validationError = ""; sourceEvent.currentTarget.setAttribute("aria-invalid", error ? "true" : "false"); sourceEvent.currentTarget.closest(".wrn-next--field").setAttribute("data-invalid", error ? "true" : "false") }
output[nameEvent]({ value: sourceEvent.currentTarget.value, name: name, sourceEvent: sourceEvent })
}
client function handleFocus(sourceEvent) { emitField("focus", sourceEvent); output.open({ name: name, sourceEvent: sourceEvent }) }
client function handleBlur(sourceEvent) { emitField("blur", sourceEvent); output.close({ name: name, sourceEvent: sourceEvent }) }
client function handleInvalid(sourceEvent) { output.invalid({ name: name, message: sourceEvent.currentTarget.validationMessage, sourceEvent: sourceEvent }) }
client function handleInvalid(sourceEvent) { validationError = sourceEvent.currentTarget.validationMessage; sourceEvent.currentTarget.setAttribute("aria-invalid", "true"); sourceEvent.currentTarget.closest(".wrn-next--field").setAttribute("data-invalid", "true"); output.invalid({ name: name, message: validationError, sourceEvent: sourceEvent }) }
}
view {
<div {...attrs} class="wrn-component wrn-component--color-{color} wrn-component--size-{size} wrn-next--field wrn-next--select {class}" data-variant="{variant}" data-inline="{inline}" data-floating="{variant === 'floating'}" data-invalid="{error ? 'true' : 'false'}" data-readonly="{readonly}">
<div {...attrs} class="wrn-component wrn-component--color-{color} wrn-component--size-{size} wrn-next--field wrn-next--select {class}" data-variant="{variant}" data-inline="{inline}" data-floating="{variant === 'floating'}" data-invalid="{error || validationError ? 'true' : 'false'}" data-readonly="{readonly}">
<div class="wrn-next__field-heading"><label class="{hiddenLabel ? 'wrn-next__sr-only' : ''}" for="{id || name}">{label}</label>{#if cornerHint}<span class="wrn-next__field-hint">{cornerHint}</span>{/if}</div>
<div class="wrn-next__field-control" data-icon-position="{iconPosition}">
{#if icon}<span class="{icon} wrn-next__field-icon" aria-hidden="true"></span>{/if}
<select id="{id || name}" name="{name}" multiple="{multiple}" disabled="{disabled || readonly}" required="{required}" aria-readonly="{readonly}" aria-invalid="{error ? 'true' : 'false'}" @input="emitField('input', event)" @change="emitField('change', event)" @focus="handleFocus(event)" @blur="handleBlur(event)" @invalid="handleInvalid(event)">
<select id="{id || name}" name="{name}" multiple="{multiple}" disabled="{disabled || readonly}" required="{required}" aria-readonly="{readonly}" aria-invalid="{error || validationError ? 'true' : 'false'}" aria-describedby="{error || validationError ? (id || name) + '-error' : (helperText ? (id || name) + '-help' : '')}" @input="emitField('input', event)" @change="emitField('change', event)" @focus="handleFocus(event)" @blur="handleBlur(event)" @invalid="handleInvalid(event)">
{#if !multiple}<option value="" selected="{value === ''}" disabled="{required}">{placeholder}</option>{/if}
{#each options as option}<option value="{option.value}" selected="{selected(option)}" disabled="{option.disabled}">{option.label}</option>{/each}
</select>
{#if !multiple}<span class="icon-[lucide--chevron-down] wrn-next__select-indicator" aria-hidden="true"></span>{/if}
{#if variant === "floating"}<span class="wrn-next__field-floating-label">{label}</span>{/if}
</div>
{#if helperText}<small class="wrn-next__field-help">{helperText}</small>{/if}
<small class="wrn-next__field-error" data-error="{name}">{error}</small>
<small id="{(id || name) + '-error'}" class="wrn-next__field-error" data-error="{name}" aria-live="polite">{error || validationError}</small>
</div>
}
style {
.wrn-next--select select {
appearance: none;
width: 100%;
min-height: 2.65rem;
margin: 0;
padding-inline-end: 2.5rem;
line-height: 1.35;
color: var(--wrn-color-text);
background-color: var(--wrn-color-surface);
}
.wrn-next__select-indicator {
position: absolute;
right: 0.85rem;
color: var(--wrn-color-muted);
pointer-events: none;
}
/* Shared component foundation. */