iter(g337): 可访问名纳入 verify 门禁
This commit is contained in:
@@ -3419,6 +3419,15 @@
|
|||||||
"motivation": "selectable 表格里,表头全选框与每行复选框都没有可访问名,读屏连续播报「复选框、复选框、复选框」,用户不知道选的是哪一行;选中行的反馈只有一层淡底(视觉),tr 上没有 aria-selected,读屏完全听不出「这行已被选中」——批量操作前确认选对了没有,全靠眼睛。",
|
"motivation": "selectable 表格里,表头全选框与每行复选框都没有可访问名,读屏连续播报「复选框、复选框、复选框」,用户不知道选的是哪一行;选中行的反馈只有一层淡底(视觉),tr 上没有 aria-selected,读屏完全听不出「这行已被选中」——批量操作前确认选对了没有,全靠眼睛。",
|
||||||
"acceptance": "① 表头全选框 aria-label「全选本页」,行复选框「选择第 N 行」(与展开列的行号口径一致);② 选中行带 aria-selected(可点行按 selectable 给 true/false,不可点行只给 true),淡底仍作视觉层;③ dataTable 文档的 selectable 步骤同步说明;④ dataTable.ts 仍 ≤ 200 行(CODING.md 硬上限);⑤ npm run check 全绿。",
|
"acceptance": "① 表头全选框 aria-label「全选本页」,行复选框「选择第 N 行」(与展开列的行号口径一致);② 选中行带 aria-selected(可点行按 selectable 给 true/false,不可点行只给 true),淡底仍作视觉层;③ dataTable 文档的 selectable 步骤同步说明;④ dataTable.ts 仍 ≤ 200 行(CODING.md 硬上限);⑤ npm run check 全绿。",
|
||||||
"notes": ""
|
"notes": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "g337",
|
||||||
|
"domain": "tooling",
|
||||||
|
"status": "open",
|
||||||
|
"title": "可访问名门禁:combobox / slider / range 缺 aria-label 直接红",
|
||||||
|
"motivation": "本轮修输入件可访问名时发现口径靠人记:13 个组件都有 ariaLabel,commandPalette 的搜索框却只有 role=combobox + aria-controls,没有名字——读屏进命令面板只念「编辑框」,用户不知道搜什么。这类缺陷肉眼看不出来(placeholder 看着像有名字),只有读屏用户会撞上,必须让门禁兜住。",
|
||||||
|
"acceptance": "① ua verify 新增 checkAriaNames:扫描皮肤包源码里含 role: 'combobox' / role: 'slider' / type: 'range' 的创建块,块内既无 aria-label / aria-labelledby、紧随其后 3 行也没有 setAttribute('aria-label') 即报错,报出文件:行号;② 窗口只取单次 h() 调用块(回退到 h('input|textarea|select'、前进到收尾行),避免把相邻元素的 aria-label 误算成本控件的名字;③ 实跑抓到 commandPalette 一处并修好(aria-label 取 placeholder,缺省「搜索命令」);④ AGENTS 的门禁说明与 verify 成功串同步;⑤ npm run check 全绿。",
|
||||||
|
"notes": ""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export function commandPalette(props: CommandPaletteProps): CommandPaletteHandle
|
|||||||
class: 'ua-cmdk-input',
|
class: 'ua-cmdk-input',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
placeholder: props.placeholder ?? '搜索命令…',
|
placeholder: props.placeholder ?? '搜索命令…',
|
||||||
|
'aria-label': props.placeholder ?? '搜索命令', // 占位文案只是提示,不是可访问名:读屏要靠这一条才知道搜的是什么
|
||||||
'aria-controls': `${uid}-list`,
|
'aria-controls': `${uid}-list`,
|
||||||
role: 'combobox',
|
role: 'combobox',
|
||||||
'aria-expanded': 'true',
|
'aria-expanded': 'true',
|
||||||
|
|||||||
@@ -1630,6 +1630,32 @@ function checkVisualChecklist() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*** 清单一致性校验:组件文档块的 id / props 说明 / demo 锚点必须与代码和演示产物对齐 ***/
|
/*** 清单一致性校验:组件文档块的 id / props 说明 / demo 锚点必须与代码和演示产物对齐 ***/
|
||||||
|
/*** 校验输入控件的可访问名:role=combobox / slider 与原生 range 不自带名字,缺 aria-label 时读屏只念「编辑框 / 滑块」
|
||||||
|
* 只查这三类「必须有名字」的控件,包裹式 <label> 与 aria-labelledby 都算通过,避免误伤复用标签的写法 ***/
|
||||||
|
function checkAriaNames(pkgs) {
|
||||||
|
const issues = [];
|
||||||
|
for (const pkg of pkgs) {
|
||||||
|
if (pkg.type !== 'skin-dom') continue; // 只有皮肤包会产生 DOM
|
||||||
|
const srcDir = path.join(ROOT, pkg.dir, 'src');
|
||||||
|
if (!fs.existsSync(srcDir)) continue;
|
||||||
|
for (const file of fs.readdirSync(srcDir).filter((f) => f.endsWith('.ts'))) {
|
||||||
|
const lines = fs.readFileSync(path.join(srcDir, file), 'utf8').split(/\r?\n/); // 归一换行:CRLF 文件不会把一行拆成两半
|
||||||
|
lines.forEach((line, i) => {
|
||||||
|
if (!/role: '(combobox|slider)'|type: 'range'/.test(line)) return; // 卫语句:只关心这三类无名即失声的控件
|
||||||
|
let head = i; // 回退到 h('input' / h('textarea' / h('select' 那一行:一处控件的属性只在同一次调用里
|
||||||
|
while (head > 0 && !/h\('(input|textarea|select)'/.test(lines[head])) head--;
|
||||||
|
let tail = i; // 前进到这次调用的收尾行(`})` 或 `}) as ...`),窗口只取这一个创建块
|
||||||
|
while (tail < lines.length - 1 && !/\}\)/.test(lines[tail])) tail++;
|
||||||
|
const block = lines.slice(head, tail + 1).join(' ');
|
||||||
|
if (/aria-label|aria-labelledby/.test(block)) return; // 同名两法都算过(后者由外部 label 提供)
|
||||||
|
if (/setAttribute\('aria-label'/.test(lines.slice(tail + 1, tail + 4).join(' '))) return; // 也认「建完立刻 setAttribute」的写法
|
||||||
|
issues.push(`${pkg.name}/${file}:${i + 1} 的 ${line.trim().slice(0, 40)} 缺可访问名(role=combobox / slider 与 range 都不会从内容里取名,读屏只念控件类型)`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return issues;
|
||||||
|
}
|
||||||
|
|
||||||
/*** 校验同义词表:目标 id 必须真实存在(改名后残留会让回退指到空气),同一个词不得归两组(歧义会让结果随表序漂移) ***/
|
/*** 校验同义词表:目标 id 必须真实存在(改名后残留会让回退指到空气),同一个词不得归两组(歧义会让结果随表序漂移) ***/
|
||||||
function checkSynonyms(ids) {
|
function checkSynonyms(ids) {
|
||||||
const issues = [];
|
const issues = [];
|
||||||
@@ -1710,6 +1736,7 @@ function cmdVerify() {
|
|||||||
.flatMap((p) => p.docs?.components ?? []).length;
|
.flatMap((p) => p.docs?.components ?? []).length;
|
||||||
issues.push(...checkCountDocs(skinTotal));
|
issues.push(...checkCountDocs(skinTotal));
|
||||||
issues.push(...checkReadme(pkgs));
|
issues.push(...checkReadme(pkgs));
|
||||||
|
issues.push(...checkAriaNames(pkgs)); // 输入控件必须有可访问名
|
||||||
issues.push(...checkSynonyms(new Set(pkgs.flatMap((p) => (p.docs?.components ?? []).map((c) => c.id))))); // 同义词表指向的组件必须真实存在
|
issues.push(...checkSynonyms(new Set(pkgs.flatMap((p) => (p.docs?.components ?? []).map((c) => c.id))))); // 同义词表指向的组件必须真实存在
|
||||||
checkDarkTokens(issues);
|
checkDarkTokens(issues);
|
||||||
checkTokenParity(issues);
|
checkTokenParity(issues);
|
||||||
@@ -1722,7 +1749,7 @@ function cmdVerify() {
|
|||||||
console.error(`${issues.length} violation(s)`);
|
console.error(`${issues.length} violation(s)`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
console.log('verify OK (manifest ↔ docs ↔ demo 一致,文档 props ↔ 入参接口双向一致,皮肤无硬编码颜色与未定义令牌、暗色令牌齐全、tokens.json ↔ tokens.css 名字与值双源一致、@keyframes 无复制粘贴的重复帧、滚动容器全在细条规则内,README 组件清单与分组同步,backlog ↔ 留痕双向一致(编号严格递增、无重复 id、留痕时间线单调且带 +08:00,每个 iter 提交都有留痕),组合配方结构完整,MCP 工具清单三处一致,搜索同义词表目标存在且无歧义,视觉回归抽查清单十八组件 × 五维度齐全)');
|
console.log('verify OK (manifest ↔ docs ↔ demo 一致,文档 props ↔ 入参接口双向一致,皮肤无硬编码颜色与未定义令牌、暗色令牌齐全、tokens.json ↔ tokens.css 名字与值双源一致、@keyframes 无复制粘贴的重复帧、滚动容器全在细条规则内,README 组件清单与分组同步,backlog ↔ 留痕双向一致(编号严格递增、无重复 id、留痕时间线单调且带 +08:00,每个 iter 提交都有留痕),组合配方结构完整,MCP 工具清单三处一致,搜索同义词表目标存在且无歧义,combobox / slider / range 控件都有可访问名,视觉回归抽查清单十八组件 × 五维度齐全)');
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** 校验 git 提交信息是否符合 COMMITTING.md 规范(参数为 message 文件路径或直接 message 文本),违规退出码 1 ***/
|
/*** 校验 git 提交信息是否符合 COMMITTING.md 规范(参数为 message 文件路径或直接 message 文本),违规退出码 1 ***/
|
||||||
|
|||||||
Reference in New Issue
Block a user