agentcss 0.25.1 → 0.25.3
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/tokens.css +1 -1
- package/02-primitives/button/README.md +7 -0
- package/02-primitives/button/demo.html +52 -0
- package/02-primitives/card/README.md +6 -1
- package/02-primitives/card/demo.html +13 -0
- package/02-primitives/choice/choice.css +10 -0
- package/02-primitives/choice/demo.html +38 -0
- package/02-primitives/date-range/README.md +1 -1
- package/02-primitives/date-range/demo.html +41 -1
- package/02-primitives/input/README.md +3 -2
- package/02-primitives/input/demo.html +42 -0
- package/03-patterns/accordion/demo.html +113 -0
- package/03-patterns/content/demo.html +17 -0
- package/03-patterns/drawer/README.md +11 -1
- package/03-patterns/drawer/drawer.css +22 -6
- package/03-patterns/drawer/drawer.js +16 -1
- package/03-patterns/dropdown/README.md +10 -8
- package/03-patterns/dropdown/demo.html +5 -0
- package/03-patterns/dropdown/dropdown.js +8 -0
- package/03-patterns/form-validation/demo.html +20 -0
- package/03-patterns/list/demo.html +10 -0
- package/03-patterns/overlay/overlay.js +14 -1
- package/03-patterns/pagination/demo.html +23 -0
- package/03-patterns/states/demo.html +39 -1
- package/03-patterns/tabs/demo.html +34 -0
- package/03-patterns/tooltip/tooltip.js +8 -4
- package/04-recipes/app-shell/demo.html +27 -0
- package/04-recipes/table/demo.html +34 -0
- package/09-assets/bar/bar.js +8 -4
- package/09-assets/bar/demo.html +27 -0
- package/API.md +13 -5
- package/CHANGELOG.md +211 -1
- package/CONTRIBUTING.md +5 -5
- package/README.zh-CN.md +3 -3
- package/START-HERE.md +6 -6
- package/ai/components.json +6 -3
- package/ai/tokens.json +100 -99
- package/ai/tokens.schema.json +96 -50
- package/dist/02-primitives/choice/choice.css +1 -1
- package/dist/03-patterns/drawer/drawer.css +1 -1
- package/dist/03-patterns/drawer/drawer.js +10 -1
- package/dist/03-patterns/dropdown/dropdown.js +4 -0
- package/dist/03-patterns/overlay/overlay.js +7 -1
- package/dist/03-patterns/tooltip/tooltip.js +3 -3
- package/dist/09-assets/bar/bar.js +4 -3
- package/dist/COSTS.md +13 -13
- package/docs/ACCESSIBILITY.md +1 -1
- package/docs/GUIDANCE.md +82 -0
- package/docs/LESSONS.md +182 -0
- package/index.html +1 -1
- package/llms-full.txt +3 -3
- package/package.json +4 -2
|
@@ -61,6 +61,11 @@
|
|
|
61
61
|
</ul>
|
|
62
62
|
</div>
|
|
63
63
|
<div class="log" id="log">(选中项会显示在这里)</div>
|
|
64
|
+
<p class="small">
|
|
65
|
+
菜单里的那条<strong>分隔线</strong>只写 <code>data-dd-sep</code> 就行:
|
|
66
|
+
<code>role="separator"</code> 与 <code>.dd__sep</code> 由组件补。
|
|
67
|
+
方向键会跳过它 —— 分隔线不是可选项,让它能被"选中"是错的。
|
|
68
|
+
</p>
|
|
64
69
|
</div>
|
|
65
70
|
|
|
66
71
|
<h2>2 · 带勾选(选中性不只靠颜色)</h2>
|
|
@@ -359,6 +359,14 @@
|
|
|
359
359
|
li.className = li.className ? (li.className + ' dd__item') : 'dd__item';
|
|
360
360
|
}
|
|
361
361
|
if (li.hasAttribute('data-dd-sep')) {
|
|
362
|
+
/* 🔴 和上面的 dd__item 同一个道理:分隔线那 1px 的高度与背景
|
|
363
|
+
全在 `.dd__sep` 这条规则里。作者只写 `data-dd-sep` 而不写类名时,
|
|
364
|
+
量到的渲染高度是 **0px**(实测),两组菜单项直接贴着 ——
|
|
365
|
+
看上去像"漏了一条分隔线",而且没有任何报错。
|
|
366
|
+
⇒ 由组件补类,不指望每个使用者记得写。 */
|
|
367
|
+
if (String(li.className).indexOf('dd__sep') < 0) {
|
|
368
|
+
li.className = li.className ? (li.className + ' dd__sep') : 'dd__sep';
|
|
369
|
+
}
|
|
362
370
|
li.setAttribute('role', 'separator');
|
|
363
371
|
li.setAttribute('aria-hidden', 'true');
|
|
364
372
|
} else if (!li.hasAttribute('role')) {
|
|
@@ -21,6 +21,8 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
|
|
|
21
21
|
.good { background: var(--surface-sunken); }
|
|
22
22
|
.bad h3 { color: var(--danger); }
|
|
23
23
|
.good h3 { color: var(--success); }
|
|
24
|
+
/* 校验中示例只放一个字段,别撑满整行 —— 撑满了会让人以为"整页都在查" */
|
|
25
|
+
.checking-box { max-width: 460px; }
|
|
24
26
|
.btn { display:inline-flex; align-items:center; justify-content:center; height:44px;
|
|
25
27
|
padding:0 var(--sp-5); border:1px solid transparent; border-radius:var(--r-sm);
|
|
26
28
|
font-size:var(--fs-sm); font-weight:500; cursor:pointer; font-family:inherit;
|
|
@@ -91,6 +93,24 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
|
|
|
91
93
|
而且把逻辑硬塞进样式层不可测。控制权交给 JS 加 <code>data-dirty</code> 属性。
|
|
92
94
|
</div>
|
|
93
95
|
|
|
96
|
+
<h3 class="rule-bottom">第三种状态:异步校验中(<code>field--checking</code>)</h3>
|
|
97
|
+
<div class="box checking-box">
|
|
98
|
+
<div class="field field--checking">
|
|
99
|
+
<label class="field__label" for="uname">用户名</label>
|
|
100
|
+
<input class="field__input" id="uname" value="data_team" autocomplete="username"
|
|
101
|
+
aria-describedby="uname-checking">
|
|
102
|
+
<span class="field__checking-hint" id="uname-checking">正在检查是否可用…</span>
|
|
103
|
+
</div>
|
|
104
|
+
<p class="small" style="margin-top:var(--sp-3)">
|
|
105
|
+
🔴 <strong>校验中绝不能把输入框置灰或禁用</strong> —— 用户会以为"不让打字了",
|
|
106
|
+
于是停在那里等,而其实他完全可以接着改。
|
|
107
|
+
正确做法只有一条:<strong>保持可输入</strong>,旁边给一个小的 pending 指示。
|
|
108
|
+
<br>这个指示器是<strong>原地旋转</strong>(12px),不是全屏 spinner:
|
|
109
|
+
它只说明"这一项在查",不代表"整个页面在忙"。
|
|
110
|
+
<code>prefers-reduced-motion</code> 下放慢到 1.2s 但不停(停了会被当成卡死)。
|
|
111
|
+
</p>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
94
114
|
<h2 class="rule-bottom">二、完整表单:提交后出错误汇总</h2>
|
|
95
115
|
<p class="small">什么都不填,直接点提交。焦点会移到汇总上,汇总里每条可点击跳转。</p>
|
|
96
116
|
|
|
@@ -47,6 +47,9 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
|
|
|
47
47
|
</div>
|
|
48
48
|
|
|
49
49
|
<ul class="list" id="list" role="list"></ul>
|
|
50
|
+
<!-- 列表末尾的一行小字:说明"这个列表本身"(条数、排序、口径),
|
|
51
|
+
不是空状态。空状态用 states 的 .state--empty(见下面)。 -->
|
|
52
|
+
<p class="list__note" id="note" aria-live="polite">共 0 条</p>
|
|
50
53
|
|
|
51
54
|
<!-- 空状态:删光后显示,容器位置不变所以不跳动 -->
|
|
52
55
|
<div id="empty" hidden>
|
|
@@ -165,6 +168,8 @@ var EXTRA = [
|
|
|
165
168
|
|
|
166
169
|
var list = document.getElementById('list');
|
|
167
170
|
var emptyBox = document.getElementById('empty');
|
|
171
|
+
var note = document.getElementById('note');
|
|
172
|
+
var mode = '插入顺序';
|
|
168
173
|
var data = SEED.slice();
|
|
169
174
|
var seq = 100;
|
|
170
175
|
|
|
@@ -210,6 +215,7 @@ function paint() {
|
|
|
210
215
|
for (var i = 0; i < data.length; i++) { html += rowHtml(data[i]); }
|
|
211
216
|
list.innerHTML = html;
|
|
212
217
|
emptyBox.hidden = data.length > 0;
|
|
218
|
+
note.textContent = '共 ' + data.length + ' 条 · ' + mode;
|
|
213
219
|
}
|
|
214
220
|
|
|
215
221
|
function repaint(mutate) {
|
|
@@ -219,6 +225,7 @@ function repaint(mutate) {
|
|
|
219
225
|
for (var i = 0; i < data.length; i++) { html += rowHtml(data[i]); }
|
|
220
226
|
list.innerHTML = html;
|
|
221
227
|
emptyBox.hidden = data.length > 0;
|
|
228
|
+
note.textContent = '共 ' + data.length + ' 条 · ' + mode;
|
|
222
229
|
});
|
|
223
230
|
}
|
|
224
231
|
|
|
@@ -254,12 +261,14 @@ document.getElementById('add').onclick = function () {
|
|
|
254
261
|
};
|
|
255
262
|
|
|
256
263
|
document.getElementById('sort').onclick = function () {
|
|
264
|
+
mode = '按记录数降序';
|
|
257
265
|
repaint(function () {
|
|
258
266
|
data.sort(function (a, b) { return b.rec - a.rec; });
|
|
259
267
|
});
|
|
260
268
|
};
|
|
261
269
|
|
|
262
270
|
document.getElementById('reverse').onclick = function () {
|
|
271
|
+
mode = '按名称排序';
|
|
263
272
|
repaint(function () {
|
|
264
273
|
data.sort(function (a, b) { return a.name.localeCompare(b.name, 'zh-Hans-CN'); });
|
|
265
274
|
});
|
|
@@ -270,6 +279,7 @@ document.getElementById('clear').onclick = function () {
|
|
|
270
279
|
};
|
|
271
280
|
|
|
272
281
|
document.getElementById('reload').onclick = function () {
|
|
282
|
+
mode = '插入顺序';
|
|
273
283
|
data = SEED.slice();
|
|
274
284
|
for (var i = 0; i < data.length; i++) { data[i].id = 'r' + (++seq); }
|
|
275
285
|
paint();
|
|
@@ -252,6 +252,19 @@
|
|
|
252
252
|
🔴 绝不抢焦点。role="status" 会被屏幕阅读器自动播报,
|
|
253
253
|
focus() 会打断用户当前正在做的事。
|
|
254
254
|
*/
|
|
255
|
+
/* 变体 ⇒ 类名。🔴 两个理由,都不是"为了好看":
|
|
256
|
+
① **白名单**:`toast({variant:'warn'})` 原本会拼出 `toast--warn`,
|
|
257
|
+
没有任何 CSS 命中 ⇒ 一条本该是黄色的警示条渲染成普通白条,
|
|
258
|
+
不报错、控制台干净。现在未知值退回无变体(至少不假装有语义)。
|
|
259
|
+
② **写完整字面量**:`'toast--' + variant` 在源码里搜不到
|
|
260
|
+
`toast--success`,覆盖率门禁认不出"谁在用这个类"
|
|
261
|
+
(实测 success / warning 因此长期躺在"只被 README 认领"名单里)。 */
|
|
262
|
+
var TOAST_VARIANT = {
|
|
263
|
+
success: 'toast--success',
|
|
264
|
+
error: 'toast--error',
|
|
265
|
+
warning: 'toast--warning'
|
|
266
|
+
};
|
|
267
|
+
|
|
255
268
|
function toast(opts) {
|
|
256
269
|
opts = opts || {};
|
|
257
270
|
|
|
@@ -266,7 +279,7 @@
|
|
|
266
279
|
}
|
|
267
280
|
|
|
268
281
|
var el = document.createElement('div');
|
|
269
|
-
el.className = 'toast' + (opts.variant ? '
|
|
282
|
+
el.className = 'toast' + (TOAST_VARIANT[opts.variant] ? ' ' + TOAST_VARIANT[opts.variant] : '');
|
|
270
283
|
|
|
271
284
|
var body = document.createElement('div');
|
|
272
285
|
body.className = 'toast__body';
|
|
@@ -121,6 +121,23 @@ th { font-size: var(--fs-xs); color: var(--text-secondary); font-weight: 500; }
|
|
|
121
121
|
|
|
122
122
|
|
|
123
123
|
|
|
124
|
+
<h2 class="rule-top">7 · 表格页脚里的精简分页(不占一行位置)</h2>
|
|
125
|
+
<p class="note">
|
|
126
|
+
数据表底部通常只有一两行的空间:<b>状态文字可以省,播报区不能省</b>。
|
|
127
|
+
<br>做法是把 <code>data-pg-live</code> 挂在一个 <code>.pagination__sr</code> 上 ——
|
|
128
|
+
<b>视觉上不占位,读屏照常播报</b>(用 <code>clip</code> 裁掉,不是 <code>display:none</code>;
|
|
129
|
+
后者会把整块移出无障碍树,播报就没了)。
|
|
130
|
+
<br>⚠️ 省略 <code>data-pg-status</code> 是允许的(JS 里有判空);
|
|
131
|
+
省略 <code>data-pg-live</code> 则等于"翻页了但不告诉读屏"。
|
|
132
|
+
</p>
|
|
133
|
+
<div class="demo-box">
|
|
134
|
+
<nav class="pagination" aria-label="明细表分页"
|
|
135
|
+
data-pagination data-total="248" data-page-size="20" data-page="7" id="pg2">
|
|
136
|
+
<ul class="pagination__list" data-pg-list></ul>
|
|
137
|
+
<p class="pagination__sr" data-pg-live aria-live="polite"></p>
|
|
138
|
+
</nav>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
124
141
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
125
142
|
<div class="cmp">
|
|
126
143
|
<div class="cmp__pane cmp__bad" data-bad="li.pagination__item|count|>7">
|
|
@@ -200,6 +217,12 @@ pg.onChange = function (page) {
|
|
|
200
217
|
}
|
|
201
218
|
};
|
|
202
219
|
load(pg.page);
|
|
220
|
+
|
|
221
|
+
/* 精简分页(页脚用):没有 data-pg-status,播报区挂在 .pagination__sr 上 */
|
|
222
|
+
var pg2 = Pagination.create(document.getElementById('pg2'));
|
|
223
|
+
pg2.onChange = function (page) {
|
|
224
|
+
document.getElementById('pg2').setAttribute('data-page', String(page));
|
|
225
|
+
};
|
|
203
226
|
</script>
|
|
204
227
|
</body>
|
|
205
228
|
</html>
|
|
@@ -56,6 +56,12 @@ p.lead { color: var(--text-secondary); }
|
|
|
56
56
|
}
|
|
57
57
|
.lbl { font-size: var(--fs-xs); color: var(--text-tertiary);
|
|
58
58
|
margin-bottom: var(--sp-2); display: block; }
|
|
59
|
+
/* 列表项骨架:头像 + 两行 —— 结构与真实行一致(.row 的 padding 与 gap 对齐) */
|
|
60
|
+
.sk-list { border: 1px solid var(--border-decor); border-radius: var(--r-sm);
|
|
61
|
+
padding: var(--sp-2) 0; max-width: 420px; }
|
|
62
|
+
.sk-row { display: flex; align-items: center; gap: var(--sp-3);
|
|
63
|
+
padding: var(--sp-3) var(--sp-4); }
|
|
64
|
+
.sk-lines { flex: 1; min-width: 0; }
|
|
59
65
|
</style>
|
|
60
66
|
</head>
|
|
61
67
|
<body class="page">
|
|
@@ -140,6 +146,31 @@ p.lead { color: var(--text-secondary); }
|
|
|
140
146
|
|
|
141
147
|
<hr class="rule">
|
|
142
148
|
|
|
149
|
+
<h2>列表项的骨架:头像占位 + 两行文字</h2>
|
|
150
|
+
<p>
|
|
151
|
+
<code>skeleton--avatar</code> 是 40×40 的全圆占位,专门配 <code>skeleton--text</code>
|
|
152
|
+
做「头像 + 姓名 + 副标题」这种列表项。宽度节奏要跟真内容一致,
|
|
153
|
+
否则内容到位时头像会把文字挤走 —— 那正是骨架屏要避免的事。
|
|
154
|
+
</p>
|
|
155
|
+
<div class="sk-list" role="status" aria-live="polite" aria-label="正在加载公司列表">
|
|
156
|
+
<div class="sk-row">
|
|
157
|
+
<div class="skeleton skeleton--avatar" aria-hidden="true"></div>
|
|
158
|
+
<div class="sk-lines">
|
|
159
|
+
<div class="skeleton skeleton--text" aria-hidden="true"></div>
|
|
160
|
+
<div class="skeleton skeleton--text" aria-hidden="true"></div>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="sk-row">
|
|
164
|
+
<div class="skeleton skeleton--avatar" aria-hidden="true"></div>
|
|
165
|
+
<div class="sk-lines">
|
|
166
|
+
<div class="skeleton skeleton--text" aria-hidden="true"></div>
|
|
167
|
+
<div class="skeleton skeleton--text" aria-hidden="true"></div>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<hr class="rule">
|
|
173
|
+
|
|
143
174
|
<h2>300ms:不显示比闪一下好</h2>
|
|
144
175
|
<p>
|
|
145
176
|
请求在 300ms 内返回时<strong>不要显示加载态</strong>。骨架屏闪一下比不显示更糟 ——
|
|
@@ -218,7 +249,14 @@ var VIEWS = {
|
|
|
218
249
|
'<div class="state__action">' +
|
|
219
250
|
'<button class="btn btn--sm btn--primary" type="button">重试</button>' +
|
|
220
251
|
'<button class="btn btn--sm btn--secondary" type="button">复制诊断信息</button>' +
|
|
221
|
-
'</div
|
|
252
|
+
'</div>' +
|
|
253
|
+
/* 技术细节放这里、并且折叠起来:主文案说"我该怎么办",
|
|
254
|
+
细节留给要把它贴给技术支持的人。默认摊开会把所有人都吓一遍。 */
|
|
255
|
+
'<details class="state__more">' +
|
|
256
|
+
'<summary>诊断信息</summary>' +
|
|
257
|
+
'<div class="state__detail">sqlite3 · SELECT … FROM financial_facts WHERE ws=19 · err: SQLITE_BUSY (database is locked) · 12,480 ms · trace 8f2c41a7</div>' +
|
|
258
|
+
'</details>' +
|
|
259
|
+
'</div>';
|
|
222
260
|
}
|
|
223
261
|
};
|
|
224
262
|
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
<style>
|
|
14
14
|
.log { font-family: var(--mono); font-size: var(--fs-xs);
|
|
15
15
|
color: var(--text-secondary); min-height: 1.5em; }
|
|
16
|
+
/* 键帽:用真正的 <kbd> 元素,不是 span+class(后者会被 refs 判成"未定义的类") */
|
|
17
|
+
kbd { display: inline-block; padding: 2px 7px; border: 1px solid var(--border-control);
|
|
18
|
+
border-bottom-width: 2px; border-radius: 5px; background: var(--surface);
|
|
19
|
+
font-family: var(--mono); font-size: 11px; color: var(--text-primary); }
|
|
16
20
|
</style>
|
|
17
21
|
</head>
|
|
18
22
|
<body class="page">
|
|
@@ -86,6 +90,36 @@
|
|
|
86
90
|
</div>
|
|
87
91
|
</div>
|
|
88
92
|
|
|
93
|
+
<h2 class="rule-top">纵向(设置页侧栏那种)</h2>
|
|
94
|
+
<p class="note">
|
|
95
|
+
加 <code>.tabs--vertical</code>,并把 tablist 的
|
|
96
|
+
<code>aria-orientation</code> 改成 <code>vertical</code> ——
|
|
97
|
+
<strong>两件事必须同时做</strong>:只改 CSS 的话,键盘仍然是
|
|
98
|
+
<code>←</code> <code>→</code> 切换,而屏幕阅读器被告知"横向",
|
|
99
|
+
于是它提示用户按左右键;用户按上下键,没反应。
|
|
100
|
+
</p>
|
|
101
|
+
<div class="tabs tabs--vertical" data-tabs>
|
|
102
|
+
<div class="tabs__list" role="tablist" aria-orientation="vertical" aria-label="账户设置">
|
|
103
|
+
<button class="tabs__tab" role="tab" id="v1" aria-controls="vp1"
|
|
104
|
+
aria-selected="true">基本资料</button>
|
|
105
|
+
<button class="tabs__tab" role="tab" id="v2" aria-controls="vp2"
|
|
106
|
+
aria-selected="false">安全</button>
|
|
107
|
+
<button class="tabs__tab" role="tab" id="v3" aria-controls="vp3"
|
|
108
|
+
aria-selected="false">通知</button>
|
|
109
|
+
</div>
|
|
110
|
+
<div class="tabs__panel" role="tabpanel" id="vp1" aria-labelledby="v1">
|
|
111
|
+
<p>头像、显示名、所属工作区。</p>
|
|
112
|
+
<p><button class="btn btn--md btn--secondary">修改头像(可 Tab 到)</button></p>
|
|
113
|
+
</div>
|
|
114
|
+
<div class="tabs__panel" role="tabpanel" id="vp2" aria-labelledby="v2" hidden>
|
|
115
|
+
<p>两步验证、登录设备、API 令牌。</p>
|
|
116
|
+
</div>
|
|
117
|
+
<div class="tabs__panel" role="tabpanel" id="vp3" aria-labelledby="v3" hidden>
|
|
118
|
+
<p>周报邮件、异常告警的接收范围。</p>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
<p class="log">试 <kbd>↓</kbd> <kbd>↑</kbd>:选中线应该在<strong>文字右侧</strong>(RTL 下换到左侧)。</p>
|
|
122
|
+
|
|
89
123
|
<h2 class="rule-top">键盘契约(APG / Radix)</h2>
|
|
90
124
|
<div class="box">
|
|
91
125
|
<p>
|
|
@@ -157,10 +157,14 @@ var flDismissable = function (opts) {
|
|
|
157
157
|
return r;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
/* 逻辑方位 ⇒
|
|
160
|
+
/* 逻辑方位 ⇒ 完整类名。CSS 里只有 --inset-inline-start / --inset-inline-end。
|
|
161
|
+
🔴 为什么写整串而不是 `'tooltip--' + CLS[pos]`:拼出来的名字
|
|
162
|
+
在源码里 grep 不到,改 CSS 时无法反查谁在用;覆盖率门禁也只能看到
|
|
163
|
+
一段前缀,于是四个方位长期被记成"只被 README 认领"
|
|
164
|
+
—— 而它们其实每次 hover 都在渲染。 */
|
|
161
165
|
var CLS = {
|
|
162
|
-
top: 'top', bottom: 'bottom',
|
|
163
|
-
start: 'inset-inline-start', end: 'inset-inline-end',
|
|
166
|
+
top: 'tooltip--top', bottom: 'tooltip--bottom',
|
|
167
|
+
start: 'tooltip--inset-inline-start', end: 'tooltip--inset-inline-end',
|
|
164
168
|
};
|
|
165
169
|
|
|
166
170
|
/**
|
|
@@ -195,7 +199,7 @@ var flDismissable = function (opts) {
|
|
|
195
199
|
var id = 'tt' + (++uid);
|
|
196
200
|
var tip = document.createElement('div');
|
|
197
201
|
tip.id = id;
|
|
198
|
-
tip.className = 'tooltip
|
|
202
|
+
tip.className = 'tooltip ' + CLS[pos];
|
|
199
203
|
tip.setAttribute('role', 'tooltip');
|
|
200
204
|
tip.textContent = text;
|
|
201
205
|
|
|
@@ -316,6 +316,33 @@
|
|
|
316
316
|
<p class="note">只做纯图标栏、不接抽屉的话,第 4 节那种写法(图标 + <code>aria-label</code>)
|
|
317
317
|
也成立;但只要你会用 <code>data-shell-nav</code>,就把文字留在 DOM 里 ——
|
|
318
318
|
一份标记同时满足两种形态。</p>
|
|
319
|
+
<h2>8 · 平面外壳(<code>shell--flat</code>):融进纸面,不画第二层框</h2>
|
|
320
|
+
<div class="demo" style="padding:0">
|
|
321
|
+
<div class="shell shell--flat shell-demo--short">
|
|
322
|
+
<header class="shell__topbar">
|
|
323
|
+
<h2 class="shell__title">内嵌在报告里的外壳</h2>
|
|
324
|
+
</header>
|
|
325
|
+
<nav class="shell__side" aria-label="主导航(平面外壳)">
|
|
326
|
+
<ul class="shell__side-group">
|
|
327
|
+
<li><a class="shell__link" href="#" aria-current="page">概览</a></li>
|
|
328
|
+
<li><a class="shell__link" href="#">明细</a></li>
|
|
329
|
+
</ul>
|
|
330
|
+
</nav>
|
|
331
|
+
<div class="shell__main">
|
|
332
|
+
<div class="shell__content">
|
|
333
|
+
<div class="ph">外壳底色改回纸色(--paper)</div>
|
|
334
|
+
<div class="ph">主区边线透明 ⇒ 不与外层的卡片边框重影</div>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
<p class="note">
|
|
340
|
+
什么时候用:外壳<strong>本身已经在卡片 / 弹窗 / 报告版式里</strong>。
|
|
341
|
+
这时它再自带一层底色和左边线,就会和外层边框形成"双重描边" ——
|
|
342
|
+
两条相距 1px 的线,看上去像渲染错位。
|
|
343
|
+
<br>什么时候别用:外壳是整页容器。那时它需要自己的边界,
|
|
344
|
+
<code>--flat</code> 会让侧栏和主区糊成一片。
|
|
345
|
+
</p>
|
|
319
346
|
|
|
320
347
|
</main>
|
|
321
348
|
<!-- shell-nav.js 依赖 Drawer(抽屉的行为层)。顺序不能反:
|
|
@@ -122,6 +122,40 @@
|
|
|
122
122
|
|
|
123
123
|
|
|
124
124
|
|
|
125
|
+
<h2 class="rule-top">7 · 缺失值与紧凑密度(对账表这种"一屏二十行"的场合)</h2>
|
|
126
|
+
<p class="note">
|
|
127
|
+
<code>table--compact</code> 把字号降到 <code>--fs-xs</code>、内边距收到
|
|
128
|
+
<code>--sp-1 --sp-2</code> —— <strong>只给数字表、对账表用</strong>,
|
|
129
|
+
正文表格用了会读不动。
|
|
130
|
+
<br><code>cell--na</code> 是缺失值的统一写法:灰色、居中、不留空。
|
|
131
|
+
<strong>空格子和 0 是两件事</strong>:空格子读者分不清"没有数据"还是"漏了",
|
|
132
|
+
0 会被当成真实数值参与心算。
|
|
133
|
+
</p>
|
|
134
|
+
<div class="table table--compact">
|
|
135
|
+
<table>
|
|
136
|
+
<caption class="table__cap">表 G · 银行对账明细(示例,含缺失值)</caption>
|
|
137
|
+
<thead>
|
|
138
|
+
<tr>
|
|
139
|
+
<th scope="col">科目</th>
|
|
140
|
+
<th scope="col" class="num">期初</th>
|
|
141
|
+
<th scope="col" class="num">本期发生</th>
|
|
142
|
+
<th scope="col" class="num">期末</th>
|
|
143
|
+
</tr>
|
|
144
|
+
</thead>
|
|
145
|
+
<tbody>
|
|
146
|
+
<tr><th scope="row">银行存款</th><td class="num">1,204,300</td><td class="num">318,200</td><td class="num">1,522,500</td></tr>
|
|
147
|
+
<tr><th scope="row">应收账款</th><td class="num">884,120</td><td class="cell--na">—</td><td class="num">884,120</td></tr>
|
|
148
|
+
<tr><th scope="row">其他应收款</th><td class="cell--na">N/A</td><td class="num">12,400</td><td class="num">12,400</td></tr>
|
|
149
|
+
<tr><th scope="row">预付账款</th><td class="num">46,700</td><td class="num">3,100</td><td class="num">49,800</td></tr>
|
|
150
|
+
</tbody>
|
|
151
|
+
</table>
|
|
152
|
+
</div>
|
|
153
|
+
<p class="table__foot">
|
|
154
|
+
两处缺失值用了两种写法(<code>—</code> 与 <code>N/A</code>):
|
|
155
|
+
会计表里 <code>—</code> 表示"本期无发生",<code>N/A</code> 表示"未取到数"。
|
|
156
|
+
混用会让人以为两者是同一件事 —— <strong>选一种并在表注里说明</strong>。
|
|
157
|
+
</p>
|
|
158
|
+
|
|
125
159
|
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
126
160
|
<div class="cmp">
|
|
127
161
|
<div class="cmp__pane cmp__bad" data-bad="td.num--pos|glyphs|=5">
|
package/09-assets/bar/bar.js
CHANGED
|
@@ -23,8 +23,12 @@
|
|
|
23
23
|
var NS = 'http://www.w3.org/2000/svg';
|
|
24
24
|
var uid = 0;
|
|
25
25
|
/* 分类色 5 个(bar.css 里的 .bar__s0..s4),与 scientific-plot 的 ramp 对齐。
|
|
26
|
-
超过 5 个系列请由调用方给 `series[i].cls` 自定义 —— 颜色循环会造成误读。
|
|
27
|
-
|
|
26
|
+
超过 5 个系列请由调用方给 `series[i].cls` 自定义 —— 颜色循环会造成误读。
|
|
27
|
+
🔴 写成**完整类名数组**而不是 `'bar__s' + n`:
|
|
28
|
+
拼串在源码里搜不到 `bar__s4`,改配色时无法反查谁在用;
|
|
29
|
+
也让 .bar__s4 一直躺在覆盖率门禁的"只被 README 认领"名单里。 */
|
|
30
|
+
var SERIES_CLS = ['bar__s0', 'bar__s1', 'bar__s2', 'bar__s3', 'bar__s4'];
|
|
31
|
+
var SERIES_STYLES = SERIES_CLS.length;
|
|
28
32
|
|
|
29
33
|
/* ==================================================================== *
|
|
30
34
|
* 铁律二的核心:哪些点值得标数字
|
|
@@ -193,7 +197,7 @@
|
|
|
193
197
|
yy = padT + plotH - vt;
|
|
194
198
|
|
|
195
199
|
rect = el('rect', {
|
|
196
|
-
'class': 'bar__bar
|
|
200
|
+
'class': 'bar__bar ' + SERIES_CLS[s % SERIES_STYLES] +
|
|
197
201
|
(series[s].cls ? ' ' + series[s].cls : ''),
|
|
198
202
|
x: x, y: yy, width: barW, height: vt - vb,
|
|
199
203
|
});
|
|
@@ -277,7 +281,7 @@
|
|
|
277
281
|
var li = document.createElement('li');
|
|
278
282
|
li.className = 'bar__legend-item';
|
|
279
283
|
var sw = document.createElement('span');
|
|
280
|
-
sw.className = 'bar__legend-swatch
|
|
284
|
+
sw.className = 'bar__legend-swatch ' + SERIES_CLS[si % SERIES_STYLES] +
|
|
281
285
|
(se.cls ? ' ' + se.cls : '');
|
|
282
286
|
/* 🔴 用 textContent,不用 innerHTML —— 系列名来自调用方的数据 */
|
|
283
287
|
li.appendChild(sw);
|
package/09-assets/bar/demo.html
CHANGED
|
@@ -82,6 +82,16 @@
|
|
|
82
82
|
<button class="btn" id="try">不给 max,看会发生什么</button>
|
|
83
83
|
<div id="err"></div>
|
|
84
84
|
|
|
85
|
+
<h2>5 · 五个系列 = 五档分类色;第 6 个必须自带 <code>cls</code></h2>
|
|
86
|
+
<p class="note">
|
|
87
|
+
<code>bar.css</code> 只提供 <code>.bar__s0</code>–<code>.bar__s4</code> 五档分类色
|
|
88
|
+
(与 <code>scientific-plot</code> 的 ramp 对齐)。超过五个系列时,本库
|
|
89
|
+
<b>不会循环取色</b>——循环会让两个不同的系列拿到同一个颜色,读者会以为它们是同一类。
|
|
90
|
+
<br>⇒ 第 6 个系列起,请由调用方给 <code>series[i].cls</code>。
|
|
91
|
+
下面这张图正好用满五档:五根柱子五种颜色,都能在基线截图里逐色核对。
|
|
92
|
+
</p>
|
|
93
|
+
<div class="bar" id="b5"></div>
|
|
94
|
+
|
|
85
95
|
</main>
|
|
86
96
|
|
|
87
97
|
<script src="bar.js"></script>
|
|
@@ -156,6 +166,23 @@
|
|
|
156
166
|
ariaLabel: '每日 09:00 前的事项数,含阈值线 1',
|
|
157
167
|
});
|
|
158
168
|
|
|
169
|
+
/* 5 · 五系列 = 五档分类色(.bar__s0..s4 全部真的渲染出来) */
|
|
170
|
+
Bar.draw({
|
|
171
|
+
host: document.getElementById('b5'),
|
|
172
|
+
mode: 'group',
|
|
173
|
+
max: 9,
|
|
174
|
+
height: 190,
|
|
175
|
+
labels: LBL.slice(0, 6),
|
|
176
|
+
series: [
|
|
177
|
+
{ name: '受理', values: [3, 4, 5, 2, 6, 4] },
|
|
178
|
+
{ name: '审核', values: [2, 3, 4, 5, 3, 6] },
|
|
179
|
+
{ name: '放款', values: [4, 2, 3, 4, 5, 3] },
|
|
180
|
+
{ name: '对账', values: [5, 5, 2, 3, 4, 2] },
|
|
181
|
+
{ name: '归档', values: [1, 6, 6, 1, 2, 5] },
|
|
182
|
+
],
|
|
183
|
+
ariaLabel: '六天五个环节的并列分组柱状图,五档分类色',
|
|
184
|
+
});
|
|
185
|
+
|
|
159
186
|
/* 4 · 演示"量程必填" */
|
|
160
187
|
document.getElementById('try').addEventListener('click', function () {
|
|
161
188
|
var box = document.getElementById('err');
|
package/API.md
CHANGED
|
@@ -29,23 +29,31 @@
|
|
|
29
29
|
除了 `<script src>`,16 个交互组件的脚本还有一个 ESM 入口:
|
|
30
30
|
|
|
31
31
|
```js
|
|
32
|
-
//
|
|
32
|
+
// 只要一个(推荐:只引入这一个脚本,体积最小)
|
|
33
33
|
import { Accordion } from 'agentcss/esm/accordion.mjs';
|
|
34
34
|
|
|
35
|
-
//
|
|
35
|
+
// 或者一次拿全 —— ⚠️ 不会摇树,17 个行为脚本全部进产物
|
|
36
36
|
import { Accordion, Tabs, Select } from 'agentcss/esm';
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
四条容易踩的:
|
|
40
40
|
|
|
41
41
|
1. **入口是包装,不是第二份实现**。`esm/*.mjs` 只做
|
|
42
42
|
「导入副作用 + 把全局上那个对象 re-export」,所以
|
|
43
43
|
`Accordion === window.Accordion` 恒成立(门禁 `behavior-api` 断言这一点)。
|
|
44
44
|
它由 `05-audit/gen-esm.py` 生成,改源码后要重新生成,`esm-gen` 门禁会拦住漂移。
|
|
45
|
-
2.
|
|
45
|
+
2. **聚合入口不会摇树**(0.25.3 更正)。`esm/*.mjs` 的形式是
|
|
46
|
+
「先 `import` 目标脚本(副作用:把对象挂到全局),再把它 re-export」——
|
|
47
|
+
模块体本身就有副作用,打包器无法判断哪个导出没被用到,只能整包收下。
|
|
48
|
+
实测(esbuild):`import { Select } from 'agentcss/esm'` 产物 **190 KB**,
|
|
49
|
+
而 `import { Select } from 'agentcss/esm/select'` 只有 **20.8 KB**(约 9 倍)。
|
|
50
|
+
`package.json` 的 `sideEffects` 把 `**/*.js` / `**/*.mjs` 标成有副作用,
|
|
51
|
+
**这是按事实写的、是对的** —— 错的是 0.25.2 之前本文档那句"会按需摇树"。
|
|
52
|
+
⇒ 要体积就逐个 `import 'agentcss/esm/<组件>'`。
|
|
53
|
+
3. **无 DOM 环境可以直接 import**(SSR 安全)。但自动初始化会被跳过 ——
|
|
46
54
|
服务端没有 DOM 可扫,到浏览器里再调 `init()`/构造函数。16 个脚本
|
|
47
55
|
在 Node 里 `require` 全部成功(门禁的判据 A)。
|
|
48
|
-
|
|
56
|
+
4. **重复初始化是安全的**。`init()` 与构造函数都做了幂等处理:
|
|
49
57
|
同一 root 只会有一个实例,不会出现「点一次切换两次」。
|
|
50
58
|
|
|
51
59
|
### 四种接入形态
|