@fast-china/utils 2.1.10 → 2.1.11
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 +22 -0
- package/README.md +5 -2
- package/README.zh.md +5 -2
- package/dist/array/index.mjs +21 -21
- package/dist/array/index.mjs.map +1 -1
- package/dist/async/index.mjs +32 -32
- package/dist/async/index.mjs.map +1 -1
- package/dist/base64/index.mjs +44 -51
- package/dist/base64/index.mjs.map +1 -1
- package/dist/color/index.mjs +33 -33
- package/dist/color/index.mjs.map +1 -1
- package/dist/crypto/index.mjs +153 -155
- package/dist/crypto/index.mjs.map +1 -1
- package/dist/date/index.mjs +79 -46
- package/dist/date/index.mjs.map +1 -1
- package/dist/dom/style.mjs +7 -7
- package/dist/dom/style.mjs.map +1 -1
- package/dist/env/index.mjs +9 -14
- package/dist/env/index.mjs.map +1 -1
- package/dist/function/index.mjs +4 -4
- package/dist/function/index.mjs.map +1 -1
- package/dist/identity/index.mjs +7 -7
- package/dist/identity/index.mjs.map +1 -1
- package/dist/index.d.mts +371 -366
- package/dist/index.global.min.js +2 -2
- package/dist/index.global.min.js.map +1 -1
- package/dist/internal/text.mjs +70 -26
- package/dist/internal/text.mjs.map +1 -1
- package/dist/logger/index.mjs +12 -13
- package/dist/logger/index.mjs.map +1 -1
- package/dist/number/index.mjs +63 -42
- package/dist/number/index.mjs.map +1 -1
- package/dist/object/index.mjs +32 -31
- package/dist/object/index.mjs.map +1 -1
- package/dist/storage/index.mjs +58 -63
- package/dist/storage/index.mjs.map +1 -1
- package/dist/string/index.mjs +60 -64
- package/dist/string/index.mjs.map +1 -1
- package/dist/vue/breakpoints.mjs +14 -10
- package/dist/vue/breakpoints.mjs.map +1 -1
- package/dist/vue/element-size.mjs +4 -4
- package/dist/vue/element-size.mjs.map +1 -1
- package/dist/vue/emits.mjs +7 -7
- package/dist/vue/emits.mjs.map +1 -1
- package/dist/vue/event-listener.mjs +5 -5
- package/dist/vue/event-listener.mjs.map +1 -1
- package/dist/vue/expose.mjs +3 -3
- package/dist/vue/expose.mjs.map +1 -1
- package/dist/vue/func.mjs +2 -2
- package/dist/vue/func.mjs.map +1 -1
- package/dist/vue/install.mjs +24 -24
- package/dist/vue/install.mjs.map +1 -1
- package/dist/vue/now.mjs +4 -5
- package/dist/vue/now.mjs.map +1 -1
- package/dist/vue/props.mjs +5 -5
- package/dist/vue/props.mjs.map +1 -1
- package/dist/vue/render.mjs +2 -2
- package/dist/vue/render.mjs.map +1 -1
- package/dist/vue/resize-observer.mjs +4 -6
- package/dist/vue/resize-observer.mjs.map +1 -1
- package/dist/vue/slots.mjs.map +1 -1
- package/dist/vue/transition.mjs +5 -7
- package/dist/vue/transition.mjs.map +1 -1
- package/dist/vue/window-size.mjs +2 -4
- package/dist/vue/window-size.mjs.map +1 -1
- package/dist/vue/with.mjs +1 -1
- package/dist/vue/with.mjs.map +1 -1
- package/package.json +2 -1
- package/dist/internal/runtime.mjs +0 -32
- package/dist/internal/runtime.mjs.map +0 -1
package/dist/date/index.mjs
CHANGED
|
@@ -2,29 +2,29 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* 校验日期算术移动量。
|
|
4
4
|
*
|
|
5
|
-
* @param amount -
|
|
5
|
+
* @param amount - 待校验的日、月或年移动量
|
|
6
6
|
* @throws `RangeError` 当值不是安全整数。
|
|
7
7
|
*/
|
|
8
8
|
const assertIntegerAmount = (amount) => {
|
|
9
|
-
if (!Number.isSafeInteger(amount)) throw new RangeError("`amount`
|
|
9
|
+
if (!Number.isSafeInteger(amount)) throw new RangeError("`amount` must be a safe integer.");
|
|
10
10
|
};
|
|
11
11
|
/**
|
|
12
12
|
* 转换并克隆有效日期。
|
|
13
13
|
*
|
|
14
14
|
* @remarks 数字不进行秒/毫秒猜测;字符串遵循运行时 `Date` 解析规则,跨平台代码应传带显式时区的完整 ISO 8601。
|
|
15
|
-
* @param value - Date、Unix
|
|
16
|
-
* @returns 与输入不共享可变状态的新 Date
|
|
15
|
+
* @param value - Date、Unix 毫秒时间戳或运行时可解析字符串
|
|
16
|
+
* @returns 与输入不共享可变状态的新 Date
|
|
17
17
|
* @throws 输入无效时抛出 `TypeError`。
|
|
18
18
|
*/
|
|
19
19
|
function toDate(value) {
|
|
20
20
|
const date = value instanceof Date ? new Date(value.getTime()) : new Date(value);
|
|
21
|
-
if (!Number.isFinite(date.getTime())) throw new TypeError("
|
|
21
|
+
if (!Number.isFinite(date.getTime())) throw new TypeError("The value is not a valid date.");
|
|
22
22
|
return date;
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
25
|
* 判断输入能否转换为有效日期。
|
|
26
26
|
*
|
|
27
|
-
* @param value -
|
|
27
|
+
* @param value - 任意待检查值
|
|
28
28
|
* @returns 仅 Date、数字或字符串且时间戳有限时返回 `true`。
|
|
29
29
|
*/
|
|
30
30
|
function isValidDate(value) {
|
|
@@ -34,8 +34,8 @@ function isValidDate(value) {
|
|
|
34
34
|
/**
|
|
35
35
|
* 返回输入日期所在本地时区日期的 `00:00:00.000`,不修改输入。
|
|
36
36
|
*
|
|
37
|
-
* @param value -
|
|
38
|
-
* @returns
|
|
37
|
+
* @param value - 有效日期输入
|
|
38
|
+
* @returns 新建的本地日开始时间
|
|
39
39
|
* @throws 输入无效时抛出 `TypeError`。
|
|
40
40
|
*/
|
|
41
41
|
function startOfDay(value) {
|
|
@@ -46,8 +46,8 @@ function startOfDay(value) {
|
|
|
46
46
|
/**
|
|
47
47
|
* 返回输入日期所在本地时区日期的 `23:59:59.999`,不修改输入。
|
|
48
48
|
*
|
|
49
|
-
* @param value -
|
|
50
|
-
* @returns
|
|
49
|
+
* @param value - 有效日期输入
|
|
50
|
+
* @returns 新建的本地日结束时间
|
|
51
51
|
* @throws 输入无效时抛出 `TypeError`。
|
|
52
52
|
*/
|
|
53
53
|
function endOfDay(value) {
|
|
@@ -58,8 +58,8 @@ function endOfDay(value) {
|
|
|
58
58
|
/**
|
|
59
59
|
* 按本地日历增加整数天,不修改输入。
|
|
60
60
|
*
|
|
61
|
-
* @param value -
|
|
62
|
-
* @param amount -
|
|
61
|
+
* @param value - 基准日期
|
|
62
|
+
* @param amount - 可为负数的安全整数日数
|
|
63
63
|
* @returns 本地日历运算后的新 Date;夏令时变化可能使实际毫秒差不等于 24 小时。
|
|
64
64
|
* @throws 日期无效时抛出 `TypeError`;数量或结果非法时抛出 `RangeError`。
|
|
65
65
|
*/
|
|
@@ -73,9 +73,9 @@ function addDays(value, amount) {
|
|
|
73
73
|
* 按本地日历增加整数月,并把不存在的日期夹到目标月末。
|
|
74
74
|
*
|
|
75
75
|
* @example 1 月 31 日增加一个月会落在 2 月最后一天。
|
|
76
|
-
* @param value -
|
|
77
|
-
* @param amount -
|
|
78
|
-
* @returns 月份运算后的新 Date
|
|
76
|
+
* @param value - 基准日期
|
|
77
|
+
* @param amount - 可为负数的安全整数月数
|
|
78
|
+
* @returns 月份运算后的新 Date
|
|
79
79
|
* @throws 日期无效时抛出 `TypeError`;数量或结果非法时抛出 `RangeError`。
|
|
80
80
|
*/
|
|
81
81
|
function addMonths(value, amount) {
|
|
@@ -93,9 +93,9 @@ function addMonths(value, amount) {
|
|
|
93
93
|
/**
|
|
94
94
|
* 按本地日历增加整数年,并沿用 {@link addMonths} 的月末夹取规则。
|
|
95
95
|
*
|
|
96
|
-
* @param value -
|
|
97
|
-
* @param amount -
|
|
98
|
-
* @returns 年份运算后的新 Date
|
|
96
|
+
* @param value - 基准日期
|
|
97
|
+
* @param amount - 可为负数的安全整数年数
|
|
98
|
+
* @returns 年份运算后的新 Date
|
|
99
99
|
* @throws 日期无效时抛出 `TypeError`;数量或结果非法时抛出 `RangeError`。
|
|
100
100
|
*/
|
|
101
101
|
function addYears(value, amount) {
|
|
@@ -105,8 +105,8 @@ function addYears(value, amount) {
|
|
|
105
105
|
/**
|
|
106
106
|
* 判断两个输入是否属于同一本地日历日。
|
|
107
107
|
*
|
|
108
|
-
* @param left -
|
|
109
|
-
* @param right -
|
|
108
|
+
* @param left - 第一日期
|
|
109
|
+
* @param right - 第二日期
|
|
110
110
|
* @returns 本地年、月、日均相同时返回 `true`。
|
|
111
111
|
* @throws 任一输入无效时抛出 `TypeError`。
|
|
112
112
|
*/
|
|
@@ -118,8 +118,8 @@ function isSameDay(left, right) {
|
|
|
118
118
|
/**
|
|
119
119
|
* 判断时间是否晚于基准时间。
|
|
120
120
|
*
|
|
121
|
-
* @param value -
|
|
122
|
-
* @param now -
|
|
121
|
+
* @param value - 待比较时间
|
|
122
|
+
* @param now - 比较基准,默认调用时的当前时刻
|
|
123
123
|
* @returns `value` 严格晚于基准时返回 `true`。
|
|
124
124
|
* @throws 任一输入无效时抛出 `TypeError`。
|
|
125
125
|
*/
|
|
@@ -129,8 +129,8 @@ function isFuture(value, now = Date.now()) {
|
|
|
129
129
|
/**
|
|
130
130
|
* 返回指定基准所在本地日历日的完整闭区间。
|
|
131
131
|
*
|
|
132
|
-
* @param value -
|
|
133
|
-
* @returns
|
|
132
|
+
* @param value - 日期基准,默认调用时当前日期
|
|
133
|
+
* @returns 新建的本地日开始和结束时间二元组
|
|
134
134
|
* @throws 输入无效时抛出 `TypeError`。
|
|
135
135
|
*/
|
|
136
136
|
function getLocalDayBounds(value = Date.now()) {
|
|
@@ -139,9 +139,9 @@ function getLocalDayBounds(value = Date.now()) {
|
|
|
139
139
|
/**
|
|
140
140
|
* 判断日期是否位于包含首尾的时间区间。
|
|
141
141
|
*
|
|
142
|
-
* @param value -
|
|
143
|
-
* @param start -
|
|
144
|
-
* @param end -
|
|
142
|
+
* @param value - 待检查日期
|
|
143
|
+
* @param start - 包含的起点
|
|
144
|
+
* @param end - 包含的终点
|
|
145
145
|
* @returns 时间戳位于闭区间内时返回 `true`。
|
|
146
146
|
* @throws 无效日期抛出 `TypeError`;首尾反向时抛出 `RangeError`。
|
|
147
147
|
*/
|
|
@@ -149,17 +149,18 @@ function isWithinInterval(value, start, end) {
|
|
|
149
149
|
const timestamp = toDate(value).getTime();
|
|
150
150
|
const startTimestamp = toDate(start).getTime();
|
|
151
151
|
const endTimestamp = toDate(end).getTime();
|
|
152
|
-
if (startTimestamp > endTimestamp) throw new RangeError("`start`
|
|
152
|
+
if (startTimestamp > endTimestamp) throw new RangeError("`start` must not be later than `end`.");
|
|
153
153
|
return timestamp >= startTimestamp && timestamp <= endTimestamp;
|
|
154
154
|
}
|
|
155
155
|
/**
|
|
156
|
-
*
|
|
156
|
+
* 生成人类可读相对时间;默认中文使用内部实现,显式语言使用 Intl.RelativeTimeFormat。
|
|
157
157
|
*
|
|
158
158
|
* @remarks 秒、分钟、小时、天、周、月和年按固定时长阈值选择;这适合展示,不适合计费或日历运算。
|
|
159
|
-
* @param value -
|
|
160
|
-
* @param options -
|
|
161
|
-
* @returns
|
|
159
|
+
* @param value - 目标时间
|
|
160
|
+
* @param options - 语言、样式与比较基准
|
|
161
|
+
* @returns 相对时间文本
|
|
162
162
|
* @throws 日期无效时抛出 `TypeError`;Locale 或 Intl 选项非法时抛出 `RangeError`。
|
|
163
|
+
* 显式指定语言且缺少 Intl.RelativeTimeFormat 时抛出 `Error`。
|
|
163
164
|
*/
|
|
164
165
|
function formatRelativeTime(value, options = {}) {
|
|
165
166
|
const differenceSeconds = (toDate(value).getTime() - toDate(options.now ?? Date.now()).getTime()) / 1e3;
|
|
@@ -188,18 +189,50 @@ function formatRelativeTime(value, options = {}) {
|
|
|
188
189
|
divisor = 31557600;
|
|
189
190
|
unit = "year";
|
|
190
191
|
}
|
|
192
|
+
const amount = Math.round(differenceSeconds / divisor);
|
|
193
|
+
if (options.locale === void 0) {
|
|
194
|
+
const numeric = options.numeric ?? "auto";
|
|
195
|
+
const style = options.style ?? "long";
|
|
196
|
+
if (numeric !== "auto" && numeric !== "always") throw new RangeError("Relative time numeric must be auto or always.");
|
|
197
|
+
if (style !== "long" && style !== "short" && style !== "narrow") throw new RangeError("Relative time style must be long, short, or narrow.");
|
|
198
|
+
if (numeric === "auto") {
|
|
199
|
+
if (unit === "second" && amount === 0) return "现在";
|
|
200
|
+
if (unit === "day") {
|
|
201
|
+
if (amount === -2) return "前天";
|
|
202
|
+
if (amount === -1) return "昨天";
|
|
203
|
+
if (amount === 1) return "明天";
|
|
204
|
+
if (amount === 2) return "后天";
|
|
205
|
+
}
|
|
206
|
+
if (amount === -1 || amount === 1) {
|
|
207
|
+
if (unit === "week") return amount < 0 ? "上周" : "下周";
|
|
208
|
+
if (unit === "month") return amount < 0 ? "上个月" : "下个月";
|
|
209
|
+
if (unit === "year") return amount < 0 ? "去年" : "明年";
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const labels = {
|
|
213
|
+
second: style === "long" ? "秒钟" : "秒",
|
|
214
|
+
minute: "分钟",
|
|
215
|
+
hour: "小时",
|
|
216
|
+
day: "天",
|
|
217
|
+
week: "周",
|
|
218
|
+
month: "个月",
|
|
219
|
+
year: "年"
|
|
220
|
+
};
|
|
221
|
+
return String(Math.abs(amount)).replace(/\B(?=(?:\d{3})+(?!\d))/gu, ",") + labels[unit] + (amount < 0 || Object.is(amount, -0) ? "前" : "后");
|
|
222
|
+
}
|
|
223
|
+
if (typeof Intl === "undefined" || typeof Intl.RelativeTimeFormat !== "function") throw new Error("The current runtime does not support Intl.RelativeTimeFormat.");
|
|
191
224
|
return new Intl.RelativeTimeFormat(options.locale ?? "zh-CN", {
|
|
192
225
|
numeric: options.numeric ?? "auto",
|
|
193
226
|
style: options.style ?? "long"
|
|
194
|
-
}).format(
|
|
227
|
+
}).format(amount, unit);
|
|
195
228
|
}
|
|
196
229
|
/**
|
|
197
230
|
* 移动本地日历字段。
|
|
198
231
|
*
|
|
199
232
|
* @remarks 直接使用 Date Setter,以保留历史快捷项在月底和闰年的溢出语义。
|
|
200
|
-
* @param date -
|
|
201
|
-
* @param amount -
|
|
202
|
-
* @param unit -
|
|
233
|
+
* @param date - 会被原地修改的日期
|
|
234
|
+
* @param amount - 对目标字段增加的整数
|
|
235
|
+
* @param unit - 要移动的日历字段
|
|
203
236
|
*/
|
|
204
237
|
const shiftCalendarFieldInPlace = (date, amount, unit) => {
|
|
205
238
|
switch (unit) {
|
|
@@ -215,9 +248,9 @@ const shiftCalendarFieldInPlace = (date, amount, unit) => {
|
|
|
215
248
|
/**
|
|
216
249
|
* 创建动态单日期快捷项。
|
|
217
250
|
*
|
|
218
|
-
* @param text -
|
|
219
|
-
* @param amount -
|
|
220
|
-
* @param unit -
|
|
251
|
+
* @param text - 日期选择器显示文本
|
|
252
|
+
* @param amount - 相对当前时间的移动量
|
|
253
|
+
* @param unit - 移动使用的日历单位
|
|
221
254
|
* @returns 每次执行 `value` 都重新读取当前时间的快捷项。
|
|
222
255
|
*/
|
|
223
256
|
const createDateShortcut = (text, amount, unit) => ({
|
|
@@ -232,9 +265,9 @@ const createDateShortcut = (text, amount, unit) => ({
|
|
|
232
265
|
/**
|
|
233
266
|
* 创建动态日期范围快捷项。
|
|
234
267
|
*
|
|
235
|
-
* @param text -
|
|
236
|
-
* @param amount -
|
|
237
|
-
* @param unit -
|
|
268
|
+
* @param text - 日期选择器显示文本
|
|
269
|
+
* @param amount - 范围边界相对当前时间的移动量
|
|
270
|
+
* @param unit - 移动使用的日历单位
|
|
238
271
|
* @param towardFuture - `true` 移动结束边界,`false` 移动开始边界。
|
|
239
272
|
* @returns 每次求值都覆盖完整本地日边界的范围快捷项。
|
|
240
273
|
*/
|
|
@@ -253,7 +286,7 @@ const createRangeShortcut = (text, amount, unit, towardFuture) => ({
|
|
|
253
286
|
* 把日期转换为固定中文相对时间文本。
|
|
254
287
|
*
|
|
255
288
|
* @remarks 10 位以内数字按 Unix 秒处理,其余数字按毫秒处理;月份与年份按本地日历月差计算。
|
|
256
|
-
* @param value - Date
|
|
289
|
+
* @param value - Date、时间戳、可解析字符串或空值
|
|
257
290
|
* @returns 例如“3分钟前”“半年后”;非法或空输入返回空字符串。
|
|
258
291
|
*/
|
|
259
292
|
function formatChineseRelativeTime(value) {
|
|
@@ -302,7 +335,7 @@ function createOneMonthRangeFromToday(towardFuture = false) {
|
|
|
302
335
|
/**
|
|
303
336
|
* 判断日期是否晚于调用时的当前时刻。
|
|
304
337
|
*
|
|
305
|
-
* @param time -
|
|
338
|
+
* @param time - 待比较日期
|
|
306
339
|
* @returns 时间戳严格晚于 `Date.now()` 时返回 `true`。
|
|
307
340
|
*/
|
|
308
341
|
function isDateAfterNow(time) {
|
|
@@ -311,7 +344,7 @@ function isDateAfterNow(time) {
|
|
|
311
344
|
/**
|
|
312
345
|
* 根据浏览器本地小时返回固定中文问候语。
|
|
313
346
|
*
|
|
314
|
-
* @returns
|
|
347
|
+
* @returns 与当前时段对应的中文欢迎文本
|
|
315
348
|
*/
|
|
316
349
|
function getLocalTimeGreeting() {
|
|
317
350
|
const hour = (/* @__PURE__ */ new Date()).getHours();
|
|
@@ -372,7 +405,7 @@ function createDateShortcuts(towardFuture = false) {
|
|
|
372
405
|
/**
|
|
373
406
|
* 返回今天的本地零点。
|
|
374
407
|
*
|
|
375
|
-
* @returns 新建的 `00:00:00.000` Date
|
|
408
|
+
* @returns 新建的 `00:00:00.000` Date
|
|
376
409
|
*/
|
|
377
410
|
function getStartOfToday() {
|
|
378
411
|
return startOfDay(/* @__PURE__ */ new Date());
|
package/dist/date/index.mjs.map
CHANGED
|
@@ -1 +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` 必须是安全整数。\");\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(\"该值不是有效日期。\");\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` 不能晚于 `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: () => {\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":";;;;;;;AAqBA,MAAM,uBAAuB,WAAyB;CACrD,IAAI,CAAC,OAAO,cAAc,MAAM,GAAG,MAAM,IAAI,WAAW,mBAAmB;AAC5E;;;;;;;;;AAUA,SAAgB,OAAO,OAAwB;CAC9C,MAAM,OAAO,iBAAiB,OAAO,IAAI,KAAK,MAAM,QAAQ,CAAC,IAAI,IAAI,KAAK,KAAK;CAC/E,IAAI,CAAC,OAAO,SAAS,KAAK,QAAQ,CAAC,GAClC,MAAM,IAAI,UAAU,WAAW;CAEhC,OAAO;AACR;;;;;;;AAQA,SAAgB,YAAY,OAAoC;CAC/D,IAAI,EAAE,iBAAiB,QAAQ,OAAO,UAAU,YAAY,OAAO,UAAU,WAAW,OAAO;CAC/F,OAAO,OAAO,SAAS,IAAI,KAAK,KAAK,CAAC,CAAC,QAAQ,CAAC;AACjD;;;;;;;;AASA,SAAgB,WAAW,OAAwB;CAClD,MAAM,OAAO,OAAO,KAAK;CACzB,KAAK,SAAS,GAAG,GAAG,GAAG,CAAC;CACxB,OAAO,OAAO,IAAI;AACnB;;;;;;;;AASA,SAAgB,SAAS,OAAwB;CAChD,MAAM,OAAO,OAAO,KAAK;CACzB,KAAK,SAAS,IAAI,IAAI,IAAI,GAAG;CAC7B,OAAO,OAAO,IAAI;AACnB;;;;;;;;;AAUA,SAAgB,QAAQ,OAAkB,QAAsB;CAC/D,oBAAoB,MAAM;CAC1B,MAAM,OAAO,OAAO,KAAK;CACzB,KAAK,QAAQ,KAAK,QAAQ,IAAI,MAAM;CACpC,OAAO,OAAO,IAAI;AACnB;;;;;;;;;;AAWA,SAAgB,UAAU,OAAkB,QAAsB;CACjE,oBAAoB,MAAM;CAC1B,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,cAAc,KAAK,QAAQ;CACjC,KAAK,QAAQ,CAAC;CACd,KAAK,SAAS,KAAK,SAAS,IAAI,MAAM;CACtC,MAAM,iBAAiB,IAAI,KAAK,KAAK,QAAQ,CAAC;CAE9C,eAAe,SAAS,eAAe,SAAS,IAAI,GAAG,CAAC;CACxD,MAAM,UAAU,eAAe,QAAQ;CACvC,KAAK,QAAQ,KAAK,IAAI,aAAa,OAAO,CAAC;CAC3C,OAAO,OAAO,IAAI;AACnB;;;;;;;;;AAUA,SAAgB,SAAS,OAAkB,QAAsB;CAChE,oBAAoB,MAAM;CAC1B,OAAO,UAAU,OAAO,SAAS,EAAE;AACpC;;;;;;;;;AAUA,SAAgB,UAAU,MAAiB,OAA2B;CACrE,MAAM,QAAQ,OAAO,IAAI;CACzB,MAAM,SAAS,OAAO,KAAK;CAC3B,OAAO,MAAM,YAAY,MAAM,OAAO,YAAY,KAAK,MAAM,SAAS,MAAM,OAAO,SAAS,KAAK,MAAM,QAAQ,MAAM,OAAO,QAAQ;AACrI;;;;;;;;;AAUA,SAAgB,SAAS,OAAkB,MAAiB,KAAK,IAAI,GAAY;CAChF,OAAO,OAAO,KAAK,CAAC,CAAC,QAAQ,IAAI,OAAO,GAAG,CAAC,CAAC,QAAQ;AACtD;;;;;;;;AASA,SAAgB,kBAAkB,QAAmB,KAAK,IAAI,GAA6B;CAC1F,OAAO,CAAC,WAAW,KAAK,GAAG,SAAS,KAAK,CAAC;AAC3C;;;;;;;;;;AAWA,SAAgB,iBAAiB,OAAkB,OAAkB,KAAyB;CAC7F,MAAM,YAAY,OAAO,KAAK,CAAC,CAAC,QAAQ;CACxC,MAAM,iBAAiB,OAAO,KAAK,CAAC,CAAC,QAAQ;CAC7C,MAAM,eAAe,OAAO,GAAG,CAAC,CAAC,QAAQ;CACzC,IAAI,iBAAiB,cAAc,MAAM,IAAI,WAAW,qBAAqB;CAC7E,OAAO,aAAa,kBAAkB,aAAa;AACpD;;;;;;;;;;AAWA,SAAgB,mBAAmB,OAAkB,UAA+B,CAAC,GAAW;CAC/F,MAAM,qBAAqB,OAAO,KAAK,CAAC,CAAC,QAAQ,IAAI,OAAO,QAAQ,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,KAAK;CACpG,MAAM,WAAW,KAAK,IAAI,iBAAiB;CAC3C,IAAI;CACJ,IAAI;CACJ,IAAI,WAAW,IAAI;EAClB,UAAU;EACV,OAAO;CACR,OAAO,IAAI,WAAW,MAAO;EAC5B,UAAU;EACV,OAAO;CACR,OAAO,IAAI,WAAW,OAAQ;EAC7B,UAAU;EACV,OAAO;CACR,OAAO,IAAI,WAAW,QAAS;EAC9B,UAAU;EACV,OAAO;CACR,OAAO,IAAI,WAAW,SAAW;EAChC,UAAU;EACV,OAAO;CACR,OAAO,IAAI,WAAW,UAAY;EACjC,UAAU;EACV,OAAO;CACR,OAAO;EACN,UAAU;EACV,OAAO;CACR;CAKA,OAAO,IAJe,KAAK,mBAAmB,QAAQ,UAAU,SAAS;EACxE,SAAS,QAAQ,WAAW;EAC5B,OAAO,QAAQ,SAAS;CACzB,CACe,CAAC,CAAC,OAAO,KAAK,MAAM,oBAAoB,OAAO,GAAG,IAAI;AACtE;;;;;;;;;AAmCA,MAAM,6BAA6B,MAAY,QAAgB,SAA6B;CAC3F,QAAQ,MAAR;EACC,KAAK;GACJ,KAAK,QAAQ,KAAK,QAAQ,IAAI,MAAM;GACpC;EACD,KAAK;GACJ,KAAK,SAAS,KAAK,SAAS,IAAI,MAAM;GACtC;EACD,KAAK,QACJ,KAAK,YAAY,KAAK,YAAY,IAAI,MAAM;CAE9C;AACD;;;;;;;;;AAUA,MAAM,sBAAsB,MAAc,QAAgB,UAAsC;CAC/F;CACA,aAAa;EACZ,MAAM,uBAAO,IAAI,KAAK;EACtB,0BAA0B,MAAM,QAAQ,IAAI;EAC5C,KAAK,SAAS,GAAG,GAAG,GAAG,CAAC;EACxB,OAAO;CACR;AACD;;;;;;;;;;AAWA,MAAM,uBAAuB,MAAc,QAAgB,MAAoB,kBAA8C;CAC5H;CACA,aAA2B;EAC1B,MAAM,wBAAQ,IAAI,KAAK;EACvB,MAAM,sBAAM,IAAI,KAAK;EACrB,0BAA0B,eAAe,MAAM,OAAO,eAAe,SAAS,CAAC,QAAQ,IAAI;EAC3F,MAAM,SAAS,GAAG,GAAG,GAAG,CAAC;EACzB,IAAI,SAAS,IAAI,IAAI,IAAI,GAAG;EAC5B,OAAO,CAAC,OAAO,GAAG;CACnB;AACD;;;;;;;;AASA,SAAgB,0BAA0B,OAA0D;CACnG,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;CAClD,IAAI;CACJ,IAAI,OAAO,UAAU,UAAU,YAAY,IAAI,KAAK,KAAK,CAAC,CAAC,QAAQ;MAC9D,IAAI,OAAO,UAAU,UAAU,YAAY,MAAM,SAAS,CAAC,CAAC,UAAU,KAAK,QAAQ,MAAO;MAC1F,YAAY,MAAM,QAAQ;CAC/B,IAAI,CAAC,OAAO,SAAS,SAAS,GAAG,OAAO;CAExC,MAAM,SAAS;CACf,MAAM,OAAO,SAAS;CACtB,MAAM,MAAM,OAAO;CACnB,MAAM,mBAAmB,KAAK,IAAI;CAClC,MAAM,aAAa,mBAAmB;CACtC,MAAM,mBAAmB,KAAK,IAAI,UAAU,IAAI;CAChD,MAAM,iBAAiB,KAAK,IAAI,UAAU,IAAI;CAC9C,MAAM,gBAAgB,KAAK,IAAI,UAAU,IAAI;CAC7C,MAAM,cAAc,IAAI,KAAK,gBAAgB;CAC7C,MAAM,aAAa,IAAI,KAAK,SAAS;CACrC,MAAM,mBAAmB,YAAY,YAAY,IAAI,WAAW,YAAY,KAAK,KAAK,YAAY,SAAS,IAAI,WAAW,SAAS;CACnI,MAAM,SAAS,aAAa,IAAI,MAAM;CACtC,IAAI,KAAK,IAAI,eAAe,KAAK,IAAI,OAAO,GAAG,KAAK,MAAM,KAAK,IAAI,eAAe,IAAI,EAAE,EAAE,GAAG;CAC7F,IAAI,KAAK,IAAI,eAAe,KAAK,GAAG,OAAO,KAAK;CAChD,IAAI,KAAK,IAAI,eAAe,KAAK,GAAG,OAAO,GAAG,KAAK,IAAI,eAAe,EAAE,GAAG;CAC3E,IAAI,iBAAiB,IAAI,OAAO,KAAK;CACrC,IAAI,iBAAiB,GAAG,OAAO,GAAG,KAAK,MAAM,gBAAgB,CAAC,EAAE,GAAG;CACnE,IAAI,iBAAiB,GAAG,OAAO,GAAG,KAAK,MAAM,aAAa,EAAE,GAAG;CAC/D,IAAI,kBAAkB,GAAG,OAAO,GAAG,KAAK,MAAM,cAAc,EAAE,IAAI;CAClE,IAAI,oBAAoB,GAAG,OAAO,GAAG,KAAK,MAAM,gBAAgB,EAAE,IAAI;CACtE,OAAO;AACR;;;;;;;AAQA,SAAgB,6BAA6B,eAAe,OAAiC;CAC5F,MAAM,wBAAQ,IAAI,KAAK;CACvB,MAAM,sBAAM,IAAI,KAAK;CACrB,0BAA0B,eAAe,MAAM,OAAO,eAAe,IAAI,IAAI,OAAO;CACpF,MAAM,SAAS,GAAG,GAAG,GAAG,CAAC;CACzB,IAAI,SAAS,IAAI,IAAI,IAAI,GAAG;CAC5B,OAAO,CAAC,OAAO,GAAG;AACnB;;;;;;;AAQA,SAAgB,eAAe,MAAqB;CACnD,OAAO,KAAK,QAAQ,IAAI,KAAK,IAAI;AAClC;;;;;;AAOA,SAAgB,uBAA+B;CAC9C,MAAM,wBAAO,IAAI,KAAK,EAAA,CAAE,SAAS;CACjC,IAAI,OAAO,GAAG,OAAO;CACrB,IAAI,OAAO,GAAG,OAAO;CACrB,IAAI,OAAO,IAAI,OAAO;CACtB,IAAI,OAAO,IAAI,OAAO;CACtB,IAAI,OAAO,IAAI,OAAO;CACtB,IAAI,OAAO,IAAI,OAAO;CACtB,OAAO;AACR;;;;;;;AAQA,SAAgB,yBAAyB,eAAe,OAA4B;CACnF,OAAO,eACJ;EACA,oBAAoB,OAAO,GAAG,OAAO,IAAI;EACzC,oBAAoB,OAAO,GAAG,OAAO,IAAI;EACzC,oBAAoB,OAAO,GAAG,OAAO,IAAI;EACzC,oBAAoB,OAAO,GAAG,SAAS,IAAI;EAC3C,oBAAoB,OAAO,GAAG,SAAS,IAAI;EAC3C,oBAAoB,OAAO,GAAG,SAAS,IAAI;EAC3C,oBAAoB,OAAO,GAAG,QAAQ,IAAI;CAC3C,IACC;EACA,oBAAoB,OAAO,GAAG,OAAO,KAAK;EAC1C,oBAAoB,OAAO,GAAG,OAAO,KAAK;EAC1C,oBAAoB,OAAO,GAAG,OAAO,KAAK;EAC1C,oBAAoB,OAAO,GAAG,SAAS,KAAK;EAC5C,oBAAoB,OAAO,GAAG,SAAS,KAAK;EAC5C,oBAAoB,OAAO,GAAG,SAAS,KAAK;EAC5C,oBAAoB,OAAO,GAAG,QAAQ,KAAK;CAC5C;AACH;;;;;;;AAQA,SAAgB,oBAAoB,eAAe,OAAuB;CACzE,OAAO,eACJ;EACA,mBAAmB,MAAM,GAAG,KAAK;EACjC,mBAAmB,MAAM,GAAG,KAAK;EACjC,mBAAmB,OAAO,GAAG,KAAK;EAClC,mBAAmB,OAAO,GAAG,OAAO;EACpC,mBAAmB,OAAO,GAAG,MAAM;CACpC,IACC;EACA,mBAAmB,MAAM,GAAG,KAAK;EACjC,mBAAmB,MAAM,IAAI,KAAK;EAClC,mBAAmB,OAAO,IAAI,KAAK;EACnC,mBAAmB,OAAO,IAAI,OAAO;EACrC,mBAAmB,OAAO,IAAI,MAAM;CACrC;AACH;;;;;;AAOA,SAAgB,kBAAwB;CACvC,OAAO,2BAAW,IAAI,KAAK,CAAC;AAC7B"}
|
|
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;省略时内部输出简体中文。 */\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` must not 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 相对时间文本\n * @throws 日期无效时抛出 `TypeError`;Locale 或 Intl 选项非法时抛出 `RangeError`。\n * 显式指定语言且缺少 Intl.RelativeTimeFormat 时抛出 `Error`。\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 amount = Math.round(differenceSeconds / divisor);\n\tif (options.locale === undefined) {\n\t\tconst numeric = options.numeric ?? \"auto\";\n\t\tconst style = options.style ?? \"long\";\n\t\tif (numeric !== \"auto\" && numeric !== \"always\") throw new RangeError(\"Relative time numeric must be auto or always.\");\n\t\tif (style !== \"long\" && style !== \"short\" && style !== \"narrow\") throw new RangeError(\"Relative time style must be long, short, or narrow.\");\n\t\tif (numeric === \"auto\") {\n\t\t\tif (unit === \"second\" && amount === 0) return \"现在\";\n\t\t\tif (unit === \"day\") {\n\t\t\t\tif (amount === -2) return \"前天\";\n\t\t\t\tif (amount === -1) return \"昨天\";\n\t\t\t\tif (amount === 1) return \"明天\";\n\t\t\t\tif (amount === 2) return \"后天\";\n\t\t\t}\n\t\t\tif (amount === -1 || amount === 1) {\n\t\t\t\tif (unit === \"week\") return amount < 0 ? \"上周\" : \"下周\";\n\t\t\t\tif (unit === \"month\") return amount < 0 ? \"上个月\" : \"下个月\";\n\t\t\t\tif (unit === \"year\") return amount < 0 ? \"去年\" : \"明年\";\n\t\t\t}\n\t\t}\n\t\tconst labels = { second: style === \"long\" ? \"秒钟\" : \"秒\", minute: \"分钟\", hour: \"小时\", day: \"天\", week: \"周\", month: \"个月\", year: \"年\" };\n\t\tconst count = String(Math.abs(amount)).replace(/\\B(?=(?:\\d{3})+(?!\\d))/gu, \",\");\n\t\treturn count + labels[unit] + (amount < 0 || Object.is(amount, -0) ? \"前\" : \"后\");\n\t}\n\tif (typeof Intl === \"undefined\" || typeof Intl.RelativeTimeFormat !== \"function\") {\n\t\tthrow new Error(\"The current runtime does not support Intl.RelativeTimeFormat.\");\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(amount, 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: () => {\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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"}
|
package/dist/dom/style.mjs
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* 判断 StyleInput 是否为递归样式数组。
|
|
4
4
|
*
|
|
5
|
-
* @param value -
|
|
5
|
+
* @param value - 待缩小的样式输入
|
|
6
6
|
* @returns 是只读样式数组时返回 `true`。
|
|
7
7
|
*/
|
|
8
8
|
const isStyleArray = (value) => Array.isArray(value);
|
|
9
9
|
/**
|
|
10
10
|
* 判断文本是否可按十进制数值追加 CSS 单位。
|
|
11
11
|
*
|
|
12
|
-
* @param value -
|
|
12
|
+
* @param value - 已去除外围空白的文本
|
|
13
13
|
* @returns 有限十进制数值返回 `true`;二、八、十六进制前缀返回 `false`。
|
|
14
14
|
*/
|
|
15
15
|
const isNumericString = (value) => {
|
|
@@ -31,15 +31,15 @@ const toCssPropertyName = (key) => {
|
|
|
31
31
|
* 为数值或纯数字字符串添加 CSS 单位。
|
|
32
32
|
*
|
|
33
33
|
* @param value - 数字、数字字符串或已有单位的 CSS 值;空值返回空字符串。
|
|
34
|
-
* @param unit - 非零数字使用的单位,默认 `px
|
|
34
|
+
* @param unit - 非零数字使用的单位,默认 `px`
|
|
35
35
|
* @returns 零统一返回 `"0"`;非数字字符串保持原样。
|
|
36
36
|
* @throws `RangeError` 当数字非有限或单位为空。
|
|
37
37
|
*/
|
|
38
38
|
function addCssUnit(value, unit = "px") {
|
|
39
39
|
if (value === null || value === void 0 || value === "") return "";
|
|
40
|
-
if (unit.length === 0) throw new RangeError("`unit`
|
|
40
|
+
if (unit.length === 0) throw new RangeError("`unit` must not be empty.");
|
|
41
41
|
if (typeof value === "number") {
|
|
42
|
-
if (!Number.isFinite(value)) throw new RangeError("`value`
|
|
42
|
+
if (!Number.isFinite(value)) throw new RangeError("`value` must be a finite number.");
|
|
43
43
|
return value === 0 ? "0" : `${value}${unit}`;
|
|
44
44
|
}
|
|
45
45
|
const trimmed = value.trim();
|
|
@@ -53,7 +53,7 @@ function addCssUnit(value, unit = "px") {
|
|
|
53
53
|
* 实际渲染上下文验证,尤其不能允许用户控制属性名、`url()` 或自定义属性内容。
|
|
54
54
|
* 数字不会自动附加单位;需要长度单位时应先调用 {@link addCssUnit}。
|
|
55
55
|
* @param styles - 可嵌套样式输入;后出现的声明由 CSS 层叠规则覆盖先前声明。
|
|
56
|
-
* @returns 以分号结束、以空格分隔的 CSS
|
|
56
|
+
* @returns 以分号结束、以空格分隔的 CSS 声明字符串
|
|
57
57
|
* @throws `RangeError` 当对象中包含 `NaN` 或无穷数字。
|
|
58
58
|
*/
|
|
59
59
|
function serializeStyle(styles) {
|
|
@@ -64,7 +64,7 @@ function serializeStyle(styles) {
|
|
|
64
64
|
return value.length === 0 ? "" : value.endsWith(";") ? value : `${value};`;
|
|
65
65
|
}
|
|
66
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(
|
|
67
|
+
if (typeof value === "number" && !Number.isFinite(value)) throw new RangeError(`Style property "${key}" must be a finite number.`);
|
|
68
68
|
return `${toCssPropertyName(key)}:${String(value)};`;
|
|
69
69
|
}).join(" ");
|
|
70
70
|
}
|
package/dist/dom/style.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"style.mjs","names":[],"sources":["../../src/dom/style.ts"],"sourcesContent":["/** 可序列化为内联 CSS
|
|
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` must not be empty.\");\n\tif (typeof value === \"number\") {\n\t\tif (!Number.isFinite(value)) throw new RangeError(\"`value` must be a finite number.\");\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 a finite number.`);\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,2BAA2B;CACvE,IAAI,OAAO,UAAU,UAAU;EAC9B,IAAI,CAAC,OAAO,SAAS,KAAK,GAAG,MAAM,IAAI,WAAW,kCAAkC;EACpF,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,2BAA2B;EACjI,OAAO,GAAG,kBAAkB,GAAG,EAAE,GAAG,OAAO,KAAK,EAAE;CACnD,CAAC,CAAC,CACD,KAAK,GAAG;AACX"}
|
package/dist/env/index.mjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getRuntimeUni, runtimeGlobals } from "../internal/runtime.mjs";
|
|
2
1
|
//#region src/env/index.ts
|
|
3
2
|
/**
|
|
4
3
|
* 延迟读取当前 User-Agent。
|
|
@@ -6,7 +5,7 @@ import { getRuntimeUni, runtimeGlobals } from "../internal/runtime.mjs";
|
|
|
6
5
|
* @returns Navigator 不存在或字段类型异常时返回空字符串。
|
|
7
6
|
*/
|
|
8
7
|
const currentUserAgent = () => {
|
|
9
|
-
const userAgent =
|
|
8
|
+
const userAgent = typeof navigator === "undefined" ? void 0 : navigator.userAgent;
|
|
10
9
|
return typeof userAgent === "string" ? userAgent : "";
|
|
11
10
|
};
|
|
12
11
|
/**
|
|
@@ -15,7 +14,7 @@ const currentUserAgent = () => {
|
|
|
15
14
|
* @returns Navigator 不存在或字段类型异常时返回 `0`。
|
|
16
15
|
*/
|
|
17
16
|
const currentTouchPoints = () => {
|
|
18
|
-
const maxTouchPoints =
|
|
17
|
+
const maxTouchPoints = typeof navigator === "undefined" ? void 0 : navigator.maxTouchPoints;
|
|
19
18
|
return typeof maxTouchPoints === "number" ? maxTouchPoints : 0;
|
|
20
19
|
};
|
|
21
20
|
/**
|
|
@@ -24,7 +23,7 @@ const currentTouchPoints = () => {
|
|
|
24
23
|
* @returns 两项能力均存在时返回 `true`;不读取 DOM 内容。
|
|
25
24
|
*/
|
|
26
25
|
function isBrowser() {
|
|
27
|
-
return
|
|
26
|
+
return typeof window !== "undefined" && window.document !== void 0;
|
|
28
27
|
}
|
|
29
28
|
/**
|
|
30
29
|
* 判断当前运行时是否像 Web Worker 且不是 Window。
|
|
@@ -34,7 +33,7 @@ function isBrowser() {
|
|
|
34
33
|
* @returns 具有 `importScripts` 且不是浏览器 Window 时返回 `true`。
|
|
35
34
|
*/
|
|
36
35
|
function isWebWorker() {
|
|
37
|
-
return !isBrowser() && typeof
|
|
36
|
+
return !isBrowser() && typeof importScripts === "function";
|
|
38
37
|
}
|
|
39
38
|
/**
|
|
40
39
|
* 判断当前运行时是否暴露 Node.js 版本标记。
|
|
@@ -42,19 +41,17 @@ function isWebWorker() {
|
|
|
42
41
|
* @returns `process.versions.node` 为字符串时返回 `true`。
|
|
43
42
|
*/
|
|
44
43
|
function isNode() {
|
|
45
|
-
const process = runtimeGlobals.process;
|
|
46
44
|
if (typeof process !== "object" && typeof process !== "function" || process === null) return false;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return typeof versions.node === "string";
|
|
45
|
+
if (typeof process.versions !== "object" && typeof process.versions !== "function" || process.versions === null) return false;
|
|
46
|
+
return typeof process.versions.node === "string";
|
|
50
47
|
}
|
|
51
48
|
/**
|
|
52
49
|
* 判断当前运行时是否暴露 uni-app 的 `uni` 运行时对象。
|
|
53
50
|
*
|
|
54
|
-
* @returns `uni`
|
|
51
|
+
* @returns `uni` 标识符存在时返回 `true`;不调用任何平台 API。
|
|
55
52
|
*/
|
|
56
53
|
function isUniApp() {
|
|
57
|
-
return
|
|
54
|
+
return typeof uni !== "undefined";
|
|
58
55
|
}
|
|
59
56
|
/**
|
|
60
57
|
* 判断当前运行时是否具备本库完整加密 API 所需的 Web Crypto 能力。
|
|
@@ -64,9 +61,7 @@ function isUniApp() {
|
|
|
64
61
|
* @returns 同时提供本库 Web Crypto 功能所需方法时返回 `true`。
|
|
65
62
|
*/
|
|
66
63
|
function hasWebCrypto() {
|
|
67
|
-
|
|
68
|
-
const subtle = crypto?.subtle;
|
|
69
|
-
return typeof crypto?.getRandomValues === "function" && typeof subtle?.decrypt === "function" && typeof subtle.deriveBits === "function" && typeof subtle.deriveKey === "function" && typeof subtle.digest === "function" && typeof subtle.encrypt === "function" && typeof subtle.exportKey === "function" && typeof subtle.generateKey === "function" && typeof subtle.importKey === "function" && typeof subtle.sign === "function" && typeof subtle.verify === "function";
|
|
64
|
+
return typeof crypto !== "undefined" && typeof crypto?.getRandomValues === "function" && typeof crypto.subtle?.decrypt === "function" && typeof crypto.subtle.deriveBits === "function" && typeof crypto.subtle.deriveKey === "function" && typeof crypto.subtle.digest === "function" && typeof crypto.subtle.encrypt === "function" && typeof crypto.subtle.exportKey === "function" && typeof crypto.subtle.generateKey === "function" && typeof crypto.subtle.importKey === "function" && typeof crypto.subtle.sign === "function" && typeof crypto.subtle.verify === "function";
|
|
70
65
|
}
|
|
71
66
|
/**
|
|
72
67
|
* 返回当前主要运行环境。
|
package/dist/env/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/env/index.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/env/index.ts"],"sourcesContent":["/** 可识别的主要 JavaScript 运行环境 */\nexport type RuntimeKind = \"browser\" | \"node\" | \"unknown\" | \"worker\";\n\n/**\n * 延迟读取当前 User-Agent。\n *\n * @returns Navigator 不存在或字段类型异常时返回空字符串。\n */\nconst currentUserAgent = (): string => {\n\tconst userAgent: unknown = typeof navigator === \"undefined\" ? undefined : navigator.userAgent;\n\treturn typeof userAgent === \"string\" ? userAgent : \"\";\n};\n/**\n * 延迟读取当前设备报告的最大触点数。\n *\n * @returns Navigator 不存在或字段类型异常时返回 `0`。\n */\nconst currentTouchPoints = (): number => {\n\tconst maxTouchPoints: unknown = typeof navigator === \"undefined\" ? undefined : navigator.maxTouchPoints;\n\treturn typeof maxTouchPoints === \"number\" ? maxTouchPoints : 0;\n};\n\n/**\n * 判断当前运行时是否具有浏览器 `window` 与 `document`。\n *\n * @returns 两项能力均存在时返回 `true`;不读取 DOM 内容。\n */\nexport function isBrowser(): boolean {\n\treturn typeof window !== \"undefined\" && window.document !== undefined;\n}\n\n/**\n * 判断当前运行时是否像 Web Worker 且不是 Window。\n *\n * @remarks 经典、模块、Shared 与 Service Worker 全局通常都暴露 `importScripts`;模块\n * Worker 中调用它可能抛错,本检测只检查能力存在,不会执行。\n * @returns 具有 `importScripts` 且不是浏览器 Window 时返回 `true`。\n */\nexport function isWebWorker(): boolean {\n\treturn !isBrowser() && typeof importScripts === \"function\";\n}\n\n/**\n * 判断当前运行时是否暴露 Node.js 版本标记。\n *\n * @returns `process.versions.node` 为字符串时返回 `true`。\n */\nexport function isNode(): boolean {\n\tif ((typeof process !== \"object\" && typeof process !== \"function\") || process === null) return false;\n\tif ((typeof process.versions !== \"object\" && typeof process.versions !== \"function\") || process.versions === null) return false;\n\treturn typeof process.versions.node === \"string\";\n}\n\n/**\n * 判断当前运行时是否暴露 uni-app 的 `uni` 运行时对象。\n *\n * @returns `uni` 标识符存在时返回 `true`;不调用任何平台 API。\n */\nexport function isUniApp(): boolean {\n\treturn typeof uni !== \"undefined\";\n}\n\n/**\n * 判断当前运行时是否具备本库完整加密 API 所需的 Web Crypto 能力。\n *\n * @remarks 普通随机数、随机字符串和 UUID 在缺少 Web Crypto 时可以回退到 `Math.random()`,但本函数\n * 仍会返回 `false`,因为摘要、PBKDF2、AES-GCM、RSA 与 ECC 需要完整的 Web Crypto 能力。\n * @returns 同时提供本库 Web Crypto 功能所需方法时返回 `true`。\n */\nexport function hasWebCrypto(): boolean {\n\treturn (\n\t\ttypeof crypto !== \"undefined\" &&\n\t\ttypeof crypto?.getRandomValues === \"function\" &&\n\t\ttypeof crypto.subtle?.decrypt === \"function\" &&\n\t\ttypeof crypto.subtle.deriveBits === \"function\" &&\n\t\ttypeof crypto.subtle.deriveKey === \"function\" &&\n\t\ttypeof crypto.subtle.digest === \"function\" &&\n\t\ttypeof crypto.subtle.encrypt === \"function\" &&\n\t\ttypeof crypto.subtle.exportKey === \"function\" &&\n\t\ttypeof crypto.subtle.generateKey === \"function\" &&\n\t\ttypeof crypto.subtle.importKey === \"function\" &&\n\t\ttypeof crypto.subtle.sign === \"function\" &&\n\t\ttypeof crypto.subtle.verify === \"function\"\n\t);\n}\n\n/**\n * 返回当前主要运行环境。\n *\n * @remarks 在使用 DOM 模拟器的 Node.js 进程中优先报告 `browser`,因为可观察能力比宿主进程名称更有用。\n * @returns `browser`、`worker`、`node` 或无法识别时的 `unknown`。\n */\nexport function detectRuntime(): RuntimeKind {\n\tif (isBrowser()) return \"browser\";\n\tif (isWebWorker()) return \"worker\";\n\tif (isNode()) return \"node\";\n\treturn \"unknown\";\n}\n\n/**\n * 基于 User-Agent 启发式判断手机设备。\n *\n * @param userAgent - 默认读取当前 `navigator.userAgent`;平台对象不存在时使用空字符串。\n * @remarks User-Agent 可以被伪造,不得用于鉴权、安全策略或永久功能分流。\n * @returns 命中手机特征时返回 `true`。\n */\nexport function isMobileUserAgent(userAgent: string = currentUserAgent()): boolean {\n\treturn /Mobile|iPhone|Android.*Mobile|Windows Phone/iu.test(userAgent);\n}\n\n/**\n * 基于 User-Agent 与触点数量启发式判断平板设备。\n *\n * @param userAgent - 默认读取当前 User-Agent。\n * @param maxTouchPoints - 用于识别桌面 User-Agent 模式下的 iPadOS,默认读取当前触点数。\n * @returns 命中平板特征时返回 `true`。\n */\nexport function isTabletUserAgent(userAgent: string = currentUserAgent(), maxTouchPoints: number = currentTouchPoints()): boolean {\n\treturn /iPad|Android(?!.*Mobile)|Tablet/iu.test(userAgent) || (/Macintosh/iu.test(userAgent) && maxTouchPoints > 1);\n}\n"],"mappings":";;;;;;AAQA,MAAM,yBAAiC;CACtC,MAAM,YAAqB,OAAO,cAAc,cAAc,KAAA,IAAY,UAAU;CACpF,OAAO,OAAO,cAAc,WAAW,YAAY;AACpD;;;;;;AAMA,MAAM,2BAAmC;CACxC,MAAM,iBAA0B,OAAO,cAAc,cAAc,KAAA,IAAY,UAAU;CACzF,OAAO,OAAO,mBAAmB,WAAW,iBAAiB;AAC9D;;;;;;AAOA,SAAgB,YAAqB;CACpC,OAAO,OAAO,WAAW,eAAe,OAAO,aAAa,KAAA;AAC7D;;;;;;;;AASA,SAAgB,cAAuB;CACtC,OAAO,CAAC,UAAU,KAAK,OAAO,kBAAkB;AACjD;;;;;;AAOA,SAAgB,SAAkB;CACjC,IAAK,OAAO,YAAY,YAAY,OAAO,YAAY,cAAe,YAAY,MAAM,OAAO;CAC/F,IAAK,OAAO,QAAQ,aAAa,YAAY,OAAO,QAAQ,aAAa,cAAe,QAAQ,aAAa,MAAM,OAAO;CAC1H,OAAO,OAAO,QAAQ,SAAS,SAAS;AACzC;;;;;;AAOA,SAAgB,WAAoB;CACnC,OAAO,OAAO,QAAQ;AACvB;;;;;;;;AASA,SAAgB,eAAwB;CACvC,OACC,OAAO,WAAW,eAClB,OAAO,QAAQ,oBAAoB,cACnC,OAAO,OAAO,QAAQ,YAAY,cAClC,OAAO,OAAO,OAAO,eAAe,cACpC,OAAO,OAAO,OAAO,cAAc,cACnC,OAAO,OAAO,OAAO,WAAW,cAChC,OAAO,OAAO,OAAO,YAAY,cACjC,OAAO,OAAO,OAAO,cAAc,cACnC,OAAO,OAAO,OAAO,gBAAgB,cACrC,OAAO,OAAO,OAAO,cAAc,cACnC,OAAO,OAAO,OAAO,SAAS,cAC9B,OAAO,OAAO,OAAO,WAAW;AAElC;;;;;;;AAQA,SAAgB,gBAA6B;CAC5C,IAAI,UAAU,GAAG,OAAO;CACxB,IAAI,YAAY,GAAG,OAAO;CAC1B,IAAI,OAAO,GAAG,OAAO;CACrB,OAAO;AACR;;;;;;;;AASA,SAAgB,kBAAkB,YAAoB,iBAAiB,GAAY;CAClF,OAAO,gDAAgD,KAAK,SAAS;AACtE;;;;;;;;AASA,SAAgB,kBAAkB,YAAoB,iBAAiB,GAAG,iBAAyB,mBAAmB,GAAY;CACjI,OAAO,oCAAoC,KAAK,SAAS,KAAM,cAAc,KAAK,SAAS,KAAK,iBAAiB;AAClH"}
|
package/dist/function/index.mjs
CHANGED
|
@@ -5,18 +5,18 @@
|
|
|
5
5
|
* @remarks 首次成功返回后,后续调用返回同一结果;Promise 会保持引用不变。首次同步抛错时缓存错误,后续调用重新抛出同一错误。
|
|
6
6
|
* 包装函数使用首次调用时的参数和 `this`,之后传入的参数不会再次执行原函数。
|
|
7
7
|
* 首次调用尚未返回时同步重入会抛出 `Error`,不会重复执行原函数;该错误如向外传播,会作为首次错误缓存。
|
|
8
|
-
* @param callback -
|
|
9
|
-
* @returns
|
|
8
|
+
* @param callback - 只允许执行一次的函数
|
|
9
|
+
* @returns 保持原参数与返回类型的包装函数
|
|
10
10
|
* @throws `TypeError` 当 `callback` 不是函数。
|
|
11
11
|
*/
|
|
12
12
|
function once(callback) {
|
|
13
|
-
if (typeof callback !== "function") throw new TypeError("`callback`
|
|
13
|
+
if (typeof callback !== "function") throw new TypeError("`callback` must be a function.");
|
|
14
14
|
let state = { status: "pending" };
|
|
15
15
|
return function(...arguments_) {
|
|
16
16
|
switch (state.status) {
|
|
17
17
|
case "returned": return state.value;
|
|
18
18
|
case "threw": throw state.error;
|
|
19
|
-
case "running": throw new Error("`once`
|
|
19
|
+
case "running": throw new Error("The `once` callback has not returned and cannot be reentered synchronously.");
|
|
20
20
|
case "pending":
|
|
21
21
|
state = { status: "running" };
|
|
22
22
|
try {
|