agentcss 0.0.0-stage → 0.11.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.
Files changed (274) hide show
  1. package/01-tokens/README.md +236 -0
  2. package/01-tokens/behavior/dismissable.js +132 -0
  3. package/01-tokens/behavior/emit.js +76 -0
  4. package/01-tokens/behavior/focus-return.js +81 -0
  5. package/01-tokens/behavior/roving.js +152 -0
  6. package/01-tokens/behavior/typeahead.js +124 -0
  7. package/01-tokens/focus-ring-demo.html +133 -0
  8. package/01-tokens/focus-ring.css +102 -0
  9. package/01-tokens/page.css +244 -0
  10. package/01-tokens/polyfill.js +116 -0
  11. package/01-tokens/responsive-demo.html +171 -0
  12. package/01-tokens/scrollbar-demo.html +194 -0
  13. package/01-tokens/scrollbar.css +179 -0
  14. package/01-tokens/theme-toggle.js +103 -0
  15. package/01-tokens/tokens.css +972 -0
  16. package/01-tokens/typography.css +156 -0
  17. package/02-primitives/badge/README.md +71 -0
  18. package/02-primitives/badge/badge.css +141 -0
  19. package/02-primitives/badge/demo.html +101 -0
  20. package/02-primitives/button/README.md +99 -0
  21. package/02-primitives/button/button.css +353 -0
  22. package/02-primitives/button/demo.html +200 -0
  23. package/02-primitives/card/README.md +145 -0
  24. package/02-primitives/card/card.css +246 -0
  25. package/02-primitives/card/demo.html +170 -0
  26. package/02-primitives/choice/README.md +161 -0
  27. package/02-primitives/choice/choice.css +395 -0
  28. package/02-primitives/choice/demo.html +277 -0
  29. package/02-primitives/combobox/README.md +79 -0
  30. package/02-primitives/combobox/combobox.css +223 -0
  31. package/02-primitives/combobox/combobox.js +485 -0
  32. package/02-primitives/combobox/demo.html +109 -0
  33. package/02-primitives/date-range/README.md +100 -0
  34. package/02-primitives/date-range/date-range.css +206 -0
  35. package/02-primitives/date-range/date-range.js +301 -0
  36. package/02-primitives/date-range/demo.html +104 -0
  37. package/02-primitives/input/README.md +170 -0
  38. package/02-primitives/input/demo.html +246 -0
  39. package/02-primitives/input/input.css +239 -0
  40. package/02-primitives/popover/README.md +80 -0
  41. package/02-primitives/popover/demo.html +150 -0
  42. package/02-primitives/popover/popover.css +187 -0
  43. package/02-primitives/popover/popover.js +331 -0
  44. package/02-primitives/progress/README.md +82 -0
  45. package/02-primitives/progress/demo.html +154 -0
  46. package/02-primitives/progress/progress.css +172 -0
  47. package/02-primitives/select/README.md +95 -0
  48. package/02-primitives/select/demo.html +121 -0
  49. package/02-primitives/select/select.css +151 -0
  50. package/02-primitives/select/select.js +549 -0
  51. package/02-primitives/separator/README.md +63 -0
  52. package/02-primitives/separator/demo.html +84 -0
  53. package/02-primitives/separator/separator.css +73 -0
  54. package/02-primitives/skeleton/README.md +69 -0
  55. package/02-primitives/skeleton/demo.html +110 -0
  56. package/02-primitives/skeleton/skeleton.css +96 -0
  57. package/02-primitives/switch/README.md +65 -0
  58. package/02-primitives/switch/demo.html +115 -0
  59. package/02-primitives/switch/switch.css +370 -0
  60. package/03-patterns/accordion/README.md +121 -0
  61. package/03-patterns/accordion/accordion.css +453 -0
  62. package/03-patterns/accordion/accordion.js +369 -0
  63. package/03-patterns/accordion/demo.html +223 -0
  64. package/03-patterns/content/README.md +103 -0
  65. package/03-patterns/content/content.css +286 -0
  66. package/03-patterns/content/demo.html +88 -0
  67. package/03-patterns/drawer/README.md +79 -0
  68. package/03-patterns/drawer/demo.html +101 -0
  69. package/03-patterns/drawer/drawer.css +129 -0
  70. package/03-patterns/drawer/drawer.js +258 -0
  71. package/03-patterns/dropdown/README.md +78 -0
  72. package/03-patterns/dropdown/demo.html +101 -0
  73. package/03-patterns/dropdown/dropdown.css +135 -0
  74. package/03-patterns/dropdown/dropdown.js +451 -0
  75. package/03-patterns/form-validation/README.md +147 -0
  76. package/03-patterns/form-validation/demo.html +280 -0
  77. package/03-patterns/form-validation/form-validation.css +263 -0
  78. package/03-patterns/list/README.md +150 -0
  79. package/03-patterns/list/demo.html +225 -0
  80. package/03-patterns/list/flip.js +201 -0
  81. package/03-patterns/list/list.css +162 -0
  82. package/03-patterns/nav/README.md +225 -0
  83. package/03-patterns/nav/demo.html +131 -0
  84. package/03-patterns/nav/nav.css +314 -0
  85. package/03-patterns/nav/nav.js +430 -0
  86. package/03-patterns/nav/toc.js +111 -0
  87. package/03-patterns/overlay/README.md +196 -0
  88. package/03-patterns/overlay/demo.html +241 -0
  89. package/03-patterns/overlay/overlay.css +319 -0
  90. package/03-patterns/overlay/overlay.js +563 -0
  91. package/03-patterns/pagination/README.md +114 -0
  92. package/03-patterns/pagination/demo.html +165 -0
  93. package/03-patterns/pagination/pagination.css +195 -0
  94. package/03-patterns/pagination/pagination.js +395 -0
  95. package/03-patterns/reveal/README.md +91 -0
  96. package/03-patterns/reveal/demo.html +106 -0
  97. package/03-patterns/reveal/reveal.css +93 -0
  98. package/03-patterns/reveal/reveal.js +172 -0
  99. package/03-patterns/states/README.md +141 -0
  100. package/03-patterns/states/demo.html +240 -0
  101. package/03-patterns/states/states.css +252 -0
  102. package/03-patterns/tabs/README.md +84 -0
  103. package/03-patterns/tabs/demo.html +139 -0
  104. package/03-patterns/tabs/tabs.css +172 -0
  105. package/03-patterns/tabs/tabs.js +307 -0
  106. package/03-patterns/tooltip/README.md +96 -0
  107. package/03-patterns/tooltip/demo.html +78 -0
  108. package/03-patterns/tooltip/tooltip.css +89 -0
  109. package/03-patterns/tooltip/tooltip.js +288 -0
  110. package/03-patterns/tree/README.md +94 -0
  111. package/03-patterns/tree/demo.html +138 -0
  112. package/03-patterns/tree/tree.css +113 -0
  113. package/03-patterns/tree/tree.js +456 -0
  114. package/04-recipes/analysis-report/README.md +80 -0
  115. package/04-recipes/analysis-report/analysis-report.css +187 -0
  116. package/04-recipes/analysis-report/demo.html +274 -0
  117. package/04-recipes/data-showcase/README.md +45 -0
  118. package/04-recipes/data-showcase/demo.html +247 -0
  119. package/04-recipes/detail-page/README.md +93 -0
  120. package/04-recipes/detail-page/demo.html +55 -0
  121. package/04-recipes/detail-page/detail-page.css +190 -0
  122. package/04-recipes/detail-page/full.html +60 -0
  123. package/04-recipes/detail-page/null.html +60 -0
  124. package/04-recipes/detail-page/partial.html +60 -0
  125. package/04-recipes/detail-page/verify-skeleton.py +19 -0
  126. package/04-recipes/longform/README.md +93 -0
  127. package/04-recipes/longform/agc-memory.svg +4 -0
  128. package/04-recipes/longform/content.html +610 -0
  129. package/04-recipes/longform/content.md +51 -0
  130. package/04-recipes/longform/longform.html +627 -0
  131. package/04-recipes/report/README.md +176 -0
  132. package/04-recipes/report/demo.html +355 -0
  133. package/04-recipes/report/report.css +364 -0
  134. package/04-recipes/table/README.md +103 -0
  135. package/04-recipes/table/demo.html +455 -0
  136. package/04-recipes/table/table.css +372 -0
  137. package/04-recipes/tierA-tokens.css +189 -0
  138. package/09-assets/README.md +74 -0
  139. package/09-assets/bar/README.md +94 -0
  140. package/09-assets/bar/bar.css +166 -0
  141. package/09-assets/bar/bar.js +320 -0
  142. package/09-assets/bar/demo.html +176 -0
  143. package/09-assets/echarts-adapter/demo.html +192 -0
  144. package/09-assets/echarts-adapter/echarts-adapter.js +277 -0
  145. package/09-assets/model-viewer/README.md +155 -0
  146. package/09-assets/model-viewer/demo.html +105 -0
  147. package/09-assets/model-viewer/model-viewer.css +143 -0
  148. package/09-assets/model-viewer/model-viewer.js +423 -0
  149. package/09-assets/scientific-plot/README.md +94 -0
  150. package/09-assets/scientific-plot/demo.html +252 -0
  151. package/09-assets/scientific-plot/scientific-plot.css +141 -0
  152. package/09-assets/scientific-plot/scientific-plot.js +610 -0
  153. package/09-assets/sparkline/README.md +153 -0
  154. package/09-assets/sparkline/demo.html +163 -0
  155. package/09-assets/sparkline/sparkline.css +158 -0
  156. package/09-assets/sparkline/sparkline.js +508 -0
  157. package/API.md +332 -0
  158. package/CHANGELOG.md +3138 -0
  159. package/CODE_OF_CONDUCT.md +64 -0
  160. package/CONTRIBUTING.md +89 -0
  161. package/ERRORS.md +1658 -0
  162. package/LICENSE +21 -0
  163. package/README.md +497 -2
  164. package/SECURITY.md +72 -0
  165. package/START-HERE.md +164 -0
  166. package/SUPPORT.md +109 -0
  167. package/adapters/presentation/README.md +113 -0
  168. package/adapters/presentation/check.js +101 -0
  169. package/adapters/presentation/deck.schema.json +74 -0
  170. package/adapters/presentation/to-doc.js +138 -0
  171. package/ai/START-HERE.md +104 -0
  172. package/ai/behaviors.json +380 -0
  173. package/ai/behaviors.schema.json +61 -0
  174. package/ai/capabilities.json +56 -0
  175. package/ai/cli.js +387 -0
  176. package/ai/components.json +1944 -0
  177. package/ai/components.meta.json +168 -0
  178. package/ai/components.schema.json +220 -0
  179. package/ai/contract.schema.json +147 -0
  180. package/ai/deprecations.json +55 -0
  181. package/ai/deprecations.schema.json +85 -0
  182. package/ai/motion-policy.json +22 -0
  183. package/ai/patch.js +323 -0
  184. package/ai/patch.schema.json +63 -0
  185. package/ai/profiles.json +92 -0
  186. package/ai/tokens.audience.json +55 -0
  187. package/ai/tokens.audience.schema.json +73 -0
  188. package/ai/tokens.json +762 -0
  189. package/ai/tokens.schema.json +58 -0
  190. package/ai/tokens.tree.json +752 -0
  191. package/ai/tokens.tree.schema.json +56 -0
  192. package/ai/validate.js +383 -0
  193. package/dist/01-tokens/behavior/dismissable.js +60 -0
  194. package/dist/01-tokens/behavior/emit.js +21 -0
  195. package/dist/01-tokens/behavior/focus-return.js +31 -0
  196. package/dist/01-tokens/behavior/roving.js +77 -0
  197. package/dist/01-tokens/behavior/typeahead.js +61 -0
  198. package/dist/01-tokens/focus-ring.css +1 -0
  199. package/dist/01-tokens/page.css +1 -0
  200. package/dist/01-tokens/polyfill.js +78 -0
  201. package/dist/01-tokens/scrollbar.css +1 -0
  202. package/dist/01-tokens/theme-toggle.js +61 -0
  203. package/dist/01-tokens/tokens.css +1 -0
  204. package/dist/01-tokens/typography.css +1 -0
  205. package/dist/02-primitives/badge/badge.css +1 -0
  206. package/dist/02-primitives/button/button.css +1 -0
  207. package/dist/02-primitives/card/card.css +1 -0
  208. package/dist/02-primitives/choice/choice.css +1 -0
  209. package/dist/02-primitives/combobox/combobox.css +1 -0
  210. package/dist/02-primitives/combobox/combobox.js +439 -0
  211. package/dist/02-primitives/date-range/date-range.css +1 -0
  212. package/dist/02-primitives/date-range/date-range.js +267 -0
  213. package/dist/02-primitives/input/input.css +1 -0
  214. package/dist/02-primitives/popover/popover.css +1 -0
  215. package/dist/02-primitives/popover/popover.js +257 -0
  216. package/dist/02-primitives/progress/progress.css +1 -0
  217. package/dist/02-primitives/select/select.css +1 -0
  218. package/dist/02-primitives/select/select.js +449 -0
  219. package/dist/02-primitives/separator/separator.css +1 -0
  220. package/dist/02-primitives/skeleton/skeleton.css +1 -0
  221. package/dist/02-primitives/switch/switch.css +1 -0
  222. package/dist/03-patterns/accordion/accordion.css +1 -0
  223. package/dist/03-patterns/accordion/accordion.js +219 -0
  224. package/dist/03-patterns/content/content.css +1 -0
  225. package/dist/03-patterns/drawer/drawer.css +1 -0
  226. package/dist/03-patterns/drawer/drawer.js +229 -0
  227. package/dist/03-patterns/dropdown/dropdown.css +1 -0
  228. package/dist/03-patterns/dropdown/dropdown.js +365 -0
  229. package/dist/03-patterns/form-validation/form-validation.css +1 -0
  230. package/dist/03-patterns/list/flip.js +117 -0
  231. package/dist/03-patterns/list/list.css +1 -0
  232. package/dist/03-patterns/nav/nav.css +1 -0
  233. package/dist/03-patterns/nav/nav.js +322 -0
  234. package/dist/03-patterns/nav/toc.js +76 -0
  235. package/dist/03-patterns/overlay/overlay.css +1 -0
  236. package/dist/03-patterns/overlay/overlay.js +398 -0
  237. package/dist/03-patterns/pagination/pagination.css +1 -0
  238. package/dist/03-patterns/pagination/pagination.js +273 -0
  239. package/dist/03-patterns/reveal/reveal.css +1 -0
  240. package/dist/03-patterns/reveal/reveal.js +138 -0
  241. package/dist/03-patterns/states/states.css +1 -0
  242. package/dist/03-patterns/tabs/tabs.css +1 -0
  243. package/dist/03-patterns/tabs/tabs.js +242 -0
  244. package/dist/03-patterns/tooltip/tooltip.css +1 -0
  245. package/dist/03-patterns/tooltip/tooltip.js +213 -0
  246. package/dist/03-patterns/tree/tree.css +1 -0
  247. package/dist/03-patterns/tree/tree.js +383 -0
  248. package/dist/04-recipes/analysis-report/analysis-report.css +1 -0
  249. package/dist/04-recipes/detail-page/detail-page.css +1 -0
  250. package/dist/04-recipes/report/report.css +1 -0
  251. package/dist/04-recipes/table/table.css +1 -0
  252. package/dist/04-recipes/tierA-tokens.css +1 -0
  253. package/dist/09-assets/bar/bar.css +1 -0
  254. package/dist/09-assets/bar/bar.js +222 -0
  255. package/dist/09-assets/echarts-adapter/echarts-adapter.js +185 -0
  256. package/dist/09-assets/model-viewer/model-viewer.css +1 -0
  257. package/dist/09-assets/model-viewer/model-viewer.js +320 -0
  258. package/dist/09-assets/scientific-plot/scientific-plot.css +1 -0
  259. package/dist/09-assets/scientific-plot/scientific-plot.js +568 -0
  260. package/dist/09-assets/sparkline/sparkline.css +1 -0
  261. package/dist/09-assets/sparkline/sparkline.js +265 -0
  262. package/dist/COSTS.md +56 -0
  263. package/docs/AI-CONTRACT.md +197 -0
  264. package/docs/BACKLOG.md +127 -0
  265. package/docs/BENCHMARK.md +252 -0
  266. package/docs/GUIDANCE.md +154 -0
  267. package/docs/INVARIANT.md +372 -0
  268. package/docs/LAYER-BASELINE.md +158 -0
  269. package/docs/RELEASING.md +311 -0
  270. package/docs/RFC-THEME-SCOPE.md +141 -0
  271. package/docs/ROADMAP.md +182 -0
  272. package/index.html +82 -0
  273. package/package.json +107 -4
  274. package/types/index.d.ts +310 -0
