agentcss 0.25.6 → 0.26.0

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.
Files changed (56) hide show
  1. package/01-tokens/behavior/labels.js +44 -0
  2. package/02-primitives/combobox/combobox.js +26 -3
  3. package/02-primitives/date-range/date-range.js +27 -5
  4. package/02-primitives/popover/popover.js +22 -2
  5. package/02-primitives/select/select.js +23 -2
  6. package/02-primitives/upload/upload.js +50 -17
  7. package/03-patterns/content/content.css +13 -1
  8. package/03-patterns/content/demo.html +1 -1
  9. package/03-patterns/drawer/drawer.js +44 -6
  10. package/03-patterns/dropdown/dropdown.js +21 -1
  11. package/03-patterns/overlay/overlay.js +39 -2
  12. package/03-patterns/pagination/pagination.js +37 -10
  13. package/03-patterns/tooltip/tooltip.js +17 -2
  14. package/03-patterns/tree/tree.js +21 -1
  15. package/04-recipes/analysis-report/demo.html +1 -1
  16. package/04-recipes/data-showcase/demo.html +2 -2
  17. package/04-recipes/longform/content.html +989 -74
  18. package/04-recipes/longform/longform.html +3 -3
  19. package/04-recipes/report/demo.html +1 -1
  20. package/04-recipes/table/README.md +22 -0
  21. package/04-recipes/table/demo.html +6 -6
  22. package/09-assets/bar/bar.js +28 -4
  23. package/09-assets/model-viewer/model-viewer.js +44 -12
  24. package/09-assets/scientific-plot/demo.html +6 -6
  25. package/API.md +32 -0
  26. package/CHANGELOG.md +71 -0
  27. package/README.md +17 -0
  28. package/README.zh-CN.md +16 -0
  29. package/SECURITY.md +16 -4
  30. package/START-HERE.md +14 -14
  31. package/ai/components.json +9 -4
  32. package/ai/pdf.js +50 -8
  33. package/dist/01-tokens/behavior/labels.js +11 -0
  34. package/dist/02-primitives/combobox/combobox.js +25 -3
  35. package/dist/02-primitives/date-range/date-range.js +26 -5
  36. package/dist/02-primitives/popover/popover.js +14 -1
  37. package/dist/02-primitives/select/select.js +22 -2
  38. package/dist/02-primitives/upload/upload.js +47 -17
  39. package/dist/03-patterns/content/content.css +1 -1
  40. package/dist/03-patterns/drawer/drawer.js +32 -4
  41. package/dist/03-patterns/dropdown/dropdown.js +20 -1
  42. package/dist/03-patterns/overlay/overlay.js +25 -1
  43. package/dist/03-patterns/pagination/pagination.js +34 -10
  44. package/dist/03-patterns/tooltip/tooltip.js +14 -2
  45. package/dist/03-patterns/tree/tree.js +20 -1
  46. package/dist/09-assets/bar/bar.js +23 -4
  47. package/dist/09-assets/model-viewer/model-viewer.js +38 -12
  48. package/dist/COSTS.md +14 -14
  49. package/docs/ACCESSIBILITY.md +2 -2
  50. package/docs/L10N.md +92 -0
  51. package/docs/RELEASE-PROCESS.md +7 -1
  52. package/docs/VERIFY-RELEASE.md +7 -2
  53. package/index.html +1 -1
  54. package/llms-full.txt +2 -2
  55. package/package.json +3 -2
  56. package/types/index.d.ts +218 -13
