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,104 @@
|
|
|
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>date-range · 日期区间 · 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="date-range.css">
|
|
13
|
+
<style>
|
|
14
|
+
.pad { padding: var(--sp-3) 0 var(--sp-6); }
|
|
15
|
+
.log {
|
|
16
|
+
margin-top: var(--sp-3); padding: var(--sp-3);
|
|
17
|
+
background: var(--surface-sunken); border-radius: var(--r-sm);
|
|
18
|
+
font-family: var(--mono); font-size: var(--fs-xs);
|
|
19
|
+
color: var(--text-secondary); min-height: 2.8em;
|
|
20
|
+
}
|
|
21
|
+
</style>
|
|
22
|
+
</head>
|
|
23
|
+
<body class="page page">
|
|
24
|
+
<main class="wrap">
|
|
25
|
+
|
|
26
|
+
<h1>date-range · 日期区间</h1>
|
|
27
|
+
<p class="note">
|
|
28
|
+
财务/成本刚需。三件最容易漏:
|
|
29
|
+
<br>① <strong>必须有「清除」</strong>(填错了要能一键清空,而不是逐个退格)
|
|
30
|
+
<br>② <strong>约束校验</strong>:开始晚于结束、超出范围 ⇒ <strong>明确报错</strong>(不静默)
|
|
31
|
+
<br>③ 每个 input 有 <code><label></code>;错误用 <code>role="alert"</code> 播报
|
|
32
|
+
<br>⚠️ <strong>基于原生 <code><input type="date"></code></strong>:
|
|
33
|
+
原生自带触屏日历、本地化、键盘输入、无障碍语义 ——
|
|
34
|
+
自建日历要重做这一切,且移动端更差。
|
|
35
|
+
</p>
|
|
36
|
+
|
|
37
|
+
<h2>1 · 常用(无边界)</h2>
|
|
38
|
+
<div class="pad">
|
|
39
|
+
<div class="drange" id="d1">
|
|
40
|
+
<div class="drange__field">
|
|
41
|
+
<label class="drange__label" for="f1">开始</label>
|
|
42
|
+
<input class="drange__input" type="date" id="f1" data-dr-from>
|
|
43
|
+
</div>
|
|
44
|
+
<span class="drange__sep" aria-hidden="true">→</span>
|
|
45
|
+
<div class="drange__field">
|
|
46
|
+
<label class="drange__label" for="t1">结束</label>
|
|
47
|
+
<input class="drange__input" type="date" id="t1" data-dr-to>
|
|
48
|
+
</div>
|
|
49
|
+
<button class="drange__clear" type="button" data-dr-clear>清除</button>
|
|
50
|
+
<div class="drange__error" data-dr-error role="alert" hidden></div>
|
|
51
|
+
<div class="drange__summary" data-dr-summary aria-live="polite"></div>
|
|
52
|
+
<div class="drange__presets" data-dr-presets></div>
|
|
53
|
+
</div>
|
|
54
|
+
<div class="log" id="log">(onChange 会显示在这里)</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<h2>2 · 限定范围(2020–2026)</h2>
|
|
58
|
+
<div class="pad">
|
|
59
|
+
<div class="drange" id="d2">
|
|
60
|
+
<div class="drange__field">
|
|
61
|
+
<label class="drange__label" for="f2">开始</label>
|
|
62
|
+
<input class="drange__input" type="date" id="f2" data-dr-from>
|
|
63
|
+
</div>
|
|
64
|
+
<span class="drange__sep" aria-hidden="true">→</span>
|
|
65
|
+
<div class="drange__field">
|
|
66
|
+
<label class="drange__label" for="t2">结束</label>
|
|
67
|
+
<input class="drange__input" type="date" id="t2" data-dr-to>
|
|
68
|
+
</div>
|
|
69
|
+
<button class="drange__clear" type="button" data-dr-clear>清除</button>
|
|
70
|
+
<div class="drange__error" data-dr-error role="alert" hidden></div>
|
|
71
|
+
<div class="drange__summary" data-dr-summary aria-live="polite"></div>
|
|
72
|
+
<div class="drange__presets" data-dr-presets></div>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="log" id="log2">(试着填一个 2019 年或 2027 年 ⇒ 会报错)</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
</main>
|
|
78
|
+
|
|
79
|
+
<script src="date-range.js"></script>
|
|
80
|
+
<script>
|
|
81
|
+
(function () {
|
|
82
|
+
var log = document.getElementById('log');
|
|
83
|
+
var r1 = DateRange.create(document.getElementById('d1'), {
|
|
84
|
+
onChange: function (v, ok) {
|
|
85
|
+
log.textContent = 'from=' + (v.from || '(空)') + ' to=' + (v.to || '(空)') +
|
|
86
|
+
' valid=' + ok;
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
/* 探针:给契约脚本用 */
|
|
90
|
+
window.__dr1 = r1;
|
|
91
|
+
|
|
92
|
+
var log2 = document.getElementById('log2');
|
|
93
|
+
DateRange.create(document.getElementById('d2'), {
|
|
94
|
+
min: '2020-01-01', max: '2026-12-31',
|
|
95
|
+
presets: ['thisQ', 'lastQ', 'lastM', 'yoy'],
|
|
96
|
+
onChange: function (v, ok) {
|
|
97
|
+
log2.textContent = 'from=' + (v.from || '(空)') + ' to=' + (v.to || '(空)') +
|
|
98
|
+
' valid=' + ok + '(范围 2020-01-01 ~ 2026-12-31)';
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
})();
|
|
102
|
+
</script>
|
|
103
|
+
</body>
|
|
104
|
+
</html>
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# input / field
|
|
2
|
+
|
|
3
|
+
> 🔴 **没有 `.input` 这个类名。**
|
|
4
|
+
>
|
|
5
|
+
> 真实类名是 **`.field__input`**,而且必须包在 `.field` 里:
|
|
6
|
+
>
|
|
7
|
+
> ```html
|
|
8
|
+
> <div class="field">
|
|
9
|
+
> <label class="field__label" for="q">订单号</label>
|
|
10
|
+
> <input class="field__input" id="q" type="search" inputmode="numeric">
|
|
11
|
+
> <span class="field__hint">支持后四位模糊匹配</span>
|
|
12
|
+
> </div>
|
|
13
|
+
> ```
|
|
14
|
+
>
|
|
15
|
+
> 写 `<input class="input">` **不会报错、不会警告、控制台干净** ——
|
|
16
|
+
> 它只是拿到浏览器默认样式:高度约 **21px**(本组件是 44px),
|
|
17
|
+
> 边框、圆角、聚焦环、错误态全部没有。
|
|
18
|
+
> 这类"静默失效"比报错更贵:你会以为是 HTML 写错了,或者以为库就该这样。
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
| 字段 | 内容 |
|
|
22
|
+
|---|---|
|
|
23
|
+
| **来源** | 原创(依据 WCAG 2.1 + 移动端实测约束) |
|
|
24
|
+
| **实测** | 占位符 5.16 AA、错误文字 6.26 AA、边框 3.56(UI 边界);16px 依据 iOS Safari 行为 |
|
|
25
|
+
| **适用** | 精简档 / B / C,纯 CSS,可整段复制 |
|
|
26
|
+
| **依赖** | `01-tokens/tokens.css` · demo 另需 `02-primitives/button/button.css`(校验按钮)与 `01-tokens/typography.css`(`.small`) + `01-tokens/page.css`(页面骨架:`.wrap` / `.lede` / `.note`) |
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 五态(input 不是七态)
|
|
31
|
+
|
|
32
|
+
| 状态 | 实现 | 不做会怎样 |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| hover | 边框加深 | 边界无反馈 |
|
|
35
|
+
| **focus** | `:focus` 边框变色 + ring | **用户不知道光标在哪** |
|
|
36
|
+
| **disabled** | 灰底、不可聚焦、**值不提交** | 以为坏了 |
|
|
37
|
+
| **error** | `[aria-invalid]` 红框 + 图标 + 文字 | **校验错误对屏幕阅读器静默** |
|
|
38
|
+
| readonly | `[readonly]` 可聚焦、**值会提交** | 与 disabled 混淆 |
|
|
39
|
+
|
|
40
|
+
**input 没有 active 态**——输入框不响应按压。这一条也写进了 `states.py` 的形态区分里。
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 🔴 与 button 最关键的一处不同,别照抄
|
|
45
|
+
|
|
46
|
+
| | button | input |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| 焦点选择器 | `:focus-visible` | `:focus` |
|
|
49
|
+
| 理由 | 鼠标点击时不该冒出难看的框 | **用户必须随时知道"我现在在哪个框里"**,不管是鼠标点的还是 Tab 过去的 |
|
|
50
|
+
|
|
51
|
+
搞反了表单会非常难用。
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 无障碍清单(缺一条就不算做对)
|
|
56
|
+
|
|
57
|
+
```html
|
|
58
|
+
<div class="field">
|
|
59
|
+
<label class="field__label" for="amount">
|
|
60
|
+
金额 <span class="field__req" aria-hidden="true">*</span>
|
|
61
|
+
</label>
|
|
62
|
+
<input class="field__input" id="amount" type="text"
|
|
63
|
+
required
|
|
64
|
+
aria-describedby="amount-hint amount-err"
|
|
65
|
+
aria-invalid="true">
|
|
66
|
+
<span class="field__hint" id="amount-hint">最多两位小数</span>
|
|
67
|
+
<span class="field__error" id="amount-err" role="alert">请输入有效金额</span>
|
|
68
|
+
</div>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- **`for` / `id` 配对** —— 没有它,屏幕阅读器读出来只是"编辑框"
|
|
72
|
+
- **`aria-describedby`** —— 把说明和错误都挂到输入框上,聚焦时能听到
|
|
73
|
+
- **`aria-invalid="true"`** —— 机器可读的错误状态
|
|
74
|
+
- **`role="alert"` 或 `aria-live`** —— 错误动态出现时会被读出来
|
|
75
|
+
- **`required`** —— 别只放一个红色星号
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 三个容易踩的坑
|
|
80
|
+
|
|
81
|
+
**1. `font-size` 必须 ≥ 16px**
|
|
82
|
+
|
|
83
|
+
iOS Safari 在输入框字号小于 16px 时**会自动放大整个页面**。这是移动端最常见的表单 bug,而且是硬下限,不是审美。
|
|
84
|
+
|
|
85
|
+
**2. 不能只靠颜色表示错误(WCAG 1.4.1)**
|
|
86
|
+
|
|
87
|
+
只把边框变红,色盲用户完全看不出哪里错了。本库给三条线索:红边框 + 叹号图标 + 错误文字。
|
|
88
|
+
|
|
89
|
+
**3. `readonly` 和 `disabled` 是两回事**
|
|
90
|
+
|
|
91
|
+
| | disabled | readonly |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| 可聚焦 | ❌ | ✅ |
|
|
94
|
+
| 可复制 | ❌ | ✅ |
|
|
95
|
+
| **值提交** | **❌** | **✅** |
|
|
96
|
+
|
|
97
|
+
展示计算结果、展示带过来的值 → 用 `readonly`,让用户能复制。做成 `disabled` 的样子,用户会以为这个值不提交。
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 反例 —— 什么时候不该用
|
|
102
|
+
|
|
103
|
+
- ❌ **用 placeholder 当 label** —— 一输入就消失,对比度通常不达标,屏幕阅读器支持不一致
|
|
104
|
+
- ❌ **普通输入框加成功态** —— 只在有明确"校验通过"语义时用(如"用户名可用"),其余是噪音
|
|
105
|
+
- ❌ **错误提示只变红不写文字** —— 对屏幕阅读器用户等于静默失败
|
|
106
|
+
- ❌ **`.field--row` 用在窄屏** —— 480px 以下会自动回退纵向,别手动覆盖这个回退
|
|
107
|
+
- ❌ **textarea 不设 `min-height`** —— 高度会塌,用户不知道能输入多少
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 门禁
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
python 05-audit/states.py 02-primitives/input/input.css
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
当前:零缺失(含 HIGH 五项全过)。
|
|
118
|
+
`states.py` 会把 `input/field` 类自动归到 input 形态,不查 `active`。
|
|
119
|
+
|
|
120
|
+
## 备好的能力(demo 里没演示,但可以直接用)
|
|
121
|
+
|
|
122
|
+
| 类 | 作用 | 什么时候用 |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| `.field__input--sm` | 小尺寸(表格 / 紧凑栏) | 表格行内、侧栏等密度高的场景 |
|
|
125
|
+
|
|
126
|
+
> ⚠️ **demo 里没有演示这一档** —— 它是留给复用者的钩子,
|
|
127
|
+
> 不是"看起来有其实没有"。要用就自己加类。
|
|
128
|
+
|
|
129
|
+
## 🔴 移动端必填:inputmode 与 autocomplete
|
|
130
|
+
|
|
131
|
+
这两个属性**不影响桌面**,但**决定手机上用户体验**:
|
|
132
|
+
|
|
133
|
+
### 1 · `inputmode` —— 决定**弹出哪种键盘**
|
|
134
|
+
|
|
135
|
+
```html
|
|
136
|
+
<!-- 金额 / 数量 → 数字键盘(有小数点)-->
|
|
137
|
+
<input type="text" inputmode="decimal">
|
|
138
|
+
|
|
139
|
+
<!-- 邮箱 → 带 @ 的键盘 -->
|
|
140
|
+
<input type="email" inputmode="email">
|
|
141
|
+
|
|
142
|
+
<!-- 电话 → 纯数字键盘 -->
|
|
143
|
+
<input type="tel" inputmode="tel">
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
⚠️ **`type="text"` + 不写 `inputmode` ⇒ 手机上弹的是全键盘**,
|
|
147
|
+
用户要自己切到数字 —— 这是**成本类表单最常见的体验缺陷**。
|
|
148
|
+
|
|
149
|
+
### 2 · `autocomplete` —— 决定**浏览器是否自动填充**
|
|
150
|
+
|
|
151
|
+
```html
|
|
152
|
+
<!-- 开启自动填充(推荐)-->
|
|
153
|
+
<input name="email" autocomplete="email" type="email">
|
|
154
|
+
|
|
155
|
+
<!-- 演示/测试页要避免真实数据填充 -->
|
|
156
|
+
autocomplete="off"
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
⚠️ 缺失 ⇒ 用户每次都要手打一遍。
|
|
160
|
+
|
|
161
|
+
**依据**:HTML 规范 + WCAG 1.3.5 Identify Input Purpose。
|
|
162
|
+
|
|
163
|
+
### 3 · `required` 与原生校验
|
|
164
|
+
|
|
165
|
+
```html
|
|
166
|
+
<input required aria-required="true">
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
⚠️ 必填字段**优先用原生 `required`**(浏览器自带校验气泡与键盘类型优化),
|
|
170
|
+
JS 校验只做**补充**(服务端仍必须再校验一次)。
|
|
@@ -0,0 +1,246 @@
|
|
|
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>input · 五态矩阵</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="input.css">
|
|
12
|
+
<!-- 下面第 5 格的 disabled 演示用到了 button 的类,必须显式引进来。
|
|
13
|
+
🔴 这个漏了 refs.py 当场抓出来:按钮会变成浏览器默认样式,页面不报错、只是静默失效。 -->
|
|
14
|
+
<link rel="stylesheet" href="../button/button.css">
|
|
15
|
+
<style>
|
|
16
|
+
/* 🔴 `minmax(min(320px, 100%), 1fr)` 的 `min()` 不能省:
|
|
17
|
+
视口窄于 320px 时,`minmax(320px, 1fr)` 的轨道**不会**缩到 320 以下
|
|
18
|
+
⇒ 320px 视口下每格 320px、加上页面内边距就横向溢出(实测 scrollWidth 341 > 320)。 */
|
|
19
|
+
.grid { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); }
|
|
20
|
+
.cell { background: var(--surface); border: 1px solid var(--border-decor);
|
|
21
|
+
border-radius: var(--r-md); padding: var(--sp-5); }
|
|
22
|
+
.cell h3 { margin: 0 0 var(--sp-1); font-size: var(--fs-sm); font-weight: 500; }
|
|
23
|
+
.cell p { margin: 0 0 var(--sp-3); font-size: var(--fs-xs);
|
|
24
|
+
color: var(--text-tertiary); line-height: 1.55; }
|
|
25
|
+
.cmp { display: grid; gap: var(--sp-4); grid-template-columns: 1.55fr 1fr; }
|
|
26
|
+
@media (max-width: 620px) {.cmp { grid-template-columns: 1fr; }
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
.cmp>div { padding: var(--sp-4); border-radius: var(--r-md); border: 1px solid var(--border-decor); }
|
|
30
|
+
.bad { background: var(--surface-sunken); }
|
|
31
|
+
.good { background: var(--surface-sunken); }
|
|
32
|
+
.cmp h3 { margin: 0 0 var(--sp-3); font-size: var(--fs-sm); font-weight: 500; }
|
|
33
|
+
.bad h3 { color: var(--danger); }
|
|
34
|
+
.good h3 { color: var(--success); }
|
|
35
|
+
.cmp p { font-size: var(--fs-xs); margin: var(--sp-3) 0 0; line-height: 1.6; }
|
|
36
|
+
/* 反例演示 */
|
|
37
|
+
.bad-input { width: 100%; box-sizing: border-box; height: 44px; padding: 0 var(--sp-3);
|
|
38
|
+
font-size: 14px; border: 1px solid #ccc; border-radius: var(--r-sm); }
|
|
39
|
+
.bad-input.err { border-color: red; }
|
|
40
|
+
.bad-err { color: red; font-size: 13px; }
|
|
41
|
+
</style>
|
|
42
|
+
</head>
|
|
43
|
+
<body class="page page">
|
|
44
|
+
<main class="wrap">
|
|
45
|
+
|
|
46
|
+
<h1>input · 五态矩阵</h1>
|
|
47
|
+
<p class="lede">
|
|
48
|
+
输入框取五态,不是七态——<strong>input 没有 active 态</strong>,输入框不响应按压。
|
|
49
|
+
</p>
|
|
50
|
+
|
|
51
|
+
<div class="note">
|
|
52
|
+
<strong>与 button 最关键的一处不同:</strong>
|
|
53
|
+
button 用 <code>:focus-visible</code>(鼠标点击不冒框),
|
|
54
|
+
input 用 <code>:focus</code>(用户必须随时知道光标在哪)。
|
|
55
|
+
搞反了表单会非常难用。自己 Tab 一遍下面的输入框就能体会。
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<h2 class="rule-bottom">五态</h2>
|
|
59
|
+
<div class="grid">
|
|
60
|
+
|
|
61
|
+
<div class="cell">
|
|
62
|
+
<h3>1. hover</h3>
|
|
63
|
+
<p>边框加深。鼠标移上来。</p>
|
|
64
|
+
<div class="field">
|
|
65
|
+
<label class="field__label" for="d1">默认</label>
|
|
66
|
+
<input class="field__input" id="d1" placeholder="移上来">
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div class="cell">
|
|
71
|
+
<h3>2. focus</h3>
|
|
72
|
+
<p>边框变 accent + 外圈 ring。点进去或用 Tab 切过来。</p>
|
|
73
|
+
<div class="field">
|
|
74
|
+
<label class="field__label" for="d2">聚焦</label>
|
|
75
|
+
<input class="field__input" id="d2" placeholder="点我">
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div class="cell">
|
|
80
|
+
<h3>3. disabled</h3>
|
|
81
|
+
<p>不可聚焦、<strong>值不提交</strong>。试着 Tab 过去——跳过的。</p>
|
|
82
|
+
<div class="field">
|
|
83
|
+
<label class="field__label" for="d3">禁用</label>
|
|
84
|
+
<input class="field__input" id="d3" value="不可编辑" disabled>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div class="cell">
|
|
89
|
+
<h3>4. readonly</h3>
|
|
90
|
+
<p>可聚焦、<strong>可以选中复制</strong>、值会提交。Tab 能到,试试复制这段字。</p>
|
|
91
|
+
<div class="field">
|
|
92
|
+
<label class="field__label" for="d4">只读</label>
|
|
93
|
+
<input class="field__input" id="d4" value="可以复制我" readonly>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div class="cell">
|
|
98
|
+
<h3>5. error</h3>
|
|
99
|
+
<p>红边框 + 叹号图标 + 错误文字,<strong>三条线索</strong>。不只靠颜色。</p>
|
|
100
|
+
<div class="field">
|
|
101
|
+
<label class="field__label" for="d5">金额</label>
|
|
102
|
+
<input class="field__input" id="d5" value="abc" aria-invalid="true"
|
|
103
|
+
aria-describedby="d5-err" inputmode="decimal" name="amount-d5" autocomplete="off"/>
|
|
104
|
+
<span class="field__error" id="d5-err" role="alert">请输入有效金额</span>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div class="cell">
|
|
109
|
+
<h3>带说明</h3>
|
|
110
|
+
<p><code>aria-describedby</code> 把说明挂到输入框,聚焦时屏幕阅读器会一起读出来。</p>
|
|
111
|
+
<div class="field">
|
|
112
|
+
<label class="field__label" for="d6">金额 <span class="field__req" aria-hidden="true">*</span></label>
|
|
113
|
+
<input class="field__input" id="d6" placeholder="0.00" aria-describedby="d6-hint" inputmode="decimal" name="amount-d6" autocomplete="off"/>
|
|
114
|
+
<span class="field__hint" id="d6-hint">最多两位小数</span>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<h2 class="rule-bottom">对比:常见写法 vs 本库写法</h2>
|
|
121
|
+
<div class="cmp">
|
|
122
|
+
<div class="box bad">
|
|
123
|
+
<h3>常见写法</h3>
|
|
124
|
+
<input class="bad-input err" value="abc" aria-hidden="true" tabindex="-1">
|
|
125
|
+
<!-- 🔴 加 aria-hidden。
|
|
126
|
+
这一段是**反例示范**("常见写法:纯红 + 只有颜色"),
|
|
127
|
+
旁边的输入框已经有 aria-hidden + tabindex="-1",
|
|
128
|
+
但这段红字漏了 ⇒ 读屏会**把一个错误的示范念出来**。
|
|
129
|
+
反例对人要可见(不能 display:none),
|
|
130
|
+
但对辅助技术应当隐藏 —— aria-hidden 正合适。 -->
|
|
131
|
+
<p class="bad-err" aria-hidden="true">格式错误</p>
|
|
132
|
+
<p>
|
|
133
|
+
<code>placeholder 当 label</code> —— 一输入就消失<br>
|
|
134
|
+
<code>只把边框变红</code> —— WCAG 1.4.1 禁止只靠颜色传达信息<br>
|
|
135
|
+
<code>font-size: 14px</code> —— iOS Safari 会自动放大整个页面<br>
|
|
136
|
+
错误文字没有 <code>aria-describedby</code> —— 屏幕阅读器读不到
|
|
137
|
+
</p>
|
|
138
|
+
</div>
|
|
139
|
+
<div class="good">
|
|
140
|
+
<h3>本库写法</h3>
|
|
141
|
+
<div class="field">
|
|
142
|
+
<label class="field__label" for="g1">金额</label>
|
|
143
|
+
<input class="field__input" id="g1" value="abc" aria-invalid="true" aria-describedby="g1-err" inputmode="decimal" name="amount-g1" autocomplete="off"/>
|
|
144
|
+
<span class="field__error" id="g1-err" role="alert">请输入有效金额</span>
|
|
145
|
+
</div>
|
|
146
|
+
<p>
|
|
147
|
+
有可见 label,不会消失<br>
|
|
148
|
+
红边框 + 图标 + 文字,三条线索<br>
|
|
149
|
+
<code>font-size: 16px</code> —— iOS 不缩放<br>
|
|
150
|
+
<code>role="alert"</code> + <code>aria-describedby</code> —— 会被读出来
|
|
151
|
+
</p>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<h2 class="rule-bottom">交互演示:校验</h2>
|
|
156
|
+
<p class="small" style="max-width: var(--measure-prose); margin:0 0 var(--sp-4)">
|
|
157
|
+
这里是<strong>分层校验</strong>:逐个输入试,每种错误会得到<strong>各自对应的</strong>提示。
|
|
158
|
+
<span style="color:var(--danger)">1111aaa</span> → 「只能输入数字」·
|
|
159
|
+
<span style="font-family:var(--mono)">12.345</span> → 「最多两位小数」·
|
|
160
|
+
<span style="font-family:var(--mono)">1.2.3</span> → 「只能有一个小数点」。
|
|
161
|
+
<br>🔴 原来的实现只有一条正则 + 一句固定提示,于是不管输入什么都说「最多两位小数」——
|
|
162
|
+
提示与实际错误对不上,用户根本不知道该改什么。
|
|
163
|
+
</p>
|
|
164
|
+
<div class="cell">
|
|
165
|
+
<div class="field">
|
|
166
|
+
<label class="field__label" for="amt">金额 <span class="field__req" aria-hidden="true">*</span></label>
|
|
167
|
+
<input class="field__input" id="amt" placeholder="0.00" aria-describedby="amt-hint" inputmode="decimal" name="amount-demo" autocomplete="off"/>
|
|
168
|
+
<span class="field__hint" id="amt-hint">最多两位小数,留空点校验试试</span>
|
|
169
|
+
</div>
|
|
170
|
+
<div style="margin-top:var(--sp-4); display:flex; gap:var(--sp-3); align-items:center">
|
|
171
|
+
<button class="btn btn--sm btn--primary" id="go" style="height:44px;padding:0 var(--sp-5);
|
|
172
|
+
background:var(--accent);color:var(--text-on-accent);border:none;border-radius:var(--r-sm);cursor:pointer;
|
|
173
|
+
font-size:var(--fs-sm)">校验</button>
|
|
174
|
+
<span id="out" style="font-size:var(--fs-xs); color:var(--text-tertiary)"></span>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
<h2 class="rule-bottom">iOS 那条 16px 不是审美</h2>
|
|
179
|
+
<div class="note">
|
|
180
|
+
iOS Safari 在输入框 <code>font-size < 16px</code> 时,用户一聚焦就<strong>自动放大整个页面</strong>,
|
|
181
|
+
而且不会自动缩回去。用户会以为页面坏了。<br><br>
|
|
182
|
+
这是移动端最常见的表单 bug,也是本库把 input 字号钉死在 16px 的唯一原因。
|
|
183
|
+
紧凑场景用 <code>--sm</code>(36px 高)但字号仍然是 16px。
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<script>
|
|
187
|
+
var inp = document.getElementById('amt');
|
|
188
|
+
var out = document.getElementById('out');
|
|
189
|
+
/* ------------------------------------------------------------------
|
|
190
|
+
金额校验:**分层判断,每层给对应的提示**
|
|
191
|
+
|
|
192
|
+
🔴 修正。原实现只有一条正则 + 一条固定提示:
|
|
193
|
+
|
|
194
|
+
var ok = /^\d+(\.\d{1,2})?$/.test(v);
|
|
195
|
+
e.textContent = v === '' ? '金额不能为空' : '最多两位小数,例如 12.34';
|
|
196
|
+
|
|
197
|
+
于是 `1111aaa`、`abc`、任何不匹配的值 **全都得到同一句
|
|
198
|
+
"最多两位小数"** —— 输入英文却被告诫小数位,
|
|
199
|
+
提示与实际错误对不上,用户根本不知道该改什么。
|
|
200
|
+
|
|
201
|
+
正确的顺序很重要:**先排除更根本的错误**。
|
|
202
|
+
含字母时先说"只能输入数字",而不是先说小数位 ——
|
|
203
|
+
因为用户要先解决"有字母"这个障碍,才谈得上小数位。
|
|
204
|
+
------------------------------------------------------------------ */
|
|
205
|
+
function validateAmount(v) {
|
|
206
|
+
if (v === '') { return '金额不能为空'; }
|
|
207
|
+
/* 第 1 层:有非数字字符(字母 / 中文 / 符号)—— 最先查这个 */
|
|
208
|
+
if (/[^\d.]/.test(v)) {
|
|
209
|
+
var bad = v.match(/[^\d.]/);
|
|
210
|
+
return '只能输入数字,' + JSON.stringify(bad[0]).replace(/"/g, '') + ' 不是有效字符';
|
|
211
|
+
}
|
|
212
|
+
/* 第 2 层:小数点超过一个 */
|
|
213
|
+
if ((v.match(/\./g) || []).length > 1) { return '只能有一个小数点'; }
|
|
214
|
+
/* 第 3 层:小数位超过两位 */
|
|
215
|
+
if (/^\d*\.\d{3,}$/.test(v)) { return '最多两位小数,例如 12.34'; }
|
|
216
|
+
/* 第 4 层:兜底("." 或 "12." 之类的半截输入) */
|
|
217
|
+
if (!/^\d+(\.\d{1,2})?$/.test(v)) { return '格式不对,示例:12.34'; }
|
|
218
|
+
return null; /* null = 通过 */
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
document.getElementById('go').addEventListener('click', function () {
|
|
222
|
+
var v = inp.value.trim();
|
|
223
|
+
var err = validateAmount(v);
|
|
224
|
+
var old = document.getElementById('amt-err');
|
|
225
|
+
if (old) old.remove();
|
|
226
|
+
if (!err) {
|
|
227
|
+
inp.setAttribute('aria-invalid', 'false');
|
|
228
|
+
inp.setAttribute('aria-describedby', 'amt-hint');
|
|
229
|
+
out.textContent = '通过';
|
|
230
|
+
out.style.color = 'var(--success)';
|
|
231
|
+
} else {
|
|
232
|
+
inp.setAttribute('aria-invalid', 'true');
|
|
233
|
+
var e = document.createElement('span');
|
|
234
|
+
e.className = 'field__error';
|
|
235
|
+
e.id = 'amt-err';
|
|
236
|
+
e.setAttribute('role', 'alert');
|
|
237
|
+
e.textContent = err; /* 🔴 用具体的那一条,不是固定文案 */
|
|
238
|
+
inp.parentNode.insertBefore(e, inp.nextSibling);
|
|
239
|
+
inp.setAttribute('aria-describedby', 'amt-hint amt-err');
|
|
240
|
+
out.textContent = '已标红,且错误文字可被屏幕阅读器读出';
|
|
241
|
+
out.style.color = 'var(--danger)';
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
</script></main>
|
|
245
|
+
</body>
|
|
246
|
+
</html>
|