agentcss 0.11.14 → 0.11.16

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 (67) hide show
  1. package/02-primitives/combobox/combobox.js +1 -1
  2. package/02-primitives/date-range/date-range.js +1 -1
  3. package/02-primitives/popover/demo.html +1 -1
  4. package/02-primitives/popover/popover.js +1 -1
  5. package/02-primitives/select/select.js +1 -1
  6. package/03-patterns/accordion/accordion.js +25 -5
  7. package/03-patterns/drawer/drawer.js +1 -1
  8. package/03-patterns/dropdown/dropdown.js +1 -1
  9. package/03-patterns/list/flip.js +1 -1
  10. package/03-patterns/nav/nav.js +6 -3
  11. package/03-patterns/nav/toc.js +1 -1
  12. package/03-patterns/overlay/overlay.js +1 -1
  13. package/03-patterns/pagination/demo.html +1 -1
  14. package/03-patterns/pagination/pagination.js +9 -3
  15. package/03-patterns/reveal/reveal.js +4 -1
  16. package/03-patterns/tabs/tabs.js +12 -3
  17. package/03-patterns/tooltip/tooltip.js +1 -1
  18. package/03-patterns/tree/tree.js +1 -1
  19. package/04-recipes/report/demo.html +2 -2
  20. package/09-assets/echarts-adapter/demo.html +5 -5
  21. package/09-assets/model-viewer/demo.html +2 -2
  22. package/API.md +37 -3
  23. package/CHANGELOG.md +221 -0
  24. package/CONTRIBUTING.md +2 -1
  25. package/README.zh-CN.md +1 -1
  26. package/START-HERE.md +5 -5
  27. package/ai/components.json +8 -4
  28. package/ai/deprecations.schema.json +2 -2
  29. package/dist/02-primitives/combobox/combobox.js +1 -1
  30. package/dist/02-primitives/date-range/date-range.js +1 -1
  31. package/dist/02-primitives/popover/popover.js +1 -1
  32. package/dist/02-primitives/select/select.js +1 -1
  33. package/dist/03-patterns/accordion/accordion.js +10 -4
  34. package/dist/03-patterns/drawer/drawer.js +1 -1
  35. package/dist/03-patterns/dropdown/dropdown.js +1 -1
  36. package/dist/03-patterns/list/flip.js +1 -1
  37. package/dist/03-patterns/nav/nav.js +4 -3
  38. package/dist/03-patterns/nav/toc.js +1 -1
  39. package/dist/03-patterns/overlay/overlay.js +1 -1
  40. package/dist/03-patterns/pagination/pagination.js +4 -3
  41. package/dist/03-patterns/reveal/reveal.js +2 -1
  42. package/dist/03-patterns/tabs/tabs.js +8 -3
  43. package/dist/03-patterns/tooltip/tooltip.js +1 -1
  44. package/dist/03-patterns/tree/tree.js +1 -1
  45. package/dist/COSTS.md +10 -10
  46. package/docs/ACCESSIBILITY.md +3 -3
  47. package/docs/DEPRECATIONS.md +128 -0
  48. package/esm/accordion.mjs +7 -0
  49. package/esm/combobox.mjs +7 -0
  50. package/esm/date-range.mjs +7 -0
  51. package/esm/drawer.mjs +7 -0
  52. package/esm/dropdown.mjs +7 -0
  53. package/esm/flip.mjs +7 -0
  54. package/esm/index.mjs +38 -0
  55. package/esm/nav.mjs +7 -0
  56. package/esm/overlay.mjs +7 -0
  57. package/esm/pagination.mjs +7 -0
  58. package/esm/popover.mjs +7 -0
  59. package/esm/reveal.mjs +7 -0
  60. package/esm/select.mjs +7 -0
  61. package/esm/tabs.mjs +7 -0
  62. package/esm/toc.mjs +7 -0
  63. package/esm/tooltip.mjs +7 -0
  64. package/esm/tree.mjs +7 -0
  65. package/index.html +1 -1
  66. package/package.json +10 -3
  67. package/types/index.d.ts +131 -1
@@ -482,4 +482,4 @@
482
482
  }
483
483
 
484
484
  global.Combobox = { create: create };
485
- })(window);
485
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -298,4 +298,4 @@
298
298
  iso: iso, addDays: addDays, addMonths: addMonths,