@@ -0,0 +1,455 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <link rel="icon" href="data:,">
7
+ <title>table · 专业数据表格 · frontend-lib</title>
8
+ <link rel="stylesheet" href="../../01-tokens/tokens.css">
9
+ <link rel="stylesheet" href="../../01-tokens/typography.css">
10
+ <link rel="stylesheet" href="../../01-tokens/focus-ring.css">
11
+ <link rel="stylesheet" href="../../01-tokens/page.css">
12
+ <link rel="stylesheet" href="../../02-primitives/button/button.css">
13
+ <link rel="stylesheet" href="table.css">
14
+ <style>
15
+ .toolbar { display: flex; gap: var(--sp-2); margin: 0 0 var(--sp-3); flex-wrap: wrap; }
16
+ .section-note { margin: 0 0 var(--sp-3); }
17
+ </style>
18
+ </head>
19
+ <body class="page">
20
+ <main class="wrap">
21
+
22
+ <h1>table · 专业数据表格</h1>
23
+ <p class="lede">
24
+ 同一套表格基础,三种用途预设。数字怎么显示由<strong>列定义</strong>决定,
25
+ 不由"这个数看起来有多大"决定。
26
+ </p>
27
+
28
+ <!-- ========================================================================
29
+ 预设一:业务 / 财务
30
+ ===================================================================== -->
31
+ <h2 class="rule-top">1 · 业务 / 财务数据表</h2>
32
+ <p class="note">
33
+ 金额、比率、同比变化放在一张表里 —— 它们需要的格式<strong>各不相同</strong>:
34
+ 营收不带正负号,同比必须带。单位写在列头,不在每个格子里重复。
35
+ </p>
36
+
37
+ <div class="toolbar">
38
+ <button class="btn btn--sm" data-demo="ready">常规</button>
39
+ <button class="btn btn--sm" data-demo="loading">加载态</button>
40
+ <button class="btn btn--sm" data-demo="empty">空态</button>
41
+ <button class="btn btn--sm" data-demo="error">出错态</button>
42
+ <button class="btn btn--sm btn--primary" id="sortBtn">按营收排序</button>
43
+ </div>
44
+ <div id="host"></div>
45
+
46
+ <!-- ========================================================================
47
+ 预设二:学术出版
48
+ ===================================================================== -->
49
+ <h2 class="rule-top">2 · 学术出版表格(三线表 + 多级表头)</h2>
50
+ <p class="note">
51
+ 论文表格的读者要逐格比对数值,竖线和斑马纹会干扰视线 ⇒ 只留三条线。
52
+ 多级表头用 <code>scope</code> 标明层级,读屏才知道"这一格属于哪一列"。
53
+ </p>
54
+ <div id="academic"></div>
55
+
56
+ <!-- ========================================================================
57
+ 预设三:统计 / 实验结果
58
+ ===================================================================== -->
59
+ <h2 class="rule-top">3 · 统计与实验结果表</h2>
60
+ <p class="note">
61
+ 估计值、标准误、置信区间、p 值各自有书写惯例,不能用同一个数字格式化函数打发。
62
+ <strong>本表不计算、也不宣称显著性</strong> —— 区间与 p 值由调用方提供。
63
+ </p>
64
+ <div id="stats"></div>
65
+
66
+ <!-- ========================================================================
67
+ 自证:格式不再靠猜
68
+ ===================================================================== -->
69
+ <h2 class="rule-top">4 · 格式自证(这几行就是反例)</h2>
70
+ <p class="note">
71
+ 下面两列<strong>数值完全相同</strong>(都是 <code>0.25</code>),
72
+ 只有列定义的 <code>format</code> 不同。旧实现会把两者都显示成
73
+ <code>25.0%</code> —— 因为它按"小于 1 就当比例"来猜。
74
+ </p>
75
+ <div id="proof"></div>
76
+
77
+ <!-- ========================================================================
78
+ 自证:动态文本进表格前必须转义
79
+ ===================================================================== -->
80
+ <h2 class="rule-top">5 · 转义自证(这一列故意含有 HTML)</h2>
81
+ <p class="note">
82
+ 表格 Cells 经常来自外部系统:<code>&lt;b&gt;</code>、<code>&amp;</code>、
83
+ 带着引号的属性串都会出现在真实数据里。
84
+ 它们必须<strong>原样显示</strong>,而不是被当成标签执行。
85
+ <br>⇒ 示例里的每一处动态文本都先过 <code>esc()</code>,UI 结构才是写死的 HTML。
86
+ </p>
87
+ <div id="escproof"></div>
88
+
89
+ </main>
90
+
91
+ <script>
92
+ (function () {
93
+ 'use strict';
94
+
95
+ /* ======================================================================
96
+ 列定义:格式与单位由这里声明,不做任何"看数值猜语义"的事
97
+ ----------------------------------------------------------------------
98
+ format 取值:
99
+ text 原样输出
100
+ integer 整数 + 千分位
101
+ decimal 定长小数(输入即显示值,不乘 100)
102
+ currency 千分位 + 定长小数(金额,正数不加 +)
103
+ percent 输入是**比例** ⇒ 乘 100 加 %(正数不加 +)
104
+ signed-percent 同上,但正数加 + 、负数加 −(用于"同比/变化")
105
+ ====================================================================== */
106
+
107
+ /* 🔴🔴 动态文本一律**先转义**再拼进 HTML(外部评审 VIZ-REPORT-01 · P1-1)
108
+ ----------------------------------------------------------------------
109
+ 这个页面是给人**照抄**的示例。用字符串拼表格本身没问题,
110
+ 问题在于把**数据里的文本**(公司名、列标签、单位)直接塞进 innerHTML:
111
+ 数据一旦来自外部,`<b>x</b>` 会被当成标签、`<img onerror=…>` 会被执行。
112
+ ⇒ 规则只有一条:**凡是数据来的,出门前先 esc()**。
113
+ schema 写死的 HTML(表头结构、样式类)才可以直接拼。 */
114
+ function esc(s) {
115
+ return String(s == null ? '' : s)
116
+ .replace(/&/g, '&amp;').replace(/</g, '&lt;')
117
+ .replace(/>/g, '&gt;').replace(/"/g, '&quot;')
118
+ .replace(/'/g, '&#39;');
119
+ }
120
+
121
+ function fixed(v, p) {
122
+ var n = Number(v);
123
+ if (!isFinite(n)) return '—';
124
+ return p === undefined || p === null ? String(n) : n.toFixed(p);
125
+ }
126
+
127
+ function grouped(v, p) {
128
+ var n = Number(v);
129
+ if (!isFinite(n)) return '—';
130
+ var s = (p === undefined || p === null) ? String(n) : n.toFixed(p);
131
+ var parts = s.split('.');
132
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
133
+ return parts.join('.');
134
+ }
135
+
136
+ /**
137
+ * 渲染一个单元格。
138
+ * 🔴 两条硬规则:
139
+ * ① 缺失值既不是 0 也不是空 —— 显示占位符,且不带任何符号
140
+ * ② 正负号只由 format 决定:只有 signed-* 的正数才加 +
141
+ * (营收、成本、毛利率这类"量",正数没有 + 号)
142
+ */
143
+ function cell(v, col) {
144
+ var f = col.format || 'text';
145
+
146
+ if (v === null || v === undefined || v === '') {
147
+ return { text: '—', cls: (f === 'text' ? '' : 'num num--na') };
148
+ }
149
+ if (f === 'text') return { text: String(v), cls: '' };
150
+
151
+ var abs = Math.abs(Number(v));
152
+ var text;
153
+ if (f === 'currency' || f === 'integer') text = grouped(abs, col.precision);
154
+ else if (f === 'percent' || f === 'signed-percent') text = fixed(abs * 100, col.precision) + '%';
155
+ else text = fixed(abs, col.precision);
156
+
157
+ /* 符号:负数总是有 −(数值语义 + 无障碍第二线索);
158
+ 正数只有 signed-* 才带 + —— 这就是"按列语义处理" */
159
+ var cls = 'num';
160
+ if (Number(v) < 0) cls += ' num--neg';
161
+ else if (f === 'signed-percent' || f === 'signed-decimal') cls += ' num--pos';
162
+
163
+ return { text: text, cls: cls };
164
+ }
165
+
166
+ function headLabel(c) {
167
+ /* 标签与单位都来自列定义(外部数据)⇒ 都要 esc
168
+ 🔴 括号由组件加,不要求调用方记得写(GB/T 7713.2 要求「名称(单位)」,
169
+ 但让每个调用方自己记得加括号 = 迟早漏一处)。
170
+ 已经带括号的(如「(标准误)」这类注记)不再套,避免双括号。 */
171
+ var u = '';
172
+ if (c.unit) {
173
+ var raw = String(c.unit);
174
+ var has = /^\(.*\)$/.test(raw.trim());
175
+ u = ' <span class="table__unit">'
176
+ + esc(has ? raw : '(' + raw + ')') + '</span>';
177
+ }
178
+ return esc(c.label) + u;
179
+ }
180
+
181
+ /* ---------------------------------------------------------------- 数据 */
182
+
183
+ var FIN_COLS = [
184
+ { key: 'name', label: '公司', format: 'text' },
185
+ { key: 'rev', label: '营业收入', format: 'currency', precision: 0, unit: '万元' },
186
+ { key: 'cost', label: '营业成本', format: 'currency', precision: 0, unit: '万元' },
187
+ { key: 'margin', label: '毛利率', format: 'percent', precision: 1 },
188
+ { key: 'yoy', label: '同比', format: 'signed-percent', precision: 1 },
189
+ { key: 'roe', label: 'ROE', format: 'percent', precision: 1 },
190
+ { key: 'src', label: '数据源', format: 'text' }
191
+ ];
192
+
193
+ /* 🔴 单位与量级必须自洽(0.8.0 修):
194
+ 此前这些数写成 1,505,600 而列头声明「万元」⇒ 那等于 150.6 亿元,
195
+ 和一个千亿级公司的量级差了 10 倍。**样例数据也必须单位诚实** ——
196
+ 读者会照着列头换算,算错了不会报错,只会记错。
197
+ ⇒ 现值以公开年报的「亿元」口径 ×10⁸ 换算回万元,量级与列头一致。
198
+ ⚠️ 毛利率 / 同比 / ROE 是**示意值**,请勿当真实财务数据引用。 */
199
+ var FIN_DATA = [
200
+ { name: '贵州茅台', rev: 15056000, cost: 2198000, margin: 0.854, yoy: 0.163, roe: 0.347, src: '年报' },
201
+ { name: '宁德时代', rev: 40090000, cost: 30540000, margin: 0.238, yoy: 0.104, roe: 0.241, src: '年报' },
202
+ { name: '比亚迪', rev: 60231500, cost: 48185200, margin: 0.200, yoy: 0.243, roe: 0.225, src: '年报' },
203
+ { name: '招商银行', rev: 33912300, cost: 9632000, margin: 0.716, yoy: -0.008, roe: 0.152, src: '季报' },
204
+ { name: '美的集团', rev: 37370000, cost: 28900000, margin: 0.227, yoy: 0.093, roe: 0.221, src: '年报' },
205
+ { name: '五粮液', rev: 8327200, cost: 1862000, margin: 0.776, yoy: 0.129, roe: null, src: '年报' }
206
+ ];
207
+
208
+ /* ------------------------------------------------- 预设一:渲染财务表 */
209
+ var host = document.getElementById('host');
210
+ var sortKey = null, sortDir = 1;
211
+
212
+ function renderFinance(state) {
213
+ if (state === 'loading') {
214
+ host.innerHTML = '<div class="table"><div class="table__loading" role="status" ' +
215
+ 'aria-live="polite" aria-label="正在加载数据">' +
216
+ '<div class="table__bar"></div><div class="table__bar"></div>' +
217
+ '<div class="table__bar"></div></div></div>';
218
+ return;
219
+ }
220
+ if (state === 'empty') {
221
+ host.innerHTML = '<div class="table"><div class="table__empty">' +
222
+ '<p>当前筛选条件下没有数据。</p>' +
223
+ '<button class="btn btn--sm">清除筛选条件</button></div></div>';
224
+ return;
225
+ }
226
+ if (state === 'error') {
227
+ host.innerHTML = '<div class="table"><div class="table__error" role="alert">' +
228
+ '<p>数据加载失败:接口返回 503。</p>' +
229
+ '<button class="btn btn--sm">重试</button></div></div>';
230
+ return;
231
+ }
232
+
233
+ var rows = FIN_DATA.slice();
234
+ if (sortKey) {
235
+ rows.sort(function (a, b) {
236
+ var x = a[sortKey], y = b[sortKey];
237
+ if (x === null) return 1; /* 缺失值始终排最后,不当 0 */
238
+ if (y === null) return -1;
239
+ return (x - y) * sortDir;
240
+ });
241
+ }
242
+
243
+ var h = '<div class="table table--financial"><table>' +
244
+ '<caption class="table__cap">A 股样例数据(非真实财报,量级参照公开年报)· ' +
245
+ '金额单位:万元 · 比率为比例值</caption>' +
246
+ '<thead><tr>';
247
+ FIN_COLS.forEach(function (c) {
248
+ var ariaSort = '';
249
+ if (sortKey === c.key) {
250
+ ariaSort = ' aria-sort="' + (sortDir === 1 ? 'ascending' : 'descending') + '"';
251
+ }
252
+ h += '<th scope="col"' + ariaSort + '>' +
253
+ '<button class="table__sort" data-key="' + c.key + '">' +
254
+ headLabel(c) + '</button></th>';
255
+ });
256
+ h += '</tr></thead><tbody>';
257
+
258
+ rows.forEach(function (r) {
259
+ h += '<tr>';
260
+ FIN_COLS.forEach(function (c) {
261
+ var v = cell(r[c.key], c);
262
+ if (c.format === 'text' && c.key === 'name') {
263
+ h += '<th scope="row">' + esc(v.text) + '</th>'; /* 行表头:读屏知道是谁的行 */
264
+ } else if (v.cls) {
265
+ h += '<td class="' + v.cls + '">' + esc(v.text) + '</td>';
266
+ } else {
267
+ h += '<td>' + esc(v.text) + '</td>';
268
+ }
269
+ });
270
+ h += '</tr>';
271
+ });
272
+ h += '</tbody></table></div>' +
273
+ '<p class="table__foot">注:ROE 缺失表示该期未披露;缺失显示为「—」,不作为 0 参与任何计算。</p>';
274
+ host.innerHTML = h;
275
+
276
+ host.querySelectorAll('.table__sort').forEach(function (b) {
277
+ b.addEventListener('click', function () {
278
+ var k = b.getAttribute('data-key');
279
+ if (sortKey === k) { sortDir = -sortDir; } else { sortKey = k; sortDir = 1; }
280
+ renderFinance('ready');
281
+ });
282
+ });
283
+ }
284
+
285
+ document.querySelectorAll('[data-demo]').forEach(function (b) {
286
+ b.addEventListener('click', function () {
287
+ renderFinance(b.getAttribute('data-demo'));
288
+ });
289
+ });
290
+ document.getElementById('sortBtn').addEventListener('click', function () {
291
+ sortKey = 'rev'; sortDir = -sortDir; renderFinance('ready');
292
+ });
293
+ renderFinance('ready');
294
+
295
+ /* ------------------------------------------- 预设二:学术三线表(多级表头) */
296
+ /* 多级表头的语义:
297
+ 第一层 th 带 colspan ⇒ scope="colgroup"(它管一组列)
298
+ 第二层 th ⇒ scope="col"(它管自己那一列)
299
+ 行表头 ⇒ scope="row" */
300
+ var ACAD = [
301
+ { alloy: '6061-T6', rt: [312, 18.4], h100: [286, 15.1], h150: [241, 12.7] },
302
+ { alloy: '6061-T651',rt: [324, 17.9], h100: [298, 14.6], h150: [252, 11.9] },
303
+ { alloy: '7075-T6', rt: [572, 21.3], h100: [498, 17.2], h150: [403, 13.4] },
304
+ { alloy: '2024-T3', rt: [483, 19.8], h100: [421, 16.1], h150: [347, 12.2] }
305
+ ];
306
+ var GROUPS = [
307
+ /* 🔴 分组表头也是"量 + 温度条件",按GB/T 7713.2「量的名称/单位符号」
308
+ 统一成 `时效 (100 °C)`:原来三个写法三种样(全角括号 / 无括号 / 空格),
309
+ 且 `white-space: nowrap` 之前让 `时效 100 °C` 在窄屏被折成两行。 */
310
+ { label: '室温 (25 °C)', key: 'rt' },
311
+ { label: '时效 (100 °C)', key: 'h100' },
312
+ { label: '时效 (150 °C)', key: 'h150' }
313
+ ];
314
+
315
+ var a = '<div class="table table--academic"><table>' +
316
+ '<caption class="table__cap">表 1 · 不同时效温度下铝合金的拉伸性能(示例数据)</caption>' +
317
+ '<thead><tr><th scope="col" rowspan="2">合金牌号</th>';
318
+ GROUPS.forEach(function (g) {
319
+ /* colspan=2:每个温度组下有"抗拉强度"和"断后伸长率"两列 */
320
+ a += '<th scope="colgroup" colspan="2">' + esc(g.label) + '</th>';
321
+ });
322
+ a += '</tr><tr>';
323
+ GROUPS.forEach(function () {
324
+ a += '<th scope="col">抗拉强度 <span class="table__unit">(MPa)</span></th>' +
325
+ '<th scope="col">伸长率 <span class="table__unit">(%)</span></th>';
326
+ });
327
+ a += '</tr></thead><tbody>';
328
+
329
+ ACAD.forEach(function (r) {
330
+ a += '<tr><th scope="row">' + esc(r.alloy) + '</th>';
331
+ GROUPS.forEach(function (g) {
332
+ var pair = r[g.key];
333
+ a += '<td class="num">' + grouped(pair[0], 0) + '</td>' +
334
+ '<td class="num">' + fixed(pair[1], 1) + '</td>';
335
+ });
336
+ a += '</tr>';
337
+ });
338
+ a += '</tbody></table></div>' +
339
+ '<div class="table__foot"><ol>' +
340
+ '<li>示例数据,用于演示三线表与多级表头的结构,<strong>非真实实验测量结果</strong>。</li>' +
341
+ '<li>每组为 2 个测值的算术平均;原始测值未提供,故不给出标准差。</li>' +
342
+ '</ol></div>';
343
+ document.getElementById('academic').innerHTML = a;
344
+
345
+ /* --------------------------------------- 预设三:统计 / 实验结果表 */
346
+ var STATS = [
347
+ { term: '截距', est: 2.413, se: 0.182, ci: [2.056, 2.770], p: 0.0001 },
348
+ { term: '处理效应', est: 0.487, se: 0.196, ci: [0.103, 0.871], p: 0.013 },
349
+ { term: '基线得分', est: 0.312, se: 0.074, ci: [0.167, 0.457], p: 0.0001 },
350
+ { term: '年龄', est: -0.021, se: 0.018, ci: [-0.056, 0.014], p: 0.241 },
351
+ { term: '性别(男=1)', est: 0.094, se: 0.121, ci: [-0.143, 0.331], p: 0.437 }
352
+ ];
353
+
354
+ function pText(p) {
355
+ /* p 值有自己的书写惯例:< .001 不写成 0.000
356
+ ⚠️ 这里返回的是**纯文本**(尖括号留给插入点的 esc 处理),
357
+ 以前直接返回 '&lt;' 这种转义后的串 ⇒ 再逃一次会露出 &amp;lt; */
358
+ if (p < 0.001) return '< .001';
359
+ return fixed(p, 3).replace(/^0\./, '.');
360
+ }
361
+
362
+ var s = '<div class="table table--stats"><table>' +
363
+ '<caption class="table__cap">表 2 · 处理效应的 OLS 估计(示例数据,n = 412)</caption>' +
364
+ '<thead><tr>' +
365
+ '<th scope="col">项</th>' +
366
+ '<th scope="col">估计值 <span class="table__unit">(标准误)</span></th>' +
367
+ '<th scope="col">95% 置信区间</th>' +
368
+ '<th scope="col">p 值</th>' +
369
+ '</tr></thead><tbody>';
370
+
371
+ STATS.forEach(function (r) {
372
+ /* 🔴 标准误不是"小数点后更多位",它是**另一个数** —— 放在次级位置并弱化 */
373
+ var estCell = '<td class="num">' + fixed(r.est, 3) +
374
+ ' <span class="stat__se">(' + fixed(r.se, 3) + ')</span></td>';
375
+ var ciCell = '<td class="num stat__ci">[' + fixed(r.ci[0], 3) + ', ' +
376
+ fixed(r.ci[1], 3) + ']</td>';
377
+ var sig = r.p < 0.05 ? ' stat__p--sig' : '';
378
+ var pCell = '<td class="num stat__p' + sig + '">' + esc(pText(r.p)) + '</td>';
379
+ s += '<tr><th scope="row">' + esc(r.term) + '</th>' + estCell + ciCell + pCell + '</tr>';
380
+ });
381
+ s += '</tbody></table></div>' +
382
+ '<p class="table__foot">注:括号内为标准误;置信区间与 p 值由调用方计算并提供,' +
383
+ '本表只负责呈现,<strong>不推断显著性</strong>。示例数据,非真实研究结果。</p>';
384
+ document.getElementById('stats').innerHTML = s;
385
+
386
+ /* ------------------------------------------------- 自证区:同数不同义 */
387
+ var PROOF = [
388
+ { label: '比例(percent)', value: 0.25, format: 'percent', precision: 1,
389
+ why: '列定义说它是比例 ⇒ 显示 25.0%' },
390
+ { label: '测量值(decimal)', value: 0.25, format: 'decimal', precision: 2,
391
+ why: '列定义说它是普通测量值 ⇒ 显示 0.25,绝不乘 100' },
392
+ { label: '金额(currency)', value: 1505600, format: 'currency', precision: 0,
393
+ why: '金额正数 ⇒ 有千分位,没有 + 号' },
394
+ { label: '同比(signed-percent)', value: 0.163, format: 'signed-percent', precision: 1,
395
+ why: '变化量 ⇒ 正数带 +(颜色之外的第二线索)' },
396
+ { label: '同比为负(signed-percent)', value: -0.008, format: 'signed-percent', precision: 1,
397
+ why: '负数用真正的减号 −,不是连字符' },
398
+ { label: '缺失(null)', value: null, format: 'currency', precision: 0,
399
+ why: '缺失 ⇒ 显示 —,不是 0,也不带符号' }
400
+ ];
401
+
402
+ var p = '<div class="table"><table>' +
403
+ '<caption class="table__cap">同一个数、不同的列定义 ⇒ 不同的显示</caption>' +
404
+ '<thead><tr><th scope="col">列定义</th><th scope="col">输入</th>' +
405
+ '<th scope="col">渲染结果</th><th scope="col">为什么</th></tr></thead><tbody>';
406
+ PROOF.forEach(function (r) {
407
+ var v = cell(r.value, { format: r.format, precision: r.precision });
408
+ var rawIn = r.value === null ? 'null' : String(r.value);
409
+ p += '<tr><th scope="row">' + esc(r.label) + '</th>' +
410
+ '<td class="num">' + esc(rawIn) + '</td>' +
411
+ '<td class="' + (v.cls || '') + '">' + esc(v.text) + '</td>' +
412
+ '<td>' + esc(r.why) + '</td></tr>';
413
+ });
414
+ p += '</tbody></table></div>';
415
+ document.getElementById('proof').innerHTML = p;
416
+
417
+ /* --------------------------------- 自证区:动态文本不会被当成 HTML ------ *
418
+ 数据里出现尖括号在现实中很常见:
419
+ 部门名写成「研发 & 设计」、SKU 里带 `<规划>N/A`、第三方系统吐出 HTML。
420
+ 它们必须**原样显示**,而不是变成标签。
421
+
422
+ ⚠️ 为什么这里不放真正可执行的攻击串
423
+ —— 一是没必要(能证明"没被当成标签"的判据不需要真的去执行它),
424
+ 二是不想在一份会被到处复制的示例里留下一份开箱即用的载荷。
425
+ ⇒ 这里用"标签 + 引号闭合的属性串"两种形态,足以覆盖失败模式。 */
426
+ var ESC_DATA = [
427
+ { label: '含标签的组名 <b>加粗</b>', raw: '<b>加粗</b>',
428
+ expect: '尖括号原样显示,不产生 <b> 元素' },
429
+ { label: '含链接 Github 风格的 <a href="#">链接</a>', raw: '<a href="#">链接</a>',
430
+ expect: '不产生 <a> 元素' },
431
+ { label: '引号 + 事件属性', raw: '" onmouseover="window.__escaped = 0" x="',
432
+ expect: '不产生任何事件处理属性' },
433
+ { label: '和号 & 大于小于', raw: 'A & B < C > D',
434
+ expect: '显示为字面字符' }
435
+ ];
436
+
437
+ var e2 = '<div class="table"><table>' +
438
+ '<caption class="table__cap">动态文本按**文本**渲染,不当 HTML 解释</caption>' +
439
+ '<thead><tr><th scope="col">列定义</th><th scope="col">原始数据</th>' +
440
+ '<th scope="col">渲染结果</th><th scope="col">验收点</th></tr></thead><tbody>';
441
+ ESC_DATA.forEach(function (r) {
442
+ /* ⭐ 每一处插值都过 esc —— 这就是这一节要示范的做法 */
443
+ e2 += '<tr><th scope="row">' + esc(r.label) + '</th>' +
444
+ '<td class="num"><code>' + esc(r.raw) + '</code></td>' +
445
+ '<td><code>' + esc(r.raw) + '</code></td>' +
446
+ '<td>' + esc(r.expect) + '</td></tr>';
447
+ });
448
+ e2 += '</tbody></table></div>' +
449
+ '<p class="table__foot">复查方式:用浏览器看这一节的单元格 —— ' +
450
+ '第二、三列应当<b>原样显示</b>尖括号文本,而不是把它们变成样式。</p>';
451
+ document.getElementById('escproof').innerHTML = e2;
452
+ })();
453
+ </script>
454
+ </body>
455
+ </html>