@@ -479,7 +479,7 @@ th {
479
479
  哪怕会覆盖掉别人写的东西。
480
480
  </p>
481
481
 
482
- <div class="table-wrap">
482
+ <div class="table-wrap" tabindex="0" role="region" aria-label="对照表(可横向滚动)">
483
483
  <table>
484
484
  <colgroup>
485
485
  <col style="width: 34%">
@@ -520,7 +520,7 @@ th {
520
520
  这套机制加上"准入控制",让关键任务(姿态、点火)不会被普通计算饿死。
521
521
  </p>
522
522
 
523
- <pre><code><span class="c-cmt"># 不是原文,是按手册重述的伪代码</span>
523
+ <pre tabindex="0"><code><span class="c-cmt"># 不是原文,是按手册重述的伪代码</span>
524
524
  if computer_overload():
525
525
  issue(1202) <span class="c-cmt"># 告警编号写入 AGC 计数区</span>
526
526
  <span class="c-cmt"># 不停机:停机会丢掉姿态信息,地面将失去唯一的实时数据源</span>
@@ -540,7 +540,7 @@ if computer_overload():
540
540
  包括读取真实的操作数、写入真实的寄存器地址。
541
541
  </p>
542
542
 
543
- <pre><code><span class="c-cmt"># 一段能真正跑起来的最小例子</span>
543
+ <pre tabindex="0"><code><span class="c-cmt"># 一段能真正跑起来的最小例子</span>
544
544
  <span class="c-out">$</span> python3 -m agc --help
545
545
  <span class="c-out">$</span> ./yaAGC -t demo.agc <span class="c-cmt"># 载入磁带并执行</span>
546
546
  <span class="c-out">AGC 0000 GO</span></code></pre>
@@ -32,7 +32,7 @@
32
32
  <!-- ======================================================================== -->
33
33
  <h2 class="rule-top">先看取舍:三套版式各自放弃了什么</h2>
34
34
 
35
- <div class="table"><table>
35
+ <div class="table" tabindex="0" role="region" aria-label="三种 profile 的结构差异(可横向滚动)"><table>
36
36
  <caption class="table__cap">表 A · 三种 profile 的结构差异(判据可验证,不是文案差异)</caption>
37
37
  <thead><tr>
38
38
  <th scope="col">profile</th>
@@ -65,6 +65,28 @@ var COLS = [
65
65
 
66
66
  ## 无障碍
67
67
 
68
+ - **滚动容器必须能用键盘进入。** `.table` 是 `overflow:auto` 的二维滚动区域,
69
+ 窄屏下会横向滚动;**纯 CSS 无法让它被 Tab 到**,必须在标记上给入口:
70
+
71
+ ```html
72
+ <div class="table table--financial" tabindex="0" role="region"
73
+ aria-label="A 股样例数据(可横向滚动)">
74
+ <table>…</table>
75
+ </div>
76
+ ```
77
+
78
+ 三个属性缺一不可:`tabindex="0"`(进 Tab 序列)、`role="region"`(成为可命名地标)、
79
+ `aria-label`(读屏要念出"这是什么表")。少了它们的后果是:只用键盘的用户
80
+ 和部分读屏用户**看不到超出可视区的列**(WCAG 2.1.1)。
81
+ 名字里带上 **「(可横向滚动)」**:只说"什么表"不告诉用户这里能滚,
82
+ 焦点停上来时读屏不会提示还有更多列 —— 那正是这条规则要解决的问题。
83
+ 图/图表容器同理,但名字应来自图注(`aria-labelledby` 指向 `<figcaption>`),
84
+ 不要再手写一份同名文案(同一事实不留第二出处)。
85
+ > 这条由两道门禁守着:`table-check.js` ⑮ 在 320px 下逐个量「会滚的容器有没有入口」,
86
+ > `a11y-scan.js` 在同一个页面上补跑 320px 的 `scrollable-region-focusable`。
87
+ > 桌面宽(1280)下表格不溢出,那条 axe 规则根本不会触发 —— 所以窄屏必须单独判。
88
+ > ⚠️ 只有**真的会滚**的容器需要 `tabindex`;不滚的容器加上去只是多一个空的 Tab 停靠点。
89
+
68
90
  - 数据表用 `<th scope="col">` / `<th scope="row">`;多级表头第一层用 `scope="colgroup"`。
69
91
  复杂表格可用 `id` + `headers` 建立显式关联(本示例未用到,但结构支持)。
70
92
  - 排序状态放在 `<th aria-sort="ascending|descending">`,**不是**放在按钮上。
@@ -131,7 +131,7 @@
131
131
  <strong>空格子和 0 是两件事</strong>:空格子读者分不清"没有数据"还是"漏了",
132
132
  0 会被当成真实数值参与心算。
133
133
  </p>
134
- <div class="table table--compact">
134
+ <div class="table table--compact" tabindex="0" role="region" aria-label="银行对账明细(可横向滚动)">
135
135
  <table>
136
136
  <caption class="table__cap">表 G · 银行对账明细(示例,含缺失值)</caption>
137
137
  <thead>
@@ -353,7 +353,7 @@
353
353
  });
354
354
  }
355
355
 
356
- var h = '<div class="table table--financial"><table>' +
356
+ var h = '<div class="table table--financial" tabindex="0" role="region" aria-label="A 股样例数据(可横向滚动)"><table>' +
357
357
  '<caption class="table__cap">A 股样例数据(非真实财报,量级参照公开年报)· ' +
358
358
  '金额单位:万元 · 比率为比例值</caption>' +
359
359
  '<thead><tr>';
@@ -425,7 +425,7 @@
425
425
  { label: '时效 (150 °C)', key: 'h150' }
426
426
  ];
427
427
 
428
- var a = '<div class="table table--academic"><table>' +
428
+ var a = '<div class="table table--academic" tabindex="0" role="region" aria-label="时效温度对铝合金拉伸性能的影响(可横向滚动)"><table>' +
429
429
  '<caption class="table__cap">表 1 · 不同时效温度下铝合金的拉伸性能(示例数据)</caption>' +
430
430
  '<thead><tr><th scope="col" rowspan="2">合金牌号</th>';
431
431
  GROUPS.forEach(function (g) {
@@ -472,7 +472,7 @@
472
472
  return fixed(p, 3).replace(/^0\./, '.');
473
473
  }
474
474
 
475
- var s = '<div class="table table--stats"><table>' +
475
+ var s = '<div class="table table--stats" tabindex="0" role="region" aria-label="OLS 估计结果(可横向滚动)"><table>' +
476
476
  '<caption class="table__cap">表 2 · 处理效应的 OLS 估计(示例数据,n = 412)</caption>' +
477
477
  '<thead><tr>' +
478
478
  '<th scope="col">项</th>' +
@@ -512,7 +512,7 @@
512
512
  why: '缺失 ⇒ 显示 —,不是 0,也不带符号' }
513
513
  ];
514
514
 
515
- var p = '<div class="table"><table>' +
515
+ var p = '<div class="table" tabindex="0" role="region" aria-label="同一个数在不同列定义下的显示(可横向滚动)"><table>' +
516
516
  '<caption class="table__cap">同一个数、不同的列定义 ⇒ 不同的显示</caption>' +
517
517
  '<thead><tr><th scope="col">列定义</th><th scope="col">输入</th>' +
518
518
  '<th scope="col">渲染结果</th><th scope="col">为什么</th></tr></thead><tbody>';
@@ -547,7 +547,7 @@
547
547
  expect: '显示为字面字符' }
548
548
  ];
549
549
 
550
- var e2 = '<div class="table"><table>' +
550
+ var e2 = '<div class="table" tabindex="0" role="region" aria-label="转义演示:动态文本按文本渲染(可横向滚动)"><table>' +
551
551
  '<caption class="table__cap">动态文本按**文本**渲染,不当 HTML 解释</caption>' +
552
552
  '<thead><tr><th scope="col">列定义</th><th scope="col">原始数据</th>' +
553
553
  '<th scope="col">渲染结果</th><th scope="col">验收点</th></tr></thead><tbody>';
@@ -95,9 +95,35 @@
95
95
  * 并且只画(画布内的那一部分),不能画到坐标系外面去