299
299
  quarterOf: quarterOf, quarterRange: quarterRange,
300
300
  };
301
- })(window);
301
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -78,7 +78,7 @@ th { font-weight: 600; }
78
78
 
79
79
  <h2>怎么选?</h2>
80
80
  <div class="demo">
81
- <table>
81
+ <table aria-label="tooltip / popover / dialog 三者的选择依据">
82
82
  <tr><th>组件</th><th>能放交互内容</th><th>背景是否锁死</th><th>用在哪</th></tr>
83
83
  <tr><td><code>tooltip</code></td><td>❌ 只能纯文字</td><td>—</td><td>补充说明、术语解释</td></tr>
84
84
  <tr><td><b><code>popover</code></b></td><td>✅</td><td><b>不锁</b></td><td>筛选、快捷设置、补充表单</td></tr>
@@ -328,4 +328,4 @@
328
328
  };
329
329
 
330
330
  global.Popover = Popover;
331
- })(window);
331
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -546,4 +546,4 @@
546
546
  }
547
547
 
548
548
  global.Select = { create: create };
549
- })(window);
549
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -28,7 +28,7 @@
28
28
  * 否则造成 "landmark region proliferation"(地标泛滥)。
29
29
  * ⇒ 加 .accordion--many 即可关闭。
30
30
  */
31
- (function () {
31
+ (function (global) {
32
32
  'use strict';
33
33
  // 🔴 手风琴实例序号(保证 id 全局唯一,见 init 里的说明)
34
34
  var uid = 0;
@@ -60,8 +60,13 @@
60
60
 
61
61
  function toArray(x) { return Array.prototype.slice.call(x); }
62
62
 
63
- /* requestAnimationFrame 的 ES5 安全版 */
64
- var raf = (typeof window.requestAnimationFrame === 'function')
63
+ /* requestAnimationFrame 的 ES5 安全版
64
+ ⚠️ 必须先判 `window` 存在再摸 `.requestAnimationFrame`:
65
+ `typeof window.requestAnimationFrame` 会**先解引用 window**,
66
+ 在 Node / SSR 里直接 ReferenceError(不是返回 'undefined')。
67
+ 这条曾经让本文件在 Next.js 里 import 即崩。 */
68
+ var raf = (typeof window !== 'undefined' &&
69
+ typeof window.requestAnimationFrame === 'function')
65
70
  ? window.requestAnimationFrame.bind(window)
66
71
  : function (fn) { return setTimeout(fn, 16); };
67
72
 
@@ -85,6 +90,12 @@
85
90
  }
86
91
 
87
92
  function Accordion(root, opts) {
93
+ /* 🔴 同一 root 只允许一个实例。
94
+ 不加这条时 `new Accordion(root)` 两次会留下**两份** click 监听
95
+ (旧实例不会被回收:它的监听器闭包referencing this,还挂在 root 上)
96
+ ⇒ 一次点击 toggle 两次,肉眼看到的是"点了没反应"。
97
+ 构造器返回对象是 JS 合法行为,会覆盖 `this`,调用方拿到的就是旧实例。 */
98
+ if (root && root.accordionInstance) return root.accordionInstance;
88
99
  this.root = root;
89
100
  this.opts = opts || {};
90
101
  this.items = toArray(root.querySelectorAll('.accordion__item'));
@@ -351,19 +362,28 @@
351
362
  // 这是「用原生控件」的又一处收益:**无障碍行为免费且必然正确**。
352
363
  };
353
364
 
354
- window.Accordion = Accordion;
365
+ global.Accordion = Accordion;
355
366
 
356
367
  function init() {
357
368
  var nodes = document.querySelectorAll('[data-accordion]');
358
369
  for (var i = 0; i < nodes.length; i++) {
370
+ /* 🔴 幂等:已经初始化过就跳过。
371
+ 不加守卫的后果是**静默的**:第二次 new 会把 accordionInstance 覆盖成新实例,
372
+ 而**旧实例挂在 root 上的 click 监听还在** ⇒ 一次点击 toggle 两次
373
+ (展开又收起,看上去"点了没反应")。
374
+ SPA 路由切换、HMR、重复引入同一份脚本都会触发这条路。 */
375
+ if (nodes[i].accordionInstance) continue;
359
376
  nodes[i].accordionInstance = new Accordion(nodes[i]);
360
377
  }
361
378
  }
362
379
 
363
380
  // @autoinit: [data-accordion] —— 自动初始化;与 API.md「自动初始化」表由 autoinit-gate 双向校验
381
+ // ⚠️ 无 DOM 环境(SSR / 构建期 / Node 里 import)不自动初始化。
382
+ // 之前这里直接摸 document ⇒ Next.js 等 SSR 里 import 即 ReferenceError。
383
+ if (typeof document === 'undefined') return;
364
384
  if (document.readyState === 'loading') {
365
385
  document.addEventListener('DOMContentLoaded', init);
366
386
  } else {
367
387
  init();
368
388
  }
369
- })();
389
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -255,4 +255,4 @@
255
255
  }
