agentcss 0.11.11 → 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 +15 -0
- package/02-primitives/badge/demo.html +5 -0
- package/02-primitives/button/demo.html +2 -2
- package/02-primitives/card/demo.html +2 -2
- package/02-primitives/choice/demo.html +1 -0
- package/02-primitives/combobox/demo.html +2 -2
- package/02-primitives/date-range/demo.html +2 -2
- package/02-primitives/input/demo.html +2 -2
- package/02-primitives/popover/demo.html +2 -2
- package/02-primitives/progress/demo.html +2 -2
- package/02-primitives/select/demo.html +2 -2
- package/02-primitives/separator/demo.html +7 -0
- package/02-primitives/skeleton/demo.html +2 -2
- package/02-primitives/switch/demo.html +10 -0
- package/03-patterns/accordion/demo.html +17 -3
- package/03-patterns/content/demo.html +2 -2
- package/03-patterns/drawer/demo.html +2 -2
- package/03-patterns/dropdown/demo.html +2 -2
- package/03-patterns/form-validation/demo.html +4 -3
- package/03-patterns/list/demo.html +11 -3
- package/03-patterns/nav/demo.html +8 -4
- package/03-patterns/overlay/demo.html +8 -1
- package/03-patterns/pagination/demo.html +2 -2
- package/03-patterns/reveal/demo.html +2 -2
- package/03-patterns/states/demo.html +2 -2
- package/03-patterns/tabs/demo.html +17 -2
- package/03-patterns/tooltip/demo.html +2 -2
- package/03-patterns/tree/demo.html +2 -2
- package/04-recipes/analysis-report/demo.html +6 -2
- package/04-recipes/data-showcase/demo.html +2 -2
- package/04-recipes/detail-page/demo.html +2 -2
- package/04-recipes/report/demo.html +2 -2
- package/04-recipes/table/demo.html +2 -2
- package/09-assets/echarts-adapter/demo.html +1 -0
- package/09-assets/sparkline/demo.html +3 -2
- package/CHANGELOG.md +158 -0
- package/CONTRIBUTING.md +22 -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
|
@@ -233,6 +233,21 @@
|
|
|
233
233
|
那三页的左栏是"一大段解释 + 示例",右栏只是一句话,所以给左栏 1.55。
|
|
234
234
|
现在每一栏都是"一个小示例 + 一句理由",两栏等宽才不会让人以为
|
|
235
235
|
某一栏更重要。真需要不等宽时用 `.cmp--wide-first`,别在页面里重写栅格。
|
|
236
|
+
|
|
237
|
+
两栏各写一条能被机器量的断言(`data-bad` / `data-good`):
|
|
238
|
+
`<div class="cmp__pane cmp__bad" data-bad="li.dd-bug|h|<=24">`
|
|
239
|
+
`<div class="cmp__pane cmp__good" data-good="li.dd__item|h|>=44">`
|
|
240
|
+
格式是 `选择器|指标|断言`,指标见 `05-audit/demo-claim-gate.js` 头部。
|
|
241
|
+
两栏的断言必须**互斥**且各自成立 —— 能同时成立的两条断言,
|
|
242
|
+
什么也证明不了。这两个属性不影响渲染,但缺了它们,
|
|
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 为什么没有成对断言 -->`
|
|
236
251
|
========================================================================= */
|
|
237
252
|
.page .cmp {
|
|
238
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>:
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
|
|
121
121
|
<h2 class="rule-bottom">对比:常见的错写法 vs 本库写法</h2>
|
|
122
122
|
<div class="cmp cmp--wide-first">
|
|
123
|
-
<div class="cmp__pane cmp__bad">
|
|
123
|
+
<div class="cmp__pane cmp__bad" data-bad="button|gw|>=3">
|
|
124
124
|
<h3>常见写法(某项目 现状)</h3>
|
|
125
125
|
<div class="row">
|
|
126
126
|
<button class="btn--naive">hover 看我</button>
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
<code>transition: all</code> —— 所有属性一起动画,性能和观感失控。
|
|
134
134
|
</p>
|
|
135
135
|
</div>
|
|
136
|
-
<div class="cmp__pane cmp__good">
|
|
136
|
+
<div class="cmp__pane cmp__good" data-good="button|gw|=0">
|
|
137
137
|
<h3>本库写法</h3>
|
|
138
138
|
<div class="row">
|
|
139
139
|
<button class="btn btn--md btn--primary">hover 看我</button>
|
|
@@ -43,7 +43,7 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
|
|
|
43
43
|
</div>
|
|
44
44
|
|
|
45
45
|
<div class="cmp cmp--wide-first">
|
|
46
|
-
<div class="cmp__pane cmp__bad">
|
|
46
|
+
<div class="cmp__pane cmp__bad" data-bad=".card-bad|gh|>=2">
|
|
47
47
|
<h3>反例:hover 改 border-width</h3>
|
|
48
48
|
<p class="small" style="margin:0 0 var(--sp-3)">
|
|
49
49
|
移上来 —— 里面那把尺子和文字整体往下挪了,卡片也变高了。
|
|
@@ -61,7 +61,7 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
|
|
|
61
61
|
</div>
|
|
62
62
|
</div>
|
|
63
63
|
|
|
64
|
-
<div class="cmp__pane cmp__good">
|
|
64
|
+
<div class="cmp__pane cmp__good" data-good=".card|gh|=0">
|
|
65
65
|
<h3>本库:transform + box-shadow</h3>
|
|
66
66
|
<p class="small" style="margin:0 0 var(--sp-3)">
|
|
67
67
|
移上来 —— 卡片整体抬起 2px,<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">
|
|
@@ -79,7 +79,7 @@ label.f { display: block; margin-bottom: var(--sp-1); font-size: var(--fs-sm); }
|
|
|
79
79
|
|
|
80
80
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
81
81
|
<div class="cmp">
|
|
82
|
-
<div class="cmp__pane cmp__bad">
|
|
82
|
+
<div class="cmp__pane cmp__bad" data-bad="mark|count|=0">
|
|
83
83
|
<h3>❌ 常见写法:结果不高亮</h3>
|
|
84
84
|
<div class="cb-demo" aria-hidden="true">
|
|
85
85
|
<div class="cb-demo__q">半导体</div>
|
|
@@ -91,7 +91,7 @@ label.f { display: block; margin-bottom: var(--sp-1); font-size: var(--fs-sm); }
|
|
|
91
91
|
</div>
|
|
92
92
|
<p class="cmp__why">输入"半导体"之后三条都留下了,但三条长得一模一样 —— 用户说不出<b>凭什么</b>是这三条,只会觉得"顺序乱了"。</p>
|
|
93
93
|
</div>
|
|
94
|
-
<div class="cmp__pane cmp__good">
|
|
94
|
+
<div class="cmp__pane cmp__good" data-good="mark|count|>=1">
|
|
95
95
|
<h3>✅ 本库:命中片段高亮</h3>
|
|
96
96
|
<div class="cb-demo" aria-hidden="true">
|
|
97
97
|
<div class="cb-demo__q">半导体</div>
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
|
|
81
81
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
82
82
|
<div class="cmp">
|
|
83
|
-
<div class="cmp__pane cmp__bad">
|
|
83
|
+
<div class="cmp__pane cmp__bad" data-bad=".drange__error|count|=0">
|
|
84
84
|
<h3>❌ 常见写法:错误只改边框颜色</h3>
|
|
85
85
|
<div class="drange" aria-hidden="true">
|
|
86
86
|
<div class="drange__field">
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
</div>
|
|
95
95
|
<p class="cmp__why">结束日期早于开始日期,界面上只是"红了一圈":色觉障碍用户看不出来,灰度打印也看不出来,而且<b>没有一句话</b>告诉你错在哪。</p>
|
|
96
96
|
</div>
|
|
97
|
-
<div class="cmp__pane cmp__good">
|
|
97
|
+
<div class="cmp__pane cmp__good" data-good=".drange__error|count|>=1">
|
|
98
98
|
<h3>✅ 本库:左侧竖线 + 一句话说明</h3>
|
|
99
99
|
<div class="drange" aria-hidden="true">
|
|
100
100
|
<div class="drange__field">
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
|
|
109
109
|
<h2 class="rule-bottom">对比:常见写法 vs 本库写法</h2>
|
|
110
110
|
<div class="cmp cmp--wide-first">
|
|
111
|
-
<div class="cmp__pane cmp__bad">
|
|
111
|
+
<div class="cmp__pane cmp__bad" data-bad=".field__error|count|=0">
|
|
112
112
|
<h3>常见写法</h3>
|
|
113
113
|
<input class="bad-input err" value="abc" aria-hidden="true" tabindex="-1">
|
|
114
114
|
<!-- 🔴 加 aria-hidden。
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
错误文字没有 <code>aria-describedby</code> —— 屏幕阅读器读不到
|
|
126
126
|
</p>
|
|
127
127
|
</div>
|
|
128
|
-
<div class="cmp__pane cmp__good">
|
|
128
|
+
<div class="cmp__pane cmp__good" data-good=".field__error|count|>=1">
|
|
129
129
|
<h3>本库写法</h3>
|
|
130
130
|
<div class="field">
|
|
131
131
|
<label class="field__label" for="g1">金额</label>
|
|
@@ -160,7 +160,7 @@ demo('bad1', 'bottom',
|
|
|
160
160
|
|
|
161
161
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
162
162
|
<div class="cmp">
|
|
163
|
-
<div class="cmp__pane cmp__bad">
|
|
163
|
+
<div class="cmp__pane cmp__bad" data-bad=".popover|count|>=2">
|
|
164
164
|
<h3>❌ 常见写法:两个浮层同时开着</h3>
|
|
165
165
|
<div class="pp-stage" aria-hidden="true">
|
|
166
166
|
<button class="btn btn--sm btn--ghost" type="button" tabindex="-1">筛选</button>
|
|
@@ -177,7 +177,7 @@ demo('bad1', 'bottom',
|
|
|
177
177
|
</div>
|
|
178
178
|
<p class="cmp__why">两个都开着:点空白处到底关的是哪一个?读屏用户按 Esc 也不知道关掉的是哪个,而两个浮层的内容还叠在一起。</p>
|
|
179
179
|
</div>
|
|
180
|
-
<div class="cmp__pane cmp__good">
|
|
180
|
+
<div class="cmp__pane cmp__good" data-good=".popover|count|=1">
|
|
181
181
|
<h3>✅ 本库:同一时刻只开一个</h3>
|
|
182
182
|
<div class="pp-stage" aria-hidden="true">
|
|
183
183
|
<button class="btn btn--sm btn--ghost" type="button" tabindex="-1">筛选</button>
|
|
@@ -155,7 +155,7 @@ document.querySelectorAll('.progress, .progress-ring, .progress-steps').forEach(
|
|
|
155
155
|
|
|
156
156
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
157
157
|
<div class="cmp">
|
|
158
|
-
<div class="cmp__pane cmp__bad">
|
|
158
|
+
<div class="cmp__pane cmp__bad" data-bad=".progress--success|count|=1">
|
|
159
159
|
<h3>❌ 常见写法:到 100% 就变绿</h3>
|
|
160
160
|
<p class="pr-label">导出 6 个文件 · 100%</p>
|
|
161
161
|
<span class="progress progress--success" role="progressbar" aria-valuenow="100"
|
|
@@ -164,7 +164,7 @@ document.querySelectorAll('.progress, .progress-ring, .progress-steps').forEach(
|
|
|
164
164
|
<p class="pr-note">界面写"导出完成",条是绿的。</p>
|
|
165
165
|
<p class="cmp__why">100% 说的是"走到头了",不是"成功了"。这一批实际有 2 个文件失败,绿条仍然亮着 —— 用户以为可以走了。</p>
|
|
166
166
|
</div>
|
|
167
|
-
<div class="cmp__pane cmp__good">
|
|
167
|
+
<div class="cmp__pane cmp__good" data-good=".progress--success|count|=0">
|
|
168
168
|
<h3>✅ 本库:颜色讲结果,数值只讲进度</h3>
|
|
169
169
|
<p class="pr-label">导出 6 个文件 · 100%</p>
|
|
170
170
|
<span class="progress progress--danger" role="progressbar" aria-valuenow="100"
|
|
@@ -115,7 +115,7 @@ label.f { display: block; margin-bottom: var(--sp-1); font-size: var(--fs-sm); }
|
|
|
115
115
|
|
|
116
116
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
117
117
|
<div class="cmp">
|
|
118
|
-
<div class="cmp__pane cmp__bad">
|
|
118
|
+
<div class="cmp__pane cmp__bad" data-bad="li.sl-tight|h|<=28">
|
|
119
119
|
<h3>❌ 常见写法:选项挤在一起</h3>
|
|
120
120
|
<div class="sl-wrap" aria-hidden="true">
|
|
121
121
|
<div class="sl-head">所属板块</div>
|
|
@@ -127,7 +127,7 @@ label.f { display: block; margin-bottom: var(--sp-1); font-size: var(--fs-sm); }
|
|
|
127
127
|
</div>
|
|
128
128
|
<p class="cmp__why">行高只有 28px:手指点下去必然压到隔壁那一行。鼠标用户感觉不到,触屏用户每点错一次就要重来。</p>
|
|
129
129
|
</div>
|
|
130
|
-
<div class="cmp__pane cmp__good">
|
|
130
|
+
<div class="cmp__pane cmp__good" data-good="li.select__opt|h|>=44">
|
|
131
131
|
<h3>✅ 本库:每项 44px 命中区</h3>
|
|
132
132
|
<div class="sl-wrap" aria-hidden="true">
|
|
133
133
|
<div class="sl-head">所属板块</div>
|
|
@@ -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>),
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
|
|
114
114
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
115
115
|
<div class="cmp">
|
|
116
|
-
<div class="cmp__pane cmp__bad">
|
|
116
|
+
<div class="cmp__pane cmp__bad" data-bad=".progress|count|=0">
|
|
117
117
|
<h3>❌ 常见写法:有确切进度却用骨架屏</h3>
|
|
118
118
|
<p class="sk-label">上传 素材包.zip(3.1 MB)</p>
|
|
119
119
|
<div class="sk-box" aria-hidden="true">
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
</div>
|
|
124
124
|
<p class="cmp__why">文件大小是已知的、进度是算得出来的,却只给一片灰条:用户等了 40 秒也不知道还差 3 秒还是 3 分钟。</p>
|
|
125
125
|
</div>
|
|
126
|
-
<div class="cmp__pane cmp__good">
|
|
126
|
+
<div class="cmp__pane cmp__good" data-good=".progress|count|>=1">
|
|
127
127
|
<h3>✅ 本库:确定进度用 progress,不确定才用 skeleton</h3>
|
|
128
128
|
<p class="sk-label">上传 素材包.zip(3.1 MB)</p>
|
|
129
129
|
<p class="sk-pct">已传 1.9 MB / 3.1 MB</p>
|
|
@@ -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>
|
|
@@ -87,14 +87,14 @@
|
|
|
87
87
|
|
|
88
88
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
89
89
|
<div class="cmp">
|
|
90
|
-
<div class="cmp__pane cmp__bad">
|
|
90
|
+
<div class="cmp__pane cmp__bad" data-bad="hr|h|>=2">
|
|
91
91
|
<h3>❌ 常见写法:裸 <code><hr></code></h3>
|
|
92
92
|
<div aria-hidden="true"> <p class="ct-p">本季应收账款周转天数下降 6 天。</p>
|
|
93
93
|
<hr>
|
|
94
94
|
<p class="ct-p">下一节:存货跌价准备的计提口径。</p></div>
|
|
95
95
|
<p class="cmp__why">默认 <code><hr></code> 自带上下两条 inset 边框,不清 <code>height</code> 和 <code>border-top</code> 出来就是一条粗双线 —— 而且不同浏览器的厚度还不一样。</p>
|
|
96
96
|
</div>
|
|
97
|
-
<div class="cmp__pane cmp__good">
|
|
97
|
+
<div class="cmp__pane cmp__good" data-good="hr|h|<=1">
|
|
98
98
|
<h3>✅ 本库:<code>.prose > hr</code></h3>
|
|
99
99
|
<div class="prose" aria-hidden="true"> <p class="ct-p">本季应收账款周转天数下降 6 天。</p>
|
|
100
100
|
<hr>
|
|
@@ -68,7 +68,7 @@ ul.demo-list li { margin: var(--sp-1) 0; }
|
|
|
68
68
|
|
|
69
69
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
70
70
|
<div class="cmp">
|
|
71
|
-
<div class="cmp__pane cmp__bad">
|
|
71
|
+
<div class="cmp__pane cmp__bad" data-bad="button.dr-tiny|h|<=24">
|
|
72
72
|
<h3>❌ 常见写法:关闭按钮做小</h3>
|
|
73
73
|
<div class="dr-bar" aria-hidden="true">
|
|
74
74
|
<span class="dr-bar__title">筛选</span>
|
|
@@ -76,7 +76,7 @@ ul.demo-list li { margin: var(--sp-1) 0; }
|
|
|
76
76
|
</div>
|
|
77
77
|
<p class="cmp__why">关闭是**独立目标**:抽屉滑进来之后,用户第一件事就是找它。24px 的叉在触屏上要瞄准,点偏了就点到了背后的内容。</p>
|
|
78
78
|
</div>
|
|
79
|
-
<div class="cmp__pane cmp__good">
|
|
79
|
+
<div class="cmp__pane cmp__good" data-good="button.drawer__close|h|>=44">
|
|
80
80
|
<h3>✅ 本库:<code>drawer__close</code> 44px</h3>
|
|
81
81
|
<div class="dr-bar" aria-hidden="true">
|
|
82
82
|
<span class="dr-bar__title">筛选</span>
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
|
|
92
92
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
93
93
|
<div class="cmp">
|
|
94
|
-
<div class="cmp__pane cmp__bad">
|
|
94
|
+
<div class="cmp__pane cmp__bad" data-bad="li.dd-bug|h|<=24">
|
|
95
95
|
<h3>❌ 常见写法:菜单项只有 24px</h3>
|
|
96
96
|
<p class="dd-cap">下拉菜单 · 每项高度</p>
|
|
97
97
|
<ul class="dd-demo" aria-hidden="true">
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
</ul>
|
|
102
102
|
<p class="cmp__why">24px 一行:手指点下去会压到隔壁,触屏用户每点错一次就得重来。本库在这块踩过更隐蔽的坑 —— 写 <code>min-height: 44px</code>,量出来只有 24px(<code><li></code> 是 <code>display:list-item</code>),所以现在用的是显式 <code>height</code>。</p>
|
|
103
103
|
</div>
|
|
104
|
-
<div class="cmp__pane cmp__good">
|
|
104
|
+
<div class="cmp__pane cmp__good" data-good="li.dd__item|h|>=44">
|
|
105
105
|
<h3>✅ 本库:显式 <code>height: 44px</code></h3>
|
|
106
106
|
<p class="dd-cap">下拉菜单 · 每项高度</p>
|
|
107
107
|
<ul class="dd-demo" aria-hidden="true">
|
|
@@ -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>
|
|
@@ -20,6 +20,14 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
|
|
|
20
20
|
.ls-demo { list-style: none; margin: 0; padding: 0; background: var(--surface-raised);
|
|
21
21
|
border: 1px solid var(--border-decor); border-radius: var(--r-sm); }
|
|
22
22
|
.ls-clip { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
23
|
+
/* 🔴 反例栏演示的是"长文本直接换行"。.list__title 自带 ellipsis,
|
|
24
|
+
不把 nowrap 取消掉,两栏就一模一样(实测:这个错犯过)。 */
|
|
25
|
+
.ls-wrap { white-space: normal; }
|
|
26
|
+
/* 🔴 对照块里的列表必须**和栏宽无关**:这一块演示的是"长文本折行",
|
|
27
|
+
而折行取决于行宽 —— 宽屏(1440px,栏约 490px)下它就不折了,
|
|
28
|
+
反例在那种宽度上根本不成立(实测:900px 折成 2 行,1440px 只有 1 行)。
|
|
29
|
+
钉住 300px 之后,任何视口宽度下都是同一个样子。 */
|
|
30
|
+
.cmp .ls-demo { max-width: 300px; }
|
|
23
31
|
</style>
|
|
24
32
|
</head>
|
|
25
33
|
<body class="page page">
|
|
@@ -92,12 +100,12 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
|
|
|
92
100
|
|
|
93
101
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
94
102
|
<div class="cmp">
|
|
95
|
-
<div class="cmp__pane cmp__bad">
|
|
103
|
+
<div class="cmp__pane cmp__bad" data-bad="li.list__item|h|>=80">
|
|
96
104
|
<h3>❌ 常见写法:长文本直接换行</h3>
|
|
97
105
|
<ul class="ls-demo" aria-hidden="true">
|
|
98
106
|
<li class="list__item">
|
|
99
107
|
<span class="list__main">
|
|
100
|
-
<span class="list__title">长鑫存储技术有限公司(合肥)2026 年三季报</span>
|
|
108
|
+
<span class="list__title ls-wrap">长鑫存储技术有限公司(合肥)2026 年三季报</span>
|
|
101
109
|
<span class="list__meta">12 条记录 · 半导体</span>
|
|
102
110
|
</span>
|
|
103
111
|
<span class="list__value num list__value--up">+3.21%</span>
|
|
@@ -112,7 +120,7 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
|
|
|
112
120
|
</ul>
|
|
113
121
|
<p class="cmp__why">第一行折成两行,行高就不一样了:一屏能扫到的条目从 8 条掉到 5 条,而且"哪一行对应哪个数字"要重新对。</p>
|
|
114
122
|
</div>
|
|
115
|
-
<div class="cmp__pane cmp__good">
|
|
123
|
+
<div class="cmp__pane cmp__good" data-good="li.list__item|h|<=70">
|
|
116
124
|
<h3>✅ 本库:单行省略 + <code>title</code></h3>
|
|
117
125
|
<ul class="ls-demo" aria-hidden="true">
|
|
118
126
|
<li class="list__item">
|
|
@@ -32,6 +32,10 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.7
|
|
|
32
32
|
|
|
33
33
|
/* 让目录高亮有滚动空间 */
|
|
34
34
|
.toc-clip { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
35
|
+
/* 🔴 反例栏要演示的是"不写 nowrap 会怎样"。.toc__link 自带 nowrap,
|
|
36
|
+
所以只在反例栏里把它取消掉 —— 不取消的话两栏都是 1 行,
|
|
37
|
+
反例就变成"被裁掉",而标题说的是"换行"(实测:这个错犯过)。 */
|
|
38
|
+
.toc-wrap { white-space: normal; }
|
|
35
39
|
</style>
|
|
36
40
|
</head>
|
|
37
41
|
<body class="page page">
|
|
@@ -120,24 +124,24 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.7
|
|
|
120
124
|
(`href="#x"`)点下去会跳到页面顶部,读者以为"目录坏了"。
|
|
121
125
|
第一条故意写得长 —— 真实文档的长章节名就是这样,能不能单行是这条的判据。 -->
|
|
122
126
|
<div class="cmp">
|
|
123
|
-
<div class="cmp__pane cmp__bad">
|
|
127
|
+
<div class="cmp__pane cmp__bad" data-bad="a.toc__link|lines|>=2">
|
|
124
128
|
<h3>❌ 常见写法:目录项允许换行</h3>
|
|
125
129
|
<nav class="toc" aria-label="示例目录" aria-hidden="true">
|
|
126
130
|
<p class="toc__title">本页</p>
|
|
127
131
|
<ul class="toc__list">
|
|
128
|
-
<li><a class="toc__link" href="#mem" tabindex="-1">存储器构成与 2026
|
|
132
|
+
<li><a class="toc__link toc-wrap" href="#mem" tabindex="-1">存储器构成与 2026 年三季度的产能爬坡节奏与毛利口径拆解</a></li>
|
|
129
133
|
<li><a class="toc__link" href="#sched" tabindex="-1">优先级与准入</a></li>
|
|
130
134
|
<li><a class="toc__link" href="#sim" tabindex="-1">把它跑起来</a></li>
|
|
131
135
|
</ul>
|
|
132
136
|
</nav>
|
|
133
137
|
<p class="cmp__why">一条目录折成两行,下面两条就都被推下去了 —— 目录的价值是"一眼看到全文结构",行高一变,结构感就没了。</p>
|
|
134
138
|
</div>
|
|
135
|
-
<div class="cmp__pane cmp__good">
|
|
139
|
+
<div class="cmp__pane cmp__good" data-good="a.toc__link|lines|<=1">
|
|
136
140
|
<h3>✅ 本库:单行、超出省略</h3>
|
|
137
141
|
<nav class="toc" aria-label="示例目录">
|
|
138
142
|
<p class="toc__title">本页</p>
|
|
139
143
|
<ul class="toc__list">
|
|
140
|
-
<li><a class="toc__link toc-clip" href="#mem">存储器构成与 2026
|
|
144
|
+
<li><a class="toc__link toc-clip" href="#mem">存储器构成与 2026 年三季度的产能爬坡节奏与毛利口径拆解</a></li>
|
|
141
145
|
<li><a class="toc__link" href="#sched">优先级与准入</a></li>
|
|
142
146
|
<li><a class="toc__link" href="#sim">把它跑起来</a></li>
|
|
143
147
|
</ul>
|
|
@@ -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
|
用户可能根本没看见就把文件删了,而且没法撤销。
|
|
@@ -123,7 +123,7 @@ th { font-size: var(--fs-xs); color: var(--text-secondary); font-weight: 500; }
|
|
|
123
123
|
|
|
124
124
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
125
125
|
<div class="cmp">
|
|
126
|
-
<div class="cmp__pane cmp__bad">
|
|
126
|
+
<div class="cmp__pane cmp__bad" data-bad="li.pagination__item|count|>7">
|
|
127
127
|
<h3>❌ 常见写法:页码全铺开</h3>
|
|
128
128
|
<nav aria-label="分页示例" aria-hidden="true">
|
|
129
129
|
<ul class="pagination__list">
|
|
@@ -143,7 +143,7 @@ th { font-size: var(--fs-xs); color: var(--text-secondary); font-weight: 500; }
|
|
|
143
143
|
</nav>
|
|
144
144
|
<p class="cmp__why">12 个页码硬塞进一行,窄屏上要么换行、要么每个都挤到点不中。而 4~10 页其实是读者最不会去点的区间。</p>
|
|
145
145
|
</div>
|
|
146
|
-
<div class="cmp__pane cmp__good">
|
|
146
|
+
<div class="cmp__pane cmp__good" data-good="li.pagination__item|count|<=7">
|
|
147
147
|
<h3>✅ 本库:7 个槽位封顶 + 省略号</h3>
|
|
148
148
|
<nav aria-label="分页示例">
|
|
149
149
|
<ul class="pagination__list">
|
|
@@ -91,12 +91,12 @@
|
|
|
91
91
|
|
|
92
92
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
93
93
|
<div class="cmp">
|
|
94
|
-
<div class="cmp__pane cmp__bad">
|
|
94
|
+
<div class="cmp__pane cmp__bad" data-bad=".rv-card|hidden|=1">
|
|
95
95
|
<h3>❌ 常见写法:藏了但没兜底</h3>
|
|
96
96
|
<div class="rv-card rv-hidden" aria-hidden="true"></div>
|
|
97
97
|
<p class="cmp__why">内容真被 <code>opacity: 0</code> 藏着,而脚本没跑起来(被拦、报错、CSP)—— 这一块就永远是空的,读者不知道这里有东西。</p>
|
|
98
98
|
</div>
|
|
99
|
-
<div class="cmp__pane cmp__good">
|
|
99
|
+
<div class="cmp__pane cmp__good" data-good=".rv-card|hidden|=0">
|
|
100
100
|
<h3>✅ 本库:只有 <code>data-reveal-ready</code> 时才藏</h3>
|
|
101
101
|
<div class="rv-card" aria-hidden="true">
|
|
102
102
|
<p class="rv-card__t">2026 年三季度回顾</p>
|
|
@@ -239,7 +239,7 @@ show('content');
|
|
|
239
239
|
|
|
240
240
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
241
241
|
<div class="cmp">
|
|
242
|
-
<div class="cmp__pane cmp__bad">
|
|
242
|
+
<div class="cmp__pane cmp__bad" data-bad=".state__action|count|=0">
|
|
243
243
|
<h3>❌ 常见写法:empty 态没有出口</h3>
|
|
244
244
|
<div class="state state--empty" aria-hidden="true">
|
|
245
245
|
<div class="state__icon">□</div>
|
|
@@ -248,7 +248,7 @@ show('content');
|
|
|
248
248
|
</div>
|
|
249
249
|
<p class="cmp__why">说明写得很清楚,然后呢?用户读完只剩一个动作:刷新,或者退出。一条"没有数据"的提示不构成一个状态,它只是死路。</p>
|
|
250
250
|
</div>
|
|
251
|
-
<div class="cmp__pane cmp__good">
|
|
251
|
+
<div class="cmp__pane cmp__good" data-good=".state__action|count|>=1">
|
|
252
252
|
<h3>✅ 本库:empty 态必须带动作</h3>
|
|
253
253
|
<div class="state state--empty" aria-hidden="true">
|
|
254
254
|
<div class="state__icon">□</div>
|
|
@@ -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>
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
|
|
67
67
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
68
68
|
<div class="cmp">
|
|
69
|
-
<div class="cmp__pane cmp__bad">
|
|
69
|
+
<div class="cmp__pane cmp__bad" data-bad=".tt-real|count|=0">
|
|
70
70
|
<h3>❌ 常见写法:关键信息只放在气泡里</h3>
|
|
71
71
|
<div class="tt-field" aria-hidden="true">
|
|
72
72
|
<span class="tt-label">账期</span>
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
<p class="tt-hint">(把鼠标停在那颗 ⓘ 上才看得到说明)</p>
|
|
76
76
|
<p class="cmp__why">触屏没有 hover,键盘用户也未必会停留 —— 这条说明对一半以上的读者等于不存在。判断标准很硬:<b>气泡里的字不能是"必须知道才能填对"的信息</b>。</p>
|
|
77
77
|
</div>
|
|
78
|
-
<div class="cmp__pane cmp__good">
|
|
78
|
+
<div class="cmp__pane cmp__good" data-good=".tt-real|count|>=1">
|
|
79
79
|
<h3>✅ 本库:该看见的写在页面上</h3>
|
|
80
80
|
<div class="tt-field" aria-hidden="true">
|
|
81
81
|
<span class="tt-label">账期</span>
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
|
|
61
61
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
62
62
|
<div class="cmp">
|
|
63
|
-
<div class="cmp__pane cmp__bad">
|
|
63
|
+
<div class="cmp__pane cmp__bad" data-bad="span.tr-one|uniq|=1">
|
|
64
64
|
<h3>❌ 常见写法:靠"旋转"表示状态</h3>
|
|
65
65
|
<ul class="tr-demo" role="tree" aria-label="文件树示例" aria-hidden="true">
|
|
66
66
|
<li class="tree__item" aria-expanded="true">
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
</ul>
|
|
78
78
|
<p class="cmp__why">三个节点长得一模一样:已展开的 src、收起的 components、叶子的 index.js 全是 ▸。旋转只能表达"变过",表达不了"现在是哪一态"。</p>
|
|
79
79
|
</div>
|
|
80
|
-
<div class="cmp__pane cmp__good">
|
|
80
|
+
<div class="cmp__pane cmp__good" data-good="span.tree__marker|uniq|>=3">
|
|
81
81
|
<h3>✅ 本库:三态三种字形</h3>
|
|
82
82
|
<ul class="tr-demo" role="tree" aria-label="文件树示例" aria-hidden="true">
|
|
83
83
|
<li class="tree__item" aria-expanded="true">
|
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
那个"列被长内容撑开"的毛病就被自己抹平了(第一版就是这么写坏的) */
|
|
29
29
|
.ar-long { font-family: var(--font-mono, monospace); white-space: nowrap; }
|
|
30
30
|
.ar-g--ok .ar-long { overflow: hidden; text-overflow: ellipsis; }
|
|
31
|
+
/* 🔴 对照块里的栅格宽度钉住:断言量的是"列宽有没有被长串顶开",
|
|
32
|
+
而列宽本身随栏宽变 —— 宽屏下两栏都变宽,绝对阈值就失去意义。
|
|
33
|
+
钉在 360px,任何视口宽度下量的都是同一个东西。 */
|
|
34
|
+
.cmp .ar-g { max-width: 360px; }
|
|
31
35
|
</style>
|
|
32
36
|
</head>
|
|
33
37
|
<body class="page">
|
|
@@ -239,7 +243,7 @@
|
|
|
239
243
|
<!-- cmp--clip:这一块演示的就是"长串把列撑开",栏内必须允许算得比栏宽更宽;
|
|
240
244
|
越出栏框的部分裁掉(不裁的话 320px 下整页横向溢出 64px,WCAG 1.4.10 红)。 -->
|
|
241
245
|
<div class="cmp cmp--clip">
|
|
242
|
-
<div class="cmp__pane cmp__bad">
|
|
246
|
+
<div class="cmp__pane cmp__bad" data-bad=".ar-cell.ar-long|w|>250">
|
|
243
247
|
<h3>❌ 常见写法:栅格列写 <code>1fr</code></h3>
|
|
244
248
|
<div class="ar-g" aria-hidden="true">
|
|
245
249
|
<div class="ar-cell">正文一栏:营收同比 +18%。</div>
|
|
@@ -247,7 +251,7 @@
|
|
|
247
251
|
</div>
|
|
248
252
|
<p class="cmp__why">右栏里一串不肯断的长代码把列撑开了:它不换行、也不省略,整页横向溢出 —— 而外层容器并<b>没有</b>写错,错的是列定义。</p>
|
|
249
253
|
</div>
|
|
250
|
-
<div class="cmp__pane cmp__good">
|
|
254
|
+
<div class="cmp__pane cmp__good" data-good=".ar-cell.ar-long|w|<=200">
|
|
251
255
|
<h3>✅ 本库:<code>minmax(0, 1fr)</code></h3>
|
|
252
256
|
<div class="ar-g ar-g--ok" aria-hidden="true">
|
|
253
257
|
<div class="ar-cell">正文一栏:营收同比 +18%。</div>
|
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
|
|
228
228
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
229
229
|
<div class="cmp">
|
|
230
|
-
<div class="cmp__pane cmp__bad">
|
|
230
|
+
<div class="cmp__pane cmp__bad" data-bad=".ds-u|fs|>=20">
|
|
231
231
|
<h3>❌ 常见写法:单位与数字同字号同色</h3>
|
|
232
232
|
<div class="ds-stat" aria-hidden="true">
|
|
233
233
|
<span class="ds-k">营业收入</span>
|
|
@@ -235,7 +235,7 @@
|
|
|
235
235
|
</div>
|
|
236
236
|
<p class="cmp__why">"亿元"只是个量级标记,却和 1,284.6 抢同一层注意力:读者先读到的是四个字,而不是那个数。</p>
|
|
237
237
|
</div>
|
|
238
|
-
<div class="cmp__pane cmp__good">
|
|
238
|
+
<div class="cmp__pane cmp__good" data-good=".ds-u|fs|<=16">
|
|
239
239
|
<h3>✅ 本库:数字当主角,单位退一档</h3>
|
|
240
240
|
<div class="ds-stat" aria-hidden="true">
|
|
241
241
|
<span class="ds-k">营业收入</span>
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
|
|
62
62
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
63
63
|
<div class="cmp">
|
|
64
|
-
<div class="cmp__pane cmp__bad">
|
|
64
|
+
<div class="cmp__pane cmp__bad" data-bad=".dp-kpi__v|fs|>=24">
|
|
65
65
|
<h3>❌ 常见写法:缺的字段写 0</h3>
|
|
66
66
|
<div class="dp-kpi" aria-hidden="true">
|
|
67
67
|
<span class="dp-kpi__k">研发投入</span>
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
</div>
|
|
70
70
|
<p class="cmp__why"><code>0</code> 是一个真实的值:读者会拿它去算"研发强度 = 0",也会拿它去比同比 —— 而这一格其实<b>没有</b>数据。</p>
|
|
71
71
|
</div>
|
|
72
|
-
<div class="cmp__pane cmp__good">
|
|
72
|
+
<div class="cmp__pane cmp__good" data-good=".detail-null__v|fs|<=22">
|
|
73
73
|
<h3>✅ 本库:写「未披露」+ 弱化色</h3>
|
|
74
74
|
<div class="dp-kpi" aria-hidden="true">
|
|
75
75
|
<span class="dp-kpi__k">研发投入</span>
|
|
@@ -306,7 +306,7 @@
|
|
|
306
306
|
|
|
307
307
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
308
308
|
<div class="cmp">
|
|
309
|
-
<div class="cmp__pane cmp__bad">
|
|
309
|
+
<div class="cmp__pane cmp__bad" data-bad=".rep__cap|count|=0">
|
|
310
310
|
<h3>❌ 常见写法:来源与正文同一副面孔</h3>
|
|
311
311
|
<div class="rep" aria-hidden="true">
|
|
312
312
|
<p class="rep-p">本季度毛利率 42.1%,环比提升 1.3 个百分点。</p>
|
|
@@ -314,7 +314,7 @@
|
|
|
314
314
|
</div>
|
|
315
315
|
<p class="cmp__why">读者读到第二句才知道这是"出处",而它和正文长得一模一样 —— 分不清哪句是结论、哪句是依据。</p>
|
|
316
316
|
</div>
|
|
317
|
-
<div class="cmp__pane cmp__good">
|
|
317
|
+
<div class="cmp__pane cmp__good" data-good=".rep__cap|count|>=1">
|
|
318
318
|
<h3>✅ 本库:来源用 <code>rep__cap</code> 这一档角色</h3>
|
|
319
319
|
<div class="rep" aria-hidden="true">
|
|
320
320
|
<p class="rep-p">本季度毛利率 42.1%,环比提升 1.3 个百分点。</p>
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
|
|
97
97
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
98
98
|
<div class="cmp">
|
|
99
|
-
<div class="cmp__pane cmp__bad">
|
|
99
|
+
<div class="cmp__pane cmp__bad" data-bad="td.num--pos|glyphs|=5">
|
|
100
100
|
<h3>❌ 常见写法:只靠红绿 + 连字符</h3>
|
|
101
101
|
<div class="table tb-bad" aria-hidden="true">
|
|
102
102
|
<table class="tb-demo">
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
</div>
|
|
111
111
|
<p class="cmp__why">涨的那行没有 <code>+</code>,跌的用连字符 <code>-</code>:灰度打印、色觉障碍、以及"只扫一列符号"的读法,全都不成立;连字符还比真减号窄,一列数字的个位对不齐。</p>
|
|
112
112
|
</div>
|
|
113
|
-
<div class="cmp__pane cmp__good">
|
|
113
|
+
<div class="cmp__pane cmp__good" data-good="td.num--pos|glyphs|>=6">
|
|
114
114
|
<h3>✅ 本库:符号 + 真减号 <code>−</code></h3>
|
|
115
115
|
<div class="table" aria-hidden="true">
|
|
116
116
|
<table class="tb-demo">
|
|
@@ -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,164 @@
|
|
|
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
|
+
|
|
114
|
+
## [0.11.12] - 2026-10-29
|
|
115
|
+
|
|
116
|
+
**反例从「看得见」走到「量得出」**。0.11.11 把 24 个对照块摆了出来,
|
|
117
|
+
但那只证明了「块在」—— 块里那两栏到底有没有差别,没人验过。
|
|
118
|
+
这一版让每栏写一条数字断言:门禁量它,并且要求两栏的断言**互斥**。
|
|
119
|
+
|
|
120
|
+
### 抓到的真问题:三处「块在、标题对、反例不成立」
|
|
121
|
+
|
|
122
|
+
- `03-patterns/nav` —— 反例栏那条长标题**根本没换行**。`.toc__link` 自带
|
|
123
|
+
`white-space: nowrap`,长标题只是被 `overflow: hidden` 裁掉了,而标题写着
|
|
124
|
+
「允许换行」。量出来两栏高度都是 28.4px,谁也看不出区别。
|
|
125
|
+
- `03-patterns/list` —— 同一形态:`.list__title` 自带 `text-overflow: ellipsis`,
|
|
126
|
+
两栏一模一样,多的那个类名不改变任何结果。
|
|
127
|
+
- `02-primitives/dropdown` —— 上一版就踩过:反例写了 `display: flex`,
|
|
128
|
+
恰好绕开了 `<li>` 的真实 24px 坑。
|
|
129
|
+
|
|
130
|
+
前两处已改成「两栏用同一句长文本,反例真的折行、正例真的省略」。
|
|
131
|
+
第三处的教训写进了 `internal/ERRORS.md` —— 反例的坏必须来自**量过的**坑。
|
|
132
|
+
|
|
133
|
+
### 新增
|
|
134
|
+
|
|
135
|
+
- **`05-audit/demo-claim-gate.js`**:每个 `.cmp` 块的两栏各写一条
|
|
136
|
+
`data-bad` / `data-good`(格式 `选择器|指标|断言`,例 `li.dd-bug|h|<=24`),
|
|
137
|
+
判三件事:① 两栏都写了 ② 各自成立 ③ 两条断言**互斥**。
|
|
138
|
+
③ 是这套判据的关键 —— `>=24` 与 `>=44` 也能双双成立,那什么也证明不了;
|
|
139
|
+
互斥成立,才等于用机器证明了「这两栏不可能是同一个东西」。
|
|
140
|
+
指标 10 个:`count` / `h` / `w` / `lines` / `fs` / `uniq` / `glyphs` /
|
|
141
|
+
`hidden` / `gh` / `gw`(后两个量 hover 前后的尺寸变化)。
|
|
142
|
+
自检 10 条反向控制 + 1 条正例(含"少一栏也必须红"两个方向);
|
|
143
|
+
在真实仓库做过反向验证:把 nav 的修复撤回去,
|
|
144
|
+
门禁立刻报「`a.toc__link` 的 lines 量得 1,断言是 >=2」。
|
|
145
|
+
- 24 个对照块全部写上断言,**24 / 24** 成立。
|
|
146
|
+
- 两块的演示容器宽度钉死(`.cmp .ls-demo` 300px、`.cmp .ar-g` 360px):
|
|
147
|
+
它们演示的是「折行」「列被撑开」这类**由宽度决定**的现象,
|
|
148
|
+
不钉住的话 1440px 下 `list` 的反例根本不折行、`analysis-report` 的正例列宽
|
|
149
|
+
会随视口变宽。现在 393 / 734 / 900 / 1440 四个宽度下量出来的数字一致。
|
|
150
|
+
|
|
151
|
+
### 修
|
|
152
|
+
|
|
153
|
+
- **门禁的临时端口会撞上 Chrome 的禁用端口。** 上一版全量跑到 195/196,
|
|
154
|
+
唯一红的是 `token-resolved`:`net::ERR_UNSAFE_PORT at 127.0.0.1:3659`。
|
|
155
|
+
5 道门禁都用 `listen(0)` 让系统随便给端口,而浏览器有一张
|
|
156
|
+
`kRestrictedPorts` 表(3659 / 4045 / 6000 / 6665–6669 / 10080…)一律拒连 ——
|
|
157
|
+
端口随机 ⇒ 约 1/1000 概率"与你无关地红",而且这种红看着像环境抖动,
|
|
158
|
+
最容易被重跑一次糊过去。现在统一走 `05-audit/safe-listen.js`
|
|
159
|
+
的 `safeListen()`:撞到就重绑,并带 3 条自检(重试分支平时跑不到,
|
|
160
|
+
不主动验一次等于没写)。
|
|
161
|
+
- `demo-counterexample-gate.py` 的可见性判据原来对**整个标签**做
|
|
162
|
+
`\bhidden\b` 子串匹配。新加的 `data-bad=".rv-card|hidden|=1"`(断言指标名
|
|
163
|
+
就叫 hidden)直接把它触发,`reveal` 被误判成「反例块被藏起来了」——
|
|
164
|
+
判据认的是**词**,不是**属性**。改成按属性判,并补一条自检:
|
|
165
|
+
断言里带 hidden 字样必须放行。
|
|
166
|
+
- 门禁总数 194 → **197**(`demo-claim` + 自检 + `port-safe`)。
|
|
167
|
+
|
|
168
|
+
### 已知未覆盖(写在这里,不假装覆盖了)
|
|
169
|
+
|
|
170
|
+
- 10 页仍是 `box bad` 单栏反例,没有成对结构可量 —— 门禁会把条数打出来。
|
|
171
|
+
- 反例**该不该**算错、`cmp__why` 里的文案说得准不准,机器判不了;
|
|
172
|
+
这道门禁判的是「演示物确实在、且两栏真的不同」,不是审美判断。
|
|
173
|
+
- 真机读屏(NVDA / VoiceOver)仍未执行。
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
19
177
|
## [0.11.11] - 2026-10-29
|
|
20
178
|
|
|
21
179
|
**反例从"写着"走到"看得见"**。0.11.10 让 33 个组件都写清了「什么时候不该用」,
|
package/CONTRIBUTING.md
CHANGED
|
@@ -8,7 +8,7 @@ npm run gate # 全量:含浏览器类
|
|
|
8
8
|
npm run gate:fast # 只跑静态层,不用装浏览器
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
全量是 **
|
|
11
|
+
全量是 **197 道门禁**(本机约 15–20 分钟:组件层要起浏览器,还要逐张比 95 张视觉截图)。
|
|
12
12
|
只想确认没改坏静态契约的话用 `gate:fast`,几秒到几分钟。
|
|
13
13
|
|
|
14
14
|
环境依赖只有一套(Node + Chromium),缺了对应的门禁会**跳过**(跳过不算通过):
|
|
@@ -112,6 +112,27 @@ fix: <一句话说清改了什么、为什么>
|
|
|
112
112
|
而边界恰恰是组件库最该交付的东西。标题怎么写都行
|
|
113
113
|
(`## 反例` / `## 反模式` / `## 何时不该用` 都认),内容不能省。
|
|
114
114
|
**不要为了过门禁改文案**:判据认的是"这段话在讲不该怎么用",不是你用哪个标题。
|
|
115
|
+
7. **并排对照块(`.cmp`)两栏各写一条能被机器量的断言。**
|
|
116
|
+
写法:`data-bad="选择器|指标|断言"`,例 `data-bad="li.dd-bug|h|<=24"`;
|
|
117
|
+
正例栏同理写 `data-good`。指标与语义见 `05-audit/demo-claim-gate.js` 头部。
|
|
118
|
+
两栏的断言必须**互斥**(不存在一个数能同时满足两边)且各自成立。
|
|
119
|
+
这条规矩是踩出来的:曾有 3 个块"块在、标题对、反例不成立"——
|
|
120
|
+
`nav` 的反例长标题根本没换行(`.toc__link` 自带 `nowrap`,只是被裁掉),
|
|
121
|
+
`list` 同形,`dropdown` 的反例写了 `display:flex` 绕开了真实的 24px 坑。
|
|
122
|
+
只判"块在不在"时,这三处全是绿的。
|
|
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
|
+
—— 一句"量化不了"掩盖一处没做的活,比不写更糟。
|
|
115
136
|
|
|
116
137
|
## 提 PR 之前
|
|
117
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