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.
- package/01-tokens/README.md +236 -0
- package/01-tokens/behavior/dismissable.js +132 -0
- package/01-tokens/behavior/emit.js +76 -0
- package/01-tokens/behavior/focus-return.js +81 -0
- package/01-tokens/behavior/roving.js +152 -0
- package/01-tokens/behavior/typeahead.js +124 -0
- package/01-tokens/focus-ring-demo.html +133 -0
- package/01-tokens/focus-ring.css +102 -0
- package/01-tokens/page.css +244 -0
- package/01-tokens/polyfill.js +116 -0
- package/01-tokens/responsive-demo.html +171 -0
- package/01-tokens/scrollbar-demo.html +194 -0
- package/01-tokens/scrollbar.css +179 -0
- package/01-tokens/theme-toggle.js +103 -0
- package/01-tokens/tokens.css +972 -0
- package/01-tokens/typography.css +156 -0
- package/02-primitives/badge/README.md +71 -0
- package/02-primitives/badge/badge.css +141 -0
- package/02-primitives/badge/demo.html +101 -0
- package/02-primitives/button/README.md +99 -0
- package/02-primitives/button/button.css +353 -0
- package/02-primitives/button/demo.html +200 -0
- package/02-primitives/card/README.md +145 -0
- package/02-primitives/card/card.css +246 -0
- package/02-primitives/card/demo.html +170 -0
- package/02-primitives/choice/README.md +161 -0
- package/02-primitives/choice/choice.css +395 -0
- package/02-primitives/choice/demo.html +277 -0
- package/02-primitives/combobox/README.md +79 -0
- package/02-primitives/combobox/combobox.css +223 -0
- package/02-primitives/combobox/combobox.js +485 -0
- package/02-primitives/combobox/demo.html +109 -0
- package/02-primitives/date-range/README.md +100 -0
- package/02-primitives/date-range/date-range.css +206 -0
- package/02-primitives/date-range/date-range.js +301 -0
- package/02-primitives/date-range/demo.html +104 -0
- package/02-primitives/input/README.md +170 -0
- package/02-primitives/input/demo.html +246 -0
- package/02-primitives/input/input.css +239 -0
- package/02-primitives/popover/README.md +80 -0
- package/02-primitives/popover/demo.html +150 -0
- package/02-primitives/popover/popover.css +187 -0
- package/02-primitives/popover/popover.js +331 -0
- package/02-primitives/progress/README.md +82 -0
- package/02-primitives/progress/demo.html +154 -0
- package/02-primitives/progress/progress.css +172 -0
- package/02-primitives/select/README.md +95 -0
- package/02-primitives/select/demo.html +121 -0
- package/02-primitives/select/select.css +151 -0
- package/02-primitives/select/select.js +549 -0
- package/02-primitives/separator/README.md +63 -0
- package/02-primitives/separator/demo.html +84 -0
- package/02-primitives/separator/separator.css +73 -0
- package/02-primitives/skeleton/README.md +69 -0
- package/02-primitives/skeleton/demo.html +110 -0
- package/02-primitives/skeleton/skeleton.css +96 -0
- package/02-primitives/switch/README.md +65 -0
- package/02-primitives/switch/demo.html +115 -0
- package/02-primitives/switch/switch.css +370 -0
- package/03-patterns/accordion/README.md +121 -0
- package/03-patterns/accordion/accordion.css +453 -0
- package/03-patterns/accordion/accordion.js +369 -0
- package/03-patterns/accordion/demo.html +223 -0
- package/03-patterns/content/README.md +103 -0
- package/03-patterns/content/content.css +286 -0
- package/03-patterns/content/demo.html +88 -0
- package/03-patterns/drawer/README.md +79 -0
- package/03-patterns/drawer/demo.html +101 -0
- package/03-patterns/drawer/drawer.css +129 -0
- package/03-patterns/drawer/drawer.js +258 -0
- package/03-patterns/dropdown/README.md +78 -0
- package/03-patterns/dropdown/demo.html +101 -0
- package/03-patterns/dropdown/dropdown.css +135 -0
- package/03-patterns/dropdown/dropdown.js +451 -0
- package/03-patterns/form-validation/README.md +147 -0
- package/03-patterns/form-validation/demo.html +280 -0
- package/03-patterns/form-validation/form-validation.css +263 -0
- package/03-patterns/list/README.md +150 -0
- package/03-patterns/list/demo.html +225 -0
- package/03-patterns/list/flip.js +201 -0
- package/03-patterns/list/list.css +162 -0
- package/03-patterns/nav/README.md +225 -0
- package/03-patterns/nav/demo.html +131 -0
- package/03-patterns/nav/nav.css +314 -0
- package/03-patterns/nav/nav.js +430 -0
- package/03-patterns/nav/toc.js +111 -0
- package/03-patterns/overlay/README.md +196 -0
- package/03-patterns/overlay/demo.html +241 -0
- package/03-patterns/overlay/overlay.css +319 -0
- package/03-patterns/overlay/overlay.js +563 -0
- package/03-patterns/pagination/README.md +114 -0
- package/03-patterns/pagination/demo.html +165 -0
- package/03-patterns/pagination/pagination.css +195 -0
- package/03-patterns/pagination/pagination.js +395 -0
- package/03-patterns/reveal/README.md +91 -0
- package/03-patterns/reveal/demo.html +106 -0
- package/03-patterns/reveal/reveal.css +93 -0
- package/03-patterns/reveal/reveal.js +172 -0
- package/03-patterns/states/README.md +141 -0
- package/03-patterns/states/demo.html +240 -0
- package/03-patterns/states/states.css +252 -0
- package/03-patterns/tabs/README.md +84 -0
- package/03-patterns/tabs/demo.html +139 -0
- package/03-patterns/tabs/tabs.css +172 -0
- package/03-patterns/tabs/tabs.js +307 -0
- package/03-patterns/tooltip/README.md +96 -0
- package/03-patterns/tooltip/demo.html +78 -0
- package/03-patterns/tooltip/tooltip.css +89 -0
- package/03-patterns/tooltip/tooltip.js +288 -0
- package/03-patterns/tree/README.md +94 -0
- package/03-patterns/tree/demo.html +138 -0
- package/03-patterns/tree/tree.css +113 -0
- package/03-patterns/tree/tree.js +456 -0
- package/04-recipes/analysis-report/README.md +80 -0
- package/04-recipes/analysis-report/analysis-report.css +187 -0
- package/04-recipes/analysis-report/demo.html +274 -0
- package/04-recipes/data-showcase/README.md +45 -0
- package/04-recipes/data-showcase/demo.html +247 -0
- package/04-recipes/detail-page/README.md +93 -0
- package/04-recipes/detail-page/demo.html +55 -0
- package/04-recipes/detail-page/detail-page.css +190 -0
- package/04-recipes/detail-page/full.html +60 -0
- package/04-recipes/detail-page/null.html +60 -0
- package/04-recipes/detail-page/partial.html +60 -0
- package/04-recipes/detail-page/verify-skeleton.py +19 -0
- package/04-recipes/longform/README.md +93 -0
- package/04-recipes/longform/agc-memory.svg +4 -0
- package/04-recipes/longform/content.html +610 -0
- package/04-recipes/longform/content.md +51 -0
- package/04-recipes/longform/longform.html +627 -0
- package/04-recipes/report/README.md +176 -0
- package/04-recipes/report/demo.html +355 -0
- package/04-recipes/report/report.css +364 -0
- package/04-recipes/table/README.md +103 -0
- package/04-recipes/table/demo.html +455 -0
- package/04-recipes/table/table.css +372 -0
- package/04-recipes/tierA-tokens.css +189 -0
- package/09-assets/README.md +74 -0
- package/09-assets/bar/README.md +94 -0
- package/09-assets/bar/bar.css +166 -0
- package/09-assets/bar/bar.js +320 -0
- package/09-assets/bar/demo.html +176 -0
- package/09-assets/echarts-adapter/demo.html +192 -0
- package/09-assets/echarts-adapter/echarts-adapter.js +277 -0
- package/09-assets/model-viewer/README.md +155 -0
- package/09-assets/model-viewer/demo.html +105 -0
- package/09-assets/model-viewer/model-viewer.css +143 -0
- package/09-assets/model-viewer/model-viewer.js +423 -0
- package/09-assets/scientific-plot/README.md +94 -0
- package/09-assets/scientific-plot/demo.html +252 -0
- package/09-assets/scientific-plot/scientific-plot.css +141 -0
- package/09-assets/scientific-plot/scientific-plot.js +610 -0
- package/09-assets/sparkline/README.md +153 -0
- package/09-assets/sparkline/demo.html +163 -0
- package/09-assets/sparkline/sparkline.css +158 -0
- package/09-assets/sparkline/sparkline.js +508 -0
- package/API.md +332 -0
- package/CHANGELOG.md +3116 -0
- package/CODE_OF_CONDUCT.md +64 -0
- package/CONTRIBUTING.md +89 -0
- package/ERRORS.md +1658 -0
- package/LICENSE +21 -0
- package/README.md +491 -2
- package/SECURITY.md +72 -0
- package/START-HERE.md +164 -0
- package/SUPPORT.md +109 -0
- package/adapters/presentation/README.md +113 -0
- package/adapters/presentation/check.js +101 -0
- package/adapters/presentation/deck.schema.json +74 -0
- package/adapters/presentation/to-doc.js +138 -0
- package/ai/START-HERE.md +104 -0
- package/ai/behaviors.json +380 -0
- package/ai/behaviors.schema.json +61 -0
- package/ai/capabilities.json +56 -0
- package/ai/cli.js +387 -0
- package/ai/components.json +1944 -0
- package/ai/components.meta.json +168 -0
- package/ai/components.schema.json +220 -0
- package/ai/contract.schema.json +147 -0
- package/ai/deprecations.json +55 -0
- package/ai/deprecations.schema.json +85 -0
- package/ai/motion-policy.json +22 -0
- package/ai/patch.js +323 -0
- package/ai/patch.schema.json +63 -0
- package/ai/profiles.json +92 -0
- package/ai/tokens.audience.json +55 -0
- package/ai/tokens.audience.schema.json +73 -0
- package/ai/tokens.json +762 -0
- package/ai/tokens.schema.json +58 -0
- package/ai/tokens.tree.json +752 -0
- package/ai/tokens.tree.schema.json +56 -0
- package/ai/validate.js +383 -0
- package/dist/01-tokens/behavior/dismissable.js +60 -0
- package/dist/01-tokens/behavior/emit.js +21 -0
- package/dist/01-tokens/behavior/focus-return.js +31 -0
- package/dist/01-tokens/behavior/roving.js +77 -0
- package/dist/01-tokens/behavior/typeahead.js +61 -0
- package/dist/01-tokens/focus-ring.css +1 -0
- package/dist/01-tokens/page.css +1 -0
- package/dist/01-tokens/polyfill.js +78 -0
- package/dist/01-tokens/scrollbar.css +1 -0
- package/dist/01-tokens/theme-toggle.js +61 -0
- package/dist/01-tokens/tokens.css +1 -0
- package/dist/01-tokens/typography.css +1 -0
- package/dist/02-primitives/badge/badge.css +1 -0
- package/dist/02-primitives/button/button.css +1 -0
- package/dist/02-primitives/card/card.css +1 -0
- package/dist/02-primitives/choice/choice.css +1 -0
- package/dist/02-primitives/combobox/combobox.css +1 -0
- package/dist/02-primitives/combobox/combobox.js +439 -0
- package/dist/02-primitives/date-range/date-range.css +1 -0
- package/dist/02-primitives/date-range/date-range.js +267 -0
- package/dist/02-primitives/input/input.css +1 -0
- package/dist/02-primitives/popover/popover.css +1 -0
- package/dist/02-primitives/popover/popover.js +257 -0
- package/dist/02-primitives/progress/progress.css +1 -0
- package/dist/02-primitives/select/select.css +1 -0
- package/dist/02-primitives/select/select.js +449 -0
- package/dist/02-primitives/separator/separator.css +1 -0
- package/dist/02-primitives/skeleton/skeleton.css +1 -0
- package/dist/02-primitives/switch/switch.css +1 -0
- package/dist/03-patterns/accordion/accordion.css +1 -0
- package/dist/03-patterns/accordion/accordion.js +219 -0
- package/dist/03-patterns/content/content.css +1 -0
- package/dist/03-patterns/drawer/drawer.css +1 -0
- package/dist/03-patterns/drawer/drawer.js +229 -0
- package/dist/03-patterns/dropdown/dropdown.css +1 -0
- package/dist/03-patterns/dropdown/dropdown.js +365 -0
- package/dist/03-patterns/form-validation/form-validation.css +1 -0
- package/dist/03-patterns/list/flip.js +117 -0
- package/dist/03-patterns/list/list.css +1 -0
- package/dist/03-patterns/nav/nav.css +1 -0
- package/dist/03-patterns/nav/nav.js +322 -0
- package/dist/03-patterns/nav/toc.js +76 -0
- package/dist/03-patterns/overlay/overlay.css +1 -0
- package/dist/03-patterns/overlay/overlay.js +398 -0
- package/dist/03-patterns/pagination/pagination.css +1 -0
- package/dist/03-patterns/pagination/pagination.js +273 -0
- package/dist/03-patterns/reveal/reveal.css +1 -0
- package/dist/03-patterns/reveal/reveal.js +138 -0
- package/dist/03-patterns/states/states.css +1 -0
- package/dist/03-patterns/tabs/tabs.css +1 -0
- package/dist/03-patterns/tabs/tabs.js +242 -0
- package/dist/03-patterns/tooltip/tooltip.css +1 -0
- package/dist/03-patterns/tooltip/tooltip.js +213 -0
- package/dist/03-patterns/tree/tree.css +1 -0
- package/dist/03-patterns/tree/tree.js +383 -0
- package/dist/04-recipes/analysis-report/analysis-report.css +1 -0
- package/dist/04-recipes/detail-page/detail-page.css +1 -0
- package/dist/04-recipes/report/report.css +1 -0
- package/dist/04-recipes/table/table.css +1 -0
- package/dist/04-recipes/tierA-tokens.css +1 -0
- package/dist/09-assets/bar/bar.css +1 -0
- package/dist/09-assets/bar/bar.js +222 -0
- package/dist/09-assets/echarts-adapter/echarts-adapter.js +185 -0
- package/dist/09-assets/model-viewer/model-viewer.css +1 -0
- package/dist/09-assets/model-viewer/model-viewer.js +320 -0
- package/dist/09-assets/scientific-plot/scientific-plot.css +1 -0
- package/dist/09-assets/scientific-plot/scientific-plot.js +568 -0
- package/dist/09-assets/sparkline/sparkline.css +1 -0
- package/dist/09-assets/sparkline/sparkline.js +265 -0
- package/dist/COSTS.md +56 -0
- package/docs/AI-CONTRACT.md +197 -0
- package/docs/BACKLOG.md +127 -0
- package/docs/BENCHMARK.md +252 -0
- package/docs/GUIDANCE.md +154 -0
- package/docs/INVARIANT.md +372 -0
- package/docs/LAYER-BASELINE.md +158 -0
- package/docs/RELEASING.md +275 -0
- package/docs/RFC-THEME-SCOPE.md +141 -0
- package/docs/ROADMAP.md +182 -0
- package/index.html +82 -0
- package/package.json +107 -4
- package/types/index.d.ts +310 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* popover.js — 气泡卡片的行为
|
|
3
|
+
* ============================================================================
|
|
4
|
+
* ⭐ 与 dialog 的根本区别:**非模态**
|
|
5
|
+
* dialog : 背景锁死、focus trap、必须处理完才能继续
|
|
6
|
+
* popover : 背景**仍可交互**,用户可以"看一眼再关掉"
|
|
7
|
+
* ⇒ 所以 popover **不**做 focus trap(那会让人出不去),
|
|
8
|
+
* 只做「焦点进入」+「Esc 关闭」+「焦点归还」。
|
|
9
|
+
*
|
|
10
|
+
* 参照:Radix Popover / MUI Popover
|
|
11
|
+
* ============================================================================
|
|
12
|
+
*/
|
|
13
|
+
(function (global) {
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
/* ==== BEHAVIOR INJECT BEGIN: dismissable ==== */
|
|
17
|
+
var flDismissable = function (opts) {
|
|
18
|
+
var opt = opts || {};
|
|
19
|
+
var escOn = opt.escOn || [];
|
|
20
|
+
var inside = opt.inside || [];
|
|
21
|
+
var when = opt.when || function () { return true; };
|
|
22
|
+
var onDismiss = opt.onDismiss || function () {};
|
|
23
|
+
|
|
24
|
+
var shared = window.__flDismiss || (window.__flDismiss = { stack: [], bound: false });
|
|
25
|
+
|
|
26
|
+
function isInside(node) {
|
|
27
|
+
if (!node) return false;
|
|
28
|
+
for (var i = 0; i < inside.length; i++) {
|
|
29
|
+
var el = inside[i];
|
|
30
|
+
if (!el || !el.contains) continue;
|
|
31
|
+
if (el === node || el.contains(node)) return true;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function onKey(e) {
|
|
37
|
+
if (!when()) return;
|
|
38
|
+
var k = e.key;
|
|
39
|
+
if (k !== 'Escape' && k !== 'Esc' && e.keyCode !== 27) return;
|
|
40
|
+
/* 🔴 为什么 onDismiss 在前、preventDefault/stopPropagation 在后
|
|
41
|
+
(0.8.8 改):**分级 Esc** 需要核让组件有机会说"这次我没关"。
|
|
42
|
+
combobox 的 Esc 是三级的:
|
|
43
|
+
① 列表开着 → 关列表
|
|
44
|
+
② 列表关了、输入有字 → 清空输入
|
|
45
|
+
③ 都没了 → 什么都不做(此时**不该**阻止默认行为,
|
|
46
|
+
让 Esc 能继续传给外层浮层)
|
|
47
|
+
而原实现是「无条件 preventDefault + stopPropagation,再调 onDismiss」
|
|
48
|
+
⇒ 组件根本无法表达"这次我没关"。
|
|
49
|
+
⇒ 改成:先问onDismiss,它返回 false 就**不干预**默认行为。
|
|
50
|
+
⚠️ 顺序安全性:stopPropagation 只需在**本次监听器执行期间**被调用即可
|
|
51
|
+
阻止冒泡到外层,放在 onDismiss 之后仍然有效。
|
|
52
|
+
*/
|
|
53
|
+
var closed = onDismiss(e, 'esc');
|
|
54
|
+
if (closed === false) return;
|
|
55
|
+
if (e.preventDefault) e.preventDefault();
|
|
56
|
+
if (e.stopPropagation) e.stopPropagation();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function onDocClick(e) {
|
|
60
|
+
var top = null;
|
|
61
|
+
for (var i = shared.stack.length - 1; i >= 0; i--) {
|
|
62
|
+
if (shared.stack[i].when()) { top = shared.stack[i]; break; }
|
|
63
|
+
}
|
|
64
|
+
if (!top || top.isInside(e.target)) return;
|
|
65
|
+
top.onDismiss(e, 'outside');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
var rec = { when: when, isInside: isInside, onDismiss: onDismiss };
|
|
69
|
+
|
|
70
|
+
for (var j = 0; j < escOn.length; j++) {
|
|
71
|
+
if (escOn[j]) escOn[j].addEventListener('keydown', onKey);
|
|
72
|
+
}
|
|
73
|
+
shared.stack.push(rec);
|
|
74
|
+
if (!shared.bound) {
|
|
75
|
+
shared.bound = true;
|
|
76
|
+
document.addEventListener('click', onDocClick, true);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
destroy: function () {
|
|
81
|
+
for (var j = 0; j < escOn.length; j++) {
|
|
82
|
+
if (escOn[j]) escOn[j].removeEventListener('keydown', onKey);
|
|
83
|
+
}
|
|
84
|
+
var k = shared.stack.indexOf(rec);
|
|
85
|
+
if (k >= 0) shared.stack.splice(k, 1);
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
/* ==== BEHAVIOR INJECT END: dismissable ==== */
|
|
90
|
+
|
|
91
|
+
/* ==== BEHAVIOR INJECT BEGIN: focus-return ==== */
|
|
92
|
+
var flFocusReturn = function (opts) {
|
|
93
|
+
var opt = opts || {};
|
|
94
|
+
var saved = null;
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
save: function () {
|
|
98
|
+
var a = document.activeElement;
|
|
99
|
+
saved = (a && a !== document.body && a.focus) ? a : null;
|
|
100
|
+
return saved;
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
restore: function () {
|
|
104
|
+
var target = saved;
|
|
105
|
+
/* ⚠️ 见文档 ②:不判 contains 的话,元素被移除后 focus() 不报错
|
|
106
|
+
但焦点不动 ⇒ 用户以为还回去了,其实掉在 body 上。 */
|
|
107
|
+
if (!target || !document.contains(target)) target = opt.fallback || null;
|
|
108
|
+
if (!target) { saved = null; return false; }
|
|
109
|
+
try {
|
|
110
|
+
target.focus();
|
|
111
|
+
} catch (e) {
|
|
112
|
+
saved = null;
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
var ok = document.activeElement === target;
|
|
116
|
+
saved = null; /* 见文档 ③ */
|
|
117
|
+
return ok;
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
target: function () { return saved; },
|
|
121
|
+
clear: function () { saved = null; },
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
/* ==== BEHAVIOR INJECT END: focus-return ==== */
|
|
125
|
+
/* ==== BEHAVIOR INJECT BEGIN: emit ==== */
|
|
126
|
+
var flEmit = function (el, name, detail) {
|
|
127
|
+
if (!el || !el.dispatchEvent) return null;
|
|
128
|
+
var type = name.indexOf('fl-') === 0 ? name : 'fl-' + name;
|
|
129
|
+
var ev = null;
|
|
130
|
+
/* 老 WebView 没有 CustomEvent 构造函数 ⇒ 兜底走 createEvent(理由见文件头)*/
|
|
131
|
+
if (typeof window.CustomEvent === 'function') {
|
|
132
|
+
try {
|
|
133
|
+
ev = new window.CustomEvent(type, {
|
|
134
|
+
detail: detail || null, bubbles: true, cancelable: false
|
|
135
|
+
});
|
|
136
|
+
} catch (e) { ev = null; }
|
|
137
|
+
}
|
|
138
|
+
if (!ev) {
|
|
139
|
+
try {
|
|
140
|
+
ev = document.createEvent('CustomEvent');
|
|
141
|
+
ev.initCustomEvent(type, true, false, detail || null);
|
|
142
|
+
} catch (e2) { return null; }
|
|
143
|
+
}
|
|
144
|
+
el.dispatchEvent(ev);
|
|
145
|
+
return ev;
|
|
146
|
+
};
|
|
147
|
+
/* ==== BEHAVIOR INJECT END: emit ==== */
|
|
148
|
+
|
|
149
|
+
/* ⭐ 同一时间只开一个 popover —— 两个叠着会让用户不知道该关哪个 */
|
|
150
|
+
var current = null;
|
|
151
|
+
|
|
152
|
+
function focusableIn(root) {
|
|
153
|
+
var all = root.querySelectorAll(
|
|
154
|
+
'a[href], button:not([disabled]), input:not([disabled]), ' +
|
|
155
|
+
'select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
|
156
|
+
);
|
|
157
|
+
return Array.prototype.filter.call(all, function (el) {
|
|
158
|
+
var cs = global.getComputedStyle(el);
|
|
159
|
+
if (cs.display === 'none' || cs.visibility === 'hidden') return false;
|
|
160
|
+
var r = el.getBoundingClientRect();
|
|
161
|
+
return r.width >= 1 && r.height >= 1;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 绑定一个 popover
|
|
167
|
+
* @param {Element} trigger 触发元素
|
|
168
|
+
* @param {object} opts
|
|
169
|
+
* - place: 'bottom' | 'top' | 'end' | 'start'(默认 bottom)
|
|
170
|
+
* - html: 浮层内容 HTML
|
|
171
|
+
* - onOpen / onClose 回调
|
|
172
|
+
* - closeOnOutsideClick(默认 true)
|
|
173
|
+
* - closeOnEsc(默认 true)
|
|
174
|
+
*/
|
|
175
|
+
function attach(trigger, opts) {
|
|
176
|
+
opts = opts || {};
|
|
177
|
+
|
|
178
|
+
/* ---- 建 DOM ---- */
|
|
179
|
+
var anchor = document.createElement('span');
|
|
180
|
+
anchor.className = 'popover-anchor';
|
|
181
|
+
trigger.parentNode.insertBefore(anchor, trigger);
|
|
182
|
+
anchor.appendChild(trigger);
|
|
183
|
+
|
|
184
|
+
var box = document.createElement('div');
|
|
185
|
+
box.className = 'popover popover--' + (opts.place || 'bottom');
|
|
186
|
+
box.setAttribute('data-open', 'false');
|
|
187
|
+
box.setAttribute('inert', ''); /* 初始关闭态即不可聚焦 */
|
|
188
|
+
/* 🔴 关键:popover 用 `dialog` 角色但**不加 aria-modal**
|
|
189
|
+
(加了就是模态,语义就错了)。 */
|
|
190
|
+
box.setAttribute('role', 'dialog');
|
|
191
|
+
box.setAttribute('aria-label', opts.label || '');
|
|
192
|
+
box.innerHTML =
|
|
193
|
+
'<span class="popover__arrow" aria-hidden="true"></span>' +
|
|
194
|
+
'<div class="popover__body">' + (opts.html || '') + '</div>';
|
|
195
|
+
|
|
196
|
+
/* 箭头要指向触发元素的中心,而不是浮层的中心 */
|
|
197
|
+
positionArrow(box, trigger);
|
|
198
|
+
anchor.appendChild(box);
|
|
199
|
+
|
|
200
|
+
/* 焦点归位走 focus-return 核(01-tokens/behavior/focus-return.js)。
|
|
201
|
+
fallback 给 trigger:万一打开它的那个元素被删了,焦点至少回到触发器,
|
|
202
|
+
而不是掉到 body 上(键盘用户要从页面开头重新 Tab)。 */
|
|
203
|
+
var fr = flFocusReturn({ fallback: trigger });
|
|
204
|
+
|
|
205
|
+
function open() {
|
|
206
|
+
if (current && current !== api) current.close();
|
|
207
|
+
fr.save();
|
|
208
|
+
box.setAttribute('data-open', 'true');
|
|
209
|
+
/* 🔴 关闭态必须用 **inert**,不能用 pointer-events ——
|
|
210
|
+
* 实测踩坑:`pointer-events:none` **拦不住 Tab**
|
|
211
|
+
* (Tab 走的是 tabindex/focusability,不是命中测试)
|
|
212
|
+
* ⇒ 关闭的浮层里的按钮仍能被 Tab 到,用户焦点会跑到"看不见"的地方。
|
|
213
|
+
* inert 一次性解决:不可聚焦、不可点击、读屏也不读。 */
|
|
214
|
+
box.removeAttribute('inert');
|
|
215
|
+
|
|
216
|
+
/* 🔴 焦点**进入**浮层 —— 键盘用户要能直接操作里面的内容。
|
|
217
|
+
* ⚠️ 与 dialog 不同:这里**不做 trap**(那会让人出不去)。 */
|
|
218
|
+
var f = focusableIn(box);
|
|
219
|
+
if (f.length) f[0].focus();
|
|
220
|
+
else box.setAttribute('tabindex', '-1'), box.focus();
|
|
221
|
+
|
|
222
|
+
current = api;
|
|
223
|
+
/* 先派发事件再调回调 ⇒ 回调抛异常也不连累其它订阅者(核文件头 ⑦)*/
|
|
224
|
+
flEmit(anchor, 'fl-open', null);
|
|
225
|
+
if (opts.onOpen) opts.onOpen();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function close() {
|
|
229
|
+
if (box.getAttribute('data-open') !== 'true') return;
|
|
230
|
+
box.setAttribute('data-open', 'false');
|
|
231
|
+
/* ⭐ 见 open() 的说明:必须 inert,不能只靠 pointer-events */
|
|
232
|
+
box.setAttribute('inert', '');
|
|
233
|
+
/* ⭐ 焦点归还 —— 否则键盘用户要从页面开头重新 Tab。 */
|
|
234
|
+
fr.restore();
|
|
235
|
+
if (current === api) current = null;
|
|
236
|
+
flEmit(anchor, 'fl-close', null);
|
|
237
|
+
if (opts.onClose) opts.onClose();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function toggle() {
|
|
241
|
+
if (box.getAttribute('data-open') === 'true') close();
|
|
242
|
+
else open();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* ---- 触发器 ---- */
|
|
246
|
+
trigger.setAttribute('aria-haspopup', 'dialog');
|
|
247
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
248
|
+
trigger.addEventListener('click', function (e) {
|
|
249
|
+
/* ⭐ 不要把它交给"外部点击"判定:触发器在浮层**外面**,
|
|
250
|
+
但点它是"切换"不是"关闭"。dismissable 核的 `inside` 含 anchor,
|
|
251
|
+
anchor 又含 trigger ⇒ 已经算作内部;这里 stopPropagation 只是
|
|
252
|
+
顺手阻止更外层的组件(如 dropdown)也收到这次点击。 */
|
|
253
|
+
e.stopPropagation();
|
|
254
|
+
toggle();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
/* ---- Esc / 点击外部关闭:走 dismissable 核(01-tokens/behavior/dismissable.js)
|
|
258
|
+
—— 手写的两个 Esc 分支与外部点击监听已删除,全库统一到一处。 ---- */
|
|
259
|
+
var dismiss = flDismissable({
|
|
260
|
+
escOn: [trigger, box],
|
|
261
|
+
inside: [box, anchor], /* anchor 含 trigger ⇒ 点触发器不算"外部" */
|
|
262
|
+
when: function () { return box.getAttribute('data-open') === 'true'; },
|
|
263
|
+
onDismiss: function (e, reason) { close(); },
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
/* 🔴 Tab **不做任何处理** —— 这是修正后的行为,别"顺手"加回来。
|
|
267
|
+
*
|
|
268
|
+
* 第一版(错):在浮层内循环 Tab(最后一个绕回第一个)。
|
|
269
|
+
* 实测焦点序列:近 7 天 → 近 30 天 → 取消 → 应用 → 近 7 天 → …
|
|
270
|
+
* ⇒ 用户**被困住了**,出不去。
|
|
271
|
+
*
|
|
272
|
+
* ⭐ 正确做法(Radix Popover 的默认 `trapFocus={false}`):
|
|
273
|
+
* popover 是**非模态**的,Tab 应当自然走到页面下一个可聚焦元素。
|
|
274
|
+
* 困住用户是 dialog 的行为,不是 popover 的。
|
|
275
|
+
* 焦点"进入"浮层已经由 open() 里的 focus() 保证了,
|
|
276
|
+
* 不需要用 trap 来维持。
|
|
277
|
+
*/
|
|
278
|
+
|
|
279
|
+
/* ---- 关闭态的内容不可聚焦 ---- */
|
|
280
|
+
box.addEventListener('transitionend', function () {
|
|
281
|
+
if (box.getAttribute('data-open') === 'false') {
|
|
282
|
+
box.setAttribute('aria-hidden', 'true');
|
|
283
|
+
} else {
|
|
284
|
+
box.removeAttribute('aria-hidden');
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
var api = {
|
|
289
|
+
open: open,
|
|
290
|
+
close: close,
|
|
291
|
+
toggle: toggle,
|
|
292
|
+
destroy: function () {
|
|
293
|
+
dismiss.destroy();
|
|
294
|
+
close();
|
|
295
|
+
box.remove();
|
|
296
|
+
anchor.remove();
|
|
297
|
+
},
|
|
298
|
+
el: box,
|
|
299
|
+
};
|
|
300
|
+
return api;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/* 箭头对准触发元素中心(浮层可能比触发元素宽) */
|
|
304
|
+
function positionArrow(box, trigger) {
|
|
305
|
+
var arrow = box.querySelector('.popover__arrow');
|
|
306
|
+
if (!arrow) return;
|
|
307
|
+
var place = (box.className.match(/popover--(\w+)/) || [])[1];
|
|
308
|
+
if (place === 'start' || place === 'end') {
|
|
309
|
+
/* 左右方向:箭头固定垂直居中 */
|
|
310
|
+
arrow.style.top = '50%';
|
|
311
|
+
arrow.style.marginTop = '';
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
var br = trigger.getBoundingClientRect();
|
|
315
|
+
var ar = box.getBoundingClientRect();
|
|
316
|
+
var offset = br.left + br.width / 2 - (ar.left + parseFloat(getComputedStyle(box).paddingLeft || 0));
|
|
317
|
+
/* 夹在浮层内,避免箭头跑出圆角外 */
|
|
318
|
+
var max = ar.width - 24;
|
|
319
|
+
arrow.style.marginLeft = Math.max(12, Math.min(offset, max)) + 'px';
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
var Popover = {
|
|
323
|
+
attach: function (trigger, opts) {
|
|
324
|
+
return attach(trigger, opts);
|
|
325
|
+
},
|
|
326
|
+
closeAll: function () { if (current) current.close(); },
|
|
327
|
+
get current() { return current; },
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
global.Popover = Popover;
|
|
331
|
+
})(window);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# progress · 进度条
|
|
2
|
+
|
|
3
|
+
> 进度**已知**时用它;进度**不知道**时用 [`skeleton`](../skeleton/)。
|
|
4
|
+
> ⭐ 两者混用是常见错误:不知道进度却给一个假的百分比,**比不给更糟**。
|
|
5
|
+
|
|
6
|
+
## 用法
|
|
7
|
+
|
|
8
|
+
```html
|
|
9
|
+
<link rel="stylesheet" href="progress.css">
|
|
10
|
+
|
|
11
|
+
<!-- 线形(最常用)-->
|
|
12
|
+
<span class="progress" role="progressbar" aria-valuenow="60"
|
|
13
|
+
aria-valuemin="0" aria-valuemax="100" aria-label="上传进度"
|
|
14
|
+
data-progress="60">
|
|
15
|
+
<span class="progress__bar"></span>
|
|
16
|
+
</span>
|
|
17
|
+
|
|
18
|
+
<!-- 语义色 -->
|
|
19
|
+
<span class="progress progress--danger" ...> <!-- success / warning / danger -->
|
|
20
|
+
<!-- 尺寸 --> <span class="progress progress--lg"> / progress--sm
|
|
21
|
+
|
|
22
|
+
<!-- 环形(纯 CSS conic-gradient,零 SVG)-->
|
|
23
|
+
<span class="progress-ring" data-progress="72" role="progressbar" aria-valuenow="72">
|
|
24
|
+
<span class="progress-ring__label" aria-hidden="true">72%</span>
|
|
25
|
+
</span>
|
|
26
|
+
|
|
27
|
+
<!-- 不确定进度(不知道要多久)-->
|
|
28
|
+
<span class="progress progress--indeterminate" aria-busy="true" aria-label="正在处理">
|
|
29
|
+
<span class="progress__bar"></span>
|
|
30
|
+
</span>
|
|
31
|
+
|
|
32
|
+
<!-- 步骤分段(第 3 步 / 共 5 步)-->
|
|
33
|
+
<span class="progress-steps" role="progressbar" aria-valuenow="3" aria-valuemax="5">
|
|
34
|
+
<span class="progress-steps__seg progress-steps__seg--done"></span>
|
|
35
|
+
<span class="progress-steps__seg progress-steps__seg--current"></span>
|
|
36
|
+
<span class="progress-steps__seg"></span>
|
|
37
|
+
</span>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**动态进度用 JS 写**(比行内 `style` 便宜,只改一个自定义属性):
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
el.style.setProperty('--progress', '60%');
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 令牌
|
|
47
|
+
|
|
48
|
+
| 令牌 | 默认 | 说明 |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| `--progress` | `0%` | **当前值**。默认 0% 是正确默认,不是占位 |
|
|
51
|
+
| `--progress-h` | `6px` | 线形高度(`--sm` 4px / `--lg` 10px)|
|
|
52
|
+
| `--progress-dur` | `240ms` | 宽度过渡 |
|
|
53
|
+
| `--ease-out` | `cubic-bezier(.23,1,.32,1)` | ⭐ 独立于 `--ease`:进度要"向前"的 feel,匀速反而像卡住 |
|
|
54
|
+
| `--r-full` | `999px` | 胶囊圆角 |
|
|
55
|
+
| `--ring-size` / `--ring-thickness` | `64px` / `8px` | 环形尺寸 |
|
|
56
|
+
|
|
57
|
+
## 无障碍(**红线**)
|
|
58
|
+
|
|
59
|
+
```html
|
|
60
|
+
<!-- ✅ 确定进度 -->
|
|
61
|
+
role="progressbar" + aria-valuenow/min/max + aria-label
|
|
62
|
+
|
|
63
|
+
<!-- ✅ 不确定进度:只标 aria-busy,**不要**给 valuenow -->
|
|
64
|
+
role="progressbar" aria-busy="true" aria-label="正在处理"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
⚠️ **不确定时不要显示百分比** —— 那是撒谎。读屏只会听到「正在处理,进度未知」,这才是事实。
|
|
68
|
+
|
|
69
|
+
⚠️ `prefers-reduced-motion: reduce` 时扫光动画**停掉,但条子仍可见**(用户依然知道系统在忙)。
|
|
70
|
+
|
|
71
|
+
## 何时**不该**用
|
|
72
|
+
|
|
73
|
+
- ❌ **不知道进度时**(停在 0% 或乱跳的百分比)⇒ 用 `skeleton`
|
|
74
|
+
- ❌ **不足 ~1 秒就完成**的操作 ⇒ 闪一下更伤体验,直接不显示
|
|
75
|
+
- ❌ **代替"还剩多久"** ⇒ 百分比给的是"比例",不是"时间"
|
|
76
|
+
- ❌ **环形用在极小尺寸**(< 32px)⇒ 环太细,读不出进度
|
|
77
|
+
- ❌ **在一个页面里同时出现 3 个以上进度条** ⇒ 用户不知道该看哪个
|
|
78
|
+
|
|
79
|
+
## 相关
|
|
80
|
+
|
|
81
|
+
- 未知进度 ⇒ [`skeleton`](../skeleton/)
|
|
82
|
+
- 短反馈("已保存")⇒ `toast`(**尚未提供**)
|
|
@@ -0,0 +1,154 @@
|
|
|
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>进度条 · progress</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="progress.css">
|
|
12
|
+
<style>
|
|
13
|
+
.demo {
|
|
14
|
+
background: var(--surface);
|
|
15
|
+
border: 1px solid var(--border-decor);
|
|
16
|
+
border-radius: var(--r-md);
|
|
17
|
+
padding: var(--sp-4);
|
|
18
|
+
margin-bottom: var(--sp-4);
|
|
19
|
+
}
|
|
20
|
+
.row { display: flex; align-items: center; gap: var(--sp-3); }
|
|
21
|
+
.row+.row { margin-top: var(--sp-3); }
|
|
22
|
+
.pct { font-size: var(--fs-sm); color: var(--text-secondary);
|
|
23
|
+
font-variant-numeric: tabular-nums; min-width: 3.2em; text-align: right; }
|
|
24
|
+
.bad-demo { background: var(--danger-bg); border-color: var(--danger); }
|
|
25
|
+
.bad { color: var(--danger); }
|
|
26
|
+
</style>
|
|
27
|
+
</head>
|
|
28
|
+
<body class="page page">
|
|
29
|
+
<main class="wrap">
|
|
30
|
+
<!-- 🔴 0.8.9:axe 报 landmark-one-main(moderate)—— 本页原本没有任何 landmark 容器。
|
|
31
|
+
加 <main> 是语义正确且不改布局的做法(<main> 默认不产生 box)。
|
|
32
|
+
⚠️ 试过给 <body> 加 role="main":axe 明确报 aria-allowed-role(该 role 对 <body>
|
|
33
|
+
是禁止的),而且也没解决 landmark 违规 ⇒ 已回退。 -->
|
|
34
|
+
|
|
35
|
+
<h1>进度条 · progress</h1>
|
|
36
|
+
<p class="lede">进度<strong>已知</strong>时用它;进度<strong>不知道</strong>时用 <code>skeleton</code>。混用是常见错误。</p>
|
|
37
|
+
|
|
38
|
+
<h2>1 · 线形(最常用)</h2>
|
|
39
|
+
<div class="demo">
|
|
40
|
+
<div class="row">
|
|
41
|
+
<span class="progress" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" aria-label="上传进度" data-progress="60">
|
|
42
|
+
<span class="progress__bar"></span>
|
|
43
|
+
</span>
|
|
44
|
+
<span class="pct" aria-hidden="true">60%</span>
|
|
45
|
+
</div>
|
|
46
|
+
<p class="note">用 <code>--progress</code> 驱动宽度;<code>role="progressbar"</code> + <code>aria-valuenow</code> 是<b>必须</b>的(读屏要能念出进度)。</p>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<h2>2 · 语义色</h2>
|
|
50
|
+
<div class="demo">
|
|
51
|
+
<div class="row">
|
|
52
|
+
<span class="progress progress--success" role="progressbar" aria-valuenow="100" aria-label="已完成" data-progress="100"><span class="progress__bar"></span></span>
|
|
53
|
+
<span class="pct" aria-hidden="true">完成</span>
|
|
54
|
+
</div>
|
|
55
|
+
<div class="row">
|
|
56
|
+
<span class="progress progress--warning" role="progressbar" aria-valuenow="85" aria-label="即将超时" data-progress="85"><span class="progress__bar"></span></span>
|
|
57
|
+
<span class="pct" aria-hidden="true">85%</span>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="row">
|
|
60
|
+
<span class="progress progress--danger" role="progressbar" aria-valuenow="40" aria-label="失败" data-progress="40"><span class="progress__bar"></span></span>
|
|
61
|
+
<span class="pct" aria-hidden="true">40%</span>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<h2>3 · 尺寸</h2>
|
|
66
|
+
<div class="demo">
|
|
67
|
+
<span class="progress progress--sm" role="progressbar" aria-valuenow="40" aria-label="小" data-progress="40"><span class="progress__bar"></span></span>
|
|
68
|
+
<div style="height:var(--sp-2)"></div>
|
|
69
|
+
<span class="progress" role="progressbar" aria-valuenow="40" aria-label="中" data-progress="40"><span class="progress__bar"></span></span>
|
|
70
|
+
<div style="height:var(--sp-2)"></div>
|
|
71
|
+
<span class="progress progress--lg" role="progressbar" aria-valuenow="40" aria-label="大" data-progress="40"><span class="progress__bar"></span></span>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<h2>4 · 环形</h2>
|
|
75
|
+
<div class="demo">
|
|
76
|
+
<div class="row" style="gap:var(--sp-5)">
|
|
77
|
+
<span class="progress-ring" data-progress="72" role="progressbar" aria-valuenow="72" aria-label="环形进度">
|
|
78
|
+
<span class="progress-ring__label" aria-hidden="true">72%</span>
|
|
79
|
+
</span>
|
|
80
|
+
<span class="progress-ring progress-ring--success" data-progress="100" role="progressbar" aria-valuenow="100" aria-label="已完成">
|
|
81
|
+
<span class="progress-ring__label" aria-hidden="true">✓</span>
|
|
82
|
+
</span>
|
|
83
|
+
<span class="progress-ring progress-ring--danger" data-progress="35" role="progressbar" aria-valuenow="35" aria-label="失败">
|
|
84
|
+
<span class="progress-ring__label" aria-hidden="true">35%</span>
|
|
85
|
+
</span>
|
|
86
|
+
</div>
|
|
87
|
+
<p class="note">纯 CSS <code>conic-gradient</code>,零 SVG、零依赖。数字用 <code>tabular-nums</code>,变化时不会左右跳。</p>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<h2>5 · 不确定进度</h2>
|
|
91
|
+
<div class="demo">
|
|
92
|
+
<span class="progress progress--indeterminate" role="progressbar" aria-label="正在处理(进度未知)" aria-busy="true">
|
|
93
|
+
<span class="progress__bar"></span>
|
|
94
|
+
</span>
|
|
95
|
+
<p class="note">
|
|
96
|
+
<b>不确定时不要显示百分比</b> —— 那是撒谎。
|
|
97
|
+
读屏只会听到「正在处理,进度未知」,这才是事实。
|
|
98
|
+
</p>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<h2>6 · 步骤分段</h2>
|
|
102
|
+
<div class="demo">
|
|
103
|
+
<div class="progress-steps" role="progressbar" aria-valuenow="3" aria-valuemin="1" aria-valuemax="5" aria-label="第 3 步,共 5 步">
|
|
104
|
+
<span class="progress-steps__seg progress-steps__seg--done"></span>
|
|
105
|
+
<span class="progress-steps__seg progress-steps__seg--done"></span>
|
|
106
|
+
<span class="progress-steps__seg progress-steps__seg--current"></span>
|
|
107
|
+
<span class="progress-steps__seg"></span>
|
|
108
|
+
<span class="progress-steps__seg"></span>
|
|
109
|
+
</div>
|
|
110
|
+
<p class="note">流程类进度("第 3 步 / 共 5 步")比百分比更贴切。</p>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<h2>7 · 减少动态效果时</h2>
|
|
114
|
+
<div class="demo">
|
|
115
|
+
<span class="progress progress--indeterminate" role="progressbar" aria-label="正在处理" aria-busy="true">
|
|
116
|
+
<span class="progress__bar"></span>
|
|
117
|
+
</span>
|
|
118
|
+
<p class="note">开启「减少动态效果」后扫光动画停掉,但条子<b>仍然可见</b> —— 用户依然知道系统在忙。</p>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<h2>反例 · 不该这样用</h2>
|
|
122
|
+
<div class="demo bad-demo">
|
|
123
|
+
<span class="progress" role="progressbar" aria-valuenow="0" aria-label="反例" data-progress="0">
|
|
124
|
+
<span class="progress__bar"></span>
|
|
125
|
+
</span>
|
|
126
|
+
<p class="note bad">
|
|
127
|
+
<b>不要用进度条表示「不知道要多久」。</b><br>
|
|
128
|
+
停在 0% 或乱跳的百分比,比 <code>skeleton</code> 更让人困惑 ——
|
|
129
|
+
用户会盯着它算还剩多久。<br>
|
|
130
|
+
<b>也不知道进度时</b>,用 <code>skeleton</code>(形状提示"这里会有内容")
|
|
131
|
+
或不确定进度条(明说"不知道")。
|
|
132
|
+
</p>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<script>
|
|
136
|
+
/* 🔴 I5:进度值用 **JS 写入**而不是行内 style
|
|
137
|
+
* ---------------------------------------------------------------
|
|
138
|
+
* ① 为什么不用行内 data-progress="60":
|
|
139
|
+
* 真实场景里进度是**动态**的,行内 style 每次要重新渲染 DOM。
|
|
140
|
+
* 用 setProperty 只改一个自定义属性,成本最低。
|
|
141
|
+
* ② 门禁 deps.py 认 setProperty ⇒ 不会误报"未定义令牌"
|
|
142
|
+
* (--progress 是**每个实例自己的值**,不是全局设计令牌)。
|
|
143
|
+
*/
|
|
144
|
+
document.querySelectorAll('.progress, .progress-ring, .progress-steps').forEach((el) => {
|
|
145
|
+
const m = el.getAttribute('data-progress');
|
|
146
|
+
if (m) {
|
|
147
|
+
el.style.setProperty('--progress', m + '%'); // 线形用(要带 %)
|
|
148
|
+
el.style.setProperty('--progress-num', m); // 环形用(不能带 %)
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
</script>
|
|
152
|
+
</main>
|
|
153
|
+
</body>
|
|
154
|
+
</html>
|