agentcss 0.11.12 → 0.11.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/01-tokens/page.css +7 -0
- package/02-primitives/badge/demo.html +5 -0
- package/02-primitives/choice/demo.html +1 -0
- package/02-primitives/separator/demo.html +7 -0
- package/02-primitives/switch/demo.html +10 -0
- package/03-patterns/accordion/demo.html +17 -3
- package/03-patterns/form-validation/demo.html +4 -3
- package/03-patterns/overlay/demo.html +8 -1
- package/03-patterns/tabs/demo.html +17 -2
- package/09-assets/echarts-adapter/demo.html +1 -0
- package/09-assets/sparkline/demo.html +3 -2
- package/CHANGELOG.md +95 -0
- package/CONTRIBUTING.md +13 -1
- package/ai/components.json +4 -2
- package/dist/COSTS.md +1 -1
- package/index.html +1 -1
- package/package.json +1 -1
package/01-tokens/page.css
CHANGED
|
@@ -241,6 +241,13 @@
|
|
|
241
241
|
两栏的断言必须**互斥**且各自成立 —— 能同时成立的两条断言,
|
|
242
242
|
什么也证明不了。这两个属性不影响渲染,但缺了它们,
|
|
243
243
|
这块对照就退化成"看着像错的",`demo-claim` 门禁会报红。
|
|
244
|
+
|
|
245
|
+
反例栏里还得有**标本**:真渲染出来的控件或内容。只有一段
|
|
246
|
+
`<p class="small">` 描述"别这么做",不算反例 —— 读者看不见那个"错"。
|
|
247
|
+
摆不出来的(运行时行为 / 读屏输出 / 这页没有图表实例)写在块上:
|
|
248
|
+
`<div class="cmp__pane cmp__bad" data-prose="为什么摆不出来">`
|
|
249
|
+
单栏的 `box bad` 页没有成对结构,页内要写
|
|
250
|
+
`<!-- demo-claim:skip 为什么没有成对断言 -->`
|
|
244
251
|
========================================================================= */
|
|
245
252
|
.page .cmp {
|
|
246
253
|
display: grid;
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
</head>
|
|
18
18
|
<body class="page page">
|
|
19
19
|
<main class="wrap">
|
|
20
|
+
<!-- demo-claim:skip 反例的差别是「饱和底白字的对比度」和「div 能不能被 Tab」:前者 demo-claim 没有对比度指标(contrast.py 单独测),后者没有焦点指标 -->
|
|
20
21
|
<h1>badge · 状态标记</h1>
|
|
21
22
|
<p class="lede">
|
|
22
23
|
badge 承载<strong>一个短状态词</strong>("已停牌""审计中")。
|
|
@@ -81,6 +82,10 @@
|
|
|
81
82
|
|
|
82
83
|
<div class="box bad">
|
|
83
84
|
<h3>反例:用 badge 装可点的东西</h3>
|
|
85
|
+
<div class="row">
|
|
86
|
+
<div class="badge badge--info">待复核</div>
|
|
87
|
+
<span class="small">← 它和上面的 badge 长得一模一样,但 Tab 一遍就会发现焦点跳过了它</span>
|
|
88
|
+
</div>
|
|
84
89
|
<p class="small">
|
|
85
90
|
<code><div class="badge badge--info" onclick="...">待复核</div></code>
|
|
86
91
|
—— div 加 onclick 对读屏是<strong>「没有角色的按钮」</strong>:
|
|
@@ -32,6 +32,7 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
|
|
|
32
32
|
</head>
|
|
33
33
|
<body class="page page">
|
|
34
34
|
<main class="wrap">
|
|
35
|
+
<!-- demo-claim:skip 可量(display:none 的 input 高度是 0,opacity:0 的不是),但这一页仍是单栏结构,升级成 cmp 要重排整页 —— 本轮没做,已知欠账 -->
|
|
35
36
|
|
|
36
37
|
<h1>checkbox / radio / select</h1>
|
|
37
38
|
<p class="lede">
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
</head>
|
|
16
16
|
<body class="page page">
|
|
17
17
|
<main class="wrap">
|
|
18
|
+
<!-- demo-claim:skip 两块都可量(<hr> 的个数 0 vs 1),但这一页仍是单栏结构,升级成 cmp 要重排整页 —— 本轮没做,已知欠账 -->
|
|
18
19
|
<h1>separator · 分隔线</h1>
|
|
19
20
|
<p class="lede">
|
|
20
21
|
分隔线有两种,<strong>必须分清</strong>:装饰性的只分开内容块,
|
|
@@ -62,6 +63,11 @@
|
|
|
62
63
|
<h2>反例</h2>
|
|
63
64
|
<div class="box bad">
|
|
64
65
|
<h3>反例:语义分隔用装饰色</h3>
|
|
66
|
+
<div style="max-width:32em">
|
|
67
|
+
<p class="small" style="margin:0">基本信息</p>
|
|
68
|
+
<hr class="separator separator--tight">
|
|
69
|
+
<p class="small" style="margin:0">高级设置</p>
|
|
70
|
+
</div>
|
|
65
71
|
<p class="small">
|
|
66
72
|
分隔"基本信息"与"高级设置"——这是<strong>两类不同的东西</strong>,
|
|
67
73
|
但如果用 <code>--border-decor</code>(1.26:1),
|
|
@@ -73,6 +79,7 @@
|
|
|
73
79
|
|
|
74
80
|
<div class="box bad">
|
|
75
81
|
<h3>反例:用 div + border-bottom 代替 hr</h3>
|
|
82
|
+
<div style="border-bottom:1px solid var(--border-decor); margin:var(--sp-4) 0"></div>
|
|
76
83
|
<p class="small">
|
|
77
84
|
<code><div class="line"></code> 对读屏是<strong>什么都没有</strong>。
|
|
78
85
|
语义分隔必须用 <code><hr></code>(或 <code>role="separator"</code>),
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
</head>
|
|
19
19
|
<body class="page page">
|
|
20
20
|
<main class="wrap">
|
|
21
|
+
<!-- demo-claim:skip 两块都可量(input 个数 0 vs 1、label 的高度),但这一页仍是单栏结构,升级成 cmp 要重排整页 —— 本轮没做,已知欠账 -->
|
|
21
22
|
<h1>switch · 开关</h1>
|
|
22
23
|
<p class="lede">
|
|
23
24
|
开关用于<strong>立即生效的设置项</strong>("开/关通知")。
|
|
@@ -84,7 +85,12 @@
|
|
|
84
85
|
<h2 class="rule-top">反例</h2>
|
|
85
86
|
<div class="box bad">
|
|
86
87
|
<h3>反例:用 div + role="switch",自己管所有状态</h3>
|
|
88
|
+
<div class="switch" role="switch" aria-checked="false" tabindex="0">
|
|
89
|
+
<span class="switch__track"><span class="switch__thumb"></span></span>
|
|
90
|
+
<span class="switch__label">开启动价推送</span>
|
|
91
|
+
</div>
|
|
87
92
|
<p class="small">
|
|
93
|
+
它<strong>看起来和上面那个一模一样</strong> —— 这正是坑所在:
|
|
88
94
|
常见写法是 <code><div role="switch" tabindex="0" onclick="..."></code>,
|
|
89
95
|
然后手写 <code>aria-checked</code> 切换。这需要自己处理:
|
|
90
96
|
Space 键、焦点样式、状态播报。<strong>三件事漏一件就坏</strong>,
|
|
@@ -94,6 +100,10 @@
|
|
|
94
100
|
|
|
95
101
|
<div class="box bad">
|
|
96
102
|
<h3>反例:给 label 加 display:none</h3>
|
|
103
|
+
<div class="switch">
|
|
104
|
+
<span class="switch__track"><span class="switch__thumb"></span></span>
|
|
105
|
+
<label class="switch__label" style="display:none">自动续订</label>
|
|
106
|
+
</div>
|
|
97
107
|
<p class="small">
|
|
98
108
|
为了"只留滑块"而把 <code><label></code> 隐藏掉,
|
|
99
109
|
开关就<strong>失去了可点击的文字区域</strong>,也失去了语义名字 ——
|
|
@@ -15,6 +15,7 @@ p { margin: 0 0 var(--sp-3); }
|
|
|
15
15
|
</head>
|
|
16
16
|
<body class="page page">
|
|
17
17
|
<main class="wrap">
|
|
18
|
+
<!-- demo-claim:skip 两块都可量(button 的个数、面板里链接的高度),但这一页仍是单栏结构,升级成 cmp 要重排整页 —— 本轮没做,已知欠账 -->
|
|
18
19
|
<h1>accordion · 折叠面板</h1>
|
|
19
20
|
<p class="lede">
|
|
20
21
|
用于把长表单或长内容分成几段。<strong>默认只开一个</strong>(同一个问题的不同答案),
|
|
@@ -202,6 +203,13 @@ p { margin: 0 0 var(--sp-3); }
|
|
|
202
203
|
<h2 class="rule-top">反例</h2>
|
|
203
204
|
<div class="box bad">
|
|
204
205
|
<h3>反例:用 div 当 trigger</h3>
|
|
206
|
+
<div class="accordion">
|
|
207
|
+
<div class="accordion__item">
|
|
208
|
+
<div class="accordion__heading">
|
|
209
|
+
<div class="accordion__trigger">用 div 写的标题(Tab 不到,也念不出状态)</div>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
205
213
|
<p class="small">
|
|
206
214
|
<code><div class="accordion__trigger"></code> 键盘上 Tab 不到、
|
|
207
215
|
也不响应 Enter/Space。<strong>APG 要求 header 里是
|
|
@@ -211,10 +219,16 @@ p { margin: 0 0 var(--sp-3); }
|
|
|
211
219
|
</div>
|
|
212
220
|
<div class="box bad">
|
|
213
221
|
<h3>反例:折叠时只用 max-height: 0</h3>
|
|
222
|
+
<div id="acc-bad" style="max-height:0; overflow:hidden">
|
|
223
|
+
<p class="small" style="margin:0 0 var(--sp-2)">这一整段文字,读屏此刻仍然念得出来。</p>
|
|
224
|
+
<a href="#acc-bad">里面的这个链接,Tab 也仍然进得去</a>
|
|
225
|
+
</div>
|
|
214
226
|
<p class="small">
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
227
|
+
上面那块的高度被压成了 <code>0</code>,你<strong>看不见它</strong> ——
|
|
228
|
+
但按一下 Tab,焦点会停在里面的链接上;读屏也会把那句"仍然念得出来"念完。
|
|
229
|
+
内容在无障碍树里<strong>仍然存在</strong>,只是视觉上消失:
|
|
230
|
+
<strong>必须用 <code>hidden</code> 或 <code>display:none</code></strong>,
|
|
231
|
+
让它真正离开无障碍树。
|
|
218
232
|
</p>
|
|
219
233
|
</div>
|
|
220
234
|
</main>
|
|
@@ -42,6 +42,7 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
|
|
|
42
42
|
</head>
|
|
43
43
|
<body class="page page">
|
|
44
44
|
<main class="wrap">
|
|
45
|
+
<!-- demo-claim:skip 两块反例的差别都是时序(什么时候报错)和措辞(文案怎么说),没有可量的元素属性 -->
|
|
45
46
|
|
|
46
47
|
<h1>表单校验</h1>
|
|
47
48
|
<p class="lede">
|
|
@@ -128,9 +129,9 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
|
|
|
128
129
|
<div class="grid2">
|
|
129
130
|
<div class="box bad">
|
|
130
131
|
<h3>反例</h3>
|
|
131
|
-
<p
|
|
132
|
-
<p
|
|
133
|
-
<p
|
|
132
|
+
<p class="naive-error">格式错误</p>
|
|
133
|
+
<p class="naive-error">非法输入</p>
|
|
134
|
+
<p class="naive-error" style="margin-bottom:0">值不合法</p>
|
|
134
135
|
<p class="small" style="margin-top:var(--sp-3)">
|
|
135
136
|
WCAG 3.3.3 要求给修正建议。这三句用户读完还是不知道该改成什么。
|
|
136
137
|
</p>
|
|
@@ -42,6 +42,7 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
|
|
|
42
42
|
</head>
|
|
43
43
|
<body class="page page">
|
|
44
44
|
<main class="wrap">
|
|
45
|
+
<!-- demo-claim:skip 焦点落点与「点遮罩关不关」都是运行时行为,静态块里摆不出「错的那一版」 -->
|
|
45
46
|
|
|
46
47
|
<h1>Toast vs Dialog</h1>
|
|
47
48
|
<p class="lede">
|
|
@@ -92,7 +93,7 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
|
|
|
92
93
|
<strong>点遮罩不关闭</strong>。误点遮罩的代价太大。
|
|
93
94
|
</p>
|
|
94
95
|
</div>
|
|
95
|
-
<div class="box bad">
|
|
96
|
+
<div class="box bad" data-prose="焦点落在哪颗按钮上、点遮罩关不关,都是运行时行为:要摆出「错的那一版」得真的渲染一个会抢焦点的对话框,而演示页里放一个真 modal 会盖住整页">
|
|
96
97
|
<h3 style="margin:0 0 var(--sp-3); font-size:var(--fs-sm); font-weight:500">常见做法</h3>
|
|
97
98
|
<p class="small" style="margin:0">
|
|
98
99
|
焦点默认落在"删除"上,用户看清标题之前就按了 Enter。<br><br>
|
|
@@ -114,6 +115,12 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
|
|
|
114
115
|
<div class="box bad aside-col">
|
|
115
116
|
<h3 style="margin:0 0 var(--sp-3); font-size:var(--fs-sm); font-weight:500; color:var(--danger)">
|
|
116
117
|
❌ 错用</h3>
|
|
118
|
+
<div class="toast toast--error" role="status" style="margin:0 0 var(--sp-3)">
|
|
119
|
+
<div class="toast__body">
|
|
120
|
+
<div class="toast__title">文件已删除</div>
|
|
121
|
+
<div class="toast__desc">3 秒后自动消失,没有撤销</div>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
117
124
|
<p class="small" style="margin:0 0 var(--sp-2)">
|
|
118
125
|
<strong>把"确认删除"做成 toast</strong><br>
|
|
119
126
|
用户可能根本没看见就把文件删了,而且没法撤销。
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
</head>
|
|
18
18
|
<body class="page page">
|
|
19
19
|
<main class="wrap">
|
|
20
|
+
<!-- demo-claim:skip 两块都可量(button 的个数、tabindex="0" 的个数),但这一页仍是单栏结构,升级成 cmp 要重排整页 —— 本轮没做,已知欠账 -->
|
|
20
21
|
<h1>tabs · 标签页</h1>
|
|
21
22
|
<p class="lede">
|
|
22
23
|
标签页用于在同一位置切换<strong>同层级的几块内容</strong>。
|
|
@@ -105,7 +106,14 @@
|
|
|
105
106
|
<h2 class="rule-top">反例</h2>
|
|
106
107
|
<div class="box bad">
|
|
107
108
|
<h3>反例:用 div 当 tab</h3>
|
|
109
|
+
<div class="tabs">
|
|
110
|
+
<div class="tabs__list" role="tablist">
|
|
111
|
+
<div class="tabs__tab" role="tab" aria-selected="true">基本信息</div>
|
|
112
|
+
<div class="tabs__tab" role="tab" aria-selected="false">高级设置</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
108
115
|
<p class="small">
|
|
116
|
+
上面这两个 tab <strong>看起来完全正常</strong>,但
|
|
109
117
|
<code><div role="tab"></code> 键盘上根本<strong>Tab 不到</strong>
|
|
110
118
|
(除非手写 <code>tabindex</code>),也不会响应 Enter/Space。
|
|
111
119
|
APG 明确要求 tab 是 <code><button role="tab"></code> ——
|
|
@@ -114,9 +122,16 @@
|
|
|
114
122
|
</div>
|
|
115
123
|
<div class="box bad">
|
|
116
124
|
<h3>反例:每个 tab 都写 tabindex="0"</h3>
|
|
125
|
+
<div class="tabs">
|
|
126
|
+
<div class="tabs__list" role="tablist">
|
|
127
|
+
<button class="tabs__tab" role="tab" tabindex="0" aria-selected="true">基本信息</button>
|
|
128
|
+
<button class="tabs__tab" role="tab" tabindex="0">高级设置</button>
|
|
129
|
+
<button class="tabs__tab" role="tab" tabindex="0">日志</button>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
117
132
|
<p class="small">
|
|
118
|
-
看起来"都能 Tab 到",但一排
|
|
119
|
-
|
|
133
|
+
看起来"都能 Tab 到",但一排 3 个 tab 就要按 <strong>3 次 Tab</strong>
|
|
134
|
+
才能进内容区(真到 5 个 tab 的页面上只会更糟)。Tab 键应该是<strong>进出</strong>控件的,
|
|
120
135
|
组内移动是方向键的活。
|
|
121
136
|
</p>
|
|
122
137
|
</div>
|
|
@@ -35,6 +35,7 @@ td.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabu
|
|
|
35
35
|
</head>
|
|
36
36
|
<body class="page page">
|
|
37
37
|
<div class="wrap">
|
|
38
|
+
<!-- demo-claim:skip 反例是一张「不要做」清单表;3D、双 Y 轴这些错法都要真的渲染一张图才立得住,本页没有图表实例可摆 -->
|
|
38
39
|
<h1>图表规范</h1>
|
|
39
40
|
<p class="lede">
|
|
40
41
|
<strong>下次画图照这里做就行</strong>,不用再反复微调。
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
</head>
|
|
35
35
|
<body class="page page">
|
|
36
36
|
<main class="wrap">
|
|
37
|
+
<!-- demo-claim:skip 两块反例(读屏念什么、reduced-motion 动画)都是运行时或读屏输出,没有可量的元素属性 -->
|
|
37
38
|
<h1>sparkline · 迷你趋势图</h1>
|
|
38
39
|
<p class="lede">
|
|
39
40
|
在一行里显示"最近走势"。它是<strong>辅助</strong>不是图表 ——
|
|
@@ -107,7 +108,7 @@
|
|
|
107
108
|
</div>
|
|
108
109
|
|
|
109
110
|
<h2 class="rule-top">🔴 无障碍:它没有可读的文本等价物</h2>
|
|
110
|
-
<div class="box bad">
|
|
111
|
+
<div class="box bad" data-prose="这一块说的是原因(为什么刻意不加 role=img),反例是读屏念出来的那句话,不是一个看得见的元素 —— 图形本身在下面那块里已经摆出来了">
|
|
111
112
|
<p>
|
|
112
113
|
纯 SVG 路径对读屏<strong>没有意义</strong> ——
|
|
113
114
|
读屏只会说"图像",不会说"最近上涨 2.3%"。
|
|
@@ -131,7 +132,7 @@
|
|
|
131
132
|
</p>
|
|
132
133
|
<span class="sparkline" data-spark="12,18,15,24,22,31,28" data-spark-height="40"></span>
|
|
133
134
|
</div>
|
|
134
|
-
<div class="box bad">
|
|
135
|
+
<div class="box bad" data-prose="「动画有没有被 reduced-motion 关掉」是媒体查询在运行时决定的行为,同一个 sparkline 在两种设置下长得一模一样 —— 静态块摆不出「错的那一版」">
|
|
135
136
|
<h3>反例:动画不管 reduced-motion</h3>
|
|
136
137
|
<p class="small">
|
|
137
138
|
前庭功能障碍者会因动画眩晕(WCAG 2.3.3)。
|
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,101 @@
|
|
|
16
16
|
|
|
17
17
|
(无)
|
|
18
18
|
|
|
19
|
+
## [0.11.13] - 2026-10-29
|
|
20
|
+
|
|
21
|
+
**先得有东西可看,才谈得上「两栏不一样」。**
|
|
22
|
+
|
|
23
|
+
0.11.12 让 24 个对照块各写了一条可证伪的数字断言。但那套判据有个前提:
|
|
24
|
+
块里得有标本。发布后把全部 34 个 demo 页扫了一遍 —— **13 个反例块里
|
|
25
|
+
一个标本都没有**,只有 `<p class="small">` 在**描述**一个反例。
|
|
26
|
+
|
|
27
|
+
最刺眼的一处:`02-primitives/separator` 的标题写着
|
|
28
|
+
「反例:用 div + border-bottom 代替 hr」,块里只有一段话说
|
|
29
|
+
"`<div class="line">` 对读屏什么都没有"—— **那个 div 根本没被渲染**。
|
|
30
|
+
读者在这一屏里学不到任何看得见的东西。`switch` / `accordion` / `tabs`
|
|
31
|
+
各两处、`badge`、`sparkline`、`form-validation` 同形。
|
|
32
|
+
|
|
33
|
+
`demo-counterexample-gate.py` 对它们全绿:它判的是"块在、没被藏、
|
|
34
|
+
块后有标题",一段纯文字全都满足。这是比"反例不成立"更早一层的漏洞 ——
|
|
35
|
+
**没有反例**。
|
|
36
|
+
|
|
37
|
+
### 新增的两条判据(`05-audit/demo-claim-gate.js`,同一道门禁)
|
|
38
|
+
|
|
39
|
+
1. **反例块里必须有标本** —— 真渲染出来的控件或内容。判据看标签语义:
|
|
40
|
+
原生控件/绘图元素算;`h3 p code pre strong` 这些"天生是字"的标签,
|
|
41
|
+
带**非排版类**时才算(`p.rep-p` 是内容本身,`p.small` 是描述它的文字)。
|
|
42
|
+
摆不出来的写在块上:`data-prose="为什么摆不出来"`(理由 ≥8 字,
|
|
43
|
+
逐条打印,可以被反驳)。
|
|
44
|
+
2. **单栏页必须逐页登记** —— 只有 `box bad`、没有 `cmp` 的页,页内要写
|
|
45
|
+
`<!-- demo-claim:skip 理由 -->`。以前只在末尾打一行"10 页未覆盖",
|
|
46
|
+
现在每页的理由都打出来,并且**要求区分"量不了"和"可量但没做"**。
|
|
47
|
+
既有 `cmp` 又写 `skip` 也算矛盾,同样红。
|
|
48
|
+
|
|
49
|
+
自检从 10 条加打到 32 条(21 条必须红 + 11 条必须绿)。
|
|
50
|
+
|
|
51
|
+
### 判据本身走过的两条错路(都留在代码注释与 `internal/ERRORS.md` 里)
|
|
52
|
+
|
|
53
|
+
- 第一版按「类名是否定义在组件样式里」判标本 → `sparkline.css` 里
|
|
54
|
+
`.row > .sparkline` 这种后代选择器把排版类收进了类名表(假绿);
|
|
55
|
+
而 `separator` 那个 `<div class="line">` 的类定义在 **demo 自己的
|
|
56
|
+
`<style>`** 里,真标本被判成没标本(假红)。
|
|
57
|
+
- 第二版按「有没有边框/底色」判 → `tooltip`(`div.tt-field`)、
|
|
58
|
+
`tree`(`div.tree__node`)、`report`(`<p class="rep-p">`)三处
|
|
59
|
+
**纯文字构成的真标本**被冤枉。这一库有大量反例的差别就在字号、
|
|
60
|
+
字重、行高、颜色上,这些东西不产生边框。
|
|
61
|
+
|
|
62
|
+
### 补上的 10 处标本(另有 3 块实在摆不出来,写了 `data-prose`)
|
|
63
|
+
|
|
64
|
+
`separator` ×2(真渲染 `<hr>` 与 `div` 分隔线)、`switch` ×2(假开关与
|
|
65
|
+
丢了 label 的开关)、`accordion` ×2(div trigger、`max-height:0` 里仍可
|
|
66
|
+
Tab 的链接)、`tabs` ×2(div 当 tab、三个 `tabindex="0"`)、`badge` ×1
|
|
67
|
+
(长得一样但 Tab 不到的 div)、`overlay` ×1(一条「文件已删除」的真 toast)。
|
|
68
|
+
|
|
69
|
+
### 登记的 10 页(不假装解决)
|
|
70
|
+
|
|
71
|
+
- **可量但本轮没做(5 页)**:`choice` / `separator` / `switch` /
|
|
72
|
+
`accordion` / `tabs` —— 差别用 `count` / `h` 就能写成互斥断言
|
|
73
|
+
(`<hr>` 个数 0 vs 1、`input` 个数 0 vs 1…),缺的是把页面重排成 `cmp`。
|
|
74
|
+
**这是欠账,不是边界**,理由里原话写着。
|
|
75
|
+
- **量不了(5 页)**:`badge`(对比度与焦点可达性,`demo-claim` 没有这两个
|
|
76
|
+
指标)、`form-validation`(时序与措辞)、`overlay`(运行时焦点落点)、
|
|
77
|
+
`echarts-adapter`(没有图表实例可摆)、`sparkline`(读屏输出与
|
|
78
|
+
`reduced-motion`)。
|
|
79
|
+
|
|
80
|
+
### 契约变化(一处,如实记录)
|
|
81
|
+
|
|
82
|
+
`ai/components.json` 里 `switch` 的 `aria.attrs` 从 `[]` 变成 `["checked"]`。
|
|
83
|
+
原因是新加的反例真的写了 `aria-checked`(那一块讲的就是"手写这个属性"),
|
|
84
|
+
而抽取器只取 `aria-` 后面的名字(与 `expanded` 同约定)。原生
|
|
85
|
+
`<input role="switch">` 并不需要它 —— 这条是**反例带进契约**的。
|
|
86
|
+
选择保留并写在这里,而不是悄悄让它躺在 diff 里(`internal/ERRORS.md` E122)。
|
|
87
|
+
|
|
88
|
+
### 门禁
|
|
89
|
+
|
|
90
|
+
条数不变(197 道,本轮没有新增门禁文件,只是同一道门禁多了两条判据)。
|
|
91
|
+
|
|
92
|
+
两处**门禁自身**的改动:
|
|
93
|
+
|
|
94
|
+
- `a11y-ax` 从 10 页扩到 **11 页**,新增 `accordion`(`button + expanded +
|
|
95
|
+
受控 region` 这一组关系,与 `tablist` 的 `selected/controls` 不同源;
|
|
96
|
+
而 `accordion-contract.js` 查的是"属性写得对不对",查不到"读屏实际念出什么")。
|
|
97
|
+
加进去之后顺带证实:`max-height:0` 那块里被裁掉的 `<a>` **仍在无障碍树里**
|
|
98
|
+
(快照里是 `link "里面的这个链接,Tab 也仍然进得去"`),反例不是嘴上说的。
|
|
99
|
+
- 三页的朗读序列快照更新(`switch` / `tabs` / `overlay`)—— 都是因为新增了
|
|
100
|
+
真标本:假开关现在会被念成 `switch "开启动价推送"`,假 tablist 会被念成
|
|
101
|
+
`tablist "基本信息 高级设置"`,`「文件已删除」` 那条 toast 会被念成
|
|
102
|
+
`status [live=polite atomic=true]`。三处都逐条核过,不是照单全收。
|
|
103
|
+
|
|
104
|
+
### 自己踩的坑(`internal/ERRORS.md` E119–E123)
|
|
105
|
+
|
|
106
|
+
- **E119/E120**:判"有没有标本"的两条错路(类名定义表 / 边框底色),
|
|
107
|
+
两头都有误判实例,都留在代码注释里。
|
|
108
|
+
- **E121**:自检结论把"必须绿"的用例算进了"反向控制"总数 —— 数字对、
|
|
109
|
+
措辞撒谎。
|
|
110
|
+
- **E122**:反例把 `aria-checked` 带进了对外契约(见上一节)。
|
|
111
|
+
- **E123**:给反例的 `div` 顺手补了 `aria-expanded`,`axe` 报
|
|
112
|
+
`aria-allowed-attr` —— 反例里写"正确答案"会让反例变成半成品正例。
|
|
113
|
+
|
|
19
114
|
## [0.11.12] - 2026-10-29
|
|
20
115
|
|
|
21
116
|
**反例从「看得见」走到「量得出」**。0.11.11 把 24 个对照块摆了出来,
|
package/CONTRIBUTING.md
CHANGED
|
@@ -120,7 +120,19 @@ fix: <一句话说清改了什么、为什么>
|
|
|
120
120
|
`nav` 的反例长标题根本没换行(`.toc__link` 自带 `nowrap`,只是被裁掉),
|
|
121
121
|
`list` 同形,`dropdown` 的反例写了 `display:flex` 绕开了真实的 24px 坑。
|
|
122
122
|
只判"块在不在"时,这三处全是绿的。
|
|
123
|
-
⇒
|
|
123
|
+
⇒ **写不出断言的块,先看第 8 条**:要么补标本让它可量,要么写明为什么量不了。
|
|
124
|
+
8. **反例块里必须有标本;摆不出来就写清理由。**
|
|
125
|
+
`box bad` / `cmp__bad` 里出现"标题 + 一段说明"这种形态,`demo-claim` 会红。
|
|
126
|
+
这不是假想的洁癖:0.11.12 发布后扫了全部 34 个 demo 页,**13 个反例块
|
|
127
|
+
一个标本都没有** —— `separator` 标题写着"用 div + border-bottom 代替 hr",
|
|
128
|
+
块里连那个 div 都没有,只有一段话在描述它。读者学不到任何看得见的东西。
|
|
129
|
+
摆不出可见标本的(运行时行为、读屏输出、读者要自己按 Tab 才能发现、
|
|
130
|
+
页面上没有图表实例),写在块上:
|
|
131
|
+
`data-prose="为什么摆不出来"`。理由至少 8 个字,会被逐条打印出来,可以被反驳。
|
|
132
|
+
只有 `box bad` 单栏、没有 `cmp` 结构的页,还要在页面里写
|
|
133
|
+
`<!-- demo-claim:skip 为什么没有成对断言 -->`。
|
|
134
|
+
**理由要区分"量不了"和"可量但没做"**:后者是欠账,写明白,别混进前者
|
|
135
|
+
—— 一句"量化不了"掩盖一处没做的活,比不写更糟。
|
|
124
136
|
|
|
125
137
|
## 提 PR 之前
|
|
126
138
|
|
package/ai/components.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
3
|
"generatedBy": "05-audit/gen-ai-contract.py",
|
|
4
|
-
"libraryVersion": "0.11.
|
|
4
|
+
"libraryVersion": "0.11.13",
|
|
5
5
|
"$comment": "机器可读的组件契约。slots = BEM 元素名(在本库里元素名就是插槽名),states.enum = 组件会在 DOM 上写出的 data-* 取值,states.legacyClass = 仍未收敛到 data 属性的历史状态类(INVARIANT I-8 未达标)。",
|
|
6
6
|
"maturityCriteria": {
|
|
7
7
|
"alpha": "缺 demo.html 或缺 05-audit 下的专属门禁 ⇒ 还没被机器验证过,不要用",
|
|
@@ -1725,7 +1725,9 @@
|
|
|
1725
1725
|
"roles": [
|
|
1726
1726
|
"switch"
|
|
1727
1727
|
],
|
|
1728
|
-
"attrs": [
|
|
1728
|
+
"attrs": [
|
|
1729
|
+
"checked"
|
|
1730
|
+
]
|
|
1729
1731
|
},
|
|
1730
1732
|
"usesOtherNamespaces": [],
|
|
1731
1733
|
"global": null,
|
package/dist/COSTS.md
CHANGED
package/index.html
CHANGED