iter(g345): 提交按钮补忙碌态与 aria-busy
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { effect, h } from '@ui-arch/frame';
|
||||
import { effect, h, signal } from '@ui-arch/frame';
|
||||
import { button } from './button.js';
|
||||
import { checkRule, checkRuleAsync } from './formRules.js';
|
||||
import { grid } from './grid.js';
|
||||
@@ -124,7 +124,8 @@ export function form(props: FormProps, items: FormItemHandle[]): HTMLElement {
|
||||
summary.textContent = '';
|
||||
props.onReset?.();
|
||||
};
|
||||
const submitBtn = button({ label: props.submitText ?? '提交', type: 'submit' }) as HTMLButtonElement; // 走原生 submit,避免 click + submit 双触发
|
||||
const busy = signal(false); // 校验进行中:按钮转圈 + aria-busy,异步规则跑 400ms 时用户看得见在等
|
||||
const submitBtn = button({ label: props.submitText ?? '提交', type: 'submit', loading: busy }) as HTMLButtonElement; // 走原生 submit,避免 click + submit 双触发
|
||||
const resetBtn = props.resetText ? button({ label: props.resetText, variant: 'ghost', type: 'button', onClick: doReset }) : null; // 重置不得提交表单
|
||||
const actions = h('div', { class: 'ua-form-actions' }, submitBtn, resetBtn);
|
||||
let running = false; // 提交中标记:异步校验期间屏蔽重复提交
|
||||
@@ -133,7 +134,7 @@ export function form(props: FormProps, items: FormItemHandle[]): HTMLElement {
|
||||
const submit = async (): Promise<void> => {
|
||||
if (running) return; // 卫语句:上一轮异步校验还没结束
|
||||
running = true;
|
||||
submitBtn.disabled = true; // 校验期间禁用,避免连点重复提交
|
||||
busy(true); // loading 自带禁点,不必再手写 disabled(两处都设反而容易漏复位)
|
||||
let firstBad: HTMLElement | null = null;
|
||||
let bad = 0; // 不通过的字段数
|
||||
for (const item of items) {
|
||||
@@ -145,7 +146,7 @@ export function form(props: FormProps, items: FormItemHandle[]): HTMLElement {
|
||||
if (!target) item.el.tabIndex = -1; // 整块无可聚焦控件时把字段容器本身设为焦点落点(-1 不进 Tab 序,只接受程序聚焦)
|
||||
firstBad = target ?? item.el;
|
||||
}
|
||||
submitBtn.disabled = false;
|
||||
busy(false);
|
||||
running = false;
|
||||
summary.hidden = bad === 0;
|
||||
summary.textContent = bad ? `有 ${bad} 项需要修改,已定位到第一处` : '';
|
||||
|
||||
@@ -1853,7 +1853,7 @@
|
||||
{
|
||||
"n": "submitText",
|
||||
"t": "string",
|
||||
"d": "提交按钮文案;缺省「提交」"
|
||||
"d": "提交按钮文案;缺省「提交」。异步规则校验期间按钮转圈并禁点(aria-busy),跑 400ms 的远程校验也看得见在等"
|
||||
},
|
||||
{
|
||||
"n": "resetText",
|
||||
|
||||
@@ -1741,6 +1741,21 @@ function checkFormSummary(pkgs) {
|
||||
return issues;
|
||||
}
|
||||
|
||||
/*** 校验提交忙碌态:异步规则跑起来时按钮只变灰,用户会以为点空了;也防「设了 loading 忘了复位」 ***/
|
||||
function checkFormBusy(pkgs) {
|
||||
const issues = [];
|
||||
for (const pkg of pkgs) {
|
||||
if (pkg.type !== 'skin-dom') continue;
|
||||
const formPath = path.join(ROOT, pkg.dir, 'src', 'form.ts');
|
||||
if (!fs.existsSync(formPath)) continue;
|
||||
const src = fs.readFileSync(formPath, 'utf8');
|
||||
if (!/loading: busy/.test(src)) issues.push(`${pkg.name}/src/form.ts 的提交按钮没接 loading:异步校验期间只变灰,用户看不出在等`);
|
||||
if (!/busy\(false\)/.test(src)) issues.push(`${pkg.name}/src/form.ts 缺 busy(false) 复位:按钮会一直转圈,表单再也提交不了`);
|
||||
if (/submitBtn\.disabled = true/.test(src)) issues.push(`${pkg.name}/src/form.ts 又手写 submitBtn.disabled:与 loading 双写会让禁用态漏复位,只留 loading 一条路`);
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
|
||||
/*** 校验同义词表:目标 id 必须真实存在(改名后残留会让回退指到空气),同一个词不得归两组(歧义会让结果随表序漂移) ***/
|
||||
function checkSynonyms(ids) {
|
||||
const issues = [];
|
||||
@@ -1827,6 +1842,7 @@ function cmdVerify() {
|
||||
issues.push(...checkFormReset(pkgs)); // 重置按钮必须真复位
|
||||
issues.push(...checkInvalidRing(pkgs)); // 错误环只该在聚焦时亮
|
||||
issues.push(...checkFormSummary(pkgs)); // 汇总条必须实时重算
|
||||
issues.push(...checkFormBusy(pkgs)); // 提交忙碌态必须有可见反馈
|
||||
issues.push(...checkSynonyms(new Set(pkgs.flatMap((p) => (p.docs?.components ?? []).map((c) => c.id))))); // 同义词表指向的组件必须真实存在
|
||||
checkDarkTokens(issues);
|
||||
checkTokenParity(issues);
|
||||
@@ -1839,7 +1855,7 @@ function cmdVerify() {
|
||||
console.error(`${issues.length} violation(s)`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('verify OK (manifest ↔ docs ↔ demo 一致,文档 props ↔ 入参接口双向一致,皮肤无硬编码颜色与未定义令牌、暗色令牌齐全、tokens.json ↔ tokens.css 名字与值双源一致、@keyframes 无复制粘贴的重复帧、滚动容器全在细条规则内,README 组件清单与分组同步,backlog ↔ 留痕双向一致(编号严格递增、无重复 id、留痕时间线单调且带 +08:00,每个 iter 提交都有留痕),组合配方结构完整,MCP 工具清单三处一致,搜索同义词表目标存在且无歧义,combobox / slider / range 控件都有可访问名,toast 有 live 区域与 alert 抢占播报,视觉回归抽查清单十八组件 × 五维度齐全)');
|
||||
console.log('verify OK (manifest ↔ docs ↔ demo 一致,文档 props ↔ 入参接口双向一致,皮肤无硬编码颜色与未定义令牌、暗色令牌齐全、tokens.json ↔ tokens.css 名字与值双源一致、@keyframes 无复制粘贴的重复帧、滚动容器全在细条规则内,README 组件清单与分组同步,backlog ↔ 留痕双向一致(编号严格递增、无重复 id、留痕时间线单调且带 +08:00,每个 iter 提交都有留痕),组合配方结构完整,MCP 工具清单三处一致,搜索同义词表目标存在且无歧义,combobox / slider / range 控件都有可访问名,toast 有 live 区域与 alert 抢占播报,表单错误文案被 aria-describedby 关联、重置真复位、错误环只在聚焦时亮、汇总条随校验重算、提交按钮有忙碌态,视觉回归抽查清单十八组件 × 五维度齐全)');
|
||||
}
|
||||
|
||||
/*** 校验 git 提交信息是否符合 COMMITTING.md 规范(参数为 message 文件路径或直接 message 文本),违规退出码 1 ***/
|
||||
|
||||
Reference in New Issue
Block a user