agentcss 0.11.9 → 0.11.11
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 +71 -0
- package/02-primitives/badge/README.md +14 -0
- package/02-primitives/button/demo.html +5 -15
- package/02-primitives/card/README.md +1 -1
- package/02-primitives/card/demo.html +3 -13
- package/02-primitives/choice/demo.html +1 -0
- package/02-primitives/combobox/README.md +15 -0
- package/02-primitives/combobox/demo.html +38 -0
- package/02-primitives/date-range/README.md +11 -0
- package/02-primitives/date-range/demo.html +36 -0
- package/02-primitives/input/demo.html +5 -16
- package/02-primitives/popover/README.md +27 -1
- package/02-primitives/popover/demo.html +45 -0
- package/02-primitives/progress/README.md +4 -0
- package/02-primitives/progress/demo.html +25 -0
- package/02-primitives/select/README.md +12 -0
- package/02-primitives/select/demo.html +46 -0
- package/02-primitives/separator/README.md +3 -1
- package/02-primitives/skeleton/demo.html +28 -0
- package/02-primitives/switch/README.md +14 -0
- package/03-patterns/accordion/README.md +17 -0
- package/03-patterns/content/demo.html +20 -0
- package/03-patterns/drawer/README.md +12 -0
- package/03-patterns/drawer/demo.html +36 -0
- package/03-patterns/dropdown/README.md +15 -0
- package/03-patterns/dropdown/demo.html +39 -0
- package/03-patterns/list/demo.html +48 -0
- package/03-patterns/nav/demo.html +34 -1
- package/03-patterns/overlay/README.md +3 -3
- package/03-patterns/pagination/README.md +14 -0
- package/03-patterns/pagination/demo.html +40 -0
- package/03-patterns/reveal/README.md +15 -0
- package/03-patterns/reveal/demo.html +24 -0
- package/03-patterns/states/README.md +1 -1
- package/03-patterns/states/demo.html +25 -1
- package/03-patterns/tabs/README.md +15 -0
- package/03-patterns/tooltip/README.md +13 -0
- package/03-patterns/tooltip/demo.html +27 -0
- package/03-patterns/tree/README.md +16 -0
- package/03-patterns/tree/demo.html +43 -0
- package/04-recipes/analysis-report/README.md +16 -0
- package/04-recipes/analysis-report/demo.html +34 -0
- package/04-recipes/data-showcase/README.md +12 -0
- package/04-recipes/data-showcase/demo.html +29 -0
- package/04-recipes/detail-page/README.md +13 -0
- package/04-recipes/detail-page/demo.html +28 -0
- package/04-recipes/report/README.md +13 -0
- package/04-recipes/report/demo.html +24 -0
- package/04-recipes/table/README.md +38 -0
- package/04-recipes/table/demo.html +39 -0
- package/CHANGELOG.md +90 -0
- package/CONTRIBUTING.md +8 -1
- package/ai/components.json +38 -10
- package/dist/01-tokens/page.css +1 -1
- package/dist/COSTS.md +1 -1
- package/index.html +1 -1
- package/package.json +1 -1
package/01-tokens/page.css
CHANGED
|
@@ -216,6 +216,77 @@
|
|
|
216
216
|
background: var(--surface-sunken);
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
/* ============================================================================
|
|
220
|
+
6. 对错对照 `.cmp`(并排两栏:错写法 / 本库写法)
|
|
221
|
+
----------------------------------------------------------------------------
|
|
222
|
+
为什么要收编(量过才写的,不是凭印象):
|
|
223
|
+
3 个页面(button / card / input)各自写了一遍,**语义完全相同**,
|
|
224
|
+
但参数各是各的 —— `grid-template-columns` 都是 1.55fr 1fr(这一条一致,
|
|
225
|
+
说明它确实是个固定意图),而 gap 是 sp-4 还是 sp-5、断点是 640 还是 700,
|
|
226
|
+
三页三种。这不是"各页局部排布",是同一个东西的无意分叉。
|
|
227
|
+
判据与 `.row` 的区别就在这里:
|
|
228
|
+
`.row` 15 页 14 种写法 —— **语义**都不一样(有的是工具条、有的是按钮排),不收;
|
|
229
|
+
`.cmp` 3 页 3 种写法 —— **语义**一样,差的只是手抖,收。
|
|
230
|
+
另外 21 个页面缺"能看见的反例",补上之后若不收编,就是 24 份副本。
|
|
231
|
+
|
|
232
|
+
为什么列宽是 1fr 1fr(而不是原来那三页的 1.55fr 1fr):
|
|
233
|
+
那三页的左栏是"一大段解释 + 示例",右栏只是一句话,所以给左栏 1.55。
|
|
234
|
+
现在每一栏都是"一个小示例 + 一句理由",两栏等宽才不会让人以为
|
|
235
|
+
某一栏更重要。真需要不等宽时用 `.cmp--wide-first`,别在页面里重写栅格。
|
|
236
|
+
========================================================================= */
|
|
237
|
+
.page .cmp {
|
|
238
|
+
display: grid;
|
|
239
|
+
/* ⚠️ 必须是 `minmax(0, 1fr)`,不能写 `1fr`:`1fr` 的下限是 `auto`(= 内容最小宽度),
|
|
240
|
+
一栏里只要有不换行的长串(`.ls-clip` 的 nowrap、`.ar-long` 的股票代码、宽表格),
|
|
241
|
+
两栏就一起被顶宽 ⇒ **整页**横向溢出。
|
|
242
|
+
实测代价:本库 4 个页面在 320px / 834px / RTL 阿拉伯语膨胀后溢出 81–155px,
|
|
243
|
+
`zoom-reflow` `rtl-render` `responsive` 三道门禁同时红。
|
|
244
|
+
——这条正是 `04-recipes/analysis-report` 那一页在教别人的东西,
|
|
245
|
+
我写这个收编的时候照着旧三页抄了 `1fr`,把要讲的坑又踩了一遍。 */
|
|
246
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
247
|
+
gap: var(--sp-4);
|
|
248
|
+
/* 不写 align-items: start —— 两栏等高(stretch)边框才对得齐;
|
|
249
|
+
start 会让短的那栏提前结束,看起来像"没写完"。 */
|
|
250
|
+
}
|
|
251
|
+
.page .cmp--wide-first {
|
|
252
|
+
grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
|
|
253
|
+
}
|
|
254
|
+
/* 断点 640 而不是 720:文档页里的活标本 iframe 实测只有 734px 宽
|
|
255
|
+
(guide/xxx.html 里那个框),720 的断点意味着"再窄一点就叠成单栏"——
|
|
256
|
+
而叠成单栏,"并排"这件事就不存在了。留出 90px 余量。 */
|
|
257
|
+
@media (max-width: 640px) {
|
|
258
|
+
.page .cmp,
|
|
259
|
+
.page .cmp--wide-first,
|
|
260
|
+
.page .cmp--clip { grid-template-columns: minmax(0, 1fr); }
|
|
261
|
+
}
|
|
262
|
+
.page .cmp__pane {
|
|
263
|
+
padding: var(--sp-4);
|
|
264
|
+
border: 1px solid var(--border-decor);
|
|
265
|
+
border-radius: var(--r-md);
|
|
266
|
+
background: var(--surface-sunken);
|
|
267
|
+
/* 栅格项默认 min-width:auto ⇒ 内容最小宽度照样能把栏顶开。 */
|
|
268
|
+
min-width: 0;
|
|
269
|
+
}
|
|
270
|
+
/* 内容本身就是"宽、且故意不收缩"的页(超长不换行串 / 宽表格)用这个修饰符。
|
|
271
|
+
⚠️ 为什么不写进 `.cmp__pane` 全局:`popover` / `tooltip` / `dropdown` 的对照块里
|
|
272
|
+
气泡是绝对定位伸出栏外的,`overflow: hidden` 会把气泡直接裁掉 ——
|
|
273
|
+
那几页的演示就没了。裁的是"演示内容越出栏框",不是"栏内的布局算错":
|
|
274
|
+
栏内列的宽度仍按内容算,读者看到的还是被撑宽的那一栏。 */
|
|
275
|
+
.page .cmp--clip .cmp__pane { overflow: hidden; }
|
|
276
|
+
.page .cmp__pane > h3 {
|
|
277
|
+
margin: 0 0 var(--sp-3);
|
|
278
|
+
font-size: var(--fs-sm);
|
|
279
|
+
font-weight: 500;
|
|
280
|
+
}
|
|
281
|
+
.page .cmp__bad > h3 { color: var(--danger); }
|
|
282
|
+
.page .cmp__good > h3 { color: var(--success); }
|
|
283
|
+
.page .cmp__why {
|
|
284
|
+
margin: var(--sp-3) 0 0;
|
|
285
|
+
font-size: var(--fs-xs);
|
|
286
|
+
line-height: 1.6;
|
|
287
|
+
color: var(--text-secondary);
|
|
288
|
+
}
|
|
289
|
+
|
|
219
290
|
/* ============================================================================
|
|
220
291
|
反模式(本文件自己的边界)
|
|
221
292
|
----------------------------------------------------------------------------
|
|
@@ -65,6 +65,20 @@
|
|
|
65
65
|
|
|
66
66
|
高度 24px(默认)· 字号 12px —— 达 WCAG 2.2 的 24×24 最小目标。
|
|
67
67
|
|
|
68
|
+
|
|
69
|
+
## 反例
|
|
70
|
+
|
|
71
|
+
- ❌ **用 `badge--danger` 当按钮** —— badge 不可点;要可点必须换成 `<button>` / `<a>` 并加 `badge--clickable`,
|
|
72
|
+
`div` + `onClick` 对读屏是"没有角色的按钮"(`badge.css` 里写明了这一条)
|
|
73
|
+
- ❌ **只用颜色区分状态、不加文字** —— WCAG 1.4.1 要求形状或文字参与区分;
|
|
74
|
+
纯色块在灰度打印和色觉障碍下不传递任何信息。所以本库是「淡底 + 深字」而不是「饱和底 + 白字」
|
|
75
|
+
- ❌ **用 badge 装数字角标** —— 那是另一类设计:数字一变化读屏未必会念,用户不知道"3 变成 4"是发生了什么事
|
|
76
|
+
- ❌ **给不可点的 badge 写 hover 过渡** —— 光标和过渡都在暗示"我能点",用户去点,什么也不会发生
|
|
77
|
+
- ❌ **列表里每一行都挂 `badge--solid-warning`** —— 饱和底在长列表里会盖过正文;`solid` 那三档只留给"这一行必须被看见"的少数情况
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
|
|
68
82
|
## 验证
|
|
69
83
|
|
|
70
84
|
- `component-contrast.py`:5 组字/底组合两套配色全过
|
|
@@ -16,16 +16,6 @@
|
|
|
16
16
|
.cell p { margin: 0 0 var(--sp-4); font-size: var(--fs-xs);
|
|
17
17
|
color: var(--text-tertiary); line-height: 1.55; }
|
|
18
18
|
.row { display: flex; gap: var(--sp-3); flex-wrap: wrap; align-items: center; }
|
|
19
|
-
.cmp { display: grid; gap: var(--sp-4); grid-template-columns: 1.55fr 1fr; }
|
|
20
|
-
@media (max-width: 640px) {.cmp { grid-template-columns: 1fr; }
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
.cmp>div { padding: var(--sp-4); border-radius: var(--r-md); border: 1px solid var(--border-decor); }
|
|
24
|
-
.bad { background: var(--surface-sunken); }
|
|
25
|
-
.good { background: var(--surface-sunken); }
|
|
26
|
-
.cmp h3 { margin: 0 0 var(--sp-3); font-size: var(--fs-sm); font-weight: 500; }
|
|
27
|
-
.bad h3 { color: var(--danger); }
|
|
28
|
-
.good h3 { color: var(--success); }
|
|
29
19
|
/* 反例演示用 */
|
|
30
20
|
.btn--naive { height: 44px; padding: 0 var(--sp-5); border: none; border-radius: var(--r-sm);
|
|
31
21
|
background: var(--accent); color: #fff; cursor: pointer; font-size: var(--fs-sm);
|
|
@@ -129,27 +119,27 @@
|
|
|
129
119
|
</p>
|
|
130
120
|
|
|
131
121
|
<h2 class="rule-bottom">对比:常见的错写法 vs 本库写法</h2>
|
|
132
|
-
<div class="cmp">
|
|
133
|
-
<div class="
|
|
122
|
+
<div class="cmp cmp--wide-first">
|
|
123
|
+
<div class="cmp__pane cmp__bad">
|
|
134
124
|
<h3>常见写法(某项目 现状)</h3>
|
|
135
125
|
<div class="row">
|
|
136
126
|
<button class="btn--naive">hover 看我</button>
|
|
137
127
|
<button class="btn--naive" disabled>disabled 看我</button>
|
|
138
128
|
</div>
|
|
139
|
-
<p
|
|
129
|
+
<p class="cmp__why">
|
|
140
130
|
<code>filter: brightness(1.08)</code> —— hover 色是算出来的,无法验证对比度,不同底色表现不一致。<br>
|
|
141
131
|
<code>transform: scale(1.05)</code> —— 按钮"长大",廉价感。<br>
|
|
142
132
|
<code>opacity: 0.5</code> —— 对比度变成随机数。<br>
|
|
143
133
|
<code>transition: all</code> —— 所有属性一起动画,性能和观感失控。
|
|
144
134
|
</p>
|
|
145
135
|
</div>
|
|
146
|
-
<div class="
|
|
136
|
+
<div class="cmp__pane cmp__good">
|
|
147
137
|
<h3>本库写法</h3>
|
|
148
138
|
<div class="row">
|
|
149
139
|
<button class="btn btn--md btn--primary">hover 看我</button>
|
|
150
140
|
<button class="btn btn--md btn--primary" disabled>disabled 看我</button>
|
|
151
141
|
</div>
|
|
152
|
-
<p
|
|
142
|
+
<p class="cmp__why">
|
|
153
143
|
明确的 <code>--accent-hover</code> 色值,实测 7.73 AAA。<br>
|
|
154
144
|
active 只下沉 1px。<br>
|
|
155
145
|
disabled 用明确的低对比色组,仍可读。<br>
|
|
@@ -125,7 +125,7 @@ hover 只改 **`transform`** 和 **`box-shadow`**,这两个属性不触发回
|
|
|
125
125
|
## 反例 —— 什么时候不该用
|
|
126
126
|
|
|
127
127
|
- ❌ **一排卡片全用 `--elevated`** —— 阴影互相打架,整页发灰。列表用 `--flat`
|
|
128
|
-
- ❌ **hover 抬起 8px 以上** ——
|
|
128
|
+
- ❌ **hover 抬起 8px 以上** —— 鼠标扫过列表时整页都在晃,注意力从内容跑到动画上;悬停只需要"轻微浮起"这一点点反馈
|
|
129
129
|
- ❌ **整卡套 `div onclick`** —— 键盘不可达,见上
|
|
130
130
|
- ❌ **给链接本身再画焦点环** —— 会变成两个框
|
|
131
131
|
- ❌ **用 JS 截断描述文字** —— 会留下半个词或半个字,用 `-webkit-line-clamp`
|
|
@@ -12,16 +12,6 @@
|
|
|
12
12
|
<style>
|
|
13
13
|
p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6; }
|
|
14
14
|
.grid3 { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
|
|
15
|
-
.cmp { display: grid; gap: var(--sp-5); grid-template-columns: 1.55fr 1fr; }
|
|
16
|
-
@media (max-width: 700px) {.cmp { grid-template-columns: 1fr; }
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
.cmp>div { padding: var(--sp-5); border-radius: var(--r-md); border: 1px solid var(--border-decor); }
|
|
20
|
-
.bad { background: var(--surface-sunken); }
|
|
21
|
-
.good { background: var(--surface-sunken); }
|
|
22
|
-
.cmp h3 { margin: 0 0 var(--sp-2); font-size: var(--fs-sm); font-weight: 500; }
|
|
23
|
-
.bad h3 { color: var(--danger); }
|
|
24
|
-
.good h3 { color: var(--success); }
|
|
25
15
|
.cmp .stack { display: flex; flex-direction: column; gap: var(--sp-3); }
|
|
26
16
|
/* ---- 反例卡片:hover 时改 border-width,引起回流 ---- */
|
|
27
17
|
.card-bad { position: relative; background: var(--surface);
|
|
@@ -52,8 +42,8 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
|
|
|
52
42
|
下面<strong>故意放大到 4px</strong>,好让你一次看清楚。请分别把鼠标移到两张卡上。
|
|
53
43
|
</div>
|
|
54
44
|
|
|
55
|
-
<div class="cmp">
|
|
56
|
-
<div class="
|
|
45
|
+
<div class="cmp cmp--wide-first">
|
|
46
|
+
<div class="cmp__pane cmp__bad">
|
|
57
47
|
<h3>反例:hover 改 border-width</h3>
|
|
58
48
|
<p class="small" style="margin:0 0 var(--sp-3)">
|
|
59
49
|
移上来 —— 里面那把尺子和文字整体往下挪了,卡片也变高了。
|
|
@@ -71,7 +61,7 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
|
|
|
71
61
|
</div>
|
|
72
62
|
</div>
|
|
73
63
|
|
|
74
|
-
<div class="
|
|
64
|
+
<div class="cmp__pane cmp__good">
|
|
75
65
|
<h3>本库:transform + box-shadow</h3>
|
|
76
66
|
<p class="small" style="margin:0 0 var(--sp-3)">
|
|
77
67
|
移上来 —— 卡片整体抬起 2px,<strong>里面的内容一个像素都不动</strong>,
|
|
@@ -188,6 +188,7 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
|
|
|
188
188
|
|
|
189
189
|
<h2 class="rule-bottom">反例:用 display:none 隐藏原生控件</h2>
|
|
190
190
|
<div class="box bad">
|
|
191
|
+
<h3>反例:把原生勾选框 <code>display:none</code> 藏掉</h3>
|
|
191
192
|
<div class="choice choice--checkbox">
|
|
192
193
|
<input class="choice__input" type="checkbox" style="display:none" id="bad1">
|
|
193
194
|
<label class="choice__label" for="bad1">
|
|
@@ -73,6 +73,21 @@ if (!list.id) list.id = 'combo-list-' + seq;
|
|
|
73
73
|
|---|---|
|
|
74
74
|
| `dismissable:manual` | 点外部关闭是手写的,**不是统一机制** |
|
|
75
75
|
|
|
76
|
+
## 反例
|
|
77
|
+
|
|
78
|
+
- ❌ **标签只能点小叉删除、不支持 Backspace** —— 键盘用户删不掉已选项,
|
|
79
|
+
这是标签输入的事实标准交互(`combobox.js` 里 Backspace 删最后一个是刻意实现的)
|
|
80
|
+
- ❌ **过滤结果不高亮命中片段** —— 用户看到的是"顺序乱了",而不是"因为匹配才留下";
|
|
81
|
+
`combobox.js` 高亮命中片段就是为了回答"为什么这条匹配"
|
|
82
|
+
- ❌ **把焦点环画在 `combo__input` 上** —— 环画在 `combo__field:focus-within` 上;
|
|
83
|
+
两处都画会出现双层框
|
|
84
|
+
- ❌ **`combo__tag-del` 只做视觉上的小叉、不补命中区** —— 命中区要 44px;手指点不中,等于删不掉
|
|
85
|
+
- ❌ **禁用时只让输入框变灰、`combo__tag-del` 还能点** —— 看着能点,点下去要么没反应要么出事;
|
|
86
|
+
禁用态要把删除按钮一起变灰
|
|
87
|
+
- ❌ **在自定义匹配函数里做异步过滤** —— 它会被逐项同步调用,返回 Promise 等同于"全部不匹配"
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
76
91
|
## 门禁
|
|
77
92
|
|
|
78
93
|
- `05-audit/combobox-check.js`
|
|
@@ -19,6 +19,15 @@ label.f { display: block; margin-bottom: var(--sp-1); font-size: var(--fs-sm); }
|
|
|
19
19
|
font-family: var(--mono); font-size: var(--fs-xs);
|
|
20
20
|
color: var(--text-secondary); min-height: 2.8em;
|
|
21
21
|
}
|
|
22
|
+
/* 一次性演示用:把选项摆成静态列表(组件里它是浮层,这里要它躺着) */
|
|
23
|
+
.cb-demo { display: grid; gap: var(--sp-1); }
|
|
24
|
+
.cb-demo__q { font-size: var(--fs-sm); color: var(--text-tertiary); padding: 0 0 var(--sp-1); }
|
|
25
|
+
.cb-demo__list {
|
|
26
|
+
list-style: none; margin: 0; padding: var(--sp-1);
|
|
27
|
+
background: var(--surface-raised);
|
|
28
|
+
border: 1px solid var(--border-decor);
|
|
29
|
+
border-radius: var(--r-sm);
|
|
30
|
+
}
|
|
22
31
|
</style>
|
|
23
32
|
</head>
|
|
24
33
|
<body class="page page">
|
|
@@ -66,6 +75,35 @@ label.f { display: block; margin-bottom: var(--sp-1); font-size: var(--fs-sm); }
|
|
|
66
75
|
</div>
|
|
67
76
|
</div>
|
|
68
77
|
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
81
|
+
<div class="cmp">
|
|
82
|
+
<div class="cmp__pane cmp__bad">
|
|
83
|
+
<h3>❌ 常见写法:结果不高亮</h3>
|
|
84
|
+
<div class="cb-demo" aria-hidden="true">
|
|
85
|
+
<div class="cb-demo__q">半导体</div>
|
|
86
|
+
<ul class="cb-demo__list">
|
|
87
|
+
<li class="combo__opt">设备</li>
|
|
88
|
+
<li class="combo__opt">材料</li>
|
|
89
|
+
<li class="combo__opt">封测</li>
|
|
90
|
+
</ul>
|
|
91
|
+
</div>
|
|
92
|
+
<p class="cmp__why">输入"半导体"之后三条都留下了,但三条长得一模一样 —— 用户说不出<b>凭什么</b>是这三条,只会觉得"顺序乱了"。</p>
|
|
93
|
+
</div>
|
|
94
|
+
<div class="cmp__pane cmp__good">
|
|
95
|
+
<h3>✅ 本库:命中片段高亮</h3>
|
|
96
|
+
<div class="cb-demo" aria-hidden="true">
|
|
97
|
+
<div class="cb-demo__q">半导体</div>
|
|
98
|
+
<ul class="cb-demo__list">
|
|
99
|
+
<li class="combo__opt"><mark>半导体</mark>设备</li>
|
|
100
|
+
<li class="combo__opt"><mark>半导体</mark>材料</li>
|
|
101
|
+
<li class="combo__opt"><mark>半导体</mark>封测</li>
|
|
102
|
+
</ul>
|
|
103
|
+
</div>
|
|
104
|
+
<p class="cmp__why">高亮用的是本库的 <code>combo__opt mark</code>:accent 色 + 下划线,<b>不用</b>浏览器默认的黄底 —— 黄底与文字的对比度不可控,深色主题下更糟。</p>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
69
107
|
</main>
|
|
70
108
|
|
|
71
109
|
<script src="combobox.js"></script>
|
|
@@ -95,6 +95,17 @@ if (msg) {
|
|
|
95
95
|
契约里 `behaviorDebt` 为空 —— 清除、校验、preset 接线都有门禁覆盖
|
|
96
96
|
(`05-audit/date-range-check.js`)。
|
|
97
97
|
|
|
98
|
+
## 反例
|
|
99
|
+
|
|
100
|
+
- ❌ **不给 `drange__clear`** —— 日期填错要能一键清空;没有它,用户得逐格删六个数字
|
|
101
|
+
- ❌ **错误态只改颜色、不加图标** —— WCAG 1.4.1;本库的 `drange__error` 是「边框 + 图标」,颜色只是辅助
|
|
102
|
+
- ❌ **禁用态用 `opacity`** —— 透明度会把文字对比度一起拉低,禁用不等于允许不可读(`date-range.css` 里改用中性色)
|
|
103
|
+
- ❌ **自建日历替代 `<input type="date">`** —— 移动端原生选择器是系统级的:读屏、键盘、本地化都免费;
|
|
104
|
+
自建的那一套要全部重做,而且一定做得更差
|
|
105
|
+
- ❌ **三个预设按钮只做鼠标点击** —— `drange__preset` 必须能 Tab 到,快捷选择对键盘用户才是"快捷"
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
98
109
|
## 门禁
|
|
99
110
|
|
|
100
111
|
`05-audit/date-range-check.js`
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
font-family: var(--mono); font-size: var(--fs-xs);
|
|
19
19
|
color: var(--text-secondary); min-height: 2.8em;
|
|
20
20
|
}
|
|
21
|
+
/* 反例专用:只改颜色 */
|
|
22
|
+
.dr-bad-field .drange__input { border-color: var(--danger); }
|
|
21
23
|
</style>
|
|
22
24
|
</head>
|
|
23
25
|
<body class="page page">
|
|
@@ -74,6 +76,40 @@
|
|
|
74
76
|
<div class="log" id="log2">(试着填一个 2019 年或 2027 年 ⇒ 会报错)</div>
|
|
75
77
|
</div>
|
|
76
78
|
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
82
|
+
<div class="cmp">
|
|
83
|
+
<div class="cmp__pane cmp__bad">
|
|
84
|
+
<h3>❌ 常见写法:错误只改边框颜色</h3>
|
|
85
|
+
<div class="drange" aria-hidden="true">
|
|
86
|
+
<div class="drange__field">
|
|
87
|
+
<label class="drange__label" for="dr-b1">开始</label>
|
|
88
|
+
<input id="dr-b1" class="drange__input" type="date" value="2026-01-10" readonly tabindex="-1">
|
|
89
|
+
</div>
|
|
90
|
+
<div class="drange__field dr-bad-field">
|
|
91
|
+
<label class="drange__label" for="dr-b2">结束</label>
|
|
92
|
+
<input id="dr-b2" class="drange__input" type="date" value="2026-01-03" readonly tabindex="-1">
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
<p class="cmp__why">结束日期早于开始日期,界面上只是"红了一圈":色觉障碍用户看不出来,灰度打印也看不出来,而且<b>没有一句话</b>告诉你错在哪。</p>
|
|
96
|
+
</div>
|
|
97
|
+
<div class="cmp__pane cmp__good">
|
|
98
|
+
<h3>✅ 本库:左侧竖线 + 一句话说明</h3>
|
|
99
|
+
<div class="drange" aria-hidden="true">
|
|
100
|
+
<div class="drange__field">
|
|
101
|
+
<label class="drange__label" for="dr-g1">开始</label>
|
|
102
|
+
<input id="dr-g1" class="drange__input" type="date" value="2026-01-10" readonly tabindex="-1">
|
|
103
|
+
</div>
|
|
104
|
+
<div class="drange__field">
|
|
105
|
+
<label class="drange__label" for="dr-g2">结束</label>
|
|
106
|
+
<input id="dr-g2" class="drange__input" type="date" value="2026-01-03" readonly tabindex="-1">
|
|
107
|
+
</div>
|
|
108
|
+
<div class="drange__error">结束日期早于开始日期</div>
|
|
109
|
+
</div>
|
|
110
|
+
<p class="cmp__why">形状(左侧竖线)与文字一起承担信息,颜色只是辅助 —— 这是 WCAG 1.4.1 的要求。同时 JS 会给两个字段打 <code>aria-invalid</code> 并用 <code>aria-describedby</code> 指到这句话。</p>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
77
113
|
</main>
|
|
78
114
|
|
|
79
115
|
<script src="date-range.js"></script>
|
|
@@ -22,17 +22,6 @@
|
|
|
22
22
|
.cell h3 { margin: 0 0 var(--sp-1); font-size: var(--fs-sm); font-weight: 500; }
|
|
23
23
|
.cell p { margin: 0 0 var(--sp-3); font-size: var(--fs-xs);
|
|
24
24
|
color: var(--text-tertiary); line-height: 1.55; }
|
|
25
|
-
.cmp { display: grid; gap: var(--sp-4); grid-template-columns: 1.55fr 1fr; }
|
|
26
|
-
@media (max-width: 620px) {.cmp { grid-template-columns: 1fr; }
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
.cmp>div { padding: var(--sp-4); border-radius: var(--r-md); border: 1px solid var(--border-decor); }
|
|
30
|
-
.bad { background: var(--surface-sunken); }
|
|
31
|
-
.good { background: var(--surface-sunken); }
|
|
32
|
-
.cmp h3 { margin: 0 0 var(--sp-3); font-size: var(--fs-sm); font-weight: 500; }
|
|
33
|
-
.bad h3 { color: var(--danger); }
|
|
34
|
-
.good h3 { color: var(--success); }
|
|
35
|
-
.cmp p { font-size: var(--fs-xs); margin: var(--sp-3) 0 0; line-height: 1.6; }
|
|
36
25
|
/* 反例演示 */
|
|
37
26
|
.bad-input { width: 100%; box-sizing: border-box; height: 44px; padding: 0 var(--sp-3);
|
|
38
27
|
font-size: 14px; border: 1px solid #ccc; border-radius: var(--r-sm); }
|
|
@@ -118,8 +107,8 @@
|
|
|
118
107
|
</div>
|
|
119
108
|
|
|
120
109
|
<h2 class="rule-bottom">对比:常见写法 vs 本库写法</h2>
|
|
121
|
-
<div class="cmp">
|
|
122
|
-
<div class="
|
|
110
|
+
<div class="cmp cmp--wide-first">
|
|
111
|
+
<div class="cmp__pane cmp__bad">
|
|
123
112
|
<h3>常见写法</h3>
|
|
124
113
|
<input class="bad-input err" value="abc" aria-hidden="true" tabindex="-1">
|
|
125
114
|
<!-- 🔴 加 aria-hidden。
|
|
@@ -129,21 +118,21 @@
|
|
|
129
118
|
反例对人要可见(不能 display:none),
|
|
130
119
|
但对辅助技术应当隐藏 —— aria-hidden 正合适。 -->
|
|
131
120
|
<p class="bad-err" aria-hidden="true">格式错误</p>
|
|
132
|
-
<p>
|
|
121
|
+
<p class="cmp__why">
|
|
133
122
|
<code>placeholder 当 label</code> —— 一输入就消失<br>
|
|
134
123
|
<code>只把边框变红</code> —— WCAG 1.4.1 禁止只靠颜色传达信息<br>
|
|
135
124
|
<code>font-size: 14px</code> —— iOS Safari 会自动放大整个页面<br>
|
|
136
125
|
错误文字没有 <code>aria-describedby</code> —— 屏幕阅读器读不到
|
|
137
126
|
</p>
|
|
138
127
|
</div>
|
|
139
|
-
<div class="
|
|
128
|
+
<div class="cmp__pane cmp__good">
|
|
140
129
|
<h3>本库写法</h3>
|
|
141
130
|
<div class="field">
|
|
142
131
|
<label class="field__label" for="g1">金额</label>
|
|
143
132
|
<input class="field__input" id="g1" value="abc" aria-invalid="true" aria-describedby="g1-err" inputmode="decimal" name="amount-g1" autocomplete="off"/>
|
|
144
133
|
<span class="field__error" id="g1-err" role="alert">请输入有效金额</span>
|
|
145
134
|
</div>
|
|
146
|
-
<p>
|
|
135
|
+
<p class="cmp__why">
|
|
147
136
|
有可见 label,不会消失<br>
|
|
148
137
|
红边框 + 图标 + 文字,三条线索<br>
|
|
149
138
|
<code>font-size: 16px</code> —— iOS 不缩放<br>
|
|
@@ -77,4 +77,30 @@ Popover.current; // 当前开着的实例(没有则null)
|
|
|
77
77
|
## 动画
|
|
78
78
|
|
|
79
79
|
尊重 `prefers-reduced-motion` —— 用户在系统里关了动效,
|
|
80
|
-
组件的过渡也要跟着关。
|
|
80
|
+
组件的过渡也要跟着关。
|
|
81
|
+
|
|
82
|
+
## 反例
|
|
83
|
+
|
|
84
|
+
- ❌ **给 popover 加 focus trap** —— 不做 trap 是刻意的:它没有"确认/取消"来放行,
|
|
85
|
+
trap 住了人出不去。焦点**进入**浮层(键盘用户要能直接操作)就够了
|
|
86
|
+
- ❌ **关闭态只用 `pointer-events: none`** —— Tab 仍能走进去,焦点会跑到看不见的地方;
|
|
87
|
+
必须是 `inert`(`popover.js` 的 `open()` 里写明了这一条)
|
|
88
|
+
- ❌ **同时开两个 popover** —— 两个叠着,用户不知道该关哪个,也不知道点空白处关的是哪一个
|
|
89
|
+
- ❌ **把箭头塞进带 `overflow: hidden` 的容器** —— 箭头会被裁掉;所以本组件不用 overflow 方式隐藏
|
|
90
|
+
|
|
91
|
+
## 已知局限:不做落点检测
|
|
92
|
+
|
|
93
|
+
浮层靠 CSS 定位(`--bottom` / `--top` / `--start` / `--end` 四个方向类),
|
|
94
|
+
**不测量、不在运行时判断空间够不够**。这是刻意的取舍:
|
|
95
|
+
|
|
96
|
+
- 好处:没有布局抖动、没有"打开瞬间先闪一下再翻位"、不依赖 `getBoundingClientRect`
|
|
97
|
+
- 代价:锚点右边真的没有空间时,`--end` / `--bottom` 仍会朝那个方向弹出去
|
|
98
|
+
|
|
99
|
+
所以**调用方要给浮层留位置**:靠边的按钮用 `--start` 而不是 `--end`;
|
|
100
|
+
演示页里那一栏就是给气泡留了 `max-width` 的口子,而不是让气泡自己算。
|
|
101
|
+
|
|
102
|
+
- ❌ **指望组件"自动翻边"** —— 它不会。库里没有落点检测,也不会加:
|
|
103
|
+
一旦按测量定位,滚动、旋转屏幕、软键盘弹起都要重新算,代价比收益大
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
---
|
|
@@ -31,6 +31,17 @@
|
|
|
31
31
|
table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
|
|
32
32
|
td, th { text-align: left; padding: var(--sp-2); border-bottom: 1px solid var(--border-decor); }
|
|
33
33
|
th { font-weight: 600; }
|
|
34
|
+
/* 一次性演示用:给浮层一个定位舞台(组件里它挂在触发元素的锚点上) */
|
|
35
|
+
.pp-stage { position: relative; min-height: 210px; --popover-min-w: 0; }
|
|
36
|
+
/* 气泡不设最小宽度:320px 下栏内宽只有约 256px,`min-width:200px` + 起点 120px
|
|
37
|
+
会直接顶出视口(实测溢出 33px)。宽度改由 max-width 向栏内收。 */
|
|
38
|
+
/* 🔴 反例块里的气泡是"静态摆拍",**不接行为**,所以用 data-demo-open,
|
|
39
|
+
不能用 data-open:`data-open[='true']` 是组件真实的"开着"状态,
|
|
40
|
+
`Popover.closeAll()` 和契约门禁都按它判断 —— 演示块一旦写了它,
|
|
41
|
+
门禁里「Esc 关闭后必须没有开着的浮层」就永远为假(实测两个条目连红)。 */
|
|
42
|
+
.pp-stage .popover[data-demo-open] { opacity: 1; visibility: visible; }
|
|
43
|
+
.pp-stage .popover__desc { margin: var(--sp-1) 0 0; font-size: var(--fs-xs); color: var(--text-secondary); }
|
|
44
|
+
.pp-stage .popover__title { margin: 0; font-size: var(--fs-sm); font-weight: 500; }
|
|
34
45
|
</style>
|
|
35
46
|
</head>
|
|
36
47
|
<body class="page page">
|
|
@@ -145,6 +156,40 @@ demo('bad1', 'bottom',
|
|
|
145
156
|
'<button class="btn btn--sm btn--danger">删除</button>' +
|
|
146
157
|
'</div>', '反面示例');
|
|
147
158
|
</script>
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
162
|
+
<div class="cmp">
|
|
163
|
+
<div class="cmp__pane cmp__bad">
|
|
164
|
+
<h3>❌ 常见写法:两个浮层同时开着</h3>
|
|
165
|
+
<div class="pp-stage" aria-hidden="true">
|
|
166
|
+
<button class="btn btn--sm btn--ghost" type="button" tabindex="-1">筛选</button>
|
|
167
|
+
<div class="popover popover--bottom" data-demo-open="true"
|
|
168
|
+
style="top:36px; inset-inline-start:8px; max-width:calc(100% - 16px); transform:none">
|
|
169
|
+
<p class="popover__title">筛选条件</p>
|
|
170
|
+
<p class="popover__desc">按板块过滤,可多选。</p>
|
|
171
|
+
</div>
|
|
172
|
+
<div class="popover popover--bottom" data-demo-open="true"
|
|
173
|
+
style="top:84px; inset-inline-start:38%; max-width:52%; transform:none">
|
|
174
|
+
<p class="popover__title">排序方式</p>
|
|
175
|
+
<p class="popover__desc">按涨跌幅。</p>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
<p class="cmp__why">两个都开着:点空白处到底关的是哪一个?读屏用户按 Esc 也不知道关掉的是哪个,而两个浮层的内容还叠在一起。</p>
|
|
179
|
+
</div>
|
|
180
|
+
<div class="cmp__pane cmp__good">
|
|
181
|
+
<h3>✅ 本库:同一时刻只开一个</h3>
|
|
182
|
+
<div class="pp-stage" aria-hidden="true">
|
|
183
|
+
<button class="btn btn--sm btn--ghost" type="button" tabindex="-1">筛选</button>
|
|
184
|
+
<div class="popover popover--bottom" data-demo-open="true"
|
|
185
|
+
style="top:36px; inset-inline-start:8px; max-width:calc(100% - 16px); transform:none">
|
|
186
|
+
<p class="popover__title">筛选条件</p>
|
|
187
|
+
<p class="popover__desc">按板块过滤,可多选。</p>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
<p class="cmp__why"><code>popover.js</code> 的 <code>open()</code> 会先关掉当前打开的那个 —— 不是"约定",是写死在实现里的。</p>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
148
193
|
</main>
|
|
149
194
|
</body>
|
|
150
195
|
</html>
|
|
@@ -75,6 +75,10 @@ role="progressbar" aria-busy="true" aria-label="正在处理"
|
|
|
75
75
|
- ❌ **代替"还剩多久"** ⇒ 百分比给的是"比例",不是"时间"
|
|
76
76
|
- ❌ **环形用在极小尺寸**(< 32px)⇒ 环太细,读不出进度
|
|
77
77
|
- ❌ **在一个页面里同时出现 3 个以上进度条** ⇒ 用户不知道该看哪个
|
|
78
|
+
- ❌ **用 `display: none` 隐藏进度条** ⇒ 会留下 `::after` 参与合成的空盒,位置还占着;改宽度或改状态,别改可见性
|
|
79
|
+
- ❌ **进度到 100% 就自动变绿** ⇒ 颜色的语义是"结果"不是"数值",100% 也可能是失败;`progress--success` 按业务给
|
|
80
|
+
- ❌ **把超过 100 的值喂给 `progress-ring`** ⇒ 弧长按百分比算,超过就是绕圈或错位
|
|
81
|
+
- ❌ **用 `progress-steps` 表达连续百分比** ⇒ 它按离散阶段设计(`progress-steps__seg--done` / `--current`),用户会以为"每段就是 25%"
|
|
78
82
|
|
|
79
83
|
## 相关
|
|
80
84
|
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
font-variant-numeric: tabular-nums; min-width: 3.2em; text-align: right; }
|
|
24
24
|
.bad-demo { background: var(--danger-bg); border-color: var(--danger); }
|
|
25
25
|
.bad { color: var(--danger); }
|
|
26
|
+
.pr-label { margin: 0 0 var(--sp-2); font-size: var(--fs-sm); }
|
|
27
|
+
.pr-note { margin: var(--sp-2) 0 0; font-size: var(--fs-xs); color: var(--text-secondary); }
|
|
26
28
|
</style>
|
|
27
29
|
</head>
|
|
28
30
|
<body class="page page">
|
|
@@ -149,6 +151,29 @@ document.querySelectorAll('.progress, .progress-ring, .progress-steps').forEach(
|
|
|
149
151
|
}
|
|
150
152
|
});
|
|
151
153
|
</script>
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
157
|
+
<div class="cmp">
|
|
158
|
+
<div class="cmp__pane cmp__bad">
|
|
159
|
+
<h3>❌ 常见写法:到 100% 就变绿</h3>
|
|
160
|
+
<p class="pr-label">导出 6 个文件 · 100%</p>
|
|
161
|
+
<span class="progress progress--success" role="progressbar" aria-valuenow="100"
|
|
162
|
+
aria-valuemin="0" aria-valuemax="100" aria-label="导出进度"
|
|
163
|
+
style="--progress:100%"><span class="progress__bar"></span></span>
|
|
164
|
+
<p class="pr-note">界面写"导出完成",条是绿的。</p>
|
|
165
|
+
<p class="cmp__why">100% 说的是"走到头了",不是"成功了"。这一批实际有 2 个文件失败,绿条仍然亮着 —— 用户以为可以走了。</p>
|
|
166
|
+
</div>
|
|
167
|
+
<div class="cmp__pane cmp__good">
|
|
168
|
+
<h3>✅ 本库:颜色讲结果,数值只讲进度</h3>
|
|
169
|
+
<p class="pr-label">导出 6 个文件 · 100%</p>
|
|
170
|
+
<span class="progress progress--danger" role="progressbar" aria-valuenow="100"
|
|
171
|
+
aria-valuemin="0" aria-valuemax="100" aria-label="导出失败"
|
|
172
|
+
style="--progress:100%"><span class="progress__bar"></span></span>
|
|
173
|
+
<p class="pr-note">同样 100%,红了:2 个文件失败。</p>
|
|
174
|
+
<p class="cmp__why"><code>progress--success</code> / <code>--danger</code> 由调用方按<b>业务结果</b>给,不按数值自动切换 —— 颜色是语义,不是刻度。</p>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
152
177
|
</main>
|
|
153
178
|
</body>
|
|
154
179
|
</html>
|
|
@@ -90,6 +90,18 @@ const sel = Select.create(元素, { multiple: false, value: 'b', label: '选项'
|
|
|
90
90
|
|
|
91
91
|
接统一行为层前,别当`stable` 用。
|
|
92
92
|
|
|
93
|
+
## 反例
|
|
94
|
+
|
|
95
|
+
- ❌ **不实现类型搜索** —— 原生 select 支持连续打字跳转;自建却丢掉这个,是**功能倒退**。
|
|
96
|
+
`select.js` 里做了 500ms 缓冲(超时清空,否则 "abc" 永远搜不到)
|
|
97
|
+
- ❌ **把焦点放进 `select__list`** —— 焦点必须常驻 `select__btn`,"当前在第几项"靠
|
|
98
|
+
`aria-activedescendant` 表达;焦点进了 list,读屏会开始念"列表"而不是"选项"
|
|
99
|
+
- ❌ **`select__opt` 命中区不足 44px** —— 本库按 AAA 要求做命中区;选项挤在一起,触屏必点错
|
|
100
|
+
- ❌ **把 `select__value--placeholder` 当已选值** —— 占位不是值:表单提交时它是空的,
|
|
101
|
+
而用户以为自己选了
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
93
105
|
## 门禁
|
|
94
106
|
|
|
95
107
|
`05-audit/select-check.js` —— 覆盖键盘契约与 ARIA 属性同步。
|
|
@@ -19,6 +19,23 @@
|
|
|
19
19
|
color: var(--text-secondary); min-height: 3em;
|
|
20
20
|
}
|
|
21
21
|
label.f { display: block; margin-bottom: var(--sp-1); font-size: var(--fs-sm); }
|
|
22
|
+
/* 一次性演示用:把选项摆成静态列表 */
|
|
23
|
+
.sl-wrap { display: grid; gap: var(--sp-1); }
|
|
24
|
+
.sl-head { font-size: var(--fs-sm); color: var(--text-tertiary); padding: 0 0 var(--sp-1); }
|
|
25
|
+
.sl-list {
|
|
26
|
+
list-style: none; margin: 0; padding: var(--sp-1);
|
|
27
|
+
background: var(--surface-raised);
|
|
28
|
+
border: 1px solid var(--border-decor);
|
|
29
|
+
border-radius: var(--r-sm);
|
|
30
|
+
}
|
|
31
|
+
/* 反例专用:把命中区压到 28px */
|
|
32
|
+
.sl-tight {
|
|
33
|
+
display: flex; align-items: center;
|
|
34
|
+
min-height: 28px; padding: 0 var(--sp-2);
|
|
35
|
+
border-radius: var(--r-xs);
|
|
36
|
+
font-size: var(--fs-sm);
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
}
|
|
22
39
|
</style>
|
|
23
40
|
</head>
|
|
24
41
|
<body class="page page">
|
|
@@ -94,6 +111,35 @@ label.f { display: block; margin-bottom: var(--sp-1); font-size: var(--fs-sm); }
|
|
|
94
111
|
</div>
|
|
95
112
|
</div>
|
|
96
113
|
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
117
|
+
<div class="cmp">
|
|
118
|
+
<div class="cmp__pane cmp__bad">
|
|
119
|
+
<h3>❌ 常见写法:选项挤在一起</h3>
|
|
120
|
+
<div class="sl-wrap" aria-hidden="true">
|
|
121
|
+
<div class="sl-head">所属板块</div>
|
|
122
|
+
<ul class="sl-list">
|
|
123
|
+
<li class="sl-tight">半导体设备</li>
|
|
124
|
+
<li class="sl-tight">半导体材料</li>
|
|
125
|
+
<li class="sl-tight">半导体封测</li>
|
|
126
|
+
</ul>
|
|
127
|
+
</div>
|
|
128
|
+
<p class="cmp__why">行高只有 28px:手指点下去必然压到隔壁那一行。鼠标用户感觉不到,触屏用户每点错一次就要重来。</p>
|
|
129
|
+
</div>
|
|
130
|
+
<div class="cmp__pane cmp__good">
|
|
131
|
+
<h3>✅ 本库:每项 44px 命中区</h3>
|
|
132
|
+
<div class="sl-wrap" aria-hidden="true">
|
|
133
|
+
<div class="sl-head">所属板块</div>
|
|
134
|
+
<ul class="sl-list">
|
|
135
|
+
<li class="select__opt">半导体设备</li>
|
|
136
|
+
<li class="select__opt">半导体材料</li>
|
|
137
|
+
<li class="select__opt">半导体封测</li>
|
|
138
|
+
</ul>
|
|
139
|
+
</div>
|
|
140
|
+
<p class="cmp__why">命中区 44px 是 AAA 的下限(<code>select.css</code> 里写着这条),与 <code>dropdown</code> 同规格 —— 两个控件的选项高度不该不一样。</p>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
97
143
|
</main>
|
|
98
144
|
|
|
99
145
|
<script src="select.js"></script>
|