@fast-china/utils 1.0.38 → 2.0.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/CHANGELOG.md +48 -0
- package/CONTRIBUTING.md +87 -0
- package/README.md +109 -50
- package/README.zh.md +109 -50
- package/SECURITY.md +46 -0
- package/dist/array/index.d.mts +87 -0
- package/dist/array/index.mjs +161 -0
- package/dist/array/index.mjs.map +1 -0
- package/dist/async/index.d.mts +146 -0
- package/dist/async/index.mjs +336 -0
- package/dist/async/index.mjs.map +1 -0
- package/dist/base64/index.d.mts +105 -0
- package/dist/base64/index.mjs +427 -0
- package/dist/base64/index.mjs.map +1 -0
- package/dist/color/index.d.mts +90 -0
- package/dist/color/index.mjs +178 -0
- package/dist/color/index.mjs.map +1 -0
- package/dist/crypto/index.d.mts +326 -0
- package/dist/crypto/index.mjs +843 -0
- package/dist/crypto/index.mjs.map +1 -0
- package/dist/date/index.d.mts +191 -0
- package/dist/date/index.mjs +383 -0
- package/dist/date/index.mjs.map +1 -0
- package/dist/dom/style.d.mts +30 -0
- package/dist/dom/style.mjs +74 -0
- package/dist/dom/style.mjs.map +1 -0
- package/dist/env/index.d.mts +63 -0
- package/dist/env/index.mjs +97 -0
- package/dist/env/index.mjs.map +1 -0
- package/dist/identity/index.d.mts +78 -0
- package/dist/identity/index.mjs +87 -0
- package/dist/identity/index.mjs.map +1 -0
- package/dist/index.d.mts +24 -0
- package/dist/index.global.min.js +3 -2
- package/dist/index.global.min.js.map +1 -1
- package/dist/index.mjs +23 -0
- package/dist/internal/text.mjs +36 -0
- package/dist/internal/text.mjs.map +1 -0
- package/dist/logger/index.d.mts +87 -0
- package/dist/logger/index.mjs +124 -0
- package/dist/logger/index.mjs.map +1 -0
- package/dist/number/index.d.mts +89 -0
- package/dist/number/index.mjs +215 -0
- package/dist/number/index.mjs.map +1 -0
- package/dist/object/index.d.mts +76 -0
- package/dist/object/index.mjs +134 -0
- package/dist/object/index.mjs.map +1 -0
- package/dist/storage/index.d.mts +107 -0
- package/dist/storage/index.mjs +327 -0
- package/dist/storage/index.mjs.map +1 -0
- package/dist/string/index.d.mts +130 -0
- package/dist/string/index.mjs +275 -0
- package/dist/string/index.mjs.map +1 -0
- package/dist/vue/emits.d.mts +24 -0
- package/dist/vue/emits.mjs +47 -0
- package/dist/vue/emits.mjs.map +1 -0
- package/dist/vue/expose.d.mts +12 -0
- package/dist/vue/expose.mjs +16 -0
- package/dist/vue/expose.mjs.map +1 -0
- package/dist/vue/func.d.mts +14 -0
- package/dist/vue/func.mjs +16 -0
- package/dist/vue/func.mjs.map +1 -0
- package/dist/vue/index.d.mts +9 -0
- package/dist/vue/install.d.mts +51 -0
- package/dist/vue/install.mjs +123 -0
- package/dist/vue/install.mjs.map +1 -0
- package/dist/vue/props.d.mts +23 -0
- package/dist/vue/props.mjs +41 -0
- package/dist/vue/props.mjs.map +1 -0
- package/dist/vue/render.d.mts +12 -0
- package/dist/vue/render.mjs +17 -0
- package/dist/vue/render.mjs.map +1 -0
- package/dist/vue/slots.d.mts +19 -0
- package/dist/vue/slots.mjs +13 -0
- package/dist/vue/slots.mjs.map +1 -0
- package/dist/vue/with.d.mts +12 -0
- package/dist/vue/with.mjs +15 -0
- package/dist/vue/with.mjs.map +1 -0
- package/docs/API.md +112 -0
- package/docs/API.zh-CN.md +112 -0
- package/docs/DEVELOPMENT_RELEASE.zh-CN.md +65 -0
- package/docs/RUNTIME_CONTRACT.md +37 -0
- package/package.json +63 -73
- package/dist/index.global.js +0 -14500
- package/dist/index.global.js.map +0 -1
- package/es/array/index.d.ts +0 -19
- package/es/array/index.mjs +0 -2
- package/es/array/index.mjs.map +0 -1
- package/es/base64/index.d.ts +0 -21
- package/es/base64/index.mjs +0 -2
- package/es/base64/index.mjs.map +0 -1
- package/es/click/index.d.ts +0 -33
- package/es/click/index.mjs +0 -2
- package/es/click/index.mjs.map +0 -1
- package/es/color/index.d.ts +0 -33
- package/es/color/index.mjs +0 -2
- package/es/color/index.mjs.map +0 -1
- package/es/console/index.d.ts +0 -31
- package/es/console/index.mjs +0 -2
- package/es/console/index.mjs.map +0 -1
- package/es/crypto/index.d.ts +0 -46
- package/es/crypto/index.mjs +0 -2
- package/es/crypto/index.mjs.map +0 -1
- package/es/date/index.d.ts +0 -44
- package/es/date/index.mjs +0 -2
- package/es/date/index.mjs.map +0 -1
- package/es/dom/index.d.ts +0 -1
- package/es/dom/index.mjs +0 -2
- package/es/dom/index.mjs.map +0 -1
- package/es/dom/style.d.ts +0 -12
- package/es/dom/style.mjs +0 -2
- package/es/dom/style.mjs.map +0 -1
- package/es/env/index.d.ts +0 -25
- package/es/env/index.mjs +0 -2
- package/es/env/index.mjs.map +0 -1
- package/es/error/index.d.ts +0 -3
- package/es/error/index.mjs +0 -2
- package/es/error/index.mjs.map +0 -1
- package/es/identity/index.d.ts +0 -14
- package/es/identity/index.mjs +0 -2
- package/es/identity/index.mjs.map +0 -1
- package/es/index.d.ts +0 -15
- package/es/index.es.d.ts +0 -2
- package/es/index.mjs +0 -2
- package/es/index.mjs.map +0 -1
- package/es/object/index.d.ts +0 -13
- package/es/object/index.mjs +0 -2
- package/es/object/index.mjs.map +0 -1
- package/es/storage/index.d.ts +0 -96
- package/es/storage/index.mjs +0 -2
- package/es/storage/index.mjs.map +0 -1
- package/es/string/index.d.ts +0 -114
- package/es/string/index.mjs +0 -2
- package/es/string/index.mjs.map +0 -1
- package/es/vue/emits.d.ts +0 -8
- package/es/vue/emits.mjs +0 -2
- package/es/vue/emits.mjs.map +0 -1
- package/es/vue/expose.d.ts +0 -4
- package/es/vue/expose.mjs +0 -2
- package/es/vue/expose.mjs.map +0 -1
- package/es/vue/func.d.ts +0 -6
- package/es/vue/func.mjs +0 -2
- package/es/vue/func.mjs.map +0 -1
- package/es/vue/index.d.ts +0 -8
- package/es/vue/index.mjs +0 -2
- package/es/vue/index.mjs.map +0 -1
- package/es/vue/install.d.ts +0 -5
- package/es/vue/install.mjs +0 -2
- package/es/vue/install.mjs.map +0 -1
- package/es/vue/props.d.ts +0 -9
- package/es/vue/props.mjs +0 -2
- package/es/vue/props.mjs.map +0 -1
- package/es/vue/slots.d.ts +0 -11
- package/es/vue/slots.mjs +0 -2
- package/es/vue/slots.mjs.map +0 -1
- package/es/vue/useRender.d.ts +0 -6
- package/es/vue/useRender.mjs +0 -2
- package/es/vue/useRender.mjs.map +0 -1
- package/es/vue/with.d.ts +0 -5
- package/es/vue/with.mjs +0 -2
- package/es/vue/with.mjs.map +0 -1
- package/lib/array/index.d.ts +0 -19
- package/lib/array/index.js +0 -2
- package/lib/array/index.js.map +0 -1
- package/lib/base64/index.d.ts +0 -21
- package/lib/base64/index.js +0 -2
- package/lib/base64/index.js.map +0 -1
- package/lib/click/index.d.ts +0 -33
- package/lib/click/index.js +0 -2
- package/lib/click/index.js.map +0 -1
- package/lib/color/index.d.ts +0 -33
- package/lib/color/index.js +0 -2
- package/lib/color/index.js.map +0 -1
- package/lib/console/index.d.ts +0 -31
- package/lib/console/index.js +0 -2
- package/lib/console/index.js.map +0 -1
- package/lib/crypto/index.d.ts +0 -46
- package/lib/crypto/index.js +0 -2
- package/lib/crypto/index.js.map +0 -1
- package/lib/date/index.d.ts +0 -44
- package/lib/date/index.js +0 -2
- package/lib/date/index.js.map +0 -1
- package/lib/dom/index.d.ts +0 -1
- package/lib/dom/index.js +0 -2
- package/lib/dom/index.js.map +0 -1
- package/lib/dom/style.d.ts +0 -12
- package/lib/dom/style.js +0 -2
- package/lib/dom/style.js.map +0 -1
- package/lib/env/index.d.ts +0 -25
- package/lib/env/index.js +0 -2
- package/lib/env/index.js.map +0 -1
- package/lib/error/index.d.ts +0 -3
- package/lib/error/index.js +0 -2
- package/lib/error/index.js.map +0 -1
- package/lib/identity/index.d.ts +0 -14
- package/lib/identity/index.js +0 -2
- package/lib/identity/index.js.map +0 -1
- package/lib/index.d.ts +0 -15
- package/lib/index.es.d.ts +0 -2
- package/lib/index.js +0 -2
- package/lib/index.js.map +0 -1
- package/lib/object/index.d.ts +0 -13
- package/lib/object/index.js +0 -2
- package/lib/object/index.js.map +0 -1
- package/lib/storage/index.d.ts +0 -96
- package/lib/storage/index.js +0 -2
- package/lib/storage/index.js.map +0 -1
- package/lib/string/index.d.ts +0 -114
- package/lib/string/index.js +0 -2
- package/lib/string/index.js.map +0 -1
- package/lib/vue/emits.d.ts +0 -8
- package/lib/vue/emits.js +0 -2
- package/lib/vue/emits.js.map +0 -1
- package/lib/vue/expose.d.ts +0 -4
- package/lib/vue/expose.js +0 -2
- package/lib/vue/expose.js.map +0 -1
- package/lib/vue/func.d.ts +0 -6
- package/lib/vue/func.js +0 -2
- package/lib/vue/func.js.map +0 -1
- package/lib/vue/index.d.ts +0 -8
- package/lib/vue/index.js +0 -2
- package/lib/vue/index.js.map +0 -1
- package/lib/vue/install.d.ts +0 -5
- package/lib/vue/install.js +0 -2
- package/lib/vue/install.js.map +0 -1
- package/lib/vue/props.d.ts +0 -9
- package/lib/vue/props.js +0 -2
- package/lib/vue/props.js.map +0 -1
- package/lib/vue/slots.d.ts +0 -11
- package/lib/vue/slots.js +0 -2
- package/lib/vue/slots.js.map +0 -1
- package/lib/vue/useRender.d.ts +0 -6
- package/lib/vue/useRender.js +0 -2
- package/lib/vue/useRender.js.map +0 -1
- package/lib/vue/with.d.ts +0 -5
- package/lib/vue/with.js +0 -2
- package/lib/vue/with.js.map +0 -1
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
//#region src/date/index.ts
|
|
2
|
+
/**
|
|
3
|
+
* 校验日期算术移动量。
|
|
4
|
+
*
|
|
5
|
+
* @param amount - 待校验的日、月或年移动量。
|
|
6
|
+
* @throws `RangeError` 当值不是安全整数。
|
|
7
|
+
*/
|
|
8
|
+
const assertIntegerAmount = (amount) => {
|
|
9
|
+
if (!Number.isSafeInteger(amount)) throw new RangeError("amount must be a safe integer.");
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* 转换并克隆有效日期。
|
|
13
|
+
*
|
|
14
|
+
* @remarks 数字不进行秒/毫秒猜测;字符串遵循运行时 `Date` 解析规则,跨平台代码应传带显式时区的完整 ISO 8601。
|
|
15
|
+
* @param value - Date、Unix 毫秒时间戳或运行时可解析字符串。
|
|
16
|
+
* @returns 与输入不共享可变状态的新 Date。
|
|
17
|
+
* @throws 输入无效时抛出 `TypeError`。
|
|
18
|
+
*/
|
|
19
|
+
function toDate(value) {
|
|
20
|
+
const date = value instanceof Date ? new Date(value.getTime()) : new Date(value);
|
|
21
|
+
if (!Number.isFinite(date.getTime())) throw new TypeError("The value is not a valid date.");
|
|
22
|
+
return date;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 判断输入能否转换为有效日期。
|
|
26
|
+
*
|
|
27
|
+
* @param value - 任意待检查值。
|
|
28
|
+
* @returns 仅 Date、数字或字符串且时间戳有限时返回 `true`。
|
|
29
|
+
*/
|
|
30
|
+
function isValidDate(value) {
|
|
31
|
+
if (!(value instanceof Date || typeof value === "number" || typeof value === "string")) return false;
|
|
32
|
+
return Number.isFinite(new Date(value).getTime());
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 返回输入日期所在本地时区日期的 `00:00:00.000`,不修改输入。
|
|
36
|
+
*
|
|
37
|
+
* @param value - 有效日期输入。
|
|
38
|
+
* @returns 新建的本地日开始时间。
|
|
39
|
+
* @throws 输入无效时抛出 `TypeError`。
|
|
40
|
+
*/
|
|
41
|
+
function startOfDay(value) {
|
|
42
|
+
const date = toDate(value);
|
|
43
|
+
date.setHours(0, 0, 0, 0);
|
|
44
|
+
return toDate(date);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 返回输入日期所在本地时区日期的 `23:59:59.999`,不修改输入。
|
|
48
|
+
*
|
|
49
|
+
* @param value - 有效日期输入。
|
|
50
|
+
* @returns 新建的本地日结束时间。
|
|
51
|
+
* @throws 输入无效时抛出 `TypeError`。
|
|
52
|
+
*/
|
|
53
|
+
function endOfDay(value) {
|
|
54
|
+
const date = toDate(value);
|
|
55
|
+
date.setHours(23, 59, 59, 999);
|
|
56
|
+
return toDate(date);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 按本地日历增加整数天,不修改输入。
|
|
60
|
+
*
|
|
61
|
+
* @param value - 基准日期。
|
|
62
|
+
* @param amount - 可为负数的安全整数日数。
|
|
63
|
+
* @returns 本地日历运算后的新 Date;夏令时变化可能使实际毫秒差不等于 24 小时。
|
|
64
|
+
* @throws 日期无效时抛出 `TypeError`;数量或结果非法时抛出 `RangeError`。
|
|
65
|
+
*/
|
|
66
|
+
function addDays(value, amount) {
|
|
67
|
+
assertIntegerAmount(amount);
|
|
68
|
+
const date = toDate(value);
|
|
69
|
+
date.setDate(date.getDate() + amount);
|
|
70
|
+
return toDate(date);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 按本地日历增加整数月,并把不存在的日期夹到目标月末。
|
|
74
|
+
*
|
|
75
|
+
* @example 1 月 31 日增加一个月会落在 2 月最后一天。
|
|
76
|
+
* @param value - 基准日期。
|
|
77
|
+
* @param amount - 可为负数的安全整数月数。
|
|
78
|
+
* @returns 月份运算后的新 Date。
|
|
79
|
+
* @throws 日期无效时抛出 `TypeError`;数量或结果非法时抛出 `RangeError`。
|
|
80
|
+
*/
|
|
81
|
+
function addMonths(value, amount) {
|
|
82
|
+
assertIntegerAmount(amount);
|
|
83
|
+
const date = toDate(value);
|
|
84
|
+
const originalDay = date.getDate();
|
|
85
|
+
date.setDate(1);
|
|
86
|
+
date.setMonth(date.getMonth() + amount);
|
|
87
|
+
const targetMonthEnd = new Date(date.getTime());
|
|
88
|
+
targetMonthEnd.setMonth(targetMonthEnd.getMonth() + 1, 0);
|
|
89
|
+
const lastDay = targetMonthEnd.getDate();
|
|
90
|
+
date.setDate(Math.min(originalDay, lastDay));
|
|
91
|
+
return toDate(date);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 按本地日历增加整数年,并沿用 {@link addMonths} 的月末夹取规则。
|
|
95
|
+
*
|
|
96
|
+
* @param value - 基准日期。
|
|
97
|
+
* @param amount - 可为负数的安全整数年数。
|
|
98
|
+
* @returns 年份运算后的新 Date。
|
|
99
|
+
* @throws 日期无效时抛出 `TypeError`;数量或结果非法时抛出 `RangeError`。
|
|
100
|
+
*/
|
|
101
|
+
function addYears(value, amount) {
|
|
102
|
+
assertIntegerAmount(amount);
|
|
103
|
+
return addMonths(value, amount * 12);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 判断两个输入是否属于同一本地日历日。
|
|
107
|
+
*
|
|
108
|
+
* @param left - 第一日期。
|
|
109
|
+
* @param right - 第二日期。
|
|
110
|
+
* @returns 本地年、月、日均相同时返回 `true`。
|
|
111
|
+
* @throws 任一输入无效时抛出 `TypeError`。
|
|
112
|
+
*/
|
|
113
|
+
function isSameDay(left, right) {
|
|
114
|
+
const first = toDate(left);
|
|
115
|
+
const second = toDate(right);
|
|
116
|
+
return first.getFullYear() === second.getFullYear() && first.getMonth() === second.getMonth() && first.getDate() === second.getDate();
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 判断时间是否晚于基准时间。
|
|
120
|
+
*
|
|
121
|
+
* @param value - 待比较时间。
|
|
122
|
+
* @param now - 比较基准,默认调用时的当前时刻。
|
|
123
|
+
* @returns `value` 严格晚于基准时返回 `true`。
|
|
124
|
+
* @throws 任一输入无效时抛出 `TypeError`。
|
|
125
|
+
*/
|
|
126
|
+
function isFuture(value, now = Date.now()) {
|
|
127
|
+
return toDate(value).getTime() > toDate(now).getTime();
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 返回指定基准所在本地日历日的完整闭区间。
|
|
131
|
+
*
|
|
132
|
+
* @param value - 日期基准,默认调用时当前日期。
|
|
133
|
+
* @returns 新建的本地日开始和结束时间二元组。
|
|
134
|
+
* @throws 输入无效时抛出 `TypeError`。
|
|
135
|
+
*/
|
|
136
|
+
function getLocalDayBounds(value = Date.now()) {
|
|
137
|
+
return [startOfDay(value), endOfDay(value)];
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 判断日期是否位于包含首尾的时间区间。
|
|
141
|
+
*
|
|
142
|
+
* @param value - 待检查日期。
|
|
143
|
+
* @param start - 包含的起点。
|
|
144
|
+
* @param end - 包含的终点。
|
|
145
|
+
* @returns 时间戳位于闭区间内时返回 `true`。
|
|
146
|
+
* @throws 无效日期抛出 `TypeError`;首尾反向时抛出 `RangeError`。
|
|
147
|
+
*/
|
|
148
|
+
function isWithinInterval(value, start, end) {
|
|
149
|
+
const timestamp = toDate(value).getTime();
|
|
150
|
+
const startTimestamp = toDate(start).getTime();
|
|
151
|
+
const endTimestamp = toDate(end).getTime();
|
|
152
|
+
if (startTimestamp > endTimestamp) throw new RangeError("start cannot be later than end.");
|
|
153
|
+
return timestamp >= startTimestamp && timestamp <= endTimestamp;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* 使用 `Intl.RelativeTimeFormat` 生成人类可读相对时间。
|
|
157
|
+
*
|
|
158
|
+
* @remarks 秒、分钟、小时、天、周、月和年按固定时长阈值选择;这适合展示,不适合计费或日历运算。
|
|
159
|
+
* @param value - 目标时间。
|
|
160
|
+
* @param options - 语言、样式与比较基准。
|
|
161
|
+
* @returns 由 `Intl.RelativeTimeFormat` 生成的本地化文本。
|
|
162
|
+
* @throws 日期无效时抛出 `TypeError`;Locale 或 Intl 选项非法时抛出 `RangeError`。
|
|
163
|
+
*/
|
|
164
|
+
function formatRelativeTime(value, options = {}) {
|
|
165
|
+
const differenceSeconds = (toDate(value).getTime() - toDate(options.now ?? Date.now()).getTime()) / 1e3;
|
|
166
|
+
const absolute = Math.abs(differenceSeconds);
|
|
167
|
+
let divisor;
|
|
168
|
+
let unit;
|
|
169
|
+
if (absolute < 60) {
|
|
170
|
+
divisor = 1;
|
|
171
|
+
unit = "second";
|
|
172
|
+
} else if (absolute < 3600) {
|
|
173
|
+
divisor = 60;
|
|
174
|
+
unit = "minute";
|
|
175
|
+
} else if (absolute < 86400) {
|
|
176
|
+
divisor = 3600;
|
|
177
|
+
unit = "hour";
|
|
178
|
+
} else if (absolute < 604800) {
|
|
179
|
+
divisor = 86400;
|
|
180
|
+
unit = "day";
|
|
181
|
+
} else if (absolute < 2629800) {
|
|
182
|
+
divisor = 604800;
|
|
183
|
+
unit = "week";
|
|
184
|
+
} else if (absolute < 31557600) {
|
|
185
|
+
divisor = 2629800;
|
|
186
|
+
unit = "month";
|
|
187
|
+
} else {
|
|
188
|
+
divisor = 31557600;
|
|
189
|
+
unit = "year";
|
|
190
|
+
}
|
|
191
|
+
return new Intl.RelativeTimeFormat(options.locale ?? "zh-CN", {
|
|
192
|
+
numeric: options.numeric ?? "auto",
|
|
193
|
+
style: options.style ?? "long"
|
|
194
|
+
}).format(Math.round(differenceSeconds / divisor), unit);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* 移动本地日历字段。
|
|
198
|
+
*
|
|
199
|
+
* @remarks 直接使用 Date Setter,以保留历史快捷项在月底和闰年的溢出语义。
|
|
200
|
+
* @param date - 会被原地修改的日期。
|
|
201
|
+
* @param amount - 对目标字段增加的整数。
|
|
202
|
+
* @param unit - 要移动的日历字段。
|
|
203
|
+
*/
|
|
204
|
+
const shiftCalendarFieldInPlace = (date, amount, unit) => {
|
|
205
|
+
switch (unit) {
|
|
206
|
+
case "day":
|
|
207
|
+
date.setDate(date.getDate() + amount);
|
|
208
|
+
break;
|
|
209
|
+
case "month":
|
|
210
|
+
date.setMonth(date.getMonth() + amount);
|
|
211
|
+
break;
|
|
212
|
+
case "year": date.setFullYear(date.getFullYear() + amount);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
/**
|
|
216
|
+
* 创建动态单日期快捷项。
|
|
217
|
+
*
|
|
218
|
+
* @param text - 日期选择器显示文本。
|
|
219
|
+
* @param amount - 相对当前时间的移动量。
|
|
220
|
+
* @param unit - 移动使用的日历单位。
|
|
221
|
+
* @returns 每次执行 `value` 都重新读取当前时间的快捷项。
|
|
222
|
+
*/
|
|
223
|
+
const createDateShortcut = (text, amount, unit) => ({
|
|
224
|
+
text,
|
|
225
|
+
value: () => {
|
|
226
|
+
const date = /* @__PURE__ */ new Date();
|
|
227
|
+
shiftCalendarFieldInPlace(date, amount, unit);
|
|
228
|
+
date.setHours(0, 0, 0, 0);
|
|
229
|
+
return date;
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
/**
|
|
233
|
+
* 创建动态日期范围快捷项。
|
|
234
|
+
*
|
|
235
|
+
* @param text - 日期选择器显示文本。
|
|
236
|
+
* @param amount - 范围边界相对当前时间的移动量。
|
|
237
|
+
* @param unit - 移动使用的日历单位。
|
|
238
|
+
* @param towardFuture - `true` 移动结束边界,`false` 移动开始边界。
|
|
239
|
+
* @returns 每次求值都覆盖完整本地日边界的范围快捷项。
|
|
240
|
+
*/
|
|
241
|
+
const createRangeShortcut = (text, amount, unit, towardFuture) => ({
|
|
242
|
+
text,
|
|
243
|
+
value: () => {
|
|
244
|
+
const start = /* @__PURE__ */ new Date();
|
|
245
|
+
const end = /* @__PURE__ */ new Date();
|
|
246
|
+
shiftCalendarFieldInPlace(towardFuture ? end : start, towardFuture ? amount : -amount, unit);
|
|
247
|
+
start.setHours(0, 0, 0, 0);
|
|
248
|
+
end.setHours(23, 59, 59, 999);
|
|
249
|
+
return [start, end];
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
/**
|
|
253
|
+
* 把日期转换为固定中文相对时间文本。
|
|
254
|
+
*
|
|
255
|
+
* @remarks 10 位以内数字按 Unix 秒处理,其余数字按毫秒处理;月份与年份按本地日历月差计算。
|
|
256
|
+
* @param value - Date、时间戳、可解析字符串或空值。
|
|
257
|
+
* @returns 例如“3分钟前”“半年后”;非法或空输入返回空字符串。
|
|
258
|
+
*/
|
|
259
|
+
function formatChineseRelativeTime(value) {
|
|
260
|
+
if (value === null || value === void 0) return "";
|
|
261
|
+
let timestamp;
|
|
262
|
+
if (typeof value === "string") timestamp = new Date(value).getTime();
|
|
263
|
+
else if (typeof value === "number") timestamp = value.toString().length <= 10 ? value * 1e3 : value;
|
|
264
|
+
else timestamp = value.getTime();
|
|
265
|
+
if (!Number.isFinite(timestamp)) return "";
|
|
266
|
+
const minute = 6e4;
|
|
267
|
+
const hour = minute * 60;
|
|
268
|
+
const day = hour * 24;
|
|
269
|
+
const currentTimestamp = Date.now();
|
|
270
|
+
const difference = currentTimestamp - timestamp;
|
|
271
|
+
const minuteDifference = Math.abs(difference) / minute;
|
|
272
|
+
const hourDifference = Math.abs(difference) / hour;
|
|
273
|
+
const dayDifference = Math.abs(difference) / day;
|
|
274
|
+
const currentDate = new Date(currentTimestamp);
|
|
275
|
+
const targetDate = new Date(timestamp);
|
|
276
|
+
const monthDifference = (currentDate.getFullYear() - targetDate.getFullYear()) * 12 + currentDate.getMonth() - targetDate.getMonth();
|
|
277
|
+
const suffix = difference < 0 ? "后" : "前";
|
|
278
|
+
if (Math.abs(monthDifference) >= 12) return `${Math.floor(Math.abs(monthDifference) / 12)}年${suffix}`;
|
|
279
|
+
if (Math.abs(monthDifference) >= 6) return `半年${suffix}`;
|
|
280
|
+
if (Math.abs(monthDifference) >= 1) return `${Math.abs(monthDifference)}月${suffix}`;
|
|
281
|
+
if (dayDifference >= 15) return `半月${suffix}`;
|
|
282
|
+
if (dayDifference >= 7) return `${Math.floor(dayDifference / 7)}周${suffix}`;
|
|
283
|
+
if (dayDifference >= 1) return `${Math.floor(dayDifference)}天${suffix}`;
|
|
284
|
+
if (hourDifference >= 1) return `${Math.floor(hourDifference)}小时${suffix}`;
|
|
285
|
+
if (minuteDifference >= 1) return `${Math.floor(minuteDifference)}分钟${suffix}`;
|
|
286
|
+
return "刚刚";
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* 创建从今天到前后一个月日期的完整本地日范围。
|
|
290
|
+
*
|
|
291
|
+
* @param towardFuture - `true` 返回今天至一个月后,默认返回一个月前至今天。
|
|
292
|
+
* @returns 每次调用新建的本地日首尾边界。
|
|
293
|
+
*/
|
|
294
|
+
function createOneMonthRangeFromToday(towardFuture = false) {
|
|
295
|
+
const start = /* @__PURE__ */ new Date();
|
|
296
|
+
const end = /* @__PURE__ */ new Date();
|
|
297
|
+
shiftCalendarFieldInPlace(towardFuture ? end : start, towardFuture ? 1 : -1, "month");
|
|
298
|
+
start.setHours(0, 0, 0, 0);
|
|
299
|
+
end.setHours(23, 59, 59, 999);
|
|
300
|
+
return [start, end];
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* 判断日期是否晚于调用时的当前时刻。
|
|
304
|
+
*
|
|
305
|
+
* @param time - 待比较日期。
|
|
306
|
+
* @returns 时间戳严格晚于 `Date.now()` 时返回 `true`。
|
|
307
|
+
*/
|
|
308
|
+
function isDateAfterNow(time) {
|
|
309
|
+
return time.getTime() > Date.now();
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* 根据浏览器本地小时返回固定中文问候语。
|
|
313
|
+
*
|
|
314
|
+
* @returns 与当前时段对应的中文欢迎文本。
|
|
315
|
+
*/
|
|
316
|
+
function getLocalTimeGreeting() {
|
|
317
|
+
const hour = (/* @__PURE__ */ new Date()).getHours();
|
|
318
|
+
if (hour < 5) return "夜深了,注意身体哦!";
|
|
319
|
+
if (hour < 9) return "早上好!欢迎回来!";
|
|
320
|
+
if (hour < 12) return "上午好!欢迎回来!";
|
|
321
|
+
if (hour < 14) return "中午好!欢迎回来!";
|
|
322
|
+
if (hour < 18) return "下午好!欢迎回来!";
|
|
323
|
+
if (hour < 24) return "晚上好!欢迎回来!";
|
|
324
|
+
return "您好!欢迎回来!";
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* 创建面向过去或未来的常用完整日期范围快捷项。
|
|
328
|
+
*
|
|
329
|
+
* @param towardFuture - `true` 创建未来范围,默认创建历史范围。
|
|
330
|
+
* @returns 每次求值都会重新读取当前时间的范围快捷项。
|
|
331
|
+
*/
|
|
332
|
+
function createDateRangeShortcuts(towardFuture = false) {
|
|
333
|
+
return towardFuture ? [
|
|
334
|
+
createRangeShortcut("后1天", 1, "day", true),
|
|
335
|
+
createRangeShortcut("后3天", 3, "day", true),
|
|
336
|
+
createRangeShortcut("后1周", 7, "day", true),
|
|
337
|
+
createRangeShortcut("后1月", 1, "month", true),
|
|
338
|
+
createRangeShortcut("后3月", 3, "month", true),
|
|
339
|
+
createRangeShortcut("后6月", 6, "month", true),
|
|
340
|
+
createRangeShortcut("后1年", 1, "year", true)
|
|
341
|
+
] : [
|
|
342
|
+
createRangeShortcut("近1天", 1, "day", false),
|
|
343
|
+
createRangeShortcut("近3天", 3, "day", false),
|
|
344
|
+
createRangeShortcut("近1周", 7, "day", false),
|
|
345
|
+
createRangeShortcut("近1月", 1, "month", false),
|
|
346
|
+
createRangeShortcut("近3月", 3, "month", false),
|
|
347
|
+
createRangeShortcut("近6月", 6, "month", false),
|
|
348
|
+
createRangeShortcut("近1年", 1, "year", false)
|
|
349
|
+
];
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* 创建面向过去或未来的常用单日期快捷项。
|
|
353
|
+
*
|
|
354
|
+
* @param towardFuture - `true` 创建未来日期,默认创建历史日期。
|
|
355
|
+
* @returns 每次求值都会重新读取当前时间的单日期快捷项。
|
|
356
|
+
*/
|
|
357
|
+
function createDateShortcuts(towardFuture = false) {
|
|
358
|
+
return towardFuture ? [
|
|
359
|
+
createDateShortcut("今天", 0, "day"),
|
|
360
|
+
createDateShortcut("明天", 1, "day"),
|
|
361
|
+
createDateShortcut("一周后", 7, "day"),
|
|
362
|
+
createDateShortcut("一月后", 1, "month"),
|
|
363
|
+
createDateShortcut("一年后", 1, "year")
|
|
364
|
+
] : [
|
|
365
|
+
createDateShortcut("今天", 0, "day"),
|
|
366
|
+
createDateShortcut("昨天", -1, "day"),
|
|
367
|
+
createDateShortcut("一周前", -7, "day"),
|
|
368
|
+
createDateShortcut("一月前", -1, "month"),
|
|
369
|
+
createDateShortcut("一年前", -1, "year")
|
|
370
|
+
];
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* 返回今天的本地零点。
|
|
374
|
+
*
|
|
375
|
+
* @returns 新建的 `00:00:00.000` Date。
|
|
376
|
+
*/
|
|
377
|
+
function getStartOfToday() {
|
|
378
|
+
return startOfDay(/* @__PURE__ */ new Date());
|
|
379
|
+
}
|
|
380
|
+
//#endregion
|
|
381
|
+
export { addDays, addMonths, addYears, createDateRangeShortcuts, createDateShortcuts, createOneMonthRangeFromToday, endOfDay, formatChineseRelativeTime, formatRelativeTime, getLocalDayBounds, getLocalTimeGreeting, getStartOfToday, isDateAfterNow, isFuture, isSameDay, isValidDate, isWithinInterval, startOfDay, toDate };
|
|
382
|
+
|
|
383
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/date/index.ts"],"sourcesContent":["/** 可转换为日期的输入;数字始终按 Unix 毫秒时间戳处理。 */\nexport type DateInput = Date | number | string;\n\n/** {@link formatRelativeTime} 的语言与基准时间选项。 */\nexport interface RelativeTimeOptions {\n\t/** `Intl.RelativeTimeFormat` 使用的语言;默认固定为 `zh-CN`。 */\n\tlocale?: string | readonly string[];\n\t/** 比较基准,默认当前时间。 */\n\tnow?: DateInput;\n\t/** 是否允许“昨天”“明天”等文本;默认 `auto`。 */\n\tnumeric?: Intl.RelativeTimeFormatNumeric;\n\t/** 输出长度;默认 `long`。 */\n\tstyle?: Intl.RelativeTimeFormatStyle;\n}\n\n/**\n * 校验日期算术移动量。\n *\n * @param amount - 待校验的日、月或年移动量。\n * @throws `RangeError` 当值不是安全整数。\n */\nconst assertIntegerAmount = (amount: number): void => {\n\tif (!Number.isSafeInteger(amount)) throw new RangeError(\"amount must be a safe integer.\");\n};\n\n/**\n * 转换并克隆有效日期。\n *\n * @remarks 数字不进行秒/毫秒猜测;字符串遵循运行时 `Date` 解析规则,跨平台代码应传带显式时区的完整 ISO 8601。\n * @param value - Date、Unix 毫秒时间戳或运行时可解析字符串。\n * @returns 与输入不共享可变状态的新 Date。\n * @throws 输入无效时抛出 `TypeError`。\n */\nexport function toDate(value: DateInput): Date {\n\tconst date = value instanceof Date ? new Date(value.getTime()) : new Date(value);\n\tif (!Number.isFinite(date.getTime())) {\n\t\tthrow new TypeError(\"The value is not a valid date.\");\n\t}\n\treturn date;\n}\n\n/**\n * 判断输入能否转换为有效日期。\n *\n * @param value - 任意待检查值。\n * @returns 仅 Date、数字或字符串且时间戳有限时返回 `true`。\n */\nexport function isValidDate(value: unknown): value is DateInput {\n\tif (!(value instanceof Date || typeof value === \"number\" || typeof value === \"string\")) return false;\n\treturn Number.isFinite(new Date(value).getTime());\n}\n\n/**\n * 返回输入日期所在本地时区日期的 `00:00:00.000`,不修改输入。\n *\n * @param value - 有效日期输入。\n * @returns 新建的本地日开始时间。\n * @throws 输入无效时抛出 `TypeError`。\n */\nexport function startOfDay(value: DateInput): Date {\n\tconst date = toDate(value);\n\tdate.setHours(0, 0, 0, 0);\n\treturn toDate(date);\n}\n\n/**\n * 返回输入日期所在本地时区日期的 `23:59:59.999`,不修改输入。\n *\n * @param value - 有效日期输入。\n * @returns 新建的本地日结束时间。\n * @throws 输入无效时抛出 `TypeError`。\n */\nexport function endOfDay(value: DateInput): Date {\n\tconst date = toDate(value);\n\tdate.setHours(23, 59, 59, 999);\n\treturn toDate(date);\n}\n\n/**\n * 按本地日历增加整数天,不修改输入。\n *\n * @param value - 基准日期。\n * @param amount - 可为负数的安全整数日数。\n * @returns 本地日历运算后的新 Date;夏令时变化可能使实际毫秒差不等于 24 小时。\n * @throws 日期无效时抛出 `TypeError`;数量或结果非法时抛出 `RangeError`。\n */\nexport function addDays(value: DateInput, amount: number): Date {\n\tassertIntegerAmount(amount);\n\tconst date = toDate(value);\n\tdate.setDate(date.getDate() + amount);\n\treturn toDate(date);\n}\n\n/**\n * 按本地日历增加整数月,并把不存在的日期夹到目标月末。\n *\n * @example 1 月 31 日增加一个月会落在 2 月最后一天。\n * @param value - 基准日期。\n * @param amount - 可为负数的安全整数月数。\n * @returns 月份运算后的新 Date。\n * @throws 日期无效时抛出 `TypeError`;数量或结果非法时抛出 `RangeError`。\n */\nexport function addMonths(value: DateInput, amount: number): Date {\n\tassertIntegerAmount(amount);\n\tconst date = toDate(value);\n\tconst originalDay = date.getDate();\n\tdate.setDate(1);\n\tdate.setMonth(date.getMonth() + amount);\n\tconst targetMonthEnd = new Date(date.getTime());\n\t// 避免 `new Date(year, ...)` 把 0 至 99 年解释为 1900 至 1999 年。\n\ttargetMonthEnd.setMonth(targetMonthEnd.getMonth() + 1, 0);\n\tconst lastDay = targetMonthEnd.getDate();\n\tdate.setDate(Math.min(originalDay, lastDay));\n\treturn toDate(date);\n}\n\n/**\n * 按本地日历增加整数年,并沿用 {@link addMonths} 的月末夹取规则。\n *\n * @param value - 基准日期。\n * @param amount - 可为负数的安全整数年数。\n * @returns 年份运算后的新 Date。\n * @throws 日期无效时抛出 `TypeError`;数量或结果非法时抛出 `RangeError`。\n */\nexport function addYears(value: DateInput, amount: number): Date {\n\tassertIntegerAmount(amount);\n\treturn addMonths(value, amount * 12);\n}\n\n/**\n * 判断两个输入是否属于同一本地日历日。\n *\n * @param left - 第一日期。\n * @param right - 第二日期。\n * @returns 本地年、月、日均相同时返回 `true`。\n * @throws 任一输入无效时抛出 `TypeError`。\n */\nexport function isSameDay(left: DateInput, right: DateInput): boolean {\n\tconst first = toDate(left);\n\tconst second = toDate(right);\n\treturn first.getFullYear() === second.getFullYear() && first.getMonth() === second.getMonth() && first.getDate() === second.getDate();\n}\n\n/**\n * 判断时间是否晚于基准时间。\n *\n * @param value - 待比较时间。\n * @param now - 比较基准,默认调用时的当前时刻。\n * @returns `value` 严格晚于基准时返回 `true`。\n * @throws 任一输入无效时抛出 `TypeError`。\n */\nexport function isFuture(value: DateInput, now: DateInput = Date.now()): boolean {\n\treturn toDate(value).getTime() > toDate(now).getTime();\n}\n\n/**\n * 返回指定基准所在本地日历日的完整闭区间。\n *\n * @param value - 日期基准,默认调用时当前日期。\n * @returns 新建的本地日开始和结束时间二元组。\n * @throws 输入无效时抛出 `TypeError`。\n */\nexport function getLocalDayBounds(value: DateInput = Date.now()): [start: Date, end: Date] {\n\treturn [startOfDay(value), endOfDay(value)];\n}\n\n/**\n * 判断日期是否位于包含首尾的时间区间。\n *\n * @param value - 待检查日期。\n * @param start - 包含的起点。\n * @param end - 包含的终点。\n * @returns 时间戳位于闭区间内时返回 `true`。\n * @throws 无效日期抛出 `TypeError`;首尾反向时抛出 `RangeError`。\n */\nexport function isWithinInterval(value: DateInput, start: DateInput, end: DateInput): boolean {\n\tconst timestamp = toDate(value).getTime();\n\tconst startTimestamp = toDate(start).getTime();\n\tconst endTimestamp = toDate(end).getTime();\n\tif (startTimestamp > endTimestamp) throw new RangeError(\"start cannot be later than end.\");\n\treturn timestamp >= startTimestamp && timestamp <= endTimestamp;\n}\n\n/**\n * 使用 `Intl.RelativeTimeFormat` 生成人类可读相对时间。\n *\n * @remarks 秒、分钟、小时、天、周、月和年按固定时长阈值选择;这适合展示,不适合计费或日历运算。\n * @param value - 目标时间。\n * @param options - 语言、样式与比较基准。\n * @returns 由 `Intl.RelativeTimeFormat` 生成的本地化文本。\n * @throws 日期无效时抛出 `TypeError`;Locale 或 Intl 选项非法时抛出 `RangeError`。\n */\nexport function formatRelativeTime(value: DateInput, options: RelativeTimeOptions = {}): string {\n\tconst differenceSeconds = (toDate(value).getTime() - toDate(options.now ?? Date.now()).getTime()) / 1000;\n\tconst absolute = Math.abs(differenceSeconds);\n\tlet divisor: number;\n\tlet unit: Intl.RelativeTimeFormatUnit;\n\tif (absolute < 60) {\n\t\tdivisor = 1;\n\t\tunit = \"second\";\n\t} else if (absolute < 3_600) {\n\t\tdivisor = 60;\n\t\tunit = \"minute\";\n\t} else if (absolute < 86_400) {\n\t\tdivisor = 3_600;\n\t\tunit = \"hour\";\n\t} else if (absolute < 604_800) {\n\t\tdivisor = 86_400;\n\t\tunit = \"day\";\n\t} else if (absolute < 2_629_800) {\n\t\tdivisor = 604_800;\n\t\tunit = \"week\";\n\t} else if (absolute < 31_557_600) {\n\t\tdivisor = 2_629_800;\n\t\tunit = \"month\";\n\t} else {\n\t\tdivisor = 31_557_600;\n\t\tunit = \"year\";\n\t}\n\tconst formatter = new Intl.RelativeTimeFormat(options.locale ?? \"zh-CN\", {\n\t\tnumeric: options.numeric ?? \"auto\",\n\t\tstyle: options.style ?? \"long\",\n\t});\n\treturn formatter.format(Math.round(differenceSeconds / divisor), unit);\n}\n\n/** 日期选择器单日期快捷项。 */\nexport interface DateShortcut {\n\t/** 面向中文日期选择器的显示文本;调用方可直接用于菜单标签。 */\n\ttext: string;\n\t/**\n\t * 计算快捷项对应日期。\n\t * @returns 每次调用时基于当前本地时间创建的新 `Date`,调用方可安全修改。\n\t */\n\tvalue: () => Date;\n}\n\n/** 日期选择器范围快捷项。 */\nexport interface DateRangeShortcut {\n\t/** 面向中文日期范围选择器的显示文本;调用方可直接用于菜单标签。 */\n\ttext: string;\n\t/**\n\t * 计算快捷项对应的本地日期范围。\n\t * @returns 每次调用时创建的新元组;起点为 `00:00:00.000`,终点为 `23:59:59.999`。\n\t */\n\tvalue: () => [start: Date, end: Date];\n}\n\n/** 历史快捷项允许移动的本地日历单位。 */\ntype CalendarUnit = \"day\" | \"month\" | \"year\";\n\n/**\n * 移动本地日历字段。\n *\n * @remarks 直接使用 Date Setter,以保留历史快捷项在月底和闰年的溢出语义。\n * @param date - 会被原地修改的日期。\n * @param amount - 对目标字段增加的整数。\n * @param unit - 要移动的日历字段。\n */\nconst shiftCalendarFieldInPlace = (date: Date, amount: number, unit: CalendarUnit): void => {\n\tswitch (unit) {\n\t\tcase \"day\":\n\t\t\tdate.setDate(date.getDate() + amount);\n\t\t\tbreak;\n\t\tcase \"month\":\n\t\t\tdate.setMonth(date.getMonth() + amount);\n\t\t\tbreak;\n\t\tcase \"year\":\n\t\t\tdate.setFullYear(date.getFullYear() + amount);\n\t\t\tbreak;\n\t}\n};\n\n/**\n * 创建动态单日期快捷项。\n *\n * @param text - 日期选择器显示文本。\n * @param amount - 相对当前时间的移动量。\n * @param unit - 移动使用的日历单位。\n * @returns 每次执行 `value` 都重新读取当前时间的快捷项。\n */\nconst createDateShortcut = (text: string, amount: number, unit: CalendarUnit): DateShortcut => ({\n\ttext,\n\tvalue: (): Date => {\n\t\tconst date = new Date();\n\t\tshiftCalendarFieldInPlace(date, amount, unit);\n\t\tdate.setHours(0, 0, 0, 0);\n\t\treturn date;\n\t},\n});\n\n/**\n * 创建动态日期范围快捷项。\n *\n * @param text - 日期选择器显示文本。\n * @param amount - 范围边界相对当前时间的移动量。\n * @param unit - 移动使用的日历单位。\n * @param towardFuture - `true` 移动结束边界,`false` 移动开始边界。\n * @returns 每次求值都覆盖完整本地日边界的范围快捷项。\n */\nconst createRangeShortcut = (text: string, amount: number, unit: CalendarUnit, towardFuture: boolean): DateRangeShortcut => ({\n\ttext,\n\tvalue: (): [Date, Date] => {\n\t\tconst start = new Date();\n\t\tconst end = new Date();\n\t\tshiftCalendarFieldInPlace(towardFuture ? end : start, towardFuture ? amount : -amount, unit);\n\t\tstart.setHours(0, 0, 0, 0);\n\t\tend.setHours(23, 59, 59, 999);\n\t\treturn [start, end];\n\t},\n});\n\n/**\n * 把日期转换为固定中文相对时间文本。\n *\n * @remarks 10 位以内数字按 Unix 秒处理,其余数字按毫秒处理;月份与年份按本地日历月差计算。\n * @param value - Date、时间戳、可解析字符串或空值。\n * @returns 例如“3分钟前”“半年后”;非法或空输入返回空字符串。\n */\nexport function formatChineseRelativeTime(value: Date | number | string | null | undefined): string {\n\tif (value === null || value === undefined) return \"\";\n\tlet timestamp: number;\n\tif (typeof value === \"string\") timestamp = new Date(value).getTime();\n\telse if (typeof value === \"number\") timestamp = value.toString().length <= 10 ? value * 1000 : value;\n\telse timestamp = value.getTime();\n\tif (!Number.isFinite(timestamp)) return \"\";\n\n\tconst minute = 60_000;\n\tconst hour = minute * 60;\n\tconst day = hour * 24;\n\tconst currentTimestamp = Date.now();\n\tconst difference = currentTimestamp - timestamp;\n\tconst minuteDifference = Math.abs(difference) / minute;\n\tconst hourDifference = Math.abs(difference) / hour;\n\tconst dayDifference = Math.abs(difference) / day;\n\tconst currentDate = new Date(currentTimestamp);\n\tconst targetDate = new Date(timestamp);\n\tconst monthDifference = (currentDate.getFullYear() - targetDate.getFullYear()) * 12 + currentDate.getMonth() - targetDate.getMonth();\n\tconst suffix = difference < 0 ? \"后\" : \"前\";\n\tif (Math.abs(monthDifference) >= 12) return `${Math.floor(Math.abs(monthDifference) / 12)}年${suffix}`;\n\tif (Math.abs(monthDifference) >= 6) return `半年${suffix}`;\n\tif (Math.abs(monthDifference) >= 1) return `${Math.abs(monthDifference)}月${suffix}`;\n\tif (dayDifference >= 15) return `半月${suffix}`;\n\tif (dayDifference >= 7) return `${Math.floor(dayDifference / 7)}周${suffix}`;\n\tif (dayDifference >= 1) return `${Math.floor(dayDifference)}天${suffix}`;\n\tif (hourDifference >= 1) return `${Math.floor(hourDifference)}小时${suffix}`;\n\tif (minuteDifference >= 1) return `${Math.floor(minuteDifference)}分钟${suffix}`;\n\treturn \"刚刚\";\n}\n\n/**\n * 创建从今天到前后一个月日期的完整本地日范围。\n *\n * @param towardFuture - `true` 返回今天至一个月后,默认返回一个月前至今天。\n * @returns 每次调用新建的本地日首尾边界。\n */\nexport function createOneMonthRangeFromToday(towardFuture = false): [start: Date, end: Date] {\n\tconst start = new Date();\n\tconst end = new Date();\n\tshiftCalendarFieldInPlace(towardFuture ? end : start, towardFuture ? 1 : -1, \"month\");\n\tstart.setHours(0, 0, 0, 0);\n\tend.setHours(23, 59, 59, 999);\n\treturn [start, end];\n}\n\n/**\n * 判断日期是否晚于调用时的当前时刻。\n *\n * @param time - 待比较日期。\n * @returns 时间戳严格晚于 `Date.now()` 时返回 `true`。\n */\nexport function isDateAfterNow(time: Date): boolean {\n\treturn time.getTime() > Date.now();\n}\n\n/**\n * 根据浏览器本地小时返回固定中文问候语。\n *\n * @returns 与当前时段对应的中文欢迎文本。\n */\nexport function getLocalTimeGreeting(): string {\n\tconst hour = new Date().getHours();\n\tif (hour < 5) return \"夜深了,注意身体哦!\";\n\tif (hour < 9) return \"早上好!欢迎回来!\";\n\tif (hour < 12) return \"上午好!欢迎回来!\";\n\tif (hour < 14) return \"中午好!欢迎回来!\";\n\tif (hour < 18) return \"下午好!欢迎回来!\";\n\tif (hour < 24) return \"晚上好!欢迎回来!\";\n\treturn \"您好!欢迎回来!\";\n}\n\n/**\n * 创建面向过去或未来的常用完整日期范围快捷项。\n *\n * @param towardFuture - `true` 创建未来范围,默认创建历史范围。\n * @returns 每次求值都会重新读取当前时间的范围快捷项。\n */\nexport function createDateRangeShortcuts(towardFuture = false): DateRangeShortcut[] {\n\treturn towardFuture\n\t\t? [\n\t\t\t\tcreateRangeShortcut(\"后1天\", 1, \"day\", true),\n\t\t\t\tcreateRangeShortcut(\"后3天\", 3, \"day\", true),\n\t\t\t\tcreateRangeShortcut(\"后1周\", 7, \"day\", true),\n\t\t\t\tcreateRangeShortcut(\"后1月\", 1, \"month\", true),\n\t\t\t\tcreateRangeShortcut(\"后3月\", 3, \"month\", true),\n\t\t\t\tcreateRangeShortcut(\"后6月\", 6, \"month\", true),\n\t\t\t\tcreateRangeShortcut(\"后1年\", 1, \"year\", true),\n\t\t\t]\n\t\t: [\n\t\t\t\tcreateRangeShortcut(\"近1天\", 1, \"day\", false),\n\t\t\t\tcreateRangeShortcut(\"近3天\", 3, \"day\", false),\n\t\t\t\tcreateRangeShortcut(\"近1周\", 7, \"day\", false),\n\t\t\t\tcreateRangeShortcut(\"近1月\", 1, \"month\", false),\n\t\t\t\tcreateRangeShortcut(\"近3月\", 3, \"month\", false),\n\t\t\t\tcreateRangeShortcut(\"近6月\", 6, \"month\", false),\n\t\t\t\tcreateRangeShortcut(\"近1年\", 1, \"year\", false),\n\t\t\t];\n}\n\n/**\n * 创建面向过去或未来的常用单日期快捷项。\n *\n * @param towardFuture - `true` 创建未来日期,默认创建历史日期。\n * @returns 每次求值都会重新读取当前时间的单日期快捷项。\n */\nexport function createDateShortcuts(towardFuture = false): DateShortcut[] {\n\treturn towardFuture\n\t\t? [\n\t\t\t\tcreateDateShortcut(\"今天\", 0, \"day\"),\n\t\t\t\tcreateDateShortcut(\"明天\", 1, \"day\"),\n\t\t\t\tcreateDateShortcut(\"一周后\", 7, \"day\"),\n\t\t\t\tcreateDateShortcut(\"一月后\", 1, \"month\"),\n\t\t\t\tcreateDateShortcut(\"一年后\", 1, \"year\"),\n\t\t\t]\n\t\t: [\n\t\t\t\tcreateDateShortcut(\"今天\", 0, \"day\"),\n\t\t\t\tcreateDateShortcut(\"昨天\", -1, \"day\"),\n\t\t\t\tcreateDateShortcut(\"一周前\", -7, \"day\"),\n\t\t\t\tcreateDateShortcut(\"一月前\", -1, \"month\"),\n\t\t\t\tcreateDateShortcut(\"一年前\", -1, \"year\"),\n\t\t\t];\n}\n\n/**\n * 返回今天的本地零点。\n *\n * @returns 新建的 `00:00:00.000` Date。\n */\nexport function getStartOfToday(): Date {\n\treturn startOfDay(new Date());\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/dom/style.d.ts
|
|
2
|
+
/** 可序列化为内联 CSS 的单个值。 */
|
|
3
|
+
type StyleValue = number | string | null | undefined;
|
|
4
|
+
/** camelCase、kebab-case 或 CSS 自定义属性组成的只读样式对象。 */
|
|
5
|
+
type StyleObject = Readonly<Record<string, StyleValue>>;
|
|
6
|
+
/** 字符串、样式对象、嵌套数组或空值。 */
|
|
7
|
+
type StyleInput = string | StyleObject | readonly StyleInput[] | null | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* 为数值或纯数字字符串添加 CSS 单位。
|
|
10
|
+
*
|
|
11
|
+
* @param value - 数字、数字字符串或已有单位的 CSS 值;空值返回空字符串。
|
|
12
|
+
* @param unit - 非零数字使用的单位,默认 `px`。
|
|
13
|
+
* @returns 零统一返回 `"0"`;非数字字符串保持原样。
|
|
14
|
+
* @throws `RangeError` 当数字非有限或单位为空。
|
|
15
|
+
*/
|
|
16
|
+
declare function addCssUnit(value?: string | number | null, unit?: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* 将样式字符串、对象或嵌套数组序列化为内联 CSS。
|
|
19
|
+
*
|
|
20
|
+
* @remarks 本函数只负责结构转换,不是 CSS 安全清洗器。不可信值必须由调用方按照
|
|
21
|
+
* 实际渲染上下文验证,尤其不能允许用户控制属性名、`url()` 或自定义属性内容。
|
|
22
|
+
* 数字不会自动附加单位;需要长度单位时应先调用 {@link addCssUnit}。
|
|
23
|
+
* @param styles - 可嵌套样式输入;后出现的声明由 CSS 层叠规则覆盖先前声明。
|
|
24
|
+
* @returns 以分号结束、以空格分隔的 CSS 声明字符串。
|
|
25
|
+
* @throws `RangeError` 当对象中包含 `NaN` 或无穷数字。
|
|
26
|
+
*/
|
|
27
|
+
declare function serializeStyle(styles: StyleInput): string;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { StyleInput, StyleObject, StyleValue, addCssUnit, serializeStyle };
|
|
30
|
+
//# sourceMappingURL=style.d.mts.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
//#region src/dom/style.ts
|
|
2
|
+
/**
|
|
3
|
+
* 判断 StyleInput 是否为递归样式数组。
|
|
4
|
+
*
|
|
5
|
+
* @param value - 待缩小的样式输入。
|
|
6
|
+
* @returns 是只读样式数组时返回 `true`。
|
|
7
|
+
*/
|
|
8
|
+
const isStyleArray = (value) => Array.isArray(value);
|
|
9
|
+
/**
|
|
10
|
+
* 判断文本是否可按十进制数值追加 CSS 单位。
|
|
11
|
+
*
|
|
12
|
+
* @param value - 已去除外围空白的文本。
|
|
13
|
+
* @returns 有限十进制数值返回 `true`;二、八、十六进制前缀返回 `false`。
|
|
14
|
+
*/
|
|
15
|
+
const isNumericString = (value) => {
|
|
16
|
+
if (value.length === 0 || !Number.isFinite(Number(value))) return false;
|
|
17
|
+
const unsigned = value.startsWith("+") || value.startsWith("-") ? value.slice(1) : value;
|
|
18
|
+
return !/^0[box]/iu.test(unsigned);
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* 把 JavaScript 样式属性名转换为 CSS 属性名。
|
|
22
|
+
*
|
|
23
|
+
* @param key - camelCase、kebab-case 或 CSS 自定义属性名。
|
|
24
|
+
* @returns kebab-case 属性名;`--` 自定义属性保持原样。
|
|
25
|
+
*/
|
|
26
|
+
const toCssPropertyName = (key) => {
|
|
27
|
+
if (key.startsWith("--")) return key;
|
|
28
|
+
return (key.startsWith("ms") ? `-${key}` : key).replace(/([A-Z])/gu, "-$1").toLowerCase();
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* 为数值或纯数字字符串添加 CSS 单位。
|
|
32
|
+
*
|
|
33
|
+
* @param value - 数字、数字字符串或已有单位的 CSS 值;空值返回空字符串。
|
|
34
|
+
* @param unit - 非零数字使用的单位,默认 `px`。
|
|
35
|
+
* @returns 零统一返回 `"0"`;非数字字符串保持原样。
|
|
36
|
+
* @throws `RangeError` 当数字非有限或单位为空。
|
|
37
|
+
*/
|
|
38
|
+
function addCssUnit(value, unit = "px") {
|
|
39
|
+
if (value === null || value === void 0 || value === "") return "";
|
|
40
|
+
if (unit.length === 0) throw new RangeError("unit cannot be empty.");
|
|
41
|
+
if (typeof value === "number") {
|
|
42
|
+
if (!Number.isFinite(value)) throw new RangeError("value must be finite.");
|
|
43
|
+
return value === 0 ? "0" : `${value}${unit}`;
|
|
44
|
+
}
|
|
45
|
+
const trimmed = value.trim();
|
|
46
|
+
if (!isNumericString(trimmed)) return value;
|
|
47
|
+
return Number(trimmed) === 0 ? "0" : `${trimmed}${unit}`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 将样式字符串、对象或嵌套数组序列化为内联 CSS。
|
|
51
|
+
*
|
|
52
|
+
* @remarks 本函数只负责结构转换,不是 CSS 安全清洗器。不可信值必须由调用方按照
|
|
53
|
+
* 实际渲染上下文验证,尤其不能允许用户控制属性名、`url()` 或自定义属性内容。
|
|
54
|
+
* 数字不会自动附加单位;需要长度单位时应先调用 {@link addCssUnit}。
|
|
55
|
+
* @param styles - 可嵌套样式输入;后出现的声明由 CSS 层叠规则覆盖先前声明。
|
|
56
|
+
* @returns 以分号结束、以空格分隔的 CSS 声明字符串。
|
|
57
|
+
* @throws `RangeError` 当对象中包含 `NaN` 或无穷数字。
|
|
58
|
+
*/
|
|
59
|
+
function serializeStyle(styles) {
|
|
60
|
+
if (styles === null || styles === void 0 || styles === "") return "";
|
|
61
|
+
if (isStyleArray(styles)) return styles.map((item) => serializeStyle(item)).filter((item) => item.length > 0).join(" ");
|
|
62
|
+
if (typeof styles === "string") {
|
|
63
|
+
const value = styles.trim();
|
|
64
|
+
return value.length === 0 ? "" : value.endsWith(";") ? value : `${value};`;
|
|
65
|
+
}
|
|
66
|
+
return Object.entries(styles).filter(([, value]) => value !== null && value !== void 0 && value !== "").map(([key, value]) => {
|
|
67
|
+
if (typeof value === "number" && !Number.isFinite(value)) throw new RangeError(`Style property "${key}" must be finite.`);
|
|
68
|
+
return `${toCssPropertyName(key)}:${String(value)};`;
|
|
69
|
+
}).join(" ");
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
export { addCssUnit, serializeStyle };
|
|
73
|
+
|
|
74
|
+
//# sourceMappingURL=style.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"style.mjs","names":[],"sources":["../../src/dom/style.ts"],"sourcesContent":["/** 可序列化为内联 CSS 的单个值。 */\nexport type StyleValue = number | string | null | undefined;\n\n/** camelCase、kebab-case 或 CSS 自定义属性组成的只读样式对象。 */\nexport type StyleObject = Readonly<Record<string, StyleValue>>;\n\n/** 字符串、样式对象、嵌套数组或空值。 */\nexport type StyleInput = string | StyleObject | readonly StyleInput[] | null | undefined;\n\n/**\n * 判断 StyleInput 是否为递归样式数组。\n *\n * @param value - 待缩小的样式输入。\n * @returns 是只读样式数组时返回 `true`。\n */\nconst isStyleArray = (value: StyleInput): value is readonly StyleInput[] => Array.isArray(value);\n\n/**\n * 判断文本是否可按十进制数值追加 CSS 单位。\n *\n * @param value - 已去除外围空白的文本。\n * @returns 有限十进制数值返回 `true`;二、八、十六进制前缀返回 `false`。\n */\nconst isNumericString = (value: string): boolean => {\n\tif (value.length === 0 || !Number.isFinite(Number(value))) return false;\n\tconst unsigned = value.startsWith(\"+\") || value.startsWith(\"-\") ? value.slice(1) : value;\n\treturn !/^0[box]/iu.test(unsigned);\n};\n\n/**\n * 把 JavaScript 样式属性名转换为 CSS 属性名。\n *\n * @param key - camelCase、kebab-case 或 CSS 自定义属性名。\n * @returns kebab-case 属性名;`--` 自定义属性保持原样。\n */\nconst toCssPropertyName = (key: string): string => {\n\tif (key.startsWith(\"--\")) return key;\n\tconst normalized = key.startsWith(\"ms\") ? `-${key}` : key;\n\treturn normalized.replace(/([A-Z])/gu, \"-$1\").toLowerCase();\n};\n\n/**\n * 为数值或纯数字字符串添加 CSS 单位。\n *\n * @param value - 数字、数字字符串或已有单位的 CSS 值;空值返回空字符串。\n * @param unit - 非零数字使用的单位,默认 `px`。\n * @returns 零统一返回 `\"0\"`;非数字字符串保持原样。\n * @throws `RangeError` 当数字非有限或单位为空。\n */\nexport function addCssUnit(value?: string | number | null, unit = \"px\"): string {\n\tif (value === null || value === undefined || value === \"\") return \"\";\n\tif (unit.length === 0) throw new RangeError(\"unit cannot be empty.\");\n\tif (typeof value === \"number\") {\n\t\tif (!Number.isFinite(value)) throw new RangeError(\"value must be finite.\");\n\t\treturn value === 0 ? \"0\" : `${value}${unit}`;\n\t}\n\tconst trimmed = value.trim();\n\tif (!isNumericString(trimmed)) return value;\n\treturn Number(trimmed) === 0 ? \"0\" : `${trimmed}${unit}`;\n}\n\n/**\n * 将样式字符串、对象或嵌套数组序列化为内联 CSS。\n *\n * @remarks 本函数只负责结构转换,不是 CSS 安全清洗器。不可信值必须由调用方按照\n * 实际渲染上下文验证,尤其不能允许用户控制属性名、`url()` 或自定义属性内容。\n * 数字不会自动附加单位;需要长度单位时应先调用 {@link addCssUnit}。\n * @param styles - 可嵌套样式输入;后出现的声明由 CSS 层叠规则覆盖先前声明。\n * @returns 以分号结束、以空格分隔的 CSS 声明字符串。\n * @throws `RangeError` 当对象中包含 `NaN` 或无穷数字。\n */\nexport function serializeStyle(styles: StyleInput): string {\n\tif (styles === null || styles === undefined || styles === \"\") return \"\";\n\tif (isStyleArray(styles)) {\n\t\treturn styles\n\t\t\t.map((item) => serializeStyle(item))\n\t\t\t.filter((item) => item.length > 0)\n\t\t\t.join(\" \");\n\t}\n\tif (typeof styles === \"string\") {\n\t\tconst value = styles.trim();\n\t\treturn value.length === 0 ? \"\" : value.endsWith(\";\") ? value : `${value};`;\n\t}\n\n\treturn Object.entries(styles)\n\t\t.filter(([, value]) => value !== null && value !== undefined && value !== \"\")\n\t\t.map(([key, value]) => {\n\t\t\tif (typeof value === \"number\" && !Number.isFinite(value)) throw new RangeError(`Style property \"${key}\" must be finite.`);\n\t\t\treturn `${toCssPropertyName(key)}:${String(value)};`;\n\t\t})\n\t\t.join(\" \");\n}\n"],"mappings":";;;;;;;AAeA,MAAM,gBAAgB,UAAsD,MAAM,QAAQ,KAAK;;;;;;;AAQ/F,MAAM,mBAAmB,UAA2B;CACnD,IAAI,MAAM,WAAW,KAAK,CAAC,OAAO,SAAS,OAAO,KAAK,CAAC,GAAG,OAAO;CAClE,MAAM,WAAW,MAAM,WAAW,GAAG,KAAK,MAAM,WAAW,GAAG,IAAI,MAAM,MAAM,CAAC,IAAI;CACnF,OAAO,CAAC,YAAY,KAAK,QAAQ;AAClC;;;;;;;AAQA,MAAM,qBAAqB,QAAwB;CAClD,IAAI,IAAI,WAAW,IAAI,GAAG,OAAO;CAEjC,QADmB,IAAI,WAAW,IAAI,IAAI,IAAI,QAAQ,IAAA,CACpC,QAAQ,aAAa,KAAK,CAAC,CAAC,YAAY;AAC3D;;;;;;;;;AAUA,SAAgB,WAAW,OAAgC,OAAO,MAAc;CAC/E,IAAI,UAAU,QAAQ,UAAU,KAAA,KAAa,UAAU,IAAI,OAAO;CAClE,IAAI,KAAK,WAAW,GAAG,MAAM,IAAI,WAAW,uBAAuB;CACnE,IAAI,OAAO,UAAU,UAAU;EAC9B,IAAI,CAAC,OAAO,SAAS,KAAK,GAAG,MAAM,IAAI,WAAW,uBAAuB;EACzE,OAAO,UAAU,IAAI,MAAM,GAAG,QAAQ;CACvC;CACA,MAAM,UAAU,MAAM,KAAK;CAC3B,IAAI,CAAC,gBAAgB,OAAO,GAAG,OAAO;CACtC,OAAO,OAAO,OAAO,MAAM,IAAI,MAAM,GAAG,UAAU;AACnD;;;;;;;;;;;AAYA,SAAgB,eAAe,QAA4B;CAC1D,IAAI,WAAW,QAAQ,WAAW,KAAA,KAAa,WAAW,IAAI,OAAO;CACrE,IAAI,aAAa,MAAM,GACtB,OAAO,OACL,KAAK,SAAS,eAAe,IAAI,CAAC,CAAC,CACnC,QAAQ,SAAS,KAAK,SAAS,CAAC,CAAC,CACjC,KAAK,GAAG;CAEX,IAAI,OAAO,WAAW,UAAU;EAC/B,MAAM,QAAQ,OAAO,KAAK;EAC1B,OAAO,MAAM,WAAW,IAAI,KAAK,MAAM,SAAS,GAAG,IAAI,QAAQ,GAAG,MAAM;CACzE;CAEA,OAAO,OAAO,QAAQ,MAAM,CAAC,CAC3B,QAAQ,GAAG,WAAW,UAAU,QAAQ,UAAU,KAAA,KAAa,UAAU,EAAE,CAAC,CAC5E,KAAK,CAAC,KAAK,WAAW;EACtB,IAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,GAAG,MAAM,IAAI,WAAW,mBAAmB,IAAI,kBAAkB;EACxH,OAAO,GAAG,kBAAkB,GAAG,EAAE,GAAG,OAAO,KAAK,EAAE;CACnD,CAAC,CAAC,CACD,KAAK,GAAG;AACX"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
//#region src/env/index.d.ts
|
|
2
|
+
/** 可识别的主要 JavaScript 运行环境。 */
|
|
3
|
+
type RuntimeKind = "browser" | "node" | "unknown" | "worker";
|
|
4
|
+
/**
|
|
5
|
+
* 判断当前运行时是否具有浏览器 `window` 与 `document`。
|
|
6
|
+
*
|
|
7
|
+
* @returns 两项能力均存在时返回 `true`;不读取 DOM 内容。
|
|
8
|
+
*/
|
|
9
|
+
declare function isBrowser(): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* 判断当前运行时是否像 Web Worker 且不是 Window。
|
|
12
|
+
*
|
|
13
|
+
* @remarks 经典、模块、Shared 与 Service Worker 全局通常都暴露 `importScripts`;模块
|
|
14
|
+
* Worker 中调用它可能抛错,本检测只检查能力存在,不会执行。
|
|
15
|
+
* @returns 具有 `importScripts` 且不是浏览器 Window 时返回 `true`。
|
|
16
|
+
*/
|
|
17
|
+
declare function isWebWorker(): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* 判断当前运行时是否暴露 Node.js 版本标记。
|
|
20
|
+
*
|
|
21
|
+
* @returns `process.versions.node` 为字符串时返回 `true`。
|
|
22
|
+
*/
|
|
23
|
+
declare function isNode(): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* 判断当前运行时是否暴露 uni-app 的 `uni` 全局对象。
|
|
26
|
+
*
|
|
27
|
+
* @returns 全局属性存在且不为 `undefined` 时返回 `true`;不调用任何平台 API。
|
|
28
|
+
*/
|
|
29
|
+
declare function isUniApp(): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* 判断当前运行时是否具备本库完整加密 API 所需的 Web Crypto 能力。
|
|
32
|
+
*
|
|
33
|
+
* @remarks 只具有 `getRandomValues` 的平台仍可调用随机数与随机字符串 API,但本函数
|
|
34
|
+
* 会返回 `false`,因为摘要、PBKDF2、AES-GCM、RSA 与 ECC 还需要完整的 `SubtleCrypto` 方法集。
|
|
35
|
+
* @returns 同时提供本库 Web Crypto 功能所需方法时返回 `true`。
|
|
36
|
+
*/
|
|
37
|
+
declare function hasWebCrypto(): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* 返回当前主要运行环境。
|
|
40
|
+
*
|
|
41
|
+
* @remarks 在使用 DOM 模拟器的 Node.js 进程中优先报告 `browser`,因为可观察能力比宿主进程名称更有用。
|
|
42
|
+
* @returns `browser`、`worker`、`node` 或无法识别时的 `unknown`。
|
|
43
|
+
*/
|
|
44
|
+
declare function detectRuntime(): RuntimeKind;
|
|
45
|
+
/**
|
|
46
|
+
* 基于 User-Agent 启发式判断手机设备。
|
|
47
|
+
*
|
|
48
|
+
* @param userAgent - 默认读取当前 `navigator.userAgent`;平台对象不存在时使用空字符串。
|
|
49
|
+
* @remarks User-Agent 可以被伪造,不得用于鉴权、安全策略或永久功能分流。
|
|
50
|
+
* @returns 命中手机特征时返回 `true`。
|
|
51
|
+
*/
|
|
52
|
+
declare function isMobileUserAgent(userAgent?: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* 基于 User-Agent 与触点数量启发式判断平板设备。
|
|
55
|
+
*
|
|
56
|
+
* @param userAgent - 默认读取当前 User-Agent。
|
|
57
|
+
* @param maxTouchPoints - 用于识别桌面 User-Agent 模式下的 iPadOS,默认读取当前触点数。
|
|
58
|
+
* @returns 命中平板特征时返回 `true`。
|
|
59
|
+
*/
|
|
60
|
+
declare function isTabletUserAgent(userAgent?: string, maxTouchPoints?: number): boolean;
|
|
61
|
+
//#endregion
|
|
62
|
+
export { RuntimeKind, detectRuntime, hasWebCrypto, isBrowser, isMobileUserAgent, isNode, isTabletUserAgent, isUniApp, isWebWorker };
|
|
63
|
+
//# sourceMappingURL=index.d.mts.map
|