256
256
 
257
257
  global.Drawer = { open: open };
258
- })(window);
258
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -448,4 +448,4 @@
448
448
  }
449
449
 
450
450
  global.Dropdown = { create: create };
451
- })(window);
451
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -198,4 +198,4 @@
198
198
 
199
199
  global.FLIP = { flip: flip, remove: removeWithAnimation };
200
200
 
201
- })(window);
201
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -59,7 +59,7 @@
59
59
  * 手动:new Nav(rootEl, { labels: { open: '打开菜单', close: '关闭菜单' } })
60
60
  * 事件:fl-nav-open / fl-nav-close(detail = { root: <nav> })
61
61
  */
62
- (function () {
62
+ (function (global) {
63
63
  'use strict';
64
64
 
65
65
  /* ==== BEHAVIOR INJECT BEGIN: emit ==== */
@@ -413,7 +413,7 @@
413
413
  return root.navInstance;
414
414
  };
415
415
 
416
- window.Nav = Nav;
416
+ global.Nav = Nav;
417
417
 
418
418
  function init() {
419
419
  each(document.querySelectorAll('[data-nav]'), function (el) {
@@ -422,9 +422,12 @@
422
422
  }
423
423
 
424
424
  // @autoinit: [data-nav] —— 扫属性自动建实例(详见 README「自动初始化」)
425
+ // ⚠️ 无 DOM 环境(SSR / 构建期 / Node 里 import)不自动初始化。
426
+ // 之前这里直接摸 document ⇒ Next.js 等 SSR 里 import 即 ReferenceError。
427
+ if (typeof document === 'undefined') return;
425
428
  if (document.readyState === 'loading') {
426
429
  document.addEventListener('DOMContentLoaded', init);
427
430
  } else {
428
431
  init();
429
432
  }
430
- })();
433
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -108,4 +108,4 @@
108
108
 
109
109
  global.Toc = { init: init };
110
110
 
111
- })(window);
111
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -560,4 +560,4 @@
560
560
 
561
561
  global.Overlay = { toast: toast, dialog: dialog };
562
562
 
563
- })(window);
563
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -69,7 +69,7 @@ th { font-size: var(--fs-xs); color: var(--text-secondary); font-weight: 500; }
69
69
  </div>
70
70
 
71
71
  <h2 class="rule-top">2 · 结构:六条硬要求</h2>
72
- <table>
72
+ <table aria-label="分页容器的六条结构要求与理由">
73
73
  <tr><th>要求</th><th>为什么</th></tr>
74
74
  <tr><td>包在 <code>&lt;nav aria-label&gt;</code> 里</td>
75
75
  <td>页面常有多个 nav(主导航/面包屑/分页),
@@ -35,7 +35,7 @@
35
35
  * });
36
36
  * </script>
37
37
  */
38
- (function () {
38
+ (function (global) {
39
39
  'use strict';
40
40
 
41
41
  /* ==== BEHAVIOR INJECT BEGIN: emit ==== */
@@ -385,11 +385,17 @@
385
385
  },
386
386
  };
387
387
 
388
- if (typeof window !== 'undefined') window.Pagination = API;
388
+ /* global 已在 IIFE 收尾处解析成 window 或 globalThis(无 DOM 环境),
389
+ 所以这里**无条件挂**——再套一层 `typeof window !== 'undefined'` 会让
390
+ SSR 里 require 得到 undefined(`esm/` 包装就取不到 API)。 */
391
+ global.Pagination = API;
389
392
  // @autoinit: [data-pagination] —— 扫属性自动 update()(详见 API.md「自动初始化」)
393
+ // ⚠️ 无 DOM 环境(SSR / 构建期 / Node 里 import)不自动初始化。
394
+ // 之前这里直接摸 document ⇒ Next.js 等 SSR 里 import 即 ReferenceError。
395
+ if (typeof document === 'undefined') return;
390
396
  if (document.readyState === 'loading') {
391
397
  document.addEventListener('DOMContentLoaded', function () { API.update(); });
392
398
  } else {
393
399
  API.update();
394
400
  }
395
- })();
401
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -164,9 +164,12 @@
164
164
  }
