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,456 @@
1
+ /**
2
+ * tree.js — 树形视图(D1 /
3
+ *
4
+ * 依据:APG Tree View 模式
5
+ * ============================================================================
6
+ * ⭐ 两条设计要点(都不显然,但缺了就"用起来别扭")
7
+ *
8
+ * ① **Roving tabindex**(游标式 tabindex)
9
+ * 整棵树**只占一个 Tab 位**:当前节点 tabindex=0,其余全是 -1。
10
+ * ⇒ 若每个节点都 tabindex=0,Tab 30 次才能穿过一棵树。
11
+ * ⇒ 这也是 APG 的明确要求。
12
+ *
13
+ * ② **↑↓ 只移动焦点,←→ 才展开/收起**(且会带着焦点走)
14
+ * APG 规定:
15
+ * → 收起时:若已展开,先收起;已收起则**移到父节点**
16
+ * ← 展开时:若已收起,先展开;已展开则**移到第一个子节点**
17
+ * ⇒ 这条最容易做成"只展开不移动焦点",键盘用户会觉得"按了没反应"。
18
+ *
19
+ * 无依赖 · ES5
20
+ */
21
+ (function (global) {
22
+ 'use strict';
23
+
24
+ /* ==== BEHAVIOR INJECT BEGIN: emit ==== */
25
+ var flEmit = function (el, name, detail) {
26
+ if (!el || !el.dispatchEvent) return null;
27
+ var type = name.indexOf('fl-') === 0 ? name : 'fl-' + name;
28
+ var ev = null;
29
+ /* 老 WebView 没有 CustomEvent 构造函数 ⇒ 兜底走 createEvent(理由见文件头)*/
30
+ if (typeof window.CustomEvent === 'function') {
31
+ try {
32
+ ev = new window.CustomEvent(type, {
33
+ detail: detail || null, bubbles: true, cancelable: false
34
+ });
35
+ } catch (e) { ev = null; }
36
+ }
37
+ if (!ev) {
38
+ try {
39
+ ev = document.createEvent('CustomEvent');
40
+ ev.initCustomEvent(type, true, false, detail || null);
41
+ } catch (e2) { return null; }
42
+ }
43
+ el.dispatchEvent(ev);
44
+ return ev;
45
+ };
46
+ /* ==== BEHAVIOR INJECT END: emit ==== */
47
+
48
+ /* ==== BEHAVIOR INJECT BEGIN: roving ==== */
49
+ var flRoving = function (opts) {
50
+ var opt = opts || {};
51
+ var container = opt.container;
52
+ var all = opt.items || function () { return []; };
53
+ var movable = opt.movable || all;
54
+ var nodeOf = opt.nodeOf || function (x) { return x; };
55
+ var axis = opt.axis || 'x';
56
+ var loop = opt.loop !== false;
57
+ var homeEnd = opt.homeEnd !== false;
58
+ /* ④ tabindex: false ⇒ 只要步进,不要 roving tabindex(理由见文件头)*/
59
+ var roveTab = opt.tabindex !== false;
60
+ var onMove = opt.onMove || function () {};
61
+
62
+ /* ① tabindex 落在**全集**上:禁用项也必须是 -1,否则 Tab 会停上去 */
63
+ function rove(target) {
64
+ if (!roveTab) return target;
65
+ var l = all() || [];
66
+ for (var i = 0; i < l.length; i++) {
67
+ var n = nodeOf(l[i]);
68
+ if (!n || !n.setAttribute) continue;
69
+ n.setAttribute('tabindex', n === target ? '0' : '-1');
70
+ }
71
+ return target;
72
+ }
73
+
74
+ /* ② 取模循环:对负数、对 |d| > n 都对 */
75
+ function step(i, d) {
76
+ var n = (movable() || []).length;
77
+ if (!n) return -1;
78
+ if (loop) return ((i + d) % n + n) % n;
79
+ var t = i + d;
80
+ return (t < 0 || t >= n) ? -1 : t;
81
+ }
82
+
83
+ function indexOfNode(node) {
84
+ var l = movable() || [];
85
+ for (var i = 0; i < l.length; i++) {
86
+ if (nodeOf(l[i]) === node) return i;
87
+ }
88
+ return -1;
89
+ }
90
+
91
+ function onKey(e) {
92
+ var l = movable() || [];
93
+ if (!l.length) return;
94
+ var i = indexOfNode(e.target);
95
+ if (i < 0) return;
96
+ var k = e.key;
97
+ var next = -1;
98
+ var reason = 'step';
99
+
100
+ if (axis === 'y') {
101
+ if (k === 'ArrowDown') next = step(i, 1);
102
+ else if (k === 'ArrowUp') next = step(i, -1);
103
+ } else {
104
+ if (k === 'ArrowRight') next = step(i, 1);
105
+ else if (k === 'ArrowLeft') next = step(i, -1);
106
+ }
107
+ if (homeEnd && k === 'Home') { next = 0; reason = 'home'; }
108
+ else if (homeEnd && k === 'End') { next = l.length - 1; reason = 'end'; }
109
+
110
+ if (next < 0 || next >= l.length) return;
111
+ /* ③ 只拦事件,不替组件决定移动后做什么 */
112
+ if (e.preventDefault) e.preventDefault();
113
+ if (e.stopPropagation) e.stopPropagation();
114
+ onMove(next, l[next], reason);
115
+ }
116
+
117
+ if (container) container.addEventListener('keydown', onKey);
118
+
119
+ /* ⚠️ 只暴露有人用的东西(理由见文件头)*/
120
+ return {
121
+ rove: rove,
122
+ step: step,
123
+ indexOf: indexOfNode,
124
+ destroy: function () {
125
+ if (container) container.removeEventListener('keydown', onKey);
126
+ },
127
+ };
128
+ };
129
+ /* ==== BEHAVIOR INJECT END: roving ==== */
130
+
131
+ /* ==== BEHAVIOR INJECT BEGIN: typeahead ==== */
132
+ var flTypeahead = function (opts) {
133
+ var opt = opts || {};
134
+ var items = opt.items || function () { return []; };
135
+ var textOf = opt.textOf || function (x) { return String((x && x.textContent) || ''); };
136
+ var usable = opt.usable || function () { return true; };
137
+ var timeout = opt.timeout || 500;
138
+ var onHit = opt.onHit || function () {};
139
+ var buf = '';
140
+ var timer = null;
141
+ var last = -1;
142
+
143
+ function stop() {
144
+ if (timer) { clearTimeout(timer); timer = null; }
145
+ }
146
+
147
+ /* 连打同一字符 ⇒ 只按首字符匹配(理由见文件头 ③④)*/
148
+ function repeated(s) {
149
+ if (s.length < 2) return false;
150
+ for (var i = 1; i < s.length; i++) {
151
+ if (s.charAt(i) !== s.charAt(0)) return false;
152
+ }
153
+ return true;
154
+ }
155
+
156
+ function find(needle) {
157
+ var l = items() || [];
158
+ var n = l.length;
159
+ if (!n) return -1;
160
+ for (var k = 0; k < n; k++) {
161
+ var i = (last + 1 + k) % n; /* ① 从当前项之后开始 ⇒ 会绕回 */
162
+ if (!usable(l[i])) continue; /* ⑤ */
163
+ if (textOf(l[i]).toLowerCase().indexOf(needle) === 0) return i;
164
+ }
165
+ return -1; /* ⑥ */
166
+ }
167
+
168
+ function type(ch) {
169
+ if (!ch) return -1;
170
+ buf += String(ch).toLowerCase(); /* ② */
171
+ stop();
172
+ timer = setTimeout(clear, timeout); /* ④ */
173
+ var needle = repeated(buf) ? buf.charAt(0) : buf; /* ③ */
174
+ var i = find(needle);
175
+ if (i >= 0) {
176
+ last = i;
177
+ onHit(i, (items() || [])[i], buf);
178
+ }
179
+ return i;
180
+ }
181
+
182
+ /* 只清缓冲,不清 last(理由见文件头)*/
183
+ function clear() {
184
+ buf = '';
185
+ stop();
186
+ }
187
+
188
+ return {
189
+ type: type,
190
+ clear: clear,
191
+ buffer: function () { return buf; },
192
+ destroy: function () { stop(); },
193
+ };
194
+ };
195
+ /* ==== BEHAVIOR INJECT END: typeahead ==== */
196
+
197
+ /* ---- 旧环境兼容:Element.closest 在老 WebView 上不存在 ----
198
+ 直接调用会在真机上抛 `is not a function`(本地完全正常,
199
+ 只在老环境炸 —— 属静默失效)。这里做模块内兜底,
200
+ 不污染全局,也不需要使用者额外引入 polyfill。 */
201
+ function closest(el, sel) {
202
+ if (!el) return null;
203
+ if (el.closest) return el.closest(sel);
204
+ while (el && el.nodeType === 1) {
205
+ if (elMatches(el, sel)) return el;
206
+ el = el.parentElement;
207
+ }
208
+ return null;
209
+ }
210
+ function elMatches(el, sel) {
211
+ var m = sel.match(/^([a-zA-Z][\w-]*)/);
212
+ if (m && el.tagName.toLowerCase() !== m[1].toLowerCase()) return false;
213
+ var cls = sel.match(/\.([\w-]+)/g);
214
+ if (cls) {
215
+ for (var i = 0; i < cls.length; i++) {
216
+ var c = cls[i].slice(1);
217
+ if ((' ' + (el.className || '') + ' ').indexOf(' ' + c + ' ') < 0) return false;
218
+ }
219
+ }
220
+ var id = sel.match(/#([\w-]+)/);
221
+ if (id && el.id !== id[1]) return false;
222
+ var at = sel.match(/\[([\w-]+)(?:=["']?([^\]"']*)["']?)?\]/);
223
+ if (at) {
224
+ var v = el.getAttribute(at[1]);
225
+ if (v === null) return false;
226
+ if (at[2] !== undefined && v !== at[2]) return false;
227
+ }
228
+ return true;
229
+ }
230
+
231
+ var uid = 0;
232
+
233
+ function create(root, opt) {
234
+ opt = opt || {};
235
+ root.classList.add('tree');
236
+ if (!root.getAttribute('role')) root.setAttribute('role', 'tree');
237
+ if (!root.getAttribute('aria-label') && !root.getAttribute('aria-labelledby')) {
238
+ root.setAttribute('aria-label', opt.label || '树形视图');
239
+ }
240
+
241
+ /* ---------- 初始化结构 ---------- */
242
+ var items = [];
243
+ Array.prototype.forEach.call(root.querySelectorAll('li'), function (li) {
244
+ if (li.className.indexOf('tree__item') < 0) {
245
+ li.className = (li.className ? li.className + ' ' : '') + 'tree__item';
246
+ }
247
+ li.setAttribute('role', 'treeitem');
248
+ var kids = li.querySelector(':scope > ul');
249
+ if (kids) {
250
+ li.setAttribute('aria-expanded', kids.hidden ? 'false' : 'true');
251
+ }
252
+ var node = li.querySelector(':scope > .tree__node') || li.firstElementChild;
253
+ if (node) {
254
+ node.setAttribute('role', 'none'); /* 节点本身不是 treeitem */
255
+ node.setAttribute('tabindex', '-1');
256
+ if (!node.querySelector('.tree__marker')) {
257
+ var m = document.createElement('span');
258
+ m.className = 'tree__marker';
259
+ m.setAttribute('aria-hidden', 'true');
260
+ node.insertBefore(m, node.firstChild);
261
+ }
262
+ }
263
+ items.push({ li: li, node: node, ul: kids });
264
+ });
265
+
266
+ /* ---------- 焦点管理(roving tabindex) ---------- */
267
+ var cur = -1;
268
+ function visible() {
269
+ return items.filter(function (it) {
270
+ return it.node && it.node.offsetParent !== null;
271
+ });
272
+ }
273
+ /* ⭐ tabindex 的复位交给 roving 核(01-tokens/behavior/roving.js):
274
+ `rove()` 把 **items(全集,含收起的)** 都置 -1,只给目标置 0。
275
+ 自己写很容易只复位"可见的那几个" ⇒ 被收起的节点还留着 0,
276
+ 等它重新展开时树上就多出一个 Tab 停靠点。
277
+ ⇒ 那条不变量由 tree-check 里「走一遍键盘 + 收起再展开」这一条守着。 */
278
+ function focusAt(i) {
279
+ var list = visible();
280
+ if (!list.length) return;
281
+ if (i < 0) i = list.length - 1;
282
+ if (i >= list.length) i = 0;
283
+ roving.rove(list[i].node);
284
+ list[i].node.focus();
285
+ cur = i;
286
+ }
287
+ function current() {
288
+ var list = visible();
289
+ return cur >= 0 && cur < list.length ? list[cur] : null;
290
+ }
291
+
292
+ /* ↑↓ 与 Home / End 走 roving 核。
293
+ ⭐ axis 必须是 'y':树里 ←→ 是**展开 / 收起**(APG 的两段式),
294
+ 不能让核把它们当"左右移动"吃掉。
295
+ ⭐ items / movable 分开:全集负责 tabindex,可见集负责移动。 */
296
+ var roving = flRoving({
297
+ container: root,
298
+ items: function () { return items; },
299
+ movable: function () { return visible(); },
300
+ nodeOf: function (it) { return it.node; },
301
+ axis: 'y',
302
+ onMove: function (i) { focusAt(i); },
303
+ });
304
+
305
+ /* 类型搜索走 typeahead 核。
306
+ ⭐ items 用 **visible()**:收起的节点不在视野里,不该被匹配到;
307
+ ⭐ textOf 要 trim —— 节点文本前后常带缩进空白,不 trim 会一个都匹配不上。 */
308
+ var typeahead = flTypeahead({
309
+ items: function () { return visible(); },
310
+ textOf: function (it) { return String((it && it.node && it.node.textContent) || '').trim(); },
311
+ onHit: function (i) { focusAt(i); },
312
+ });
313
+ function setExpanded(it, open) {
314
+ if (!it.ul) return;
315
+ it.li.setAttribute('aria-expanded', open ? 'true' : 'false');
316
+ it.ul.hidden = !open;
317
+ }
318
+
319
+ /* 🔴 修:外部聚焦时**同步游标**。
320
+ `cur` 只在 `focusAt()` 里更新;但焦点也可能是**从别处**来的
321
+ (Tab 进树、编程式 el.focus()、点击)
322
+ ⇒ 那些路径下 `cur` 仍是 -1 ⇒ 按 ↑↓ 会跳到第 0 项(= 没动)。
323
+ ⚠️ 症状:鼠标点击正常,键盘 ↑↓ "没反应" —— 两者矛盾正是线索。
324
+ ⇒ 正解:监听 focusin,实时把 cur 对齐到当前聚焦的节点。 */
325
+ root.addEventListener('focusin', function (e) {
326
+ var node = e.target.closest ? closest(e.target, '.tree__node') : null;
327
+ if (!node) return;
328
+ var list = visible();
329
+ for (var i = 0; i < list.length; i++) {
330
+ if (list[i].node === node) { cur = i; break; }
331
+ }
332
+ });
333
+
334
+ /* ---------- 键盘(APG Tree View 12 条里最常用的 6 条)---------- */
335
+ root.addEventListener('keydown', function (e) {
336
+ var k = e.key;
337
+ var it = current();
338
+ if (!it) return;
339
+
340
+ /* ① ↑↓ 移动 · ⑤ Home / End 跳首末
341
+ ⇒ 已移到 roving 核(见上面的 flRoving),这里不再重复实现。
342
+ ⚠️ 不要在这里再写一遍:核与组件挂在**同一个 root** 上,
343
+ 两个监听器都会跑 ⇒ 一次按键走两步。 */
344
+
345
+ /* ② → / ← 展开收起(⭐ 且带着焦点走,这是最易漏的一半)*/
346
+ if (k === 'ArrowRight') {
347
+ e.preventDefault();
348
+ if (it.ul && it.li.getAttribute('aria-expanded') === 'false') {
349
+ setExpanded(it, true); /* 先展开,不动焦点 */
350
+ } else if (it.ul) {
351
+ focusAt(cur + 1); /* 已展开 ⇒ 进第一个子节点 */
352
+ }
353
+ return;
354
+ }
355
+ if (k === 'ArrowLeft') {
356
+ e.preventDefault();
357
+ if (it.ul && it.li.getAttribute('aria-expanded') === 'true') {
358
+ setExpanded(it, false); /* 先收起,不动焦点 */
359
+ } else {
360
+ /* 找到父节点并聚焦它(← 在收起状态下要"往回走")*/
361
+ for (var i = cur; i > 0; i--) {
362
+ var cand = visible()[i];
363
+ if (it.li.contains(cand.li)) { focusAt(i); return; }
364
+ }
365
+ }
366
+ return;
367
+ }
368
+
369
+ /* ③ `*` 展开当前层全部同级(APG 特有)*/
370
+ if (k === '*') {
371
+ e.preventDefault();
372
+ var lvl = levelOf(it);
373
+ visible().forEach(function (x) {
374
+ if (x.ul && levelOf(x) === lvl) setExpanded(x, true);
375
+ });
376
+ return;
377
+ }
378
+
379
+ /* ④ Enter 切换展开 / Space 选中 */
380
+ if (k === 'Enter') {
381
+ e.preventDefault();
382
+ if (it.ul) setExpanded(it, it.li.getAttribute('aria-expanded') !== 'true');
383
+ if (opt.onActivate) opt.onActivate(it);
384
+ return;
385
+ }
386
+ if (k === ' ' || k === 'Spacebar') {
387
+ e.preventDefault();
388
+ select(it);
389
+ return;
390
+ }
391
+
392
+ /* ⑥ 首字母跳转 —— 已移到 typeahead 核(见上面的 flTypeahead)。
393
+ ⭐ 之前这里只实现"单字符跳转",不累积 ⇒ 打 "co" 只会先跳到 c 项
394
+ 再跳到 o 项,永远到不了 "components"。累积那一半由核补上。 */
395
+ if (k.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey &&
396
+ k !== ' ') {
397
+ e.preventDefault();
398
+ typeahead.type(k);
399
+ }
400
+ });
401
+
402
+ function select(it) {
403
+ items.forEach(function (x) { x.node.setAttribute('aria-selected', 'false'); });
404
+ it.node.setAttribute('aria-selected', 'true');
405
+ flEmit(root, 'fl-select', { value: it.node.getAttribute('data-value') ||
406
+ it.node.textContent.replace(/^\s+|\s+$/g, ''),
407
+ item: it });
408
+ if (opt.onSelect) opt.onSelect(it);
409
+ }
410
+
411
+ /* ---------- 点击 ---------- */
412
+ root.addEventListener('click', function (e) {
413
+ var node = e.target.closest ? closest(e.target, '.tree__node') : null;
414
+ if (!node || !root.contains(node)) return;
415
+ var list = visible();
416
+ for (var i = 0; i < list.length; i++) {
417
+ if (list[i].node === node) { focusAt(i); break; }
418
+ }
419
+ var it = current();
420
+ if (it) {
421
+ /* 点箭头只展开/收起,点文字则选中 */
422
+ if (e.target.closest && closest(e.target, '.tree__marker') && it.ul) {
423
+ setExpanded(it, it.li.getAttribute('aria-expanded') !== 'true');
424
+ } else {
425
+ select(it);
426
+ }
427
+ }
428
+ });
429
+
430
+ /* 🔴 修一个**整棵树进不去**的 bug:
431
+ JS 只在 `focusAt()` 里把当前节点设成 tabindex="0",
432
+ 但**初始化时从来没调用过它** ⇒ 所有节点都是 tabindex="-1"
433
+ ⇒ **整棵树占不到任何 Tab 位** ⇒ 纯键盘用户 Tab 根本进不来。
434
+ ⚠️ 症状:鼠标点击一切正常,键盘完全不可达 —— 肉眼与鼠标都测不出来。
435
+ ⇒ 修:初始化结束时把焦点落在**第一个可见节点**上(只设 tabindex,不抢焦点)。 */
436
+ var firstVisible = visible()[0];
437
+ if (firstVisible) roving.rove(firstVisible.node);
438
+
439
+ return {
440
+ focusAt: focusAt,
441
+ get selected() { return current(); },
442
+ destroy: function () { roving.destroy(); typeahead.destroy(); root.innerHTML = ''; },
443
+ };
444
+ }
445
+
446
+ function levelOf(it) {
447
+ var n = 0, p = it.li.parentElement;
448
+ while (p && p !== document.body) {
449
+ if (p.getAttribute && p.getAttribute('role') === 'treeitem') n++;
450
+ p = p.parentElement;
451
+ }
452
+ return n;
453
+ }
454
+
455
+ global.Tree = { create: create };
456
+ })(window);
@@ -0,0 +1,80 @@
1
+ # analysis-report · 组合型研究报告
2
+
3
+ 一个**多栏组合**页面的可运行示例。它证明本库能组合出的不只是单栏长文。
4
+
5
+ ## 用途
6
+
7
+ 给「有结论、有数据、有出处」的报告用:实验报告、技术评估、调研纪要。
8
+ 和 `04-recipes/longform` 的分工很清楚:
9
+
10
+ | | `longform` | `analysis-report`(本示例) |
11
+ |---|---|---|
12
+ | 布局 | 单栏长文 | **正文 + 辅助栏并排**,窄屏退回单栏 |
13
+ | 证明的是 | 长文排版与阅读节奏 | **信息层级 + 组合能力** |
14
+ | 数据 | 无 | 表格 + 坐标系图表 |
15
+
16
+ ## 文件
17
+
18
+ | 文件 | 说明 |
19
+ |---|---|
20
+ | `demo.html` | 可直接打开的示例页 |
21
+ | `analysis-report.css` | 本示例的局部样式(并排布局、关键发现区、引用块、代码块) |
22
+ | `README.md` | 本文件 |
23
+
24
+ ## 依赖
25
+
26
+ **零新增运行时依赖。** 只引用库内已有资产:
27
+
28
+ ```
29
+ 01-tokens/tokens.css ← 设计令牌(必引)
30
+ 01-tokens/typography.css ← 排版基线
31
+ 01-tokens/page.css ← 页面骨架(.page / .wrap / .lede)
32
+ 02-primitives/card/card.css ← 辅助栏卡片,复用现有组件
33
+ 02-primitives/badge/badge.css ← 状态标记
34
+ 04-recipes/table/table.css ← 数据表格(复用,不另造)
35
+ 09-assets/scientific-plot/… ← 图表(复用,不另造)
36
+ analysis-report.css ← 本示例的局部样式
37
+ ```
38
+
39
+ 引用顺序:**骨架在组件之前**。组件靠「后来者同特异性胜出」覆盖骨架,
40
+ 不需要 `!important`(这条顺序有门禁守着)。
41
+
42
+ ## 布局策略
43
+
44
+ ```
45
+ .report__body {
46
+ display: grid;
47
+ grid-template-columns: minmax(0, 1fr); /* 默认单栏 */
48
+ }
49
+ @media (min-width: 64rem) { /* --bp-lg = 1024px */
50
+ grid-template-columns: minmax(0, 1fr) 15rem; /* 正文 + 辅助栏 */
51
+ }
52
+ ```
53
+
54
+ 三个要点:
55
+
56
+ 1. **默认就是单栏**,并排是宽屏才**发生**的事 —— 不是把桌面版缩小。
57
+ 窄屏下不需要任何额外媒体查询,栅格自然退成一行。
58
+ 2. `minmax(0, 1fr)` 而不是 `1fr`:否则长表格/长代码会把栅格列撑开,
59
+ 产生横向溢出。这是 Grid 的经典陷阱。
60
+ 3. 辅助栏 `position: sticky` 必须配 `align-self: start`,
61
+ 否则 sticky 在 Grid 里不生效(默认 stretch 会撑满整列高度)。
62
+
63
+ ## 已知限制
64
+
65
+ - 断点写不了 `var()` —— 媒体查询里的 `64rem` 与令牌 `--bp-lg`
66
+ 是同一数值,但**必须写字面值**(CSS 规范限制)。改令牌时这里要手动同步。
67
+ - 辅助栏在 `100vh` 内滚动,正文很长时它不会跟着到页脚。
68
+ - 页面宽度为 `--measure-page + 16rem`,若宿主项目另有页面框架,请按实际情况调整。
69
+
70
+ ## 预览
71
+
72
+ ```bash
73
+ python -m http.server 8000 # 仓库根
74
+ # http://127.0.0.1:8000/04-recipes/analysis-report/demo.html
75
+ ```
76
+
77
+ ## 数据说明
78
+
79
+ 页面里的实验数据**全部是演示用示例数据**,不是真实实验结果。
80
+ 留出这条是因为:「长得像真实数据」的示例最容易被误引用。