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,307 @@
1
+ /*
2
+ * tabs.js — Tabs 的行为层(APG: Tabs Pattern)
3
+ *
4
+ * 语法刻意用 ES5(无箭头函数 / const / let / 模板字符串)——
5
+ * 精简档(某项目 上的 WebView)版本不确定。
6
+ *
7
+ * 🔴 键盘契约(照抄 Radix / APG,不是我的设计)
8
+ * ------------------------------------------------
9
+ * Tab 进入组时落在**当前选中**的 tab;再按 Tab 走到面板
10
+ * ArrowRight 下一个 tab(并激活)
11
+ * ArrowLeft 上一个 tab(并激活)
12
+ * ArrowDown 下一个(纵向)
13
+ * ArrowUp 上一个(纵向)
14
+ * Home 第一个 tab(并激活)
15
+ * End 最后一个 tab(并激活)
16
+ *
17
+ * 🔴 roving tabindex(本文件最容易写错的地方)
18
+ * ------------------------------------------------
19
+ * 一组 tab 里**只有一个** tabindex="0",其余全是 -1。
20
+ * 为什么要这样:否则 Tab 键会**逐个穿过所有 tab** ——
21
+ * 一排 5 个 tab 要按 5 次 Tab 才能进内容。
22
+ * roving 让整组只占**一个** Tab 停靠点,组内用方向键移动。
23
+ *
24
+ * ⚠️ 而且这个唯一的 "0" 必须**跟着选中项走** ——
25
+ * 激活某个 tab 时要把它的 tabindex 设为 0、把上一个设为 -1。
26
+ * 只在初始化时设一次是最常见的 bug:用户激活第 3 个 tab 后,
27
+ * Tab 键还是会回到第 1 个。
28
+ */
29
+ (function () {
30
+ 'use strict';
31
+
32
+ /* ==== BEHAVIOR INJECT BEGIN: emit ==== */
33
+ var flEmit = function (el, name, detail) {
34
+ if (!el || !el.dispatchEvent) return null;
35
+ var type = name.indexOf('fl-') === 0 ? name : 'fl-' + name;
36
+ var ev = null;
37
+ /* 老 WebView 没有 CustomEvent 构造函数 ⇒ 兜底走 createEvent(理由见文件头)*/
38
+ if (typeof window.CustomEvent === 'function') {
39
+ try {
40
+ ev = new window.CustomEvent(type, {
41
+ detail: detail || null, bubbles: true, cancelable: false
42
+ });
43
+ } catch (e) { ev = null; }
44
+ }
45
+ if (!ev) {
46
+ try {
47
+ ev = document.createEvent('CustomEvent');
48
+ ev.initCustomEvent(type, true, false, detail || null);
49
+ } catch (e2) { return null; }
50
+ }
51
+ el.dispatchEvent(ev);
52
+ return ev;
53
+ };
54
+ /* ==== BEHAVIOR INJECT END: emit ==== */
55
+
56
+ /* ==== BEHAVIOR INJECT BEGIN: roving ==== */
57
+ var flRoving = function (opts) {
58
+ var opt = opts || {};
59
+ var container = opt.container;
60
+ var all = opt.items || function () { return []; };
61
+ var movable = opt.movable || all;
62
+ var nodeOf = opt.nodeOf || function (x) { return x; };
63
+ var axis = opt.axis || 'x';
64
+ var loop = opt.loop !== false;
65
+ var homeEnd = opt.homeEnd !== false;
66
+ /* ④ tabindex: false ⇒ 只要步进,不要 roving tabindex(理由见文件头)*/
67
+ var roveTab = opt.tabindex !== false;
68
+ var onMove = opt.onMove || function () {};
69
+
70
+ /* ① tabindex 落在**全集**上:禁用项也必须是 -1,否则 Tab 会停上去 */
71
+ function rove(target) {
72
+ if (!roveTab) return target;
73
+ var l = all() || [];
74
+ for (var i = 0; i < l.length; i++) {
75
+ var n = nodeOf(l[i]);
76
+ if (!n || !n.setAttribute) continue;
77
+ n.setAttribute('tabindex', n === target ? '0' : '-1');
78
+ }
79
+ return target;
80
+ }
81
+
82
+ /* ② 取模循环:对负数、对 |d| > n 都对 */
83
+ function step(i, d) {
84
+ var n = (movable() || []).length;
85
+ if (!n) return -1;
86
+ if (loop) return ((i + d) % n + n) % n;
87
+ var t = i + d;
88
+ return (t < 0 || t >= n) ? -1 : t;
89
+ }
90
+
91
+ function indexOfNode(node) {
92
+ var l = movable() || [];
93
+ for (var i = 0; i < l.length; i++) {
94
+ if (nodeOf(l[i]) === node) return i;
95
+ }
96
+ return -1;
97
+ }
98
+
99
+ function onKey(e) {
100
+ var l = movable() || [];
101
+ if (!l.length) return;
102
+ var i = indexOfNode(e.target);
103
+ if (i < 0) return;
104
+ var k = e.key;
105
+ var next = -1;
106
+ var reason = 'step';
107
+
108
+ if (axis === 'y') {
109
+ if (k === 'ArrowDown') next = step(i, 1);
110
+ else if (k === 'ArrowUp') next = step(i, -1);
111
+ } else {
112
+ if (k === 'ArrowRight') next = step(i, 1);
113
+ else if (k === 'ArrowLeft') next = step(i, -1);
114
+ }
115
+ if (homeEnd && k === 'Home') { next = 0; reason = 'home'; }
116
+ else if (homeEnd && k === 'End') { next = l.length - 1; reason = 'end'; }
117
+
118
+ if (next < 0 || next >= l.length) return;
119
+ /* ③ 只拦事件,不替组件决定移动后做什么 */
120
+ if (e.preventDefault) e.preventDefault();
121
+ if (e.stopPropagation) e.stopPropagation();
122
+ onMove(next, l[next], reason);
123
+ }
124
+
125
+ if (container) container.addEventListener('keydown', onKey);
126
+
127
+ /* ⚠️ 只暴露有人用的东西(理由见文件头)*/
128
+ return {
129
+ rove: rove,
130
+ step: step,
131
+ indexOf: indexOfNode,
132
+ destroy: function () {
133
+ if (container) container.removeEventListener('keydown', onKey);
134
+ },
135
+ };
136
+ };
137
+ /* ==== BEHAVIOR INJECT END: roving ==== */
138
+
139
+ function toArray(x) { return Array.prototype.slice.call(x); }
140
+
141
+ /* 🔴 刻意不用 Element.closest / Array.filter ——
142
+ 它们是 ES5 之后才有的(closest 还要 ES6 风格的 DOM 扩展),
143
+ 而本库要跑在**精简档(某项目 上的 WebView)**上,版本不确定。
144
+ 同目录的 list.js / overlay.js 也都避开这两个 ——
145
+ 一致比方便重要。 */
146
+ function closest(el, sel) {
147
+ while (el && el.nodeType === 1) {
148
+ if (matches(el, sel)) return el;
149
+ el = el.parentNode;
150
+ }
151
+ return null;
152
+ }
153
+
154
+ function matches(el, sel) {
155
+ var fn = el.matches || el.msMatchesSelector || el.webkitMatchesSelector;
156
+ return fn ? fn.call(el, sel) : false;
157
+ }
158
+
159
+ function filter(list, fn, self) {
160
+ var out = [];
161
+ for (var i = 0; i < list.length; i++) {
162
+ if (fn.call(self, list[i], i, list)) out.push(list[i]);
163
+ }
164
+ return out;
165
+ }
166
+
167
+ function Tabs(root, opts) {
168
+ this.root = root;
169
+ this.opts = opts || {};
170
+ this.list = root.querySelector('[role="tablist"]');
171
+ if (!this.list) return;
172
+ this.tabs = toArray(this.list.querySelectorAll('[role="tab"]'));
173
+ this.panels = toArray(root.querySelectorAll('[role="tabpanel"]'));
174
+ this.vertical = this.list.getAttribute('aria-orientation') === 'vertical';
175
+ // automatic(默认):方向键移动即激活;manual:只移动焦点
176
+ this.manual = root.classList.contains('tabs--manual');
177
+
178
+ /* 方向键游走走 roving 核(01-tokens/behavior/roving.js)。
179
+ ⭐ 两个列表必须分开:
180
+ items = 全部 tab(含禁用)⇒ tabindex 也要给禁用项设成 -1,
181
+ 否则 Tab 会停在禁用项上
182
+ movable = 可用 tab ⇒ 方向键只在这里面移动,跳过禁用项 */
183
+ var self = this;
184
+ this.roving = flRoving({
185
+ container: this.list,
186
+ items: function () { return self.tabs; },
187
+ movable: function () { return self.focusables(); },
188
+ axis: this.vertical ? 'y' : 'x',
189
+ onMove: function (i, tab) {
190
+ if (self.manual) { self.rove(tab); tab.focus(); } // manual:只移焦点
191
+ else self.select(tab, true); // automatic:移动即激活
192
+ },
193
+ });
194
+ this.bind();
195
+ }
196
+
197
+ Tabs.prototype.enabled = function (tab) {
198
+ return tab.getAttribute('aria-disabled') !== 'true';
199
+ };
200
+
201
+ Tabs.prototype.focusables = function () {
202
+ return filter(this.tabs, this.enabled, this);
203
+ };
204
+
205
+ /* 🔴 roving 的核心:把 tabindex="0" 给 active,其余 -1
206
+ —— 实现已移到 roving 核,这里只做转发(规则只有一份)。 */
207
+ Tabs.prototype.rove = function (target) {
208
+ if (!this.roving) return;
209
+ this.roving.rove(target);
210
+ };
211
+ Tabs.prototype.select = function (tab, focusIt) {
212
+ if (!this.enabled(tab)) return;
213
+ for (var i = 0; i < this.tabs.length; i++) {
214
+ var on = this.tabs[i] === tab;
215
+ this.tabs[i].setAttribute('aria-selected', on ? 'true' : 'false');
216
+ // APG:tab 的 id 与 panel 的 aria-labelledby / aria-controls 互指
217
+ var pid = this.tabs[i].getAttribute('aria-controls');
218
+ var panel = pid ? document.getElementById(pid) : null;
219
+ if (!panel) {
220
+ // 没有 aria-controls 时按顺序对应
221
+ panel = this.panels[i] || null;
222
+ }
223
+ if (panel) {
224
+ if (on) {
225
+ panel.removeAttribute('hidden');
226
+ panel.classList.add('tabs__panel--enter');
227
+ } else {
228
+ panel.setAttribute('hidden', '');
229
+ panel.classList.remove('tabs__panel--enter');
230
+ }
231
+ }
232
+ }
233
+ this.active = tab;
234
+ this.rove(tab);
235
+ if (focusIt) tab.focus();
236
+ flEmit(this.root, 'fl-change',
237
+ { value: tab.getAttribute('data-value') || tab.id, tab: tab });
238
+ if (typeof this.opts.onChange === 'function') {
239
+ this.opts.onChange(tab.getAttribute('data-value') || tab.id, tab);
240
+ }
241
+ };
242
+
243
+ Tabs.prototype.bind = function () {
244
+ var self = this;
245
+
246
+ // 初始化:激活 aria-selected="true" 的那个;没有就选第一个
247
+ var initial = null;
248
+ for (var i = 0; i < this.tabs.length; i++) {
249
+ if (this.tabs[i].getAttribute('aria-selected') === 'true') {
250
+ initial = this.tabs[i];
251
+ break;
252
+ }
253
+ }
254
+ this.select(initial || this.tabs[0], false);
255
+
256
+ // 点击
257
+ this.list.addEventListener('click', function (e) {
258
+ var tab = closest(e.target, '[role="tab"]');
259
+ if (!tab || !self.list.contains(tab)) return;
260
+ if (!self.enabled(tab)) return;
261
+ self.select(tab, false);
262
+ });
263
+
264
+ // 键盘
265
+ this.list.addEventListener('keydown', function (e) {
266
+ var tab = closest(e.target, '[role="tab"]');
267
+ if (!tab) return;
268
+ var k = e.key;
269
+
270
+ /* ↑↓←→ 与 Home / End 已由 roving 核接管(见构造函数里的 flRoving),
271
+ 这里只剩 Enter / Space。
272
+ ⚠️ automatic 模式下**不要** preventDefault —— 交给 button 的默认行为
273
+ (它会触发 click ⇒ select)。只有 manual 模式才需要显式激活:
274
+ 那种模式下方向键只移了焦点,面板还没切。 */
275
+ if (k !== 'Enter' && k !== ' ' && k !== 'Spacebar') return;
276
+ if (self.manual) {
277
+ e.preventDefault();
278
+ self.select(tab, false);
279
+ }
280
+ });
281
+ };
282
+
283
+ // 暴露
284
+ window.Tabs = Tabs;
285
+
286
+ // 自动初始化:<div class="tabs" data-tabs>
287
+ //
288
+ // ⚠️ 这里**曾经**写过 `onChange: el.getAttribute('data-on-change')` ——
289
+ // 它把一个**字符串**塞进了要求函数的位置,`select()` 里
290
+ // `typeof this.opts.onChange === 'function'` 永远不成立
291
+ // ⇒ 这个属性从第一天起就**从来没有生效过**,而且没有任何报错。
292
+ // 现已删除:自动初始化不接回调,要收通知请用 `fl-change` 事件
293
+ // (见 API.md「事件」一节)。
294
+ function init() {
295
+ var nodes = document.querySelectorAll('[data-tabs]');
296
+ for (var i = 0; i < nodes.length; i++) {
297
+ nodes[i].tabsInstance = new Tabs(nodes[i], {});
298
+ }
299
+ }
300
+
301
+ // @autoinit: [data-tabs] —— 扫属性自动建实例(详见 API.md「自动初始化」)
302
+ if (document.readyState === 'loading') {
303
+ document.addEventListener('DOMContentLoaded', init);
304
+ } else {
305
+ init();
306
+ }
307
+ })();
@@ -0,0 +1,96 @@
1
+ # tooltip · 文字提示
2
+
3
+ 补充说明,**不是**主要信息的载体。
4
+
5
+ ## 🔴 一条铁律:tooltip 不承载必要信息
6
+
7
+ 触屏设备**没有 hover** ⇒ tooltip 在手机上等于不存在。
8
+ 凡是"用户必须知道"的内容,绝不能只放在 tooltip 里。
9
+
10
+ **怎么自己查**:tooltip 里的文字必须与触发元素的可见文字或 `aria-label`
11
+ **不重复**。重复了,就说明它本该直接可见。
12
+
13
+ > 这是本库唯一一条"用法约束"级别的规则,也是最常被违反的一条。
14
+
15
+ ## 结构(JS 生成,不要手写)
16
+
17
+ ```js
18
+ Tooltip.attach(按钮, '说明文字', 'top');
19
+ ```
20
+
21
+ 生成的结构:
22
+
23
+ ```html
24
+ <button data-tooltip="说明文字" aria-describedby="tt1">…</button>
25
+ <div id="tt1" class="tooltip tooltip--top" role="tooltip">说明文字</div>
26
+ ```
27
+
28
+ | 参数 | 取值 | 说明 |
29
+ |---|---|---|
30
+ | `el` | Element | 触发元素 |
31
+ | `text` | string | 纯文本(用 `textContent` 写入,**天然免疫注入**)|
32
+ | `place` | `top`(默认)/ `bottom` / `inset-inline-start` / `inset-inline-end` | 方位 |
33
+
34
+ 公开 API 只有 `Tooltip.attach()`,**没有** `show` / `hide` ——
35
+ 显隐由 hover / focus / click 自动驱动。
36
+
37
+ ## 🔴 用 `aria-describedby`,不是 `aria-label`
38
+
39
+ tooltip 与触发元素的关系是**描述**,不是**命名**。
40
+ `aria-label` 会**覆盖**元素本来的可访问名——按钮上的文字会被抹掉。
41
+
42
+ ```js
43
+ el.setAttribute('aria-describedby', cur ? (cur + ' ' + id) : id);
44
+ ```
45
+
46
+ ⚠️ 已有 `aria-describedby` 时是**追加空格**,不是覆盖 —— 可能同时有多个描述。
47
+
48
+ `role="tooltip"` + `aria-live="polite"`:内容变化时读屏会念出来。
49
+
50
+ ## 键盘与触屏
51
+
52
+ | 输入 | 行为 |
53
+ |---|---|
54
+ | 键盘 `focus` | 出(**键盘必须可达**,只做 hover 等于对键盘用户不存在)|
55
+ | `Esc` | 关(WCAG 1.4.13 Content on Hover or Focus)|
56
+ | 触屏 `click` | 切换开/关 |
57
+
58
+ ## 🔴 定位前必须先入 DOM
59
+
60
+ 游离元素的 `getBoundingClientRect()` **全是 0**。
61
+
62
+ 实测踩过:`top` 方位的气泡与锚点完全重叠 —— 原因就是先量尺寸、
63
+ 后插进 DOM。正确顺序是**先挂上去、藏起来量,量完再显示**。
64
+
65
+ ## 显隐靠 `data-state`,别写 `display`
66
+
67
+ ```css
68
+ .tooltip[data-state="open"] { … }
69
+ ```
70
+
71
+ JS 只切`data-state`,显隐与动画全交给 CSS。
72
+ 写 `style.display` 会把过渡动画彻底干掉。
73
+
74
+ ## 降级
75
+
76
+ | 场景 | 处理 |
77
+ |---|---|
78
+ | `prefers-color-scheme: dark` | 已适配 |
79
+ | `prefers-reduced-motion: reduce` | 已关闭动效 |
80
+ | 无 `(hover: hover) and (pointer: fine)` | 只保留点击触发 |
81
+
82
+ ## 成熟度:beta(两条行为债,如实列出)
83
+
84
+ 契约里标的是 `beta`,两条债都记在这儿,**别当stable 用**:
85
+
86
+ | 债 | 含义 | 现在能做什么 |
87
+ |---|---|---|
88
+ | `dismissable:manual` | 关闭逻辑是手写的,不是统一机制 | 点击/`Esc` 能关,但**点击外部空白不关** |
89
+ | `focus-return:manual` | 焦点归位是手写的 | 关闭后焦点**不一定**回到触发元素 |
90
+
91
+ 要接统一行为层,得先清这两条债。
92
+
93
+ ## 门禁
94
+
95
+ `05-audit/tooltip-check.js`(契约 `gates` 里登记的唯一一道)——
96
+ 覆盖 `aria-describedby` 关联、`Esc` 关闭、触屏点击三条。
@@ -0,0 +1,78 @@
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>tooltip · 文字提示 · frontend-lib</title>
8
+ <link rel="stylesheet" href="../../01-tokens/tokens.css">
9
+ <link rel="stylesheet" href="../../01-tokens/typography.css">
10
+ <link rel="stylesheet" href="../../01-tokens/focus-ring.css">
11
+ <link rel="stylesheet" href="../../01-tokens/page.css">
12
+ <link rel="stylesheet" href="../../02-primitives/button/button.css">
13
+ <link rel="stylesheet" href="tooltip.css">
14
+ <style>
15
+ .row { display: flex; gap: var(--sp-4); flex-wrap: wrap; align-items: center; }
16
+ .pad { padding: var(--sp-9) 0; }
17
+ </style>
18
+ </head>
19
+ <body class="page page">
20
+ <main class="wrap">
21
+
22
+ <h1>tooltip · 文字提示</h1>
23
+ <p class="note">
24
+ ⛔ <strong>tooltip 不承载必要信息</strong> —— 触屏没有 hover,
25
+ 只放在 tooltip 里的内容等于不存在。
26
+ <br>✅ 键盘 focus 也会出 · <code>Esc</code> 能关 · 用
27
+ <code>aria-describedby</code> 关联(不是 aria-label:那个是「名字」)。
28
+ </p>
29
+
30
+ <h2>1 · 键盘可达(Tab 聚焦即出)</h2>
31
+ <div class="row pad">
32
+ <button class="btn" id="b1">悬停/聚焦我</button>
33
+ <span class="btn btn--ghost" id="b2" tabindex="0">可聚焦的说明文字</span>
34
+ </div>
35
+
36
+ <h2>2 · 四个方位</h2>
37
+ <div class="row pad">
38
+ <button class="btn" id="p-top">上方</button>
39
+ <button class="btn" id="p-bottom">下方</button>
40
+ <button class="btn" id="p-left">左侧</button>
41
+ <button class="btn" id="p-right">右侧</button>
42
+ </div>
43
+
44
+ <h2>3 · 🔴 错误示范:把必要信息只放在 tooltip 里</h2>
45
+ <p class="note">
46
+ 下面这个按钮的 tooltip 文字是「删除」,与按钮可见文字完全相同
47
+ ⇒ 说明它<strong>没有提供额外信息</strong>,只是重复 ⇒ 应该删掉 tooltip,
48
+ 或改成真正的补充说明。
49
+ <br>契约里有一条判据专门抓这个(<code>tooltip 不承载必要信息</code>)。
50
+ </p>
51
+ <div class="row pad">
52
+ <button class="btn" id="bad" data-bad-example="1">删除</button>
53
+ </div>
54
+
55
+ <h2>4 · 正确示范:补充说明</h2>
56
+ <div class="row pad">
57
+ <button class="btn" id="good">导出 CSV</button>
58
+ </div>
59
+
60
+ </main>
61
+
62
+ <script src="tooltip.js"></script>
63
+ <script>
64
+ (function () {
65
+ Tooltip.attach(document.getElementById('b1'), '这是补充说明:点击后会进入批量模式');
66
+ Tooltip.attach(document.getElementById('b2'), '聚焦也会出现,不必依赖鼠标');
67
+ Tooltip.attach(document.getElementById('p-top'), '出现在上方', 'top');
68
+ Tooltip.attach(document.getElementById('p-bottom'), '出现在下方', 'bottom');
69
+ Tooltip.attach(document.getElementById('p-left'), '出现在左侧', 'left');
70
+ Tooltip.attach(document.getElementById('p-right'), '出现在右侧', 'right');
71
+ /* 🔴 反例:tooltip 文字 = 按钮可见文字 ⇒ 没有额外信息 */
72
+ Tooltip.attach(document.getElementById('bad'), '删除');
73
+ /* ✅ 正例:提供了额外信息 */
74
+ Tooltip.attach(document.getElementById('good'), '导出当前筛选结果,含表头;UTF-8 编码');
75
+ })();
76
+ </script>
77
+ </body>
78
+ </html>
@@ -0,0 +1,89 @@
1
+ /* ============================================================================
2
+ tooltip.css — 文字提示(B3 /
3
+ ============================================================================
4
+ ⭐ 一条铁律:**tooltip 不承载必要信息**。
5
+
6
+ 触屏设备**没有 hover** ⇒ tooltip 在手机上等于不存在。
7
+ ⇒ 凡是"用户必须知道"的内容,绝不能只放在 tooltip 里。
8
+ ⇒ 判据:tooltip 里的文字必须与触发元素的可见文字/aria-label **不重复**,
9
+ 否则说明它承载了本该可见的信息。
10
+
11
+ 另外:
12
+ · **键盘必须可达**(focus 时也要能出)
13
+ · `Esc` 必须能关(WCAG 1.4.13 Content on Hover or Focus)
14
+ ============================================================================ */
15
+
16
+ .tooltip {
17
+ position: absolute;
18
+ z-index: var(--z-raised);
19
+ max-width: 260px;
20
+ padding: var(--sp-2) var(--sp-3);
21
+ background: var(--tooltip-bg); /* 深色气泡(浅色作用域下) */
22
+ color: var(--tooltip-fg);
23
+ font-size: var(--fs-xs);
24
+ line-height: 1.5;
25
+ border-radius: var(--r-sm);
26
+ /* 描边用 inset 环而不用 border:同色时零像素,暗色下正好贴在原边线上。
27
+ `--shadow-2` 是公开令牌,不写兜底字面量(兜底 = 第二份真值,见 I-7)。 */
28
+ box-shadow: inset 0 0 0 1px var(--tooltip-border), var(--shadow-2);
29
+ /* 文字不被选中(否则会误选) */
30
+ user-select: none;
31
+ /* 淡入用透明度,不用 display(display 会让读屏读不到)*/
32
+ opacity: 0;
33
+ visibility: hidden;
34
+ transition: opacity 120ms var(--ease-out, ease);
35
+ pointer-events: none; /* 不挡住下面的元素 */
36
+ }
37
+
38
+ .tooltip[data-state="open"] {
39
+ opacity: 1;
40
+ visibility: visible;
41
+ }
42
+
43
+ /* ---------- 箭头(小三角,用 CSS 画,不引图片)---------- */
44
+ .tooltip::after {
45
+ content: "";
46
+ position: absolute;
47
+ border: 5px solid transparent;
48
+ }
49
+
50
+ /* 上下是**块轴**(RTL 不翻转,写物理没问题);
51
+ 左右是**内联轴**(RTL 会翻转 ⇒ 必须写逻辑属性)。
52
+ 🔴 0.4.2 修:原本写物理 border-left/right-color。小三角是
53
+ "给哪条边上色、尖角就指向反方向" —— RTL 下左边变成内联终点 ⇒ 尖角指反,
54
+ 指向气泡外侧而不是锚点(I-12 同一类:不报错,只是看着不对)。 */
55
+ .tooltip--top::after {
56
+ top: 100%; inset-inline-start: 50%;
57
+ margin-inline-start: -5px;
58
+ border-top-color: var(--tooltip-bg);
59
+ }
60
+ .tooltip--bottom::after {
61
+ bottom: 100%; inset-inline-start: 50%;
62
+ margin-inline-start: -5px;
63
+ border-bottom-color: var(--tooltip-bg);
64
+ }
65
+ .tooltip--inset-inline-start::after {
66
+ inset-inline-start: 100%; top: 50%;
67
+ margin-top: -5px;
68
+ border-inline-start-color: var(--tooltip-bg);
69
+ }
70
+ .tooltip--inset-inline-end::after {
71
+ inset-inline-end: 100%; top: 50%;
72
+ margin-top: -5px;
73
+ border-inline-end-color: var(--tooltip-bg);
74
+ }
75
+
76
+ /* 原有一段 `@media (prefers-color-scheme: dark)`,已改为令牌驱动:
77
+ 主题能挂在容器上之后,media 只反映系统偏好(RFC-THEME-SCOPE D5)。 */
78
+
79
+ /* ---------- 减弱动效:不要淡入动画 ---------- */
80
+ @media (prefers-reduced-motion: reduce) {
81
+ .tooltip { transition: none; }
82
+ }
83
+
84
+ /* ---------- 悬停显示:只对真能悬停的设备 ----------
85
+ 🔴 触屏上 hover 会"粘住" ⇒ 触屏靠 JS 的 click/focus 触发。 */
86
+ @media (hover: hover) and (pointer: fine) {
87
+ [data-tooltip]:hover + .tooltip,
88
+ [data-tooltip]:focus-visible + .tooltip { opacity: 1; visibility: visible; }
89
+ }