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 };
}