agentcss 0.0.0-stage → 0.11.2

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 +3116 -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 +491 -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 +275 -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,153 @@
1
+ # sparkline · 迷你趋势图
2
+
3
+ 在一行里显示"最近走势"。**它是辅助,不是图表。**
4
+
5
+ ## 用法
6
+
7
+ ```html
8
+ <span class="sparkline" data-spark="12,18,15,24,22,31,28"></span>
9
+ ```
10
+
11
+ | 属性 | 值 | 默认 |
12
+ |---|---|---|
13
+ | `data-spark` | 逗号分隔的数字 | — |
14
+ | `data-spark-type` | `line` / `bar` / `area` | `line` |
15
+ | `data-spark-height` | 像素 | `32` |
16
+ | `data-spark-color` | CSS 颜色或 `var(--x)` | `var(--accent)` |
17
+ | `data-spark-fill` | `no` 关闭面积填充 | 填 |
18
+ | `data-spark-last` | `yes` 标出最后一个点 | 不标 |
19
+ | `data-spark-animate` | `no` 关闭入场动画 | 开 |
20
+
21
+ 更新数据:
22
+
23
+ ```js
24
+ el.setAttribute('data-spark', '1,2,3');
25
+ Chart.emit(el); // 重画这一个
26
+ Chart.update(container); // 重画某区域内全部
27
+ ```
28
+
29
+ ## 🔴 无障碍:它没有可读的文本等价物
30
+
31
+ 纯 SVG 路径对读屏**没有意义** —— 读屏只会说"图像"。
32
+
33
+ ⇒ 本组件**刻意不加 `role="img"`**(那会让人以为它有可读的替代文本,而它没有)
34
+ ⇒ SVG 标了 `aria-hidden="true"` + `focusable="false"`
35
+ ⇒ **要给出可读的值**:在旁边放数字,或用「名字 + 当前值 + 涨跌」的组合
36
+ ⇒ **必须**配文字的场景:数值重要时(如"营收 128 万,同比 +3.2%")。
37
+ 纯装饰的小图可以不配。
38
+
39
+ **只放 sparkline 不放数字 = 把视觉当成唯一通道**,这是反例。
40
+
41
+ ## 动画
42
+
43
+ 用 `stroke-dasharray` 做"从左往右画出来"的效果。
44
+
45
+ **为什么不用 JS 逐帧**:
46
+ ① 零 JS 帧循环,动画交给合成器
47
+ ② **可被 `prefers-reduced-motion` 一行 CSS 关掉**
48
+ ③ 几十个同时动画也不掉帧
49
+
50
+ **关掉动画的三种情况**(CSS 里都做了):
51
+ ① 用户在系统里说了"减少动态效果"(WCAG 2.3.3,前庭功能障碍者会眩晕)
52
+ ② 打印时(`@media print`)
53
+ ③ 元素上写 `data-spark-animate="no"`
54
+
55
+ ## 为什么自己写而不引库
56
+
57
+ 调研了 GitHub 上几个零依赖方案,最小的 `mitjafelicijan/sparklines`
58
+ 是 **6390 字节 / 195 行 / gzip 1675 字节**、**BSD-2 许可**(可商用)——
59
+ **它做得很好,值得参考**。
60
+
61
+ 但没有直接引,两个原因(都不是嫌它差):
62
+
63
+ | | 它 | 本库要求 |
64
+ |---|---|---|
65
+ | 语法 | `const` + 箭头函数(ES6)| **ES5**(精简档 跑在 某项目 的 WebView,版本不确定)|
66
+ | 颜色 | data 属性传 HEX / 颜色名 | **令牌驱动**(换主题要跟着变)|
67
+
68
+ ⇒ **借鉴它的接口设计**(data 属性驱动、零依赖、BSD 思路),
69
+ 实现按本库约束重写。
70
+
71
+ **当前体量**:`sparkline.js` 约 8 KB · `sparkline.css` 约 2 KB · 零依赖。
72
+
73
+ ## 🔴 修两个把线画坏的 bug(真机实测截图指出)
74
+
75
+ ### ① `preserveAspectRatio="none"` ⇒ 横向拉伸
76
+
77
+ 它允许 **x/y 独立缩放**。本组件 viewBox 宽度已经是容器**实际像素宽**,
78
+ `width:100%` 渲染到同一宽度 ⇒ **横向本来就不该缩放**,
79
+ 但 `none` 仍会在容器宽 ≠ viewBox 宽时(整数舍入 / 滚动条 / flex 未稳定)
80
+ 把图形**拉变形**。
81
+
82
+ ⇒ 删掉,走默认 `xMidYMid meet`:**1px 就是 1px**,比例有出入也只等比留边。
83
+
84
+ ### ② 🔴 CSS 的 `fill` 覆盖了 `fill="none"` 属性
85
+
86
+ 我原来写「path / rect / circle 一律 `fill: var(--spark-c)`」——
87
+ **CSS 优先级高于 SVG presentation attribute**,
88
+ JS 里写的 `fill="none"` **被完全无视**。
89
+
90
+ 实测:`fill` 属性 = `none`,**计算值 = `rgb(27,77,143)`** ⇒
91
+ **折线被填成实心多边形** ⇒ 看起来是「扁平的色带」而不是趋势线。
92
+
93
+ **这才是"色带"的直接原因**(① 只是让它更扁)。
94
+
95
+ ⇒ 修法:**按类型分四类**,不靠属性覆盖(属性打不过 CSS):
96
+ `.spark-line` 描边 / `.spark-area` 填充 / `.spark-bar` 填充 / `.spark-dot` 填充。
97
+
98
+ ### ③ 连带发现:动画类被牵连(真 bug)
99
+
100
+ 重写外观后 `spark-line` 必须**常驻**(它负责 `fill:none`),
101
+ 于是旧逻辑「没动画就不加 `spark-line` 类」**形同虚设** ——
102
+ **动画会无视设置照播**。
103
+
104
+ ⇒ 拆出独立的 `.spark-anim` 专管动画。契约测试已改判据并锁住。
105
+
106
+ ## 🔴 MISSING != ZERO(最重要的一条)
107
+
108
+ **空值和 0 是两件事。**
109
+
110
+ | 写法 | 含义 | 画成什么 |
111
+ |---|---|---|
112
+ | `data-spark="1,0,3"` | 1、**真的 0**、3 | 一条连续的线,中间有个低谷 |
113
+ | `data-spark="1,,3"` | 1、**没数据**、3 | **两段线,中间断开** |
114
+ | `data-spark="1,2,3,4,"` | 末点没数据 | 两段线,**不画末点圆点** |
115
+
116
+ **为什么这么重要**:一条横跨缺失点的线,等于在说"从 1 平滑升到 3,
117
+ 中间一切正常" —— 而真相是"中间没数据"。
118
+ **数据缺失被画成了平稳过渡**,这是图表最容易忽略、最会误导人的一种错。
119
+
120
+ **契约来源**(不是我想的):`ChartsPage.tsx` 第 10 行自己写着
121
+ > 「MISSING != ZERO / INVALID != MISSING / 过滤必须可见」
122
+
123
+ 它自己做到了 `connectNulls={false}` 且 0 处遗漏 —— 本库照它做,
124
+ 并在自己的契约测试里锁死这六条。
125
+
126
+ **实现要点**:
127
+ - `parsePoints` 把空值 / `-` / `null` / `NaN` 都保留成 `null`(**不是 0**)
128
+ - `scale` 求最值时**排除 null**(否则 `Math.min(1, null, 3) === 1` 会把图压扁)
129
+ - `draw` 把序列切成**连续段**,每段画一条 path,**段间留空**
130
+ - 柱状图:缺失点**不画柱**(画成 0 高度等于假装"这里是 0")
131
+
132
+ ## 边界情况(都实测过)
133
+
134
+ | 输入 | 行为 |
135
+ |---|---|
136
+ | 少于 2 个**有效**点 | 不画(画不出趋势)|
137
+ | **全部缺失**(`,,,,`)| 不画,且**不崩**(`scale` 返回 null 有防护)|
138
+ | 中间缺失 | **断开成两段**,不连线 |
139
+ | **全部相同**(`10,10,10`)| 人工造上下界,**不除零**,画成一条水平线 |
140
+ | 单点成段 | 画成一个小圆点(否则那段"什么都不画",用户以为图坏了)|
141
+ | 元素宽度为 0(隐藏中)| 用 `data-spark-width` 或等布局就绪 |
142
+
143
+ ## 验证
144
+
145
+ **28 项全过**,含 `MISSING != ZERO` 六条:
146
+ 连续=1 段 · 中间缺失=2 段 · **两段有真实间隔(381px)** · 中间是 0 仍 1 段 ·
147
+ 全缺失不崩 · 末点缺失不画圆点。
148
+
149
+ 另:`aria-hidden` 9/9 · `role=img` 0 个 · axe 0 违规 / 19 通过。
150
+
151
+ **两个判别力验证**:
152
+ ① 复现"类名不一致"那个真 bug ⇒ 8 项 FAIL
153
+ ② 注入"空值丢弃"老写法 ⇒ 3 项 FAIL
@@ -0,0 +1,163 @@
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
+ <title>sparkline · 迷你趋势图</title>
7
+ <link rel="stylesheet" href="../../01-tokens/tokens.css">
8
+ <link rel="stylesheet" href="../../01-tokens/typography.css">
9
+ <link rel="stylesheet" href="../../01-tokens/page.css">
10
+ <link rel="stylesheet" href="../../02-primitives/button/button.css">
11
+ <link rel="stylesheet" href="../../09-assets/sparkline/sparkline.css">
12
+ <style>
13
+ .row { display: flex; align-items: center; gap: var(--sp-4);
14
+ padding: var(--sp-3) 0; }
15
+ .name { width: 82px; flex: 0 0 auto; font-size: var(--fs-sm); }
16
+ .val { width: 96px; flex: 0 0 auto; font-family: var(--mono);
17
+ font-size: var(--fs-sm); text-align: right; }
18
+ .chg { width: 68px; flex: 0 0 auto; font-size: var(--fs-xs); }
19
+ .up { color: var(--danger); }
20
+ /* A 股习惯:涨红 */
21
+ .down { color: var(--success); }
22
+ /* 🔴 修:原来这里 flex:1 1 auto 加在 .sparkline 上, 而组件的 .sparkline 自带 width:100% ⇒ **宽度算两遍**, 在 flex 行里被压成 **5px 宽**(实测)⇒ 趋势图变成一条竖线、 与旁边的文字糊在一起。 ⇒ 解法:flex 作用在**包装层**,组件本身只管自己的宽度。 🔴 修 iOS 实机报的「趋势图出边框」(实测溢出 76px): `.row` 是 `flex-wrap: nowrap`,前三列固定宽(82+96+68=246px) 已吃掉 300px 里的大部分,而我给 `.spark-wrap` 设了 **`min-width: 80px` 阻止它收缩** ⇒ flex 放不下 ⇒ **必然溢出**。 ⚠️ 我一度判断成「inline 元素 flex 无效」—— **那是错的**(实测 display:block)。 ⇒ ① `min-width: 0`+`flex: 1 1 0`(**允许被压到任意窄**,flex 收缩的必要条件) ⚠️ 试过 `flex-wrap: wrap`(让极窄屏换行)—— **不行**: 换行后 spark-wrap 独占整行 300px,但里面的 `.sparkline` 仍被 压成 5px 竖线(截图实测)。**换行不解决宽度计算问题,只会让布局更乱**。 ⚠️ 试过只给 spark-wrap 收缩 —— **也不行**,实测压成 **5px**: 三个固定列(`flex: 0 0 auto`)已经吃掉 246px+gap, 393px 屏里**真的没有空间**给第四列了 (320px 时连 0px 都没有)。 ⇒ **真正的解法:数字列必须允许收缩**。 它们的 `flex: 0 0 auto` 是错的 —— 数字宽度虽固定, 但**优先牺牲它、保证走势图可见**才是对的优先级 (走势图是这一行的信息主体,数字可以截断)。 ⚠️ 这是**布局优先级**问题,不是 flex 语法问题。 */
23
+ .row { flex-wrap: nowrap; }
24
+ .name, .val, .chg { flex: 0 1 auto; min-width: 0; }
25
+ /* 🔴 允许收缩 */
26
+ .val { flex-shrink: 2; }
27
+ /* 🔴 数字先让位 */
28
+ .spark-wrap { flex: 1 1 0; min-width: 0; }
29
+ /* 走势图至少 40px */
30
+ .spark-wrap { min-width: 40px; }
31
+
32
+ /* 🔴 但别小于 40 */
33
+ </style>
34
+ </head>
35
+ <body class="page page">
36
+ <main class="wrap">
37
+ <h1>sparkline · 迷你趋势图</h1>
38
+ <p class="lede">
39
+ 在一行里显示"最近走势"。它是<strong>辅助</strong>不是图表 ——
40
+ 读屏读不出路径,所以<strong>旁边必须有数字</strong>。
41
+ </p>
42
+
43
+ <h2 class="rule-top">真实语境(数字 + 涨跌 + 走势)</h2>
44
+ <div class="box">
45
+ <div class="row">
46
+ <span class="name">600519</span>
47
+ <span class="val">1,684.20</span>
48
+ <span class="chg up">+2.31%</span>
49
+ <span class="spark-wrap"><span class="sparkline" data-spark="1602,1618,1609,1631,1625,1648,1662,1655,1684"></span></span>
50
+ </div>
51
+ <div class="row">
52
+ <span class="name">000001</span>
53
+ <span class="val">11.87</span>
54
+ <span class="chg down">−0.84%</span>
55
+ <span class="spark-wrap"><span class="sparkline" data-spark="12.05,12.11,12.02,11.96,11.99,11.92,11.88,11.91,11.87"></span></span>
56
+ </div>
57
+ <div class="row">
58
+ <span class="name">688981</span>
59
+ <span class="val">86.40</span>
60
+ <span class="chg up">+5.12%</span>
61
+ <span class="spark-wrap"><span class="sparkline" data-spark="82.1,82.8,83.4,83.1,84.2,84.9,85.3,85.0,86.4"></span></span>
62
+ </div>
63
+ </div>
64
+ <p class="small">
65
+ 每行都有<strong>名字 + 当前值 + 涨跌</strong>,走势图是第四层信息。
66
+ 这就是为什么<strong>不能只有 sparkline</strong>。
67
+ </p>
68
+
69
+ <h2 class="rule-top">三种形态</h2>
70
+ <div class="box">
71
+ <p class="small">折线(默认)</p>
72
+ <span class="sparkline" data-spark="12,18,15,24,22,31,28" data-spark-height="48"
73
+ data-spark-last="yes"></span>
74
+
75
+ <p class="small" style="margin-top:var(--sp-5)">面积(fill=yes)</p>
76
+ <span class="sparkline" data-spark-type="area" data-spark="12,18,15,24,22,31,28"
77
+ data-spark-height="48" data-spark-last="yes"></span>
78
+
79
+ <p class="small" style="margin-top:var(--sp-5)">柱状</p>
80
+ <span class="sparkline" data-spark-type="bar" data-spark="12,18,15,24,22,31,28"
81
+ data-spark-height="48"></span>
82
+ </div>
83
+
84
+ <h2 class="rule-top">换主题会自动跟着变</h2>
85
+ <div class="box">
86
+ <p class="small">
87
+ 颜色默认是 <code>var(--accent)</code>,<strong>不是写死的蓝</strong> ——
88
+ 切到暗色它会跟着变。
89
+ </p>
90
+ <span class="sparkline" data-spark="12,18,15,24,22,31,28" data-spark-height="52"
91
+ data-spark-color="var(--danger)"></span>
92
+ <p class="small" style="margin-top:var(--sp-3)">
93
+ 上面这条是 <code>var(--danger)</code>(本库的涨红)。
94
+ </p>
95
+ </div>
96
+
97
+ <h2 class="rule-top">点击更新数据</h2>
98
+ <div class="box">
99
+ <span class="sparkline" id="upd" data-spark="12,18,15,24,22,31,28"
100
+ data-spark-height="56" data-spark-last="yes"></span>
101
+ <p style="margin-top:var(--sp-4)">
102
+ <button class="btn btn--md btn--secondary" id="btn">换一组数据</button>
103
+ </p>
104
+ <p class="small">
105
+ 更新只需改 <code>data-spark</code> 再调 <code>Chart.emit(el)</code>。
106
+ </p>
107
+ </div>
108
+
109
+ <h2 class="rule-top">🔴 无障碍:它没有可读的文本等价物</h2>
110
+ <div class="box bad">
111
+ <p>
112
+ 纯 SVG 路径对读屏<strong>没有意义</strong> ——
113
+ 读屏只会说"图像",不会说"最近上涨 2.3%"。
114
+ </p>
115
+ <p class="small">
116
+ ⇒ 所以本组件<strong>刻意不加 <code>role="img"</code></strong>
117
+ (那会让人以为它有可读的替代文本,而它没有)。
118
+ ⇒ <strong>要给出可读的值</strong>:在旁边放数字,
119
+ 或用上面那种「名字 + 当前值 + 涨跌」的组合。
120
+ ⇒ 什么时候<strong>必须</strong>配文字:数值重要时
121
+ (例如"营收 128 万,同比 +3.2%")。纯装饰的小图可以不配。
122
+ </p>
123
+ </div>
124
+
125
+ <h2 class="rule-top">反例</h2>
126
+ <div class="box bad">
127
+ <h3>反例:只放 sparkline,没有数字</h3>
128
+ <p class="small">
129
+ 视觉上"很有信息",但读屏用户<strong>什么都不知道</strong> ——
130
+ 这是把视觉当成了唯一通道。
131
+ </p>
132
+ <span class="sparkline" data-spark="12,18,15,24,22,31,28" data-spark-height="40"></span>
133
+ </div>
134
+ <div class="box bad">
135
+ <h3>反例:动画不管 reduced-motion</h3>
136
+ <p class="small">
137
+ 前庭功能障碍者会因动画眩晕(WCAG 2.3.3)。
138
+ 本组件在 <code>@media (prefers-reduced-motion: reduce)</code> 下
139
+ <strong>完全关掉动画</strong>,且打印时也不动。
140
+ </p>
141
+ </div>
142
+ </main>
143
+ <script src="sparkline.js"></script>
144
+ <script>
145
+ // 仅为 demo:换数据
146
+ (function () {
147
+ var el = document.getElementById('upd');
148
+ var sets = [
149
+ '12,18,15,24,22,31,28',
150
+ '4,9,6,14,11,22,19,30',
151
+ '28,24,25,18,20,12,14,6',
152
+ '10,10,10,10,10,10,10' // 全平 —— 看几何怎么处理除零
153
+ ];
154
+ var i = 0;
155
+ document.getElementById('btn').addEventListener('click', function () {
156
+ i = (i + 1) % sets.length;
157
+ el.setAttribute('data-spark', sets[i]);
158
+ window.Chart.emit(el);
159
+ });
160
+ })();
161
+ </script>
162
+ </body>
163
+ </html>
@@ -0,0 +1,158 @@
1
+ /* ============================================================================
2
+ sparkline — 迷你趋势图的样式
3
+ ============================================================================
4
+ 🔴 为什么要单独一个 CSS 文件(它只有一个类)
5
+ ----------------------------------------------------------------------------
6
+ 因为**动画**。SVG 的 path 动画属于"视觉层",放 JS 里就变成内联 style,
7
+ 换主题与 reduced-motion 都难管。放 CSS 里才能被 motion 门禁盯住。
8
+
9
+ 🔴 动画为什么用 stroke-dasharray 而不是 JS 逐帧
10
+ ----------------------------------------------------------------------------
11
+ ① **零 JS 帧循环** —— 动画完全交给合成器,JS 只画一次路径。
12
+ ② **可被 prefers-reduced-motion 关掉** —— 一行 CSS 的事,
13
+ 写在 JS 里要自己写 if 判断(而且容易漏)。
14
+ ③ **不阻塞主线程** —— 几十个 sparkline 同时动画也不会掉帧。
15
+ */
16
+
17
+ /* 🔴 **修 真机实测反馈的"趋势图和文字重叠"**
18
+
19
+ 根因(实测,不是猜的):
20
+ ① 这里 `height: 32px` 是**写死的**,但 JS 按 `data-spark-height`
21
+ 可以画成 48 / 52 / 56px ⇒ **容器装不下**
22
+ ② `overflow: visible` ⇒ 溢出的部分**直接画到容器外面**,
23
+ 压在下面一行的文字上
24
+
25
+ ⭐ 这个判断是对的,而且这是**通用设计规范**:
26
+ **任何图都不能与文字重叠。** 图要么在自己的框里,
27
+ 要么明确留出间距 —— 不能靠"溢出"去侵占别的元素的空间。
28
+
29
+ ⇒ 修法:
30
+ ① 容器高度**由 JS 同步写入**(消除"容器与图不一致"这个根)
31
+ ② overflow 改 hidden(溢出即被裁,不会压到别处)
32
+ ⚠️ 这里**不能用 visible**:末尾那个"标出最后一点"的圆点
33
+ 是画在 path 上的,不会出框;改成 hidden 是安全的。 */
34
+ .sparkline {
35
+ display: block;
36
+ /* 🔴 iOS 实机报的「出边框」修复:
37
+ `width: 100%` 在**两层**都会被算:
38
+ 外层 `.spark-wrap` 是 flex 项(要收缩)
39
+ 内层 `.sparkline` 又是 100%
40
+ ⇒ **同一个宽度算两遍**,收缩量被吃掉,组件被压成 5px 竖线。
41
+ 修法:**`min-width: 0` 允许 flex 收缩**(这是 flex 收缩的前提),
42
+ 宽度仍交给 `width:100%`—— 但父层必须允许它被压。 */
43
+ min-width: 0;
44
+ width: 100%;
45
+ height: var(--spark-h, 32px);
46
+ overflow: hidden;
47
+ /* 🔴 图与文字之间的间距:组件自己给,不靠调用方记得留 */
48
+ margin: var(--sp-1) 0;
49
+ }
50
+
51
+ .sparkline svg {
52
+ display: block;
53
+ width: 100%;
54
+ overflow: hidden;
55
+ }
56
+
57
+ /* 🔴 颜色**一律走 CSS**,不用 stroke="var(--x)" 属性。
58
+ presentation attribute 上的 var() 各浏览器支持不一致(规范没保证,
59
+ Safari 尤其不可靠)。JS 只写 --spark-c,具体取值由这里决定 ——
60
+ 顺带得到一个好处:**调用方可以用 CSS 覆盖颜色,不必改 data 属性**。 */
61
+ /* 🔴 **修「趋势图变成扁平色带」**(真机实测截图指出的)
62
+
63
+ ⚠️ 我原来写成「path / rect / circle 一律 fill: var(--spark-c)」——
64
+ **这会把折线也填成面**!因为
65
+ **CSS 的优先级高于 SVG presentation attribute**,
66
+ JS 里写的 `fill="none"` **被完全无视**。
67
+ 实测:fill 属性 = none,但计算值 = rgb(27,77,143) ⇒
68
+ 折线被填成了实心多边形 ⇒ 看起来是「扁平的色带」而不是趋势线。
69
+
70
+ ⇒ 修法:**按类型分开**,不靠属性覆盖(属性打不过 CSS):
71
+
72
+ .spark-line → 只描边,不填充(折线)
73
+ .spark-area → 填充(面积图)
74
+ .spark-bar → 填充(柱状图)
75
+ .spark-dot → 填充(端点/单点) */
76
+ .spark-line {
77
+ stroke: var(--spark-c, var(--accent));
78
+ fill: none;
79
+ }
80
+ .spark-area {
81
+ stroke: none;
82
+ fill: var(--spark-c, var(--accent));
83
+ }
84
+ .spark-bar {
85
+ stroke: none;
86
+ fill: var(--spark-c, var(--accent));
87
+ }
88
+ .spark-dot {
89
+ stroke: none;
90
+ fill: var(--spark-c, var(--accent));
91
+ }
92
+
93
+ /* 放在一行里(表格/列表右侧那种)时不该有上下外边距,
94
+ 否则会把那一行撑高、看起来错位 */
95
+ .row > .sparkline,
96
+ td > .sparkline,
97
+ .spark { margin: 0; }
98
+
99
+ /* 🔴 动画独立成 `.spark-anim`(原来复用 .spark-line,
100
+ 重写外观后那个判断就失效了 —— 动画会无视设置照播)。
101
+
102
+ 🔴 0.8.0 修:**基态必须是"看得见",动画负责"从不可见到可见"**
103
+ ----------------------------------------------------------------------------
104
+ 原来写反了:
105
+ .spark-anim { stroke-dashoffset: var(--spark-len, 1000); ← 基态:整条推出可见区
106
+ animation: spark-draw … forwards } ← 靠它才变可见
107
+ @keyframes spark-draw { to { stroke-dashoffset: 0 } }
108
+
109
+ 这个写法有个致命性质:**动画一旦被取消,线就整条消失**
110
+ (取消 ⇒ 回落到基态 = 完全偏移 ⇒ 没有可见线段)。
111
+ 实测踩到两处,都不是理论推演:
112
+
113
+ ① **视觉门禁假红**。`shot-baseline.py` 为了"冻结动画再截图"会注入
114
+ `animation:none !important` ⇒ sparkline 的线整条不见 ⇒
115
+ 报"21 个块变化、观感不一致",重跑又绿 —— 表现为**偶发假红**。
116
+ (为什么偶发:`.spark-anim` 只在**首绘**时加;ResizeObserver 会紧接着
117
+ 再绘一次且**不加**这个类 ⇒ 截到"首绘态"就看不见、截到"重绘态"就正常。
118
+ ⇒ 每次截图落在哪个态,取决于 RO 回调与截图的先后 —— 纯竞态。)
119
+ ② **任何第三方 CSS 重置 / 用户样式**把动画关掉,线都会消失。
120
+ 这不是假设:`@media print` 与 `prefers-reduced-motion` 都得自己
121
+ 再补一行 `stroke-dashoffset: 0` 才不出错 —— 那正是"写反了"的味道。
122
+
123
+ ⇒ 正确写法:基态 `0`(可见),动画的 `from` 才是"完全偏移"。
124
+ 取消动画最多是"少一次动效",绝不会变成"少一张图"。
125
+
126
+ ⚠️ `--spark-len` 兜底 1000 是**承重的**:dasharray 必须 ≥ 路径长度,
127
+ 否则"偏移 1000"落不到可见区之外,入场效果会变形。
128
+ 目前全库没有任何 JS 去写这个变量(早先的注释说 JS 会写,是错的),
129
+ 所以 1000 就是实际生效值。超过 1000px 宽的 sparkline 不在本库场景内。 */
130
+ .spark-anim {
131
+ stroke-dasharray: var(--spark-len, 1000);
132
+ stroke-dashoffset: 0;
133
+ animation: spark-draw var(--dur-slow, 600ms) var(--ease-out) forwards;
134
+ }
135
+
136
+ @keyframes spark-draw {
137
+ from { stroke-dashoffset: var(--spark-len, 1000); }
138
+ to { stroke-dashoffset: 0; }
139
+ }
140
+
141
+ /* 🔴 关掉动画的三种情况(缺一不可):
142
+
143
+ ① 用户在系统里说了"减少动态效果" —— 这是**必须尊重的**
144
+ (前庭功能障碍者会因动画眩晕,WCAG 2.3.3)。
145
+ ② 屏幕上的图是**纯装饰**(旁边已经有完整数字)—— 动它只是噪音。
146
+ ③ 页面在后台(切走了还在动就是浪费电量)。
147
+ */
148
+ @media (prefers-reduced-motion: reduce) {
149
+ .spark-anim {
150
+ animation: none;
151
+ stroke-dashoffset: 0;
152
+ }
153
+ }
154
+
155
+ /* 打印时也别动 —— 打印是静态的,动画只会拖慢 */
156
+ @media print {
157
+ .spark-anim { animation: none; stroke-dashoffset: 0; }
158
+ }