iter(g348): 表单标签与控件建立 for / aria-labelledby 关联

This commit is contained in:
HIsheR
2026-10-27 02:33:02 +08:00
parent 840025b66b
commit 3a46b24c02
3 changed files with 27 additions and 4 deletions
+9 -2
View File
@@ -15,10 +15,17 @@ export function formItem(props: FormItemProps, control: HTMLElement): FormItemHa
// —— 状态集中声明 ——
const mode = props.validateOn ?? 'blur'; // 校验时机;缺省失焦校验
const rules = [...(props.required ? [{ required: true }] : []), ...(props.rules ?? [])]; // 必填规则排最前
const fid = `ua-form-${(uid += 1)}`; // 本字段唯一后缀:错误区与辅助说明各自派生 id
const fid = `ua-form-${(uid += 1)}`; // 本字段唯一后缀:错误区 / 辅助说明 / 标签各自派生 id
// 标签归属:恰好一个原生控件才用 for(单选组的 for 会变成「点标签选中第一项」),组类控件改用 aria-labelledby
const native = Array.from(control.querySelectorAll<HTMLElement>('input:not([type="hidden"]), textarea, select'));
const only = (control.matches('input:not([type="hidden"]), textarea, select') ? [control] : native).length === 1
? (control.matches('input:not([type="hidden"]), textarea, select') ? control : native[0])
: null;
if (only && !only.id) only.id = `${fid}-control`; // 组件自带的 id 不动:可能是它内部逻辑的锚
if (!only) (control.hasAttribute('role') ? control : focusableOf(control))?.setAttribute('aria-labelledby', `${fid}-label`); // 自绘下拉 / 单选组靠标签 id 取名
const error = h('div', { class: 'ua-form-error', id: `${fid}-error` }); // 错误区:id 供 aria-describedby 指过来
const hint = props.hint ? h('div', { class: 'ua-form-hint', id: `${fid}-hint` }, props.hint) : null; // 辅助说明:报错时隐藏,避免与错误挤在一起
const label = h('label', { class: 'ua-form-label' }, props.label, props.required ? h('i', { class: 'ua-form-req' }, '*') : null);
const label = h('label', { class: 'ua-form-label', for: only?.id ?? null, id: `${fid}-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, hint, error));
if (props.span) el.style.gridColumn = `span ${props.span}`; // 多列布局里跨列;单列容器下由 CSS 还原为整行
// 条件显示:隐藏时顺带清掉错误态,否则「看不见的红框」会拦住提交