165
165
 
166
166
  // @autoinit: conditional[data-reveal-auto] —— 只有 <html data-reveal-auto> 才 enable();不写则完全不动
167
+ // ⚠️ 无 DOM 环境(SSR / 构建期 / Node 里 import)不自动初始化。
168
+ // 之前这里直接摸 document ⇒ Next.js 等 SSR 里 import 即 ReferenceError。
169
+ if (typeof document === 'undefined') return;
167
170
  if (document.readyState === 'loading') {
168
171
  document.addEventListener('DOMContentLoaded', auto, false);
169
172
  } else {
170
173
  auto();
171
174
  }
172
- }(this));
175
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -26,7 +26,7 @@
26
26
  * 只在初始化时设一次是最常见的 bug:用户激活第 3 个 tab 后,
27
27
  * Tab 键还是会回到第 1 个。
28
28
  */
29
- (function () {
29
+ (function (global) {
30
30
  'use strict';
31
31
 
32
32
  /* ==== BEHAVIOR INJECT BEGIN: emit ==== */
@@ -165,6 +165,9 @@
165
165
  }
166
166
 
167
167
  function Tabs(root, opts) {
168
+ /* 🔴 同一 root 只允许一个实例 —— 理由同 accordion.js。
169
+ 重复构造会让 keydown 监听叠两份:方向键按一次跳两格。 */
170
+ if (root && root.tabsInstance) return root.tabsInstance;
168
171
  this.root = root;
169
172
  this.opts = opts || {};
170
173
  this.list = root.querySelector('[role="tablist"]');
@@ -281,7 +284,7 @@
281
284
  };
282
285
 
283
286
  // 暴露
284
- window.Tabs = Tabs;
287
+ global.Tabs = Tabs;
285
288
 
286
289
  // 自动初始化:<div class="tabs" data-tabs>
287
290
  //
@@ -294,14 +297,20 @@
294
297
  function init() {
295
298
  var nodes = document.querySelectorAll('[data-tabs]');
296
299
  for (var i = 0; i < nodes.length; i++) {
300
+ /* 🔴 幂等:同 accordion.js 里的理由。重复 new 会让 keydown / click
301
+ 监听叠在同一个 root 上 ⇒ 方向键按一次跳两格。 */
302
+ if (nodes[i].tabsInstance) continue;
297
303
  nodes[i].tabsInstance = new Tabs(nodes[i], {});
298
304
  }
299
305
  }
300
306
 
301
307
  // @autoinit: [data-tabs] —— 扫属性自动建实例(详见 API.md「自动初始化」)
308
+ // ⚠️ 无 DOM 环境(SSR / 构建期 / Node 里 import)不自动初始化。
309
+ // 之前这里直接摸 document ⇒ Next.js 等 SSR 里 import 即 ReferenceError。
310
+ if (typeof document === 'undefined') return;
302
311
  if (document.readyState === 'loading') {
303
312
  document.addEventListener('DOMContentLoaded', init);
304
313
  } else {
305
314
  init();
306
315
  }
307
- })();
316
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -285,4 +285,4 @@ var flDismissable = function (opts) {
285
285
  }
286
286
 
287
287
  global.Tooltip = { attach: attach };
288
- })(window);
288
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -461,4 +461,4 @@
461
461
  }
462
462
 
463
463
  global.Tree = { create: create };
464
- })(window);
464
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -181,7 +181,7 @@
181
181
  写死数字的话,插入一张新图后面全会错位。
182
182
  同一句标记放在 financial 版里不会出编号 —— 那是 profile 决定的,不是标记决定的。 -->
183
183
  <figure class="rep__fig">
184
- <div class="table"><table>
184
+ <div class="table"><table aria-label="关键读数:包覆方案与保持率">
185
185
  <thead><tr>
186
186
  <th scope="col">方案</th>
187
187
  <th scope="col">保持率</th>