96
96
  * ③ **标注跟着堆叠顶端走**:标签写的是**该列合计**,不是第一个系列的值
97
97
  */
98
+ /* 可见文案默认值(F2 / 0.26.0)。
99
+ ⚠️ 这里用 `opt.text` 而不是 `labels` —— `bar.draw` 的 `labels`
100
+ 早就是「X 轴标签」这个**数据**参数,改名是破坏性变更。
101
+ 同类情况全库只此一处,已在 docs/L10N.md 写明。 */
102
+ var DEFAULT_TEXT = {
103
+ series: function (i) { return '系列 ' + i; },
104
+ clipped: function (n, isStack, max) {
105
+ return '有 ' + n + ' 个' + (isStack ? '类目的合计' : '数据')
106
+ + '超出量程(上限 ' + max + '),已按上限截断显示。';
107
+ }
108
+ };
109
+
110
+ /* ==== BEHAVIOR INJECT BEGIN: labels ==== */
111
+ var flLabels = function (defaults, src) {
112
+ src = src || {};
113
+ var out = {};
114
+ var k;
115
+ for (k in defaults) {
116
+ if (Object.prototype.hasOwnProperty.call(defaults, k)) {
117
+ out[k] = (src[k] === undefined || src[k] === null) ? defaults[k] : src[k];
118
+ }
119
+ }
120
+ return out;
121
+ };
122
+ /* ==== BEHAVIOR INJECT END: labels ==== */
98
123
  function draw(opt) {
99
124
  opt = opt || {};
100
125
  var host = opt.host;
126
+ var T = flLabels(DEFAULT_TEXT, opt && opt.text);
101
127
  if (!host) throw new Error('bar.draw: 缺少 host');
102
128
  if (opt.max === undefined || opt.max === null) {
103
129
  /* 🔴 铁律三:不替调用方决定量程。
@@ -286,7 +312,7 @@
286
312
  /* 🔴 用 textContent,不用 innerHTML —— 系列名来自调用方的数据 */
287
313
  li.appendChild(sw);
288
314
  var tx = document.createElement('span');
289
- tx.textContent = se.name || ('系列 ' + (si + 1));
315
+ tx.textContent = se.name || T.series(si + 1);
290
316
  li.appendChild(tx);
291
317
  legendEl.appendChild(li);
292
318
  });
@@ -302,9 +328,7 @@
302
328
  } else if (clipped.length) {
303
329
  var p2 = document.createElement('p');
304
330
  p2.className = 'bar__note';
305
- p2.textContent = '有 ' + clipped.length + ' 个' +
306
- (mode === 'stack' ? '类目的合计' : '数据') +
307
- '超出量程(上限 ' + max + '),已按上限截断显示。';
331
+ p2.textContent = T.clipped(clipped.length, mode === 'stack', max);
308
332
  host.appendChild(p2);
309
333
  }
310
334
 
@@ -110,7 +110,7 @@
110
110
  n.appendChild(this.ui);
111
111
 
112
112
  // 加载按钮
113
- this.btn = el('button', 'mv__btn', '加载 3D 模型');
113
+ this.btn = el('button', 'mv__btn', LABELS.load);
114
114
  this.btn.type = 'button';
115
115
  this.btn.addEventListener('click', function () { self.load(); });
116
116
  this.ui.appendChild(this.btn);
@@ -120,23 +120,23 @@
120
120
  原来用 innerHTML 直接拼 ⇒ `<img onerror>` 这类值会被解析成节点。
121
121
  改成 textContent 组装:`<code>` 与 `<b>` 仍然存在,但文本永远只是文本。 */
122
122
  var fname = el('code', null, this.src.split('/').pop() || '—');
123
- var strong = el('b', null, '点击后才下载');
124
- this.hint.appendChild(document.createTextNode('模型 '));
123
+ var strong = el('b', null, LABELS.hintStrong);
124
+ this.hint.appendChild(document.createTextNode(LABELS.hintBefore));
125
125
  this.hint.appendChild(fname);
126
- this.hint.appendChild(document.createTextNode(' 会'));
126
+ this.hint.appendChild(document.createTextNode(LABELS.hintBetween));
127
127
  this.hint.appendChild(strong);
128
- this.hint.appendChild(document.createTextNode(',首屏不占流量。'));
128
+ this.hint.appendChild(document.createTextNode(LABELS.hintTail));
129
129
  this.ui.appendChild(this.hint);
130
130
 
131
131
  if (!hasWebGL()) {
132
- this.fail('这台设备的浏览器不支持 WebGL,无法显示 3D 模型。');
132
+ this.fail(LABELS.noWebGL);
133
133
  this.btn.disabled = true;
134
134
  }
135
135
  };
136
136
 
137
137
  Viewer.prototype.makeIdle = function () {
138
138
  var box = el('div', 'mv__stage');
139
- box.appendChild(el('div', 'mv__ph', '3D 模型'));
139
+ box.appendChild(el('div', 'mv__ph', LABELS.placeholder));
140
140
  return box;
141
141
  };
142
142
 
@@ -146,14 +146,14 @@
146
146
  this.state = 'error';
147
147
  this.ui.innerHTML = '';
148
148
  var s = el('div', 'mv__stage mv__stage--err');
149
- s.appendChild(el('div', 'mv__ph', '3D 模型'));
149
+ s.appendChild(el('div', 'mv__ph', LABELS.placeholder));
150
150
  var p = el('p', 'mv__err', msg);
151
151
  s.appendChild(p);
152
152
  this.ui.appendChild(s);
153
153
  if (this.btn) this.ui.appendChild(this.btn);
154
154
  if (this.btn) {
155
155
  this.btn.disabled = false;
156
- this.btn.textContent = '重试';
156
+ this.btn.textContent = LABELS.retry;
157
157
  }
158
158
  };
159
159
 
@@ -169,12 +169,12 @@
169
169
 
