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,100 @@
|
|
|
1
|
+
# date-range · 日期区间
|
|
2
|
+
|
|
3
|
+
财务/成本场景刚需:**"看上季度"、"对比去年同期"**。
|
|
4
|
+
|
|
5
|
+
## 🔴 三件容易做错的事(写在最前面)
|
|
6
|
+
|
|
7
|
+
| # | 做什么 | 不做会怎样 |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| ① | **必须有「清除」按钮** | 日期填错了,用户只能一个个退格删 |
|
|
10
|
+
| ② | **`min`/`max` 越界与"开始晚于结束"要明确报错** | 用户不知道自己填错了,还以为没生效 |
|
|
11
|
+
| ③ | 快捷按钮必须能 `Tab` 到 | 键盘用户完全用不了"看上季度" |
|
|
12
|
+
|
|
13
|
+
## 结构
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<div class="drange">
|
|
17
|
+
<label class="drange__label">
|
|
18
|
+
<span>开始</span>
|
|
19
|
+
<input class="drange__input" type="date" data-dr-from>
|
|
20
|
+
</label>
|
|
21
|
+
<span class="drange__sep" aria-hidden="true">→</span>
|
|
22
|
+
<label class="drange__label">
|
|
23
|
+
<span>结束</span>
|
|
24
|
+
<input class="drange__input" type="date" data-dr-to>
|
|
25
|
+
</label>
|
|
26
|
+
<button class="drange__clear" type="button" data-dr-clear>清除</button>
|
|
27
|
+
<div class="drange__presets">…上季度 / 上月 / 今年…</div>
|
|
28
|
+
<p data-dr-error role="alert"></p>
|
|
29
|
+
<p data-dr-summary></p>
|
|
30
|
+
</div>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
| 部件 | 说明 |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `.drange` | 根,`flex-wrap` —— 窄屏自动换行 |
|
|
36
|
+
| `[data-dr-from]` / `[data-dr-to]` | 两个日期输入,**缺任一个直接抛错** |
|
|
37
|
+
| `[data-dr-error]` | 错误容器,`role="alert"` |
|
|
38
|
+
| `[data-dr-summary]` | 选中区间的摘要(如「共 92 天」)|
|
|
39
|
+
| `[data-dr-clear]` | 清除,**不可省** |
|
|
40
|
+
| `.drange--disabled` | 整体禁用 |
|
|
41
|
+
|
|
42
|
+
⚠️ 标记名是 **`data-dr-*`**(`dr` 缩写),不是 `data-drange-*`。
|
|
43
|
+
|
|
44
|
+
## 启用
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
const dr = DateRange.create(元素, {
|
|
48
|
+
presets: [{ label: '上季度', from: …, to: … }],
|
|
49
|
+
min: '2020-01-01', max: '2026-12-31',
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
同时导出的一批**纯函数**(可单独用,很 handy):
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
DateRange.iso(date) // → 'YYYY-MM-DD'
|
|
57
|
+
DateRange.addDays(d, n)
|
|
58
|
+
DateRange.addMonths(d, n)
|
|
59
|
+
DateRange.quarterOf(d) // → 0..3(0 = Q1)
|
|
60
|
+
DateRange.quarterRange(y, q) // 季度必须是 0-3,传别的直接抛错
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
⚠️ `quarterOf` 曾经是**内部函数但忘了导出** —— 使用者拿到了 `quarterRange`
|
|
64
|
+
却无法从任意日期反推季度号。是单测抓出来的,补导出(纯函数,无副作用)。
|
|
65
|
+
|
|
66
|
+
## 校验做了什么
|
|
67
|
+
|
|
68
|
+
| 情况 | 行为 |
|
|
69
|
+
|---|---|
|
|
70
|
+
| 开始 > 结束 | 报「开始日期不能晚于结束日期(当前:…)」|
|
|
71
|
+
| 早于 `min` | 报「开始日期不能早于 …」|
|
|
72
|
+
| 晚于 `max` | 报「结束日期不能晚于 …」|
|
|
73
|
+
| `quarterRange` 季度号越界 | 抛错并说明「季度必须是 0-3(0=Q1),收到 …」|
|
|
74
|
+
|
|
75
|
+
### 🔴 错误要同时落到字段上,不只是显示一句话
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
from.setAttribute('aria-invalid', msg ? 'true' : 'false');
|
|
79
|
+
to.setAttribute('aria-invalid', msg ? 'true' : 'false');
|
|
80
|
+
if (msg) {
|
|
81
|
+
errBox.id = 'dr-err-N';
|
|
82
|
+
from.setAttribute('aria-describedby', id); // to 同理
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**为什么**:只显示一行红字,读屏用户**不知道是哪个字段错了**。
|
|
87
|
+
`aria-invalid` 标记问题字段,`aria-describedby` 把错误挂到字段上,
|
|
88
|
+
三件一起做才完整。
|
|
89
|
+
|
|
90
|
+
`opt.min` / `opt.max` 会**同时**写到两个输入的原生 `min`/`max` 属性上
|
|
91
|
+
(浏览器自带的日期选择器就会拦住越界值,不只是我们事后校验)。
|
|
92
|
+
|
|
93
|
+
## 成熟度:stable(无行为债)
|
|
94
|
+
|
|
95
|
+
契约里 `behaviorDebt` 为空 —— 清除、校验、preset 接线都有门禁覆盖
|
|
96
|
+
(`05-audit/date-range-check.js`)。
|
|
97
|
+
|
|
98
|
+
## 门禁
|
|
99
|
+
|
|
100
|
+
`05-audit/date-range-check.js`
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
date-range.css — 日期区间(E3 /
|
|
3
|
+
============================================================================
|
|
4
|
+
财务/成本场景刚需("看上季度" "对比去年同期")。
|
|
5
|
+
|
|
6
|
+
⭐ 三件容易做错的事:
|
|
7
|
+
① **必须有「清除」**:日期填错了要能一键清空
|
|
8
|
+
(没有清除按钮 = 用户只能一个个退格删日期)
|
|
9
|
+
② **min/max 约束**:开始晚于结束、或超出范围时要**明确报错**
|
|
10
|
+
③ **键盘可达**:三个快捷按钮(上季度/上月/今年)必须能 Tab 到
|
|
11
|
+
============================================================================ */
|
|
12
|
+
|
|
13
|
+
.drange {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-wrap: wrap;
|
|
16
|
+
align-items: flex-end;
|
|
17
|
+
gap: var(--sp-3);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.drange__field { flex: 1 1 10rem; min-width: 0; }
|
|
21
|
+
|
|
22
|
+
.drange__label {
|
|
23
|
+
display: block;
|
|
24
|
+
margin-bottom: var(--sp-1);
|
|
25
|
+
font-size: var(--fs-xs);
|
|
26
|
+
color: var(--text-secondary);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.drange__input {
|
|
30
|
+
display: block;
|
|
31
|
+
width: 100%;
|
|
32
|
+
/* 🔴 必须:input 的 UA 盒模型是 content-box,width:100% 只是内容宽,
|
|
33
|
+
加 padding+border 实际比容器宽 26px ⇒ 393px 视口横向溢出。 */
|
|
34
|
+
box-sizing: border-box;
|
|
35
|
+
min-height: 44px; /* 🔴 命中区达标 */
|
|
36
|
+
padding: 0 var(--sp-3);
|
|
37
|
+
background: var(--surface);
|
|
38
|
+
color: var(--text-primary);
|
|
39
|
+
border: 1px solid var(--border-control);
|
|
40
|
+
border-radius: var(--r-sm);
|
|
41
|
+
font: inherit;
|
|
42
|
+
font-size: var(--fs-sm);
|
|
43
|
+
font-family: var(--mono);
|
|
44
|
+
font-variant-numeric: tabular-nums;
|
|
45
|
+
color-scheme: light dark; /* ⭐ 让原生日期选择器跟随主题 */
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.drange__input:focus-visible {
|
|
49
|
+
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
|
50
|
+
outline-offset: var(--focus-ring-offset);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* ⭐ 错误态:边框 + 图标(**不能只改颜色**)*/
|
|
54
|
+
.drange__input[aria-invalid="true"] {
|
|
55
|
+
border-color: var(--danger);
|
|
56
|
+
border-width: 2px;
|
|
57
|
+
background: var(--surface-sunken);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.drange__input[aria-invalid="true"]::after { content: " ⚠"; }
|
|
61
|
+
|
|
62
|
+
.drange__sep {
|
|
63
|
+
flex: 0 0 auto;
|
|
64
|
+
padding-bottom: var(--sp-3);
|
|
65
|
+
color: var(--text-tertiary);
|
|
66
|
+
font-size: var(--fs-sm);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* ---------- 快捷按钮 ---------- */
|
|
70
|
+
.drange__presets {
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-wrap: wrap;
|
|
73
|
+
gap: var(--sp-2);
|
|
74
|
+
margin-top: var(--sp-3);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.drange__preset {
|
|
78
|
+
min-height: 32px; /* 密集数据表惯例(非 44)*/
|
|
79
|
+
padding: 0 var(--sp-3);
|
|
80
|
+
background: var(--surface);
|
|
81
|
+
color: var(--text-secondary);
|
|
82
|
+
border: 1px solid var(--border-control);
|
|
83
|
+
border-radius: var(--r-pill);
|
|
84
|
+
font: inherit;
|
|
85
|
+
font-size: var(--fs-xs);
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.drange__preset:focus-visible {
|
|
90
|
+
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
|
91
|
+
outline-offset: var(--focus-ring-offset);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@media (hover: hover) and (pointer: fine) {
|
|
95
|
+
.drange__preset:hover { background: var(--surface-sunken); }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.drange__preset[aria-pressed="true"] {
|
|
99
|
+
background: var(--accent-soft);
|
|
100
|
+
border-color: var(--accent);
|
|
101
|
+
color: var(--text-primary);
|
|
102
|
+
font-weight: 600;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* ---------- 错误提示 ---------- */
|
|
106
|
+
/* 🔴 为什么这里保留彩色竖条(0.8.4 · 全库竖条政策)
|
|
107
|
+
本库整体立场是去掉纯装饰竖条(理由见 typography.css / form-validation.css)。
|
|
108
|
+
但**错误提示**是例外:竖条在这里承担的是**状态语义** ——
|
|
109
|
+
"这个字段错了",与红色底色、`aria-invalid` 是同一份信息的三个出口
|
|
110
|
+
(视觉 / 颜色 / 无障碍)。
|
|
111
|
+
⚠️ 和 form-validation 的区别:那里原本也是靠竖条提示错误,
|
|
112
|
+
被判为「理由对、手段错」⇒ 改用**图标**;这里保留是因为
|
|
113
|
+
它同时已有 `role="alert"` + `aria-invalid`,竖条只是**第三重冗余**,
|
|
114
|
+
拿掉之后单色 / 色弱用户仍可靠图标与 aria 察觉,
|
|
115
|
+
⇒ 不构成"唯一信息出口"。 */
|
|
116
|
+
.drange__error {
|
|
117
|
+
flex: 1 1 100%;
|
|
118
|
+
display: flex;
|
|
119
|
+
align-items: flex-start;
|
|
120
|
+
gap: var(--sp-2);
|
|
121
|
+
padding: var(--sp-2) var(--sp-3);
|
|
122
|
+
background: var(--surface-sunken);
|
|
123
|
+
border-inline-start: 3px solid var(--danger);
|
|
124
|
+
border-radius: var(--r-sm);
|
|
125
|
+
color: var(--danger);
|
|
126
|
+
font-size: var(--fs-xs);
|
|
127
|
+
line-height: 1.5;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.drange__error[hidden] { display: none; }
|
|
131
|
+
|
|
132
|
+
/* ---------- 摘要 ---------- */
|
|
133
|
+
.drange__summary {
|
|
134
|
+
flex: 1 1 100%;
|
|
135
|
+
margin-top: var(--sp-2);
|
|
136
|
+
color: var(--text-secondary);
|
|
137
|
+
font-size: var(--fs-xs);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.drange__summary b {
|
|
141
|
+
font-family: var(--mono);
|
|
142
|
+
font-variant-numeric: tabular-nums;
|
|
143
|
+
color: var(--text-primary);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* ---------- 清除按钮:44px 命中区 ---------- */
|
|
147
|
+
.drange__clear {
|
|
148
|
+
flex: 0 0 auto;
|
|
149
|
+
min-height: 44px;
|
|
150
|
+
padding: 0 var(--sp-4);
|
|
151
|
+
background: var(--surface);
|
|
152
|
+
color: var(--text-secondary);
|
|
153
|
+
border: 1px solid var(--border-control);
|
|
154
|
+
border-radius: var(--r-sm);
|
|
155
|
+
font: inherit;
|
|
156
|
+
font-size: var(--fs-sm);
|
|
157
|
+
cursor: pointer;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.drange__clear:focus-visible {
|
|
161
|
+
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
|
162
|
+
outline-offset: var(--focus-ring-offset);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@media (hover: hover) and (pointer: fine) {
|
|
166
|
+
.drange__clear:hover { background: var(--surface-sunken); color: var(--text-primary); }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.drange__clear[disabled] {
|
|
170
|
+
background: var(--surface-sunken);
|
|
171
|
+
color: var(--text-tertiary);
|
|
172
|
+
cursor: not-allowed;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@media (prefers-reduced-motion: reduce) {
|
|
176
|
+
.drange * { transition: none; }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* ---------- 禁用态(states 门禁要求:HIGH 级形态不能缺)---------- */
|
|
180
|
+
.drange__input:disabled {
|
|
181
|
+
background: var(--surface-sunken);
|
|
182
|
+
border-color: var(--border-decor);
|
|
183
|
+
color: var(--text-tertiary);
|
|
184
|
+
cursor: not-allowed;
|
|
185
|
+
/* ⛔ 不写 opacity —— 那会把文字对比度一起拉低 */
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.drange__input:disabled:focus-visible {
|
|
189
|
+
outline: none; /* 禁用态不该有焦点环 */
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.drange--disabled .drange__preset,
|
|
193
|
+
.drange--disabled .drange__clear {
|
|
194
|
+
background: var(--surface-sunken);
|
|
195
|
+
color: var(--text-tertiary);
|
|
196
|
+
border-color: var(--border-decor);
|
|
197
|
+
cursor: not-allowed;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@media (hover: hover) and (pointer: fine) {
|
|
201
|
+
.drange--disabled .drange__preset:hover,
|
|
202
|
+
.drange--disabled .drange__clear:hover {
|
|
203
|
+
background: var(--surface-sunken);
|
|
204
|
+
color: var(--text-tertiary);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* date-range.js — 日期区间(E3 /
|
|
3
|
+
*
|
|
4
|
+
* 财务/成本场景刚需:看上季度、对比去年同期。
|
|
5
|
+
*
|
|
6
|
+
* ⭐ 三个关键点(最容易漏):
|
|
7
|
+
* ① **必须有「清除」**:日期填错了要能一键清空
|
|
8
|
+
* ⇒ 没有它 = 用户只能一个个退格删
|
|
9
|
+
* ② **约束校验**:开始晚于结束、超出 min/max ⇒ **明确报错**(不静默)
|
|
10
|
+
* ③ **无障碍**:每个 input 有 label;错误用 role=alert 播报
|
|
11
|
+
*
|
|
12
|
+
* ⭐ 为什么**基于原生 `<input type="date">`** 而不是自建日历:
|
|
13
|
+
* 原生自带:触屏日历面板、本地化、键盘输入、无障碍语义。
|
|
14
|
+
* 自建日历要重做这一切,且在移动端体验更差。
|
|
15
|
+
* (与 combobox 的取舍相反 —— 那里原生 select 无法定制样式。)
|
|
16
|
+
*
|
|
17
|
+
* 无依赖 · ES5
|
|
18
|
+
*/
|
|
19
|
+
(function (global) {
|
|
20
|
+
'use strict';
|
|
21
|
+
/* ==== BEHAVIOR INJECT BEGIN: emit ==== */
|
|
22
|
+
var flEmit = function (el, name, detail) {
|
|
23
|
+
if (!el || !el.dispatchEvent) return null;
|
|
24
|
+
var type = name.indexOf('fl-') === 0 ? name : 'fl-' + name;
|
|
25
|
+
var ev = null;
|
|
26
|
+
/* 老 WebView 没有 CustomEvent 构造函数 ⇒ 兜底走 createEvent(理由见文件头)*/
|
|
27
|
+
if (typeof window.CustomEvent === 'function') {
|
|
28
|
+
try {
|
|
29
|
+
ev = new window.CustomEvent(type, {
|
|
30
|
+
detail: detail || null, bubbles: true, cancelable: false
|
|
31
|
+
});
|
|
32
|
+
} catch (e) { ev = null; }
|
|
33
|
+
}
|
|
34
|
+
if (!ev) {
|
|
35
|
+
try {
|
|
36
|
+
ev = document.createEvent('CustomEvent');
|
|
37
|
+
ev.initCustomEvent(type, true, false, detail || null);
|
|
38
|
+
} catch (e2) { return null; }
|
|
39
|
+
}
|
|
40
|
+
el.dispatchEvent(ev);
|
|
41
|
+
return ev;
|
|
42
|
+
};
|
|
43
|
+
/* ==== BEHAVIOR INJECT END: emit ==== */
|
|
44
|
+
/* ---- 旧环境兼容:Element.closest 在老 WebView 上不存在 ----
|
|
45
|
+
直接调用会在真机上抛 `is not a function`(本地完全正常,
|
|
46
|
+
只在老环境炸 —— 属静默失效)。这里做模块内兜底,
|
|
47
|
+
不污染全局,也不需要使用者额外引入 polyfill。 */
|
|
48
|
+
function closest(el, sel) {
|
|
49
|
+
if (!el) return null;
|
|
50
|
+
if (el.closest) return el.closest(sel);
|
|
51
|
+
while (el && el.nodeType === 1) {
|
|
52
|
+
if (elMatches(el, sel)) return el;
|
|
53
|
+
el = el.parentElement;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
function elMatches(el, sel) {
|
|
58
|
+
var m = sel.match(/^([a-zA-Z][\w-]*)/);
|
|
59
|
+
if (m && el.tagName.toLowerCase() !== m[1].toLowerCase()) return false;
|
|
60
|
+
var cls = sel.match(/\.([\w-]+)/g);
|
|
61
|
+
if (cls) {
|
|
62
|
+
for (var i = 0; i < cls.length; i++) {
|
|
63
|
+
var c = cls[i].slice(1);
|
|
64
|
+
if ((' ' + (el.className || '') + ' ').indexOf(' ' + c + ' ') < 0) return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
var id = sel.match(/#([\w-]+)/);
|
|
68
|
+
if (id && el.id !== id[1]) return false;
|
|
69
|
+
var at = sel.match(/\[([\w-]+)(?:=["']?([^\]"']*)["']?)?\]/);
|
|
70
|
+
if (at) {
|
|
71
|
+
var v = el.getAttribute(at[1]);
|
|
72
|
+
if (v === null) return false;
|
|
73
|
+
if (at[2] !== undefined && v !== at[2]) return false;
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
var uid = 0;
|
|
79
|
+
|
|
80
|
+
function iso(d) {
|
|
81
|
+
if (!d) return '';
|
|
82
|
+
var m = String(d).match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
83
|
+
if (m) return m[1] + '-' + m[2] + '-' + m[3];
|
|
84
|
+
var dt = new Date(d);
|
|
85
|
+
if (isNaN(dt)) return '';
|
|
86
|
+
function p(n) { return (n < 10 ? '0' : '') + n; }
|
|
87
|
+
return dt.getFullYear() + '-' + p(dt.getMonth() + 1) + '-' + p(dt.getDate());
|
|
88
|
+
}
|
|
89
|
+
function addDays(isoStr, n) {
|
|
90
|
+
var d = new Date(isoStr + 'T00:00:00');
|
|
91
|
+
d.setDate(d.getDate() + n);
|
|
92
|
+
return iso(d);
|
|
93
|
+
}
|
|
94
|
+
function addMonths(isoStr, n) {
|
|
95
|
+
var d = new Date(isoStr + 'T00:00:00');
|
|
96
|
+
var day = d.getDate();
|
|
97
|
+
d.setDate(1);
|
|
98
|
+
d.setMonth(d.getMonth() + n);
|
|
99
|
+
/* ⚠️ 月末溢出防护:1/31 加一个月不该变成 3/3 */
|
|
100
|
+
var last = new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate();
|
|
101
|
+
d.setDate(Math.min(day, last));
|
|
102
|
+
return iso(d);
|
|
103
|
+
}
|
|
104
|
+
function quarterOf(date) {
|
|
105
|
+
var q = Math.floor(date.getMonth() / 3);
|
|
106
|
+
return { y: date.getFullYear(), q: q };
|
|
107
|
+
}
|
|
108
|
+
function quarterRange(y, q) {
|
|
109
|
+
/* 🔴 加防呆( H5 的单测逼出来的):
|
|
110
|
+
`q` 是 **0-based**(0=Q1),这是内部约定,但很容易被当成 1-based 用。
|
|
111
|
+
原来传 1 会**静默返回 Q2**(不报错)—— 正是本库最厌恶的那类失效。
|
|
112
|
+
⇒ 越界直接抛错,让误用在第一次就暴露。 */
|
|
113
|
+
if (q !== 0 && q !== 1 && q !== 2 && q !== 3) {
|
|
114
|
+
throw new Error('DateRange.quarterRange: 季度必须是 0-3(0=Q1),收到 ' + q);
|
|
115
|
+
}
|
|
116
|
+
var start = new Date(y, q * 3, 1);
|
|
117
|
+
var end = new Date(y, q * 3 + 3, 0); /* 该季度最后一天 */
|
|
118
|
+
return { from: iso(start), to: iso(end) };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function create(root, opt) {
|
|
122
|
+
opt = opt || {};
|
|
123
|
+
var from = root.querySelector('[data-dr-from]');
|
|
124
|
+
var to = root.querySelector('[data-dr-to]');
|
|
125
|
+
var errBox = root.querySelector('[data-dr-error]');
|
|
126
|
+
var summary = root.querySelector('[data-dr-summary]');
|
|
127
|
+
var clearBtn = root.querySelector('[data-dr-clear]');
|
|
128
|
+
if (!from || !to) throw new Error('DateRange: 缺少 [data-dr-from] / [data-dr-to]');
|
|
129
|
+
|
|
130
|
+
/* 约束:min/max 由调用方给(或默认放开)*/
|
|
131
|
+
if (opt.min) { from.min = opt.min; to.min = opt.min; }
|
|
132
|
+
if (opt.max) { from.max = opt.max; to.max = opt.max; }
|
|
133
|
+
|
|
134
|
+
function setError(msg) {
|
|
135
|
+
if (errBox) {
|
|
136
|
+
errBox.textContent = msg || '';
|
|
137
|
+
errBox.hidden = !msg;
|
|
138
|
+
}
|
|
139
|
+
/* ⭐ 错误要同时标记到具体字段 + 关联描述(读屏才知道哪个错了)*/
|
|
140
|
+
from.setAttribute('aria-invalid', msg ? 'true' : 'false');
|
|
141
|
+
to.setAttribute('aria-invalid', msg ? 'true' : 'false');
|
|
142
|
+
if (msg) {
|
|
143
|
+
var id = 'dr-err-' + (++uid);
|
|
144
|
+
errBox.id = id;
|
|
145
|
+
from.setAttribute('aria-describedby', id);
|
|
146
|
+
to.setAttribute('aria-describedby', id);
|
|
147
|
+
} else {
|
|
148
|
+
from.removeAttribute('aria-describedby');
|
|
149
|
+
to.removeAttribute('aria-describedby');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function days(fromS, toS) {
|
|
154
|
+
var a = new Date(fromS + 'T00:00:00'), b = new Date(toS + 'T00:00:00');
|
|
155
|
+
return Math.round((b - a) / 86400000) + 1;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function validate() {
|
|
159
|
+
var f = from.value, t = to.value;
|
|
160
|
+
if (!f && !t) { setError(''); paintSummary(''); return true; }
|
|
161
|
+
if (f && !t) { setError('请填写结束日期。'); return false; }
|
|
162
|
+
if (!f && t) { setError('请填写开始日期。'); return false; }
|
|
163
|
+
if (f > t) {
|
|
164
|
+
setError('开始日期不能晚于结束日期(当前:' + f + ' → ' + t + ')。');
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
if (opt.min && f < opt.min) { setError('开始日期不能早于 ' + opt.min + '。'); return false; }
|
|
168
|
+
if (opt.max && t > opt.max) { setError('结束日期不能晚于 ' + opt.max + '。'); return false; }
|
|
169
|
+
setError('');
|
|
170
|
+
paintSummary(f + ' → ' + t + '(' + days(f, t) + ' 天)');
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function paintSummary(s) {
|
|
175
|
+
if (summary) summary.innerHTML = s ? ('区间 <b>' + s + '</b>') : '';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function emit() {
|
|
179
|
+
var ok = validate();
|
|
180
|
+
flEmit(root, 'fl-change',
|
|
181
|
+
{ value: { from: from.value, to: to.value }, valid: ok });
|
|
182
|
+
if (opt.onChange) {
|
|
183
|
+
opt.onChange({ from: from.value, to: to.value, valid: ok }, ok);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* ---------- 快捷区间 ---------- */
|
|
188
|
+
function bindPresets() {
|
|
189
|
+
var now = new Date();
|
|
190
|
+
var q = quarterOf(now);
|
|
191
|
+
var presets = [
|
|
192
|
+
{ key: 'thisQ', label: '本季度', range: function () { return quarterRange(q.y, q.q); } },
|
|
193
|
+
{ key: 'lastQ', label: '上季度',
|
|
194
|
+
range: function () { return q.q === 0 ? quarterRange(q.y - 1, 3) : quarterRange(q.y, q.q - 1); } },
|
|
195
|
+
{ key: 'lastM', label: '上月',
|
|
196
|
+
range: function () {
|
|
197
|
+
var m = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
|
198
|
+
return { from: iso(m), to: iso(new Date(m.getFullYear(), m.getMonth() + 1, 0)) };
|
|
199
|
+
} },
|
|
200
|
+
{ key: 'thisY', label: '今年',
|
|
201
|
+
range: function () {
|
|
202
|
+
return { from: iso(new Date(now.getFullYear(), 0, 1)),
|
|
203
|
+
to: iso(new Date(now.getFullYear(), 11, 31)) };
|
|
204
|
+
} },
|
|
205
|
+
{ key: 'yoy', label: '去年同期',
|
|
206
|
+
range: function () {
|
|
207
|
+
var y = now.getFullYear() - 1;
|
|
208
|
+
return { from: iso(new Date(y, 0, 1)), to: iso(new Date(y, 11, 31)) };
|
|
209
|
+
} },
|
|
210
|
+
];
|
|
211
|
+
if (opt.presets) {
|
|
212
|
+
presets = presets.filter(function (x) { return opt.presets.indexOf(x.key) >= 0; });
|
|
213
|
+
}
|
|
214
|
+
var box = root.querySelector('[data-dr-presets]');
|
|
215
|
+
if (!box) return;
|
|
216
|
+
box.innerHTML = presets.map(function (p) {
|
|
217
|
+
return '<button class="drange__preset" type="button" aria-pressed="false" ' +
|
|
218
|
+
'data-dr-preset="' + p.key + '">' + p.label + '</button>';
|
|
219
|
+
}).join('');
|
|
220
|
+
box.addEventListener('click', function (e) {
|
|
221
|
+
/* 🔴 同样是"兜底永远跑不到"的死代码(与 combobox 同一处):
|
|
222
|
+
`e.target.closest ? closest(...) : null` ⇒ 没有原生 closest 时返回 null。
|
|
223
|
+
直接调 `closest()`,它内部自己判 `el.closest` 在不在。 */
|
|
224
|
+
var b = closest(e.target, '[data-dr-preset]');
|
|
225
|
+
if (!b) return;
|
|
226
|
+
var p = presets.filter(function (x) { return x.key === b.getAttribute('data-dr-preset'); })[0];
|
|
227
|
+
if (!p) return;
|
|
228
|
+
var r = p.range();
|
|
229
|
+
from.value = r.from;
|
|
230
|
+
to.value = r.to;
|
|
231
|
+
/* ⭐ 标记哪个快捷项是"当前"的(aria-pressed,非仅颜色)*/
|
|
232
|
+
box.querySelectorAll('[data-dr-preset]').forEach(function (x) {
|
|
233
|
+
x.setAttribute('aria-pressed', x === b ? 'true' : 'false');
|
|
234
|
+
});
|
|
235
|
+
emit();
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
bindPresets();
|
|
239
|
+
|
|
240
|
+
/* ---------- 清除(① 最容易漏的)---------- */
|
|
241
|
+
if (clearBtn) {
|
|
242
|
+
clearBtn.addEventListener('click', function () {
|
|
243
|
+
from.value = '';
|
|
244
|
+
to.value = '';
|
|
245
|
+
var box = root.querySelector('[data-dr-presets]');
|
|
246
|
+
if (box) {
|
|
247
|
+
box.querySelectorAll('[data-dr-preset]').forEach(function (x) {
|
|
248
|
+
x.setAttribute('aria-pressed', 'false');
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
setError('');
|
|
252
|
+
paintSummary('');
|
|
253
|
+
emit();
|
|
254
|
+
from.focus(); /* 清完把焦点放回开始日期 */
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* ---------- 手动改日期 ---------- */
|
|
259
|
+
[from, to].forEach(function (el) {
|
|
260
|
+
el.addEventListener('change', function () {
|
|
261
|
+
var box = root.querySelector('[data-dr-presets]');
|
|
262
|
+
if (box) {
|
|
263
|
+
box.querySelectorAll('[data-dr-preset]').forEach(function (x) {
|
|
264
|
+
x.setAttribute('aria-pressed', 'false');
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
emit();
|
|
268
|
+
});
|
|
269
|
+
el.addEventListener('input', emit);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
/* ---------- 便捷 API ---------- */
|
|
273
|
+
function setRange(a, b2) {
|
|
274
|
+
from.value = iso(a);
|
|
275
|
+
to.value = iso(b2);
|
|
276
|
+
emit();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
validate();
|
|
280
|
+
return {
|
|
281
|
+
get from() { return from.value; },
|
|
282
|
+
get to() { return to.value; },
|
|
283
|
+
get valid() { return !errBox || errBox.hidden; },
|
|
284
|
+
set: setRange,
|
|
285
|
+
setPreset: function (key) {
|
|
286
|
+
var b = root.querySelector('[data-dr-preset="' + key + '"]');
|
|
287
|
+
if (b) b.click();
|
|
288
|
+
},
|
|
289
|
+
clear: function () { if (clearBtn) clearBtn.click(); },
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
global.DateRange = {
|
|
294
|
+
create: create,
|
|
295
|
+
/* 🔴 补上 quarterOf( H5 的单测抓出来的):
|
|
296
|
+
它一直是**内部函数但没导出** ⇒ 使用者拿到 quarterRange 也无法
|
|
297
|
+
从任意日期反推季度号。纯函数,导出无害且有用。 */
|
|
298
|
+
iso: iso, addDays: addDays, addMonths: addMonths,
|
|
299
|
+
quarterOf: quarterOf, quarterRange: quarterRange,
|
|
300
|
+
};
|
|
301
|
+
})(window);
|