iter: d17 表单错误态覆盖包裹式控件 + 校验时机事件

This commit is contained in:
ui-arch-bot
2026-10-23 16:59:46 +08:00
parent fa168b9a1c
commit bdc1fec3f9
8 changed files with 41 additions and 10 deletions
+13 -3
View File
@@ -26,7 +26,13 @@ export function formItem(props: FormItemProps, control: HTMLElement): FormItemHa
const label = h('label', { class: 'ua-form-label' }, props.label, props.required ? h('i', { class: 'ua-form-req' }, '*') : null);
const el = h('div', { class: 'ua-form-item' }, label, h('div', { class: 'ua-form-control' }, control, error));
/*** 执行全部规则:返回首个错误消息,同时同步错误区与控件态 ***/
/*** 错误态落点:控件根 + 内部原生控件,保证 select / checkbox 这类包裹式控件也能标红 ***/
const marked = (): HTMLElement[] => [
control,
...Array.from(control.querySelectorAll<HTMLElement>('input, textarea, select, .ua-select-trigger')),
];
/*** 执行全部规则:返回首个错误消息,同时同步错误区、控件态与 aria-invalid ***/
const validate = () => {
const value = props.value();
let msg: string | null = null;
@@ -36,14 +42,18 @@ export function formItem(props: FormItemProps, control: HTMLElement): FormItemHa
}
error.textContent = msg ?? '';
control.classList.toggle('ua-invalid', !!msg);
for (const node of marked()) node.setAttribute('aria-invalid', String(!!msg)); // 读屏同步错误态
return msg;
};
control.addEventListener('input', () => {
if (mode === 'input' || error.textContent) validate(); // input 模式即时校验;其余模式仅清已报错误
});
control.addEventListener('blur', () => {
if (mode !== 'submit') validate();
control.addEventListener('change', () => {
if (mode === 'input') validate(); // select / checkbox / radio 变更即校验(原生 input 事件不冒泡)
});
control.addEventListener('focusout', () => {
if (mode !== 'submit') validate(); // 用会冒泡的 focusout 取代 blur,包裹式控件同样生效
});
return { el, validate };
}
+3 -1
View File
@@ -1076,7 +1076,9 @@
"把句柄数组交给 form:form({ onSubmit }, items),form 渲染全部字段 + 提交/重置按钮",
"提交时逐项校验:全过触发 onSubmit;有错则标红控件、显示错误文案并聚焦首个出错项",
"规则语义:required 非空(trim)、min/max 长度、pattern 正则、validator 自定义(true 通过 / 字符串为错误)",
"非必填字段为空时跳过其余规则;Enter 键触发表单原生 submit(已拦截走校验流程)"
"非必填字段为空时跳过其余规则;Enter 键触发表单原生 submit(已拦截走校验流程)",
"错误态覆盖包裹式控件:标红作用于控件根与内部原生控件(含自绘 select 的触发器),并同步 aria-invalid,读屏与视觉一致",
"触发时机用 input / change / focusout(均会冒泡),因此 checkbox / select / 单选组这类包裹式控件同样能失焦校验"
]
},
{
+7 -2
View File
@@ -1003,8 +1003,13 @@
.ua-form-error:empty {
display: none;
}
/* 校验失败:控件描红 + 危险色光环 */
.ua-invalid {
/* 校验失败:控件描红 + 危险色光环(root 与包裹式控件的内部触发器一并覆盖) */
.ua-invalid,
.ua-invalid .ua-input,
.ua-invalid .ua-textarea,
.ua-invalid .ua-select-trigger,
.ua-invalid .ua-radio,
.ua-invalid .ua-slider-input {
border-color: var(--ua-color-danger) !important;
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ua-color-danger) 15%, transparent) !important;
}