clxx 2.1.4 → 2.1.5
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/README.md +2 -2
- package/build/Alert/style.js +1 -1
- package/build/AutoGrid/index.js +9 -2
- package/build/Clickable/index.js +12 -22
- package/build/Container/index.js +53 -66
- package/build/Dialog/style.js +2 -2
- package/build/Overlay/index.js +2 -11
- package/build/ScrollView/index.js +65 -47
- package/build/Toast/index.js +5 -3
- package/build/utils/Countdown.js +11 -4
- package/build/utils/ago.js +39 -52
- package/build/utils/createApp.js +23 -8
- package/build/utils/defaultScroll.d.ts +4 -4
- package/build/utils/defaultScroll.js +6 -15
- package/build/utils/dom.js +5 -8
- package/build/utils/jsonp.js +7 -4
- package/build/utils/request.js +23 -33
- package/build/utils/tick.js +7 -14
- package/build/utils/uniqKey.js +7 -7
- package/build/utils/wait.js +1 -1
- package/package.json +10 -10
- package/test/eslint.config.js +5 -2
- package/test/package.json +11 -11
- package/test/src/ago/index.jsx +3 -1
- package/test/src/index.jsx +3 -2
package/README.md
CHANGED
package/build/Alert/style.js
CHANGED
package/build/AutoGrid/index.js
CHANGED
|
@@ -27,15 +27,22 @@ export function AutoGrid(props) {
|
|
|
27
27
|
// 生成一个能创建表格的二维数组
|
|
28
28
|
let list = [];
|
|
29
29
|
React.Children.forEach(children, (child) => {
|
|
30
|
-
if (child !== null) {
|
|
30
|
+
if (child !== null && child !== undefined) {
|
|
31
31
|
if (list.length === 0 || list[list.length - 1].length >= cols) {
|
|
32
32
|
list.push([]);
|
|
33
33
|
}
|
|
34
34
|
list[list.length - 1].push(child);
|
|
35
35
|
}
|
|
36
36
|
});
|
|
37
|
+
// 用空占位符补齐最后一行,避免最后一行元素宽度不一致
|
|
38
|
+
if (list.length > 0) {
|
|
39
|
+
const lastRow = list[list.length - 1];
|
|
40
|
+
while (lastRow.length < cols) {
|
|
41
|
+
lastRow.push(_jsx("div", { style: { visibility: 'hidden' } }, `placeholder-${lastRow.length}`));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
37
44
|
return list;
|
|
38
|
-
}, [children]);
|
|
45
|
+
}, [children, cols]);
|
|
39
46
|
// 元素的最终样式
|
|
40
47
|
const finalItemBoxStyle = [
|
|
41
48
|
style.itemBoxStyle,
|
package/build/Clickable/index.js
CHANGED
|
@@ -24,13 +24,7 @@ export function Clickable(props) {
|
|
|
24
24
|
}, [activeClassName, activeStyle]);
|
|
25
25
|
const finalActiveStyle = defaultActiveStyle || activeStyle;
|
|
26
26
|
const touchable = is('touchable');
|
|
27
|
-
const [
|
|
28
|
-
const [boxStyle, setBoxStyle] = useState(style);
|
|
29
|
-
// 监控属性的更新
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
setBoxClass(className);
|
|
32
|
-
setBoxStyle(style);
|
|
33
|
-
}, [className, style]);
|
|
27
|
+
const [isActive, setIsActive] = useState(false);
|
|
34
28
|
// 标记是否正处于触摸状态
|
|
35
29
|
const touchRef = useRef(false);
|
|
36
30
|
const onStart = (event) => {
|
|
@@ -40,26 +34,15 @@ export function Clickable(props) {
|
|
|
40
34
|
if (!bubble) {
|
|
41
35
|
event.stopPropagation();
|
|
42
36
|
}
|
|
43
|
-
|
|
44
|
-
if (typeof activeClassName === 'string') {
|
|
45
|
-
setBoxClass(typeof boxClass === 'string'
|
|
46
|
-
? `${boxClass} ${activeClassName}`
|
|
47
|
-
: activeClassName);
|
|
48
|
-
}
|
|
49
|
-
if (typeof finalActiveStyle === 'object') {
|
|
50
|
-
setBoxStyle(typeof boxStyle === 'object'
|
|
51
|
-
? Object.assign(Object.assign({}, boxStyle), finalActiveStyle) : finalActiveStyle);
|
|
52
|
-
}
|
|
37
|
+
setIsActive(true);
|
|
53
38
|
}
|
|
54
39
|
};
|
|
55
|
-
// onEnd返回记忆的版本,防止下一个effect中无意义重复执行
|
|
56
40
|
const onEnd = useCallback(() => {
|
|
57
41
|
if (touchRef.current) {
|
|
58
42
|
touchRef.current = false;
|
|
59
|
-
|
|
60
|
-
setBoxStyle(style);
|
|
43
|
+
setIsActive(false);
|
|
61
44
|
}
|
|
62
|
-
}, [
|
|
45
|
+
}, []);
|
|
63
46
|
// PC环境释放逻辑
|
|
64
47
|
useEffect(() => {
|
|
65
48
|
if (!disable && !touchable) {
|
|
@@ -70,7 +53,14 @@ export function Clickable(props) {
|
|
|
70
53
|
};
|
|
71
54
|
}
|
|
72
55
|
}, [disable, touchable, onEnd]);
|
|
73
|
-
|
|
56
|
+
// 根据激活状态计算最终的 className 和 style
|
|
57
|
+
const finalClassName = isActive && typeof activeClassName === 'string'
|
|
58
|
+
? (typeof className === 'string' ? `${className} ${activeClassName}` : activeClassName)
|
|
59
|
+
: className;
|
|
60
|
+
const finalStyle = isActive && typeof finalActiveStyle === 'object'
|
|
61
|
+
? (typeof style === 'object' ? Object.assign(Object.assign({}, style), finalActiveStyle) : finalActiveStyle)
|
|
62
|
+
: style;
|
|
63
|
+
const fullAttrs = Object.assign(Object.assign({}, attrs), { className: finalClassName, style: finalStyle });
|
|
74
64
|
// 非禁用状态有点击态行为
|
|
75
65
|
if (!disable) {
|
|
76
66
|
if (touchable) {
|
package/build/Container/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
|
|
2
2
|
import { Global } from "@emotion/react";
|
|
3
|
-
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
|
3
|
+
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useState, } from "react";
|
|
4
4
|
import { getContextValue } from "../context";
|
|
5
5
|
import { useWindowResize } from "../Effect/useWindowResize";
|
|
6
6
|
import { useViewport } from "../Effect/useViewport";
|
|
@@ -13,85 +13,72 @@ export function Container(props) {
|
|
|
13
13
|
const { minDocWidth, maxDocWidth } = getContextValue();
|
|
14
14
|
// 获取环境变量
|
|
15
15
|
const { designWidth = 750, globalStyle, children } = props;
|
|
16
|
-
//
|
|
16
|
+
// 计算理论根字体大小(未经浏览器缩放修正)
|
|
17
17
|
const calculateFontSize = useCallback((width) => {
|
|
18
|
-
|
|
19
|
-
if (width >= maxDocWidth) {
|
|
20
|
-
targetWidth = maxDocWidth;
|
|
21
|
-
}
|
|
22
|
-
else if (width <= minDocWidth) {
|
|
23
|
-
targetWidth = minDocWidth;
|
|
24
|
-
}
|
|
18
|
+
const targetWidth = Math.min(Math.max(width, minDocWidth), maxDocWidth);
|
|
25
19
|
return (targetWidth * 100) / designWidth;
|
|
26
20
|
}, [designWidth, minDocWidth, maxDocWidth]);
|
|
27
|
-
//
|
|
28
|
-
const [
|
|
29
|
-
//
|
|
21
|
+
// 理论基准字体大小(跟随窗口尺寸变化)
|
|
22
|
+
const [rawFontSize, setRawFontSize] = useState(() => calculateFontSize(window.innerWidth));
|
|
23
|
+
// 浏览器字体缩放因子(>1 表示用户放大了系统字体,<1 表示缩小)
|
|
24
|
+
// 独立存储,使得 resize 后缩放修正依然生效
|
|
25
|
+
const [scaleFactor, setScaleFactor] = useState(1);
|
|
26
|
+
// 是否已完成字体缩放检测
|
|
30
27
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
// 修正后的字体大小:统一对所有字体计算应用缩放修正
|
|
29
|
+
const correctedFontSize = useMemo(() => scaleFactor === 1
|
|
30
|
+
? rawFontSize
|
|
31
|
+
: Math.round((rawFontSize / scaleFactor) * 10) / 10, [rawFontSize, scaleFactor]);
|
|
32
|
+
// 字体缩放检测
|
|
33
|
+
// Emotion 的 <Global> 通过 useInsertionEffect 注入样式,早于 useLayoutEffect
|
|
34
|
+
// 因此 useLayoutEffect 内 getComputedStyle 可正确读取已注入的字体大小
|
|
35
|
+
// 检测和修正均在浏览器绘制前同步完成,避免闪烁
|
|
36
|
+
useLayoutEffect(() => {
|
|
37
|
+
// 缩放因子在页面生命周期内不变,只需检测一次
|
|
35
38
|
if (isInitialized)
|
|
36
39
|
return;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// 通过反向缩放修正字体大小
|
|
49
|
-
// 例如:期望 50px,实际 60px(1.2倍),则设置 50/1.2 ≈ 41.67px
|
|
50
|
-
const correctedSize = Math.round((baseFontSize / scaleFactor) * 10) / 10;
|
|
51
|
-
// 只修正一次,然后标记为已初始化
|
|
52
|
-
setBaseFontSize(correctedSize);
|
|
53
|
-
setIsInitialized(true);
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
// 字体大小正确,直接标记为已初始化
|
|
57
|
-
setIsInitialized(true);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
}, [baseFontSize, isInitialized]);
|
|
61
|
-
// 页面大小变化时,基准字体同步变化
|
|
62
|
-
// 使用 requestAnimationFrame 批量处理,避免频繁更新
|
|
40
|
+
const computedSize = parseFloat(window.getComputedStyle(document.documentElement).fontSize);
|
|
41
|
+
// 如果计算出的字体大小与期望不符(说明被浏览器字体设置影响了)
|
|
42
|
+
// 容差 1px,避免浮点精度导致误判
|
|
43
|
+
if (computedSize > 0 && Math.abs(computedSize - rawFontSize) > 1) {
|
|
44
|
+
// 记录缩放因子,后续所有字体计算(包括 resize)都会自动应用
|
|
45
|
+
setScaleFactor(computedSize / rawFontSize);
|
|
46
|
+
}
|
|
47
|
+
setIsInitialized(true);
|
|
48
|
+
}, [rawFontSize, isInitialized]);
|
|
49
|
+
// 窗口大小变化时更新理论字体大小
|
|
50
|
+
// correctedFontSize 通过 useMemo 自动应用 scaleFactor 修正
|
|
63
51
|
useWindowResize(() => {
|
|
64
|
-
|
|
65
|
-
const newFontSize = calculateFontSize(window.innerWidth);
|
|
66
|
-
if (newFontSize !== baseFontSize) {
|
|
67
|
-
setBaseFontSize(newFontSize);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
52
|
+
setRawFontSize(calculateFontSize(window.innerWidth));
|
|
70
53
|
});
|
|
71
|
-
// 设置
|
|
54
|
+
// 设置 viewport meta
|
|
72
55
|
useViewport();
|
|
73
|
-
//
|
|
56
|
+
// 激活 iOS 上的 :active 伪类
|
|
74
57
|
useEffect(() => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
document.body.addEventListener("touchstart", activable, { passive: true });
|
|
58
|
+
const noop = () => { };
|
|
59
|
+
document.body.addEventListener("touchstart", noop, { passive: true });
|
|
78
60
|
return () => {
|
|
79
|
-
document.body.removeEventListener("touchstart",
|
|
61
|
+
document.body.removeEventListener("touchstart", noop);
|
|
80
62
|
};
|
|
81
63
|
}, []);
|
|
82
|
-
//
|
|
83
|
-
const mediaQueryStyles = useMemo(() =>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
64
|
+
// 媒体查询边界样式(同样应用缩放修正,与 JS 计算保持一致)
|
|
65
|
+
const mediaQueryStyles = useMemo(() => {
|
|
66
|
+
const correct = (size) => scaleFactor === 1
|
|
67
|
+
? size
|
|
68
|
+
: Math.round((size / scaleFactor) * 10) / 10;
|
|
69
|
+
return {
|
|
70
|
+
[`@media (min-width: ${maxDocWidth}px)`]: {
|
|
71
|
+
html: {
|
|
72
|
+
fontSize: `${correct((100 * maxDocWidth) / designWidth)}px`,
|
|
73
|
+
},
|
|
87
74
|
},
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
75
|
+
[`@media (max-width: ${minDocWidth}px)`]: {
|
|
76
|
+
html: {
|
|
77
|
+
fontSize: `${correct((100 * minDocWidth) / designWidth)}px`,
|
|
78
|
+
},
|
|
92
79
|
},
|
|
93
|
-
}
|
|
94
|
-
}
|
|
80
|
+
};
|
|
81
|
+
}, [designWidth, minDocWidth, maxDocWidth, scaleFactor]);
|
|
95
82
|
return (_jsxs(React.Fragment, { children: [_jsx(Global, { styles: [
|
|
96
83
|
Object.assign({ "*": {
|
|
97
84
|
boxSizing: "border-box",
|
|
@@ -99,7 +86,7 @@ export function Container(props) {
|
|
|
99
86
|
WebkitTapHighlightColor: "transparent",
|
|
100
87
|
WebkitOverflowScrolling: "touch",
|
|
101
88
|
WebkitTextSizeAdjust: "100%",
|
|
102
|
-
fontSize: `${
|
|
89
|
+
fontSize: `${correctedFontSize}px`,
|
|
103
90
|
touchAction: "manipulation",
|
|
104
91
|
}, body: {
|
|
105
92
|
fontSize: "16px",
|
package/build/Dialog/style.js
CHANGED
|
@@ -117,7 +117,7 @@ export function getAnimation(type, status) {
|
|
|
117
117
|
return {
|
|
118
118
|
keyframes,
|
|
119
119
|
animation: css({
|
|
120
|
-
animation: `${keyframes} 300ms ease`,
|
|
120
|
+
animation: `${keyframes} 300ms ease forwards`,
|
|
121
121
|
}),
|
|
122
122
|
};
|
|
123
123
|
}
|
|
@@ -126,7 +126,7 @@ export const style = {
|
|
|
126
126
|
animation: `${maskShow} 300ms ease`,
|
|
127
127
|
}),
|
|
128
128
|
maskHide: css({
|
|
129
|
-
animation: `${maskHide} 300ms ease`,
|
|
129
|
+
animation: `${maskHide} 300ms ease forwards`,
|
|
130
130
|
}),
|
|
131
131
|
mask: css({
|
|
132
132
|
zIndex: 1,
|
package/build/Overlay/index.js
CHANGED
|
@@ -10,7 +10,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx } from "@emotion/react/jsx-runtime";
|
|
13
|
-
import {
|
|
13
|
+
import { useLayoutEffect, useMemo, useState } from "react";
|
|
14
14
|
import { createPortal } from "react-dom";
|
|
15
15
|
import { getContextValue } from "../context";
|
|
16
16
|
import { useWindowResize } from "../Effect/useWindowResize";
|
|
@@ -23,13 +23,6 @@ export function Overlay(props) {
|
|
|
23
23
|
const { children, outside = false, centerContent = true, fullScreen = true, maskColor = "rgba(0, 0, 0, .6)" } = props, extra = __rest(props, ["children", "outside", "centerContent", "fullScreen", "maskColor"]);
|
|
24
24
|
const [mount, setMount] = useState(null);
|
|
25
25
|
const [innerWidth, setInnerWidth] = useState(window.innerWidth);
|
|
26
|
-
// 这里是为了修复一个非挂载状态触发resize事件的bug
|
|
27
|
-
const isUnmount = useRef(false);
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
return () => {
|
|
30
|
-
isUnmount.current = true;
|
|
31
|
-
};
|
|
32
|
-
}, []);
|
|
33
26
|
useLayoutEffect(() => {
|
|
34
27
|
if (outside) {
|
|
35
28
|
const div = document.createElement("div");
|
|
@@ -42,9 +35,7 @@ export function Overlay(props) {
|
|
|
42
35
|
}, [outside]);
|
|
43
36
|
// 页面大小变化时,innerWidth 也会更新
|
|
44
37
|
useWindowResize(() => {
|
|
45
|
-
|
|
46
|
-
setInnerWidth(window.innerWidth);
|
|
47
|
-
}
|
|
38
|
+
setInnerWidth(window.innerWidth);
|
|
48
39
|
});
|
|
49
40
|
const ctx = getContextValue();
|
|
50
41
|
// 使用 useMemo 缓存样式计算,避免每次渲染都重新计算
|
|
@@ -10,7 +10,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
|
|
13
|
-
import { useCallback, useLayoutEffect, useRef, useState } from "react";
|
|
13
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
14
14
|
import { Indicator } from "../Indicator";
|
|
15
15
|
import { RowCenter } from "../Flex/Row";
|
|
16
16
|
import { style } from "./style";
|
|
@@ -31,19 +31,22 @@ export function ScrollView(props) {
|
|
|
31
31
|
// 节流控制
|
|
32
32
|
const throttleTimer = useRef(undefined);
|
|
33
33
|
const lastCallTime = useRef(0);
|
|
34
|
-
// 使用 ref
|
|
35
|
-
const
|
|
34
|
+
// 使用 ref 保存所有滚动处理需要的 props,彻底消除陈旧闭包
|
|
35
|
+
const propsRef = useRef({
|
|
36
36
|
onScroll,
|
|
37
37
|
onReachTop,
|
|
38
38
|
onReachBottom,
|
|
39
|
+
reachTopThreshold,
|
|
40
|
+
reachBottomThreshold,
|
|
39
41
|
});
|
|
40
|
-
|
|
41
|
-
callbacksRef.current = {
|
|
42
|
+
propsRef.current = {
|
|
42
43
|
onScroll,
|
|
43
44
|
onReachTop,
|
|
44
45
|
onReachBottom,
|
|
46
|
+
reachTopThreshold,
|
|
47
|
+
reachBottomThreshold,
|
|
45
48
|
};
|
|
46
|
-
// container是否有滚动条
|
|
49
|
+
// container 是否有滚动条
|
|
47
50
|
const [hasScrollBar, setHasScrollBar] = useState(false);
|
|
48
51
|
// 检查是否有滚动条
|
|
49
52
|
const checkScrollBar = useCallback(() => {
|
|
@@ -72,41 +75,24 @@ export function ScrollView(props) {
|
|
|
72
75
|
resizeObserver.disconnect();
|
|
73
76
|
};
|
|
74
77
|
}, [checkScrollBar]);
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// 节流控制
|
|
79
|
-
if (scrollThrottle > 0 && now - lastCallTime.current < scrollThrottle) {
|
|
80
|
-
// 清除之前的定时器
|
|
81
|
-
if (throttleTimer.current) {
|
|
82
|
-
clearTimeout(throttleTimer.current);
|
|
83
|
-
}
|
|
84
|
-
// 设置新的定时器,确保最后一次调用会被执行
|
|
85
|
-
throttleTimer.current = window.setTimeout(() => {
|
|
86
|
-
handleScroll(rawEvent);
|
|
87
|
-
}, scrollThrottle);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
lastCallTime.current = now;
|
|
91
|
-
handleScroll(rawEvent);
|
|
92
|
-
}, [scrollThrottle, reachTopThreshold, reachBottomThreshold]);
|
|
93
|
-
// 实际的滚动处理逻辑
|
|
94
|
-
const handleScroll = useCallback((rawEvent) => {
|
|
95
|
-
var _a, _b, _c, _d, _e, _f;
|
|
78
|
+
// 核心滚动处理逻辑
|
|
79
|
+
// 所有外部值从 ref 读取,deps 为空,引用永远稳定,不存在闭包过期问题
|
|
80
|
+
const processScroll = useCallback((rawEvent) => {
|
|
96
81
|
const box = container.current;
|
|
97
82
|
if (!box)
|
|
98
83
|
return;
|
|
99
|
-
|
|
84
|
+
const { onScroll, onReachTop, onReachBottom, reachTopThreshold, reachBottomThreshold, } = propsRef.current;
|
|
100
85
|
const scrollTop = box.scrollTop;
|
|
101
|
-
// 滚动容器的包含滚动内容的高度
|
|
102
86
|
const contentHeight = box.scrollHeight;
|
|
103
|
-
//
|
|
104
|
-
const containerHeight =
|
|
105
|
-
// 最大可滚动距离
|
|
87
|
+
// clientHeight 即可视区域高度(不含 border),无需 Math.min(clientHeight, offsetHeight)
|
|
88
|
+
const containerHeight = box.clientHeight;
|
|
106
89
|
const maxScroll = contentHeight - containerHeight;
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
90
|
+
// 防止零位移时误判方向(如内容变化触发的 scroll 事件)
|
|
91
|
+
if (scrollTop === lastScrollTop.current && lastScrollTop.current !== 0) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// scrollTop 增大 => 向下滚动;相等(初始 0→0)视为向下
|
|
95
|
+
const direction = scrollTop >= lastScrollTop.current ? "downward" : "upward";
|
|
110
96
|
const event = {
|
|
111
97
|
containerHeight,
|
|
112
98
|
contentHeight,
|
|
@@ -115,42 +101,74 @@ export function ScrollView(props) {
|
|
|
115
101
|
direction,
|
|
116
102
|
rawEvent,
|
|
117
103
|
};
|
|
118
|
-
|
|
119
|
-
(_b = (_a = callbacksRef.current).onScroll) === null || _b === void 0 ? void 0 : _b.call(_a, event);
|
|
104
|
+
onScroll === null || onScroll === void 0 ? void 0 : onScroll(event);
|
|
120
105
|
// 触顶逻辑(防止重复触发)
|
|
121
106
|
if (direction === "upward" && scrollTop <= reachTopThreshold) {
|
|
122
107
|
if (!hasReachedTop.current) {
|
|
123
108
|
hasReachedTop.current = true;
|
|
124
|
-
hasReachedBottom.current = false;
|
|
125
|
-
|
|
109
|
+
hasReachedBottom.current = false;
|
|
110
|
+
onReachTop === null || onReachTop === void 0 ? void 0 : onReachTop(event);
|
|
126
111
|
}
|
|
127
112
|
}
|
|
128
113
|
else if (scrollTop > reachTopThreshold) {
|
|
129
114
|
hasReachedTop.current = false;
|
|
130
115
|
}
|
|
131
116
|
// 触底逻辑(防止重复触发)
|
|
132
|
-
if (direction === "downward" &&
|
|
117
|
+
if (direction === "downward" &&
|
|
118
|
+
maxScroll > 0 &&
|
|
119
|
+
scrollTop >= maxScroll - reachBottomThreshold) {
|
|
133
120
|
if (!hasReachedBottom.current) {
|
|
134
121
|
hasReachedBottom.current = true;
|
|
135
|
-
hasReachedTop.current = false;
|
|
136
|
-
|
|
122
|
+
hasReachedTop.current = false;
|
|
123
|
+
onReachBottom === null || onReachBottom === void 0 ? void 0 : onReachBottom(event);
|
|
137
124
|
}
|
|
138
125
|
}
|
|
139
126
|
else if (scrollTop < maxScroll - reachBottomThreshold) {
|
|
140
127
|
hasReachedBottom.current = false;
|
|
141
128
|
}
|
|
142
|
-
// 更新scrollTop上次的值
|
|
143
129
|
lastScrollTop.current = scrollTop;
|
|
144
|
-
}, [
|
|
130
|
+
}, []);
|
|
131
|
+
// 节流滚动回调(leading + trailing)
|
|
132
|
+
const scrollCallback = useCallback((rawEvent) => {
|
|
133
|
+
// 不节流时直接执行
|
|
134
|
+
if (scrollThrottle <= 0) {
|
|
135
|
+
processScroll(rawEvent);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
const elapsed = now - lastCallTime.current;
|
|
140
|
+
if (elapsed >= scrollThrottle) {
|
|
141
|
+
// 前沿立即执行
|
|
142
|
+
lastCallTime.current = now;
|
|
143
|
+
processScroll(rawEvent);
|
|
144
|
+
// 消除挂起的尾部定时器
|
|
145
|
+
if (throttleTimer.current !== undefined) {
|
|
146
|
+
clearTimeout(throttleTimer.current);
|
|
147
|
+
throttleTimer.current = undefined;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// 尾部调用:按剩余时间调度,保证滚动停止后最终状态被处理
|
|
152
|
+
if (throttleTimer.current !== undefined) {
|
|
153
|
+
clearTimeout(throttleTimer.current);
|
|
154
|
+
}
|
|
155
|
+
throttleTimer.current = window.setTimeout(() => {
|
|
156
|
+
lastCallTime.current = Date.now();
|
|
157
|
+
throttleTimer.current = undefined;
|
|
158
|
+
// 尾部调用不传 rawEvent(已过期),processScroll 从 DOM 读取实时位置
|
|
159
|
+
processScroll();
|
|
160
|
+
}, scrollThrottle - elapsed);
|
|
161
|
+
}
|
|
162
|
+
}, [scrollThrottle, processScroll]);
|
|
145
163
|
// 清理节流定时器
|
|
146
|
-
|
|
164
|
+
useEffect(() => {
|
|
147
165
|
return () => {
|
|
148
|
-
if (throttleTimer.current) {
|
|
166
|
+
if (throttleTimer.current !== undefined) {
|
|
149
167
|
clearTimeout(throttleTimer.current);
|
|
150
168
|
}
|
|
151
169
|
};
|
|
152
170
|
}, []);
|
|
153
|
-
// loading内容
|
|
171
|
+
// loading 内容
|
|
154
172
|
let showLoadingContent = null;
|
|
155
173
|
if (showLoading) {
|
|
156
174
|
if (!loadingContent) {
|
package/build/Toast/index.js
CHANGED
|
@@ -26,11 +26,13 @@ export function showToast(option) {
|
|
|
26
26
|
*/
|
|
27
27
|
let portalDOM = null;
|
|
28
28
|
export function showUniqToast(option) {
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
// 先清理上一个 Toast 的 DOM 容器,避免快速连续调用时旧容器泄漏
|
|
30
|
+
if (portalDOM) {
|
|
31
|
+
portalDOM.unmount();
|
|
32
|
+
portalDOM = null;
|
|
31
33
|
}
|
|
34
|
+
portalDOM = createPortalDOM();
|
|
32
35
|
let props = {};
|
|
33
|
-
// 默认Toast是唯一的
|
|
34
36
|
if (React.isValidElement(option) || typeof option !== 'object') {
|
|
35
37
|
props.content = option;
|
|
36
38
|
}
|
package/build/utils/Countdown.js
CHANGED
|
@@ -14,8 +14,14 @@ export class Countdown {
|
|
|
14
14
|
* s:秒
|
|
15
15
|
*/
|
|
16
16
|
this.format = ['d', 'h', 'i', 's'];
|
|
17
|
-
if (typeof option.remain === '
|
|
18
|
-
|
|
17
|
+
if (typeof option.remain === 'string') {
|
|
18
|
+
const parsed = parseFloat(option.remain);
|
|
19
|
+
if (!isNaN(parsed) && parsed >= 0) {
|
|
20
|
+
this.total = this.remain = Math.floor(parsed);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else if (typeof option.remain === 'number' && option.remain >= 0) {
|
|
24
|
+
this.total = this.remain = Math.floor(option.remain);
|
|
19
25
|
}
|
|
20
26
|
// 倒计时需要展示的时间格式
|
|
21
27
|
if (typeof option.format === 'string') {
|
|
@@ -75,11 +81,12 @@ export class Countdown {
|
|
|
75
81
|
}
|
|
76
82
|
}, 1000); // ← 添加 1000ms 间隔
|
|
77
83
|
}
|
|
78
|
-
//
|
|
84
|
+
// 停止倒计时(暂停并保留当前剩余时间,可再次 start 恢复)
|
|
79
85
|
stop() {
|
|
80
86
|
var _a;
|
|
81
|
-
this.total = this.remain;
|
|
82
87
|
(_a = this._stopTick) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
88
|
+
this._stopTick = undefined;
|
|
89
|
+
this.total = this.remain;
|
|
83
90
|
}
|
|
84
91
|
/**
|
|
85
92
|
* 格式化每次更新的值
|
package/build/utils/ago.js
CHANGED
|
@@ -6,80 +6,67 @@ import dayjs from 'dayjs';
|
|
|
6
6
|
export function ago(date) {
|
|
7
7
|
const now = dayjs();
|
|
8
8
|
const input = dayjs(date);
|
|
9
|
-
|
|
10
|
-
const aMonthAgo = now.subtract(1, 'month');
|
|
11
|
-
const aDayAgo = now.subtract(1, 'day');
|
|
12
|
-
const aHourAgo = now.subtract(1, 'hour');
|
|
13
|
-
const aMinuteAgo = now.subtract(1, 'minute');
|
|
14
|
-
// 多少年前
|
|
15
|
-
if (input.isBefore(aYearAgo)) {
|
|
16
|
-
const diff = now.year() - input.year();
|
|
17
|
-
const nYearsAgo = now.subtract(diff, 'year');
|
|
18
|
-
let showNum = diff;
|
|
19
|
-
if (input.isAfter(nYearsAgo)) {
|
|
20
|
-
showNum = diff - 1;
|
|
21
|
-
}
|
|
9
|
+
if (!input.isValid()) {
|
|
22
10
|
return {
|
|
23
|
-
num:
|
|
11
|
+
num: 0,
|
|
12
|
+
unit: 's',
|
|
13
|
+
format: '刚刚',
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const isFuture = input.isAfter(now);
|
|
17
|
+
const from = isFuture ? now : input;
|
|
18
|
+
const to = isFuture ? input : now;
|
|
19
|
+
const years = to.diff(from, 'year');
|
|
20
|
+
if (years >= 1) {
|
|
21
|
+
return {
|
|
22
|
+
num: years,
|
|
24
23
|
unit: 'y',
|
|
25
|
-
format: `${
|
|
24
|
+
format: `${years}年${isFuture ? '后' : '前'}`,
|
|
26
25
|
};
|
|
27
26
|
}
|
|
28
|
-
|
|
29
|
-
if (
|
|
30
|
-
let showNum = 1;
|
|
31
|
-
for (let n = 2; n <= 12; n++) {
|
|
32
|
-
const nMonthAgo = now.subtract(n, 'month');
|
|
33
|
-
if (input.isAfter(nMonthAgo)) {
|
|
34
|
-
showNum = n - 1;
|
|
35
|
-
break;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
27
|
+
const months = to.diff(from, 'month');
|
|
28
|
+
if (months >= 1) {
|
|
38
29
|
return {
|
|
39
|
-
num:
|
|
30
|
+
num: months,
|
|
40
31
|
unit: 'm',
|
|
41
|
-
format: `${
|
|
32
|
+
format: `${months}个月${isFuture ? '后' : '前'}`,
|
|
42
33
|
};
|
|
43
34
|
}
|
|
44
|
-
|
|
45
|
-
if (
|
|
46
|
-
const showNum = Math.floor((now.unix() - input.unix()) / 86400);
|
|
35
|
+
const days = to.diff(from, 'day');
|
|
36
|
+
if (days >= 1) {
|
|
47
37
|
return {
|
|
48
|
-
num:
|
|
38
|
+
num: days,
|
|
49
39
|
unit: 'd',
|
|
50
|
-
format: `${
|
|
40
|
+
format: `${days}天${isFuture ? '后' : '前'}`,
|
|
51
41
|
};
|
|
52
42
|
}
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
const showNum = Math.floor((now.unix() - input.unix()) / 3600);
|
|
43
|
+
const hours = to.diff(from, 'hour');
|
|
44
|
+
if (hours >= 1) {
|
|
56
45
|
return {
|
|
57
|
-
num:
|
|
46
|
+
num: hours,
|
|
58
47
|
unit: 'h',
|
|
59
|
-
format: `${
|
|
48
|
+
format: `${hours}小时${isFuture ? '后' : '前'}`,
|
|
60
49
|
};
|
|
61
50
|
}
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
const showNum = Math.floor((now.unix() - input.unix()) / 60);
|
|
51
|
+
const minutes = to.diff(from, 'minute');
|
|
52
|
+
if (minutes >= 1) {
|
|
65
53
|
return {
|
|
66
|
-
num:
|
|
54
|
+
num: minutes,
|
|
67
55
|
unit: 'i',
|
|
68
|
-
format: `${
|
|
56
|
+
format: `${minutes}分钟${isFuture ? '后' : '前'}`,
|
|
69
57
|
};
|
|
70
58
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
format = '刚刚';
|
|
59
|
+
const seconds = to.diff(from, 'second');
|
|
60
|
+
if (seconds < 10) {
|
|
61
|
+
return {
|
|
62
|
+
num: seconds,
|
|
63
|
+
unit: 's',
|
|
64
|
+
format: isFuture ? '马上' : '刚刚',
|
|
65
|
+
};
|
|
79
66
|
}
|
|
80
67
|
return {
|
|
81
|
-
num:
|
|
68
|
+
num: seconds,
|
|
82
69
|
unit: 's',
|
|
83
|
-
format
|
|
70
|
+
format: `${seconds}秒${isFuture ? '后' : '前'}`,
|
|
84
71
|
};
|
|
85
72
|
}
|
package/build/utils/createApp.js
CHANGED
|
@@ -79,23 +79,38 @@ export function createApp(option) {
|
|
|
79
79
|
yield (onBefore === null || onBefore === void 0 ? void 0 : onBefore(normalizedPath));
|
|
80
80
|
// 加载并显示页面
|
|
81
81
|
if (typeof render === "function") {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
try {
|
|
83
|
+
const pageContent = yield render(normalizedPath);
|
|
84
|
+
// 如果返回 null/undefined,视为页面未找到
|
|
85
|
+
if (pageContent === null || pageContent === undefined) {
|
|
86
|
+
if (typeof notFound === "function") {
|
|
87
|
+
setPage(yield notFound(normalizedPath));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// 默认 404 页面
|
|
91
|
+
setPage(_jsxs("div", { children: ["Not Found: ", normalizedPath] }));
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
setPage(pageContent);
|
|
96
|
+
}
|
|
97
|
+
catch (_a) {
|
|
98
|
+
// 动态 import 失败等场景
|
|
85
99
|
if (typeof notFound === "function") {
|
|
86
100
|
setPage(yield notFound(normalizedPath));
|
|
87
101
|
}
|
|
88
102
|
else {
|
|
89
|
-
// 默认 404 页面
|
|
90
103
|
setPage(_jsxs("div", { children: ["Not Found: ", normalizedPath] }));
|
|
91
104
|
}
|
|
92
105
|
return;
|
|
93
106
|
}
|
|
94
|
-
setPage(pageContent);
|
|
95
107
|
}
|
|
96
108
|
// 页面加载后钩子
|
|
97
109
|
yield (onAfter === null || onAfter === void 0 ? void 0 : onAfter(normalizedPath));
|
|
98
|
-
}),
|
|
110
|
+
}),
|
|
111
|
+
// 所有外部变量在闭包创建时已捕获,不会变化
|
|
112
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
113
|
+
[]);
|
|
99
114
|
/**
|
|
100
115
|
* 监听路由变化
|
|
101
116
|
*/
|
|
@@ -119,8 +134,8 @@ export function createApp(option) {
|
|
|
119
134
|
else if (option.target instanceof HTMLElement) {
|
|
120
135
|
mount = option.target;
|
|
121
136
|
}
|
|
122
|
-
|
|
123
|
-
throw new Error("
|
|
137
|
+
if (!mount) {
|
|
138
|
+
throw new Error(`Mount target not found: ${typeof option.target === "string" ? option.target : "invalid element"}`);
|
|
124
139
|
}
|
|
125
140
|
const root = createRoot(mount);
|
|
126
141
|
root.render(_jsx(App, {}));
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 检测是否支持passive事件绑定
|
|
3
|
-
*/
|
|
4
|
-
export declare let passiveSupported: boolean;
|
|
5
1
|
/**
|
|
6
2
|
* 禁用和启用默认滚动
|
|
3
|
+
*
|
|
4
|
+
* 注意:现代浏览器将 document/documentElement 上的 touchmove 监听器
|
|
5
|
+
* 默认视为 passive: true,此时 preventDefault() 会静默失效。
|
|
6
|
+
* 因此必须显式声明 passive: false 才能真正阻止默认滚动行为。
|
|
7
7
|
*/
|
|
8
8
|
export declare const defaultScroll: {
|
|
9
9
|
disable(): void;
|
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 检测是否支持passive事件绑定
|
|
3
|
-
*/
|
|
4
|
-
export let passiveSupported = false;
|
|
5
|
-
try {
|
|
6
|
-
window.addEventListener('test', () => undefined, Object.defineProperty({}, 'passive', {
|
|
7
|
-
get: function () {
|
|
8
|
-
passiveSupported = true;
|
|
9
|
-
},
|
|
10
|
-
}));
|
|
11
|
-
// eslint-disable-next-line no-empty
|
|
12
|
-
}
|
|
13
|
-
catch (err) { }
|
|
14
1
|
/**
|
|
15
2
|
* 触摸移动事件处理器
|
|
16
3
|
*/
|
|
@@ -19,12 +6,16 @@ const touchMoveHandler = (event) => {
|
|
|
19
6
|
};
|
|
20
7
|
/**
|
|
21
8
|
* 禁用和启用默认滚动
|
|
9
|
+
*
|
|
10
|
+
* 注意:现代浏览器将 document/documentElement 上的 touchmove 监听器
|
|
11
|
+
* 默认视为 passive: true,此时 preventDefault() 会静默失效。
|
|
12
|
+
* 因此必须显式声明 passive: false 才能真正阻止默认滚动行为。
|
|
22
13
|
*/
|
|
23
14
|
export const defaultScroll = {
|
|
24
15
|
disable() {
|
|
25
|
-
document.documentElement.addEventListener('touchmove', touchMoveHandler,
|
|
16
|
+
document.documentElement.addEventListener('touchmove', touchMoveHandler, { capture: false, passive: false });
|
|
26
17
|
},
|
|
27
18
|
enable() {
|
|
28
|
-
document.documentElement.removeEventListener('touchmove', touchMoveHandler,
|
|
19
|
+
document.documentElement.removeEventListener('touchmove', touchMoveHandler, { capture: false });
|
|
29
20
|
},
|
|
30
21
|
};
|
package/build/utils/dom.js
CHANGED
|
@@ -20,15 +20,12 @@ export function createPortalDOM(point) {
|
|
|
20
20
|
root.render(component);
|
|
21
21
|
},
|
|
22
22
|
unmount() {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
mountPoint.removeChild(container);
|
|
30
|
-
}
|
|
23
|
+
// 先从 DOM 移除容器,再卸载 React 树
|
|
24
|
+
// 避免 React 18+ 在已卸载的根上发出警告
|
|
25
|
+
if (container.parentNode) {
|
|
26
|
+
container.parentNode.removeChild(container);
|
|
31
27
|
}
|
|
28
|
+
root.unmount();
|
|
32
29
|
},
|
|
33
30
|
};
|
|
34
31
|
}
|
package/build/utils/jsonp.js
CHANGED
|
@@ -25,20 +25,23 @@ export function jsonp(url_1) {
|
|
|
25
25
|
}
|
|
26
26
|
const urlObject = new URL(url);
|
|
27
27
|
urlObject.searchParams.set(callbackName, funcName);
|
|
28
|
+
// 清理辅助函数
|
|
29
|
+
const cleanup = () => {
|
|
30
|
+
delete window[funcName];
|
|
31
|
+
script.remove();
|
|
32
|
+
};
|
|
28
33
|
// 创建全局script
|
|
29
34
|
const script = document.createElement('script');
|
|
30
35
|
script.src = urlObject.href;
|
|
31
36
|
document.body.appendChild(script);
|
|
32
37
|
script.onerror = (error) => {
|
|
38
|
+
cleanup();
|
|
33
39
|
reject(error);
|
|
34
40
|
};
|
|
35
41
|
// 创建全局函数
|
|
36
42
|
window[funcName] = (result) => {
|
|
43
|
+
cleanup();
|
|
37
44
|
resolve(result);
|
|
38
|
-
// 删除全局函数
|
|
39
|
-
delete window[funcName];
|
|
40
|
-
// 删除临时脚本
|
|
41
|
-
script.remove();
|
|
42
45
|
};
|
|
43
46
|
});
|
|
44
47
|
});
|
package/build/utils/request.js
CHANGED
|
@@ -210,42 +210,32 @@ export function sendRequest(option) {
|
|
|
210
210
|
return __awaiter(this, void 0, void 0, function* () {
|
|
211
211
|
const { url, fetchOption, timeout } = parseRequestOption(option);
|
|
212
212
|
const controller = new AbortController();
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (error.name === 'AbortError') {
|
|
225
|
-
const result = {
|
|
226
|
-
code: -10001,
|
|
227
|
-
message: "Network request timeout",
|
|
228
|
-
};
|
|
229
|
-
return result;
|
|
230
|
-
}
|
|
213
|
+
// 超时定时器,请求完成后清除以防泄漏
|
|
214
|
+
const timeoutId = window.setTimeout(() => {
|
|
215
|
+
controller.abort();
|
|
216
|
+
}, timeout !== null && timeout !== void 0 ? timeout : 30000);
|
|
217
|
+
try {
|
|
218
|
+
const response = yield fetch(url, Object.assign(Object.assign({}, fetchOption), { signal: controller.signal }));
|
|
219
|
+
const result = yield response.json();
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
if (error.name === 'AbortError') {
|
|
231
224
|
const result = {
|
|
232
|
-
code: -
|
|
233
|
-
message: "
|
|
225
|
+
code: -10001,
|
|
226
|
+
message: "Network request timeout",
|
|
234
227
|
};
|
|
235
228
|
return result;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}, timeout !== null && timeout !== void 0 ? timeout : 30000);
|
|
247
|
-
}),
|
|
248
|
-
]);
|
|
229
|
+
}
|
|
230
|
+
const result = {
|
|
231
|
+
code: -10000,
|
|
232
|
+
message: "An exception occurred in the network request",
|
|
233
|
+
};
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
clearTimeout(timeoutId);
|
|
238
|
+
}
|
|
249
239
|
});
|
|
250
240
|
}
|
|
251
241
|
/**
|
package/build/utils/tick.js
CHANGED
|
@@ -4,42 +4,35 @@
|
|
|
4
4
|
* @param interval
|
|
5
5
|
*/
|
|
6
6
|
export function tick(callback, interval) {
|
|
7
|
-
// 执行状态,是否正在执行
|
|
8
7
|
let isRunning;
|
|
9
8
|
let frame;
|
|
10
9
|
let frameId;
|
|
11
|
-
//
|
|
12
|
-
if (
|
|
10
|
+
// 有效的正整数间隔才走间隔分支
|
|
11
|
+
if (typeof interval === 'number' && interval > 0) {
|
|
13
12
|
let lastTick = Date.now();
|
|
14
13
|
frame = () => {
|
|
15
|
-
if (!isRunning)
|
|
14
|
+
if (!isRunning)
|
|
16
15
|
return;
|
|
17
|
-
}
|
|
18
16
|
frameId = requestAnimationFrame(frame);
|
|
19
17
|
const now = Date.now();
|
|
20
|
-
// 每次间隔频率逻辑上保持一致,即使帧频不一致
|
|
21
18
|
if (now - lastTick >= interval) {
|
|
22
|
-
//
|
|
23
|
-
lastTick =
|
|
19
|
+
// 直接对齐到当前时间,避免长时间后台切回后的追赶风暴
|
|
20
|
+
lastTick = now;
|
|
24
21
|
callback();
|
|
25
22
|
}
|
|
26
23
|
};
|
|
27
24
|
}
|
|
28
|
-
// 没有设置tick的间隔
|
|
29
25
|
else {
|
|
26
|
+
// 没有设置 interval 或 interval <= 0 时,每帧执行
|
|
30
27
|
frame = () => {
|
|
31
|
-
if (!isRunning)
|
|
28
|
+
if (!isRunning)
|
|
32
29
|
return;
|
|
33
|
-
}
|
|
34
30
|
frameId = requestAnimationFrame(frame);
|
|
35
|
-
// 没有设置interval时,每帧都执行
|
|
36
31
|
callback();
|
|
37
32
|
};
|
|
38
33
|
}
|
|
39
|
-
// 开始执行
|
|
40
34
|
isRunning = true;
|
|
41
35
|
frameId = requestAnimationFrame(frame);
|
|
42
|
-
// 返回一个可以立即停止的函数
|
|
43
36
|
return () => {
|
|
44
37
|
isRunning = false;
|
|
45
38
|
cancelAnimationFrame(frameId);
|
package/build/utils/uniqKey.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
let keyIndex = 0;
|
|
2
|
-
let
|
|
2
|
+
let lastTimestamp = 0;
|
|
3
3
|
/**
|
|
4
4
|
* 生成一个全局唯一的key
|
|
5
5
|
* @returns
|
|
6
6
|
*/
|
|
7
7
|
export function uniqKey() {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (now !==
|
|
8
|
+
const now = Date.now();
|
|
9
|
+
// 时间戳变化时重置计数器
|
|
10
|
+
if (now !== lastTimestamp) {
|
|
11
11
|
keyIndex = 0;
|
|
12
|
+
lastTimestamp = now;
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return key;
|
|
14
|
+
keyIndex += 1;
|
|
15
|
+
return now.toString(36) + keyIndex.toString(36);
|
|
16
16
|
}
|
package/build/utils/wait.js
CHANGED
|
@@ -31,13 +31,13 @@ export function waitUntil(condition, maxTime) {
|
|
|
31
31
|
return new Promise((resolve) => {
|
|
32
32
|
const stop = tick(() => {
|
|
33
33
|
const now = Date.now();
|
|
34
|
-
const result = condition();
|
|
35
34
|
// 超时返回false
|
|
36
35
|
if (now - start >= maxTime) {
|
|
37
36
|
stop();
|
|
38
37
|
resolve(false);
|
|
39
38
|
return;
|
|
40
39
|
}
|
|
40
|
+
const result = condition();
|
|
41
41
|
// 处理结果
|
|
42
42
|
const handle = (res) => {
|
|
43
43
|
if (res) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clxx",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.5",
|
|
4
4
|
"description": "Basic JS library for mobile devices",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"module": "./build/index.js",
|
|
@@ -35,20 +35,20 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@emotion/react": "^11.14.0",
|
|
38
|
-
"dayjs": "^1.11.
|
|
38
|
+
"dayjs": "^1.11.19",
|
|
39
39
|
"history": "^5.3.0",
|
|
40
|
-
"lodash": "^4.17.
|
|
40
|
+
"lodash": "^4.17.23"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"react": "^19.2.
|
|
44
|
-
"react-dom": "^19.2.
|
|
43
|
+
"react": "^19.2.4",
|
|
44
|
+
"react-dom": "^19.2.4"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@types/lodash": "^4.17.
|
|
48
|
-
"@types/react": "^19.2.
|
|
49
|
-
"@types/react-dom": "^19.2.
|
|
50
|
-
"csstype": "^3.
|
|
51
|
-
"rimraf": "^6.
|
|
47
|
+
"@types/lodash": "^4.17.24",
|
|
48
|
+
"@types/react": "^19.2.14",
|
|
49
|
+
"@types/react-dom": "^19.2.3",
|
|
50
|
+
"csstype": "^3.2.3",
|
|
51
|
+
"rimraf": "^6.1.3",
|
|
52
52
|
"typescript": "^5.9.3"
|
|
53
53
|
}
|
|
54
54
|
}
|
package/test/eslint.config.js
CHANGED
|
@@ -10,12 +10,15 @@ export default defineConfig([
|
|
|
10
10
|
files: ['**/*.{js,jsx}'],
|
|
11
11
|
extends: [
|
|
12
12
|
js.configs.recommended,
|
|
13
|
-
reactHooks.configs['recommended-latest'],
|
|
13
|
+
reactHooks.configs.flat['recommended-latest'],
|
|
14
14
|
reactRefresh.configs.vite,
|
|
15
15
|
],
|
|
16
16
|
languageOptions: {
|
|
17
17
|
ecmaVersion: 2020,
|
|
18
|
-
globals:
|
|
18
|
+
globals: {
|
|
19
|
+
...globals.browser,
|
|
20
|
+
...globals.node,
|
|
21
|
+
},
|
|
19
22
|
parserOptions: {
|
|
20
23
|
ecmaVersion: 'latest',
|
|
21
24
|
ecmaFeatures: { jsx: true },
|
package/test/package.json
CHANGED
|
@@ -10,18 +10,18 @@
|
|
|
10
10
|
"preview": "vite preview"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"react": "^19.
|
|
14
|
-
"react-dom": "^19.
|
|
13
|
+
"react": "^19.2.4",
|
|
14
|
+
"react-dom": "^19.2.4"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@eslint/js": "^9.
|
|
18
|
-
"@types/react": "^19.
|
|
19
|
-
"@types/react-dom": "^19.
|
|
20
|
-
"@vitejs/plugin-react": "^5.
|
|
21
|
-
"eslint": "^9.
|
|
22
|
-
"eslint-plugin-react-hooks": "^
|
|
23
|
-
"eslint-plugin-react-refresh": "^0.
|
|
24
|
-
"globals": "^
|
|
25
|
-
"vite": "^7.1
|
|
17
|
+
"@eslint/js": "^9.39.3",
|
|
18
|
+
"@types/react": "^19.2.14",
|
|
19
|
+
"@types/react-dom": "^19.2.3",
|
|
20
|
+
"@vitejs/plugin-react": "^5.1.4",
|
|
21
|
+
"eslint": "^9.39.3",
|
|
22
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
23
|
+
"eslint-plugin-react-refresh": "^0.5.2",
|
|
24
|
+
"globals": "^17.4.0",
|
|
25
|
+
"vite": "^7.3.1"
|
|
26
26
|
}
|
|
27
27
|
}
|
package/test/src/ago/index.jsx
CHANGED
|
@@ -2,6 +2,8 @@ import React from "react";
|
|
|
2
2
|
import { Ago } from "@";
|
|
3
3
|
|
|
4
4
|
export default function Index() {
|
|
5
|
+
const now = 1583314718595;
|
|
6
|
+
|
|
5
7
|
return (
|
|
6
8
|
<div>
|
|
7
9
|
<p>2019-11-2</p>
|
|
@@ -17,7 +19,7 @@ export default function Index() {
|
|
|
17
19
|
<Ago date="2018/07/06 12:04:36" style={{ color: "red" }} />
|
|
18
20
|
<hr />
|
|
19
21
|
<p>Date.now()</p>
|
|
20
|
-
<Ago date={
|
|
22
|
+
<Ago date={now} style={{ color: "red" }} />
|
|
21
23
|
<hr />
|
|
22
24
|
<p>new Date("2012-07-16 12:30:06")</p>
|
|
23
25
|
<Ago date={new Date("2012-07-16 12:30:06")} style={{ color: "red" }} />
|
package/test/src/index.jsx
CHANGED
|
@@ -7,7 +7,8 @@ createApp({
|
|
|
7
7
|
target: "#root",
|
|
8
8
|
// maxDocWidth: 10000,
|
|
9
9
|
async render(pathname) {
|
|
10
|
-
|
|
10
|
+
const module = await import(`./${pathname}/index.jsx`);
|
|
11
|
+
const Page = module.default;
|
|
11
12
|
if (pathname === 'index') {
|
|
12
13
|
return <Home />;
|
|
13
14
|
}
|
|
@@ -23,7 +24,7 @@ createApp({
|
|
|
23
24
|
</button>
|
|
24
25
|
</div>
|
|
25
26
|
<div className="demo">
|
|
26
|
-
<
|
|
27
|
+
<Page />
|
|
27
28
|
</div>
|
|
28
29
|
</>
|
|
29
30
|
);
|