170
170
  this.ui.innerHTML = '';
171
171
  var stage = el('div', 'mv__stage');
172
- this.statusNode = el('p', 'mv__status', '正在准备…');
172
+ this.statusNode = el('p', 'mv__status', LABELS.preparing);
173
173
  stage.appendChild(this.statusNode);
174
174
  this.ui.appendChild(stage);
175
175
 
176
176
  var t0 = (window.performance && performance.now) ? performance.now() : Date.now();
177
- this.status('正在准备 3D 引擎…');
177
+ this.status(LABELS.preparingEngine);
178
178
  this.boot(null, t0, 0);
179
179
 
180
180
  /* 🔴🔴 **超时兜底**(实测:服务器上会卡死在 loading)
@@ -296,7 +296,7 @@
296
296
  xhr.onprogress = function (e) {
297
297
  if (e.lengthComputable && e.total) {
298
298
  self1.lastBytes = e.total;
299
- self1.status('正在下载模型 ' + Math.round(e.loaded / e.total * 100) + '%');
299
+ self1.status(LABELS.downloading(Math.round(e.loaded / e.total * 100)));
300
300
  }
301
301
  };
302
302
  xhr.onload = function () {
@@ -396,6 +396,36 @@
396
396
 
397
397
  /* ---------- 对外 API ---------- */
398
398
 
399
+ /* 可见 / 读屏文案(F2 / 0.26.0)。
400
+ 本组件由 `[data-mv]` 自动初始化、没有逐实例 opts ⇒ 用**模块级**设置:
401
+ `ModelViewer.setLabels({ load: 'Load 3D model', ... })`
402
+ ⚠️ 已知仍未覆盖的(列在 docs/L10N.md,不假装已做完):
403
+ `mv__cap` 的说明句、`fail()` 的几条错误句 ——
404
+ 它们含 `<b>` 结构与多个动态量,要改成节点拼装才能安全本地化。 */
405
+ var LABELS = {
406
+ load: '加载 3D 模型',
407
+ placeholder: '3D 模型',
408
+ retry: '重试',
409
+ preparing: '正在准备…',
410
+ preparingEngine: '正在准备 3D 引擎…',
411
+ hintBefore: '模型 ',
412
+ hintBetween: ' 会',
413
+ hintStrong: '点击后才下载',
414
+ hintTail: ',首屏不占流量。',
415
+ downloading: function (pct) { return '正在下载模型 ' + pct + '%'; },
416
+ noWebGL: '这台设备的浏览器不支持 WebGL,无法显示 3D 模型。'
417
+ };
418
+
419
+ function setLabels(patch) {
420
+ if (!patch) return;
421
+ for (var k in LABELS) {
422
+ if (Object.prototype.hasOwnProperty.call(LABELS, k)
423
+ && patch[k] !== undefined && patch[k] !== null) {
424
+ LABELS[k] = patch[k];
425
+ }
426
+ }
427
+ }
428
+
399
429
  var ModelViewer = {
400
430
  /** 扫描并初始化页面里的所有查看器 */
401
431
  update: function (root) {
@@ -421,6 +451,8 @@
421
451
  },
422
452
  };
423
453
 
454
+ ModelViewer.setLabels = setLabels;
455
+
424
456
  if (typeof window !== 'undefined') window.ModelViewer = ModelViewer;
425
457
 
426
458
  // @autoinit: [data-mv] —— 扫属性自动 update()(详见 API.md「自动初始化」)
@@ -39,8 +39,8 @@
39
39
  本模块<strong>不做表达式求值</strong>。
40
40
  </p>
41
41
  <figure class="fig">
42
- <div class="fig__box" id="fig1"></div>
43
- <figcaption class="fig__cap">
42
+ <div class="fig__box" id="fig1" tabindex="0" role="region" aria-labelledby="fig1-cap"></div>
43
+ <figcaption class="fig__cap" id="fig1-cap">
44
44
  图 1 · 阻尼振荡位移随时间的变化。A = 1.00 m,λ = 0.35 s⁻¹,f = 1.20 Hz。
45
45
  </figcaption>
46
46
  </figure>
@@ -52,8 +52,8 @@
52
52
  也不据此推断显著性 —— 这两件事必须由测量者说清楚。
53
53
  </p>
54
54
  <figure class="fig">
55
- <div class="fig__box" id="fig2"></div>
56
- <figcaption class="fig__cap">
55
+ <div class="fig__box" id="fig2" tabindex="0" role="region" aria-labelledby="fig2-cap"></div>
56
+ <figcaption class="fig__cap" id="fig2-cap">
57
57
  图 2 · 五种催化剂在不同温度下的转化率(示例数据,误差棒为 ±1 个测量标准差)。
58
58
  </figcaption>
59
59
  </figure>
@@ -105,11 +105,11 @@
105
105
  后者画出的是一段<strong>不存在的趋势</strong>。
106
106
  </p>
107
107
  <figure class="fig">
108
- <div class="fig__box" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:var(--sp-3)">
108
+ <div class="fig__box" tabindex="0" role="region" aria-labelledby="figgap-cap" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:var(--sp-3)">
109
109
  <div id="figGap"></div>
110
110
  <div id="figZero"></div>
111
111
  </div>
112
- <figcaption class="fig__cap">
112
+ <figcaption class="fig__cap" id="figgap-cap">
113
113
  图 5 · 同一组数据,两种缺失处理。左:缺失断开(正确)。右:缺失当 0(错误,仅用于对照)。
114
114
  </figcaption>
115
115
  </figure>
package/API.md CHANGED
@@ -376,6 +376,38 @@ python3 05-audit/prefix-build.py --prefix fl
376
376
 
377
377
  ---
378
378
 