@@ -248,7 +248,7 @@
248
248
  <h3>关键假设</h3>
249
249
  <!-- 同一句标记放在 finance 版:没有编号,因为 financial profile 不开 counter -->
250
250
  <figure class="rep__fig">
251
- <div class="table"><table>
251
+ <div class="table"><table aria-label="关键假设:三种情形下的良率与毛利率">
252
252
  <thead><tr>
253
253
  <th scope="col">情形</th>
254
254
  <th scope="col">良率</th>
@@ -44,7 +44,7 @@ td.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabu
44
44
  </p>
45
45
 
46
46
  <h2 class="rule-top">0 · 边界:什么该用本库,什么该用 ECharts</h2>
47
- <table>
47
+ <table aria-label="能力边界:什么场景用本库、什么用 ECharts">
48
48
  <tr><th>场景</th><th>用什么</th><th>谁维护</th></tr>
49
49
  <tr><td>列表右侧的<strong>迷你趋势</strong>(看走势)</td>
50
50
  <td class="ok">本库 <code>sparkline</code>(1.5KB,零依赖)</td><td>我们</td></tr>
@@ -94,7 +94,7 @@ td.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabu
94
94
  <div class="flex">
95
95
  <div class="box">
96
96
  <h3 style="margin-top:0">✅ 规范:柱状图(分类轴从 0)</h3>
97
- <table>
97
+ <table aria-label="示例数据:柱状图的分季度营收">
98
98
  <tr><th>季度</th><th class="num">营收(亿)</th><th class="num">同比</th></tr>
99
99
  <tr><td>Q1</td><td class="num">128.4</td><td class="num up">+12.4%</td></tr>
100
100
  <tr><td>Q2</td><td class="num">136.1</td><td class="num up">+8.1%</td></tr>
@@ -109,7 +109,7 @@ td.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabu
109
109
 
110
110
  <div class="box">
111
111
  <h3 style="margin-top:0">✅ 规范:多系列(≤ 5 条)</h3>
112
- <table>
112
+ <table aria-label="示例数据:A / B / C 三支股票的逐年对比">
113
113
  <tr><th>年份</th>
114
114
  <th class="num"><span class="swatch" style="background:var(--accent)"></span>A 股</th>
115
115
  <th class="num"><span class="swatch" style="background:var(--danger)"></span>B 股</th>
@@ -133,7 +133,7 @@ td.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabu
133
133
  </div>
134
134
 
135
135
  <h2 class="rule-top">3 · 数字格式(已经在用)</h2>
136
- <table>
136
+ <table aria-label="数字格式规则与例子">
137
137
  <tr><th>类型</th><th>规则</th><th>例子</th></tr>
138
138
  <tr><td>千分位</td><td>每三位加逗号</td><td class="num">1,234,567</td></tr>
139
139
  <tr><td>百分比</td><td>固定 1–2 位小数</td><td class="num">42.6%</td></tr>
@@ -148,7 +148,7 @@ td.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabu
148
148
 
149
149
  <h2 class="rule-top">4 · 明确禁止(这些是"看起来专业但会骗人"的)</h2>
150
150
  <div class="box bad" data-claim-skip="这一块是「不要做」清单本身(一张表),不是某个组件的错写法 —— 要给它配正例栏,得先真的渲染一张 3D 图与一张 2D 图做对照,而本页没有图表实例可摆;表格尺寸量出来的是排版,不是观点">
151
- <table>
151
+ <table aria-label="明确禁止的做法与原因">
152
152
  <tr><th>不要做</th><th>为什么</th></tr>
153
153
  <tr><td class="no">3D 图表</td><td>透视扭曲会<b>误导读数</b>(Tufte 的核心结论)</td></tr>
154
154
  <tr><td class="no">双 Y 轴</td><td>两轴的交叉点可以被随意搬动 ⇒ <b>视觉在说谎</b></td></tr>
@@ -59,7 +59,7 @@ td.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabu
59
59
  data-mv-orbit="vendor/OrbitControls.js"></div>
60
60
 
61
61
  <h2 class="rule-top">为什么是这样设计的</h2>
62
- <table>
62
+ <table aria-label="3D 模型页的设计要求与做法">
63
63
  <tr><th>要求</th><th>做法</th></tr>
64
64
  <tr><td>首屏要快</td>
