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,93 @@
1
+ /* ============================================================================
2
+ Reveal — 入场动效(属性即接口)
3
+ ============================================================================
4
+ 🔴 一条规则撑六种表现
5
+ ------------------------------------------------
6
+ 六个方向**共用同一个 transform 表达式**,只改三个变量:
7
+ --reveal-x / --reveal-y / --reveal-scale
8
+ 没有一个方向单独写 keyframes —— 这是本组件唯一值得记住的设计。
9
+ 理由:keyframes 一多,缓动/时长/延迟就会各写一套,
10
+ 最后没有人能回答"为什么这个方向和那个方向节奏不一样"。
11
+
12
+ 🔴 无 JS 必须全可见(不是"尽量")
13
+ ------------------------------------------------
14
+ 隐藏**只在** `<html data-reveal-ready>` 存在时生效。
15
+ 这个标记由引擎在**接管之后**才加 ⇒ 没有 JS、JS 报错、IO 缺失时,
16
+ 内容就是普通内容:位置不偏、透明度为 1。
17
+ ⇒ 所以本组件**不会**造成白屏,代价是"没有动效"。
18
+
19
+ 🔴 时长只走令牌,不写裸数值
20
+ ------------------------------------------------
21
+ var(--dur-base) 位移/展开(180ms)
22
+ var(--dur-fast) 颜色/透明度(120ms,降级时用)
23
+ var(--ease-out) UI 默认缓动
24
+ `05-audit/motion.py` 会查裸时长与自编曲线。
25
+
26
+ 🔴 位移上限 8px
27
+ ------------------------------------------------
28
+ 作品集那种 24px 位移能把视线带着走;产品页信息密度高,
29
+ 大位移等于让用户"等它飘完"。8px 只够表达"从哪边来"。
30
+
31
+ 🔴 降级是分级,不是一刀切
32
+ ------------------------------------------------
33
+ prefers-reduced-motion 下:位移/缩放**关掉**(前庭不适的来源),
34
+ 只保留透明度,且缩到 --dur-fast。
35
+ 与全库那条 `0.01ms !important` 兜底的关系:本文件用自己的规则覆盖它,
36
+ 因为"关掉位移"不等于"关掉一切"。
37
+ ========================================================================= */
38
+
39
+ /* `.reveal` 是契约里的根名(ai/components.json 的 rootClass 必须真实存在)。
40
+ 建议只用属性写法;这个类名是为了让契约与源码对得上,不引入第二套用法。 */
41
+ [data-reveal],
42
+ .reveal {
43
+ --reveal-x: 0px;
44
+ --reveal-y: 0px;
45
+ --reveal-scale: 1;
46
+ --reveal-delay: 0ms;
47
+ transform: translate(var(--reveal-x), var(--reveal-y)) scale(var(--reveal-scale));
48
+ transition-property: transform, opacity;
49
+ transition-duration: var(--dur-base);
50
+ transition-timing-function: var(--ease-out);
51
+ transition-delay: var(--reveal-delay);
52
+ }
53
+
54
+ /* ---- 六个方向:只改变量,不写 keyframes ---- */
55
+ [data-reveal="up"] { --reveal-y: 8px; }
56
+ [data-reveal="down"] { --reveal-y: -8px; }
57
+ [data-reveal="left"] { --reveal-x: 8px; }
58
+ [data-reveal="right"] { --reveal-x: -8px; }
59
+ [data-reveal="scale"] { --reveal-scale: .97; }
60
+ [data-reveal="none"] { --reveal-x: 0px; --reveal-y: 0px; --reveal-scale: 1; }
61
+
62
+ /* ---- 隐藏态:只有引擎接管后(html[data-reveal-ready])才允许 ---- */
63
+ [data-reveal-ready] [data-reveal]:not([data-reveal-done]) {
64
+ opacity: 0;
65
+ }
66
+
67
+ /* ---- 完成态:把三个变量归零 ⇒ 触发 transition ---- */
68
+ [data-reveal-ready] [data-reveal][data-reveal-done] {
69
+ opacity: 1;
70
+ --reveal-x: 0px;
71
+ --reveal-y: 0px;
72
+ --reveal-scale: 1;
73
+ }
74
+
75
+ /* ---- 分批(data-reveal-children):间隔由引擎写成 --reveal-delay ---- */
76
+ [data-reveal-children] > * {
77
+ transition-delay: var(--reveal-delay, 0ms);
78
+ }
79
+
80
+ /* ---- 分级降级 ---- */
81
+ @media (prefers-reduced-motion: reduce) {
82
+ [data-reveal] {
83
+ transform: none;
84
+ transition-property: opacity;
85
+ transition-duration: var(--dur-fast);
86
+ }
87
+
88
+ /* reduced 下**不隐藏**:内容直接出现。
89
+ 隐藏 + 等淡入也是"动",对前庭敏感用户不是安慰。 */
90
+ [data-reveal-ready] [data-reveal]:not([data-reveal-done]) {
91
+ opacity: 1;
92
+ }
93
+ }
@@ -0,0 +1,172 @@
1
+ /* ============================================================================
2
+ Reveal 引擎 — 无框架依赖,语法刻意用 ES5
3
+ ============================================================================
4
+ 🔴 为什么是 ES5:本库的 JS 会被 精简档 WebView(Chromium 97 级)加载,
5
+ `const` / 箭头函数在那里是**语法错误**(整段不执行,不是降级)。
6
+ `05-audit/es5-gate.py` 会守这条。
7
+
8
+ 🔴 为什么不自动接管(默认不 enable)
9
+ ------------------------------------------------
10
+ enable() 会把目标藏起来(opacity:0)。这是**有代价的决定**,
11
+ 必须由页面显式做出,不能由"引了脚本"隐式产生:
12
+ ① 页面写 `<html data-reveal-auto>` ⇒ DOMContentLoaded 时自动 enable
13
+ ② 或手动 `Reveal.enable()`
14
+ 不写就完全不动 —— 内容可见、位置不偏、零 JS 报错风险。
15
+
16
+ 🔴 兜底顺序(任何一步失败都不许留下"看不见的内容")
17
+ ------------------------------------------------
18
+ ① 没有 IntersectionObserver ⇒ 全部直接显示
19
+ ② prefers-reduced-motion ⇒ 全部直接显示(不做淡入,见 CSS 注释)
20
+ ③ 单个元素已 done ⇒ unobserve,不再参与
21
+ ④ 🆕 构造函数抛错 ⇒ try/catch 后全部直接显示
22
+ ⑤ 🆕 2 秒看门狗 ⇒ 视口内仍未显示的强制显示
23
+
24
+ ④⑤ 是 0.9.1 补的。此前 `data-reveal-ready` 先打上、再 new IntersectionObserver:
25
+ 构造函数一旦抛错(部分 WebView 有此问题),隐藏态已经生效而 revealAll()
26
+ 永远跑不到 ⇒ **整页空白**。这正是"隐藏态必须配兜底"那条的落点。
27
+ ========================================================================= */
28
+ (function (global) {
29
+ 'use strict';
30
+
31
+ var SELECTOR = '[data-reveal]';
32
+ var GROUP_SELECTOR = '[data-reveal-children]';
33
+ var STAGGER_MS = 40; /* 40 × 6 = 240ms 播完一批,超过 300ms 会觉得慢 */
34
+ var STAGGER_CAP = 6; /* 封顶:不封顶时第 20 个元素要等 800ms 才动 */
35
+ var THRESHOLD = 0.15;
36
+ var WATCHDOG_MS = 2000; /* 兜底看门狗:接管后 2s,视口内还没显示的强制显示 */
37
+ var observer = null;
38
+ var watchdog = null;
39
+
40
+ function each(list, fn) {
41
+ for (var i = 0; i < list.length; i++) { fn(list[i], i); }
42
+ }
43
+
44
+ function reduced() {
45
+ return !!(global.matchMedia &&
46
+ global.matchMedia('(prefers-reduced-motion: reduce)').matches);
47
+ }
48
+
49
+ function markDone(el) { el.setAttribute('data-reveal-done', ''); }
50
+ function clearDone(el) { el.removeAttribute('data-reveal-done'); }
51
+
52
+ /* 把 data-reveal-children 的每个子元素变成独立目标,并写入错峰延迟 */
53
+ function prepareGroups() {
54
+ var groups = document.querySelectorAll(GROUP_SELECTOR);
55
+ each(groups, function (group) {
56
+ var dir = group.getAttribute('data-reveal-children') || 'up';
57
+ var kids = group.children;
58
+ for (var i = 0; i < kids.length; i++) {
59
+ var k = kids[i];
60
+ if (!k.hasAttribute('data-reveal')) {
61
+ k.setAttribute('data-reveal', dir);
62
+ }
63
+ if (!k.hasAttribute('data-reveal-delay')) {
64
+ var step = i < STAGGER_CAP ? i : STAGGER_CAP;
65
+ k.style.setProperty('--reveal-delay', (step * STAGGER_MS) + 'ms');
66
+ }
67
+ }
68
+ });
69
+ }
70
+
71
+ function revealAll() {
72
+ each(document.querySelectorAll(SELECTOR), markDone);
73
+ }
74
+
75
+ function inViewport(el) {
76
+ var vh = global.innerHeight || document.documentElement.clientHeight || 0;
77
+ var r = el.getBoundingClientRect();
78
+ return r.bottom > 0 && r.top < vh;
79
+ }
80
+
81
+ /* 兜底一轮:只补「已经在视口里、却还没显示」的元素。
82
+ 🔴 为什么不直接 revealAll():折叠线以下的元素本来就该等滚动,
83
+ 全显会让 reveal 退化成"进页面一次性淡入",与协议不符。 */
84
+ function watchdogSweep() {
85
+ watchdog = null;
86
+ each(document.querySelectorAll(SELECTOR), function (el) {
87
+ if (el.hasAttribute('data-reveal-done')) { return; }
88
+ if (inViewport(el)) { markDone(el); }
89
+ });
90
+ }
91
+
92
+ function teardown() {
93
+ if (watchdog) { global.clearTimeout(watchdog); watchdog = null; }
94
+ if (observer) { observer.disconnect(); observer = null; }
95
+ each(document.querySelectorAll(SELECTOR), function (el) {
96
+ clearDone(el);
97
+ });
98
+ document.documentElement.removeAttribute('data-reveal-ready');
99
+ }
100
+
101
+ function enable() {
102
+ var root = document.documentElement;
103
+ if (observer) { return true; } /* 幂等:重复调用不叠加观察 */
104
+
105
+ prepareGroups();
106
+ root.setAttribute('data-reveal-ready', '');
107
+
108
+ if (!('IntersectionObserver' in global) || reduced()) {
109
+ revealAll();
110
+ return true;
111
+ }
112
+
113
+ /* 🔴 构造 + 注册都放进 try:这两步任何一步抛错,隐藏态已经生效,
114
+ 不兜底就是白屏。宁可全显,也不留看不见的内容。 */
115
+ try {
116
+ observer = new global.IntersectionObserver(function (entries) {
117
+ for (var i = 0; i < entries.length; i++) {
118
+ var en = entries[i];
119
+ if (en.isIntersecting) {
120
+ markDone(en.target);
121
+ if (!en.target.hasAttribute('data-reveal-repeat')) {
122
+ observer.unobserve(en.target);
123
+ }
124
+ } else if (en.target.hasAttribute('data-reveal-repeat')) {
125
+ clearDone(en.target);
126
+ }
127
+ }
128
+ }, { threshold: THRESHOLD, rootMargin: '0px 0px -8% 0px' });
129
+
130
+ each(document.querySelectorAll(SELECTOR), function (el) {
131
+ observer.observe(el);
132
+ });
133
+ } catch (e) {
134
+ if (observer) {
135
+ try { observer.disconnect(); } catch (e2) { observer = null; }
136
+ observer = null;
137
+ }
138
+ revealAll();
139
+ return true;
140
+ }
141
+
142
+ if (!watchdog) {
143
+ watchdog = global.setTimeout(watchdogSweep, WATCHDOG_MS);
144
+ }
145
+ return true;
146
+ }
147
+
148
+ function disable() {
149
+ teardown();
150
+ revealAll();
151
+ }
152
+
153
+ function replay() {
154
+ teardown();
155
+ return enable();
156
+ }
157
+
158
+ global.Reveal = { enable: enable, disable: disable, replay: replay };
159
+
160
+ function auto() {
161
+ if (document.documentElement.hasAttribute('data-reveal-auto')) {
162
+ enable();
163
+ }
164
+ }
165
+
166
+ // @autoinit: conditional[data-reveal-auto] —— 只有 <html data-reveal-auto> 才 enable();不写则完全不动
167
+ if (document.readyState === 'loading') {
168
+ document.addEventListener('DOMContentLoaded', auto, false);
169
+ } else {
170
+ auto();
171
+ }
172
+ }(this));
@@ -0,0 +1,141 @@
1
+ # 容器四态:empty / loading / error / success
2
+
3
+ | 字段 | 内容 |
4
+ |---|---|
5
+ | **来源** | 原创 |
6
+ | **实测** | 演示见 `demo.html`(含 300ms 延迟的真实效果);色值对比度沿用 tokens 已测值 |
7
+ | **适用** | 精简档 / B / C,纯 CSS(延迟显示需 ~10 行 JS) |
8
+ | **依赖** | `01-tokens/tokens.css` `02-primitives/button/button.css`(分段控件用 `.btn`) + `01-tokens/page.css`(页面骨架:`.wrap` / `.lede` / `.note`) + `01-tokens/typography.css` |
9
+ | **门禁** | ⚠️ **无自动门禁** —— 见下方"验收清单" |
10
+
11
+ ---
12
+
13
+ ## 四态各自必须有什么
14
+
15
+ | 态 | 必须 | 不做会怎样 |
16
+ |---|---|---|
17
+ | **empty** | 说明**为什么空** + 下一步动作 | "暂无数据"四个字把用户卡死 |
18
+ | **loading** | "在动"的指示(spinner 或骨架屏) | 用户以为加载已结束且结果为空 |
19
+ | **error** | 说清出了什么事 + **重试出口** | 死路,只能刷新页面(而刷新常常解决不了) |
20
+ | **success** | 结果摘要 + 下一步出口 | 用户不知道接下来去哪 |
21
+
22
+ 🔴 **error 的可恢复性是硬要求。** 只写"出错了"是死路。
23
+ 而且刷新往往解决不了问题(比如参数错了、文件格式不对)——必须给"改了再试"的路径。
24
+
25
+ ---
26
+
27
+ ## 骨架屏 vs spinner:按结构是否已知选
28
+
29
+ | | 骨架屏 | spinner |
30
+ |---|---|---|
31
+ | 何时用 | **结构已知**——知道会来"标题+三行+一张图" | **结构未知**——不知道会来什么 |
32
+ | 价值 | 内容到位时不跳动 | 不撒谎 |
33
+ | 用错的后果 | 结构不一致 → 内容到位时布局剧烈跳动 | 结构已知时显得慢 |
34
+
35
+ 🔴 **别在结构未知时用骨架屏**——那画出来的结构是在撒谎。
36
+
37
+ 骨架屏必须标 `aria-hidden="true"`,否则屏幕阅读器会把它读成一堆无意义色块。
38
+
39
+ ---
40
+
41
+ ## 300ms 延迟(需要 JS,约 10 行)
42
+
43
+ 请求在 300ms 内返回时**不要显示加载态**。骨架屏闪一下比不显示更糟——那是一次无意义的闪烁,反而让人觉得卡。
44
+
45
+ ```js
46
+ let t = setTimeout(() => showLoading(), 300); // 300ms 后才显示
47
+ fetch(...).then(r => { clearTimeout(t); showResult(r); });
48
+ ```
49
+
50
+ `demo.html` 里有两个按钮可以直接对比:120ms 的那个完全不显示加载态,1.4s 的才显示。
51
+
52
+ ---
53
+
54
+ ## reduced-motion 下:两个动画,处理相反
55
+
56
+ 这是本组件最值得记住的一条。判断依据是**去掉后是否损失信息**:
57
+
58
+ | 动画 | 处理 | 理由 |
59
+ |---|---|---|
60
+ | **spinner** | **保留**,放慢到 1.2s | 停了用户会以为页面卡死。原地旋转不属于"大幅位移" |
61
+ | **骨架屏扫光** | **停止**,保留静态占位 | 静态色块仍传达"内容正在来",不损失信息;而扫光正是横向位移 |
62
+
63
+ 同样是"加载中的动画",一个保留一个去掉。
64
+
65
+ ---
66
+
67
+ ## 无障碍三条
68
+
69
+ 1. loading 容器标 `aria-busy="true"`
70
+ 2. error / success 动态出现时用 `role="alert"` 或 `aria-live="polite"`——否则屏幕阅读器用户不知道状态变了
71
+ 3. 骨架屏标 `aria-hidden="true"`
72
+
73
+ ---
74
+
75
+ ## 反例
76
+
77
+ - ❌ **empty 和 loading 用同一个样式** —— 用户分不清是"在加载"还是"真没有"
78
+ - ❌ **empty 态不放动作按钮** —— 把用户卡死
79
+ - ❌ **error 态没有重试出口** —— 死路
80
+ - ❌ **结构未知时用骨架屏** —— 撒谎,且内容到位时跳动
81
+ - ❌ **请求 <300ms 也显示骨架屏** —— 闪一下更糟
82
+ - ❌ **把普通操作的成功做成整页 success** —— 成功是过渡态,用 button 的 `data-state="success"`(1.2s 自动消失)
83
+ - ❌ **把技术细节(错误码、堆栈)写进主文案** —— 用户要看的是"我该怎么办"。细节放 `.state__detail` 折叠
84
+
85
+ ---
86
+
87
+ ## 验收清单(替代自动门禁)
88
+
89
+ `states.py` 不检查容器态——它检查的是交互元素,容器不是。
90
+ 所以这一项**目前靠人工检查**,按下面七条过:
91
+
92
+ 1. 每个会承载数据的容器,是否都写了 empty 态?
93
+ 2. empty 态有没有说明"为什么是空的"?("还没有导入任何公司" > "暂无数据")
94
+ 3. empty 态有没有动作按钮?
95
+ 4. loading 是骨架屏还是 spinner?选择依据是不是"结构是否已知"?
96
+ 5. error 态有没有重试出口?
97
+ 6. 有没有做 300ms 延迟?
98
+ 7. 骨架屏有没有 `aria-hidden`?loading 容器有没有 `aria-busy`?
99
+
100
+ 🔴 为什么不做自动门禁:启发式只能查"有没有 empty 这个类",查不出"empty 态有没有动作按钮"——
101
+ 那才是真正容易漏的。做一个只能查到皮毛的脚本,会给出假绿。**不检查比假绿诚实。**
102
+
103
+ ## 钩子(CSS 有,JS 需你自己加类)
104
+
105
+ | 类 | 作用 | 谁来加 |
106
+ |---|---|---|
107
+ | `.field__input[data-state="valid"]` | 输入框"校验通过"态 | **你**:校验通过时加 `data-state="valid"` |
108
+
109
+ ⚠️ 状态一律写 `data-state` 属性,**不写状态类名**。理由见 `docs/INVARIANT.md` I-8:
110
+ 类名消失时 CSS 规则还在 ⇒ 静默错乱且无报错;属性是有限枚举,可被查询和校验。
111
+
112
+ ⚠️ **本库不提供 JS 实现** —— 这是留给复用者的钩子。
113
+ CSS 侧已经备好,JS 侧要自己写。写错了不会报错(属性没加就是默认态),
114
+ 所以**要自己验**。
115
+
116
+ ## 移除记录:`.state--delayed`(0.7.7 登记,0.8.0 移除)
117
+
118
+ 它承诺了一个「延迟显现」机制,但全库**没有任何 JS** 会去设置它依赖的
119
+ `data-state="shown"` ⇒ 使用者必须自己接,而文档当时没写明。
120
+ 它读起来像本库已经实现了 —— 这类名字最危险,因为**误会发生在读者脑子里,代码不会报错**。
121
+
122
+ **没有替代品**。想要"延迟 300ms 再显示骨架屏",自己写两行就是全部实现:
123
+
124
+ ```html
125
+ <div class="state state--loading js-delayed"><!-- 骨架屏 --></div>
126
+ ```
127
+
128
+ ```css
129
+ /* 自己加:默认藏着,到点才显 */
130
+ .js-delayed { visibility: hidden; }
131
+ .js-delayed[data-state="shown"] { visibility: visible; }
132
+ ```
133
+
134
+ ```js
135
+ setTimeout(function () {
136
+ el.setAttribute('data-state', 'shown');
137
+ }, 300);
138
+ ```
139
+
140
+ ⚠️ 注意:这么做在**请求永远不返回**时会一直空白。要么配一个兜底超时,
141
+ 要么只在"已知 300ms 内能返回"的场景用。
@@ -0,0 +1,240 @@
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>容器四态 · 真实场景</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/page.css">
11
+ <link rel="stylesheet" href="../../02-primitives/button/button.css">
12
+ <link rel="stylesheet" href="states.css">
13
+ <style>
14
+ /* 这一屏唯一的视觉焦点:标题。窄到必然换行 —— 标题才有存在感 */
15
+ .standfirst { font-size: var(--fs-md); color: var(--text-secondary);
16
+ max-width: var(--measure-lede); margin: 0 0 var(--sp-6); line-height: 1.6; }
17
+ h3 { font-size: var(--fs-md); margin: var(--sp-6) 0 var(--sp-2); }
18
+ p { max-width: var(--measure-prose); margin: 0 0 var(--sp-3); }
19
+ p.lead { color: var(--text-secondary); }
20
+ /* 状态切换器:四个按钮会读成"四张卡的导航",分段控件读成"一个东西的四个状态" */
21
+ .seg { display: inline-flex; border: 1px solid var(--border-control);
22
+ border-radius: var(--r-sm); overflow: hidden; margin-bottom: var(--sp-4); }
23
+ .seg button { appearance: none; border: 0; background: var(--surface);
24
+ padding: 9px 16px; font: inherit; font-size: var(--fs-xs);
25
+ color: var(--text-secondary); cursor: pointer;
26
+ border-right: 1px solid var(--border-decor); }
27
+ .seg button:last-child { border-right: 0; }
28
+ .seg button[aria-pressed="true"] { background: var(--accent); color: var(--text-on-accent); }
29
+ .seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
30
+ /* 舞台:整页唯一有边框的东西,因为它是演示主体 */
31
+ #stage { border: 1px solid var(--border-decor-str); border-radius: var(--r-md);
32
+ background: var(--surface); overflow: hidden; }
33
+ .stage-bar { display: flex; align-items: baseline; gap: var(--sp-3);
34
+ padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--border-decor);
35
+ background: var(--surface-sunken); }
36
+ .stage-bar strong { font-size: var(--fs-sm); }
37
+ .stage-bar span { font-size: var(--fs-xs); color: var(--text-tertiary); }
38
+ .stage-body { padding: var(--sp-2) 0; min-height: 288px; }
39
+ /* 真实数据行 */
40
+ .row { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-3) var(--sp-4); }
41
+ .row+.row { border-top: 1px solid var(--border-decor); }
42
+ .row:hover { background: var(--surface-sunken); }
43
+ .row__t { flex: 1; min-width: 0; }
44
+ .row__name { font-size: var(--fs-sm); }
45
+ .row__meta { font-size: var(--fs-xs); color: var(--text-tertiary); }
46
+ .tag { font-size: var(--fs-xs); padding: 2px 8px; border-radius: var(--r-pill);
47
+ border: 1px solid var(--border-decor); color: var(--text-secondary); white-space: nowrap; }
48
+ .tag--up { color: var(--danger); border-color: var(--danger); }
49
+ .tag--down { color: var(--success); border-color: var(--success); }
50
+ .aside { background: var(--warning-bg);
51
+ padding: var(--sp-3) var(--sp-4); font-size: var(--fs-sm);
52
+ max-width: var(--measure-prose); margin: var(--sp-4) 0; line-height: 1.65; }
53
+ .cmp2 { display: grid; gap: var(--sp-5); grid-template-columns: 1.55fr 1fr; align-items: start; }
54
+ @media (max-width: 620px) {.cmp2 { grid-template-columns: 1fr; }
55
+
56
+ }
57
+ .lbl { font-size: var(--fs-xs); color: var(--text-tertiary);
58
+ margin-bottom: var(--sp-2); display: block; }
59
+ </style>
60
+ </head>
61
+ <body class="page page">
62
+ <main class="wrap">
63
+
64
+ <h1>加载中和没有数据,是两件事</h1>
65
+ <p class="standfirst">
66
+ 视觉上它们都是"一片空白",但含义完全相反。前者是"在动",后者是"没东西"。
67
+ 混成一个,用户会以为系统坏了 —— 而他可能只是等了三秒。
68
+ </p>
69
+
70
+ <div class="seg" role="group" aria-label="切换状态">
71
+ <button type="button" data-s="content" aria-pressed="true">有数据</button>
72
+ <button type="button" data-s="loading" aria-pressed="false">加载中</button>
73
+ <button type="button" data-s="empty" aria-pressed="false">空</button>
74
+ <button type="button" data-s="error" aria-pressed="false">出错</button>
75
+ </div>
76
+
77
+ <div id="stage">
78
+ <div class="stage-bar">
79
+ <strong>企业分析工作区</strong>
80
+ <span id="stage-meta">331 实体 · 861k 条财务记录</span>
81
+ </div>
82
+ <div class="stage-body" id="stage-body" aria-live="polite"></div>
83
+ </div>
84
+
85
+ <p class="aside">
86
+ 切换时注意两件事:<strong>只有数据行在动,页头和工具条不动</strong>(布局稳定);
87
+ <strong>从"出错"点回"有数据"</strong>,内容出现的位置正好是它原来的位置。
88
+ </p>
89
+
90
+ <hr class="rule">
91
+
92
+ <h2>每态都必须给出口</h2>
93
+ <p class="lead">
94
+ 只描述现状不给动作,等于把用户卡死在那儿。这是四态里最容易漏的一条。
95
+ </p>
96
+ <p>
97
+ <strong>空</strong> —— 必须说明<em>为什么</em>是空的,并给下一步。
98
+ "还没有导入任何公司" 比 "暂无数据" 有用得多:前者告诉用户他在哪、缺什么。
99
+ </p>
100
+ <p>
101
+ <strong>出错</strong> —— 必须能恢复。只写"出错了"是死路,而且刷新往往解决不了问题
102
+ (参数错了、文件格式不对)。要告诉用户<em>哪里错了、怎么改</em>。
103
+ </p>
104
+ <p>
105
+ <strong>成功</strong> —— 是过渡态不是停留态。普通操作的成功反馈用按钮的
106
+ <code>data-state="success"</code>(1.2 秒自动消失),别做成一个整页。
107
+ </p>
108
+
109
+ <hr class="rule">
110
+
111
+ <h2>骨架屏还是 spinner:看结构是否已知</h2>
112
+ <div class="cmp2">
113
+ <div>
114
+ <span class="lbl">结构已知 · 骨架屏</span>
115
+ <div style="border:1px solid var(--border-decor); border-radius:var(--r-sm); padding:var(--sp-4)"
116
+ role="status" aria-live="polite" aria-label="正在加载内容">
117
+ <div class="skeleton skeleton--title" aria-hidden="true"></div>
118
+ <div class="skeleton skeleton--text" aria-hidden="true"></div>
119
+ <div class="skeleton skeleton--text" aria-hidden="true"></div>
120
+ <div class="skeleton skeleton--text" aria-hidden="true"></div>
121
+ <div class="skeleton skeleton--block" style="margin-top:var(--sp-3)" aria-hidden="true"></div>
122
+ </div>
123
+ <p style="font-size:var(--fs-xs); color:var(--text-tertiary); margin-top:var(--sp-2)">
124
+ 知道会来"标题 + 三行 + 一张图",就照这个结构画占位。
125
+ 价值不在好看,在于<strong>内容到位时不跳动</strong>。
126
+ </p>
127
+ </div>
128
+ <div>
129
+ <span class="lbl">结构未知 · spinner</span>
130
+ <div style="border:1px solid var(--border-decor); border-radius:var(--r-sm);
131
+ min-height:170px; display:grid; place-items:center">
132
+ <div class="spinner"></div>
133
+ </div>
134
+ <p style="font-size:var(--fs-xs); color:var(--text-tertiary); margin-top:var(--sp-2)">
135
+ 🔴 别在结构未知时用骨架屏。画出来的结构跟实际不一致,
136
+ 内容到位时布局会剧烈跳动 —— <strong>那是在撒谎</strong>。
137
+ </p>
138
+ </div>
139
+ </div>
140
+
141
+ <hr class="rule">
142
+
143
+ <h2>300ms:不显示比闪一下好</h2>
144
+ <p>
145
+ 请求在 300ms 内返回时<strong>不要显示加载态</strong>。骨架屏闪一下比不显示更糟 ——
146
+ 那是一次无意义的闪烁,反而让人觉得卡。正确做法是延迟 300ms 再显示(约 10 行 JS)。
147
+ </p>
148
+
149
+ <hr class="rule">
150
+
151
+ <h2>reduced-motion 下,两个动画处理相反</h2>
152
+ <p>
153
+ 同样是"加载中的动画",一个保留一个去掉。<strong>判断依据是去掉后是否损失信息</strong>:
154
+ </p>
155
+ <p>
156
+ <strong>spinner 保留但放慢</strong> —— 它停了用户会以为页面卡死。
157
+ reduced-motion 针对的是大幅位移(前庭功能障碍),小幅度原地旋转不属于此类。
158
+ </p>
159
+ <p>
160
+ <strong>骨架屏扫光停止</strong> —— 静态占位块仍然传达"这里有内容正在来",
161
+ 不损失信息;而扫光恰好是横向位移,正是要减少的那类。
162
+ </p>
163
+
164
+ <script>
165
+ // 真实数据 —— 不是 User 123,不是 Lorem ipsum
166
+ // 这是模板生成器做不出来的部分:本库所有 demo 都用真实项目的真数据
167
+ var DATA = [
168
+ { name: '中国平安', meta: '26,831 条记录 · 金融', chg: '+2.41%', dir: 'up' },
169
+ { name: '宁德时代', meta: '19,204 条记录 · 工业', chg: '-0.87%', dir: 'down' },
170
+ { name: '招商银行', meta: '31,556 条记录 · 金融', chg: '+1.13%', dir: 'up' },
171
+ { name: '比亚迪', meta: '28,912 条记录 · 工业', chg: '+3.69%', dir: 'up' },
172
+ { name: '长江电力', meta: '17,443 条记录 · 公用事业', chg: '-1.24%', dir: 'down' }
173
+ ];
174
+
175
+ var body = document.getElementById('stage-body');
176
+ var meta = document.getElementById('stage-meta');
177
+
178
+ function rows() {
179
+ return DATA.map(function (d) {
180
+ return '<div class="row"><div class="row__t">' +
181
+ '<div class="row__name">' + d.name + '</div>' +
182
+ '<div class="row__meta">' + d.meta + '</div></div>' +
183
+ '<span class="tag ' + (d.dir === 'up' ? 'tag--up' : 'tag--down') + '">' +
184
+ d.chg + '</span></div>';
185
+ }).join('');
186
+ }
187
+
188
+ var VIEWS = {
189
+ content: function () {
190
+ meta.textContent = '331 实体 · 861k 条财务记录';
191
+ return rows();
192
+ },
193
+ loading: function () {
194
+ meta.textContent = '正在读取…';
195
+ return '<div class="state state--loading" aria-busy="true" style="min-height:288px">' +
196
+ '<div class="spinner"></div>' +
197
+ '<p class="state__desc">正在读取 861k 条记录…</p></div>';
198
+ },
199
+ empty: function () {
200
+ meta.textContent = '0 条记录';
201
+ return '<div class="state state--empty" style="min-height:288px">' +
202
+ '<div class="state__icon" aria-hidden="true">□</div>' +
203
+ '<p class="state__title">这个时间范围下还没有记录</p>' +
204
+ '<p class="state__desc">WS_95 的公司从 2024 年才开始有数据。' +
205
+ '把范围放宽到 2023 年试试,或者改看沪深 300 全池。</p>' +
206
+ '<div class="state__action">' +
207
+ '<button class="btn btn--sm btn--primary" type="button">放宽到 2023</button>' +
208
+ '<button class="btn btn--sm btn--secondary" type="button">改看全池</button>' +
209
+ '</div></div>';
210
+ },
211
+ error: function () {
212
+ meta.textContent = '读取失败';
213
+ return '<div class="state state--error" role="alert" style="min-height:288px">' +
214
+ '<div class="state__icon" aria-hidden="true">!</div>' +
215
+ '<p class="state__title">没能连上数据库</p>' +
216
+ '<p class="state__desc">连接在读取第 <span class="num">12,480</span> 条时超时。' +
217
+ '刚才的筛选条件还在,重试即可,不用重新设一遍。</p>' +
218
+ '<div class="state__action">' +
219
+ '<button class="btn btn--sm btn--primary" type="button">重试</button>' +
220
+ '<button class="btn btn--sm btn--secondary" type="button">复制诊断信息</button>' +
221
+ '</div></div>';
222
+ }
223
+ };
224
+
225
+ function show(k) { body.innerHTML = VIEWS[k](); }
226
+
227
+ Array.prototype.forEach.call(document.querySelectorAll('.seg button'), function (b) {
228
+ b.addEventListener('click', function () {
229
+ Array.prototype.forEach.call(document.querySelectorAll('.seg button'), function (x) {
230
+ x.setAttribute('aria-pressed', 'false');
231
+ });
232
+ b.setAttribute('aria-pressed', 'true');
233
+ show(b.dataset.s);
234
+ });
235
+ });
236
+
237
+ show('content');
238
+ </script></main>
239
+ </body>
240
+ </html>