iter(g344): 错误汇总条随字段校验实时重算
This commit is contained in:
@@ -1,34 +1,8 @@
|
||||
import { effect, h } from '@ui-arch/frame';
|
||||
import { button } from './button.js';
|
||||
import { checkRule, checkRuleAsync } from './formRules.js';
|
||||
import { grid } from './grid.js';
|
||||
import type { FormItemHandle, FormItemProps, FormProps, FormRule } from './types.js';
|
||||
|
||||
/*** 执行规则的同步部分(required / min / max / pattern);null 通过,字符串为错误消息 ***/
|
||||
function syncPart(value: string, rule: FormRule): string | null {
|
||||
if (rule.required && !value.trim()) return rule.message ?? '该项为必填项';
|
||||
if (!value) return null; // 非必填且为空:其余规则跳过
|
||||
if (rule.min !== undefined && value.length < rule.min) return rule.message ?? `至少 ${rule.min} 个字符`;
|
||||
if (rule.max !== undefined && value.length > rule.max) return rule.message ?? `最多 ${rule.max} 个字符`;
|
||||
if (rule.pattern && !rule.pattern.test(value)) return rule.message ?? '格式不正确';
|
||||
return null;
|
||||
}
|
||||
|
||||
/*** 同步求值单条规则:validator 返回 Promise 时视为待定(交给异步版),null 通过 ***/
|
||||
function checkRule(value: string, rule: FormRule): string | null {
|
||||
const syncMsg = syncPart(value, rule);
|
||||
if (syncMsg || !value || !rule.validator) return syncMsg;
|
||||
const r = rule.validator(value);
|
||||
if (typeof r === 'object' && r !== null) return null; // Promise:异步规则,此处不下结论
|
||||
return r === true ? null : typeof r === 'string' ? r : rule.message ?? '校验未通过';
|
||||
}
|
||||
|
||||
/*** 异步求值单条规则:await validator,同步返回值的规则结论与同步版一致 ***/
|
||||
async function checkRuleAsync(value: string, rule: FormRule): Promise<string | null> {
|
||||
const syncMsg = syncPart(value, rule);
|
||||
if (syncMsg || !value || !rule.validator) return syncMsg;
|
||||
const r = await rule.validator(value);
|
||||
return r === true ? null : typeof r === 'string' ? r : rule.message ?? '校验未通过';
|
||||
}
|
||||
import type { FormItemHandle, FormItemProps, FormProps } from './types.js';
|
||||
|
||||
let uid = 0; // 字段序号:错误区 / 辅助说明的 id 必须全页唯一,否则多表单时读屏关联到别人家的文案
|
||||
|
||||
@@ -71,6 +45,7 @@ export function formItem(props: FormItemProps, control: HTMLElement): FormItemHa
|
||||
if (described) node.setAttribute('aria-describedby', described); // 只说「无效」不够,要把原因一起念出来
|
||||
else node.removeAttribute('aria-describedby'); // 无文案可念时摘掉,别留一个指空的关联
|
||||
}
|
||||
el.dispatchEvent(new CustomEvent('ua:field-validated')); // 不冒泡:只让归属本字段的表单重算汇总条
|
||||
};
|
||||
|
||||
/*** 同步校验:异步规则不结论,保证 blur / input 路径不被 Promise 拖慢 ***/
|
||||
@@ -131,6 +106,17 @@ export function form(props: FormProps, items: FormItemHandle[]): HTMLElement {
|
||||
// —— 状态集中声明 ——
|
||||
const summary = h('div', { class: 'ua-form-summary', role: 'alert' }) as HTMLElement; // 错误汇总:读屏能一次听到共几处要改
|
||||
summary.hidden = true;
|
||||
let quiet = false; // 重算中标记:validate → paint → 事件 → 重算 会自激,必须挡住
|
||||
/*** 汇总条实时重算:字段改了错误数要跟着掉,否则「有 2 项需要修改」会一直挂到下次提交 ***/
|
||||
const refreshSummary = (): void => {
|
||||
if (quiet || summary.hidden) return; // 卫语句:只有条子已经亮着才需要刷新
|
||||
quiet = true;
|
||||
const bad = items.filter((item) => item.validate()).length; // 同步校验足够:异步规则本就到提交时才下结论
|
||||
quiet = false;
|
||||
summary.hidden = bad === 0;
|
||||
summary.textContent = bad ? `有 ${bad} 项需要修改,已定位到第一处` : '';
|
||||
};
|
||||
for (const item of items) item.el.addEventListener('ua:field-validated', refreshSummary); // 逐字段订阅
|
||||
/*** 重置:逐项复位 + 收起汇总条,最后交给调用方回调(只跑回调等于点了没反应) ***/
|
||||
const doReset = (): void => {
|
||||
for (const item of items) item.reset();
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { FormRule } from './types.js';
|
||||
|
||||
/*** 执行规则的同步部分(required / min / max / pattern);null 通过,字符串为错误消息 ***/
|
||||
export function syncPart(value: string, rule: FormRule): string | null {
|
||||
if (rule.required && !value.trim()) return rule.message ?? '该项为必填项';
|
||||
if (!value) return null; // 非必填且为空:其余规则跳过
|
||||
if (rule.min !== undefined && value.length < rule.min) return rule.message ?? `至少 ${rule.min} 个字符`;
|
||||
if (rule.max !== undefined && value.length > rule.max) return rule.message ?? `最多 ${rule.max} 个字符`;
|
||||
if (rule.pattern && !rule.pattern.test(value)) return rule.message ?? '格式不正确';
|
||||
return null;
|
||||
}
|
||||
|
||||
/*** 同步求值单条规则:validator 返回 Promise 时视为待定(交给异步版),null 通过 ***/
|
||||
export function checkRule(value: string, rule: FormRule): string | null {
|
||||
const syncMsg = syncPart(value, rule);
|
||||
if (syncMsg || !value || !rule.validator) return syncMsg;
|
||||
const r = rule.validator(value);
|
||||
if (typeof r === 'object' && r !== null) return null; // Promise:异步规则,此处不下结论
|
||||
return r === true ? null : typeof r === 'string' ? r : rule.message ?? '校验未通过';
|
||||
}
|
||||
|
||||
/*** 异步求值单条规则:await validator,同步返回值的规则结论与同步版一致 ***/
|
||||
export async function checkRuleAsync(value: string, rule: FormRule): Promise<string | null> {
|
||||
const syncMsg = syncPart(value, rule);
|
||||
if (syncMsg || !value || !rule.validator) return syncMsg;
|
||||
const r = await rule.validator(value);
|
||||
return r === true ? null : typeof r === 'string' ? r : rule.message ?? '校验未通过';
|
||||
}
|
||||
@@ -1928,7 +1928,7 @@
|
||||
{
|
||||
"n": "errorSummary",
|
||||
"t": "boolean",
|
||||
"d": "是否在表单顶部显示「有 N 项需要修改」汇总条;缺省 true,自带错误 UI 时传 false"
|
||||
"d": "是否在表单顶部显示「有 N 项需要修改」汇总条;缺省 true,自带错误 UI 时传 false。条数随字段校验实时重算,改好一项就少一项,全部通过后自动消失"
|
||||
}
|
||||
],
|
||||
"example": "const name = signal(''); const f = form({ onSubmit: submit }, [formItem({ label: '用户名', required: true, value: name, rules: [{ min: 2, max: 12 }] }, textField({ value: name }))])",
|
||||
|
||||
Reference in New Issue
Block a user