379
+ ## 文案本地化(0.26.0 起)
380
+
381
+ **库默认输出中文;`labels` 可覆盖用户可见 / 读屏会念到的文案。**
382
+
383
+ ```js
384
+ Pagination.create(node, { labels: { prev: 'Previous', next: 'Next' } });
385
+ Overlay.toast({ title: 'Saved', labels: { closeToast: 'Dismiss' } });
386
+ Tooltip.setLabels({ region: 'Tooltip' }); // 模块级(属性初始化,无逐实例 opts)
387
+ ```
388
+
389
+ - 值可以是字符串或函数(动态量用函数:`page(p, isCurrent)`、`uploading(pct)`)。
390
+ - **不传时输出与 0.25.x 逐字相同** —— 升级补丁版本不会换掉界面语言。
391
+ - `bar.draw` 是唯一例外:`labels` 已是「X 轴标签」这个**数据**参数,
392
+ 它的文案覆盖叫 `text`。
393
+ - 开发期错误信息(`throw new Error`)**不在覆盖范围**,它们不经过 UI。
394
+
395
+ 逐组件的键名、以及**已知仍未覆盖**的几处,见 `docs/L10N.md`。
396
+ 门禁:`05-audit/i18n-labels-gate.js`(静态禁硬编码 + 真浏览器验证覆盖生效、默认值不变)。
397
+
398
+ ### HTML 净化钩子
399
+
400
+ `popover` / `drawer` 的字符串 HTML 入口可以接净化器:
401
+
402
+ ```js
403
+ Popover.setSanitizer(function (html) { return DOMPurify.sanitize(html); });
404
+ Drawer.setSanitizer(function (html) { return DOMPurify.sanitize(html); });
405
+ ```
406
+
407
+ 默认**不改变行为**(仍按"调用方已净化"处理),理由与责任边界见 `SECURITY.md`。
408
+
409
+ ---
410
+
379
411
  ## 破坏性变更怎么走
380
412
 
381
413
  公开**名字**(令牌名 / 类名 / 全局与类型成员 / 事件名)的移除走**登记制**:
package/CHANGELOG.md CHANGED
@@ -16,6 +16,77 @@
16
16
 
17
17
  (无)
18
18
 
19
+ ## [0.26.0] - 2026-11-04
20
+
21
+ 这一版有两类改动:**修外部审计确认的缺陷**,以及**新增三个公开能力**
22
+ (文案覆盖、HTML 净化钩子、toast 上限)。升级不需要改代码 ——
23
+ 新增能力都有默认值,且默认值与 0.25.6 行为一致。
24
+
25
+ ### Added
26
+
27
+ - **文案本地化**:`labels` 可以覆盖用户可见 / 读屏会念到的文案。
28
+ 逐实例入口 `Overlay.toast({ labels })`、`Drawer.open({ labels })`、
29
+ `Pagination.create(node, { labels })`、`new Upload(root, { labels })`、
30
+ `Select/Combobox/Dropdown/Tree/DateRange.create(root, { labels })`;
31
+ 属性自动初始化的 `Tooltip` / `ModelViewer` 用模块级 `setLabels()`。
32
+ **不传时输出与 0.25.6 逐字相同**。`bar.draw` 的文案覆盖叫 `text`
33
+ (它的 `labels` 早就是 X 轴标签这个数据参数)。清单与已知剩余项见 `docs/L10N.md`。
34
+ - **HTML 净化钩子**:`Popover.setSanitizer(fn)` / `Drawer.setSanitizer(fn)`,
35
+ 也可逐次传 `sanitize`。默认**不改变行为**(仍按"调用方已净化"处理)——
36
+ 静默自动净化会让既有页面里的 `<b>` / `<svg>` 突然消失。边界见 `SECURITY.md`。
37
+ - **toast 同屏上限**:`Overlay.toast({ maxVisible })`,默认 4,`0` = 不限。
38
+ 超出时摘掉**最旧的**一条(此前无上限,20 条并发提示会把屏幕铺满)。
39
+ - `types/index.d.ts`:新增上列文案类型与 `ToastOptions` / `DialogOptions` /
40
+ `PopoverOptions` / `DrawerOpenOptions` / `UploadOptions` / `DropdownOptions` /
41
+ `NavOptions`;把 `ModelViewer` 从"构造函数"改正为 `{ update, refresh, setLabels }`
42
+ (按旧声明写 `new window.ModelViewer(el)` 会在运行时抛 TypeError)。
43
+
44
+ ### Fixed
45
+
46
+ - `longform` 配方页:`.prose > img` 是**直接子代**选择器,而 Markdown 渲染出的图片
47
+ 在 `<p>` 里 ⇒ `max-width:100%` 从未生效,600px 宽的图在 375px 视口横向溢出
48
+ 246px、320px 溢出 301px。改为后代选择器(`> a` 在 0.9 踩过同一个坑,这次同一个坑换了标签)。
49
+ - 可滚动的 `.table` / `.table-wrap` / `<pre>` / 图框在窄屏下**键盘进不去**
50
+ (WCAG 2.1.1)。补 `tabindex="0"` + `role="region"` + 「内容名(可横向滚动)」为名的
51
+ 可访问名;图类用 `aria-labelledby` 指向 `<figcaption>`,不手写第二份文案。
52
+ - 浏览器自动发现:Playwright 1.6x 的缓存目录名(`chrome-headless-shell-win64/` 等)
53
+ 与代码里的候选**一个都不匹配**,此前靠系统 Chrome 兜底才跑得通。改为按可执行文件名
54
+ 有界递归 + 读 `chromium.executablePath()`,并补 `%LOCALAPPDATA%` 路径。
55
+ - 三处判据把"文本里出现过的字样"当成"当前生效的现状":`contrast.py` 把注释里的旧色值
56
+ 当真值、把 `@media dark` 块之后的浅色定义卷进暗色范围;`wiring-check.py` 把
57
+ `<style>` 里的 CSS 选择器当接线;`delta-check.js` 把 `<pre>` 里的用法示例当成
58
+ 页面真的用了 `data-market`。三处都改成只看**会渲染 / 会执行**的结构。
59
+
60
+ ### Changed
61
+
62
+ - 开发依赖 `puppeteer-core` 23 → 25(`npm audit` 从 9 high 到 0),CI 增加
63
+ `npm audit --omit=dev --audit-level=high`。
64
+ - 根导出补显式 `import` 条件;README 讲清"运行时是 ESM、`engines>=18` 只代表语法可用,
65
+ `require()` 需要 Node ≥ 22.12"。
66
+
67
+ ### 新增 / 加强的门禁
68
+
69
+ - `i18n-labels`:静态禁止「中文字面量直接进 `aria-label` / `textContent` / `el()` 文本位」,
70
+ 并在真浏览器里传 `labels` 断言 DOM 真的变了、不传时仍是中文。
71
+ - `md-inline-gate`:`md-render.py` 生成的单文件产物会把 `tokens.css` / `content.css`
72
+ 整份内联成第二份副本,此前没有任何门禁守着它漂移(实测已落后 586 字符)。
73
+ - `chrome-discovery-gate`:七种 Playwright 目录布局 × 两个入口 + 空目录不回落 + 旧写法元反向。
74
+ - `a11y-scan` 增加 320px 的 `scrollable-region-focusable` 补扫 —— 一上线就抓出
75
+ `content` / `longform` / `analysis-report` / `scientific-plot` 四页真实违规。
76
+ - `zoom-reflow` 的页面清单改为从文件系统枚举 `04-recipes/**/*.html`(43 → 49 页)——
77
+ 上面那个 `.prose` 缺陷就活在不属于任何组件的配方页里。
78
+ - `gen-behavior` 的扫描范围补上 `09-assets`:`bar` 的注入块否则是空的,
79
+ `flLabels is not defined` 只在浏览器门禁才炸。
80
+
81
+ ### 关于外部审计报告里的三条
82
+
83
+ F8(demo 对比度)、F10(model-viewer 小溢出)、F12(缺 SemVer/废弃政策)经复核
84
+ **不成立**:`.bad-err` / `.btn--naive` 都在 `.cmp__bad` 豁免容器内;本机 320px
85
+ 实测溢出 0;`STABILITY.md` / `DEPRECATIONS.md` / `deprecation-gate` 早已存在。
86
+ 没有为"关闭报告项"去改这三处。
87
+
88
+ ---
89
+
19
90
  ## [0.25.6] - 2026-11-04