65
65
  <td>3D 引擎<strong>点击后</strong>才 <code>&lt;script&gt;</code> 注入,
@@ -85,7 +85,7 @@ td.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabu
85
85
  </ul>
86
86
 
87
87
  <h2 class="rule-top">体积现状</h2>
88
- <table>
88
+ <table aria-label="各文件的体积与加载时机">
89
89
  <tr><th>文件</th><th class="num">体积</th><th>何时加载</th></tr>
90
90
  <tr><td><code>model-viewer.js</code>(本库)</td><td class="num">~9 KB</td><td><span class="ok">首屏</span></td></tr>
91
91
  <tr><td><code>models/fox.glb</code></td><td class="num">159 KB</td><td>点击后(可 XHR 显示进度)</td></tr>
package/API.md CHANGED
@@ -24,6 +24,30 @@
24
24
  各有各的调用方式。下表由`05-audit/global-api-probe.js`在真实浏览器里
25
25
  探测生成,门禁 `api-doc` 会核对本文与实现是否一致。
26
26
 
27
+ ### 模块化引入(ESM)
28
+
29
+ 除了 `<script src>`,16 个交互组件的脚本还有一个 ESM 入口:
30
+
31
+ ```js
32
+ // 只要一个
33
+ import { Accordion } from 'agentcss/esm/accordion.mjs';
34
+
35
+ // 或者一次拿全(体积更大,Vite/Rollup 会按需摇树)
36
+ import { Accordion, Tabs, Select } from 'agentcss/esm';
37
+ ```
38
+
39
+ 三条容易踩的:
40
+
41
+ 1. **入口是包装,不是第二份实现**。`esm/*.mjs` 只做
42
+ 「导入副作用 + 把全局上那个对象 re-export」,所以
43
+ `Accordion === window.Accordion` 恒成立(门禁 `behavior-api` 断言这一点)。
44
+ 它由 `05-audit/gen-esm.py` 生成,改源码后要重新生成,`esm-gen` 门禁会拦住漂移。
45
+ 2. **无 DOM 环境可以直接 import**(SSR 安全)。但自动初始化会被跳过 ——
46
+ 服务端没有 DOM 可扫,到浏览器里再调 `init()`/构造函数。16 个脚本
47
+ 在 Node 里 `require` 全部成功(门禁的判据 A)。
48
+ 3. **重复初始化是安全的**。`init()` 与构造函数都做了幂等处理:
49
+ 同一 root 只会有一个实例,不会出现「点一次切换两次」。
50
+
27
51
  ### 四种接入形态
28
52
 
29
53
  | 形态 | 调用方式 | 适合 |
@@ -327,6 +351,16 @@ python3 05-audit/prefix-build.py --prefix fl
327
351
 
328
352
  ## 破坏性变更怎么走
329
353
 
330
- - 只在**大版本**移除或重命名公开类名、`data-*`、方法
331
- - 弃用项先加警告并保留一个完整版本周期
332
- - 变更记录写在 `CHANGELOG.md`
354
+ 公开**名字**(令牌名 / 类名 / 全局与类型成员 / 事件名)的移除走**登记制**:
355
+
356
+ - 先在 `ai/deprecations.json` 里登记,写清 `deprecatedIn`、`removeIn`、`reason`
357
+ - 迁移窗口**至少跨一个次版本**:`0.7.x` 里标弃用 ⇒ 最早 `0.8.0` 才允许消失
358
+ - 到期才删。**提前删**和**到期不删**都会被门禁拦下(`05-audit/deprecation-gate.py`)
359
+ - 对外说明在 `docs/DEPRECATIONS.md`,那份文档与登记册由门禁**双向**比对,
360
+ 漏告知(登记了没写进文档)与假承诺(文档写了、登记册没有)都会红
361
+
362
+ > 🔴 本节此前写的是「只在**大版本**移除」「弃用项先加警告并保留一个完整版本周期」——
363
+ > 两句都是错的:本库在 `0.7.x → 0.8.0` 就删过名字(`state--delayed`),
364
+ > 而且组件脚本从不打印运行时警告(移动端 WebView 里那是纯噪音)。
365
+ > `0.x` 期间按 semver 本就允许破坏性变更,把话说满比说清更伤人。
366
+ > 现在这两句被上面四条取代,覆盖范围与抓不到哪些名字见 `docs/DEPRECATIONS.md`。