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,196 @@
1
+ # Toast / Dialog(反馈层)
2
+
3
+ | 字段 | 内容 |
4
+ |---|---|
5
+ | **来源** | 原创。判据部分来自 WCAG 2.1 与原生 `<dialog>` 行为 |
6
+ | **实测** | `demo.html` 顶部面板**实时显示焦点位置**,可直接验证 focus trap |
7
+ | **适用** | 精简档 / B / C(JS 用 ES5 写,见下) |
8
+ | **依赖** | `01-tokens/tokens.css` + `02-primitives/button/button.css` + `01-tokens/typography.css`(`.note` 提示框) + `01-tokens/page.css`(页面骨架:`.wrap` / `.lede` / `.note`) |
9
+
10
+ ---
11
+
12
+ ## 核心是一条判断,不是样式
13
+
14
+ ### 类清单
15
+
16
+ | 类 | 作用 |
17
+ |---|---|
18
+ | `.overlay` | 遮罩层(`role="dialog"` 的宿主) |
19
+ | `.dialog` | 弹窗面板 |
20
+ | `.dialog__title` / `__body` / `__actions` | 弹窗内部结构 |
21
+ | `.toast-region` | 通知容器(`role="status" aria-live="polite"`)|
22
+ | `.toast` | 单条通知 |
23
+ | `.toast__icon` / `__body` | 通知内部结构 |
24
+ | `.toast--success` / `--error` / `--warning` | 三种变体(**靠底色 + 图标双编码**,不只靠颜色)|
25
+
26
+ ⚠️ **三种 toast 变体在本 demo 里没有演示** ——
27
+ 它们是给复用者的钩子,样式已备好,需要你自己触发。
28
+ `[data-state="valid"]` 同理(见 `states/README.md`)。
29
+
30
+
31
+ | | toast | dialog |
32
+ |---|---|---|
33
+ | 语义 | 通知一下 | 必须处理完 |
34
+ | 用户不做任何操作 | **能继续** | **不能继续** |
35
+ | 焦点 | 不抢 | 困住 |
36
+ | 关闭 | 自动消失 | Esc / 按钮 |
37
+ | role | `status` | `dialog` + `aria-modal` |
38
+
39
+ **判据只有一条:用户不做任何操作,任务能不能继续?**
40
+
41
+ 用错比做丑严重得多:
42
+
43
+ - ❌ **把"确认删除"做成 toast** —— 用户可能根本没看见就把文件删了
44
+ - ❌ **把"已保存"做成 dialog** —— 打断用户没在做别的事,白白多一次点击
45
+
46
+ ---
47
+
48
+ ## 三件 CSS 做不到的事
49
+
50
+ ### 1. focus trap
51
+
52
+ Tab 在弹层内循环,**绝不跑到背后的页面去**。
53
+ 不做的后果:Tab 跑到背景按钮上,屏幕阅读器读到"你在模态框里,但按钮在背景上",彻底混乱。
54
+
55
+ 实现:监听 `keydown`,到边界时 `preventDefault()` 并折回。
56
+
57
+ **并加一层保险**:支持 `inert` 的浏览器上,把背景节点整体 `inert = true`——
58
+ 比 focus trap 更彻底(鼠标点击也失效)。不支持就靠 focus trap 兜。
59
+
60
+ ### 2. 焦点归还
61
+
62
+ **关闭后焦点必须回到触发它的那个元素。**
63
+ 不还的话焦点掉到 `<body>`,键盘用户要重新 Tab 一遍才能回到原处。
64
+
65
+ 触发元素可能已被移除(用户点了别处),所以要有 fallback。
66
+
67
+ ### 3. 滚动锁定 + 滚动条补偿
68
+
69
+ 锁背景滚动时**必须把滚动条宽度补到 body 上**。
70
+
71
+ 🔴 补偿要**叠加**在宿主原有值上,**不是替换**(实测踩过):
72
+
73
+ ```js
74
+ var sbw = window.innerWidth - document.documentElement.clientWidth;
75
+ // 基准取**计算样式**:宿主的 padding 也可能来自样式表,不只是内联样式
76
+ var cur = parseFloat(getComputedStyle(document.body).paddingRight) || 0;
77
+ document.body.style.paddingRight = (cur + sbw) + 'px';
78
+ ```
79
+
80
+ 写成 `= sbw + 'px'` 会把宿主原有的 `padding-right` 覆盖掉——
81
+ 宿主一有左右 padding,关掉弹层后排版就变了。
82
+
83
+ 滚动条消失后内容会横向跳一下——**弹层"闪"的感觉大半来自这里。**
84
+ 最容易被忽略、也最容易被当成"动画没做好"。
85
+
86
+ **不用 `scrollbar-gutter: stable`**(两种写法都在真机量过,都放弃):
87
+
88
+ | 写法 | 流式内容 | `100vw` 元素 | 代价 |
89
+ |---|---|---|---|
90
+ | 无条件 `html { scrollbar-gutter: stable }` | 稳 | 稳 | **导入即生效**:一个弹层没开过,宿主的 `100vw` 也被压窄 |
91
+ | 状态级 `html[data-scroll-locked] { … }` | 稳 | **跳**(393→378) | 抖动只是从流式内容转移到了 `100vw` 元素上 |
92
+
93
+ 它要稳就必须"一直"占位,而"一直"与"组件只影响自己"冲突。
94
+
95
+ **页面里有 `position: fixed` 的元素怎么办**
96
+
97
+ `fixed` 相对**视口**定位,body 的 padding 管不到它 ⇒ 锁定后吸底条会往右挪。
98
+ 本库在锁定期间把滚动条宽度公开成变量,宿主自己补:
99
+
100
+ ```css
101
+ .my-bar { right: var(--overlay-scrollbar-width, 0px); }
102
+ ```
103
+
104
+ (只在弹层打开期间存在,关闭即清除;与 Radix 的
105
+ `--removed-body-scroll-bar-size` 同款契约。)
106
+
107
+ ---
108
+
109
+ ## 危险操作的两个特殊处理
110
+
111
+ **1. 初始焦点给"取消"按钮**
112
+
113
+ ```js
114
+ Overlay.dialog({ danger: true, initialFocus: 'cancel', ... })
115
+ ```
116
+
117
+ 默认初始焦点是弹层容器(`tabindex="-1"`,让屏幕阅读器读出标题和描述)。
118
+ 但**破坏性操作必须给"取消"**——用户看清标题之前条件反射按 Enter,不该把文件删掉。
119
+
120
+ **2. 点遮罩不关闭**
121
+
122
+ `closeOnBackdrop: false`。误点遮罩的代价太大。
123
+
124
+ ---
125
+
126
+ ## 错误 toast 不自动消失
127
+
128
+ ```js
129
+ if (auto === undefined) auto = (opts.variant === 'error') ? 0 : 4000;
130
+ ```
131
+
132
+ **3 秒后悄悄消失 = 用户还没读完就等于没说。**
133
+ 错误要么不自动消失,要么给足时间 + 保留手动关闭。
134
+
135
+ ---
136
+
137
+ ## 动效克制
138
+
139
+ | 属性 | 值 | 理由 |
140
+ |---|---|---|
141
+ | dialog `scale` | 0.98 → 1 | 大幅缩放 + 回弹 = 廉价感 |
142
+ | dialog 位移 | 8px | 模态是"出现",不是"跳出来" |
143
+ | backdrop | 纯半透明,**不用 blur** | 旧 WebView 上要么无效要么掉帧 |
144
+
145
+ `prefers-reduced-motion` 下:进场动画全关,**`toast__timer` 进度条也停住**——
146
+ 不假装还在倒计时。
147
+
148
+ ---
149
+
150
+ ## 无障碍要点
151
+
152
+ 1. toast region 标 `role="status" aria-live="polite"` —— 不用 focus
153
+ 2. dialog 标 `role="dialog" aria-modal="true"` + `aria-labelledby` / `aria-describedby`
154
+ 3. dialog 容器 `tabindex="-1"` 让它本身可聚焦,作为初始焦点
155
+ 4. 背景 `inert`(支持就用)
156
+ 5. 关闭按钮要有 `aria-label`
157
+
158
+ ---
159
+
160
+ ## 🔴 库代码用 ES5
161
+
162
+ `overlay.js` **不使用箭头函数 / const / let / 模板字符串**。
163
+ 精简档 是 某项目 上的 WebView,版本不确定。demo 可以用现代语法(跑在用户浏览器上),
164
+ **库代码不行**。
165
+
166
+ 提交前可自查:
167
+
168
+ ```bash
169
+ grep -n '=>\|\bconst\b\|\blet\b' 03-patterns/overlay/overlay.js # 应无输出
170
+ ```
171
+
172
+ ---
173
+
174
+ ## 一个诚实的说明
175
+
176
+ 本库用 `div + role="dialog" + aria-modal` 而不是原生 `<dialog>` 元素——
177
+ 为了动画和样式完全可控。
178
+
179
+ **如果不需要自定义动画,`<dialog>` 的 focus trap 和 Esc 是浏览器免费提供的,应该优先用它。**
180
+ 手写 focus trap 的代码永远不如原生可靠。
181
+
182
+ ---
183
+
184
+ ## 反例
185
+
186
+ - ❌ **toast 抢焦点** —— 通知不该打断当前操作
187
+ - ❌ **用 toast 做需要确认的操作** —— 见上
188
+ - ❌ **dialog 不困住焦点** —— 混乱
189
+ - ❌ **dialog 关闭后焦点不回去** —— 键盘用户要重新 Tab
190
+ - ❌ **锁滚动不补偿滚动条宽度** —— 页面"闪"一下
191
+ - ❌ **错误 toast 3 秒消失** —— 还没读完就走了
192
+ - ❌ **危险操作初始焦点在"删除"上** —— 条件反射误触
193
+ - ❌ **危险操作点遮罩即关闭** —— 手滑就完了
194
+ - ❌ **dialog 大幅缩放/回弹** —— 廉价感
195
+ - ❌ **backdrop 加 `backdrop-filter: blur`** —— 旧 WebView 掉帧
196
+ - ❌ **用自定义 div 替代 `<dialog>` 且不需要自定义动画** —— 原生的 focus trap 更可靠
@@ -0,0 +1,241 @@
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>Toast vs Dialog · 反馈层</title>
8
+ <link rel="stylesheet" href="../../01-tokens/tokens.css">
9
+ <link rel="stylesheet" href="../../01-tokens/typography.css">
10
+ <link rel="stylesheet" href="../../01-tokens/page.css">
11
+ <link rel="stylesheet" href="../../02-primitives/button/button.css">
12
+ <link rel="stylesheet" href="overlay.css">
13
+ <style>
14
+ /* 对比演示卡:底色用中性,语义色只留在标题文字上 */
15
+ .bad { background: var(--surface-sunken); }
16
+ .good { background: var(--surface-sunken); }
17
+ .bad h3 { color: var(--danger); }
18
+ .good h3 { color: var(--success); }
19
+ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6; }
20
+ .row { display: flex; gap: var(--sp-3); flex-wrap: wrap; }
21
+ #tracker { position: sticky; top: var(--sp-3); z-index: var(--z-sticky); /* 🔴*/ background: var(--surface);
22
+ border: 1px solid var(--accent); border-radius: var(--r-md);
23
+ padding: var(--sp-3) var(--sp-4); font-family: var(--mono); /* 原为写死的 ui-monospace,统一走令牌 */
24
+ font-size: var(--fs-xs); line-height: 1.8; box-shadow: var(--shadow-2); }
25
+ #tracker b { color: var(--accent); }
26
+ .log { color: var(--text-tertiary); }
27
+ /* 🔴 原来这里是 `1fr 1fr`(两列等宽)—— 那是"AI 味"清单的第一条: 等宽并排的卡片没有主次,看起来像模板填空。 改成不等宽:左边(本库做法)是**焦点**,占更宽; 右边(常见/错用)是**配角**,占窄且视觉降级。 不对称本身产生节奏,均一不产生。 */
28
+ .two { display: grid; gap: var(--sp-5); grid-template-columns: 1.55fr 1fr; }
29
+ @media (max-width: 44rem) {.two { grid-template-columns: 1fr; }
30
+
31
+ }
32
+ /* 配角一列降级:去掉填充色,只留左边线 —— 视觉重量明显低于焦点 */
33
+ .two>.aside-col {
34
+ border-left: 2px solid var(--border-decor-str);
35
+ padding-left: var(--sp-4);
36
+ background: none;
37
+ }
38
+ @media (max-width: 700px) {.two { grid-template-columns: 1fr; }
39
+
40
+ }
41
+ </style>
42
+ </head>
43
+ <body class="page page">
44
+ <main class="wrap">
45
+
46
+ <h1>Toast vs Dialog</h1>
47
+ <p class="lede">
48
+ 这一块的核心是一条判断,不是样式。判据只有一条:
49
+ <strong>用户不做任何操作,任务能不能继续?</strong>能 → toast;不能 → dialog。
50
+ </p>
51
+
52
+ <div id="tracker">
53
+ <div>焦点所在:<b id="t-where">(页面)</b></div>
54
+ <div class="log" id="t-log">打开一个 dialog,然后按 Tab 循环 —— 焦点不会跑到背后页面去。</div>
55
+ </div>
56
+
57
+ <h2 class="rule-bottom">Toast — 通知一下,你不需要做任何事</h2>
58
+ <div class="box">
59
+ <div class="row">
60
+ <button class="btn btn--md btn--secondary" id="b1">普通提示</button>
61
+ <button class="btn btn--md btn--secondary" id="b2">成功</button>
62
+ <button class="btn btn--md btn--secondary" id="b3">错误(不自动消失)</button>
63
+ <button class="btn btn--md btn--secondary" id="b4">连发 4 个(看堆叠)</button>
64
+ </div>
65
+ <p class="small" style="margin-top:var(--sp-4)">
66
+ <strong>Toast 绝不抢焦点。</strong>按上面按钮时,看顶部面板的"焦点所在"——
67
+ 它一直停在触发按钮上。<code>role="status"</code> 会让屏幕阅读器自动播报,不需要 focus。
68
+ </p>
69
+ </div>
70
+
71
+ <h2 class="rule-bottom">Dialog — 必须处理完才能继续</h2>
72
+ <div class="box">
73
+ <div class="row">
74
+ <button class="btn btn--md btn--primary" id="d1">普通确认</button>
75
+ <button class="btn btn--md btn--danger" id="d2">删除这份报告(危险)</button>
76
+ </div>
77
+ <p class="small" style="margin-top:var(--sp-4)">
78
+ 打开后请做三件事:<br>
79
+ 1. <strong>连按 Tab</strong> —— 焦点在两个按钮之间循环,<strong>不会跑到背后页面</strong>(focus trap)<br>
80
+ 2. <strong>按 Esc</strong> —— 关闭<br>
81
+ 3. 看顶部面板 —— 关闭后焦点<strong>回到了刚才那个按钮</strong>上
82
+ </p>
83
+ </div>
84
+
85
+ <h2 class="rule-bottom">危险操作的两个特殊处理</h2>
86
+ <div class="two">
87
+ <div class="box good">
88
+ <h3 style="margin:0 0 var(--sp-3); font-size:var(--fs-sm); font-weight:500">本库做法</h3>
89
+ <p class="small" style="margin:0">
90
+ <strong>初始焦点给"取消"按钮</strong>(<code>initialFocus:'cancel'</code>)。
91
+ 用户条件反射按 Enter 时不会把文件删掉。<br><br>
92
+ <strong>点遮罩不关闭</strong>。误点遮罩的代价太大。
93
+ </p>
94
+ </div>
95
+ <div class="box bad">
96
+ <h3 style="margin:0 0 var(--sp-3); font-size:var(--fs-sm); font-weight:500">常见做法</h3>
97
+ <p class="small" style="margin:0">
98
+ 焦点默认落在"删除"上,用户看清标题之前就按了 Enter。<br><br>
99
+ 点遮罩就关闭,手滑一下文件没了。
100
+ </p>
101
+ </div>
102
+ </div>
103
+
104
+ <h2 class="rule-bottom">滚动锁定:容易漏的那个细节</h2>
105
+ <div class="note">
106
+ 打开 dialog 时页面内容会<strong>横向跳一下</strong> —— 因为滚动条被锁掉了,右边突然多出宽度。<br><br>
107
+ 修法:锁之前把滚动条宽度量出来,给 <code>body</code> 加等宽的 <code>padding-right</code>。
108
+ 弹层"闪"一下的感觉大半来自这里。<br><br>
109
+ <strong>怎么验证:</strong>本页右边有滚动条。点"普通确认",注意页面内容有没有横移。
110
+ </div>
111
+
112
+ <h2 class="rule-bottom">用错比做丑严重得多</h2>
113
+ <div class="two">
114
+ <div class="box bad aside-col">
115
+ <h3 style="margin:0 0 var(--sp-3); font-size:var(--fs-sm); font-weight:500; color:var(--danger)">
116
+ ❌ 错用</h3>
117
+ <p class="small" style="margin:0 0 var(--sp-2)">
118
+ <strong>把"确认删除"做成 toast</strong><br>
119
+ 用户可能根本没看见就把文件删了,而且没法撤销。
120
+ </p>
121
+ <p class="small" style="margin:0">
122
+ <strong>把"已保存"做成 dialog</strong><br>
123
+ 打断用户没在做别的事,白白多一次点击。
124
+ </p>
125
+ </div>
126
+ <div class="box good">
127
+ <h3 style="margin:0 0 var(--sp-3); font-size:var(--fs-sm); font-weight:500; color:var(--success)">
128
+ ✅ 判据
129
+ </h3>
130
+ <p class="small" style="margin:0 0 var(--sp-2)">
131
+ <strong>不做任何操作能继续吗?</strong><br>
132
+ 能 → toast(4 秒后自己走)<br>
133
+ 不能 → dialog(困住焦点等你处理)
134
+ </p>
135
+ <p class="small" style="margin:0">
136
+ <strong>错误 toast 默认不自动消失</strong><br>
137
+ 3 秒后悄悄消失 = 用户还没读完就等于没说。
138
+ </p>
139
+ </div>
140
+ </div>
141
+
142
+ <h2 class="rule-bottom">进场动效的克制</h2>
143
+ <div class="box">
144
+ <p class="small" style="margin:0 0 var(--sp-3)">
145
+ <code>scale</code> 只到 <strong>0.98</strong>,位移只有 <strong>8px</strong>。
146
+ 大幅缩放 + 回弹是廉价感的来源 —— 模态是"出现",不是"跳出来"。<br><br>
147
+ 遮罩刻意<strong>不用 <code>backdrop-filter: blur</code></strong>:旧 WebView 上要么无效要么掉帧,
148
+ 精简档 尤其要避开。<br><br>
149
+ 开了系统"减少动效"的话,<code>toast__timer</code> 进度条会<strong>停住</strong>——
150
+ 不假装还在倒计时。
151
+ </p>
152
+ <div class="row">
153
+ <button class="btn btn--md btn--secondary" id="d3">看进场</button>
154
+ <span class="small" style="align-self:center">连点它,看是否重叠错位</span>
155
+ </div>
156
+ </div>
157
+
158
+ <script src="overlay.js"></script>
159
+ <script>
160
+ // ---------- 焦点追踪(证明 focus trap 真的在起作用) ----------
161
+ var where = document.getElementById('t-where');
162
+ var log = document.getElementById('t-log');
163
+
164
+ function describe() {
165
+ var el = document.activeElement;
166
+ if (!el || el === document.body) return '(body)';
167
+ var t = el.tagName.toLowerCase();
168
+ var txt = (el.textContent || '').trim().slice(0, 12);
169
+ var inDlg = el.closest('.dialog') ? ' [在 dialog 内]' : ' [在背景页]';
170
+ return t + (txt ? ' “' + txt + '”' : '') + inDlg;
171
+ }
172
+ document.addEventListener('focusin', function () {
173
+ where.textContent = describe();
174
+ });
175
+ document.addEventListener('keydown', function (e) {
176
+ if (e.key === 'Tab' || e.key === 'Escape') {
177
+ log.textContent = '按了 ' + (e.key === 'Tab' ? 'Tab' : 'Esc') + ' → 焦点现在在 ' + describe();
178
+ }
179
+ });
180
+
181
+ // ---------- Toast ----------
182
+ document.getElementById('b1').onclick = function () {
183
+ Overlay.toast({ title: '正在同步', desc: '可以和别的操作同时进行' });
184
+ };
185
+ document.getElementById('b2').onclick = function () {
186
+ Overlay.toast({ title: '已保存', variant: 'success' });
187
+ };
188
+ document.getElementById('b3').onclick = function () {
189
+ Overlay.toast({
190
+ title: '导入失败',
191
+ desc: '第 3 行有 17 列,表头只有 16 列。修正后重试 —— 这条不会自己消失。',
192
+ variant: 'error'
193
+ });
194
+ };
195
+ document.getElementById('b4').onclick = function () {
196
+ Overlay.toast({ title: '1 已加入队列' });
197
+ Overlay.toast({ title: '2 已加入队列', variant: 'success' });
198
+ Overlay.toast({ title: '3 需要复核', variant: 'warning' });
199
+ Overlay.toast({ title: '4 失败:字段缺失', variant: 'error' });
200
+ };
201
+
202
+ // ---------- Dialog ----------
203
+ document.getElementById('d1').onclick = function () {
204
+ Overlay.dialog({
205
+ title: '把这份报告标记为已审?',
206
+ desc: '标记之后仍然可以撤销,但会记录操作人和时间。',
207
+ actions: [
208
+ { label: '取消', variant: 'primary' },
209
+ { label: '确认标记' }
210
+ ]
211
+ });
212
+ };
213
+
214
+ document.getElementById('d2').onclick = function () {
215
+ Overlay.dialog({
216
+ danger: true,
217
+ title: '删除这份报告?',
218
+ desc: '它已经在 3 个工作流里被引用。删除后那些工作流会标记为缺少输入。此操作无法撤销。',
219
+ // 🔴 初始焦点给"取消":用户看清标题之前就按 Enter 不会出事
220
+ initialFocus: 'cancel',
221
+ // 🔴 点遮罩不关闭
222
+ closeOnBackdrop: false,
223
+ actions: [
224
+ { label: '删除', variant: 'danger' },
225
+ { label: '取消' }
226
+ ]
227
+ });
228
+ };
229
+
230
+ document.getElementById('d3').onclick = function () {
231
+ Overlay.dialog({
232
+ title: '进场动效',
233
+ desc: 'scale 只到 0.98,位移只有 8px。大幅缩放 + 回弹是廉价感的来源。',
234
+ actions: [{ label: '知道了', variant: 'primary' }]
235
+ });
236
+ };
237
+
238
+ log.textContent = '打开一个 dialog,然后连按 Tab —— 焦点会在两个按钮间循环,不会跑到背后页面。';
239
+ </script></main>
240
+ </body>
241
+ </html>