20
91
 
21
92
  这一版**没有改组件、令牌、CSS 或运行时 JS**。改的是「装到手里的包可不可信」:
package/README.md CHANGED
@@ -10,6 +10,23 @@ npm install agentcss
10
10
 
11
11
  The npm package is `agentcss`; the repository is `frontend-lib`. They are different things: the package name is what you install, the repository name is where the source lives.
12
12
 
13
+ ### Module format: ESM-only
14
+
15
+ The JS entry points (`esm/*.mjs`) are **ESM only** — `exports` has no `require` condition.
16
+
17
+ | You write | Node **≥ 20.19 / ≥ 22.12** | Node 18 – 20.18 |
18
+ |---|---|---|
19
+ | `import 'agentcss'` | ✅ | ✅ |
20
+ | `require('agentcss')` | ✅ (Node's built-in require(ESM)) | ❌ `ERR_REQUIRE_ESM` |
21
+
22
+ - On older runtimes use `await import('agentcss')`, or consume the CSS entry points only
23
+ (`agentcss/tokens.css` etc. are not JS and work on any Node version).
24
+ - The shipped `*.js` files under `02-primitives/**` and `03-patterns/**` are **classic
25
+ scripts** for `<script>` tags, not CommonJS modules — they attach to `window.*`.
26
+ - `"type": "commonjs"` is deliberate: it describes how those classic scripts are
27
+ *parsed*. Flipping it to `module` would make bundlers re-interpret them as ESM,
28
+ which is a breaking change.
29
+
13
30
  ## Docs and live demos
14
31
 
15
32
  - **Every demo page, one index** — https://owain323.github.io/frontend-lib/
package/README.zh-CN.md CHANGED
@@ -60,6 +60,22 @@ npm install agentcss
60
60
 
61
61
  装出来的目录名就是 `agentcss`,`dist-tags.latest` 指向当前版本。
62
62
 
63
+ ### 模块格式:ESM-only(请先读这一条)
64
+
65
+ `agentcss` 的 JS 入口(`esm/*.mjs`)**只有 ESM**,`exports` 里没有 `require` 条件。
66
+
67
+ | 你的写法 | Node **≥ 20.19 / ≥ 22.12** | Node 18 ~ 20.18 |
68
+ |---|---|---|
69
+ | `import 'agentcss'` | ✅ | ✅ |
70
+ | `require('agentcss')` | ✅(Node 自带的 require(ESM)) | ❌ `ERR_REQUIRE_ESM` |
71
+
72
+ - 走 `require` 的旧环境请改用 `await import('agentcss')`,或只用 CSS 入口
73
+ (`agentcss/tokens.css` 这类**不是 JS**,任何 Node 版本都能直接引用)。
74
+ - 包里的 `*.js`(`02-primitives/**`、`03-patterns/**`)是给 `<script>` 用的**经典脚本**,
75
+ 不是 CommonJS 模块 —— 它们挂 `window.*`,不需要 bundler。
76
+ - `type` 字段保持 `commonjs` 是**有意**的:它描述的是那批经典脚本的解析方式。
77
+ 把它改成 `module` 会让 bundler 用 ESM 语义重新解析它们,属于破坏性变更。
78
+
63
79
  另外两条**同样真实可用**的路(内容与 registry 上那一份同源):
64
80
 
65
81
  ```bash
package/SECURITY.md CHANGED
@@ -44,10 +44,22 @@
44
44
  下面两处会把字符串**当 HTML 解析**。它们是**有意的 API**,不是疏漏 ——
45
45
  `popover` 要能放任意块级内容,`drawer` 要能放整页内容,只收文本就废了。
46
46
 
47
- | 文件 | 入口 | 谁负责净化 |
48
- |---|---|---|
49
- | `02-primitives/popover/popover.js` | `opts.html` | 调用方 |
50
- | `03-patterns/drawer/drawer.js` | `opt.content` | 调用方 |
47
+ | 文件 | 入口 | 净化钩子 | 谁负责净化 |
48
+ |---|---|---|---|
49
+ | `02-primitives/popover/popover.js` | `opts.html` | `opts.sanitize` / `Popover.setSanitizer(fn)` | 调用方(或接钩子) |
50
+ | `03-patterns/drawer/drawer.js` | `opt.content`(字符串) | `opt.sanitize` / `Drawer.setSanitizer(fn)` | 调用方(或接钩子) |
51
+
52
+ **0.26.0 起有钩子,但默认不改变行为**:不传 `sanitize`、也不调 `setSanitizer()`
53
+ 时,两处仍然直接把字符串当 HTML 解析 —— 与 0.25.x 完全一致(静默"自动净化"
54
+ 会让既有页面里的 `<b>` / `<svg>` 突然消失,那也是一种破坏)。
55
+ 接 DOMPurify 只需一行:
56
+
57
+ ```js
58
+ Popover.setSanitizer(function (html) { return DOMPurify.sanitize(html); });
59
+ Drawer.setSanitizer(function (html) { return DOMPurify.sanitize(html); });
60
+ ```
61
+
62
+ 逐次覆盖优先于模块级:`Popover.attach(el, { html: s, sanitize: fn })`。
51
63
 
52
64
  **为什么本库不做净化**:净化需要一个 HTML 解析器或一份允许列表实现,而本库的目标是
53
65
  零运行时依赖 + Chromium 97 可用。给一个"看起来安全"的半吊子净化,比明确说
package/START-HERE.md CHANGED
@@ -39,39 +39,39 @@
39
39
  | 标签 / 徽章 | `02-primitives/badge/badge.css` | 141 | — | 有状态标记就要("已披露""停牌") |
40
40
  | 卡片 | `02-primitives/card/card.css` | 283 | — | 有分组内容就要 |
41
41
  | 单选 / 多选 | `02-primitives/choice/choice.css` | 434 | — | 单选 / 多选就要 |
42
- | 下拉选择 | `02-primitives/select/select.css` | 151 | **必需 631** (`select.js`) | 原生 select 的外观或交互不够用就要 |
43
- | 可搜索选择 | `02-primitives/combobox/combobox.css` | 223 | **必需 480** (`combobox.js`) | 选项多到要搜索,或要选多个并留成标签就要 |
42
+ | 下拉选择 | `02-primitives/select/select.css` | 151 | **必需 652** (`select.js`) | 原生 select 的外观或交互不够用就要 |
43
+ | 可搜索选择 | `02-primitives/combobox/combobox.css` | 223 | **必需 503** (`combobox.js`) | 选项多到要搜索,或要选多个并留成标签就要 |
44
44
  | 开关 | `02-primitives/switch/switch.css` | 429 | — | 有"立即生效的设置"就要 |
45
45
  | 分隔线 | `02-primitives/separator/separator.css` | 73 | — | 要分开内容块就要 |
46
46
  | 加载骨架 | `02-primitives/skeleton/skeleton.css` | 114 | — | 数据还没到、先把版式占住就要 |
47
47
  | 进度条 / 环 | `02-primitives/progress/progress.css` | 228 | — | 有耗时过程要给进度就要 |
48
- | 浮层气泡 | `02-primitives/popover/popover.css` | 187 | **必需 325** (`popover.js`) | 要一段信息贴着触发元素浮出来就要 |
49
- | 日期区间 | `02-primitives/date-range/date-range.css` | 206 | **必需 310** (`date-range.js`) | 要选起止日期就要 |
48
+ | 浮层气泡 | `02-primitives/popover/popover.css` | 187 | **必需 345** (`popover.js`) | 要一段信息贴着触发元素浮出来就要 |
49
+ | 日期区间 | `02-primitives/date-range/date-range.css` | 206 | **必需 332** (`date-range.js`) | 要选起止日期就要 |
50
50
  | 标签页 | `03-patterns/tabs/tabs.css` | 172 | **必需 316** (`tabs.js`) | 同层级内容切换就要 |
51
51
  | 折叠面板 | `03-patterns/accordion/accordion.css` | 453 | **必需 389** (`accordion.js`) | 长表单分段就要 |
52
- | 动作菜单 | `03-patterns/dropdown/dropdown.css` | 135 | **必需 555** (`dropdown.js`) | 要一组动作(新建 / 导出 / 删除)就要 |
53
- | 文字提示 | `03-patterns/tooltip/tooltip.css` | 89 | **必需 284** (`tooltip.js`) | 图标按钮或缩写需要一句话解释就要 |
54
- | 分页 | `03-patterns/pagination/pagination.css` | 195 | **必需 401** (`pagination.js`) | 数据多到要翻页就要 |
55
- | 树形 | `03-patterns/tree/tree.css` | 122 | **必需 464** (`tree.js`) | 层级数据(文件 / 组织 / 分类)要展开就要 |
56
- | 侧边抽屉 | `03-patterns/drawer/drawer.css` | 145 | **必需 378** (`drawer.js`) | 要从侧边滑出面板就要 |
57
- | 弹窗 / 吐司 | `03-patterns/overlay/overlay.css` | 338 | **必需 697** (`overlay.js`) | 需要弹层(吐司 / 模态)就要 |
52
+ | 动作菜单 | `03-patterns/dropdown/dropdown.css` | 135 | **必需 575** (`dropdown.js`) | 要一组动作(新建 / 导出 / 删除)就要 |
53
+ | 文字提示 | `03-patterns/tooltip/tooltip.css` | 89 | **必需 299** (`tooltip.js`) | 图标按钮或缩写需要一句话解释就要 |
54
+ | 分页 | `03-patterns/pagination/pagination.css` | 195 | **必需 428** (`pagination.js`) | 数据多到要翻页就要 |
55
+ | 树形 | `03-patterns/tree/tree.css` | 122 | **必需 484** (`tree.js`) | 层级数据(文件 / 组织 / 分类)要展开就要 |
56
+ | 侧边抽屉 | `03-patterns/drawer/drawer.css` | 145 | **必需 416** (`drawer.js`) | 要从侧边滑出面板就要 |
57
+ | 弹窗 / 吐司 | `03-patterns/overlay/overlay.css` | 338 | **必需 734** (`overlay.js`) | 需要弹层(吐司 / 模态)就要 |
58
58
  | 状态(空 / 加载 / 错 / 成功) | `03-patterns/states/states.css` | 269 | — | **凡是会异步取数就要** |
59
59
  | 表单校验 | `03-patterns/form-validation/form-validation.css` | 274 | — | 提交前要校验就要 |
60
60
  | 列表增删 | `03-patterns/list/list.css` | 162 | 可选 201 (`flip.js`,FLIP 增删动画,纯增强) | 列表会动态增删就要 |
61
61
  | 导航 / 目录 | `03-patterns/nav/nav.css` | 314 | **必需 425** (`nav.js`) · 可选 111 (`toc.js`,自动生成目录,纯增强) | 顶部要导航,或长文要目录就要 |
62
- | 长文排版 | `03-patterns/content/content.css` | 286 | — | 写文档 / 长文就要 |
62
+ | 长文排版 | `03-patterns/content/content.css` | 298 | — | 写文档 / 长文就要 |
63
63
  | delta | `02-primitives/delta/delta.css` | 113 | — | |
64
64
  | metric | `02-primitives/metric/metric.css` | 157 | — | |
65
65
  | timeline | `02-primitives/timeline/timeline.css` | 152 | — | 有一串按时间排的事件(公告 / 大事记 / 版本记录)就要 |
66
- | upload | `02-primitives/upload/upload.css` | 490 | 可选 676 (`upload.js`,拖放 / 文件列表 / 校验;没有它基础形态照常工作) | |
66
+ | upload | `02-primitives/upload/upload.css` | 490 | 可选 709 (`upload.js`,拖放 / 文件列表 / 校验;没有它基础形态照常工作) | |
67
67
  | reveal | `03-patterns/reveal/reveal.css` | 93 | 可选 175 (`reveal.js`,入场动效,纯增强;没有它内容直接可见) | |
68
68
  | analysis-report | `04-recipes/analysis-report/analysis-report.css` | 226 | — | |
69
69
  | app-shell | `04-recipes/app-shell/app-shell.css` | 588 | **必需 289** (`shell-nav.js`) | |
70
70
  | detail-page | `04-recipes/detail-page/detail-page.css` | 190 | — | |
71
71
  | report | `04-recipes/report/report.css` | 369 | — | |
72
72
  | table | `04-recipes/table/table.css` | 409 | — | |
73
- | bar | `09-assets/bar/bar.css` | 181 | **必需 324** (`bar.js`) | |
74
- | model-viewer | `09-assets/model-viewer/model-viewer.css` | 143 | **必需 432** (`model-viewer.js`) | |
73
+ | bar | `09-assets/bar/bar.css` | 181 | **必需 348** (`bar.js`) | |
74
+ | model-viewer | `09-assets/model-viewer/model-viewer.css` | 143 | **必需 464** (`model-viewer.js`) | |
75
75
  | scientific-plot | `09-assets/scientific-plot/scientific-plot.css` | 141 | **必需 604** (`scientific-plot.js`) | |
76
76
  | sparkline | `09-assets/sparkline/sparkline.css` | 158 | **必需 508** (`sparkline.js`) | |
77
77
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
3
  "generatedBy": "05-audit/gen-ai-contract.py",
4
- "libraryVersion": "0.25.6",
4
+ "libraryVersion": "0.26.0",
5
5
  "$comment": "机器可读的组件契约。slots = BEM 元素名(在本库里元素名就是插槽名),states.enum = 组件会在 DOM 上写出的 data-* 取值,states.legacyClass = 仍未收敛到 data 属性的历史状态类(INVARIANT I-8 未达标)。printPolicy = 这个组件在纸上会发生什么(空数组 = 原样保留);pagination = 内容是否被分页裁过(server 不许与 expand 同时出现,否则展开的只是当前一页)。",
6
6
  "maturityCriteria": {
7
7
  "alpha": "缺 demo.html 或缺 05-audit 下的专属门禁 ⇒ 还没被机器验证过,不要用",
@@ -249,11 +249,13 @@
249
249
  },
250
250
  "aria": {
251
251
  "roles": [
252
- "img"
252
+ "img",
253
+ "region"
253
254
  ],
254
255
  "attrs": [
255
256
  "hidden",
256
- "label"
257
+ "label",
258
+ "labelledby"
257
259
  ]
258
260
  },
259
261
  "usesOtherNamespaces": [],
@@ -2411,7 +2413,9 @@
2411
2413
  "legacyClass": []
2412
2414
  },
2413
2415
  "aria": {
2414
- "roles": [],
2416
+ "roles": [
2417
+ "region"
2418
+ ],
2415
2419
  "attrs": [
2416
2420
  "hidden",
2417
2421
  "label"
@@ -2475,6 +2479,7 @@
2475
2479
  "aria": {
2476
2480
  "roles": [
2477
2481
  "alert",
2482
+ "region",
2478
2483
  "status"
2479
2484
  ],
2480
2485
  "attrs": [